3.1 TensorFlow实战二:波士顿房价预测

【本文使用的是TensorFlow1.x,如需TensorFlow2.x的内容参见我的“TensorFlow2实战”笔记】

一、问题描述

波士顿平均房价即为标签值,它与12个特征变量相关。

源数据存储在一个名为“boston.csv”表格中,如下图所示。最后一列“MEDV”即标签值房价,其他12列为影响房价的特征。

二、回归模型

本例里,我们用最简单的线性回归(即特征为一次幂)来构建模型。由于有12个特征,所以是多元线性回归模型
y=w_{1}x_{1}+w_{2}x_{2}+\cdots+w_{n}x_{n}+b

简写为
y=\sum^{n}_{i=0}w_ix_i+b

或写为矩阵形式
y=\left[ \begin{matrix} x_1 & x_2 & \cdots &x_n \end{matrix} \right] \left[ \begin{matrix} w_1\\ w_2\\ \vdots\\ w_n \end{matrix} \right] + b

三、读取数据

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是矩阵乘法。就相当于前文的回归函数
y=\left[ \begin{matrix} x_1 & x_2 & \cdots &x_n \end{matrix} \right] \left[ \begin{matrix} w_1\\ w_2\\ \vdots\\ w_n \end{matrix} \right] + b

然后就可以定义前向计算节点,即预测值,为

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界面了:

SCALARS标签下可以看到每轮的loss值。因为记录的不是平均值所有看到波动很大

GRAPHS标签下可以看到运行图,双击节点可以看内部的计算流

附:完整代码

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)

Reference:
https://www.icourse163.org/learn/ZUCC-1206146808?tid=1450260446#/learn/content?type=detail&id=1214536575&cid=1218338010

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 机器学习术语表 本术语表中列出了一般的机器学习术语和 TensorFlow 专用术语的定义。 A A/B 测试 (...
    yalesaleng阅读 6,094评论 0 11
  • 本内容为Udacity课程波士顿房价预测项目,欢迎阅读,有错的地方请留言。仅参考不建议作为其他用途。 优达学城毕业...
    MrMiaow阅读 14,862评论 1 18
  • 本文编译自谷歌开发者机器学习术语表项目,介绍了该项目所有的术语与基本解释。 A 准确率(accuracy) 分类模...
    630d0109dd74阅读 6,169评论 0 1
  • A 准确率(accuracy) 分类模型预测准确的比例。在多类别分类中,准确率定义如下: 在二分类中,准确率定义为...
    630d0109dd74阅读 5,249评论 0 3
  • 机器学习工程师纳米学位 模型评价与验证 项目 : 预测波士顿房价 第一步. 导入数据 在这个项目中,你将利用马萨诸...
    代号027阅读 9,226评论 0 1