开发环境
没有开发环境的读者,推荐你看这篇文章: https://blog.csdn.net/zhangchao19890805/article/details/78781003
python 版本用的是2
数据源
用了一个CSV文件,文件名是 california_housing_train.csv 。我把这个文件从放到了这个地址:https://download.csdn.net/download/zhangchao19890805/10584496
没有积分的读者请给我留言,我给你单独发。
全部代码
所有的代码都在下面,你可以把这些代码复制粘贴到一个编辑器里,然后执行代码。
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
# 从CSV文件中读取数据,返回DataFrame类型的数据集合。
def zc_func_read_csv():
zc_var_dataframe = pd.read_csv("http://yoursite.com/california_housing_train.csv", sep=",")
zc_var_dataframe = zc_var_dataframe.reindex(np.random.permutation(zc_var_dataframe.index))
zc_var_dataframe["median_house_value"] /= 1000.0
return (zc_var_dataframe)
# 训练形如 y = kx + b 的直线模型。
# feature_arr 特征值的数组。相当于 y = kx + b 中的x。
# label_arr 标签的数组。相当于 y = kx + b 中的y。
# training_steps 训练的步数。即训练的迭代次数。
# learning_rate 在梯度下降算法中,控制梯度步长的大小。
def zc_func_train_line(feature_arr, label_arr, training_steps, learning_rate):
feature_tf_arr = np.array([[1,e] for e in feature_arr]).astype(np.float32)
label_tf_arr = np.array([[e] for e in label_arr]).astype(np.float32)
# 存放 L2 损失的数组
loss_arr = []
# 开启TF会话,在TF 会话的上下文中进行 TF 的操作。
with tf.Session() as sess:
# 设置 tf 张量(tensor)。注意:TF会话中的注释里面提到的常量和变量是针对TF设置而言,不是python语法。
# 因为在TF运算过程中,x作为特征值,y作为标签
# 是不会改变的,所以分别设置成input 和 target 两个常量。
# 这是 x 取值的张量。设一共有m条数据,可以把input理解成是一个m行2列的矩阵。矩阵第一列都是1,第二列是x取值。
input = tf.constant(feature_tf_arr)
# 设置 y 取值的张量。target可以被理解成是一个m行1列的矩阵。 有些文章称target为标签。
target = tf.constant(label_tf_arr)
# 设置权重变量。因为在每次训练中,都要改变权重,来寻找L2损失最小的权重,所以权重是变量。
# 可以把权重理解成一个2行1列的矩阵。初始值是随机的。[2,1] 表示2行1列。
weights = tf.Variable(tf.random_normal([2, 1], 0, 0.1))
# 初始化上面所有的 TF 常量和变量。
tf.global_variables_initializer().run()
# input 作为特征值和权重做矩阵乘法。m行2列矩阵乘以2行1列矩阵,得到m行1列矩阵。
# yhat是新矩阵,yhat中的每个数 yhat' = w0 * 1 + w1 * x。
# yhat是预测值,随着每次TF调整权重,yhat都会变化。
yhat = tf.matmul(input, weights)
# tf.subtract计算两个张量相减,当然两个张量必须形状一样。 即 yhat - target。
yerror = tf.subtract(yhat, target)
# 计算L2损失,也就是方差。
loss = tf.nn.l2_loss(yerror)
# 梯度下降算法。
zc_optimizer = tf.train.GradientDescentOptimizer(learning_rate)
# 注意:为了安全起见,我们还会通过 clip_gradients_by_norm 将梯度裁剪应用到我们的优化器。
# 梯度裁剪可确保梯度大小在训练期间不会变得过大,梯度过大会导致梯度下降法失败。
zc_optimizer = tf.contrib.estimator.clip_gradients_by_norm(zc_optimizer, 5.0)
zc_optimizer = zc_optimizer.minimize(loss)
for _ in range(training_steps):
# 重复执行梯度下降算法,更新权重数值,找到最合适的权重数值。
sess.run(zc_optimizer)
# 每次循环都记录下损失loss的值,病放到数组loss_arr中。
loss_arr.append(loss.eval())
zc_weight_arr = weights.eval()
zc_yhat = yhat.eval()
return (zc_weight_arr, zc_yhat, loss_arr)
# end def train_line
# 取得集合中的最小值。
# arr 数组,元素是数字。
# 返回最小的数字
def zc_func_min(arr):
r = arr[0]
for item in arr:
if (r > item):
r = item
return r
# 取得集合中的最大值。
# arr 数组,元素是数字。
# 返回最大的数字
def zc_func_max(arr):
r = arr[0]
for item in arr:
if (r < item):
r = item
return r
# 把原始数据的散点和数学模型的直线都画到同一张图上。
# ax Axes 可以通过 fig.add_subplot(num1, num2, num3)函数添加。
# feature_arr 原始数据中的特征值数组。
# label_arr 原始数据中的标签集合。
# zc_weight_arr 权重数组。
# yhat 预测值集合。
def zc_func_paint_model(ax, feature_arr, label_arr, zc_weight_arr, yhat):
# 画出原始数据的散点图。
ax.set_title("House Value")
ax.set_xlabel("rooms_per_person")
ax.set_ylabel("median_house_value")
ax.scatter(feature_arr, label_arr, c="y", alpha=0.5)
# 画出预测值的散点图。
p_yhat = [a[0] for a in yhat]
ax.scatter(feature_arr, p_yhat, c="g", alpha=.1)
# 画出线性回归计算出的直线模型。
min_feature = zc_func_min(feature_arr)
max_feature = zc_func_max(feature_arr)
line_x_arr = [min_feature, max_feature]
line_y_arr = []
for item in line_x_arr:
line_y_arr.append(zc_weight_arr[0] + zc_weight_arr[1] * item)
ax.plot(line_x_arr, line_y_arr, "red", alpha=1.)
# 画损失的变化图。
# ax Axes
# zc_param_learning_steps 训练次数。
# zc_param_loss_arr 每次训练,损失变化的记录
def zc_func_paint_loss(ax, zc_param_learning_steps, zc_param_loss_arr):
ax.plot(range(0, zc_param_learning_steps), zc_param_loss_arr)
def zc_func_apply_callback(zc_param_value):
zc_var_result = 5
if (zc_param_value < zc_var_result):
zc_var_result = zc_param_value
return zc_var_result
# 主函数
def zc_func_main():
zc_var_dataframe = zc_func_read_csv()
# 合成特征。
zc_var_dataframe["rooms_per_person"] = zc_var_dataframe["total_rooms"] / zc_var_dataframe["population"]
# 学习的步数。
zc_var_leaning_step_num = 400
# 训练模型。
(weight_arr, yhat, loss_arr) = zc_func_train_line(zc_var_dataframe["rooms_per_person"],
zc_var_dataframe["median_house_value"], zc_var_leaning_step_num, 0.2)
print("No. 1 weight: ", weight_arr, " loss: ", loss_arr[zc_var_leaning_step_num-5:])
# 获得画图对象。
fig = plt.figure()
fig.set_size_inches(15, 10) # 整个绘图区域的宽度10和高度4
# 画rooms_per_person的直方图。
ax = fig.add_subplot(2, 3, 1)
ax = zc_var_dataframe["rooms_per_person"].hist()
# 画散点以及直线的图。
zc_func_paint_model(fig.add_subplot(2, 3, 2), zc_var_dataframe["rooms_per_person"], zc_var_dataframe["median_house_value"],
[e[0] for e in weight_arr], yhat)
# 画损失变化图。
zc_func_paint_loss(fig.add_subplot(2, 3, 3), zc_var_leaning_step_num, loss_arr)
plt.show()
# 截取离群值。直方图显示,大多数值都小于 5。我们将 rooms_per_person 的值截取为 5,然后绘制直方图以再次检查结果。
zc_var_dataframe["rooms_per_person"] = zc_var_dataframe["rooms_per_person"].apply(zc_func_apply_callback)
# 截取离群值后,重新训练模型。
(weight_arr, yhat, loss_arr) = zc_func_train_line(zc_var_dataframe["rooms_per_person"],
zc_var_dataframe["median_house_value"], zc_var_leaning_step_num, 0.2)
print("No. 2 weight: ", weight_arr, " loss: ", loss_arr[zc_var_leaning_step_num-5:])
fig = plt.figure()
fig.set_size_inches(15, 10)
# 画rooms_per_person的直方图。
ax = fig.add_subplot(2, 3, 4)
ax = zc_var_dataframe["rooms_per_person"].hist()
# 画散点以及直线的图。
zc_func_paint_model(fig.add_subplot(2, 3, 5), zc_var_dataframe["rooms_per_person"], zc_var_dataframe["median_house_value"],
[e[0] for e in weight_arr], yhat)
# 画损失变化图。
zc_func_paint_loss(fig.add_subplot(2, 3, 6), zc_var_leaning_step_num, loss_arr)
plt.show()
zc_func_main()
程序运行结果是:
合成特征
california_housing_train.csv 文件的数据中包含了不同地区的房价中位数(median_house_value)、房间总数(total_rooms)和人口(population)等信息。我们来探究一下哪些因素影响了房价中位数,我们猜测这个可能和人口密度相关。为了衡量人口密度,我们人均房间数(rooms_per_person)来表示。
人均房间数 = 房间总数 / 人口
反应到代码上,就是给 DataFrame 添加一个新的列“rooms_per_person” 。在上文中的主函数 zc_func_main 中,代码如下:
zc_var_dataframe["rooms_per_person"] = zc_var_dataframe["total_rooms"] / zc_var_dataframe["population"]
我们用两个特征:房价总数(total_rooms)和人口(population),合成了一个新的特征:人均房间数(rooms_per_person)
离群值
结果中,第一段No.1开头的文字和第一行图表是在没有处理离群值的情况下,进行训练后给出的结果。同理,第二段文字和第二行图表是截取离群值后,再进行训练得到的结果。
离群值很可能对我们的训练造成负面影响,让我们的预测值不够准确。在上面的代码中,我们通过代码:
ax = fig.add_subplot(2, 3, 1)
ax = zc_var_dataframe["rooms_per_person"].hist()
绘制了原始数据的直方图(结果图中第一行第一个图表),我们可以看到只有少数几个值大于5,可以看作是离群值。
那么,我们该如何处理离群值呢?
对于离群值有多种处理方法,我们最容易想到的一种方法是直接剔除离群值以及与之相关的其它数据。这就好比在散点图中直接让这个离群的点消失,当然随着点的消失,不管是图表中的横轴还是纵轴上的数据,都会跟着消失。这固然是一种解决方法,但让我们思考一下这么做有什么问题?实际中我们搜集数据会是多维度的。当我们因为某条数据在某个维度上是离群值就将其删除,会让我们丢失这条数据在其它维度上的信息。我们例子中的模型可以看作是探究两个维度的关系:人均房间数(rooms_per_person)和房价中位数(median_house_value)。现实中数据往往超过两个维度,剔除离群值会造成更多的信息丢失。
本文中使用了截取离群值的方法来处理离群值。我将人均房间数(rooms_per_person)大于5的数据统统赋值成5 。这样即让数据更加集中,又保留了每条数据在房价中位数(median_house_value)这一维度的信息。
上面的代码中我们先定义 DataFrame.apply 函数的回调函数:
def zc_func_apply_callback(zc_param_value):
zc_var_result = 5
if (zc_param_value < zc_var_result):
zc_var_result = zc_param_value
return zc_var_result
然后对人均房间数(rooms_per_person)截取离群值:
# 截取离群值。直方图显示,大多数值都小于 5。我们将 rooms_per_person 的值截取为 5,然后绘制直方图以再次检查结果。
zc_var_dataframe["rooms_per_person"] = zc_var_dataframe["rooms_per_person"].apply(zc_func_apply_callback)
对比截取离群值之前和之后的训练结果,可以看到L2损失更小,拟合度更高。对比散点图也能看出拟合度有了改善。