泛化&泛化數據集&實驗

泛化&泛化數據集&實驗

泛化 (Generalization):過擬合的風險


  • 泛化:泛化能力(generalization ability)是指機器學習算法對新鮮樣本的適應能力。學習的目的是學到隱含在數據對背後的規律,對具備同一規律的學習集之外的數據,通過訓練的網絡也能給出合適的輸出,該能力稱爲泛化能力。

前兩篇實現了線性迴歸模型得訓練,並再最後進行了採用合成特徵做爲特徵輸入,且過濾了離羣值,效果彷佛不錯,可是這個不錯的結果來自於咱們採用了原來的訓練數據進行的測試,這樣的評估意義大嗎?若是咱們使用了新的(訓練模型從未碰見過的)數據進行預測,獲得的結果可能會不敬人意。
假設我對模型不斷的進行訓練,不斷調整超參數,通過若干天后,獲得的模型可以達到完美的預測效果,對於輸入的任意特徵都能獲得正確的target,可是這並非咱們想要的模型,由於其只達到了對訓練數據的完美貼合,但並不必定能對它從未見過的新數據作出正確的預測,而且其訓練了太久,模型也會變的過於複雜,這樣就叫作過擬合python

大概過程以下圖(圖片來源於谷歌機器學習樣圖):
爲了讓您直觀地理解這一律念,咱們將展現 3 張圖。假設這些圖中的每一個點表明一棵樹在森林中的位置。圖中的兩種顏色分別表明如下含義:git

  • 藍點表明生病的樹。
  • 橙點表明健康的樹。

GeneralizationA.png

圖 1. 生病(藍色)和健康(橙色)的樹。算法

您能設想出一個有效的模型來預測之後的生病或健康的樹嗎?花點時間在腦海裏繪製一條弧線將藍點與橙點分開,或者在腦海中圈住一些橙點或藍點。而後再看看圖 2,它顯示某種機器學習模型如何將生病的樹與健康的樹區分開。請注意,該模型產生的損失很是低。api

GeneralizationB.png

圖 2. 用於區分生病的樹與健康的樹的複雜模型。
乍一看,圖 2 所示的模型在將健康的樹與生病的樹區分開方面彷佛表現得很是出色。真的是這樣嗎?網絡

GeneralizationC.png

圖 3. 該模型在預測新數據方面表現很是糟糕。
圖 3 顯示咱們向該模型中添加了新數據後所發生的狀況。結果代表,該模型在處理新數據方面表現很是糟糕。請注意,該模型對大部分新數據的分類都不正確。數據結構


圖 2 和圖 3 所示的模型過擬合了訓練數據的特性。過擬合模型在訓練過程當中產生的損失很低,但在預測新數據方面的表現卻很是糟糕。若是某個模型在擬合當前樣本方面表現良好,那麼咱們如何相信該模型會對新數據作出良好的預測呢?正如您稍後將看到的,過擬合是因爲模型的複雜程度超出所需程度而形成的。機器學習的基本衝突是適當擬合咱們的數據,但也要儘量簡單地擬合數據。app

一種解決方法是將您的數據集分紅兩個子集:dom

  • 訓練集 - 用於訓練模型的子集。
  • 測試集 - 用於測試模型的子集。

通常來講,在-測試集上表現是否良好是衡量可否在新數據上表現良好的有用指標,前提是:機器學習

  • 測試集足夠大。
  • 不會反覆使用相同的測試集來做假。

咱們從分佈中隨機抽取樣本,且這些樣本是獨立同分布 (i.i.d) 的模塊化

  • 獨立同分布:一、每次抽取是隨機的,不被其餘條件所約束 二、所抽取的樣本是同分布的,即知足同一個分佈規律(如,抽紙牌,得從一樣得一副牌中抽取)三、分佈是平穩的,即分佈在數據集內不會發生變化

接下來咱們來談談訓練集測試集

訓練集和測試集 (Training and Test Sets):拆分數據


  • 訓練集 - 用於訓練模型的子集。
  • 測試集 - 用於測試訓練後模型的子集

若是咱們只有一個數據集,可是須要訓練集測試集,辦法很簡單,拆分就行了,比例大概能夠是4:1這樣子

不過,拆分數據時候得遵循幾個原則
1.數據集夠大
2.隨機抽取的,能表明數據集的水平

注意請勿對測試數據進行訓練,若是最後測試獲得的結果意外的好,那最好檢查一下,多數是由於對測試數據進行了誤訓練

劃分A.png

這是咱們剛纔討論的劃分方式--訓練集和測試集

可是這樣的劃分方式,會不會有問題呢?若是爲了在最後的測試數據上得到最佳效果,從而更改學習速率、添加或移除特徵,到從頭開始設計全新模型。當該工做流程結束時,在測試數據上表現很好,那麼最終依然頗有多是過擬合的。

因此咱們能夠進一步劃分,即訓練集+驗證集合+測試集合
劃分BB.png

劃分B.png

在這一通過改進的工做流程中:

  1. 選擇在驗證集上得到最佳效果的模型。
  2. 使用測試集再次檢查該模型。

該工做流程之因此更好,緣由在於它暴露給測試集的信息更少

接下來,咱們將對以前的理論進行驗證

驗證

  • 使用多個特徵而非單個特徵來進一步提升模型的有效性
  • 調試模型輸入數據中的問題
  • 使用測試數據集檢查模型是否過擬合驗證數據

與在以前的練習中同樣,咱們將依然使用加利福尼亞州住房數據集,嘗試根據 1990 年的人口普查數據在城市街區級別預測 median_house_value

設置和測試

咱們首先加載並準備數據。這一次,咱們將使用多個特徵,所以咱們會將邏輯模塊化,以對特徵進行預處理:


import math

from IPython import display
from matplotlib import cm
from matplotlib import gridspec
from matplotlib import pyplot as plt
import numpy as np
import pandas as pd
from sklearn import metrics
import tensorflow as tf
from tensorflow.python.data import Dataset

tf.logging.set_verbosity(tf.logging.ERROR)
pd.options.display.max_rows = 10
pd.options.display.float_format = '{:.1f}'.format

california_housing_dataframe = pd.read_csv("https://storage.googleapis.com/mledu-datasets/california_housing_train.csv", sep=",")

# california_housing_dataframe = california_housing_dataframe.reindex(
#     np.random.permutation(california_housing_dataframe.index))

# 對特徵預處理
def preprocess_features(california_housing_dataframe):
  selected_features = california_housing_dataframe[
    ["latitude",
     "longitude",
     "housing_median_age",
     "total_rooms",
     "total_bedrooms",
     "population",
     "households",
     "median_income"]]
  processed_features = selected_features.copy()
  # 此外多建立一個合成特徵
  processed_features["rooms_per_person"] = (
    california_housing_dataframe["total_rooms"] /
    california_housing_dataframe["population"])
  return processed_features
# 對target預處理
def preprocess_targets(california_housing_dataframe):
# output_targets爲pd.DataFrame()類型的數據結構(這種結構相似於表格,有行有列的索引)
  output_targets = pd.DataFrame()
  output_targets["median_house_value"] = (
    california_housing_dataframe["median_house_value"] / 1000.0)
  return output_targets

# 訓練集取前12000(共17000樣本)
training_examples = preprocess_features(california_housing_dataframe.head(12000))
training_examples.describe()
latitude longitude housing_median_age total_rooms total_bedrooms population households median_income rooms_per_person
count 12000.0 12000.0 12000.0 12000.0 12000.0 12000.0 12000.0 12000.0 12000.0
mean 34.6 -118.5 27.5 2655.7 547.1 1476.0 505.4 3.8 1.9
std 1.6 1.2 12.1 2258.1 434.3 1174.3 391.7 1.9 1.3
min 32.5 -121.4 1.0 2.0 2.0 3.0 2.0 0.5 0.0
25% 33.8 -118.9 17.0 1451.8 299.0 815.0 283.0 2.5 1.4
50% 34.0 -118.2 28.0 2113.5 438.0 1207.0 411.0 3.5 1.9
75% 34.4 -117.8 36.0 3146.0 653.0 1777.0 606.0 4.6 2.3
max 41.8 -114.3 52.0 37937.0 5471.0 35682.0 5189.0 15.0 55.2
training_targets = preprocess_targets(california_housing_dataframe.head(12000))
training_targets.describe()
median_house_value
count 12000.0
mean 198.0
std 111.9
min 15.0
25% 117.1
50% 170.5
75% 244.4
max 500.0
# 測試集取尾5000
validation_examples = preprocess_features(california_housing_dataframe.tail(5000))
validation_examples.describe()
latitude longitude housing_median_age total_rooms total_bedrooms population households median_income rooms_per_person
count 5000.0 5000.0 5000.0 5000.0 5000.0 5000.0 5000.0 5000.0 5000.0
mean 38.1 -122.2 31.3 2614.8 521.1 1318.1 491.2 4.1 2.1
std 0.9 0.5 13.4 1979.6 388.5 1073.7 366.5 2.0 0.6
min 36.1 -124.3 1.0 8.0 1.0 8.0 1.0 0.5 0.1
25% 37.5 -122.4 20.0 1481.0 292.0 731.0 278.0 2.7 1.7
50% 37.8 -122.1 31.0 2164.0 424.0 1074.0 403.0 3.7 2.1
75% 38.4 -121.9 42.0 3161.2 635.0 1590.2 603.0 5.1 2.4
max 42.0 -121.4 52.0 32627.0 6445.0 28566.0 6082.0 15.0 18.3
validation_targets = preprocess_targets(california_housing_dataframe.tail(5000))
validation_targets.describe()
median_house_value
count 5000.0
mean 229.5
std 122.5
min 15.0
25% 130.4
50% 213.0
75% 303.2
max 500.0

在上面咱們是把數據集中除了median_house_value做爲target外,其餘得全部列都做爲了feature輸入
咱們雖然沒有什麼數據分析或者統計學得背景知識,可是其實仍是能夠看一看,或者說猜一猜輸入的這些特徵究竟是些什麼,它們可能具備哪些意義?取值範圍是怎麼樣的,數值的大小正常嘛?等等等

latitude:緯度
longitude:經度
housing_median_age:房子年紀(中值)
total_rooms:房間總數(每一個街區)
total_bedrooms:臥室總數(每一個街區)
total_bedrooms:人口數(每一個街區)
households:戶(一家人爲一戶)
median_income :收入(中值)

再看看這些feature的數值(最大值,最小值),理解一下它們的單位(固然,會有不合理的,由於存在數據比較特殊,並且這是1990年的數據了),這些數據會幫助咱們在宏觀上了解這些數據集,特別是一旦發現不合理的地方更是要注意了(尤爲在訓練本身的數據時)

異常狀況:

  • median_income 位於 3 到 15 的範圍內。咱們徹底不清楚此範圍究竟指的是什麼,看起來多是某對數尺度?沒法找到相關記錄;咱們所能假設的只是,值越高,相應的收入越高。
  • median_house_value 的最大值是 500001。這看起來像是某種人爲設定的上限。
  • rooms_per_person 特徵一般在正常範圍內,其中第 75 百分位數的值約爲 2。但也有一些很是大的值(例如 18 或 55),這可能代表數據有必定程度的損壞。

咱們將暫時使用提供的這些特徵。但但願這些示例可幫助您較爲直觀地瞭解如何檢查來自未知來源的數據

### 繪製緯度/經度與房屋價值中位數的曲線圖

    • *

    咱們來詳細瞭解一下 latitudelongitude 這兩個特徵。它們是相關城市街區的地理座標。

利用這兩個特徵能夠提供出色的可視化結果 - 咱們來繪製 latitudelongitude 的曲線圖,而後用顏色標註 median_house_value

plt.figure(figsize=(13, 8))

ax = plt.subplot(1, 2, 1)
ax.set_title("Validation Data")

# 取消y軸的自動縮放,並定義上下限
ax.set_autoscaley_on(False)
ax.set_ylim([32, 43])

# 取消x軸的自動縮放,並定義上下限
ax.set_autoscalex_on(False)
ax.set_xlim([-126, -112])

# plt.scatter()參數說明
# validation_examples["longitude"],validation_examples["latitude"]:表明x,y
# cmap:Colormap,顏色表
# c:color(色彩,或者顏色序列)
plt.scatter(validation_examples["longitude"],
            validation_examples["latitude"],
            cmap="coolwarm",
            c=validation_targets["median_house_value"] / validation_targets["median_house_value"].max())

ax = plt.subplot(1,2,2)
ax.set_title("Training Data")

ax.set_autoscaley_on(False)
ax.set_ylim([32, 43])
ax.set_autoscalex_on(False)
ax.set_xlim([-126, -112])
plt.scatter(training_examples["longitude"],
            training_examples["latitude"],
            cmap="coolwarm",
            c=training_targets["median_house_value"] / training_targets["median_house_value"].max())
_ = plt.plot()

座標_housevalue.png

噹噹噹當,這個圖的問題很明顯呢,訓練和測試的數據差別很大呢,問題在哪裏呢? 都是一個數據集的哎,其實就是處理數據的時候,忘記了隨機排序了(再次說明打亂順序真的很重要,咱們永遠沒法預知本來序列的數據可能出現哪些問題),此外採用圖標分析也很重要,可以在訓練以前幫助咱們發現問題,否則後面就完蛋啦,怎麼訓練都不會有好的結果的。

調整後的結果

座標_housevalue1.png

如今很像一個地圖了

訓練和評估模型


咱們會使用數據集中的全部特徵訓練一個線性迴歸器,定義一下之前將數據加載到 TensorFlow 模型中時所使用的同一輸入函數

def my_input_fn(features, targets, batch_size=1, shuffle=True, num_epochs=None):  
    features = {key:np.array(value) for key,value in dict(features).items()}   
                                        
    ds = Dataset.from_tensor_slices((features,targets)) # warning: 2GB limit
    ds = ds.batch(batch_size).repeat(num_epochs)

    if shuffle:
      ds = ds.shuffle(10000)
    
    features, labels = ds.make_one_shot_iterator().get_next()
    return features, labels

因爲咱們如今使用的是多個輸入特徵,所以須要把用於將特徵列配置爲獨立函數的代碼模塊化。(目前此代碼至關簡單,由於咱們的全部特徵都是數值,但當咱們在從此的練習中使用其餘類型的特徵時,會基於此代碼進行構建。)

def construct_feature_columns(input_features):
  return set([tf.feature_column.numeric_column(my_feature)
              for my_feature in input_features])

接下來,繼續完成下面的 train_model() 代碼,以設置輸入函數和計算預測。
但要確保針對相應數據集調用 predict()比較訓練數據和驗證數據的損失

def train_model(
    learning_rate,
    steps,
    batch_size,
    training_examples,
    training_targets,
    validation_examples,
    validation_targets):

  periods = 10
  steps_per_period = steps / periods
  
  # 建立線性迴歸模型並設定好特徵列和優化器
  my_optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)
  my_optimizer = tf.contrib.estimator.clip_gradients_by_norm(my_optimizer, 5.0)
  linear_regressor = tf.estimator.LinearRegressor(
      feature_columns=construct_feature_columns(training_examples),
      optimizer=my_optimizer
  )
  
  # 建立訓練、預測(使用訓練集的數據)、驗證(使用驗證集的數據)的輸入函數
  training_input_fn = lambda: my_input_fn(
      training_examples, 
      training_targets["median_house_value"], 
      batch_size=batch_size)
  predict_training_input_fn = lambda: my_input_fn(
      training_examples, 
      training_targets["median_house_value"], 
      num_epochs=1, 
      shuffle=False)
  predict_validation_input_fn = lambda: my_input_fn(
      validation_examples, validation_targets["median_house_value"], 
      num_epochs=1, 
      shuffle=False)

  # 訓練(週期性輸出結果)
  print "Training model..."
  print "RMSE (on training data):"
  training_rmse = []
  validation_rmse = []
  for period in range (0, periods):
    # 按照訓練steps進行週期性訓練
    linear_regressor.train(
        input_fn=training_input_fn,
        steps=steps_per_period,
    )
    # 記錄預測值(分別使用訓練集和驗證集)
    training_predictions = linear_regressor.predict(input_fn=predict_training_input_fn)
    training_predictions = np.array([item['predictions'][0] for item in training_predictions])
    
    validation_predictions = linear_regressor.predict(input_fn=predict_validation_input_fn)
    validation_predictions = np.array([item['predictions'][0] for item in validation_predictions])
    
    
    # 計算RMSE(使用訓練值和驗證值)
    training_root_mean_squared_error = math.sqrt(
        metrics.mean_squared_error(training_predictions, training_targets))
    validation_root_mean_squared_error = math.sqrt(
        metrics.mean_squared_error(validation_predictions, validation_targets))

    print "  period %02d : %0.2f" % (period, training_root_mean_squared_error)

    training_rmse.append(training_root_mean_squared_error)
    validation_rmse.append(validation_root_mean_squared_error)
  print "Model training finished."

  plt.ylabel("RMSE")
  plt.xlabel("Periods")
  plt.title("Root Mean Squared Error vs. Periods")
  plt.tight_layout()
  plt.plot(training_rmse, label="training")
  plt.plot(validation_rmse, label="validation")
  # plt.legend()畫圖例,圖中右上角
  plt.legend()

  return linear_regressor

訓練...

linear_regressor = train_model(
    learning_rate=0.00003,
    steps=500,
    batch_size=5,
    training_examples=training_examples,
    training_targets=training_targets,
    validation_examples=validation_examples,
    validation_targets=validation_targets)

由於使用的features較多,要耐心等一下子就能夠看到使用訓練值和校驗值的區別了,以及多特徵的效果怎麼樣

Training model...
RMSE (on training data):
period 00 : 217.67
period 01 : 201.13
period 02 : 186.67
period 03 : 176.46
period 04 : 170.31
period 05 : 167.41
period 06 : 166.75
period 07 : 166.49
period 08 : 167.72
period 09 : 169.76
Model training finished.
learning_rate=0.00003
0.00003.png

要注意這是兩條線哦,還有一條是校驗集的結果,真的是很貼合了哎,說明模型效果挺好的

上面的learning_rate=0.00003,最終的rmse爲169.76,這比上次的合成特徵的高了好多,因此下面嘗試修改learning_rate
learning_rate=0.00015

0.00015.png

learning_rate=0.005

0.005.png

learning_rate=0.05

0.05.png

咱們會發現此次的損失函數曲線好像很複雜的樣子。。。。emmmmmmm,其實到這裏,我尚未調好,不過無論啦,下一篇我會說明一下,多特徵的時候怎麼來調節超參數(其實我只是比較懶.....)

最後,再看看以學習率爲0.00003訓練出來的模型,遇到測試集的效果吧

california_housing_test_data = pd.read_csv("https://storage.googleapis.com/mledu-datasets/california_housing_test.csv", sep=",")

test_examples = preprocess_features(california_housing_test_data)
test_targets = preprocess_targets(california_housing_test_data)

predict_test_input_fn = lambda: my_input_fn(
      test_examples, 
      test_targets["median_house_value"], 
      num_epochs=1, 
      shuffle=False)

test_predictions = linear_regressor.predict(input_fn=predict_test_input_fn)
test_predictions = np.array([item['predictions'][0] for item in test_predictions])

root_mean_squared_error = math.sqrt(
    metrics.mean_squared_error(test_predictions, test_targets))

print "Final RMSE (on test data): %0.2f" % root_mean_squared_error

Final RMSE (on test data): 162.84
結果很接近了呢,說明沒有過擬合。

講完。

相關文章
相關標籤/搜索