Tensorflow-Estimator-自定義估算器

這篇文章介紹自定義一個估算器(分類器)Estimator的完整流程痢法。
請先參照鳶尾花iris案例并完成練習塘装。


自定義Custom Estimator和預制Pre-made Estimator

在上面iris的案例中我們使用了tensorflow里面自帶的深度神經(jīng)網(wǎng)絡分類器tf.estimator.DNNClassifie赴涵。這些tensorflow自帶的estimator稱為預制估算器Pre-made Estimator(預創(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()方法生成米罚,能夠像預制Estimator一樣使用。



結構概覽

從表面看丈探,我們的Estimator應該具有DNNClassifier一樣的功能

  • 創(chuàng)建的時候接收一些參數(shù)录择,如feature_columns、hidden_units碗降、n_classes等
  • 具有train()隘竭、evaluate()、predict()三個方法用來訓練讼渊、評價动看、預測

如上所說,我們使用 tf.estimator.Estimator()方法來生成自定義Estimator爪幻,它的語法格式是

tf.estimator.Estimator(
    model_fn, #模型函數(shù)
    model_dir=None, #存儲目錄
    config=None, #設置參數(shù)對象
    params=None, #超參數(shù)菱皆,將傳遞給model_fn使用
    warm_start_from=None #熱啟動目錄路徑
)

模型函數(shù)model_fn是唯一沒有默認值的參數(shù)赋兵,它也是自定義Estimator最關鍵的部分,包含了最核心的算法搔预。model_fn需要一個能夠進行運算的函數(shù)霹期,它的樣子應該長成這樣

my_model(
  features, #輸入的特征數(shù)據(jù)
  labels, #輸入的標簽數(shù)據(jù)
  mode, #train、evaluate或predict
  params #超參數(shù)拯田,對應上面Estimator傳來的參數(shù)
)

神經(jīng)網(wǎng)絡層Layers

model_fn應該怎么運作历造?下圖展示了iris案例的情況



從這個圖中我沒看到的結構:

  • 輸入層Input Layer,數(shù)據(jù)從這里進入
  • 隱藏層Hidden Layer船庇,2個吭产,每層包含多個節(jié)點,數(shù)據(jù)流經(jīng)這里鸭轮,被推測規(guī)律
  • 輸出層Output Layer臣淤,將推測的結果整理顯示出來

我們并不需要手工實現(xiàn)隱藏層的算法和工作原理,Tensorflow已經(jīng)為我們設計好窃爷。我們需要的只是創(chuàng)建這些神經(jīng)網(wǎng)絡層邑蒋,并確保它們按照正常的順序連接起來,至于其中如何推算演繹的魔法就完全交給tensorflow就可以了按厘。

mode_fn需要完成的就是創(chuàng)建和組織這些神經(jīng)層医吊。


編寫model_fn

對應我們創(chuàng)建Estimator時候的參數(shù)

classifier = tf.estimator.DNNClassifier(
    feature_columns=feature_columns,
    hidden_units=[10, 10],
    n_classes=3,
    model_dir=models_path,
    config=ckpt_config) 

這些參數(shù)都會被Estimator打包放在params超參數(shù)中,傳遞給model_fn逮京,所以我們用下面的代碼在model_fn內創(chuàng)建網(wǎng)絡層

import tensorflow as tf

#自定義模型函數(shù)
def my_model_fn(features,labels,mode,params):
    #輸入層,feature_columns對應Classifier(feature_columns=...)
    net = tf.feature_column.input_layer(features, params['feature_columns'])
    
    #隱藏層,hidden_units對應Classifier(unit=[10,10])卿堂,2個各含10節(jié)點的隱藏層
    for units in params['hidden_units']:
        net = tf.layers.dense(net, units=units, activation=tf.nn.relu)
    
    #輸出層,n_classes對應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']里面,稍后它會被繼續(xù)傳遞到隱藏層hidden layer:


輸入層Input Layer

隱藏層Hidden Layer

我們使用循環(huán)為hidden_unit列表([10,10])創(chuàng)建了2個隱藏圖層,每個圖層的神經(jīng)元節(jié)點unit都等于10.

    for units in params['hidden_units']:
        net = tf.layers.dense(net, units=units, activation=tf.nn.relu)

我們注意到上面的輸入層叫做net(暫時叫net0)策严,for循環(huán)里的隱藏層也叫net(暫叫net1)而且參數(shù)里還有net(net2),示意代碼如下

#僅供示意
net0 = tf.feature_column.input_layer...
for units ...
        net1 = tf.layers.dense(net2, ...)

實際運行到隱藏層第一層(for循環(huán)第一次)的時候穗慕,我們創(chuàng)建隱藏層net1,并把net0作為參數(shù)輸入到net1的,也就是隱藏第一層中關聯(lián)了輸入層:

input_net0=...#創(chuàng)建輸入層
hidden_net1=tf.layers.dense(input_net0,...) #創(chuàng)建隱藏層1

然后for第二次循環(huán)的時候我們又關聯(lián)了第一個隱藏層hidden_net1:

hidden_net2=tf.layers.dense(hidden_net1,...) #創(chuàng)建隱藏層2

這樣逐層傳遞就形成了鏈條享钞,數(shù)據(jù)沿著鏈條進行流動Flow和處理

intputLayer - hiddenLayer1 - hiddenLayer2 - ...
隱藏層Hidden layer

輸出層Output Layer

我們使用了這行代碼創(chuàng)建輸出層,請注意net揍诽!

    logits = tf.layers.dense(net, params['n_classes'], activation=None)

仍然是鏈條的延續(xù)!
但是activation這里改為了None栗竖,不再激活后續(xù)的部分暑脆,所以輸出層就是鏈條的終點。


輸出層Output Layer

請注意這里的[-1.3,2.6,-0.9]表示了某朵花的測量數(shù)據(jù)分別屬于三種分類的可能性狐肢,但是這里的數(shù)字很奇怪添吗,甚至還有負數(shù)...稍后我們會對它們進行轉化。


訓練train份名、評價evaluate和預測predict

前面我們知道碟联,自定義的估算分類器必須能夠用來執(zhí)行my_classifier.train()妓美、my_classifier.evaluate()、my_classifier.predict()三個方法鲤孵。

但實際上壶栋,它們都是model_fn這一個函數(shù)的分身!

上面出現(xiàn)的model_fn語法:

my_model(
  features, #輸入的特征數(shù)據(jù)
  labels, #輸入的標簽數(shù)據(jù)
  mode, #train普监、evaluate或predict
  params #超參數(shù)贵试,對應上面Estimator傳來的參數(shù)
)

注意第三個參數(shù)mode,如果它等于"TRAIN"我們就執(zhí)行訓練:

#示意代碼
my_model(..,..,"TRAIN",...)

如果是“EVAL”就執(zhí)行評價凯正,“PREDICT”就執(zhí)行預測毙玻。

我們修改my_model代碼來實現(xiàn)這三個功能:

def my_model_fn(features,labels,mode,params):
    #輸入層,feature_columns對應Classifier(feature_columns=...)
    net = tf.feature_column.input_layer(features, params['feature_columns'])
    
    #隱藏層,hidden_units對應Classifier(unit=[10,10]),2個各含10節(jié)點的隱藏層
    for units in params['hidden_units']:
        net = tf.layers.dense(net, units=units, activation=tf.nn.relu)
    
    #輸出層廊散,n_classes對應3種鳶尾花
    logits = tf.layers.dense(net, params['n_classes'], activation=None)
   
    #預測
    predicted_classes = tf.argmax(logits, 1) #預測的結果中最大值即種類
    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)
    
    #訓練
    if mode == tf.estimator.ModeKeys.TRAIN:
        optimizer = tf.train.AdagradOptimizer(learning_rate=0.1) #用它優(yōu)化損失函數(shù)桑滩,達到損失最少精度最高
        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)      
    
    #評價
    accuracy = tf.metrics.accuracy(labels=labels,
                                   predictions=predicted_classes,
                                   name='acc_op') #計算精度
    metrics = {'accuracy': accuracy} #返回格式
    tf.summary.scalar('accuracy', accuracy[1]) #僅為了后面圖表統(tǒng)計使用
    if mode == tf.estimator.ModeKeys.EVAL:
        return tf.estimator.EstimatorSpec(mode, loss=loss, eval_metric_ops=metrics) 

如上面所示允睹,請將預測Predict需要放在最先編寫运准,否則可以引發(fā)后續(xù)錯誤。

下面我們分別詳解三個方法的代碼


預測Predict

因為預測最后我們需要返回花的種類label擂找,還希望知道這個預測有多精確戳吝,所以在預測部分的代碼里面,首先取到三種花可能性最大的一個predicted_classes即[-1.3,2.6,-0.9]中的2.6贯涎;然后把它轉成列表格式[[2.6]];同時把logit得到的[-1.3,2.6,-0.9]轉化為表示0~1可能性的小數(shù)[0.01926995 0.95198274 0.02874739]

predicted_classes = tf.argmax(logits, 1) #預測的結果中最大值即種類
    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的是一個EstimatorSpec對象慢洋,下面的訓練predict和評價evaluate也都返回EstimatorSpec形式的對象塘雳,但是參數(shù)不同,請留意普筹。

我們可以使用以下代碼在單獨文件測試tf.newaxis和tf.nn.softmax對數(shù)據(jù)轉化的作用

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)絡的重要概念败明,簡單說,它能夠計算出我們模型的偏差程度太防,結果越大妻顶,我們的模型就偏差越大、離正確也遠蜒车、也越不準確讳嘱、越糟糕。

為了降低損失酿愧,我們可以使用更多更好的數(shù)據(jù)沥潭,還可以設計更好的優(yōu)化方法,來優(yōu)化改進模型嬉挡,讓損失變?yōu)樽钚 ?/p>

訓練神經(jīng)網(wǎng)絡模型的目標就是把偏差損失降為最小钝鸽,機器學習就是一批一批數(shù)據(jù)反復分析計算反復嘗試汇恤,不斷的利用優(yōu)化方法,想盡辦法把Loss的值降到最小的過程拔恰。

優(yōu)化方法設計的越好好因谎,損失也就越少,精度也就越高颜懊。
    loss = tf.losses.sparse_softmax_cross_entropy(labels=labels, logits=logits)

這里我們使用了tensorflow提供的稀疏柔性最大交叉熵sparse_softmax_cross_entropy來計算損失程度蓝角,它對于分類問題很有效,DNNClassifier也使用了這個方法饭冬。


訓練Train

我們在訓練部分代碼中使鹅,創(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ù)昌抠,達到損失最少精度最高
        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)      

評價Evaluate

我們使用下面的代碼來評價預測結果prediction和test數(shù)據(jù)中植物學家標記的數(shù)據(jù)是否足夠吻合:

    accuracy = tf.metrics.accuracy(labels=labels,
                                   predictions=predicted_classes,
                                   name='acc_op') #計算精度
    metrics = {'accuracy': accuracy} #返回格式
    tf.summary.scalar('accuracy', accuracy[1]) #僅為了后面圖表統(tǒng)計使用
    if mode == tf.estimator.ModeKeys.EVAL:
        return tf.estimator.EstimatorSpec(mode, loss=loss, eval_metric_ops=metrics) 

因為我們希望能夠評價后知道模型的精度,所以首先使用tf.metrics.accuracy方法對比植物學家的標記labels和批量預測結果predicted_classes([[-1.3,2.6,-0.9],...])炊苫,


導入數(shù)據(jù)

相關文件可以從百度云這里下載 密碼:y3id

經(jīng)過上面的過程裁厅,我們創(chuàng)建了估算分類器的核心部分model_fn,接下來我們繼續(xù)添加以下代碼侨艾,導入數(shù)據(jù)備用执虹。具體解釋請參照鳶尾花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來生成自定義分類器(請注意最后幾行):

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,
        })

訓練模型

添加下面代碼開始訓練模型

#針對訓練的喂食函數(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) #每次隨機調整數(shù)據(jù)順序
    return dataset.make_one_shot_iterator().get_next()

#開始訓練
classifier.train(
    input_fn=lambda:train_input_fn(train_x, train_y, 100),
    steps=1000)

評價模型

添加下面的代碼可以對模型進行評價并打印出精度

#針對測試的喂食函數(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()

#評估我們訓練出來的模型質量
eval_result = classifier.evaluate(
    input_fn=lambda:eval_input_fn(test_x, test_y,batch_size))

print(eval_result)

進行預測

添加以下代碼讓用我們的模型可以進行交互預測

#支持100次循環(huán)對新數(shù)據(jù)進行分類預測
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],
    }
    
    #進行預測
    predictions = classifier.predict(
        input_fn=lambda:eval_input_fn(predict_x,
                                      labels=[0,],
                                      batch_size=batch_size))    

    #預測結果是數(shù)組袋励,盡管實際我們只有一個
    for pred_dict in predictions:
        class_id = pred_dict['class_ids'][0]
        probability = pred_dict['probabilities'][class_id]
        print(SPECIES[class_id],100 * probability)

模型的恢復與保存設置

修改創(chuàng)建估算分類器的代碼設置model_dir模型保存與自動恢復,并設定日志打印

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信息板

打開新的命令行工具窗口,使用下面的命令啟動信息板:

tensorboard --logdir=~/desktop/iris/mymodels

這里的~/desktop/iris/models應該和上面配置的model_dir=models_path完全一致当叭,正常情況會輸出很多信息茬故,并在最后顯示類似下面的提示

TensorBoard 1.6.0 at http://xxx-xxx-xxx.local:6006 (Press CTRL+C to quit)

把這段http://xxx-xxx-xxx.local:6006復制到瀏覽器窗口,或者復制http://localhost:6006/就可以打開TensorBoard信息板,這里包含了很多關于模型的性能質量等方面的圖表:

關于TensorBoard更多內容可以點右上角的問號打開Github上的項目詳細說明蚁鳖。


上面的代碼和相關文件可以從百度云這里下載 密碼:y3id


探索人工智能的新邊界

如果您發(fā)現(xiàn)文章錯誤磺芭,請不吝留言指正;
如果您覺得有用醉箕,請點喜歡钾腺;
如果您覺得很有用,感謝轉發(fā)~


END

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末讥裤,一起剝皮案震驚了整個濱河市放棒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌坞琴,老刑警劉巖哨查,帶你破解...
    沈念sama閱讀 211,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異剧辐,居然都是意外死亡寒亥,警方通過查閱死者的電腦和手機邮府,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來溉奕,“玉大人褂傀,你說我怎么就攤上這事〖忧冢” “怎么了仙辟?”我有些...
    開封第一講書人閱讀 157,435評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長鳄梅。 經(jīng)常有香客問我叠国,道長,這世上最難降的妖魔是什么戴尸? 我笑而不...
    開封第一講書人閱讀 56,509評論 1 284
  • 正文 為了忘掉前任粟焊,我火速辦了婚禮,結果婚禮上孙蒙,老公的妹妹穿的比我還像新娘项棠。我一直安慰自己,他們只是感情好挎峦,可當我...
    茶點故事閱讀 65,611評論 6 386
  • 文/花漫 我一把揭開白布香追。 她就那樣靜靜地躺著,像睡著了一般坦胶。 火紅的嫁衣襯著肌膚如雪透典。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,837評論 1 290
  • 那天迁央,我揣著相機與錄音掷匠,去河邊找鬼。 笑死岖圈,一個胖子當著我的面吹牛,可吹牛的內容都是我干的钙皮。 我是一名探鬼主播蜂科,決...
    沈念sama閱讀 38,987評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼短条!你這毒婦竟也來了导匣?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,730評論 0 267
  • 序言:老撾萬榮一對情侶失蹤茸时,失蹤者是張志新(化名)和其女友劉穎贡定,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體可都,經(jīng)...
    沈念sama閱讀 44,194評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡缓待,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,525評論 2 327
  • 正文 我和宋清朗相戀三年蚓耽,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片旋炒。...
    茶點故事閱讀 38,664評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡步悠,死狀恐怖,靈堂內的尸體忽然破棺而出瘫镇,到底是詐尸還是另有隱情鼎兽,我是刑警寧澤,帶...
    沈念sama閱讀 34,334評論 4 330
  • 正文 年R本政府宣布铣除,位于F島的核電站谚咬,受9級特大地震影響,放射性物質發(fā)生泄漏尚粘。R本人自食惡果不足惜择卦,卻給世界環(huán)境...
    茶點故事閱讀 39,944評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望背苦。 院中可真熱鬧互捌,春花似錦、人聲如沸行剂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽厚宰。三九已至腌巾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間铲觉,已是汗流浹背澈蝙。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留撵幽,地道東北人灯荧。 一個月前我還...
    沈念sama閱讀 46,389評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像盐杂,于是被迫代替她去往敵國和親逗载。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,554評論 2 349

推薦閱讀更多精彩內容