线性回归实战。
样例代码
前言
使用TensorFlow进行算法设计与训练的核心步骤为:
- (1) 准备数据
- (2) 构建模型
- (3) 训练模型
- (4) 进行预测
需要用到的第三方库
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
第三方库的详解网站有:
1. 准备数据
1.1 人造数据法:
- 确定一个确定的函数;
- 根据这个函数用一定的方法造出离散数据。(主要是标签和特征)
引言,当前的这个例子是先确定一条确定的直线:y = 2x + 1(即w=2,b=1)的直线。
- 有了预先确定的正确答案之后,我们就可以根据这个公式来创造随机而离散的数据量来作为人工的数据,而这也就是所谓的准备数据。
- 在这个例子当中,我们采用
numpy 自带的制造等差数列的函数可以在指定在某一个指定的区间创造指定数量的随机数,由此可以得到一组x的值,然后再利用这组x的值规定一定的震荡幅度(称为噪声)即让原来的公式变成:y = 2x + 1 + sound 代入刚才生成的x的伪随机数组以及sound生成公式,就可以产生一组对应的y值,从而得到一组离散的(x, y) 对。这样数据的人造完成了。 - 接下来看代码:
# 设置随机种子
np.random.seed(5)
# 设置等差数列
x_data = np.linspace(-1,1,100)
# y = 2x + 1 + 噪声,其中,噪声的维度同x_data
y_data = 2 * x_data + 1.0 + np.random.randn(*x_data.shape)*0.4
代码逐句解析:
- 关于
np.random.seed(5)
- 因为计算机产生的随机数是伪随机数,它的本质是用一个触发的值(种子),要注意的点是,基于同一个种子,我们可以得到同一组随机数。
- 一个种子只对往下的一组随机数有效。
- 如果不指定随机种子的话,那计算机会从内部的计时器中的时间任取一个值为启动值。
- 关于
np.linspace(-1,1,100)
- 关于
np.random.randn(*x_data.shape)
- 首先要先解释一下,
numpy 的方法产生的object 是 NumPy Ndarray 对象 ,而shape是这种对象的一个属性,用来衡量它的维度。 - 而
* 的作用是取元组中的值,对元组进行拆包,比如 *(100,) 得到的值就是100 。 - np.random的作用是产生随机数,而rand()的作用是指定所产生的随机数所返回的array的维度,也称作形状(shape)。
- 比如shape为(3,2)的一个array长这样:
array([ [1, 2],
[3, 4],
[5, 6] ])
2. 构建模型
2.1 单变量线性回归模型的构建
由于模型中的标签值(就是y)和特征值(就是x)都需要导入外部的数据来输入,所以需要使用的是TensorFlow中的一种值,称为占位符(placeholder),而在这个例子当中,我们只有一个标签和一个变量,所以目前只需要两个占位符变量。
# 定义占位符
x = tf.compat.v1.placeholder(float,name="x")
y = tf.compat.v1.placeholder(float,name="y")
- 占位符的定义不需要指定初值,但是需要指定
输入的元素的基本type ,此处为浮点数float ,同时可以指定它的名称,这样方便在计算图(TensorBoard)中展示它。 - 通俗地来说,我们可以把占位符看成是从外部向计算图中载入数据的端口。
定义完占位符之后,需要定义好回归模型的函数模型,方便将参数传入之后,可以直接根据函数模型来计算从而得到函数值(标签)。在这个例子当中,我们需要传入的参数是(x,w,b),通过引入这三个参数之后可以返回的值为y。
# 定义函数模型
def model(x,w,b):
return tf.multiply(x,w)+b
tf.multiply(x,w) 指的是tensorflow中的乘法方法,这里由于我们需要进行训练的所有的值都是在tensorflow的计算图上面进行的,所以要求我们所进行的任何一步与参与这个模型的训练的过程所需要动用到的值和计算操作都要使用tensorflow内置的方法,这样他们才会自然而然地成为计算图上的一个结点。
定义好函数的模型后,我们发现有两个值我们还没有进行定义,那就是要用来训练的参数(w,b) 这两个参数会随这训练的过程被不断地优化,是一个变化的值,所以他们采用的是TensorFlow中的变量的定义方法。
# 参数定义(创建变量)
w = tf.compat.v1.Variable(1.0,name="w0")
b = tf.compat.v1.Variable(0.0,name="b0")
- TensorFlow中的变量要求在定义的时候给定一个初始的值,所以这里给定了1.0和0.0作为初值。
- 需要注意的一点是,定义的初值不需要太担心是否会影响结果,因为比起初值更重要的是学习的步长要合理即可,只要步长合理,根据我们采用的梯度下降的方法,就可以得到变化收敛(也就是变化最缓慢) 的区域,就可以得到想要的训练参数了。
- 另,初始值的数据类型可以人为指定,不过也可以直接给定初始值,让系统默认去认定。
由于我们对损失的计算采用的是MSE方法,也就是L2损失,使用的是均方差公式,所以要求我们要有一个预测值用来衡量当前训练出来得模型能达到一个什么样的结果。所以我们还需要一个用来做前向计算得预测值的结点。另外这个值也可以在后续当模型训练好后,用于预测。
3. 训练模型
模型训练的本质其实就是通过一定的优化方法,得到理想的回归直线的参数。 此处我们采用梯度下降法,追求将L2损失下降到最低值的方法来训练这个模型。
再进行训练之前我们会注意到有一些参数是需要在训练之前就定义好的,这些参数我们称之为超参数。
# 迭代次数
train_epochs = 10
# 学习率 (按经验来看一般定在0.01到0.1之间)
learning_rate = 0.05
# 控制显示损失值的粒度
display_step = 10
train_epochs 代表的是模型迭代的次数,我们知道模型训练的本质其实是一遍一遍地顺着某一个速度,沿着某一个目的再行进,这个过程就是迭代,而设置迭代的次数就是规定一个总的值,作为上限。超参数需要在训练的过程中不断调整。learning_rate 学习率就是前面提到的所谓的速度,这个量不能太大也不能太小,目前归纳出来的经验一般是在(0.1到0.01)之间。display_step 这个值和模型训练无关,它不会影响模型训练的结果,主要用于控制损失值监控的一个步长。
损失函数的定义,方法有很多,此处采用的是L2损失,使用的是MSE方法。
# 定义损失函数(这里采用均方差)
loss_function = tf.reduce_mean(tf.square(y-pred))
- 由于损失函数是需要在计算图中进行计算所以采用的计算方法,也都必须是tensorflow中的方法。
- tf.square(object) 此函数是对object中的每一个元素求平方值。
- tf.reduce_mean(object) 这里的用法是计算object中的所有元素和的均值。
优化器的定义,这里使用的方法是梯度下降法。
Optimizer = tf.compat.v1.train.GradientDescentOptimizer(learning_rate).minimize(loss_function)
- tf.train.GradientDescentOptimizer(learning_rate)指的是梯度下降优化器,参数是学习率,也就是梯度下降的速度。
- 后面的.minimize(loss_function)表示训练所要达到的目的是最小化损失函数。
———————————————————————————————————————————
到目前为止,我们算是把静态的计算图布置完成了,而TensorFlow不一样的是,Tensor布置完成了,需要Flow,也就是流动起来,才能进行工作,而要让静态图工作起来,我们需要的是一个让它工作起来的单元,这个单元我们称为会话。
所以针对每一张计算图,在计算之前,我们都需要先定义一个会话。
init = tf.compat.v1.global_variables_initializer() # 变量初始化的结点
sess = tf.compat.v1.Session() # 定义会话
sess.run(init) # 执行变量初始化
- 另外,显然tensorflow图中我们刚才定义的图内存在变量,要让这些变量发挥作用,我们就必须要先让变量初始化,而计算图又分为两种状态,静态和动态,静态是我们定义好各个结点的状态,动态是我们启用会话去触发对应结点的过程,而且计算图是一个拓扑图,我们执行中间的某一个结点,那这个结点前方相关的所有结点也都会被执行。
这里补充两个变量的定义(python的变量不属于计算图的部分,仅用于流程控制) 他们的作用是辅助损失值的保存,方便中途输出损失值来检查模型训练的情况。
step = 0 # 用于保存训练步数
loss_list = [] # 用于保存损失值
接下来就是模型训练的主体:
for epoch in range(train_epochs):
for xs,ys in zip(x_data,y_data):
_, loss = sess.run([Optimizer, loss_function], feed_dict={x: xs, y: ys})
# 显示损失值
loss_list.append(loss)
step += 1
if step%display_step == 0:
print(f"Train_epoch:{epoch+1}, Step:{step}, Loss:{loss}")
b0temp=b.eval(session=sess) # 取b的值
w0temp=w.eval(session=sess) # 取w的值
plt.plot(x_data, w0temp * x_data + b0temp) # 每次优化后画出对应的直线
# 打印结果
print("w: ", sess.run(w))
print("b: ", sess.run(b))
- 首先是两层循环,外层循环控制迭代的次数,内层循环控制标签值和特征值的迭代
zip() 可以把x_data和y_data两个列表按(x0,y0),(x1,y1)···的顺序整理成一个以元组为元素的新列表feed_dict={x:xs,y:ys} 是对占位符的赋值方式,相当于把数据输入端口变量=tensor.eval(session=sess) 是取tensor的值赋给变量plt.plot 是画线的方法- 最后打印取值,由于w,b是计算图中的值,所以取用他们的值依然需要用会话来执行
关于损失值监控部分
- 每经过一次代值就能算出一个损失值,而loss_list这个列表用来存储损失值。
- 我们前面预设的损失值显示的粒度是10,所以这里是每代入十次值之后,就会输出一次当前的损失值用于检测。
4. 进行预测
所谓的进行预测,其实就是根据已经训练好的参数的值,代入给定的特征值,然后执行函数模块,查看输出的标签的值,从数学角度来说,就是对训练好的模型代入x值求y值,然后与最开始预设的正确的模型代入同一个x值来对比,看看预测的值的准确程度。
x_test = 3.14
predict = sess.run(pred,feed_dict={x:x_test})
print(f"predict: {predict}")
real = 2.00000 * x_test + 1.00000
print(f"real: {real}")
- 预测部分由于是在tensorflow的计算图上执行,所以一切执行需要满足tensorflow的语法。
- 真实值部分可以直接使用python计算的语法来得到真实的值。
5. 数据的可视化部分
# 可视化(模型)
plt.scatter(x_data,y_data,label='Original data')
plt.plot(x_data,x_data * sess.run(w) + sess.run(b),\
label = 'Fitted line',c='r',linewidth=3)
plt.legend(loc=2) #通过参数loc指定图例(label)位置
plt.show() #图形展示
# 可视化(损失值显示)
plt.plot(loss_list) # way1
plt.plot(loss_list,"r2") # r表示数字,2表示第二种样式
plt.show() # 图形展示
这里就简单解释上面用到的几个方法:
plt.scatter()
- 这个方法是plt用来绘制散点图的方法,需要我们传入散点的x轴和y轴的值
- 同时它可以加载标签变量,用来标注这些散点表示的含义
plt.plot()
- 这个方法是plt中最为基础的画图函数,一般我们用来画直线
- 需要载入x轴y轴的数据
- 可以加载标签变量
- 可以调整线的颜色
- 可以调整线的宽度
plt.legend()
plt.show()
|