【121】Tensorflow合成特徵和擷取離群值
開發環境
python 版本用的是2
資料來源
沒有積分的讀者請給我留言,我給你單獨發。
全部程式碼
所有的程式碼都在下面,你可以把這些程式碼複製貼上到一個編輯器裡,然後執行程式碼。
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損失更小,擬合度更高。對比散點圖也能看出擬合度有了改善。