分享IT技术,分享生活感悟,热爱摄影,热爱航天。
目前已经广泛使用深度学习的方法进行数据分类,包括文本、图像、声音和视频等。其中Tensorflow是使用最为广泛的一种深度学习框架,其能够实现分布式以及使用GPU提高建立模型的速度,但其缺点是代码流程较为复杂,而Keras实现了对Tensorflow API的高层封装,使其相比Tensorflow更加简单易用,目前Keras已经作为Tensorflow中的一部分与Tensorflow一起发布。
为了进行模型的训练过程,需要获得一定量的样本数据,本文中的样本数据来自某问答平台,将用户提问标题的文本和问题所属的分类作为学习的训练集。目前Python的pyquery库很好的模拟了jQuery的风格,相比使用正则表达式,其能够更便利的从html文本中提取到所需位置的数据,获取数据的脚本如下
k = 0; #对于每个分类的地址将获取到的问题写入一个文件 for url in urls: k += 1; fp = open(str(k) + '.txt', 'w'); #获取该分类下的问题总数 request = urllib.request.Request(url = url, headers = headers); try: res = urllib.request.urlopen(request, timeout=3); html = res.read().decode('gbk'); except Exception as e: print(e); continue; #计算页数 num = int(jQuery(html).find('span.count-num')[0].text); page = num // size; #按页进行数据获取 for i in range(0, page): request = urllib.request.Request(url = url + '&rn=' + str(size) + '&pn=' + str(i * size), headers = headers); #如果出现错误就放弃当页的数据,防止整个程序退出 try: res = urllib.request.urlopen(request, timeout=3); html = res.read().decode('gbk'); except Exception as e: print(e); continue; #通过pyquery获取保存问题标题的a标签 for x in jQuery(html).find('a.title-link'): fp.write(x.text.strip() + "\n"); time.sleep(2); fp.close();
生成训练模型的思路为使用经典和简单的TF-IDF方法(这里没有使用目前更加流行的word2vec基于卷积神经网络的方法),对每个问题的标题进行分词,并计算出主要词汇的TF-IDF值生成对应每个问题的向量,问题所属的分类则作为该问题的分类值。
首先需要根据全部的问题生成总的词汇表——每个词汇对应一个唯一的索引值(0~词汇表长度-1),同时也决定了对于每一个问题对应的向量的长度,即train_x数组的维度为“问题个数x词汇表的长度”,train_y数组的维度则为“问题个数x1”,其每一个元素为该个问题的分类索引值。训练过程的代码如下
import jieba.analyse; import numpy; import pickle; import tensorflow.keras; import scipy.sparse; from tensorflow.keras.layers import Dense, Activation; if __name__ == '__main__': category = 20; k = 0; j = 0; #生成词汇表 words = dict(); for i in range(0, category): #对于每个文件(该分类)中的问题 fp = open(str(i + 1) + '.txt'); for x in fp: j += 1; #进行TF-IDF分词,IDF使用分词引擎中的数据 for y in jieba.analyse.extract_tags(x, withWeight=True): #如果是新词,则更新词汇表 if y[0] not in words: words[y[0]] = k; k += 1; fp.close(); #将词汇表保存,以便于预测时使用 pickle.dump(words, open('words.bin', 'wb')); #确定了两个数组的维度,这里使用稀疏矩阵可以节约一定的内存空间 train_x = scipy.sparse.lil_matrix((j, len(words))); train_y = numpy.zeros((j, 1)); j = 0; #将分类索引值和TF-IDF值填入对应的数组中 for i in range(0, category): fp = open(str(i + 1) + '.txt'); for x in fp: #填入分类索引值 train_y[j] = i; for y in jieba.analyse.extract_tags(x, withWeight=True): #填入TF-IDF train_x[j, words[y[0]]] = y[1]; j = j + 1; #初始化模型 model = tensorflow.keras.Sequential(); #设置网络 model.add(Dense(32, activation='relu', input_dim=len(words))); model.add(Dense(category, activation='sigmoid')); #设置分类的优化器 model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy']); #处理分类索引值数组 train_y = tensorflow.keras.utils.to_categorical(train_y, num_classes=category); #开始训练 model.fit(train_x, train_y, epochs=10, batch_size=32); #保存训练得到的模型 model.save('1.h5');
运行训练过程的输出结果如下,可见10轮训练后的准确率就比较高了
Epoch 1/10 14921/14921 [==============================] - 4s 289us/sample - loss: 2.9050 - accuracy: 0.2525 Epoch 2/10 14921/14921 [==============================] - 4s 269us/sample - loss: 2.3243 - accuracy: 0.6018 Epoch 3/10 14921/14921 [==============================] - 4s 273us/sample - loss: 1.5976 - accuracy: 0.7354 Epoch 4/10 14921/14921 [==============================] - 4s 275us/sample - loss: 1.1616 - accuracy: 0.7962 Epoch 5/10 14921/14921 [==============================] - 4s 274us/sample - loss: 0.8692 - accuracy: 0.8380 Epoch 6/10 14921/14921 [==============================] - 4s 271us/sample - loss: 0.6639 - accuracy: 0.8674 Epoch 7/10 14921/14921 [==============================] - 4s 272us/sample - loss: 0.5143 - accuracy: 0.8899 Epoch 8/10 14921/14921 [==============================] - 4s 267us/sample - loss: 0.4068 - accuracy: 0.9089 Epoch 9/10 14921/14921 [==============================] - 4s 267us/sample - loss: 0.3283 - accuracy: 0.9220 Epoch 10/10 14921/14921 [==============================] - 4s 267us/sample - loss: 0.2700 - accuracy: 0.9335
训练得到模型后就可以使用模型对新的问题进行分类预测了,预测的脚本如下
#读取词库 words = pickle.load(open('words.bin', 'rb')); #加载模型 model = load_model('1.h5'); #需要预测的文本信息,由命令行参数给出 text = sys.argv[1]; #输入向量的维度为1x词汇表长度 data = numpy.zeros((1, len(words))); #分词并计算TF-IDF for x in jieba.analyse.extract_tags(text, withWeight=True): #如果词汇表中有此词汇则将TF-IDF填入输入向量中 if x[0] in words: data[0, words[x[0]]] = x[1]; #预测,并取出可能性最高的一个分类的索引值 i = numpy.argmax(model.predict(data)); print(labels[i]);
尝试了几个问题,得到的分类结果如下,总体感觉似乎还可以。
健康猫爪子会不会沾染狂犬病毒,请看下面的补充评论谢谢。 -> 生活 平板网络很好,为什么有的软件显示网络不可用?早上还可以! -> 家电数码 普耐尔250二氧化碳焊机手工焊起弧电流什么凋? -> 生产制造 中国的地标城市是在那个地方? -> 旅游
使用Python+numpy做数值计算基本可以达到类似MATLAB的效果,能够快速的进行开发和部署,但由于Python语言本身的限制性能上并不算十分理想。
目前的动态语言向JIT运行方式转型已经是大趋势,而Python的JIT版本pypy虽然性能大幅提升,但对numpy和scipy的支持还存在很多问题。在寻找了很多解决方案后发现了Julia语言,原生支持多维数组及相关的运算,采用JIT的运行方式,主要面型科学计算领域,官方给出的性能测试结果表明其与LuaJIT的性能相当。
Julia的基本语法和Python非常类似,同时引入了很多数学中习惯使用的表达方式,如范围的判断可以写成1 < x < 10,函数的定义可以写成f(x) = x^2
#变量赋值 x = 3; #条件 if 1 < x < 10 @show x else println(1); end #循环 for i = 1 : 100 @show i; end #函数 #标准定义 function add(a::Int, b::Int) return a + b; end #简化的定义 f(x::Real) = x^2;
在数值计算中最为重要的数据类型,一般常用的是二维数组——矩阵,常用的矩阵运算如下
#生成一个4x4的随机矩阵,4x1的随机向量 M = rand(4, 4); v = rand(4); #计算矩阵和向量的乘积 v1 = M * v; #矩阵的转置和逆 M1 = M'; M2 = M^-1;
通过JuliaGPU提供的ClArray和CuArray包可以将数组的运算扩展到GPU设备上,而接口保持和一般的Array一致
Julia中并不支持Python中的面向对象的方式定义类型,而是定义类型和类型的静态方法(可以将静态方法的第一个参数设定为对应类型的引用实现类似成员方法的效果),并可以重载定义的方法,在定义的类型前增加mutable可使得内部的数据是可修改的
#定义一种抽象类型 abstract type A end; #定义抽象类型的子类型 mutable struct B <: A x::Int64; #构造函数 function B(x::Int64) return new(x); end end #定义方法 function add(v1::A, v2::A) return v1.x + v2.x; end #定义类似其他语言中成员方法 function show(self::A) { println(self.x); } #对于子类型B重载show方法 function show(self::B) { println("B:"); println(self.x); }
Julia和Python的pip类似也有一套自身的包管理系统,在julia控制台中可以进行安装
import Pkg; #增加和删除包MatrixMarket Pkg.add("MatrixMarket"); Pkg.rm("MatrixMarket");
使用Julia和Python对比性能对比测试——生成一个随机的10x10的矩阵,并求逆,循环1000000次,代码如下
for i = 0 : 1000000 - 1 A = rand(10, 10); A^-1; end
import numpy; for i in range(0, 1000000): A = numpy.random.rand(10, 10); numpy.linalg.inv(A);
运行结果如下,循环的次数越多Julia的优势就越明显,而当循环数少时Python反而会快一些,原因应该是循环数少时JIT编译时产生的消耗会变得特别明显
time python test.py real 0m15.129s user 0m14.958s sys 0m0.027s time julia test.jl real 0m7.872s user 0m6.648s sys 0m1.139s