前言

上一章我們通過學習線性迴歸例子入門了深度學習,同時也熟悉了PaddlePaddle的使用方式,那麼我們在本章學習更有趣的知識點卷積神經網絡。深度學習之所以那麼流行,很大程度上是得益於它在計算機視覺上得到非常好的效果,而在深度學習上幾乎是使用卷積神經網絡來提取圖像的特徵的。在PaddlePaddle上如何定義一個卷積神經網絡,並使用它來完成一個圖像識別的任務呢。在本章我們通過學習MNIST圖像數據集的分類例子,來掌握卷積神經網絡的使用。

訓練模型

創建一個mnist_classification.py文件,首先導入所需得包,這次使用到了MNIST數據集接口,也使用了處理圖像得工具包。

import numpy as np
import paddle as paddle
import paddle.dataset.mnist as mnist
import paddle.fluid as fluid
from PIL import Image
import matplotlib.pyplot as plt

在圖像識別上,使用得算法也經過了多次的迭代更新,比如多層感知器,在卷積神經網絡廣泛使用之前,多層感知器在圖像識別上是非常流行的,從這方面來看,多層感知器在當時也是有一定的優勢的。那麼如下使用PaddlePaddle來定義一個多層感知器呢,我們可以來學習一下。以下的代碼判斷就是定義一個簡單的多層感知器,一共有三層,兩個大小爲100的隱層和一個大小爲10的輸出層,因爲MNIST數據集是手寫0到9的灰度圖像,類別有10個,所以最後的輸出大小是10。最後輸出層的激活函數是Softmax,所以最後的輸出層相當於一個分類器。加上一個輸入層的話,多層感知器的結構是:輸入層-->>隱層-->>隱層-->>輸出層

# 定義多層感知器
def multilayer_perceptron(input):
    # 第一個全連接層,激活函數爲ReLU
    hidden1 = fluid.layers.fc(input=input, size=100, act='relu')
    # 第二個全連接層,激活函數爲ReLU
    hidden2 = fluid.layers.fc(input=hidden1, size=100, act='relu')
    # 以softmax爲激活函數的全連接輸出層,大小爲label大小
    fc = fluid.layers.fc(input=hidden2, size=10, act='softmax')
    return fc

卷積神經網絡普遍用在圖像特徵提取上,一些圖像分類、目標檢測、文字識別幾乎都回使用到卷積神經網絡作爲圖像的特徵提取方式。卷積神經網絡通常由卷積層、池化層和全連接層,有時還有Batch Normalization層和Dropout層。下面我們就創建一個簡單卷積神經網絡,一共定義了5層,加上輸入層的話,它的結構是:輸入層-->>卷積層-->>池化層-->>卷積層-->>池化層-->>輸出層。我們可以通過調用PaddlePaddle的接口fluid.layers.conv2d()來做一次卷積操作,我們可以通過num_filters參數設置卷積核的數量,通過filter_size設置卷積核的大小,還有通過stride來設置卷積操作時移動的步長。使用fluid.layers.pool2d()接口做一次池化操作,通過參數pool_size可以設置池化的大小,通過參數pool_stride設置池化滑動的步長,通過參數pool_type設置池化的類型,目前有最大池化和平均池化,下面使用的時最大池化,當值爲avg時是平均池化。

# 卷積神經網絡
def convolutional_neural_network(input):
    # 第一個卷積層,卷積核大小爲3*3,一共有32個卷積核
    conv1 = fluid.layers.conv2d(input=input,
                                num_filters=32,
                                filter_size=3,
                                stride=1)

    # 第一個池化層,池化大小爲2*2,步長爲1,最大池化
    pool1 = fluid.layers.pool2d(input=conv1,
                                pool_size=2,
                                pool_stride=1,
                                pool_type='max')

    # 第二個卷積層,卷積核大小爲3*3,一共有64個卷積核
    conv2 = fluid.layers.conv2d(input=pool1,
                                num_filters=64,
                                filter_size=3,
                                stride=1)

    # 第二個池化層,池化大小爲2*2,步長爲1,最大池化
    pool2 = fluid.layers.pool2d(input=conv2,
                                pool_size=2,
                                pool_stride=1,
                                pool_type='max')

    # 以softmax爲激活函數的全連接輸出層,大小爲label大小
    fc = fluid.layers.fc(input=pool2, size=10, act='softmax')
    return fc

定義輸入層,輸入的是圖像數據。圖像是28*28的灰度圖,所以輸入的形狀是[1, 28, 28],如果圖像是32*32的彩色圖,那麼輸入的形狀是[3. 32, 32],因爲灰度圖只有一個通道,而彩色圖有RGB三個通道。理論上它還有一個維度是Batch的,不過這個是PaddlePaddle幫我們默認設置的,我們可以不用理會。

# 定義輸入層
image = fluid.layers.data(name='image', shape=[1, 28, 28], dtype='float32')
label = fluid.layers.data(name='label', shape=[1], dtype='int64')

上面定義了多層感機器和卷積神經網絡,我們可以在這裏調用定義好的網絡來獲取分類器,讀者可以嘗試這兩種不同的網絡進行訓練,觀察一下他們的準確率如何。

# 獲取分類器
# model = multilayer_perceptron(image)
model = convolutional_neural_network(image)

接着是定義損失函數,這次使用的是交叉熵損失函數,該函數在分類任務上比較常用。定義了一個損失函數之後,還有對它求平均值,因爲定義的是一個Batch的損失值。同時我們還可以定義一個準確率函數,這個可以在我們訓練的時候輸出分類的準確率。

# 獲取損失函數和準確率函數
cost = fluid.layers.cross_entropy(input=model, label=label)
avg_cost = fluid.layers.mean(cost)
acc = fluid.layers.accuracy(input=model, label=label)

然後我們從主程序中克隆一個程序作爲預測程序,之後可以使用這個預測程序預測測試的準確率和預測自己的圖像。

# 獲取測試程序
test_program = fluid.default_main_program().clone(for_test=True)

接着是定義優化方法,這次我們使用的是Adam優化方法,同時指定學習率爲0.001。

# 定義優化方法
optimizer = fluid.optimizer.AdamOptimizer(learning_rate=0.001)
opts = optimizer.minimize(avg_cost)

定義讀取MNIST數據集的reader,指定一個Batch的大小爲128,也就是一次訓練128張圖像。

# 獲取MNIST數據
train_reader = paddle.batch(mnist.train(), batch_size=128)
test_reader = paddle.batch(mnist.test(), batch_size=128)

接着也是定義一個執行器和初始化參數,Fluid版本使用的流程都差不多。

# 定義一個使用CPU的執行器
place = fluid.CPUPlace()
exe = fluid.Executor(place)
# 進行參數初始化
exe.run(fluid.default_startup_program())

輸入的數據維度是圖像數據和圖像對應的標籤,每個類別的圖像都要對應一個標籤,這個標籤是從0遞增的整型數值。

# 定義輸入數據維度
feeder = fluid.DataFeeder(place=place, feed_list=[image, label])

最後就可以開始訓練了,我們這次訓練5個Pass,讀者可以根據自己的情況自由設置。在上面我們已經定義了一個求準確率的函數,所以我們在訓練的時候讓它輸出當前的準確率,計算準確率的原理很簡單,就是把訓練是預測的結果和真實的值比較,求出準確率。每一個Pass訓練結束之後,再進行一次測試,使用測試集進行測試,並求出當前的Cost和準確率的平均值。

# 開始訓練和測試
for pass_id in range(5):
    # 進行訓練
    for batch_id, data in enumerate(train_reader()):
        train_cost, train_acc = exe.run(program=fluid.default_main_program(),
                                        feed=feeder.feed(data),
                                        fetch_list=[avg_cost, acc])
        # 每100個batch打印一次信息
        if batch_id % 100 == 0:
            print('Pass:%d, Batch:%d, Cost:%0.5f, Accuracy:%0.5f' %
                  (pass_id, batch_id, train_cost[0], train_acc[0]))

    # 進行測試
    test_accs = []
    test_costs = []
    for batch_id, data in enumerate(test_reader()):
        test_cost, test_acc = exe.run(program=test_program,
                                      feed=feeder.feed(data),
                                      fetch_list=[avg_cost, acc])
        test_accs.append(test_acc[0])
        test_costs.append(test_cost[0])
    # 求測試結果的平均值
    test_cost = (sum(test_costs) / len(test_costs))
    test_acc = (sum(test_accs) / len(test_accs))
    print('Test:%d, Cost:%0.5f, Accuracy:%0.5f' % (pass_id, test_cost, test_acc))

輸出信息:

Pass:0, Batch:0, Cost:3.50138, Accuracy:0.07812
Pass:0, Batch:100, Cost:0.14832, Accuracy:0.96875
Pass:0, Batch:200, Cost:0.13408, Accuracy:0.96875
Pass:0, Batch:300, Cost:0.11601, Accuracy:0.97656
Pass:0, Batch:400, Cost:0.27977, Accuracy:0.92969
Test:0, Cost:0.08879, Accuracy:0.97379
Pass:1, Batch:0, Cost:0.11175, Accuracy:0.96875
Pass:1, Batch:100, Cost:0.07854, Accuracy:0.97656
Pass:1, Batch:200, Cost:0.04025, Accuracy:0.99219
Pass:1, Batch:300, Cost:0.09936, Accuracy:0.98438
Pass:1, Batch:400, Cost:0.19245, Accuracy:0.95312
Test:1, Cost:0.10123, Accuracy:0.97241
Pass:2, Batch:0, Cost:0.13749, Accuracy:0.96094
Pass:2, Batch:100, Cost:0.06074, Accuracy:0.98438
Pass:2, Batch:200, Cost:0.01982, Accuracy:0.99219
Pass:2, Batch:300, Cost:0.06725, Accuracy:0.97656
Pass:2, Batch:400, Cost:0.10043, Accuracy:0.96875
Test:2, Cost:0.13354, Accuracy:0.96776
Pass:3, Batch:0, Cost:0.08895, Accuracy:0.98438
Pass:3, Batch:100, Cost:0.06339, Accuracy:0.96875
Pass:3, Batch:200, Cost:0.05107, Accuracy:0.98438
Pass:3, Batch:300, Cost:0.08062, Accuracy:0.97656
Pass:3, Batch:400, Cost:0.07631, Accuracy:0.96875
Test:3, Cost:0.11465, Accuracy:0.97449
Pass:4, Batch:0, Cost:0.01259, Accuracy:1.00000
Pass:4, Batch:100, Cost:0.01203, Accuracy:1.00000
Pass:4, Batch:200, Cost:0.08451, Accuracy:0.97656
Pass:4, Batch:300, Cost:0.16532, Accuracy:0.98438
Pass:4, Batch:400, Cost:0.09657, Accuracy:0.98438
Test:4, Cost:0.14624, Accuracy:0.97211

預測圖像

訓練完成之後,我們可以使用從主程序中克隆的test_program來預測我們自己的圖像。再預測之前,要對圖像進行預處理,處理方式要跟訓練的時候一樣。首先進行灰度化,然後壓縮圖像大小爲28*28,接着將圖像轉換成一維向量,最後再對一維向量進行歸一化處理。

# 對圖片進行預處理
def load_image(file):
    im = Image.open(file).convert('L')
    im = im.resize((28, 28), Image.ANTIALIAS)
    im = np.array(im).reshape(1, 1, 28, 28).astype(np.float32)
    im = im / 255.0 * 2.0 - 1.0
    return im

我們從網上下載一張圖像,並將它命名爲infer_3.png

!wget https://github.com/yeyupiaoling/LearnPaddle2/blob/master/note4/infer_3.png?raw=true -O 'infer_3.png'

我們可以使用Matplotlib工具顯示這張圖像。

img = Image.open('infer_3.png')
plt.imshow(img)
plt.show()

輸出的圖片:

最後把圖像轉換成一維向量並進行預測,數據從feed中的image傳入,label設置一個假的label值傳進去。fetch_list的值是網絡模型的最後一層分類器,所以輸出的結果是10個標籤的概率值,這些概率值的總和爲1。

# 加載數據並開始預測
img = load_image('./infer_3.png')
results = exe.run(program=test_program,
                  feed={'image': img, "label": np.array([[1]]).astype("int64")},
                  fetch_list=[model])

拿到每個標籤的概率值之後,我們要獲取概率最大的標籤,並打印出來。

# 獲取概率最大的label
lab = np.argsort(results)
print("該圖片的預測結果的label爲: %d" % lab[0][0][-1])

輸出信息:

該圖片的預測結果的label爲: 3

到處爲止,本章就結束了。經過學完這一章節,是不是覺得PaddlePaddle非常好用呢,藉助PaddlePaddle我們很容易就定義了一個卷積神經網絡,並完成了圖像分類的訓練和預測。卷積神經網絡在圖像識別上發揮着巨大的作用,而在自然語言處理上,循環神經網絡同樣起着巨大的作用,我們下一章就學習一下循環神經網絡。

同步到百度AI Studio平臺:http://aistudio.baidu.com/aistudio/projectdetail/29346
同步到科賽網K-Lab平臺:https://www.kesci.com/home/project/5bf8c998954d6e001066d780
項目代碼GitHub地址:https://github.com/yeyupiaoling/LearnPaddle2/tree/master/note4

注意: 最新代碼以GitHub上的爲準


上一章:《PaddlePaddle從入門到煉丹》三——線性迴歸
下一章:《PaddlePaddle從入門到煉丹》五——循環神經網絡


參考資料

  1. https://blog.csdn.net/m_buddy/article/details/80224409
  2. http://www.paddlepaddle.org/documentation/docs/zh/1.0/beginners_guide/quick_start/recognize_digits/README.cn.html
小夜