使用TensorFlow 2.0完成機器學習通常有三種方式:python
一般認爲評估器由於內置的緊密結合,運行速度要高於Keras。Keras一直是一個通用的高層框架,除了支持TensorFlow做爲後端,還同時支持Theano和CNTK。高度的抽象確定會影響Keras的速度,不過本人並未實際對比測試。我覺的,對於大量數據致使的長時間訓練來講,這點效率上的差別不該當成爲大問題,不然Python這種解釋型的語言就不會成爲優選的機器學習基礎平臺了。
在TensorFlow 1.x中可使用tf.estimator.model_to_estimator方法將Keras模型轉換爲TensorFlow評估器。TensorFlow 2.0中,統一到了tf.keras.estimator.model_to_estimator方法。因此若是偏心評估器的話,使用Keras也不會成爲障礙。算法
其實從編程邏輯來看,這些高層API所提供的工做方式是很類似的。使用評估器開發機器學習大體分爲以下步驟:編程
在這個流程裏面,只有「編寫數據流水線輸入函數」這一步是跟Keras模型是不一樣的。在Keras模型中,咱們直接準備數據集,把數據集送入到模型便可。而在評估器中,數據的輸入,須要指定一個函數供評估器調用。後端
這一個來自官方文檔的實例比較殘酷,使用泰坦尼克號的乘客名單,評估在沉船事件發生後,客戶能生存下來的可能性。
數據格式是csv,建議先下載,保存到工做目錄:
訓練集數據:https://storage.googleapis.com/tf-datasets/titanic/train.csv
評估集數據:https://storage.googleapis.com/tf-datasets/titanic/eval.csv
文件下載後不要修更名稱。api
數據包含以下屬性維度:bash
屬性名稱 | 屬性描述 |
---|---|
sex | 乘客性別 |
age | 乘客年齡 |
n_siblings_spouses | 隨行兄弟或者配偶數量 |
parch | 隨行父母或者子女數量 |
fare | 船費金額 |
class | 船艙等級 |
deck | 甲板編號 |
embark_town | 登船地點 |
alone | 是否爲獨自旅行 |
從這些屬性中能看出,數據的收集者是很是用心的。
好比隨行兄弟或者配偶、隨行父母或者子女這種特徵,在大多人的傳統觀念中,確定會用相似「隨行家眷數量」這樣的維度合併在一塊兒。
但在這個案例中,兩個不一樣的維度,對於最終存活影響確定是不一樣的。app
這部分的工做其實跟評估器的使用沒有什麼關係,但這正是大數據時代的魅力所在,因此咱們仍是延續官方文檔的思路來看一看。框架
先在命令行執行Python,啓動交互環境。而後把下面這部分代碼拷貝到Python執行。這些代碼完成引用擴展庫、載入數據等基本工做。機器學習
# 引入擴展庫 from __future__ import absolute_import, division, print_function, unicode_literals import numpy as np import pandas as pd import matplotlib.pyplot as plt import tensorflow as tf # 載入數據 dftrain = pd.read_csv('train.csv') dfeval = pd.read_csv('eval.csv') # 分離標註字段 y_train = dftrain.pop('survived') y_eval = dfeval.pop('survived') dftrain.head()
這時候命令行看起來大體是這個樣子:函數
$ python3 Python 3.7.3 (default, Mar 27 2019, 09:23:39) [Clang 10.0.0 (clang-1000.11.45.5)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> # 引入擴展庫 ... from __future__ import absolute_import, division, print_function, unicode_literals >>> >>> import numpy as np >>> import pandas as pd >>> import matplotlib.pyplot as plt >>> import tensorflow as tf >>> >>> # 載入數據 ... dftrain = pd.read_csv('train.csv') >>> dfeval = pd.read_csv('eval.csv') >>> # 分離標註字段 ... y_train = dftrain.pop('survived') >>> y_eval = dfeval.pop('survived') >>> >>> dftrain.head() sex age n_siblings_spouses parch fare class deck embark_town alone 0 male 22.0 1 0 7.2500 Third unknown Southampton n 1 female 38.0 1 0 71.2833 First C Cherbourg n 2 female 26.0 0 0 7.9250 Third unknown Southampton y 3 female 35.0 1 0 53.1000 First C Southampton n 4 male 28.0 0 0 8.4583 Third unknown Queenstown y >>>
最後是列出的訓練集頭5條記錄。
咱們先看看乘客的年齡分佈(後續的代碼都是直接拷貝到Python命令行執行):
dftrain.age.hist(bins=20) plt.show()
直方圖中顯示,乘客年齡主要分佈在20歲至30歲之間。
再來看看性別分佈:
dftrain.sex.value_counts().plot(kind='barh') plt.show()
男性乘客的數量,幾乎是女性乘客的兩倍。
接着是船艙等級的分佈,這個參數能間接體現乘客的經濟實力:
dftrain['class'].value_counts().plot(kind='barh') plt.show()
圖中顯示,大多數乘客仍是在三等艙。
繼續看乘客上船的地點:
dftrain['embark_town'].value_counts().plot(kind='barh') plt.show()
大多數乘客來自南安普頓。
繼續,把性別跟最後生存標註關聯起來:
pd.concat([dftrain, y_train], axis=1).groupby('sex').survived.mean().plot(kind='barh').set_xlabel('% survive') plt.show()
女性的存活率幾乎超過男性的5倍。
再來一個更復雜的統計,咱們首先把年齡分段,而後看看不一樣年齡段的乘客最終存活率:
def calc_age_section(n, lim): return'[%.f,%.f)' % (lim*(n//lim), lim*(n//lim)+lim) # map function addone = pd.Series([calc_age_section(s, 10) for s in dftrain.age]) dftrain['ages'] = addone pd.concat([dftrain, y_train], axis=1).groupby('ages').survived.mean().plot(kind='barh').set_xlabel('% survive'); plt.show()
10歲如下兒童和80歲以上的老人獲得了最多的生存機會。
在那個寒冷、慌亂的沉船夜晚,弱者反而更多的活了下來。
數據預處理這個話題咱們講了不少次,這是一般機器學習研發工做中,工程師須要作的最多工做。
泰坦尼克號乘客名單的數據雖然不復雜,也屬於典型的結構化數據。
其中主要包含兩類,一種是分類型的數據,好比船艙等級,好比上船城市名稱。另外一類則是簡單的數值,好比年齡和購票價格。
對於數值型的數據能夠直接規範化後進入模型,對於分類型的數據,則還須要作編碼,咱們這裏仍是使用最多見的one-hot。
# 定義所需的數據列,分爲分類型屬性和數值型屬性分別定義 CATEGORICAL_COLUMNS = ['sex', 'n_siblings_spouses', 'parch', 'class', 'deck', 'embark_town', 'alone'] NUMERIC_COLUMNS = ['age', 'fare'] # 輔助函數,把給定數據列作one-hot編碼 def one_hot_cat_column(feature_name, vocab): return tf.feature_column.indicator_column( tf.feature_column.categorical_column_with_vocabulary_list(feature_name, vocab)) # 最終使用的數據列,先置空 feature_columns = [] for feature_name in CATEGORICAL_COLUMNS: # 分類的屬性都要作one-hot編碼,而後加入數據列 vocabulary = dftrain[feature_name].unique() feature_columns.append(one_hot_cat_column(feature_name, vocabulary)) for feature_name in NUMERIC_COLUMNS: # 數值類的屬性直接入列 feature_columns.append(tf.feature_column.numeric_column(feature_name, dtype=tf.float32))
評估器的訓練、評估都須要使用數據輸入函數做爲參數。輸入函數自己不接受任何參數,返回一個tf.data.Dataset對象給模型用於供給數據。
由於除了數據集不一樣,訓練和評估模型所使用的數據格式一般都是同樣的。因此常常會在程序代碼上,共用一個函數,而後用參數來區分用於評估仍是用於訓練。
然而輸入函數至關於回調函數,由評估器控制着調用,這過程當中並無參數傳遞。因此比較聰明的作法可使用嵌套函數的方法來定義,好比:
# 這是一個不多量數據的樣本,直接把整個數據集當作一批 NUM_EXAMPLES = len(y_train) # 輸入函數的構造函數 def make_input_fn(X, y, n_epochs=None, shuffle=True): def input_fn(): dataset = tf.data.Dataset.from_tensor_slices((dict(X), y)) # 亂序 if shuffle: dataset = dataset.shuffle(NUM_EXAMPLES) # 訓練時讓數據重複儘可能多的次數 dataset = dataset.repeat(n_epochs) dataset = dataset.batch(NUM_EXAMPLES) return dataset return input_fn # 訓練、評估所使用的數據輸入函數,區別只是數據是否亂序以及迭代多少次 train_input_fn = make_input_fn(dftrain, y_train) eval_input_fn = make_input_fn(dfeval, y_eval, shuffle=False, n_epochs=1)
本例中咱們直接使用預約義的評估器模型(pre-made estimator)。因此代碼很是簡單,定義、訓練、評估都是隻須要一行代碼:
# 使用線性分類器做爲模型 linear_est = tf.estimator.LinearClassifier(feature_columns) # 訓練 linear_est.train(train_input_fn, max_steps=100) # 評估 result = linear_est.evaluate(eval_input_fn)
咱們來看看完整代碼:
#!/usr/bin/env python3 # 引入擴展庫 from __future__ import absolute_import, division, print_function, unicode_literals import numpy as np import pandas as pd import matplotlib.pyplot as plt import tensorflow as tf # 載入數據 dftrain = pd.read_csv('train.csv') dfeval = pd.read_csv('eval.csv') # 分離標註字段 y_train = dftrain.pop('survived') y_eval = dfeval.pop('survived') ################################################################ # 定義所需的數據列,分爲分類型屬性和數值型屬性分別定義 CATEGORICAL_COLUMNS = ['sex', 'n_siblings_spouses', 'parch', 'class', 'deck', 'embark_town', 'alone'] NUMERIC_COLUMNS = ['age', 'fare'] # 輔助函數,把給定數據列作one-hot編碼 def one_hot_cat_column(feature_name, vocab): return tf.feature_column.indicator_column( tf.feature_column.categorical_column_with_vocabulary_list(feature_name, vocab)) # 最終使用的數據列,先置空 feature_columns = [] for feature_name in CATEGORICAL_COLUMNS: # 分類的屬性都要作one-hot編碼,而後加入數據列 vocabulary = dftrain[feature_name].unique() feature_columns.append(one_hot_cat_column(feature_name, vocabulary)) for feature_name in NUMERIC_COLUMNS: # 數值類的屬性直接入列 feature_columns.append(tf.feature_column.numeric_column(feature_name, dtype=tf.float32)) ################################################################ # 這是一個不多量數據的樣本,直接把整個數據集當作一批 NUM_EXAMPLES = len(y_train) # 輸入函數的構造函數 def make_input_fn(X, y, n_epochs=None, shuffle=True): def input_fn(): dataset = tf.data.Dataset.from_tensor_slices((dict(X), y)) # 亂序 if shuffle: dataset = dataset.shuffle(NUM_EXAMPLES) # 訓練時讓數據重複儘可能多的次數 dataset = dataset.repeat(n_epochs) dataset = dataset.batch(NUM_EXAMPLES) return dataset return input_fn # 訓練、評估所使用的數據輸入函數,區別只是數據是否亂序以及迭代多少次 train_input_fn = make_input_fn(dftrain, y_train) eval_input_fn = make_input_fn(dfeval, y_eval, shuffle=False, n_epochs=1) # 使用線性分類器做爲模型 linear_est = tf.estimator.LinearClassifier(feature_columns) # 訓練 linear_est.train(train_input_fn, max_steps=100) # 評估 result = linear_est.evaluate(eval_input_fn) print("----------------------------------") print(pd.Series(result))
程序執行的最後顯示了評估的結果,在個人電腦上顯示的結果是這樣的:
---------------------------------- accuracy 0.765152 accuracy_baseline 0.625000 auc 0.832844 auc_precision_recall 0.789631 average_loss 0.478908 label/mean 0.375000 loss 0.478908 precision 0.703297 prediction/mean 0.350790 recall 0.646465 global_step 100.000000
正確率不算過高。
評估器的模型使用起來很簡單,咱們嘗試換用另一種模型,好比提高樹分類器。
# 如下代碼放在程序最後,由於這個數據集很是小,速度很快,因此作兩次學習也並不感受慢 n_batches = 1 est = tf.estimator.BoostedTreesClassifier(feature_columns, n_batches_per_layer=n_batches) # 訓練 est.train(train_input_fn, max_steps=100) # 評估 result = est.evaluate(eval_input_fn) print("----------------------------------") print(pd.Series(result))
此次獲得的結果是這樣的:
---------------------------------- accuracy 0.825758 accuracy_baseline 0.625000 auc 0.872360 auc_precision_recall 0.857325 average_loss 0.411853 label/mean 0.375000 loss 0.411853 precision 0.784946 prediction/mean 0.382282 recall 0.737374 global_step 100.000000
雖然準確率仍然並不高,但比起來線性分類器,提升仍是算的上明顯。
評價機器學習模型的性能,除了看剛纔的統計信息,繪圖是很是好的一種方式,能夠更直觀,某些問題也能體現的一目瞭然。
咱們在上面程序的最後再增長几行代碼,繪製預測機率的統計信息:
# 繪製預測機率直方圖 pred_dicts1 = list(linear_est.predict(eval_input_fn)) pred_dicts2 = list(bt_est.predict(eval_input_fn)) probs1 = pd.Series([pred['probabilities'][1] for pred in pred_dicts1]) probs2 = pd.Series([pred['probabilities'][1] for pred in pred_dicts2]) plt.figure(figsize=(14, 5)) plt.subplot(1, 2, 1) probs1.plot(kind='hist', bins=20, title='linear-est predicted probabilities'); plt.subplot(1, 2, 2) probs2.plot(kind='hist', bins=20, title='bt-est predicted probabilities'); plt.show()
大量集中在圖形左側的數據簇,顯示了乘客九死一輩子的悲慘命運。
由於咱們的預測結果只有兩種可能:0表示未能生存;1表示生存下來。因此預測的結果,應當明顯的儘可能靠近0和1兩端。中間懸而未決的部分應當儘量少。從圖形的狀況看,若是不考慮分類準確率問題,提高樹分類器效果要更好一些。
固然做爲成熟的預約義模型,模型都是很優秀的,只是提高樹可能更適合本應用的場景。
儘管這個例子很簡單,但如今的分類算法實際愈來愈複雜。預測結果在不一樣類別數據上表現並不不均衡,使得使用正確率這樣的傳統標準不能恰當的反應分類器的性能,本例中也已經出現了這種傾向。或者說,分類器,對於不一樣類別的樣本,性能表現是不一致的。
這種狀況,使用ROC(Receiver Operating Characteristic)觀察者操做曲線可以表現的更清楚。
對於一個分類器的分類結果,通常有如下四種狀況:
ROC圖中,左上角是真陽性的極點,曲線越接近左上角,意味着分類器性能越好。因此左上角是分類器追求的方向。
下面代碼,請接續在上面代碼以後,用來繪製ROC曲線:
# 繪製ROC(Receiver Operating Characteristic)曲線 from sklearn.metrics import roc_curve def plot_roc(probs, title): fpr, tpr, _ = roc_curve(y_eval, probs) plt.plot(fpr, tpr) plt.title(title) plt.xlabel('false positive rate') plt.ylabel('true positive rate') plt.xlim(0,) plt.ylim(0,) plt.figure(figsize=(14, 5)) plt.subplot(1, 2, 1) plot_roc(probs1, "linear-est ROC") plt.subplot(1, 2, 2) plot_roc(probs2, "bt-est ROC") plt.show()
從ROC曲線看,在本例中使用提高樹模型的優點更爲明顯。
(待續...)