【深度学习】TensorFlow学习之路一
- 一、TensorFlow简介
- 二、用TensorFlow实现线性回归
- 三、用TensorFlow实现逻辑回归
- 四、模型存储
本系列文章主要是对OReilly的Hands-On Machine Learning with Scikit-learn and TensorFlow一书深度学习部分的阅读摘录和笔记。
一、TensorFlow简介
- TensorFlow是啥?
TensorFlow是一款强大的开源软件,用于数值计算,尤其适用于类似机器学习这样的大规模计算。 - TensorFlow计算流程是怎样的?
TensorFlow计算流程一般分两步:一搭建计算流程图;二执行计算。 - 啥是计算流程图?
比如我们想要计算如下公式:
以计算流程图的形式表达出来即如下图所示: - 用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实现线性回归
线性回归求解参数是有比较简洁的解析解形式的:
但这种方式求解通用性不强,如果解析解不易求得就无法复用。我们这里还是用梯度下降的方法来求解。
首先我们来整体回忆一下线性回归,其方程表达式为:
其中X为m行n+1列的一个数组,代表m个样本点,n个特征,最后加一列常数项;为一个(n+1)x1维的向量。
线性回归的目标函数我们通常用均方误差:
目标函数在第j个特征的系数上的梯度为:
所以,这个向量的梯度可以写成如下形式:
OK,得到了的梯度,之后就沿着梯度方向一点点下降就可以了,即:
下面我们来看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实现逻辑回归也非常容易,只需要更改一下目标函数即可,逻辑回归的目标函数如下:
代码如下:
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")