|  | 
|  | 1 | +#!/usr/bin/env python | 
|  | 2 | +# -*- coding:utf-8 -*- | 
|  | 3 | +################################################################################ | 
|  | 4 | +# | 
|  | 5 | +# Copyright (c) 2017 Baidu.com, Inc. All Rights Reserved | 
|  | 6 | +# | 
|  | 7 | +################################################################################ | 
|  | 8 | +""" | 
|  | 9 | +Authors: xiake(kedou1993@163.com) | 
|  | 10 | +Date: 2017/11/29 | 
|  | 11 | +
 | 
|  | 12 | +使用paddle框架实现逻辑数字识别案例,关键步骤如下: | 
|  | 13 | +1.定义分类器网络结构 | 
|  | 14 | +2.初始化 | 
|  | 15 | +3.配置网络结构 | 
|  | 16 | +4.定义成本函数cost | 
|  | 17 | +5.定义优化器optimizer | 
|  | 18 | +6.定义事件处理函数 | 
|  | 19 | +7.进行训练 | 
|  | 20 | +8.利用训练好的模型进行预测 | 
|  | 21 | +""" | 
|  | 22 | + | 
|  | 23 | +import matplotlib | 
|  | 24 | +matplotlib.use('Agg') | 
|  | 25 | +import os | 
|  | 26 | +from PIL import Image | 
|  | 27 | +import numpy as np | 
|  | 28 | +import paddle.v2 as paddle | 
|  | 29 | +from paddle.v2.plot import Ploter | 
|  | 30 | + | 
|  | 31 | +with_gpu = os.getenv('WITH_GPU', '0') != '0' | 
|  | 32 | + | 
|  | 33 | +step = 0 | 
|  | 34 | + | 
|  | 35 | +# 绘图相关标注 | 
|  | 36 | +train_title_cost = "Train cost" | 
|  | 37 | +test_title_cost = "Test cost" | 
|  | 38 | + | 
|  | 39 | +train_title_error = "Train error rate" | 
|  | 40 | +test_title_error = "Test error rate" | 
|  | 41 | + | 
|  | 42 | +def softmax_regression(img): | 
|  | 43 | + """ | 
|  | 44 | + 定义softmax分类器: | 
|  | 45 | + 只通过一层简单的以softmax为激活函数的全连接层,可以得到分类的结果 | 
|  | 46 | + Args: | 
|  | 47 | + img -- 输入的原始图像数据 | 
|  | 48 | + Return: | 
|  | 49 | + predict -- 分类的结果 | 
|  | 50 | + """ | 
|  | 51 | + predict = paddle.layer.fc( | 
|  | 52 | + input=img, size=10, act=paddle.activation.Softmax()) | 
|  | 53 | + return predict | 
|  | 54 | + | 
|  | 55 | + | 
|  | 56 | +def multilayer_perceptron(img): | 
|  | 57 | + """ | 
|  | 58 | + 定义多层感知机分类器: | 
|  | 59 | + 含有两个隐藏层(即全连接层)的多层感知器 | 
|  | 60 | + 其中两个隐藏层的激活函数均采用ReLU,输出层的激活函数用Softmax | 
|  | 61 | + Args: | 
|  | 62 | + img -- 输入的原始图像数据 | 
|  | 63 | + Return: | 
|  | 64 | + predict -- 分类的结果 | 
|  | 65 | + """ | 
|  | 66 | + # 第一个全连接层 | 
|  | 67 | + hidden1 = paddle.layer.fc(input=img, size=128, act=paddle.activation.Relu()) | 
|  | 68 | + # 第二个全连接层 | 
|  | 69 | + hidden2 = paddle.layer.fc( | 
|  | 70 | + input=hidden1, size=64, act=paddle.activation.Relu()) | 
|  | 71 | + # 第三个全连接层,需要注意输出尺寸为10,,对应0-9这10个数字 | 
|  | 72 | + predict = paddle.layer.fc( | 
|  | 73 | + input=hidden2, size=10, act=paddle.activation.Softmax()) | 
|  | 74 | + return predict | 
|  | 75 | + | 
|  | 76 | + | 
|  | 77 | +def convolutional_neural_network(img): | 
|  | 78 | + """ | 
|  | 79 | + 定义卷积神经网络分类器: | 
|  | 80 | + 输入的二维图像,经过两个卷积-池化层,使用以softmax为激活函数的全连接层作为输出层 | 
|  | 81 | + Args: | 
|  | 82 | + img -- 输入的原始图像数据 | 
|  | 83 | + Return: | 
|  | 84 | + predict -- 分类的结果 | 
|  | 85 | + """ | 
|  | 86 | + """ | 
|  | 87 | + 与第六章代码不同之处: | 
|  | 88 | + 在两个卷积-池化层之后都加入了batch normalization层norm1和norm2 | 
|  | 89 | + """ | 
|  | 90 | + # 第一个卷积-池化层 | 
|  | 91 | + conv_pool_1 = paddle.networks.simple_img_conv_pool( | 
|  | 92 | + input=img, | 
|  | 93 | + filter_size=5, | 
|  | 94 | + num_filters=20, | 
|  | 95 | + num_channel=1, | 
|  | 96 | + pool_size=2, | 
|  | 97 | + pool_stride=2, | 
|  | 98 | + act=paddle.activation.Relu()) | 
|  | 99 | + | 
|  | 100 | + norm1 = paddle.layer.batch_norm(input=conv_pool_1, act=paddle.activation.Relu()) | 
|  | 101 | +  | 
|  | 102 | + # 第二个卷积-池化层 | 
|  | 103 | + conv_pool_2 = paddle.networks.simple_img_conv_pool( | 
|  | 104 | + input=conv_pool_1, | 
|  | 105 | + filter_size=5, | 
|  | 106 | + num_filters=50, | 
|  | 107 | + num_channel=20, | 
|  | 108 | + pool_size=2, | 
|  | 109 | + pool_stride=2, | 
|  | 110 | + act=paddle.activation.Relu()) | 
|  | 111 | + | 
|  | 112 | + norm2 = paddle.layer.batch_norm(input=conv_pool_2, act=paddle.activation.Relu()) | 
|  | 113 | +  | 
|  | 114 | + # 全连接层 | 
|  | 115 | + predict = paddle.layer.fc( | 
|  | 116 | + input=norm2, size=10, act=paddle.activation.Softmax()) | 
|  | 117 | + return predict | 
|  | 118 | + | 
|  | 119 | + | 
|  | 120 | +def netconfig(): | 
|  | 121 | + """ | 
|  | 122 | + 配置网络结构 | 
|  | 123 | + Args: | 
|  | 124 | + Return: | 
|  | 125 | + images -- 输入层 | 
|  | 126 | + label -- 标签数据 | 
|  | 127 | + predict -- 输出层 | 
|  | 128 | + cost -- 损失函数 | 
|  | 129 | + parameters -- 模型参数 | 
|  | 130 | + optimizer -- 优化器 | 
|  | 131 | + """ | 
|  | 132 | +  | 
|  | 133 | + """ | 
|  | 134 | + 输入层: | 
|  | 135 | + paddle.layer.data表示数据层, | 
|  | 136 | + name=’pixel’:名称为pixel,对应输入图片特征 | 
|  | 137 | + type=paddle.data_type.dense_vector(784):数据类型为784维(输入图片的尺寸为28*28)稠密向量 | 
|  | 138 | + """ | 
|  | 139 | + images = paddle.layer.data( | 
|  | 140 | + name='pixel', type=paddle.data_type.dense_vector(784)) | 
|  | 141 | +  | 
|  | 142 | + """ | 
|  | 143 | + 数据层: | 
|  | 144 | + paddle.layer.data表示数据层, | 
|  | 145 | + name=’label’:名称为label,对应输入图片的类别标签 | 
|  | 146 | + type=paddle.data_type.dense_vector(10):数据类型为10维(对应0-9这10个数字)稠密向量 | 
|  | 147 | + """ | 
|  | 148 | + label = paddle.layer.data( | 
|  | 149 | + name='label', type=paddle.data_type.integer_value(10)) | 
|  | 150 | +  | 
|  | 151 | + """  | 
|  | 152 | + 选择分类器: | 
|  | 153 | + 在此之前已经定义了3种不同的分类器,在下面的代码中, | 
|  | 154 | + 我们可以通过保留某种方法的调用语句、注释掉其余两种,以选择特定的分类器 | 
|  | 155 | + """ | 
|  | 156 | + # predict = softmax_regression(images) | 
|  | 157 | + # predict = multilayer_perceptron(images) | 
|  | 158 | + predict = convolutional_neural_network(images) | 
|  | 159 | + | 
|  | 160 | + # 定义成本函数,addle.layer.classification_cost()函数内部采用的是交叉熵损失函数 | 
|  | 161 | + cost = paddle.layer.classification_cost(input=predict, label=label) | 
|  | 162 | + | 
|  | 163 | + # 利用cost创建参数parameters | 
|  | 164 | + parameters = paddle.parameters.create(cost) | 
|  | 165 | +  | 
|  | 166 | + # 创建优化器optimizer,下面列举了2种常用的优化器,不同类型优化器选一即可 | 
|  | 167 | + # 创建Momentum优化器,并设置学习率(learning_rate)、动量(momentum)和正则化项(regularization) | 
|  | 168 | + """ | 
|  | 169 | + 与第六章代码不同之处: | 
|  | 170 | + 学习率learning_rate和动量momentum设置的数值不同, | 
|  | 171 | + 一方面,可以通过单纯修改某个参数值而不引入其他改变,对比第六章实验结果来验证该参数的影响; | 
|  | 172 | + 另一方面,可以通过设置learning_rate=0.1 / 128.0,momentum=0.95,以使得模型的基础表现相对第六章中下降,如收敛程度或者速度下降 | 
|  | 173 | + 而进一步加入新的模块或者设置后(如加入dropout),模型表现得到提升,从而验证新加入的模块或者设置的有效性; | 
|  | 174 | + """ | 
|  | 175 | + optimizer = paddle.optimizer.Momentum( | 
|  | 176 | + learning_rate=0.1 / 128.0, | 
|  | 177 | + momentum=0.95, | 
|  | 178 | + regularization=paddle.optimizer.L2Regularization(rate=0.0005 * 128)) | 
|  | 179 | +  | 
|  | 180 | + # 创建Adam优化器,并设置参数beta1、beta2、epsilon | 
|  | 181 | + # optimizer = paddle.optimizer.Adam(beta1=0.9, beta2=0.99, epsilon=1e-06) | 
|  | 182 | +  | 
|  | 183 | + config_data = [images, label, predict, cost, parameters, optimizer] | 
|  | 184 | +  | 
|  | 185 | + return config_data | 
|  | 186 | + | 
|  | 187 | + | 
|  | 188 | +def plot_init(): | 
|  | 189 | + """ | 
|  | 190 | + 绘图初始化函数: | 
|  | 191 | + 初始化绘图相关变量 | 
|  | 192 | + Args: | 
|  | 193 | + Return: | 
|  | 194 | + cost_ploter -- 用于绘制cost曲线的变量 | 
|  | 195 | + error_ploter -- 用于绘制error_rate曲线的变量 | 
|  | 196 | + """ | 
|  | 197 | + # 绘制cost曲线所做的初始化设置 | 
|  | 198 | + cost_ploter = Ploter(train_title_cost, test_title_cost) | 
|  | 199 | +  | 
|  | 200 | + # 绘制error_rate曲线所做的初始化设置 | 
|  | 201 | + error_ploter = Ploter(train_title_error, test_title_error) | 
|  | 202 | +  | 
|  | 203 | + ploter = [cost_ploter, error_ploter] | 
|  | 204 | +  | 
|  | 205 | + return ploter | 
|  | 206 | + | 
|  | 207 | +  | 
|  | 208 | +def load_image(file): | 
|  | 209 | + """ | 
|  | 210 | + 定义读取输入图片的函数: | 
|  | 211 | + 读取指定路径下的图片,将其处理成分类网络输入数据对应形式的数据,如数据维度等 | 
|  | 212 | + Args: | 
|  | 213 | + file -- 输入图片的文件路径 | 
|  | 214 | + Return: | 
|  | 215 | + im -- 分类网络输入数据对应形式的数据 | 
|  | 216 | + """ | 
|  | 217 | + im = Image.open(file).convert('L') | 
|  | 218 | + im = im.resize((28, 28), Image.ANTIALIAS) | 
|  | 219 | + im = np.array(im).astype(np.float32).flatten() | 
|  | 220 | + im = im / 255.0 | 
|  | 221 | + return im | 
|  | 222 | + | 
|  | 223 | + | 
|  | 224 | +def infer(predict, parameters, file): | 
|  | 225 | + """ | 
|  | 226 | + 定义判断输入图片类别的函数: | 
|  | 227 | + 读取并处理指定路径下的图片,然后调用训练得到的模型进行类别预测 | 
|  | 228 | + Args: | 
|  | 229 | + predict -- 输出层 | 
|  | 230 | + parameters -- 模型参数 | 
|  | 231 | + file -- 输入图片的文件路径 | 
|  | 232 | + Return: | 
|  | 233 | + """ | 
|  | 234 | + # 读取并预处理要预测的图片 | 
|  | 235 | + test_data = [] | 
|  | 236 | + cur_dir = os.path.dirname(os.path.realpath(__file__)) | 
|  | 237 | + test_data.append((load_image(cur_dir + file), )) | 
|  | 238 | +  | 
|  | 239 | + # 利用训练好的分类模型,对输入的图片类别进行预测 | 
|  | 240 | + probs = paddle.infer( | 
|  | 241 | + output_layer=predict, parameters=parameters, input=test_data) | 
|  | 242 | + lab = np.argsort(-probs) | 
|  | 243 | + print "Label of image/infer_3.png is: %d" % lab[0][0] | 
|  | 244 | + | 
|  | 245 | + | 
|  | 246 | +  | 
|  | 247 | +def main(): | 
|  | 248 | + """ | 
|  | 249 | + 主函数: | 
|  | 250 | + 定义神经网络结构,训练模型并打印学习曲线、预测测试数据类别 | 
|  | 251 | + Args: | 
|  | 252 | + Return: | 
|  | 253 | + """ | 
|  | 254 | + # 初始化,设置是否使用gpu,trainer数量 | 
|  | 255 | + paddle.init(use_gpu=with_gpu, trainer_count=1) | 
|  | 256 | +  | 
|  | 257 | + # 定义神经网络结构 | 
|  | 258 | + images, label, predict, cost, parameters, optimizer = netconfig() | 
|  | 259 | + | 
|  | 260 | + # 构造trainer,配置三个参数cost、parameters、update_equation,它们分别表示成本函数、参数和更新公式 | 
|  | 261 | + trainer = paddle.trainer.SGD( | 
|  | 262 | + cost=cost, parameters=parameters, update_equation=optimizer) | 
|  | 263 | +  | 
|  | 264 | + # 初始化绘图变量 | 
|  | 265 | + cost_ploter, error_ploter = plot_init() | 
|  | 266 | +  | 
|  | 267 | + # lists用于存储训练的中间结果,包括cost和error_rate信息,初始化为空 | 
|  | 268 | + lists = [] | 
|  | 269 | + | 
|  | 270 | + def event_handler_plot(event): | 
|  | 271 | + """ | 
|  | 272 | + 定义event_handler_plot事件处理函数: | 
|  | 273 | + 事件处理器,可以根据训练过程的信息做相应操作:包括绘图和输出训练结果信息 | 
|  | 274 | + Args: | 
|  | 275 | + event -- 事件对象,包含event.pass_id, event.batch_id, event.cost等信息 | 
|  | 276 | + Return: | 
|  | 277 | + """ | 
|  | 278 | + global step | 
|  | 279 | + if isinstance(event, paddle.event.EndIteration): | 
|  | 280 | + # 每训练100次(即100个batch),添加一个绘图点 | 
|  | 281 | + if step % 100 == 0: | 
|  | 282 | + cost_ploter.append(train_title_cost, step, event.cost) | 
|  | 283 | + # 绘制cost图像,保存图像为‘train_test_cost.png’ | 
|  | 284 | + cost_ploter.plot('./train_test_cost') | 
|  | 285 | + error_ploter.append( | 
|  | 286 | + train_title_error, step, event.metrics['classification_error_evaluator']) | 
|  | 287 | + # 绘制error_rate图像,保存图像为‘train_test_error_rate.png’ | 
|  | 288 | + error_ploter.plot('./train_test_error_rate') | 
|  | 289 | + step += 1 | 
|  | 290 | + # 每训练100个batch,输出一次训练结果信息 | 
|  | 291 | + if event.batch_id % 100 == 0: | 
|  | 292 | + print "Pass %d, Batch %d, Cost %f, %s" % ( | 
|  | 293 | + event.pass_id, event.batch_id, event.cost, event.metrics) | 
|  | 294 | + if isinstance(event, paddle.event.EndPass): | 
|  | 295 | + # 保存参数至文件 | 
|  | 296 | + with open('params_pass_%d.tar' % event.pass_id, 'w') as f: | 
|  | 297 | + trainer.save_parameter_to_tar(f) | 
|  | 298 | + # 利用测试数据进行测试 | 
|  | 299 | + result = trainer.test(reader=paddle.batch( | 
|  | 300 | + paddle.dataset.mnist.test(), batch_size=128)) | 
|  | 301 | + print "Test with Pass %d, Cost %f, %s\n" % ( | 
|  | 302 | + event.pass_id, result.cost, result.metrics) | 
|  | 303 | + # 添加测试数据的cost和error_rate绘图数据 | 
|  | 304 | + cost_ploter.append(test_title_cost, step, result.cost) | 
|  | 305 | + error_ploter.append( | 
|  | 306 | + test_title_error, step, result.metrics['classification_error_evaluator']) | 
|  | 307 | + # 存储测试数据的cost和error_rate数据 | 
|  | 308 | + lists.append(( | 
|  | 309 | + event.pass_id, result.cost, result.metrics['classification_error_evaluator'])) | 
|  | 310 | +  | 
|  | 311 | + """ | 
|  | 312 | + 训练模型: | 
|  | 313 | + paddle.reader.shuffle(paddle.dataset.mnist.train(), buf_size=8192): | 
|  | 314 | + 表示trainer从paddle.dataset.mnist.train()这个reader中读取了buf_size=8192大小的数据并打乱顺序 | 
|  | 315 | + paddle.batch(reader(), batch_size=128): | 
|  | 316 | + 表示从打乱的数据中再取出batch_size=128大小的数据进行一次迭代训练 | 
|  | 317 | + event_handler:事件处理函数,可以自定义event_handler,根据事件信息做相应的操作, | 
|  | 318 | + 下方代码中选择的是event_handler_plot函数 | 
|  | 319 | + num_passes:定义训练的迭代次数 | 
|  | 320 | + """ | 
|  | 321 | + trainer.train( | 
|  | 322 | + reader=paddle.batch( | 
|  | 323 | + paddle.reader.shuffle(paddle.dataset.mnist.train(), buf_size=8192), | 
|  | 324 | + batch_size=128), | 
|  | 325 | + event_handler=event_handler_plot, | 
|  | 326 | + num_passes=10) | 
|  | 327 | + | 
|  | 328 | + # 在多次迭代中,找到在测试数据上表现最好的一组参数,并输出相应信息 | 
|  | 329 | + best = sorted(lists, key=lambda list: float(list[1]))[0] | 
|  | 330 | + print 'Best pass is %s, testing Avgcost is %s' % (best[0], best[1]) | 
|  | 331 | + print 'The classification accuracy is %.2f%%' % (100 - float(best[2]) * 100) | 
|  | 332 | +  | 
|  | 333 | + # 预测输入图片的类型 | 
|  | 334 | + infer(predict, parameters, '/image/infer_3.png') | 
|  | 335 | + | 
|  | 336 | + | 
|  | 337 | + | 
|  | 338 | + | 
|  | 339 | +if __name__ == '__main__': | 
|  | 340 | + main() | 
0 commit comments