學習一個東西首先要從大局在掌握,知道整體架構是什麼,有哪些部分,然後再逐個擊破,便事半功倍。
是以學習最開始,拿一個完整的例子最好不過。
本次項目是通過一系列屬性值(專業名詞稱作:特征值)來對房價進行預測,以找出最适合的模型

一個機器學習案例主要包括八個部分
1.項目概述
2.擷取資料
3.發現并可視化資料,發現規律
4.為機器學習算法準備資料
5.選擇模型,進行訓練
6.微調模型
7.給出解決方案
8.部署、監控、維護系統
歡迎來到機器學習房地産公司!你的第一個任務是利用加州普查資料,建立一個加州房價模型。
這個資料包含每個街區組的人口、收入中位數、房價中位數等名額。
街區組是美國調查局釋出樣本資料的最小地理機關(一個街區通常有 600 到 3000 人)。
我們将其簡稱為“街區”。你的模型要利用這個資料進行學習,然後根據其它名額,預測任何街區的的房價中位數。
劃定問題
問老闆的第一個問題應該是商業目标是什麼?建立模型可能不是最終目标。公司要如何使用、并從模型受益?
這非常重要,因為它決定了如何劃定問題,要選擇什麼算法,評估模型性能的名額是什麼,要花多少精力進行微調。
老闆告訴你你的模型的輸出(一個區的房價中位數)會傳給另一個機器學習系統,也有其它信号會傳入後面的系統.
這一整套系統可以确定某個區進行投資值不值。确定值不值得投資非常重要,它直接影響利潤。
選擇性能名額
回歸問題的典型名額是均方根誤差(RMSE)。均方根誤差測量的是系統預測誤差的标準差
雖然大多數時候 RMSE 是回歸任務可靠的性能名額,在有些情況下,你可能需要另外的函數。
例如,假設存在許多異常的街區。此時,你可能需要使用平均絕對誤差(Mean Absolute Error,也稱作平均絕對偏差)
核實假設
最後,最好列出并核對迄今(你或其他人)作出的假設,這樣可以盡早發現嚴重的問題。
例如,你的系統輸出的街區房價,會傳入到下遊的機器學習系統,我們假設這些價格确實會被當做街區房價使用。
但是如果下遊系統實際上将價格轉化成了分類(例如,便宜、中等、昂貴),然後使用這些分類,而不是使用價格。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
filePath = r'housing.csv'
f = open(filePath)
housing = pd.read_csv(f)
housing.head()#檢視頭檔案
housing.info()#産看資料資訊
housing['ocean_proximity'].value_counts()#資料類型
a = housing.describe()#資料描述
#資料可視化展示
housing.hist(bins = 50, figsize = (20,15))
plt.show()
#建立測試集,挑20%的執行個體放在一邊
#首先設定随機種子
np.random.seed(42)
def split_train_test(data, test_ratio):
shuffled_indices = np.random.permutation(len(data))
test_set_size = int(len(data) * test_ratio)
test_indices = shuffled_indices[:test_set_size]
train_indices = shuffled_indices[test_set_size:]
return data.iloc[train_indices], data.iloc[test_indices]
train_set, test_set = split_train_test(housing, 0.2)
如果資料集更新,那兩個方法都會失效,一個通常的解決方法是使用每個執行個體的ID來判斷
這個執行個體是否應該放入測試集,(假設每個執行個體都有唯一并且不變的ID)。例如計算出每個執行個體
ID的哈希值,隻保留其最後一個位元組,如果該值小于等于51,就将其放入測試集
import hashlib
def test_set_check(identifier, test_ratio, hash = hashlib.md5):
return hash(np.int64(identifier)).digest()[-1] < 256 * test_ratio
def split_train_test_by_id(data, test_ratio, id_column, hash=hashlib.md5):
ids = data[id_column]
in_test_set = ids.apply(lambda id_:test_set_check(id_, test_ratio, hash))
return data.loc[~in_test_set], data.loc[in_test_set]
housing_with_id = housing.reset_index() #添加索引列,就是index作為新的一列放入矩陣
train_set, train_set = split_train_test_by_id(housing_with_id, 0.2,'index')
#本執行個體沒有ID值,行列号可能會因為後來的删減而改變,是以使用地理坐标來作為ID使用
housing_with_id["id"] = housing["longitude"] * 1000 + housing["latitude"]
train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, 'id')
test_set.head()
#當然使用sklearn的子產品也有直接選取測試集的功能
from sklearn.model_selection import train_test_split
train_set, test_set = train_test_split(housing, test_size = 0.2, random_state = 42)
#收入中位數是預測房價中位數非常重要的屬性,是以首先要建立一個收入類别屬性,
#再仔細看一下收入中位數的柱狀圖
housing['median_income'].hist()
#将中位數除以1.5(以限制收入分類的數量),建立了一個收入類别屬性
#用ceil對值舍入(以産生離散的分類),然後将所有大于5的分類歸入到分類5
housing['income_cat'] = np.ceil(housing['median_income'] / 1.5)
housing['income_cat'].where(housing['income_cat'] < 5, 5.0, inplace = True)
housing['income_cat'].value_counts()
housing['income_cat'].hist()
#通過sklearn進行分層采樣也是可以的
from sklearn.model_selection import StratifiedShuffleSplit
split = StratifiedShuffleSplit(n_splits = 1, test_size = 0.2, random_state = 42)
for train_index, test_index in split.split(housing, housing['income_cat']):
strat_train_set = housing.loc[train_index]
strat_test_set = housing.loc[test_index]
housing['income_cat'].value_counts() / len(housing)
strat_test_set['income_cat'].value_counts() / len(strat_test_set)
使用相似的代碼,用随機采樣和分層采樣進行對比,發現分層采樣測試集的收入分類比例
與總資料集幾乎相同,而随機采樣資料集偏差嚴重
def income_cat_proportions(data):
return data['income_cat'].value_counts() / len(data)
train_set, test_set = train_test_split(housing, test_size = 0.2,
random_state = 42)
compare_props = pd.DataFrame({
'Overall':income_cat_proportions(housing),
'Stratified':income_cat_proportions(strat_test_set),
'Random': income_cat_proportions(test_set)
}).sort_index()
compare_props['Rand.%error'] = 100 * compare_props['Random'] / compare_props['Overall'] - 100
compare_props['Strat.%error'] = 100 * compare_props['Stratified'] / compare_props['Overall'] - 100
#删除income_cat屬性,始資料回到初始狀态:
for set in (strat_train_set, strat_test_set):
set.drop(['income_cat'], axis = 1, inplace = True)
#建立訓練集副本,以免損傷訓練集
housing = strat_train_set.copy()
#散點圖
housing.plot(kind = 'scatter', x = 'longitude', y = 'latitude')
#散點圖設定透明讀
housing.plot(kind = "scatter", x = "longitude", y = "latitude", alpha=0.1)
#散點圖設定彩色
housing.plot(kind = 'scatter', x = 'longitude', y = 'latitude', alpha=0.4,
s = housing['population']/100, label = 'population', figsize = (10,7),
c = 'median_house_value', cmap = plt.get_cmap('jet'), colorbar = True,
sharex = False)
plt.legend()
#将地圖導入散點圖
import matplotlib.image as mpimg
california_img = mpimg.imread(r'F:\python36_data\01--Sklearn 與 TensorFlow 機器學習實用指南中文版 2018.6.20\handson-ml-master\images\end_to_end_project\california.png')
ax = housing.plot(kind = 'scatter', x = 'longitude', y = 'latitude', figsize = (10,7),
s = housing['population']/100, label = 'Population',
c = 'median_house_value', cmap = plt.get_cmap('jet'),
colorbar = False, alpha = 0.4,)
plt.imshow(california_img, extent = [-124.55, -113.80, 32.45, 42.05], alpha = 0.5,
cmap = plt.get_cmap('jet'))
plt.ylabel('Latitude', fontsize = 14)
plt.xlabel('Longitude', fontsize = 14)
prices = housing['median_house_value']
tick_values = np.linspace(prices.min(), prices.max(), 11)
cbar = plt.colorbar()
cbar.ax.set_yticklabels(["$%dk"%(round(v/1000)) for v in tick_values], fontsize = 14)
cbar.set_label('Median House Value', fontsize = 16)
plt.legend(fontsize = 16)
plt.show()
檢視屬性之間的關聯度
#查找關聯度
corr_matrix = housing.corr()
corr_matrix['median_house_value'].sort_values(ascending = False)
#畫關聯度三點矩陣圖
from pandas.tools.plotting import scatter_matrix
attributes = ['median_house_value', 'median_income','total_rooms',
'housing_median_age']
scatter_matrix(housing[attributes], figsize= (12,8))
#畫圖
housing.plot(kind = 'scatter', x = 'median_income', y = 'median_house_value',
alpha = 0.1)
plt.axis([0, 16, 0, 550000])
#計算出新的名額
housing['rooms_per_household'] = housing['total_rooms']/housing['households']
housing['bedrooms_per_room'] = housing['total_bedrooms']/housing['total_rooms']
housing['population_per_household'] = housing['population']/housing['households']
corr_matrix = housing.corr()
corr_matrix['median_house_value'].sort_values(ascending = False)
housing.plot(kind = 'scatter', x = 'rooms_per_household', y = 'median_house_value',
alpha = 0.2)
plt.axis([0, 5, 0, 520000])
plt.show()
a = housing.describe()
現在來為機器學習算法準備資料。不要手工來做,你需要寫一些函數,理由如下:
4.1函數可以讓你在任何資料集上(比如,你下一次擷取的是一個新的資料集)友善地進行重複資料轉換。
4.2你能慢慢建立一個轉換函數庫,可以在未來的項目中複用。
4.3在将資料傳給算法之前,你可以在實時系統中使用這些函數。
4.這可以讓你友善地嘗試多種資料轉換,檢視哪些轉換方法結合起來效果最好。
#通過再次複制,将預測量和标簽分開
housing = strat_train_set.drop("median_house_value", axis = 1)
housing_labels = strat_train_set['median_house_value'].copy()
有三種方法處理缺失值
1.去掉對應的街區
housing.dropna(subset = [‘total_bedrooms’])
2.去掉整個屬性
housing.drop(‘total_bedrooms’, axis = 1)
3.進行指派(0、平均值、中位數等等)
median = housing[‘total_bedrooms’].median()
housing[‘total_bedrooms’].fillna(median)
#Scikit-Learn提供了一個友善的類來處理缺失值: Imputer
from sklearn.preprocessing import Imputer
imputer = Imputer(strategy = 'median')
#因為隻有數值屬性才能算出中位數,建立出一份不包括文本屬性ocean_proximity的資料副本
housing_num = housing.drop('ocean_proximity',axis = 1)
#用fit()方法将imputer執行個體拟合到訓練資料
imputer.fit(housing_num)
#檢視資料
imputer.statistics_
imputer.strategy
housing_num.median().values
#将“訓練過”的imputer來對訓練集進行轉換,将缺失值替換為中位數
X = imputer.transform(housing_num)
#将Numpy數組放回到Pandas dataframe中
housing_tr = pd.DataFrame(X, columns=housing_num.columns,
index = list(housing.index.values))
skickit-learn設計
一緻性:所有對象的接口一緻且簡單
估計器:任何可以基于資料集對一些參數進行估計的對象都被稱為估計器
轉換器:一些估計器(比如imputer)也可以轉換資料集,這些估計器被稱為轉換器。
預測器:一些估計器可以根據給出的資料集做預測,這些估計器稱為預測器。
可檢驗:所有估計器的超參數都可以通過執行個體的public變量直接通路(比如,imputer.strategy),
并且所有估計器學習到的參數也可以通過在執行個體變量名後加下劃線來通路
(比如,imputer.statistics_)。
類不可擴散:資料集被表示成 NumPy 數組或 SciPy 稀疏矩陣,而不是自制的類。
超參數隻是普通的 Python 字元串或數字。
可組合;盡可能使用現存的子產品。例如,用任意的轉換器序列加上一個估計器,
就可以做成一個流水線,後面會看到例子。
合理的預設值;Scikit-Learn 給大多數參數提供了合理的預設值,很容易就能建立一個系統。
#處理文本和類别屬性
#把文本标簽轉換為數字
#Scikit-Learn為這個任務提供了一個轉換器LabelEncoder:
from sklearn.preprocessing import LabelEncoder
encoder = LabelEncoder()#如果有多個文本特征列的時候,應使用factorize()方法來進行
housing_cat = housing['ocean_proximity']
housing_cat_encoded = encoder.fit_transform(housing_cat)
housing_cat_encoded
#檢查映射表,編碼器通過屬性classes_來學習的
print(encoder.classes_)
#因為上面這個數字編碼0和1與0和4相差太大,是以改用獨熱編碼
#Scikit-Learn提供了一個編碼器OneHotEncoder,用于将整數分類轉變為獨熱向量
from future_encoders import OneHotEncoder
encoder = OneHotEncoder()
housing_cat_1hot = encoder.fit_transform(housing_cat_encoded.reshape(-1,1))
housing_cat_1hot.toarray()
#使用類LabelBinarizer,我們可以用一步執行這兩個轉換(從文本分類到整數分類,再從整數分類到獨熱向量)
from sklearn.preprocessing import LabelBinarizer
encoder = LabelBinarizer()
housing_cat_1hot = encoder.fit_transform(housing_cat)
housing_cat_1hot
#傳回的是一個密集的Numpy數組。向構造器LabelBinarizer 傳遞sparse_output=True,
#就可以得到一個稀疏矩陣
在原書中使用LabelBinarizer的方式也是錯誤的,該類也應用于标簽列的轉換。
正确做法是使用sklearn即将提供的CategoricalEncoder類
from sklearn.base import BaseEstimator, TransformerMixin
rooms_ix, bedrooms_ix, population_ix, household_ix = 3,4,5,6
class CombinedAttributesAdder(BaseEstimator, TransformerMixin):
def __init__(self,add_bedrooms_per_room = True):#no *args or **kargs
self.add_bedrooms_per_room = add_bedrooms_per_room
def fit(self,X, y=None):
return self #nothing else to do
def transform(self, X, y=None):
rooms_per_household = X[:,rooms_ix] / X[:,household_ix]
population_per_household = X[:,bedrooms_ix] / X[:,household_ix]
if self.add_bedrooms_per_room:
bedrooms_per_room = X[:,bedrooms_ix] / X[:,rooms_ix]
return np.c_[X, rooms_per_household, population_per_household,
bedrooms_per_room]
else:
return np.c_[X, rooms_per_household, population_per_household]
attr_adder = CombinedAttributesAdder(add_bedrooms_per_room = False)
housing_extra_attribs = attr_adder.transform(housing.values)
housing_extra_attribs = pd.DataFrame(
housing_extra_attribs,
columns=list(housing.columns) + ['rooms_per_household', 'population_per_household'])
housing_extra_attribs.head()
特征縮放
線性函數歸一化(Min-Max scaling):轉換器MinMaxScaler,超參feature_range改變範圍
标準化(standardization):轉換器StandardScaler
#轉換流水線。scikit-learn提供了類Pipeline
#數值的流水線
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
num_pipeline = Pipeline([
('imputer',Imputer(strategy = 'median')),
('attribs_adder',CombinedAttributesAdder()),
('std_scaler',StandardScaler()),
])
housing_num_tr = num_pipeline.fit_transform(housing_num)
from future_encoders import ColumnTransformer
num_attribs = list(housing_num)
cat_attribs = ["ocean_proximity"]
full_pipeline = ColumnTransformer([
("num", num_pipeline, num_attribs),
("cat", OneHotEncoder(), cat_attribs),
])
housing_prepared = full_pipeline.fit_transform(housing)
5.模型訓練
5.1線性回歸
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(housing_prepared, housing_labels)
#用訓練集中的執行個體做下驗證
some_data = housing.iloc[:5]
some_labels = housing_labels.iloc[:5]
some_data_prepared = full_pipeline.transform(some_data)
print("Predictions:\t", lin_reg.predict(some_data_prepared))
print("Labels:\t\t", list(some_labels))
#使用Scikit-Learn 的mean_squared_error的函數,用全部訓練集來計算下這個模型的回歸模型
#的RMSE
from sklearn.metrics import mean_squared_error
housing_predictions = lin_reg.predict(housing_prepared)
lin_mse = mean_squared_error(housing_labels, housing_predictions)
lin_rmse = np.sqrt(lin_mse)
lin_rmse
分析:rmse68234,過大,是一個模型欠拟合的例子
這種情況說明特征沒有提供足夠多的資訊來做出一個好的預測,或者模型不夠強大
修複欠拟合的主要方法;
1.選擇一個更強大的模型
2.給訓練算法提供更好的特征
3.去掉模型上的限制
5.2 決策樹
#模型訓練
from sklearn.tree import DecisionTreeRegressor
tree_reg = DecisionTreeRegressor()
tree_reg.fit(housing_prepared, housing_labels)
#評估訓練集
housing_predictions = tree_reg.predict(housing_prepared)
tree_mse = mean_squared_error(housing_labels, housing_predictions)
tree_rmse = np.sqrt(tree_mse)
tree_rmse
'''
rmse:0,過拟合
用train_test_split來分割訓練集,得到一個更小的訓練集和驗證集
用Scikit-learn的交叉驗證功能:K折交叉驗證(K-fold cross-validation)
'''
from sklearn.model_selection import cross_val_score
scores = cross_val_score(tree_reg, housing_prepared, housing_labels,
scoring = 'neg_mean_squared_error', cv =10)
rmse_scores = np.sqrt(-scores)
#警告:Scikit-Learn交叉驗證功能期望的是效用函數(越大越好),而不是損失函數(越低越好)
#是以得分函數實際上與MSE相反(即負值),這就是為什麼前面的代碼在計算平方根之前先計算-scores
def display_scores(scores):
print("scores:",scores)
print("Mean:",scores.mean())
print("Standard deviation:",scores.std())
display_scores(rmse_scores)
決策樹的評分大約是71580,通常波動有±1375
#再用線性回歸模型的相同分分數,以做確定
lin_scores = cross_val_score(lin_reg, housing_prepared, housing_labels,
scoring = 'neg_mean_squared_error',cv = 10)
lin_rmse_scores = np.sqrt(-lin_scores)
display_scores(lin_rmse_scores)
分析:決策樹模型過拟合很嚴重,它的性能比線性回歸模型還差
5.3 随機森林
#模型訓練
from sklearn.ensemble import RandomForestRegressor
forest_reg = RandomForestRegressor()
forest_reg.fit(housing_prepared, housing_labels)
#評估訓練集
housing_predictions = forest_reg.predict(housing_prepared)
forest_mse = mean_squared_error(housing_labels, housing_predictions)
forest_rmse = np.sqrt(forest_mse)
forest_rmse
#交叉驗證
forest_scores = cross_val_score(forest_reg, housing_prepared, housing_labels,
scoring = 'neg_mean_squared_error', cv = 10)
forest_rmse_scores = np.sqrt(-forest_scores)
display_scores(forest_rmse_scores)
5.4支援向量機
from sklearn.svm import SVR
svm_reg = SVR(kernel = 'linear')
svm_reg.fit(housing_prepared, housing_labels)
housing_predictions = svm_reg.predict(housing_prepared)
svm_mse = mean_squared_error(housing_labels, housing_predictions)
svm_rmse = np.sqrt(svm_mse)
svm_rmse
6.模型微調
1.網格搜尋:Scikit-Learn 的GridSearchCV
2.随機搜尋:RandomizedSearchCV
3.內建方法:将最好模型組合起來
6.1網格搜尋
from sklearn.model_selection import GridSearchCV
param_grid = [
#try 12(3*4) combinations of hyperparameters
{'n_estimators': [3, 10, 30], 'max_features': [2, 4, 6, 8]},
#then try 6(2*3) combinations with bootstrap set as False
{'bootstrap':[False], 'n_estimators':[3,10], 'max_features':[2,3,4]}
]
forest_reg = RandomForestRegressor()
#train across 5 folds, that's a total of (12+6)*5 = 90 rounds of training
grid_search = GridSearchCV(forest_reg, param_grid,cv = 5,
scoring = 'neg_mean_squared_error', return_train_score = True)
grid_search.fit(housing_prepared, housing_labels)
grid_search.best_params_
#h還能得到最佳的估計器:
grid_search.best_estimator_
#也可以得到評估得分
cvres = grid_search.cv_results_
for mean_scores, params in zip(cvres['mean_test_score'], cvres['params']):
print(np.sqrt(-mean_scores), params)
from sklearn.model_selection import GridSearchCV
param_grid = [
{'n_estimators': [3, 10, 30], 'max_features': [2, 4, 6, 8]},
{'bootstrap': [False], 'n_estimators': [3, 10], 'max_features': [2, 3, 4]},
]
forest_reg = RandomForestRegressor()
grid_search = GridSearchCV(forest_reg, param_grid, cv=5, scoring='neg_mean_squared_error')
grid_search.fit(housing_prepared, housing_labels)
a = pd.DataFrame(grid_search.cv_results_)
6.2随機搜尋
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint
param_distribs = {
'n_estimators': randint(low=1, high=200),
'max_features': randint(low=1, high=8),
}
forest_reg = RandomForestRegressor(random_state=42)
rnd_search = RandomizedSearchCV(forest_reg, param_distributions=param_distribs,
n_iter=10, cv=5, scoring='neg_mean_squared_error', random_state=42)
rnd_search.fit(housing_prepared, housing_labels)
cvres = rnd_search.cv_results_
for mean_score, params in zip(cvres["mean_test_score"], cvres["params"]):
print(np.sqrt(-mean_score), params)
在這裡插入圖檔描述
檢視哪個屬性值重要性最大
feature_importances = grid_search.best_estimator_.feature_importances_
feature_importances
extra_attribs = ["rooms_per_hhold", "pop_per_hhold", "bedrooms_per_room"]
#cat_encoder = cat_pipeline.named_steps["cat_encoder"] # old solution
cat_encoder = full_pipeline.named_transformers_["cat"]
cat_one_hot_attribs = list(cat_encoder.categories_[0])
attributes = num_attribs + extra_attribs + cat_one_hot_attribs
sorted(zip(feature_importances, attributes), reverse=True)
1
2
3
4
5
6
7
8
9
#給出解決方案
final_model = grid_search.best_estimator_
X_test = strat_test_set.drop("median_house_value", axis=1)
y_test = strat_test_set["median_house_value"].copy()
X_test_prepared = full_pipeline.transform(X_test)
final_predictions = final_model.predict(X_test_prepared)
final_mse = mean_squared_error(y_test, final_predictions)
final_rmse = np.sqrt(final_mse)
final_rmse
#95%的置信區間來計算RMSE
from scipy import stats
confidence = 0.95
squared_errors = (final_predictions - y_test) ** 2
mean = squared_errors.mean()
m = len(squared_errors)
np.sqrt(stats.t.interval(confidence, m - 1,
loc=np.mean(squared_errors),
scale=stats.sem(squared_errors)))
#進行t-score檢驗
tscore = stats.t.ppf((1 + confidence) / 2, df=m - 1)
tmargin = tscore * squared_errors.std(ddof=1) / np.sqrt(m)
np.sqrt(mean - tmargin), np.sqrt(mean + tmargin)
#進行z-score檢驗
zscore = stats.norm.ppf((1 + confidence) / 2)
zmargin = zscore * squared_errors.std(ddof=1) / np.sqrt(m)
np.sqrt(mean - zmargin), np.sqrt(mean + zmargin)
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
8.啟動、監控、維護系統
很好,你被允許啟動系統了!你需要為實際生産做好準備,特别是接入輸入資料源,并編寫測試。
你還需要編寫監控代碼,以固定間隔檢測系統的實時表現,當發生下降時觸發報警。
這對于捕獲突然的系統崩潰和性能下降十分重要。做監控很常見,是因為模型會随着資料的演化而性能下降,
除非模型用新資料定期訓練。
評估系統的表現需要對預測值采樣并進行評估。這通常需要人來分析。分析者可能是領域專家,或者是衆包平台(比如 Amazon Mechanical Turk 或 CrowdFlower)的勞工。不管采用哪種方法,你都需要将人工評估的流水線植入系統。
你還要評估系統輸入資料的品質。有時因為低品質的信号(比如失靈的傳感器發送随機值,或另一個團隊的輸出停滞),
系統的表現會逐漸變差,但可能需要一段時間,系統的表現才能下降到一定程度,觸發警報。如果監測了系統的輸入,
你就可能盡量早的發現問題。對于線上學習系統,監測輸入資料是非常重要的
最後,你可能想定期用新資料訓練模型。你應該盡可能自動化這個過程。
如果不這麼做,非常有可能你需要每隔至少六個月更新模型,系統的表現就會産生嚴重波動。
如果你的系統是一個線上學習系統,你需要定期儲存系統狀态快照,好能友善地復原到之前的工作狀态。
reference:《Sklearn 與 TensorFlow 機器學習實用指南》