Tensorflow 体验篇-1 使用 Tensorflow 做线性回归
线性回归
线性回归是非常基础的统计学知识,也是所有机器学习研究的源头,因为现在的数学都还没有能很好的解决非线性的问题,所以基本上所有的机器学习的思路都是用非线性核函数,将数据转换到近似线性的空间中,然后再用线性方法如线性回归进行解决。一般的,线性回归是要解决如下的问题,对线性公式: $$ y = wx + b $$ 我们采集了大量 $x$ 和 $y$ 的值对,代表客观观察,现在想要还原他的参数 $w$ 和 $b$ ,从而还原出整个公式。但是由于实际的数据会有噪音,不妨假设这个噪音符合正态分布,则我们可以用如下公式表示这个真实公式: $$ y = wx + b + N(0,\epsilon) $$ 其中 $\epsilon$ 代表噪音的标准差。我们使用 NumPy 生成人造数据:
import numpy as np
N=100 # 100 个数据
w_true=5 # 假设 w = 5
b_true=5 # 假设 b = 5
noise_scale=.1
# 构建 Nx1 维的 x 数据
x_np=np.random.rand(N,1)
# 生成正态分布的 Nx1 维的噪音
noise = np.random.normal(scale=noise_scale,size=(N,1))
# 生成 y 数据,这里的 y 是 1xN 维的,方便计算
y_np=np.reshape(w_true*x_np+b_true+noise,(-1))
使用 matplotlib 画个图看一下生成的数据的分布,在 jupyter 中使用
%matplotlib inline
import matplotlib
import numpy as np
import matplotlib.pyplot as plt
line,=plt.plot(x_np,y_np,'bo')
结果如图所示
设置训练网络
机器学习的步骤最主要的就是3步:
- 定义损失函数,也就是如何评价当前参数的指标,一般来说损失函数的值越小,当前得到的参数性能越好
- 定义优化算法。常见的优化算法有梯度下降算法,退火算法和遗传算法。用于在当前参数下,快速找到下一批待选择参数组合
- 反复迭代,寻找最优解。利用上面两个步骤反复多次,直到函数收敛,得到最终的参数,也就是模型训练完成。
本次案例中,我们用最简单的方式来定义我们的训练网络。损失函数就直接使用预测值与真实值之间的空间距离: $$ loss = ( y_{pred} - y )^2 $$
而优化算法直接使用梯度下降算法: $$ W_{next} = W - \alpha\Delta W $$ 其中的 $\alpha$ 就是迭代步长,是一个超参数,需要手动选择或者使用网格搜索才能调优的参数。
具体的代码如下:
import tensorflow as tf
# 定义数据占位符
with tf.name_scope("placeholders"):
x = tf.placeholder(tf.float32, (N, 1))
y = tf.placeholder(tf.float32, (N,))
# 定义变量占位符
with tf.name_scope("weights"):
# 使用正态分布的初始化数据
W = tf.Variable(tf.random_normal((1, 1)))
b = tf.Variable(tf.random_normal((1,)))
# 定义计算函数
with tf.name_scope("prediction"):
y_pred = tf.matmul(x, W) + b
# 定义损失函数
with tf.name_scope("loss"):
l = tf.reduce_sum((y - tf.squeeze(y_pred))**2)
# 定义下降算法
with tf.name_scope("optim"):
# 使用默认的梯度下降算法, alpha = 0.001
train_op = tf.train.AdamOptimizer(.001).minimize(l)
# 定义统计项
with tf.name_scope("summaries"):
# 统计损失函数
tf.summary.scalar("loss", l)
# 将所有指标写入磁盘
merged = tf.summary.merge_all()
执行训练
我们将我们生成的模拟数据,填入到训练网络的占位符,多次迭代进行网络训练。每次迭代,都将中间结果写入磁盘,方便我们观察我们的网络的有效性。代码如下:
# 定义结果写入文件
train_writer = tf.summary.FileWriter('logs/train', tf.get_default_graph())
n_steps = 8000 # 迭代 8000 次
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
# 训练模型
for i in range(n_steps):
feed_dict = {x: x_np, y: y_np}
_, summary, loss = sess.run([train_op, merged, l], feed_dict=feed_dict)
print("step %d, loss: %f" % (i, loss))
train_writer.add_summary(summary, i)
# 计算参数
w_final, b_final = sess.run([W, b])
# 计算预测值
y_pred_np = sess.run(y_pred, feed_dict={x: x_np})
8000步后,损失函数稳定值在3.59左右
我们使用命令查看一下刚才的训练过程
tensorboard --logdir logs/train
上图为损失函数的下降曲线。可以看到快接近最终值时,曲线明显收敛。
另一张图是网络的执行图:
上图可以看到训练过程中每一步的流程和执行次数,线条越粗执行的次数越多。这个图可以方便的帮助我们优化自己的训练网络。
验证训练结果
现在我们已经得到了一个训练好的网络,这个预测网络的两个参数 $W_{final}$ 和 $b_{final}$,也就得到了最终的线性方程的公式: $$ y_{pred}=W_{final}x+b_{final} $$
现在我们用两种不同的指标来验证我们训练的这个模型: $R^2$ 和 RMSE (root-mean-squared error,均方根差). $R^2$ 是用于表示两个变量之间的相关性,用0~1之间的值表示,0表示不相关,1表示非常相关。对于数据集合 X 和 Y ,我们定义其 $R^2$ 如下 $$ R^2 = \frac{cov(X,Y)^2}{\sigma^2_X\sigma^2_Y} $$ 其中 $cov(X, Y)$ 是 X和Y的协方差,表示这两个数据集之间的相似情况,而 $\sigma_X$ 和 $\sigma_Y$ 是标准差,记录数据集各自内部数据的独立性。所以 $R^2$表示在两个数据集内,有多少个独立变量可以解释数据集的相关性。我们用如下代码计算 $y$和$y_{pred}$的$R^2$:
from scipy.stats import pearsonr
y_pred_np = np.resharpe(y_pred_np, -1)
r2 = pearsonr(y_np, y_pred_np)[0]**2
print("Pearson R^2:", r2)
>> Pearson R^2: 0.994
# 绘制图像
plt.clf()
plt.xlabel["Y-true"]
plt.ylabel["Y-pred"]
plt.title("Predicted versus True valuees")
plt.scatter(y_np, y_pred_np)
从值可以看出,预测值和真值之间 $R^2$ 近似 1 ,高度相关,说明我们的模型基本上学会了数据的真实规则。但是我们仔细观察绘制的图像发现,两个数轴之间的值并不完全对应。说明 $R^2$ 无法在缩放尺度上描述模型的准确性。我们使用 RMSE 计算:
from sklearn.metrics import mean_squared_error
rms = np.sqrt(mean_squared_error(y_np, y_pred_np))
print("RMSE: ", rms)
>> RMSE: 1.027
# 绘制 RMSE 图像
plt.clf()
plt.xlabel("x")
plt.ylabel("y")
plt.title("True Model versus Learned Model ")
plt.xlim(0, 1)
plt.scatter(x_np, y_np)
x_left = 0
y_left = w_final[0]*x_left + b_final
x_right = 1
y_right = w_final[0]*x_right + b_final
plt.plot([x_left, x_right], [y_left, y_right], color='k')
RMSE代表数据预测值和真值之间的平均差异,这个值大约 1.027,说明在一定程度上,数据和真实值之间有一定差距,说明我们的算法在梯度下降过程中,达到了局部最优险井,这也是梯度下降算法常见的问题,我们可以通过多初始点为,多步长值等超参数的调优,说着增加迭代次数的方式,降低这种风险。
构建的时候遇到的问题
运行时报错: FailedPreconditionError (see above for traceback): Attempting to use uninitialized value weights_2/Variable
这个问题是因为我在开始忘了执行 sess.run(tf.global_variables_initializer()) ,导致tf的session无法找到对应的全局值,从而报错。在初始化 session 时加上这句话就行。
运行时报错:You must feed a value for placeholder tensor ‘placeholders_6/Placeholder’ with dtype float and shape [100,1]
这是因为我在交互式命令行下执行的 python 语句,在多次执行某一语句块时,干扰了图的设定。所以在每次执行设定网络参数前,先执行一句 tf.reset_default_graph() 重置网络配置就行。
总结
本篇只是用一个简单的例子说明了线性回归的基本流程。但是基本上覆盖了机器学习的全部流程:定义模型,定义损失函数,定义调优函数,然后迭代运行,在执行方法上也涵盖了tf的大部分功能点。同时因为线性回归是基本上所有机器学习的基础,所以需要仔细体会。