PaddlePaddle新版本Fluid的使用

前言

PaddlePaddleFluid0.11.0提出的,Fluid是設計用來讓用戶像PytorchTensorflow Eager Execution一樣執行程序。在這些系統中,不再有模型這個概念,應用也不再包含一個用於描述Operator圖或者一系列層的符號描述,而是像通用程序那樣描述訓練或者預測的過程。而FluidPyTorchEager Execution的區別在於Fluid不依賴Python提供的控制流,例如 if-else-then或者for,而是提供了基於C++實現的控制流並暴露了對應的用with語法實現的Python接口。例如我們會在例子中使用到的代碼片段:

with fluid.program_guard(inference_program):
    test_accuracy = fluid.evaluator.Accuracy(input=out, label=label)
    test_target = [avg_cost] + test_accuracy.metrics + test_accuracy.states
    inference_program = fluid.io.get_inference_program(test_target)

Fluid版本中,不再使用trainer來訓練和測試模型了,而是使用了一個C++Executor用於運行一個Fluid程序,Executor類似一個解釋器,Fluid將會使用這樣一個解析器來訓練和測試模型,如:

loss, acc = exe.run(fluid.default_main_program(),
                    feed=feeder.feed(data),
                    fetch_list=[avg_cost] + accuracy.metrics)

對於這個Fluid版本,我們在此之前都沒有使用過,那麼接下來就讓我們去使用Fluid版本,同時對比一下之前所寫的,探討Fluid版本的改變。

定義神經網絡

我們這次使用的是比較熟悉的VGG16神經模型,這個模型在之前的CIFAR彩色圖像識別,爲了方便比較,我們也是使用CIFAR10數據集,以下代碼就是Paddle 1Fluid版本的VGG16的定義,把它們都拿出來對比,看看Fluid版本的改動。

通過對比這個兩神經網絡的定義可以看到img_conv_group的接口位置已經不一樣了,Fluid的相關接口都在fluid下了。同時我們看到改變最大的是Fluid取消了num_channels圖像的通道數。

Fluid版本中使用的激活函數不再是調用一個函數了,而是傳入一個字符串,比如在BN層指定一個Relu激活函數act='relu',在Paddle 1版本中是這樣的:act = paddle.activation.Relu()

Paddle 1的VGG16

def vgg_bn_drop(input,class_dim):
    # 定義卷積塊
    def conv_block(ipt, num_filter, groups, dropouts, num_channels=None):
        return paddle.networks.img_conv_group(
            input=ipt,
            num_channels=num_channels,
            pool_size=2,
            pool_stride=2,
            conv_num_filter=[num_filter] * groups,
            conv_filter_size=3,
            conv_act=paddle.activation.Relu(),
            conv_with_batchnorm=True,
            conv_batchnorm_drop_rate=dropouts,
            pool_type=paddle.pooling.Max())
    # 定義一個VGG16的卷積組
    conv1 = conv_block(input, 64, 2, [0.3, 0], 3)
    conv2 = conv_block(conv1, 128, 2, [0.4, 0])
    conv3 = conv_block(conv2, 256, 3, [0.4, 0.4, 0])
    conv4 = conv_block(conv3, 512, 3, [0.4, 0.4, 0])
    conv5 = conv_block(conv4, 512, 3, [0.4, 0.4, 0])
    # 定義第一個drop層
    drop = paddle.layer.dropout(input=conv5, dropout_rate=0.5)
    # 定義第一層全連接層
    fc1 = paddle.layer.fc(input=drop, size=512, act=paddle.activation.Linear())
    # 定義BN層
    bn = paddle.layer.batch_norm(input=fc1,
                                 act=paddle.activation.Relu(),
                                 layer_attr=paddle.attr.Extra(drop_rate=0.5))
    # 定義第二層全連接層
    fc2 = paddle.layer.fc(input=bn, size=512, act=paddle.activation.Linear())
    # 獲取全連接輸出,獲得分類器
    predict = paddle.layer.fc(input=fc2,
                          size=class_dim,
                          act=paddle.activation.Softmax())
    return predict

Fluid版本的VGG16

def vgg16_bn_drop(input):
    # 定義卷積塊
    def conv_block(input, num_filter, groups, dropouts):
        return fluid.nets.img_conv_group(
            input=input,
            pool_size=2,
            pool_stride=2,
            conv_num_filter=[num_filter] * groups,
            conv_filter_size=3,
            conv_act='relu',
            conv_with_batchnorm=True,
            conv_batchnorm_drop_rate=dropouts,
            pool_type='max')
    # 定義一個VGG16的卷積組
    conv1 = conv_block(input, 64, 2, [0.3, 0])
    conv2 = conv_block(conv1, 128, 2, [0.4, 0])
    conv3 = conv_block(conv2, 256, 3, [0.4, 0.4, 0])
    conv4 = conv_block(conv3, 512, 3, [0.4, 0.4, 0])
    conv5 = conv_block(conv4, 512, 3, [0.4, 0.4, 0])
    # 定義第一個drop層
    drop = fluid.layers.dropout(x=conv5, dropout_prob=0.5)
    # 定義第一層全連接層
    fc1 = fluid.layers.fc(input=drop, size=512, act=None)
    # 定義BN層
    bn = fluid.layers.batch_norm(input=fc1, act='relu')
    # 定義第二層全連接層
    drop2 = fluid.layers.dropout(x=bn, dropout_prob=0.5)
    # 定義第二層全連接層
    fc2 = fluid.layers.fc(input=drop2, size=512, act=None)
    # 獲取全連接輸出,獲得分類器
    predict = fluid.layers.fc(
        input=fc2,
        size=class_dim,
        act='softmax',
        param_attr=ParamAttr(name="param1", initializer=NormalInitializer()))
    return predict

通過上面獲取的全連接,可以生成一個分類器

# 定義圖像的類別數量
class_dim = 10
# 獲取神經網絡的分類器
predict = vgg16_bn_drop(image,class_dim)

定義batch平均錯誤

Fluid版本中,多了一個batch_acc的程序,這個是在訓練過程或者是測試中計算平均錯誤率的。這個需要定義在優化方法之前。

# 每個batch計算的時候能取到當前batch裏面樣本的個數,從而來求平均的準確率
batch_size = fluid.layers.create_tensor(dtype='int64')
print(batch_size)
batch_acc = fluid.layers.accuracy(input=predict, label=label, total=batch_size)

定義測試程序

這個一個定義預測的一個程序,這個是在主程序中獲取的一個程序,專門用來做測試的,這個定義要放在定義方法之前,因爲測試程序是訓練程序的前半部分(不包括優化器和backward),所以要定義在優化方法之前。

# 測試程序
inference_program = fluid.default_main_program().clone(for_test=True)

定義優化方法

在優化方法的定義上也有很大的不同,Fluidlearning_rate相關的都放在一起了,以下是兩個優化方法的定義,這不是本章項目使用到的optimizer,本章使用的optimizer比較簡單,差別不大。
Fluid版本的定義優化方法

optimizer = fluid.optimizer.Momentum(
    learning_rate=fluid.layers.exponential_decay(
        learning_rate=learning_rate,
        decay_steps=40000,
        decay_rate=0.1,
        staircase=True),
    momentum=0.9,
    regularization=fluid.regularizer.L2Decay(0.0005), )
opts = optimizer.minimize(loss)

Paddle 1版本的定義優化方法

momentum_optimizer = paddle.optimizer.Momentum(
    momentum=0.9,
    regularization=paddle.optimizer.L2Regularization(rate=0.0002 * 128),
    learning_rate=0.1 / 128.0,
    learning_rate_decay_a=0.1,
    learning_rate_decay_b=50000 * 100,
    learning_rate_schedule='discexp')

定義調試器

在前言有講到,在Fluid版本中,不會在有trainer了,Paddle 1trainer.train(...)Fluidfluid.Executor(place).Run(...),所以在Fluid起關鍵作用的是調試器。

# 是否使用GPU
place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()
# 創建調試器
exe = fluid.Executor(place)
# 初始化調試器
exe.run(fluid.default_startup_program())

獲取數據

在讀取數據成reader上沒有什麼區別,這要說的是feeder,這裏定義的更之前的feeding = {"image": 0, "label": 1}差距有點大了。不過這樣看起了更加明瞭。

# 獲取訓練數據
train_reader = paddle.batch(
        paddle.dataset.cifar.train10(), batch_size=BATCH_SIZE)
# 獲取測試數據
test_reader = paddle.batch(
        paddle.dataset.cifar.test10(), batch_size=BATCH_SIZE)

# 指定數據和label的對於關係
feeder = fluid.DataFeeder(place=place, feed_list=[image, label])

開始訓練和測試

在這裏就有很大的不一樣了,在Paddle 1中,使用的是trainer,通過num_passes來指定訓練的Pass,而Fluid的是使用一個循環來處理的,這樣就大大方便了我們在訓練過程中所做的一些操作了,而在此之前是使用一個event訓練時間的,雖然也可以做到一些操作,不過相對循環來說,筆者還是覺得循環用起來比較方便。

accuracy = fluid.average.WeightedAverage()
test_accuracy = fluid.average.WeightedAverage()
# 開始訓練,使用循環的方式來指定訓多少個Pass
for pass_id in range(num_passes):
# 從訓練數據中按照一個個batch來讀取數據
    accuracy.reset()
    for batch_id, data in enumerate(train_reader()):
        loss, acc, weight = exe.run(fluid.default_main_program(),
                                    feed=feeder.feed(data),
                                    fetch_list=[avg_cost, batch_acc, batch_size])
        accuracy.add(value=acc, weight=weight)
        print("Pass {0}, batch {1}, loss {2}, acc {3}".format(
            pass_id, batch_id, loss[0], acc[0]))

    # 測試模型
    test_accuracy.reset()
    for data in test_reader():
        loss, acc, weight = exe.run(inference_program,
                            feed=feeder.feed(data),
                            fetch_list=[avg_cost, batch_acc, batch_size])
        test_accuracy.add(value=acc, weight=weight)


    # 輸出相關日誌
    pass_acc = accuracy.eval()
    test_pass_acc = test_accuracy.eval()
    print("End pass {0}, train_acc {1}, test_acc {2}".format(
        pass_id, pass_acc, test_pass_acc))

    # 每一個Pass就保存一次模型
    # 指定保存模型的路徑
    model_path = os.path.join(model_save_dir, str(pass_id))
    # 如果保存路徑不存在就創建
    if not os.path.exists(model_save_dir):
        os.makedirs(model_save_dir)
    print('save models to %s' % (model_path))
    # 保存模型
    fluid.io.save_inference_model(model_path, ['image'], [predict], exe)

在訓練過程中,會輸出類型下的日誌信息:

Pass 0, batch 0, loss 16.5825138092, acc 0.09375
Pass 0, batch 1, loss 15.7055978775, acc 0.1484375
Pass 0, batch 2, loss 15.8206882477, acc 0.0546875
Pass 0, batch 3, loss 14.6004362106, acc 0.1953125
Pass 0, batch 4, loss 14.9484052658, acc 0.1171875
Pass 0, batch 5, loss 13.0915336609, acc 0.078125

保存模型

Fluid版本中,保存模型雖然複雜一點點,但是對於之後的預測是極大的方便了,因爲在預測中,不需要再定義神經網絡模型了,可以直接使用保存好的模型進行預測。還有要說一下的是,這個保存模型的格式跟之前的不一樣,這個保存模型是不會壓縮的。
Fluid版本的保存模型

# 指定保存模型的路徑
model_path = os.path.join(model_save_dir, str(pass_id))
# 如果保存路徑不存在就創建
if not os.path.exists(model_save_dir):
    os.makedirs(model_save_dir)
print('save models to %s' % (model_path))
# 保存預測模型
fluid.io.save_inference_model(model_path, ['image'], [net], exe)

Paddle 1的保存模型

with open(save_parameters_name, 'w') as f:
         trainer.save_parameter_to_tar(f)

預測模型

獲取調試器
在預測中,以前的Paddle 1是要使用到預測器infer的,而在Fluid中還是使用調試器,定義調試器如下

# 是否使用GPU
place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()
# 生成調試器
exe = fluid.Executor(place)

在預測中,所有的預測都要在這個控制流中執行

inference_scope = fluid.core.Scope()
with fluid.scope_guard(inference_scope):

加載訓練好的模型
加載模型,在這裏,加載模型跟之前的差距也很大,在Paddle 1的是parameters = paddle.parameters.Parameters.from_tar(f),因爲之前使用的是參數,而在Fluid沒有使用到參數這個概念。

# 加載模型
[inference_program, feed_target_names, fetch_targets] = fluid.io.load_inference_model(save_dirname, exe)

獲取預測結果

獲取預測數據

# 獲取預測數據
img = Image.open(image_file)
img = img.resize((32, 32), Image.ANTIALIAS)
test_data = np.array(img).astype("float32")
test_data = np.transpose(test_data, (2, 0, 1))
test_data = test_data[np.newaxis, :] / 255

開始預測並打印結果

# 開始預測
results = exe.run(inference_program,
                  feed={feed_target_names[0]: test_data},
                  fetch_list=fetch_targets)

results = np.argsort(-results[0])
# 打印預測結果
print("The images/horse4.png infer results label is: ", results[0][0])

調用預測函數

if __name__ == '__main__':
    image_file = '../images/horse4.png'
    model_path = '../models/0/'
    infer(image_file, False, model_path)

輸出結果如下:

The images/horse4.png infer results label is:  7

項目代碼

GitHub地址:https://github.com/yeyupiaoling/LearnPaddle

參考資料

http://paddlepaddle.org/
https://github.com/PaddlePaddle/Paddle/blob/develop/RELEASE.cn.md

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章