這篇文章介紹自定義一個(gè)估算器(分類器)Estimator的完整流程。
請(qǐng)先參照鳶尾花iris案例并完成練習(xí)。
自定義Custom Estimator和預(yù)制Pre-made Estimator
在上面iris的案例中我們使用了tensorflow里面自帶的深度神經(jīng)網(wǎng)絡(luò)分類器tf.estimator.DNNClassifie。這些tensorflow自帶的estimator稱為預(yù)制估算器Pre-made Estimator(預(yù)創(chuàng)建的Estimator)。
classifier = tf.estimator.DNNClassifier(
feature_columns=feature_columns,
hidden_units=[10, 10],
n_classes=3,
model_dir=models_path,
config=ckpt_config) #
Tensorflow允許我們自己創(chuàng)建更加靈活的Custom Estimator。自定義Estimator是tf.estimator.Estimator()方法生成,能夠像預(yù)制Estimator一樣使用。

結(jié)構(gòu)概覽
從表面看,我們的Estimator應(yīng)該具有DNNClassifier一樣的功能
- 創(chuàng)建的時(shí)候接收一些參數(shù),如feature_columns、hidden_units、n_classes等
- 具有train()、evaluate()、predict()三個(gè)方法用來訓(xùn)練、評(píng)價(jià)、預(yù)測(cè)
如上所說,我們使用 tf.estimator.Estimator()方法來生成自定義Estimator,它的語法格式是
tf.estimator.Estimator(
model_fn, #模型函數(shù)
model_dir=None, #存儲(chǔ)目錄
config=None, #設(shè)置參數(shù)對(duì)象
params=None, #超參數(shù),將傳遞給model_fn使用
warm_start_from=None #熱啟動(dòng)目錄路徑
)
模型函數(shù)model_fn是唯一沒有默認(rèn)值的參數(shù),它也是自定義Estimator最關(guān)鍵的部分,包含了最核心的算法。model_fn需要一個(gè)能夠進(jìn)行運(yùn)算的函數(shù),它的樣子應(yīng)該長(zhǎng)成這樣
my_model(
features, #輸入的特征數(shù)據(jù)
labels, #輸入的標(biāo)簽數(shù)據(jù)
mode, #train、evaluate或predict
params #超參數(shù),對(duì)應(yīng)上面Estimator傳來的參數(shù)
)
神經(jīng)網(wǎng)絡(luò)層Layers
model_fn應(yīng)該怎么運(yùn)作?下圖展示了iris案例的情況

從這個(gè)圖中我沒看到的結(jié)構(gòu):
- 輸入層Input Layer,數(shù)據(jù)從這里進(jìn)入
- 隱藏層Hidden Layer,2個(gè),每層包含多個(gè)節(jié)點(diǎn),數(shù)據(jù)流經(jīng)這里,被推測(cè)規(guī)律
- 輸出層Output Layer,將推測(cè)的結(jié)果整理顯示出來
我們并不需要手工實(shí)現(xiàn)隱藏層的算法和工作原理,Tensorflow已經(jīng)為我們?cè)O(shè)計(jì)好。我們需要的只是創(chuàng)建這些神經(jīng)網(wǎng)絡(luò)層,并確保它們按照正常的順序連接起來,至于其中如何推算演繹的魔法就完全交給tensorflow就可以了。
mode_fn需要完成的就是創(chuàng)建和組織這些神經(jīng)層。
編寫model_fn
對(duì)應(yīng)我們創(chuàng)建Estimator時(shí)候的參數(shù)
classifier = tf.estimator.DNNClassifier(
feature_columns=feature_columns,
hidden_units=[10, 10],
n_classes=3,
model_dir=models_path,
config=ckpt_config)
這些參數(shù)都會(huì)被Estimator打包放在params超參數(shù)中,傳遞給model_fn,所以我們用下面的代碼在model_fn內(nèi)創(chuàng)建網(wǎng)絡(luò)層
import tensorflow as tf
#自定義模型函數(shù)
def my_model_fn(features,labels,mode,params):
#輸入層,feature_columns對(duì)應(yīng)Classifier(feature_columns=...)
net = tf.feature_column.input_layer(features, params['feature_columns'])
#隱藏層,hidden_units對(duì)應(yīng)Classifier(unit=[10,10]),2個(gè)各含10節(jié)點(diǎn)的隱藏層
for units in params['hidden_units']:
net = tf.layers.dense(net, units=units, activation=tf.nn.relu)
#輸出層,n_classes對(duì)應(yīng)3種鳶尾花
logits = tf.layers.dense(net, params['n_classes'], activation=None)
輸入層Input Layer
在上面代碼中,我們使用這行代碼創(chuàng)建輸入層
net = tf.feature_column.input_layer(features, params['feature_columns'])
如下圖所示,Input Layer把輸入的數(shù)據(jù)features填充到特征列params['feature_column']里面,稍后它會(huì)被繼續(xù)傳遞到隱藏層hidden layer:

隱藏層Hidden Layer
我們使用循環(huán)為hidden_unit列表([10,10])創(chuàng)建了2個(gè)隱藏圖層,每個(gè)圖層的神經(jīng)元節(jié)點(diǎn)unit都等于10.
for units in params['hidden_units']:
net = tf.layers.dense(net, units=units, activation=tf.nn.relu)
我們注意到上面的輸入層叫做net(暫時(shí)叫net0),for循環(huán)里的隱藏層也叫net(暫叫net1)而且參數(shù)里還有net(net2),示意代碼如下
#僅供示意
net0 = tf.feature_column.input_layer...
for units ...
net1 = tf.layers.dense(net2, ...)
實(shí)際運(yùn)行到隱藏層第一層(for循環(huán)第一次)的時(shí)候,我們創(chuàng)建隱藏層net1,并把net0作為參數(shù)輸入到net1的,也就是隱藏第一層中關(guān)聯(lián)了輸入層:
input_net0=...#創(chuàng)建輸入層
hidden_net1=tf.layers.dense(input_net0,...) #創(chuàng)建隱藏層1
然后for第二次循環(huán)的時(shí)候我們又關(guān)聯(lián)了第一個(gè)隱藏層hidden_net1:
hidden_net2=tf.layers.dense(hidden_net1,...) #創(chuàng)建隱藏層2
這樣逐層傳遞就形成了鏈條,數(shù)據(jù)沿著鏈條進(jìn)行流動(dòng)Flow和處理
intputLayer - hiddenLayer1 - hiddenLayer2 - ...

輸出層Output Layer
我們使用了這行代碼創(chuàng)建輸出層,請(qǐng)注意net!
logits = tf.layers.dense(net, params['n_classes'], activation=None)
仍然是鏈條的延續(xù)!
但是activation這里改為了None,不再激活后續(xù)的部分,所以輸出層就是鏈條的終點(diǎn)。

請(qǐng)注意這里的[-1.3,2.6,-0.9]表示了某朵花的測(cè)量數(shù)據(jù)分別屬于三種分類的可能性,但是這里的數(shù)字很奇怪,甚至還有負(fù)數(shù)...稍后我們會(huì)對(duì)它們進(jìn)行轉(zhuǎn)化。
訓(xùn)練train、評(píng)價(jià)evaluate和預(yù)測(cè)predict
前面我們知道,自定義的估算分類器必須能夠用來執(zhí)行my_classifier.train()、my_classifier.evaluate()、my_classifier.predict()三個(gè)方法。
但實(shí)際上,它們都是model_fn這一個(gè)函數(shù)的分身!
上面出現(xiàn)的model_fn語法:
my_model(
features, #輸入的特征數(shù)據(jù)
labels, #輸入的標(biāo)簽數(shù)據(jù)
mode, #train、evaluate或predict
params #超參數(shù),對(duì)應(yīng)上面Estimator傳來的參數(shù)
)
注意第三個(gè)參數(shù)mode,如果它等于"TRAIN"我們就執(zhí)行訓(xùn)練:
#示意代碼
my_model(..,..,"TRAIN",...)
如果是“EVAL”就執(zhí)行評(píng)價(jià),“PREDICT”就執(zhí)行預(yù)測(cè)。
我們修改my_model代碼來實(shí)現(xiàn)這三個(gè)功能:
def my_model_fn(features,labels,mode,params):
#輸入層,feature_columns對(duì)應(yīng)Classifier(feature_columns=...)
net = tf.feature_column.input_layer(features, params['feature_columns'])
#隱藏層,hidden_units對(duì)應(yīng)Classifier(unit=[10,10]),2個(gè)各含10節(jié)點(diǎn)的隱藏層
for units in params['hidden_units']:
net = tf.layers.dense(net, units=units, activation=tf.nn.relu)
#輸出層,n_classes對(duì)應(yīng)3種鳶尾花
logits = tf.layers.dense(net, params['n_classes'], activation=None)
#預(yù)測(cè)
predicted_classes = tf.argmax(logits, 1) #預(yù)測(cè)的結(jié)果中最大值即種類
if mode == tf.estimator.ModeKeys.PREDICT:
predictions = {
'class_ids': predicted_classes[:, tf.newaxis], #拼成列表[[3],[2]]格式
'probabilities': tf.nn.softmax(logits), #把[-1.3,2.6,-0.9]規(guī)則化到0~1范圍,表示可能性
'logits': logits,#[-1.3,2.6,-0.9]
}
return tf.estimator.EstimatorSpec(mode, predictions=predictions)
#損失函數(shù)
loss = tf.losses.sparse_softmax_cross_entropy(labels=labels, logits=logits)
#訓(xùn)練
if mode == tf.estimator.ModeKeys.TRAIN:
optimizer = tf.train.AdagradOptimizer(learning_rate=0.1) #用它優(yōu)化損失函數(shù),達(dá)到損失最少精度最高
train_op = optimizer.minimize(loss, global_step=tf.train.get_global_step()) #執(zhí)行優(yōu)化!
return tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op)
#評(píng)價(jià)
accuracy = tf.metrics.accuracy(labels=labels,
predictions=predicted_classes,
name='acc_op') #計(jì)算精度
metrics = {'accuracy': accuracy} #返回格式
tf.summary.scalar('accuracy', accuracy[1]) #僅為了后面圖表統(tǒng)計(jì)使用
if mode == tf.estimator.ModeKeys.EVAL:
return tf.estimator.EstimatorSpec(mode, loss=loss, eval_metric_ops=metrics)
如上面所示,請(qǐng)將預(yù)測(cè)Predict需要放在最先編寫,否則可以引發(fā)后續(xù)錯(cuò)誤。
下面我們分別詳解三個(gè)方法的代碼
預(yù)測(cè)Predict
因?yàn)轭A(yù)測(cè)最后我們需要返回花的種類label,還希望知道這個(gè)預(yù)測(cè)有多精確,所以在預(yù)測(cè)部分的代碼里面,首先取到三種花可能性最大的一個(gè)predicted_classes即[-1.3,2.6,-0.9]中的2.6;然后把它轉(zhuǎn)成列表格式[[2.6]];同時(shí)把logit得到的[-1.3,2.6,-0.9]轉(zhuǎn)化為表示0~1可能性的小數(shù)[0.01926995 0.95198274 0.02874739]
predicted_classes = tf.argmax(logits, 1) #預(yù)測(cè)的結(jié)果中最大值即種類
if mode == tf.estimator.ModeKeys.PREDICT:
predictions = {
'class_ids': predicted_classes[:, tf.newaxis], #拼成列表[[3],[2]]格式
'probabilities': tf.nn.softmax(logits), #把[-1.3,2.6,-0.9]規(guī)則化到0~1范圍,表示可能性
'logits': logits,#[-1.3,2.6,-0.9]
}
return tf.estimator.EstimatorSpec(mode, predictions=predictions)
注意最后一句,我們返回return的是一個(gè)EstimatorSpec對(duì)象,下面的訓(xùn)練predict和評(píng)價(jià)evaluate也都返回EstimatorSpec形式的對(duì)象,但是參數(shù)不同,請(qǐng)留意。
我們可以使用以下代碼在單獨(dú)文件測(cè)試tf.newaxis和tf.nn.softmax對(duì)數(shù)據(jù)轉(zhuǎn)化的作用
import tensorflow as tf
a=tf.constant([2.6],name='a')
b=a[:,tf.newaxis]
a2=tf.constant([-1.3,2.6,-0.9],name='a')
b2= tf.nn.softmax(a2)
with tf.Session() as session:
session.run(tf.global_variables_initializer())
session.run(tf.tables_initializer())
print(session.run(b))
print(session.run(b2))
輸出
[[2.6]]
[0.01926995 0.95198274 0.02874739]
損失函數(shù)Loss
損失函數(shù)是Tensorflow中神經(jīng)網(wǎng)絡(luò)的重要概念,簡(jiǎn)單說,它能夠計(jì)算出我們模型的偏差程度,結(jié)果越大,我們的模型就偏差越大、離正確也遠(yuǎn)、也越不準(zhǔn)確、越糟糕。
為了降低損失,我們可以使用更多更好的數(shù)據(jù),還可以設(shè)計(jì)更好的優(yōu)化方法,來優(yōu)化改進(jìn)模型,讓損失變?yōu)樽钚 ?/p>
訓(xùn)練神經(jīng)網(wǎng)絡(luò)模型的目標(biāo)就是把偏差損失降為最小,機(jī)器學(xué)習(xí)就是一批一批數(shù)據(jù)反復(fù)分析計(jì)算反復(fù)嘗試,不斷的利用優(yōu)化方法,想盡辦法把Loss的值降到最小的過程。
優(yōu)化方法設(shè)計(jì)的越好好,損失也就越少,精度也就越高。
loss = tf.losses.sparse_softmax_cross_entropy(labels=labels, logits=logits)
這里我們使用了tensorflow提供的稀疏柔性最大交叉熵sparse_softmax_cross_entropy來計(jì)算損失程度,它對(duì)于分類問題很有效,DNNClassifier也使用了這個(gè)方法。
訓(xùn)練Train
我們?cè)谟?xùn)練部分代碼中,創(chuàng)建了優(yōu)化器optimizer,然后使用它嘗試將我們的損失函數(shù)loss變?yōu)樽钚inimize:
if mode == tf.estimator.ModeKeys.TRAIN:
optimizer = tf.train.AdagradOptimizer(learning_rate=0.1) #用它優(yōu)化損失函數(shù),達(dá)到損失最少精度最高
train_op = optimizer.minimize(loss, global_step=tf.train.get_global_step()) #執(zhí)行優(yōu)化!
return tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op)
評(píng)價(jià)Evaluate
我們使用下面的代碼來評(píng)價(jià)預(yù)測(cè)結(jié)果prediction和test數(shù)據(jù)中植物學(xué)家標(biāo)記的數(shù)據(jù)是否足夠吻合:
accuracy = tf.metrics.accuracy(labels=labels,
predictions=predicted_classes,
name='acc_op') #計(jì)算精度
metrics = {'accuracy': accuracy} #返回格式
tf.summary.scalar('accuracy', accuracy[1]) #僅為了后面圖表統(tǒng)計(jì)使用
if mode == tf.estimator.ModeKeys.EVAL:
return tf.estimator.EstimatorSpec(mode, loss=loss, eval_metric_ops=metrics)
因?yàn)槲覀兿M軌蛟u(píng)價(jià)后知道模型的精度,所以首先使用tf.metrics.accuracy方法對(duì)比植物學(xué)家的標(biāo)記labels和批量預(yù)測(cè)結(jié)果predicted_classes([[-1.3,2.6,-0.9],...]),
導(dǎo)入數(shù)據(jù)
相關(guān)文件可以從百度云這里下載 密碼:y3id
經(jīng)過上面的過程,我們創(chuàng)建了估算分類器的核心部分model_fn,接下來我們繼續(xù)添加以下代碼,導(dǎo)入數(shù)據(jù)備用。具體解釋請(qǐng)參照鳶尾花iris案例
import os
import pandas as pd
FUTURES = ['SepalLength', 'SepalWidth','PetalLength', 'PetalWidth', 'Species']
SPECIES = ['Setosa', 'Versicolor', 'Virginica']
dir_path = os.path.dirname(os.path.realpath(__file__))
train_path=os.path.join(dir_path,'iris_training.csv')
test_path=os.path.join(dir_path,'iris_test.csv')
train = pd.read_csv(train_path, names=FUTURES, header=0)
train_x, train_y = train, train.pop('Species')
test = pd.read_csv(test_path, names=FUTURES, header=0)
test_x, test_y = test, test.pop('Species')
創(chuàng)建分類器
繼續(xù)添加代碼,使用model_fn來生成自定義分類器(請(qǐng)注意最后幾行):
feature_columns = []
for key in train_x.keys():
feature_columns.append(tf.feature_column.numeric_column(key=key))
#創(chuàng)建自定義分類器
classifier = tf.estimator.Estimator(
model_fn=my_model, #注意這里!
params={
'feature_columns': feature_columns,
'hidden_units': [10, 10],
'n_classes': 3,
})
訓(xùn)練模型
添加下面代碼開始訓(xùn)練模型
#針對(duì)訓(xùn)練的喂食函數(shù)
batch_size=100
def train_input_fn(features, labels, batch_size):
dataset = tf.data.Dataset.from_tensor_slices((dict(features), labels))
dataset = dataset.shuffle(1000).repeat().batch(batch_size) #每次隨機(jī)調(diào)整數(shù)據(jù)順序
return dataset.make_one_shot_iterator().get_next()
#開始訓(xùn)練
classifier.train(
input_fn=lambda:train_input_fn(train_x, train_y, 100),
steps=1000)
評(píng)價(jià)模型
添加下面的代碼可以對(duì)模型進(jìn)行評(píng)價(jià)并打印出精度
#針對(duì)測(cè)試的喂食函數(shù)
def eval_input_fn(features, labels, batch_size):
features=dict(features)
inputs=(features,labels)
dataset = tf.data.Dataset.from_tensor_slices(inputs)
dataset = dataset.batch(batch_size)
# return dataset
return dataset.make_one_shot_iterator().get_next()
#評(píng)估我們訓(xùn)練出來的模型質(zhì)量
eval_result = classifier.evaluate(
input_fn=lambda:eval_input_fn(test_x, test_y,batch_size))
print(eval_result)
進(jìn)行預(yù)測(cè)
添加以下代碼讓用我們的模型可以進(jìn)行交互預(yù)測(cè)
#支持100次循環(huán)對(duì)新數(shù)據(jù)進(jìn)行分類預(yù)測(cè)
for i in range(0,100):
print('\nPlease enter features: SepalLength,SepalWidth,PetalLength,PetalWidth')
a,b,c,d = map(float, input().split(',')) #捕獲用戶輸入的數(shù)字
predict_x = {
'SepalLength': [a],
'SepalWidth': [b],
'PetalLength': [c],
'PetalWidth': [d],
}
#進(jìn)行預(yù)測(cè)
predictions = classifier.predict(
input_fn=lambda:eval_input_fn(predict_x,
labels=[0,],
batch_size=batch_size))
#預(yù)測(cè)結(jié)果是數(shù)組,盡管實(shí)際我們只有一個(gè)
for pred_dict in predictions:
class_id = pred_dict['class_ids'][0]
probability = pred_dict['probabilities'][class_id]
print(SPECIES[class_id],100 * probability)
模型的恢復(fù)與保存設(shè)置
修改創(chuàng)建估算分類器的代碼設(shè)置model_dir模型保存與自動(dòng)恢復(fù),并設(shè)定日志打印
tf.logging.set_verbosity(tf.logging.INFO)
models_path=os.path.join(dir_path,'mymodels/')
#創(chuàng)建自定義分類器
classifier = tf.estimator.Estimator(
model_fn=my_model_fn,
model_dir=models_path,
params={
'feature_columns': feature_columns,
'hidden_units': [10, 10],
'n_classes': 3,
})
TensorBoard信息板
打開新的命令行工具窗口,使用下面的命令啟動(dòng)信息板:
tensorboard --logdir=~/desktop/iris/mymodels
這里的~/desktop/iris/models應(yīng)該和上面配置的model_dir=models_path完全一致,正常情況會(huì)輸出很多信息,并在最后顯示類似下面的提示
TensorBoard 1.6.0 at http://xxx-xxx-xxx.local:6006 (Press CTRL+C to quit)
把這段http://xxx-xxx-xxx.local:6006復(fù)制到瀏覽器窗口,或者復(fù)制http://localhost:6006/就可以打開TensorBoard信息板,這里包含了很多關(guān)于模型的性能質(zhì)量等方面的圖表:

關(guān)于TensorBoard更多內(nèi)容可以點(diǎn)右上角的問號(hào)打開Github上的項(xiàng)目詳細(xì)說明。
上面的代碼和相關(guān)文件可以從百度云這里下載 密碼:y3id
探索人工智能的新邊界
如果您發(fā)現(xiàn)文章錯(cuò)誤,請(qǐng)不吝留言指正;
如果您覺得有用,請(qǐng)點(diǎn)喜歡;
如果您覺得很有用,感謝轉(zhuǎn)發(fā)~
END