【深度學(xué)習(xí)系列】PaddlePaddle之手寫數(shù)字識別

本文同步于知乎專欄-技術(shù)雜記博客園

上周在搜索關(guān)于深度學(xué)習(xí)分布式運行方式的資料時佛析,無意間搜到了paddlepaddle,發(fā)現(xiàn)這個框架的分布式訓(xùn)練方案做的還挺不錯的彪蓬,想跟大家分享一下寸莫。不過呢,這塊內(nèi)容太復(fù)雜了档冬,所以就簡單的介紹一下paddlepaddle的第一個“hello word”程序----mnist手寫數(shù)字識別膘茎。下一次再介紹用PaddlePaddle做分布式訓(xùn)練的方案。其實之前也寫過一篇用CNN識別手寫數(shù)字集的文章(鏈接戳這里~)酷誓,是用keras實現(xiàn)的辽狈,這次用了paddlepaddle后,正好可以簡單對比一下兩個框架的優(yōu)劣呛牲。

什么是PaddlePaddle刮萌?

PaddlePaddle是百度推出的一個深度學(xué)習(xí)框架,可能大多數(shù)人平常用的比較多的一般是tensorflow娘扩,caffe着茸,mxnet等,但其實PaddlePaddle也是一個非常不錯的框架(據(jù)說以前叫Paddle琐旁,現(xiàn)在改名叫PaddlePaddle涮阔,不知道為啥總覺得有股莫名的萌點)

PaddlePaddle能做什么?

傳統(tǒng)的基本都能做,尤其對NLP的支持很好灰殴,譬如情感分析敬特,word embedding,語言模型等牺陶,反正你想得到的伟阔,常見的都可以用它來試一試~

PaddlePaddle的安裝

不得不吐槽一下PaddlePaddle的安裝,官網(wǎng)上說“PaddlePaddle目前唯一官方支持的運行的方式是Docker容器”掰伸,而docker其實在國內(nèi)還并不是特別的流行皱炉,之前遇到的所有的框架,都有很多種安裝方式狮鸭,非常方便合搅,所以這個唯一支持docker讓人覺得非常詭異 = =!不過偶然試了一下歧蕉,居然可以用pip install灾部,不過為啥官網(wǎng)上沒有寫呢?所以惯退,對于新手來說赌髓,最簡單的安裝方式就是:

CPU版本安裝

pip install paddlepaddle

GPU版本安裝

pip install paddlepaddle-gpu

用PaddlePaddle實現(xiàn)手寫數(shù)字識別

訓(xùn)練步驟

傳統(tǒng)的方式這次就不展開講了,為了對比我們還是用CNN來進行訓(xùn)練。PaddlePaddle訓(xùn)練一次模型完整的過程可以如下幾個步驟:

導(dǎo)入數(shù)據(jù)---->定義網(wǎng)絡(luò)結(jié)構(gòu)---->訓(xùn)練模型---->保存模型---->測試結(jié)果

下面春弥,我直接用代碼來展示訓(xùn)練的過程(以后代碼都會放在github里):

#coding:utf-8
import os
from PIL import Image
import numpy as np
import paddle.v2 as paddle

# 設(shè)置是否用gpu呛哟,0為否,1為是
with_gpu = os.getenv('WITH_GPU', '0') != '1'

# 定義網(wǎng)絡(luò)結(jié)構(gòu)
def convolutional_neural_network_org(img):
    # 第一層卷積層
    conv_pool_1 = paddle.networks.simple_img_conv_pool(
        input=img,
        filter_size=5,
        num_filters=20,
        num_channel=1,
        pool_size=2,
        pool_stride=2,
        act=paddle.activation.Relu())
    # 第二層卷積層
    conv_pool_2 = paddle.networks.simple_img_conv_pool(
        input=conv_pool_1,
        filter_size=5,
        num_filters=50,
        num_channel=20,
        pool_size=2,
        pool_stride=2,
        act=paddle.activation.Relu())
    # 全連接層
    predict = paddle.layer.fc(
        input=conv_pool_2, size=10, act=paddle.activation.Softmax())
    return predict

def main():
    # 初始化定義跑模型的設(shè)備
    paddle.init(use_gpu=with_gpu, trainer_count=1)

    # 讀取數(shù)據(jù)
    images = paddle.layer.data(
        name='pixel', type=paddle.data_type.dense_vector(784))
    label = paddle.layer.data(
        name='label', type=paddle.data_type.integer_value(10))

    # 調(diào)用之前定義的網(wǎng)絡(luò)結(jié)構(gòu)
    predict = convolutional_neural_network(images)

    # 定義損失函數(shù)
    cost = paddle.layer.classification_cost(input=predict, label=label)

    # 指定訓(xùn)練相關(guān)的參數(shù)
    parameters = paddle.parameters.create(cost)

    # 定義訓(xùn)練方法
    optimizer = paddle.optimizer.Momentum(
        learning_rate=0.1 / 128.0,
        momentum=0.9,
        regularization=paddle.optimizer.L2Regularization(rate=0.0005 * 128))

    # 訓(xùn)練模型
    trainer = paddle.trainer.SGD(
        cost=cost, parameters=parameters, update_equation=optimizer)

    lists = []

    # 定義event_handler匿沛,輸出訓(xùn)練過程中的結(jié)果
    def event_handler(event):
        if isinstance(event, paddle.event.EndIteration):
            if event.batch_id % 100 == 0:
                print "Pass %d, Batch %d, Cost %f, %s" % (
                    event.pass_id, event.batch_id, event.cost, event.metrics)
        if isinstance(event, paddle.event.EndPass):
            # 保存參數(shù)
            with open('params_pass_%d.tar' % event.pass_id, 'w') as f:
                parameters.to_tar(f)

            result = trainer.test(reader=paddle.batch(
                paddle.dataset.mnist.test(), batch_size=128))
            print "Test with Pass %d, Cost %f, %s\n" % (
                event.pass_id, result.cost, result.metrics)
            lists.append((event.pass_id, result.cost,
                          result.metrics['classification_error_evaluator']))

    trainer.train(
        reader=paddle.batch(
            paddle.reader.shuffle(paddle.dataset.mnist.train(), buf_size=8192),
            batch_size=128),
        event_handler=event_handler,
        num_passes=10)

    # 找到訓(xùn)練誤差最小的一次結(jié)果
    best = sorted(lists, key=lambda list: float(list[1]))[0]
    print 'Best pass is %s, testing Avgcost is %s' % (best[0], best[1])
    print 'The classification accuracy is %.2f%%' % (100 - float(best[2]) * 100)

    # 加載數(shù)據(jù)   
    def load_image(file):
        im = Image.open(file).convert('L')
        im = im.resize((28, 28), Image.ANTIALIAS)
        im = np.array(im).astype(np.float32).flatten()
        im = im / 255.0
        return im

    # 測試結(jié)果
    test_data = []
    cur_dir = os.path.dirname(os.path.realpath(__file__))
    test_data.append((load_image(cur_dir + '/image/infer_3.png'), ))

    probs = paddle.infer(
        output_layer=predict, parameters=parameters, input=test_data)
    lab = np.argsort(-probs)  # probs and lab are the results of one batch data
    print "Label of image/infer_3.png is: %d" % lab[0][0]


if __name__ == '__main__':
    main()

上面的代碼看起來很長扫责,但結(jié)構(gòu)還是很清楚的。下面我們用實際數(shù)據(jù)測試一下逃呼,看一下效果到底怎么樣~

BaseLine版本

首先我用了官網(wǎng)給出的例子鳖孤,直接用最基本的CNN網(wǎng)絡(luò)結(jié)構(gòu)訓(xùn)練了一下,代碼如下:

def convolutional_neural_network_org(img):
    # 第一層卷積層
    conv_pool_1 = paddle.networks.simple_img_conv_pool(
        input=img,
        filter_size=5,
        num_filters=20,
        num_channel=1,
        pool_size=2,
        pool_stride=2,
        act=paddle.activation.Relu())
    # 第二層卷積層
    conv_pool_2 = paddle.networks.simple_img_conv_pool(
        input=conv_pool_1,
        filter_size=5,
        num_filters=50,
        num_channel=20,
        pool_size=2,
        pool_stride=2,
        act=paddle.activation.Relu())
    # 全連接層
    predict = paddle.layer.fc(
        input=conv_pool_2, size=10, act=paddle.activation.Softmax())
    return predict

輸出結(jié)果如下:

I1023 13:45:46.519075 34144 Util.cpp:166] commandline:  --use_gpu=True --trainer_count=1
[INFO 2017-10-23 13:45:52,667 layers.py:2539] output for __conv_pool_0___conv: c = 20, h = 24, w = 24, size = 11520
[INFO 2017-10-23 13:45:52,667 layers.py:2667] output for __conv_pool_0___pool: c = 20, h = 12, w = 12, size = 2880
[INFO 2017-10-23 13:45:52,668 layers.py:2539] output for __conv_pool_1___conv: c = 50, h = 8, w = 8, size = 3200
[INFO 2017-10-23 13:45:52,669 layers.py:2667] output for __conv_pool_1___pool: c = 50, h = 4, w = 4, size = 800
I1023 13:45:52.675750 34144 GradientMachine.cpp:85] Initing parameters..
I1023 13:45:52.686153 34144 GradientMachine.cpp:92] Init parameters done.
Pass 0, Batch 0, Cost 3.048408, {'classification_error_evaluator': 0.890625}
Pass 0, Batch 100, Cost 0.188828, {'classification_error_evaluator': 0.0546875}
Pass 0, Batch 200, Cost 0.075183, {'classification_error_evaluator': 0.015625}
Pass 0, Batch 300, Cost 0.070798, {'classification_error_evaluator': 0.015625}
Pass 0, Batch 400, Cost 0.079673, {'classification_error_evaluator': 0.046875}
Test with Pass 0, Cost 0.074587, {'classification_error_evaluator': 0.023800000548362732}
···
···
···
Pass 4, Batch 0, Cost 0.032454, {'classification_error_evaluator': 0.015625}
Pass 4, Batch 100, Cost 0.021028, {'classification_error_evaluator': 0.0078125}
Pass 4, Batch 200, Cost 0.020458, {'classification_error_evaluator': 0.0}
Pass 4, Batch 300, Cost 0.046728, {'classification_error_evaluator': 0.015625}
Pass 4, Batch 400, Cost 0.030264, {'classification_error_evaluator': 0.015625}
Test with Pass 4, Cost 0.035841, {'classification_error_evaluator': 0.01209999993443489}

Best pass is 4, testing Avgcost is 0.0358410408473
The classification accuracy is 98.79%
Label of image/infer_3.png is: 3

real    0m31.565s
user    0m20.996s
sys    0m15.891s

可以看到抡笼,第一行輸出選擇的設(shè)備是否是gpu苏揣,這里我選擇的是gpu,所以等于1推姻,如果是cpu平匈,就是0。接下來四行輸出的是網(wǎng)絡(luò)結(jié)構(gòu)藏古,然后開始輸出訓(xùn)練結(jié)果增炭,訓(xùn)練結(jié)束,我們把這幾次迭代中誤差最小的結(jié)果輸出來拧晕,98.79%隙姿,效果還是很不錯的,畢竟只迭代了5次厂捞。最后看一下輸出時間输玷,非常快靡馁,約31秒欲鹏。然而這個結(jié)果我并不是特別滿意,因為之前用keras做的時候調(diào)整的網(wǎng)絡(luò)模型訓(xùn)練往后準(zhǔn)確率能夠達到99.72%奈嘿,不過速度非常慢貌虾,迭代69次大概需要30分鐘左右,所以我覺得這個網(wǎng)絡(luò)結(jié)構(gòu)還是可以改進一下的裙犹,所以我對這個網(wǎng)絡(luò)結(jié)構(gòu)改進了一下,請看改進版衔憨。

改進版

def convolutional_neural_network(img):
    # 第一層卷積層
    conv_pool_1 = paddle.networks.simple_img_conv_pool(
        input=img,
        filter_size=5,
        num_filters=20,
        num_channel=1,
        pool_size=2,
        pool_stride=2,
        act=paddle.activation.Relu())
    # 加一層dropout層
    drop_1 = paddle.layer.dropout(input=conv_pool_1, dropout_rate=0.2)
    # 第二層卷積層
    conv_pool_2 = paddle.networks.simple_img_conv_pool(
        input=drop_1,
        filter_size=5,
        num_filters=50,
        num_channel=20,
        pool_size=2,
        pool_stride=2,
        act=paddle.activation.Relu())
    # 加一層dropout層
    drop_2 = paddle.layer.dropout(input=conv_pool_2, dropout_rate=0.5)
    # 全連接層
    fc1 = paddle.layer.fc(input=drop_2, size=10, act=paddle.activation.Linear())
    bn = paddle.layer.batch_norm(input=fc1,act=paddle.activation.Relu(),
         layer_attr=paddle.attr.Extra(drop_rate=0.2))
    predict = paddle.layer.fc(input=bn, size=10, act=paddle.activation.Softmax())
    return predict

在改進版里我們加了一些dropout層來避免過擬合叶圃。分別在第一層卷積層和第二層卷積層后加了dropout,閾值設(shè)為0.5践图。改變網(wǎng)絡(luò)結(jié)構(gòu)也非常簡單掺冠,直接在定義的網(wǎng)絡(luò)結(jié)構(gòu)函數(shù)里對模型進行修改即可,這一點其實和keras的網(wǎng)絡(luò)結(jié)構(gòu)定義方式還是挺像的,易用性很高德崭。下面來看看效果:

I1023 14:01:51.653827 34244 Util.cpp:166] commandline:  --use_gpu=True --trainer_count=1
[INFO 2017-10-23 14:01:57,830 layers.py:2539] output for __conv_pool_0___conv: c = 20, h = 24, w = 24, size = 11520
[INFO 2017-10-23 14:01:57,831 layers.py:2667] output for __conv_pool_0___pool: c = 20, h = 12, w = 12, size = 2880
[INFO 2017-10-23 14:01:57,832 layers.py:2539] output for __conv_pool_1___conv: c = 50, h = 8, w = 8, size = 3200
[INFO 2017-10-23 14:01:57,833 layers.py:2667] output for __conv_pool_1___pool: c = 50, h = 4, w = 4, size = 800
I1023 14:01:57.842871 34244 GradientMachine.cpp:85] Initing parameters..
I1023 14:01:57.854014 34244 GradientMachine.cpp:92] Init parameters done.
Pass 0, Batch 0, Cost 2.536199, {'classification_error_evaluator': 0.875}
Pass 0, Batch 100, Cost 1.668236, {'classification_error_evaluator': 0.515625}
Pass 0, Batch 200, Cost 1.024846, {'classification_error_evaluator': 0.375}
Pass 0, Batch 300, Cost 1.086315, {'classification_error_evaluator': 0.46875}
Pass 0, Batch 400, Cost 0.767804, {'classification_error_evaluator': 0.25}
Pass 0, Batch 500, Cost 0.545784, {'classification_error_evaluator': 0.1875}
Pass 0, Batch 600, Cost 0.731662, {'classification_error_evaluator': 0.328125}
···
···
···
Pass 49, Batch 0, Cost 0.415184, {'classification_error_evaluator': 0.09375}
Pass 49, Batch 100, Cost 0.067616, {'classification_error_evaluator': 0.0}
Pass 49, Batch 200, Cost 0.161415, {'classification_error_evaluator': 0.046875}
Pass 49, Batch 300, Cost 0.202667, {'classification_error_evaluator': 0.046875}
Pass 49, Batch 400, Cost 0.336043, {'classification_error_evaluator': 0.140625}
Pass 49, Batch 500, Cost 0.290948, {'classification_error_evaluator': 0.125}
Pass 49, Batch 600, Cost 0.223433, {'classification_error_evaluator': 0.109375}
Pass 49, Batch 700, Cost 0.217345, {'classification_error_evaluator': 0.0625}
Pass 49, Batch 800, Cost 0.163140, {'classification_error_evaluator': 0.046875}
Pass 49, Batch 900, Cost 0.203645, {'classification_error_evaluator': 0.078125}
Test with Pass 49, Cost 0.033639, {'classification_error_evaluator': 0.008100000210106373}

Best pass is 48, testing Avgcost is 0.0313018567383
The classification accuracy is 99.28%
Label of image/infer_3.png is: 3

real    5m3.151s
user    4m0.052s
sys    1m8.084s

從上面的數(shù)據(jù)來看斥黑,這個效果還是很不錯滴,對比之前用keras訓(xùn)練的效果來看眉厨,結(jié)果如下:

表1 mnist手寫數(shù)字實現(xiàn)對比

可以看到這個速度差異是很大的了锌奴,在準(zhǔn)確率差不多的情況下,訓(xùn)練時間幾乎比原來縮短了六倍憾股,網(wǎng)絡(luò)結(jié)構(gòu)也相對簡單鹿蜀,說明需要調(diào)整的參數(shù)也少了很多。

總結(jié)

paddlepaddle用起來還是很方便的服球,不論是定義網(wǎng)絡(luò)結(jié)構(gòu)還是訓(xùn)練速度茴恰,都值得一提,然而我個人的體驗中斩熊,認為最值得說的是這幾點:

1.導(dǎo)入數(shù)據(jù)方便往枣。這次訓(xùn)練的手寫數(shù)字識別數(shù)據(jù)量比較小,但是如果想要添加數(shù)據(jù)粉渠,也非常方便婉商,直接添加到相應(yīng)目錄下。

2.event_handler機制渣叛,可以自定義訓(xùn)練結(jié)果輸出內(nèi)容丈秩。之前用的keras,以及mxnet等都是已經(jīng)封裝好的函數(shù)淳衙,輸出信息都是一樣的蘑秽,這里paddlepaddle把這個函數(shù)并沒有完全封裝,而是讓我們用戶自定義輸出的內(nèi)容箫攀,可以方便我們減少冗余的信息肠牲,增加一些模型訓(xùn)練的細節(jié)的輸出,也可以用相應(yīng)的函數(shù)畫出模型收斂的圖片靴跛,可視化收斂曲線缀雳。

3.速度快。上面的例子已經(jīng)證明了paddlepaddle的速度梢睛,并且在提升速度的同時肥印,模型準(zhǔn)確度也與最優(yōu)結(jié)果相差不多,這對于我們訓(xùn)練海量數(shù)據(jù)的模型是一個極大的優(yōu)勢熬稀深碱!

然而,paddlepaddle也有幾點讓我用的有點難受藏畅,譬如文檔太少了啊敷硅,報錯了上網(wǎng)上搜沒啥結(jié)果啊等等,不過我覺得這個應(yīng)該不是大問題,以后用的人多了以后肯定相關(guān)資料也會更多绞蹦。所以一直很疑惑力奋,為啥paddlepaddle不火呢?安裝詭異是一個吐槽點幽七,但其實還是很優(yōu)秀的一個開源軟件景殷,尤其是最值得說的分布式訓(xùn)練方式,多機多卡的設(shè)計是非常優(yōu)秀的锉走,本篇沒有講滨彻,下次講講如何用paddlepaddle做單機單卡,單機多卡挪蹭,多機單卡和多機多卡的訓(xùn)練方式來訓(xùn)練模型亭饵,大家多多用起來呀~可以多交流呀

ps:由于paddlepaddle的文檔實在太少了,官網(wǎng)的文章理論介紹的比較多梁厉,網(wǎng)上的博文大多數(shù)都是幾個經(jīng)典例子來回跑辜羊,所以我打算寫個系列,跟實戰(zhàn)相關(guān)的词顾,不再只有深度學(xué)習(xí)的“hello world”程序八秃,這次用“hello world”做個引子,下篇開始寫點干貨哈哈~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末肉盹,一起剝皮案震驚了整個濱河市昔驱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌上忍,老刑警劉巖骤肛,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異窍蓝,居然都是意外死亡腋颠,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門吓笙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來淑玫,“玉大人,你說我怎么就攤上這事面睛⌒踺铮” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵侮穿,是天一觀的道長歌径。 經(jīng)常有香客問我,道長亲茅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮克锣,結(jié)果婚禮上茵肃,老公的妹妹穿的比我還像新娘。我一直安慰自己袭祟,他們只是感情好验残,可當(dāng)我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著巾乳,像睡著了一般您没。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上胆绊,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天氨鹏,我揣著相機與錄音,去河邊找鬼压状。 笑死仆抵,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的种冬。 我是一名探鬼主播镣丑,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼娱两!你這毒婦竟也來了莺匠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤十兢,失蹤者是張志新(化名)和其女友劉穎趣竣,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體纪挎,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡期贫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了异袄。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片通砍。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖烤蜕,靈堂內(nèi)的尸體忽然破棺而出封孙,到底是詐尸還是另有隱情,我是刑警寧澤讽营,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布虎忌,位于F島的核電站,受9級特大地震影響橱鹏,放射性物質(zhì)發(fā)生泄漏膜蠢。R本人自食惡果不足惜堪藐,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望挑围。 院中可真熱鬧礁竞,春花似錦、人聲如沸杉辙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蜘矢。三九已至狂男,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間品腹,已是汗流浹背岖食。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留珍昨,地道東北人县耽。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像镣典,于是被迫代替她去往敵國和親兔毙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,828評論 2 345