開發環境
沒有開發環境的讀者,推薦你看這篇文章: 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損失更小,擬合度更高。對比散點圖也能看出擬合度有了改善。