【本文使用的是TensorFlow1.x,如需TensorFlow2.x的内容参见我的“TensorFlow2实战”笔记】
一、问题描述
波士顿平均房价即为标签值,它与12个特征变量相关。
二、回归模型
本例里,我们用最简单的线性回归(即特征为一次幂)来构建模型。由于有12个特征,所以是多元线性回归模型
简写为
或写为矩阵形式
三、读取数据
1. 读取数据
加载所需库
import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
其中,pandas用于读取和整理数据。用pandas读取源文件并显示摘要
#读取数据
df = pd.read_csv('boston.csv', header=0)
#显示摘要
print(df.describe())
输出
CRIM ZN ... LSTAT MEDV
count 506.000000 506.000000 ... 506.000000 506.000000
mean 3.613524 11.363636 ... 12.653063 22.532806
std 8.601545 23.322453 ... 7.141062 9.197104
min 0.006320 0.000000 ... 1.730000 5.000000
25% 0.082045 0.000000 ... 6.950000 17.025000
50% 0.256510 0.000000 ... 11.360000 21.200000
75% 3.677082 12.500000 ... 16.955000 25.000000
max 88.976200 100.000000 ... 37.970000 50.000000
[8 rows x 13 columns]
也可以直接“print(df)”显示所有数据
CRIM ZN INDUS CHAS NOX ... RAD TAX PTRATIO LSTAT MEDV
0 0.00632 18.0 2.31 0 0.538 ... 1 296 15.3 4.98 24.0
1 0.02731 0.0 7.07 0 0.469 ... 2 242 17.8 9.14 21.6
2 0.02729 0.0 7.07 0 0.469 ... 2 242 17.8 4.03 34.7
.. ... ... ... ... ... ... ... ... ... ... ...
504 0.10959 0.0 11.93 0 0.573 ... 1 273 21.0 6.48 22.0
505 0.04741 0.0 11.93 0 0.573 ... 1 273 21.0 7.88 11.9
[506 rows x 13 columns]
数据太多会显示为“...”折叠
这是一个pandas的数据表格式,包含列明和行号等数据。我们需要提取其中的值来做数学计算
#提取数值
df = df.values
此时再print(df)打印结果为
[[6.3200e-03 1.8000e+01 2.3100e+00 ... 1.5300e+01 4.9800e+00 2.4000e+01]
[2.7310e-02 0.0000e+00 7.0700e+00 ... 1.7800e+01 9.1400e+00 2.1600e+01]
[2.7290e-02 0.0000e+00 7.0700e+00 ... 1.7800e+01 4.0300e+00 3.4700e+01]
...
[6.0760e-02 0.0000e+00 1.1930e+01 ... 2.1000e+01 5.6400e+00 2.3900e+01]
[1.0959e-01 0.0000e+00 1.1930e+01 ... 2.1000e+01 6.4800e+00 2.2000e+01]
[4.7410e-02 0.0000e+00 1.1930e+01 ... 2.1000e+01 7.8800e+00 1.1900e+01]]
可见数值被保存到一个python二维数组里了。然后把它转换为numpy数组以方便使用numpy的API
df = np.array(df,np.float32)
2. 分离特征和标签
用数组切片分离特征和标签数据
x_data = df[:, :12] #0~11列为特征值
y_data = df[:, 12] #第12列为标签
可以查看它们的形状
print('x_data shape = ', x_data.shape)
print('y_data shape = ', y_data.shape)
输出
x_data shape = (506, 12)
y_data shape = (506,)
即x_data有506行12列,y_data是有506个元素的数组
3. 特征归一化
对于多特征问题,不同特征数值的量级是不同的。比如本例中,特征“一氧化氮浓度NOX”小于1,而特征“TAX”在200~800范围。如果直接训练,则会出现搜索震荡甚至“梯度爆炸”问题
解决的方法是把特征归一化,即每个特征的所有数据都除以它最大值与最小值差
这样所有特征都会比较均匀地分布在0~1范文内,可加速训练和防止梯度爆炸
代码实现为
#特征数据归一化normalization
for i in range(x_data.shape[1]):
x_data[:,i] /= (x_data[:,i].max() - x_data[:,i].min())
注意本例的标签值只有一个(即标量),所以没必要归一化。
四、模型定义
1. 定义占位符
定义特征数据和标签数据的占位符
x = tf.placeholder(tf.float32, [None, 12], name = 'x') #12个特征值
y = tf.placeholder(tf.float32, [None, 1], name = 'y') #1个标签值
注意第二个参数是shape,shape的第一个维度写None的意思是行数是待定的,在实际feed时候有多少行就是多少行——这样在后面训练时候就比较灵活,可以单样本训练也可以批量训练。
2. 定义模型函数
上述“二、回归模型”中,讲模型写入矩阵时,w是有12个元素的列向量,即w的形状是[12,1]。b是一个标量。所以定义变量w和b如下
w = tf.Variable(tf.random_normal([12,1], stddev=0.01), name='w')
b = tf.Variable(1.0, name='b')
其中,w以标准差0.01的随机正态分布赋初值,b以定值0.1赋初值。(赋初值很有学问,这里不展开讲)
然后定义回归模型的函数
def model(x, w, b):
return tf.matmul(x, w) + b
tf.matmul是矩阵乘法。就相当于前文的回归函数
然后就可以定义前向计算节点,即预测值,为
pred = model(x, w, b)
3. 写入命名空间
我们可以把上面这几行代码写在一个命名空间(name scope)里,相当于把这段代码打包。好处是在查看计算图时,这段代码就会显示为一个子图,看上去更简洁。方法是写到上下文管理器“with tf.name_scope('Model'):”中即可,“Model”是自定义的空间名
#定义命名空间
with tf.name_scope('Model'):
#定义被训练变量
w=tf.Variable(tf.random_normal([12,1], stddev=0.1),name='w')
b=tf.Variable(1.0, name='b')
#定义模型
def model(x, w, b):
return tf.matmul(x, w) + b
#定义前向节点
pred = model(x, w, b)
注意写入命名空间这一步并不是必须的,只是为了计算图显示更简洁。
五、模型训练
1. 设置超参数
根据经验,设置迭代200轮,学习率0.01
train_epochs = 200 #迭代轮数
learning_rate = 0.01 #学习率
超参数是可以尝试调整的
2. 定义损失函数
使用简单的均方差(MSE)损失函数。也写入一个命名空间以打包入子图,并命名为“LossFunction”
with tf.name_scope('LossFunction'):
loss_function = tf.reduce_mean(tf.pow(y-pred, 2)) #均方差MSE
如果最后查看子图应显示为
3. 选择优化器
TensorFlow提供了非常多的优化器,这里选用最简单的梯度下降优化器。输入学习率和损失函数
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss_function)
4. 迭代训练
使用上下文管理器with/as建立会话session,并初始化变量。使用随机小批量(Mini-batch)训练,即每轮从训练集中随机抽取10个样本参与训练。每轮训练结束,都计算至今的平均损失并记录
with tf.Session() as sess:
#声明初始化变量操作
init = tf.global_variables_initializer()
#初始化变量
sess.run(init)
loss_sum=0
loss_ave_rec=[]
for epoch in range(train_epochs):
batch=np.random.randint(0, x_data.shape[0], 10) #随机取出x_data中的10组样本做训练
_, loss=sess.run([optimizer, loss_function], feed_dict={x:x_data[batch], y:y_data[batch]})
loss_sum+=loss
loss_ave_rec.append(loss_sum/(epoch+1))
训练完毕后,可绘制平均损失趋势图
plt.figure()
plt.plot(loss_ave_rec)
输出
可见平均损失逐渐趋于稳定,说明训练效果达到极限(未必是最好的,可能通过改变训练策略和调整超参数来优化)。
值得一提的是,loss越小并不一定结果越好,它只标明对现有标签拟合得好,但对新标签的预测可能并不好,即出现所谓“过拟合”(overfitting)现象。(解决方法这里不展开讲)
六、TensorBoard可视化
为了在TensorBoard中可视化计算图,对上一段“迭代计算”部分的代码添加一些内容。添加的内容注释“For TB”,修改后的代码如下
#迭代训练
with tf.Session() as sess:
#声明初始化变量操作
init = tf.global_variables_initializer()
#For TB: 设置日志存储目录
logdir='d:/log'
#For TB: 创建记录损失值的操作,命名为loss,可在TensorBoard中SCALARS栏看到
sum_loss_op=tf.summary.scalar('loss', loss_function)
#For TB: 合并所有需要记录的摘要日志文件,以方便一次性写入
merged = tf.summary.merge_all()
#初始化变量
sess.run(init)
#For TB: 创建摘要writer,以写入计算图,可在TensorBoard中GRAPHS栏中看到
writer=tf.summary.FileWriter(logdir, sess.graph)
loss_sum=0
loss_ave_rec=[]
for epoch in range(train_epochs):
batch=np.random.randint(0, x_data.shape[0], 10) #随机取出x_data中的10组样本做训练
#运行会话
#For TB: 添加运行节点sum_loss_op
_, loss, summary_str=sess.run([optimizer, loss_function, sum_loss_op], feed_dict={x:x_data[batch], y:y_data[batch]})
#For TB: 将损失值记录到日志文件
writer.add_summary(summary_str, epoch)
#记录平均损失
loss_sum+=loss
loss_ave_rec.append(loss_sum/(epoch+1))
运行成功后,可以在路径“d:/log”看到前缀“events.out.tfevents.”的文件。
打开Anaconda Prompt,输入命令
tensorboard --logdir=d:\log
返回
TensorBoard 1.13.1 at http://XXXX:6006 (Press CTRL+C to quit)
其中XXXX是你的计算机名。这时TensorBoard就在后台启动了。
然后打开浏览器,输入地址
localhost:6006
就能看到TensorBoad界面了:
附:完整代码
import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
#读取数据
df = pd.read_csv('boston.csv') #默认header=0,即第0行是列名
#显示摘要
print(df.describe())
#print(df)
df=df.values #提取数值
#print(df)
df = np.array(df,np.float32) #转换为numpy数组
x_data = df[:, :12] #0~11列为特征值
y_data=df[:, 12] #第12列为标签
#特征数据归一化normalization
for i in range(x_data.shape[1]):
x_data[:,i] /= (x_data[:,i].max() - x_data[:,i].min())
#显示x_data和y_data的形状
print('x_data shape = ', x_data.shape)
print('y_data shape = ', y_data.shape)
#模型定义
x = tf.placeholder(tf.float32, [None, 12], name = 'x') #12个特征值
y = tf.placeholder(tf.float32, [None, ], name = 'y') #1个标签值
#定义命名空间
with tf.name_scope('Model'):
#定义被训练变量
w=tf.Variable(tf.random_normal([12,1], stddev=0.1),name='w')
b=tf.Variable(1.0, name='b')
#定义模型
def model(x, w, b):
return tf.matmul(x, w) + b
#定义前向节点
pred = model(x, w, b)
#设置超参数
train_epochs = 200 #迭代轮数
learning_rate = 0.01 #学习率
#定义损失函数
with tf.name_scope('LossFunction'):
loss_function = tf.reduce_mean(tf.pow(y-pred, 2)) #均方差MSE
#调用梯度下降优化器
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss_function)
#迭代训练
with tf.Session() as sess:
#声明初始化变量操作
init = tf.global_variables_initializer()
#For TB: 设置日志存储目录
logdir='d:/log'
#For TB: 创建记录损失值的操作,命名为loss,可在TensorBoard中SCALARS栏看到
sum_loss_op=tf.summary.scalar('loss', loss_function)
#For TB: 合并所有需要记录的摘要日志文件,以方便一次性写入
merged = tf.summary.merge_all()
#初始化变量
sess.run(init)
#For TB: 创建摘要writer,以写入计算图,可在TensorBoard中GRAPHS栏中看到
writer=tf.summary.FileWriter(logdir, sess.graph)
loss_sum=0
loss_ave_rec=[]
for epoch in range(train_epochs):
batch=np.random.randint(0, x_data.shape[0], 10) #随机取出x_data中的10组样本做训练
#运行会话
#For TB: 添加运行节点sum_loss_op
_, loss, summary_str=sess.run([optimizer, loss_function, sum_loss_op], feed_dict={x:x_data[batch], y:y_data[batch]})
#For TB: 将损失值记录到日志文件
writer.add_summary(summary_str, epoch)
#记录平均损失
loss_sum+=loss
loss_ave_rec.append(loss_sum/(epoch+1))
#绘制平均损失趋势图
plt.figure()
plt.plot(loss_ave_rec)