PaddlePaddle是百度开源的深度学习框架,采用和cafee类似的layer搭建的方式构建深度神经网络,当前也在试图发布fluid新版本提供算子级别的网络构建技能,最近有一个文本分类的需求,试着使用paddle进行了实验,对paddle的使用体验为:
- 文档不全,特别简陋
- 模型库比较好,即使不懂的用法,可以搜索代码查找用法;
- github的问题回复比较及时
感觉Paddle是在大力推广和发展的,如果有Paddle同学看到的话,建议把文档补全点;
我的输入数据包含两部分:
- 用户画像,包括用户的性别、年龄、职业等信息;
- 用户搜索词列表,保持时间序列,进行分词、停用词过滤等处理;
将类别信息、词语信息,进行StringIndexer之后(使用的Spark进行),输入数据如下所示:
1 0 6 7 3 1 11069|36027|15862|11069|48152|36027|11069|33830|48152|36027|11069|50730|11069|50730|11069|47002 1 1 0 6 7 3 1 62151|21292|21666|53679|21292|21666|34384|26807|53680|381|2992|64045|2992|69922|62902|3346 0
其中单个数字列都是画像的属性分类,以|分割的数字列表代表词语列表,最后一列代表分类目标
整个代码的实现包含三部分,分别是数据读入reader的实现、训练算法的实现、使用算法的实现;
数据读入reader的实现
# coding:utf8 """ 实现paddle需要的读取数据reader的实现 其中包括如下部分: 1、读取整个数据; 2、分割成训练集和测试集; 3、提供paddle可以直接使用的reader()函数,通过yield的方式抛出数据 """ import random def read_datas(): """ 读取所有的数据 :return: """ fpath = "./inputdatas_numbers.txt" results_datas = [] for line in open(fpath): line = line[:-1] if not line or len(line) == 0: continue fields = line.split("\t") if len(fields) != 8: continue # 按属性提取 gender, age, lifeStage, trade, educationalLevel, job, words, label = fields results_datas.append([ int(float(gender)), int(float(age)), int(float(lifeStage)), int(float(trade)), int(float(educationalLevel)), int(float(job)), # 注意这里,paddle的序列数据,sequence_data,其实是元素为list的元素 [int(x) for x in words.split("|")], int(float(label)) ]) return results_datas def split_data_train_test(results_datas, rand_seed=0, test_ratio=0.1): """ 进行训练数据和测试数据的拆分,这里使用随机的方法进行 :param results_datas: :param rand_seed: :param test_ratio: :return: """ rand_seed = 37 rand = random.Random(x=rand_seed) train_data, test_data = [], [] for line in results_datas: if rand.random() > test_ratio: train_data.append(line) else: test_data.append(line) return train_data, test_data def split_data_train_test_avg(results_datas, test_ratio=0.03): """ 按词典顺序倒叙排列,然后均匀采样 :param results_datas: :param rand_seed: :param test_ratio: :return: """ total_len = len(results_datas) test_datas_cnt = total_len * test_ratio sample_gap = int(total_len * 1.0 / test_datas_cnt) sort_datas = sorted(results_datas, cmp=lambda x, y: int(x[7]) < int(y[7])) train_data, test_data = [], [] for i in range(len(sort_datas)): if i % sample_gap == 0: test_data.append(sort_datas[i]) else: train_data.append(sort_datas[i]) return train_data, test_data results_datas = read_datas() print "数据读取完毕:", len(results_datas) train_data, test_data = split_data_train_test_avg(results_datas, 0.03) print "data read over." print "训练集合数据大小:", len(train_data) print "测试集合数据大小:", len(test_data) def train_reader(): """ paddle需要,用于训练数据的提取 :return: """ for line in train_data: yield line def test_reader(): """ paddle需要,用于训练数据的提取 :return: """ for line in test_data: yield line if __name__ == "__main__": print len(train_data) print len(test_data)
其中需要注意的地方,就是paddle的sequence_data的含义,就是一个list,所以对于query的数据列表,需要处理成[x,y,list(),z]中的子元素list的形式。
查看一下本脚本的运行结果:
数据读取完毕: 20088 data read over. 训练集合数据大小: 19479 测试集合数据大小: 609 19479 609 [1, 0, 6, 7, 3, 1, [62151, 21292, 21666, 53679, 21292, 21666, 34384, 26807, 53680, 381, 2992, 64045, 2992, 69922, 62902, 3346], 0]
训练算法的实现
# coding:utf8 """ 使用paddle实现深层网络 """ import os import paddle.v2 as paddle import reader_paddle_sequence import sys # 该字典是输入数据的模式,比如通过fileds[feeding["gender"]]就可以得到gender的数值 feeding = { 'gender': 0, 'age': 1, 'lifeStage': 2, 'trade': 3, 'educationalLevel': 4, 'job': 5, 'words': 6, 'label': 7 } def convr_perceptron(): """ 搭配而成的卷积网络 :return: """ # 获取卷积层 conv1, conv2 = get_words_conv() # 获取画像特征层 features_fc = get_usr_combined_features() # concat卷积层和画像层 concat_layer = paddle.layer.concat( input=[ features_fc, conv1, conv2 ]) # 加入dropout layer,防止过拟合 dropout_layer = paddle.layer.dropout(input=concat_layer, dropout_rate=0.6) # 加入最终的分类层,使用softmax,分类成6个类别 predict = paddle.layer.fc(input=dropout_layer, size=6, act=paddle.activation.Softmax()) return predict def get_usr_combined_features(): """ 用户画像的特征,都进入FC :return: """ gender = paddle.layer.data(name='gender', type=paddle.data_type.integer_value(2)) gender_emb = paddle.layer.embedding(input=gender, size=16) gender_fc = paddle.layer.fc(input=gender_emb, size=16) age = paddle.layer.data(name='age', type=paddle.data_type.integer_value(6)) age_emb = paddle.layer.embedding(input=age, size=16) age_fc = paddle.layer.fc(input=age_emb, size=16) lifeStage = paddle.layer.data(name='lifeStage', type=paddle.data_type.integer_value(10)) lifeStage_emb = paddle.layer.embedding(input=lifeStage, size=16) lifeStage_fc = paddle.layer.fc(input=lifeStage_emb, size=16) trade = paddle.layer.data(name='trade', type=paddle.data_type.integer_value(23)) trade_emb = paddle.layer.embedding(input=trade, size=16) trade_fc = paddle.layer.fc(input=trade_emb, size=16) educationalLevel = paddle.layer.data(name='educationalLevel', type=paddle.data_type.integer_value(4)) educationalLevel_emb = paddle.layer.embedding(input=educationalLevel, size=16) educationalLevel_fc = paddle.layer.fc(input=educationalLevel_emb, size=16) job = paddle.layer.data(name='job', type=paddle.data_type.integer_value(6)) job_emb = paddle.layer.embedding(input=job, size=16) job_fc = paddle.layer.fc(input=job_emb, size=16) usr_combined_features = paddle.layer.fc( input=[gender_fc, age_fc, lifeStage_fc, trade_fc, educationalLevel_fc, job_fc], size=200, act=paddle.activation.Tanh()) return usr_combined_features def get_words_conv(): """ words的输入,进入conv :return: """ # 词表大小,这个数字来自于分词后进入list的size word_dict_len = 73614 emb_dim = 8 hid_dim = 256 # 注意这里的integer_value_sequence,意思是[1,2,3,4]这种形式 words = paddle.layer.data(name='words', type=paddle.data_type.integer_value_sequence(word_dict_len)) words_emb = paddle.layer.embedding(input=words, size=emb_dim) # 搭建卷积网络,这类可以是多个卷积层 conv1 = paddle.networks.sequence_conv_pool(input=words_emb, context_len=3, hidden_size=hid_dim) conv2 = paddle.networks.sequence_conv_pool(input=words_emb, context_len=4, hidden_size=hid_dim) return conv1, conv2 def train(): """ 执行训练 """ # 初始化paddle paddle.init(use_gpu=False, trainer_count=1) # network config y = paddle.layer.data(name='label', type=paddle.data_type.integer_value(6)) # 获取网络预测结果 y_predict = convr_perceptron() # 设定cost为分类误差 cost = paddle.layer.classification_cost(input=y_predict, label=y) # 随机初始化参数 parameters = paddle.parameters.create(cost) # 创建优化器,主要是设定L2的正则化和学习率 adam_optimizer = paddle.optimizer.Adam( learning_rate=2e-4, regularization=paddle.optimizer.L2Regularization(rate=0.9), model_average=paddle.optimizer.ModelAverage(average_window=0.5, max_average_window=10000)) # 使用SGD做训练器 trainer = paddle.trainer.SGD(cost=cost, parameters=parameters, update_equation=adam_optimizer) # 报错拓扑结构,这个拓扑结构将来回用于infer inference_topology = paddle.topology.Topology(layers=y_predict) with open("inference_topology_conv.pkl", 'wb') as f: inference_topology.serialize_for_inference(f) # 保存训练误差和测试误差,用于将来的曲线绘制 fout_pass_err = open("train_pass_error_conv.txt", "w") fout_pass_err.write("passid\ttest_data_accurcy\ttrain_data_accurcy\n") # 保存中间信息 def event_handler(event): if isinstance(event, paddle.event.EndIteration): if event.batch_id % 100 == 0: print "\nPass %d, Batch %d, Cost %f, %s" % ( event.pass_id, event.batch_id, event.cost, event.metrics) else: sys.stdout.write('.') sys.stdout.flush() if isinstance(event, paddle.event.EndPass): with open('./params_pass_conv_%d.tar' % event.pass_id, 'w') as f: trainer.save_parameter_to_tar(f) result_test = trainer.test( reader=paddle.batch( paddle.reader.shuffle(reader_paddle_sequence.test_reader, buf_size=50000), batch_size=100), feeding=feeding) print "\nTest with Pass %d, %s" % (event.pass_id, result_test.metrics["classification_error_evaluator"]) result_train = trainer.test( reader=paddle.batch( paddle.reader.shuffle(reader_paddle_sequence.train_reader, buf_size=50000), batch_size=100), feeding=feeding) print "\nTrain with Pass %d, %s" % (event.pass_id, result_train.metrics["classification_error_evaluator"]) fout_pass_err.write("%s\t%s\t%s\n" % ( str(event.pass_id), str(float(result_test.metrics["classification_error_evaluator"])), str(float(result_train.metrics["classification_error_evaluator"])) ) ) fout_pass_err.flush() # 执行训练 trainer.train( reader=paddle.batch( paddle.reader.shuffle(reader_paddle_sequence.train_reader, buf_size=50000), batch_size=100), feeding=feeding, event_handler=event_handler, num_passes=300) fout_pass_err.flush() fout_pass_err.close() if __name__ == '__main__': train()
训练后,会在当前目录下生成如下文件:
-rw-r--r-- 1 baidu staff 2.4M May 22 16:06 params_pass_conv_0.tar -rw-r--r-- 1 baidu staff 2.4M May 22 16:06 params_pass_conv_1.tar -rw-r--r-- 1 baidu staff 2.4M May 22 16:07 params_pass_conv_2.tar -rw-r--r-- 1 baidu staff 6.0K May 17 16:14 inference_topology.pkl
同时训练过程会打印过程信息:
I0523 15:53:02.666565 2921214848 Util.cpp:166] commandline: --use_gpu=False --trainer_count=1 I0523 15:53:02.690153 2921214848 GradientMachine.cpp:94] Initing parameters.. I0523 15:53:02.708933 2921214848 GradientMachine.cpp:101] Init parameters done. Pass 0, Batch 0, Cost 1.769033, {'classification_error_evaluator': 0.7699999809265137} ................................................................................................... Pass 0, Batch 100, Cost 1.773714, {'classification_error_evaluator': 0.8100000023841858} .............................................................................................. Test with Pass 0, 0.750410497189 Train with Pass 0, 0.740233063698 Pass 1, Batch 0, Cost 1.779777, {'classification_error_evaluator': 0.75} ................................................................................................... Pass 1, Batch 100, Cost 1.677371, {'classification_error_evaluator': 0.7200000286102295} .............................................................................................. Test with Pass 1, 0.610837459564 Train with Pass 1, 0.5671235919
试了试GPU和CPU的区别,真的发现GPU那是好多倍的速度呀,深度学习是建立在GPU上的技术果然不差;
同时可以试着打印训练集和错误集的准确率曲线:
可以看到,在12轮的时候打到了局部最优,之后出现过拟合;整体效果最好的是88%准确率;
在尝试多次调整dropout和l2正则化参数后依然是这个准确率,因此停止了调整;有待收集更多的数据才可以提升准确率;
利用模型做预测
既然已经训练完毕,那么怎么使用呢,看代码
# coding:utf8 """ 使用模型做预测 """ import os import paddle.v2 as paddle import reader_paddle_sequence import sys # 需要和训练的时候的feeding一样 feeding = { 'gender': 0, 'age': 1, 'lifeStage': 2, 'trade': 3, 'educationalLevel': 4, 'job': 5, 'words': 6, 'label': 7 } def test(): paddle.init(use_gpu=False, trainer_count=1) # 读取最优的那个参数集文件 tarfn = "params_pass_conv_1.tar" # 读取模型拓扑文件 topology_filepath = "inference_topology_conv.pkl" # 加载参数和拓扑到一个infer对象 with open(tarfn) as param_f, open(topology_filepath) as topo_f: params = paddle.parameters.Parameters.from_tar(param_f) inferer = paddle.inference.Inference(parameters=params, fileobj=topo_f) # 使用测试集合的一条数据,进行Infer # 这也说明了,对于要预测的输入,需要处理成和训练集、测试集一样的格式才可以 reader = reader_paddle_sequence.test_reader for k in reader(): print k[:-1] res = inferer.infer(input=[k,], feeding=feeding) print res break if __name__ == '__main__': # 两个选项 test()
打印一下运行结果:
I0523 16:00:56.536753 2921214848 Util.cpp:166] commandline: --use_gpu=False --trainer_count=1 [1, 0, 6, 7, 3, 1, [11069, 36027, 15862, 11069, 48152, 36027, 11069, 33830, 48152, 36027, 11069, 50730, 11069, 50730, 11069, 47002]] [[0.25260347 0.15734845 0.1648475 0.17209302 0.14046918 0.11263847]]
可以看到最后一行打印了预测的6个分类的概率;
总结
以上讲述了使用paddle搭建神经网络的整个流程,包括数据读取、网络搭建、训练、模型应用等方面;
对于已经训练好的模型,可以用python flask或者django进行加载和对外提供远程调用;
其实一通百通,当一个网络搭建和实现之后,对paddle都有了很好的理解,自己同时尝试了全连接网络、LSTM网络,都和卷积网络非常类似,只要替换卷积层部分即可;
对于paddle,虽然当前还不完善,但毕竟是国内的深度学习框架,同时也能够实现业务目标,这一点还是要继续支持滴;
附带Paddle链接:
- 最新文档:http://www.paddlepaddle.org/docs/develop/documentation/zh/getstarted/index_cn.html
- 开放模型库:https://github.com/PaddlePaddle/models
- 教程:https://github.com/PaddlePaddle/book
- 中文BOOK:http://www.paddlepaddle.org/docs/develop/book/04.word2vec/index.cn.html
本文链接:http://crazyant.net/2177.html,转载请注明来源。
问下博主,学习深度学习的话,什么参考资料比较好呢?
深度学习的书不多,我是了视频,然后开始看花书,同时试着用paddle和学习tf,说实话真的好难呀
这个文章必须顶。。