【深度学习】TensorFlow学习之路一

  • 一、TensorFlow简介
  • 二、用TensorFlow实现线性回归
  • 三、用TensorFlow实现逻辑回归
  • 四、模型存储



本系列文章主要是对OReilly的Hands-On Machine Learning with Scikit-learn and TensorFlow一书深度学习部分的阅读摘录和笔记。

一、TensorFlow简介

  • TensorFlow是啥?
    TensorFlow是一款强大的开源软件,用于数值计算,尤其适用于类似机器学习这样的大规模计算。
  • TensorFlow计算流程是怎样的?
    TensorFlow计算流程一般分两步:一搭建计算流程图;二执行计算。
  • 啥是计算流程图?
    比如我们想要计算如下公式:
    TensorFlow深度学习 深入理解人工智能算法设计 tensorflow online learning_python
    以计算流程图的形式表达出来即如下图所示:
  • TensorFlow深度学习 深入理解人工智能算法设计 tensorflow online learning_python_02

  • 用TensorFlow有啥优势?
    这样的计算流程其实用numpy或者pandas也可以实现,相比较而言,TensorFlow的优势就在于它会自动把流程拆分成多个进程并自动分布到GPU上进行计算。
  • 代码怎么写?
    1、搭建计算流程图:
import tensorflow as tf

x = tf.Variable(3, name="x")
y = tf.Variable(4, name="y")
f = x*x*y + y + 2

在这个阶段,虽然看似我们进行了运算,其实并没有,甚至连我们定义的变量还没有初始化呢。这个阶段仅仅是把如何计算的流程给设计定稿了。想要开始运算,就要把这个流程放到一个session里面去执行。
2、执行计算:

init = tf.global_variables_initializer() # 准备一个初始化节点
with tf.Session() as sess:
	init.run() # 在一个session里面初始化所有变量
	result = f.eval()
  • 计算图中节点变量的生命周期是怎样的?
    先看这样一个例子:
w = tf.constant(3)
x = w + 2
y = x + 5
z = x * 3
with tf.Session() as sess:
	print(y.eval()) # 10
	print(z.eval()) # 15

当我们首先计算y的时候,TensorFlow会检测到需要先计算x,而x依赖于w,所以y的计算需要先计算中间值x。当我们在计算z的时候,发现同样要先计算中间值x,但此时x这个中间值是不能直接复用算y时的中间结果的,也就是x要重新计算。
为什么会这样?因为TensorFlow会在每个计算图计算完成后,自动删除所有节点值,所以节点值的生命周期开始于计算图开始计算,结束于计算图算完。
而w就不一样了,我们已经把w定义成一个变量了,而变量的生命周期开始于你在session中初始化它,结束于session关闭。
所以想要在计算y和z时避免重复计算怎么办呢?答案是把y和z放到同一个计算图中去算:

with tf.Session() as sess:
	y_val, z_val = sess.run([y, z])
	print(y_val) # 10
	print(z_val) # 15

二、用TensorFlow实现线性回归

线性回归求解参数是有比较简洁的解析解形式的:
TensorFlow深度学习 深入理解人工智能算法设计 tensorflow online learning_深度学习_03
但这种方式求解通用性不强,如果解析解不易求得就无法复用。我们这里还是用梯度下降的方法来求解。
首先我们来整体回忆一下线性回归,其方程表达式为:
TensorFlow深度学习 深入理解人工智能算法设计 tensorflow online learning_机器学习_04
其中X为m行n+1列的一个数组,代表m个样本点,n个特征,最后加一列常数项;TensorFlow深度学习 深入理解人工智能算法设计 tensorflow online learning_tensorflow_05为一个(n+1)x1维的向量。
线性回归的目标函数我们通常用均方误差:
TensorFlow深度学习 深入理解人工智能算法设计 tensorflow online learning_机器学习_06
目标函数在第j个特征的系数TensorFlow深度学习 深入理解人工智能算法设计 tensorflow online learning_python_07上的梯度为:
TensorFlow深度学习 深入理解人工智能算法设计 tensorflow online learning_机器学习_08
所以,TensorFlow深度学习 深入理解人工智能算法设计 tensorflow online learning_tensorflow_05这个向量的梯度可以写成如下形式:
TensorFlow深度学习 深入理解人工智能算法设计 tensorflow online learning_深度学习_10
OK,得到了TensorFlow深度学习 深入理解人工智能算法设计 tensorflow online learning_tensorflow_05的梯度,之后就沿着梯度方向一点点下降就可以了,即:
TensorFlow深度学习 深入理解人工智能算法设计 tensorflow online learning_tensorflow_12
下面我们来看TensorFlow实现线性回归的代码:

import numpy as np
from sklearn.datasets import fetch_california_housing
housing = fetch_california_housing()
m, n = housing.data.shape

#做梯度下降之前,记得把训练数据X做一下标准化
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaled_housing_data = scaler.fit_transform(housing.data)

#在X数据的最后加一列1,用来拟合截距项
housing_data_plus_bias = np.c_[np.ones((m, 1)), scaled_housing_data]

n_epochs = 1000
learning_rate = 0.01

##########################
###1、计算流程图搭建阶段###
##########################
X = tf.constant(scaled_housing_data_plus_bias, dtype=tf.float32, name="X")
y = tf.constant(housing.target.reshape(-1, 1), dtype=tf.float32, name="y")
#随机初始化一个(n+1)x1维的theta,取值在-1到1之间
theta = tf.Variable(tf.random_uniform([n + 1, 1], -1.0, 1.0), name="theta")
#计算y_pred
y_pred = tf.matmul(X, theta, name="predictions")
#计算残差
error = y_pred - y
#计算目标函数:均方误差
mse = tf.reduce_mean(tf.square(error), name="mse")
#计算梯度
gradients = 2/m * tf.matmul(tf.transpose(X), error)
#沿着梯度逐步下降
training_op = tf.assign(theta, theta - learning_rate * gradients)

init = tf.global_variables_initializer()

####################
###2、执行计算阶段###
####################
with tf.Session() as sess:
	sess.run(init)
	for epoch in range(n_epochs):
		if epoch % 100 == 0:
			print("Epoch", epoch, "MSE =", mse.eval())
		sess.run(training_op)
	best_theta = theta.eval()

可以看到稍微复杂一点的地方就是对梯度的推导,好在像线性回归或逻辑回归都有比较简洁的梯度表达式;如果我们要解决的是深度神经网络问题,那梯度的求导会非常复杂,而且表达式也冗长易出错。为了解决这个问题,TensorFlow内置了一个自动求解梯度的函数:tf.gradients(),如此,我们只需要把上述代码中求解梯度那步换一下,就可以让TensorFlow自动计算梯度:

gradients = 2/m * tf.matmul(tf.transpose(X), error)
##替换为:
gradients = tf.gradients(mse, [theta])[0]

TensorFlow计算梯度使用的是反向自动差分方法(Reverse Mode Autodiff),依据求导里面的链式法则,从计算图的根节点出发,一步一步反向计算到变量节点;算法过程类似于深度网络里面的后向传播。
更进一步,TensorFlow对梯度下降算法其实也做了封装,代码可以进一步做如下修改:

gradients = 2/m * tf.matmul(tf.transpose(X), error)
training_op = tf.assign(theta, theta - learning_rate * gradients)
##替换为:
optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)
training_op = optimizer.minimize(mse)

如果我们想执行mini-batch梯度下降的话,只需要把X和Y更改为占位节点(Placeholder node),然后通过feed_dict参数每次将小批量数据喂给X和Y即可。

X = tf.placeholder(tf.float32, shape=(None, n + 1), name="X")
y = tf.placeholder(tf.float32, shape=(None, 1), name="y")

batch_size = 100
n_batches = int(np.ceil(m / batch_size))

def fetch_batch(epoch, batch_index, batch_size):
    np.random.seed(epoch * n_batches + batch_index)
    indices = np.random.randint(m, size=batch_size)
    X_batch = scaled_housing_data_plus_bias[indices]
    y_batch = housing.target.reshape(-1, 1)[indices]
	return X_batch, y_batch
	
with tf.Session() as sess:
	sess.run(init)
	for epoch in range(n_epochs):
		for batch_index in range(n_batches):
			X_batch, y_batch = fetch_batch(epoch, batch_index, batch_size)
			sess.run(training_op, feed_dict={X: X_batch, y: y_batch})

三、用TensorFlow实现逻辑回归

有了上面的代码,用TensorFlow实现逻辑回归也非常容易,只需要更改一下目标函数即可,逻辑回归的目标函数如下:
TensorFlow深度学习 深入理解人工智能算法设计 tensorflow online learning_线性回归_13
代码如下:

import numpy as np
from sklearn.datasets import load_breast_cancer
cancer= load_breast_cancer()
m, n = cancer.data.shape
#在X数据的最后加一列1,用来拟合截距项
cancer_data_plus_bias = np.c_[np.ones((m, 1)), cancer.data]

n_epochs = 1000
learning_rate = 0.01

##########################
###1、计算流程图搭建阶段###
##########################
X = tf.constant(cancer_data_plus_bias , dtype=tf.float32, name="X")
y = tf.constant(cancer.target.reshape(-1, 1), dtype=tf.float32, name="y")
#随机初始化一个(n+1)x1维的theta,取值在-1到1之间
theta = tf.Variable(tf.random_uniform([n + 1, 1], -1.0, 1.0), name="theta")

def logloss_func(X,y,theta):
	z = tf.matmul(X, theta)
	y_pred = tf.inv(tf.add(1, tf.exp(tf.neg(z))), name='predictions')
	epsilon = 1e-7
	logloss = -tf.reduce_mean(y * tf.log(y_pred + epsilon) + (1 - y) * tf.log(1 - y_pred + epsilon))

	return logloss

optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)
logloss = logloss_func(X,y,theta)
training_op = optimizer.minimize(logloss)

init = tf.global_variables_initializer()
####################
###2、执行计算阶段###
####################
with tf.Session() as sess:
	sess.run(init)
	for epoch in range(n_epochs):
		if epoch % 100 == 0:
			print("Epoch", epoch, "Logloss =", logloss.eval())
		sess.run(training_op)
	best_theta = theta.eval()

实际上,TensorFlow对sigmoid函数和对数损失函数都进行了封装,所以logloss_func函数可以简写如下:

def logloss_func(X,y,theta):
	z = tf.matmul(X, theta)
	y_pred = tf.sigmoid(z)
	logloss = tf.losses.log_loss(y, y_pred)

	return logloss

四、模型存储

模型结果存储下来,一方面是为了复用和多模型结果对比;另一方面,在训练过程中每隔几步存一下模型结果,也可以防止训练中途系统崩溃后一切从头开始。TensorFlow存储模型非常容易:创建一个Saver节点,然后通过save和restore方法来存储和载入模型:

#创建一个saver节点
saver = tf.train.Saver()
with tf.Session() as sess:
	sess.run(init)
	for epoch in range(n_epochs):
		if epoch % 100 == 0: # checkpoint every 100 epochs
			save_path = saver.save(sess, "/tmp/my_model.ckpt")
		sess.run(training_op)
	best_theta = theta.eval()
	save_path = saver.save(sess, "/tmp/my_model_final.ckpt")

with tf.Session() as sess:
	#再次载入模型时,用restore方法替换sess.run(init)
	saver.restore(sess, "/tmp/my_model_final.ckpt")