前言
PaddlePaddle
的Fluid
是0.11.0
提出的,Fluid
是設計用來讓用戶像Pytorch
和Tensorflow Eager Execution
一樣執行程序。在這些系統中,不再有模型這個概念,應用也不再包含一個用於描述Operator
圖或者一系列層的符號描述,而是像通用程序那樣描述訓練或者預測的過程。而Fluid
與PyTorch
或Eager 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 1
和Fluid
版本的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)
定義優化方法
在優化方法的定義上也有很大的不同,Fluid
把learning_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 1
用 trainer.train(...)
,Fluid
用fluid.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