1.实验数据获取:
这里的实验数据是本人自己提取的,具体方式是:
(大家可以根据自己喜好进行如下步骤)
1.选取3个不同类别的文本,每类500篇,共1500篇。
2.使用TF-IDF或词频等方式,从每个类型的文本中选出100个特征词,3个类别,共300个特征词。将300个特征词存入一个list中。
3.使用300个特征词的列表去遍历每一篇文本,如果第x个特征词在该文本中出现次数为n,则对应该文本的特征list的第x为记为n。1500篇文本,1500个特征list分别对应每个文本。
4.对每个文本设置标签,使用one-hot方式表示即可
5.将其文本顺序打乱即可,但保证其对应关系不变 文本——文本特征list——标签
例如:
这里我使用TF-IDF从3个不同类别的文本中提取到的300个特征词:
我们甚至可以从这些词中看出这三类文本的类别分别为:房产,星座,游戏
当然,有些词的区分性不是太好,我们可以通过增加文本数量,设置更精确的停用词实现更好的效果。
提取一篇文本的特征list:
文本特征list的每一位与特征词list的每一位一一对应,例如文本特征list[1]=11,对应特征词list[1]=“一个”,即表示"一个"这个词在该文本中出现11次。
再对这篇文本设置标签:
[‘0’, ‘1’, ‘0’]
表明该文本属于第二类。
2.代码实现:
关于理论实现这里就不做讲解(如果想要学习,推荐深度学习入门——基于Python的理论与实现-图灵教育-[日]斋藤康毅 著),
直接上代码,具体解释在注释中:
代码参考于:
Tensorflow中文社区
BiliBili视频 深度学习框架Tensorflow学习与应用
import tensorflow as tf
import dataN #这里是自己写的获取数据的python文件,可以获取到1200个训练数据,训练标签。300个测试数据,测试标签
train_dataN,train_labelN,test_dataN,test_labelN=dataN.dataone()
#placeholder为tensor操作创建占位符,可以在TensorFlow运行某一计算时根据该占位符输入具体的值
x=tf.placeholder("float",shape=[None,300])
#None表示一次传入数据的个数未知,他会根据实际情况自动设置
y_=tf.placeholder("float",shape=[None,3])
#y_为正确解标签,x为输入的数据
#定义两个函数用于初始化W,b:
def weight_variable(shape):
initial=tf.truncated_normal(shape,stddev=0.1) #正态分布
return tf.Variable(initial)
def bias_variable(shape):
initial=tf.constant(0.1,shape=shape)
return tf.Variable(initial)
#卷积层:
def conv2d(x,W):
return tf.nn.conv2d(x,W,strides=[1,1,1,1],padding="SAME")
# x input tersor of shape [batch,in_height,in_wight,in_channels]
# stride步幅,padd填充,x输入数据,W权重张量|滤波器,0边距(padding size)的模板
# strides[0]=strides[3]=1 (默认), strides[1]代表x方向的步长,strides[2]代表y方向的步长
# SAME就是一种padding方法,另一个是VALID,在实际中多采用SAME使卷积层不改变数据大小
# 如果padding设置为SAME,则输入图片大小和输出图片大小是一致的,如果是VALID则图片经过滤波器后可能会变小
# 大家可以去了解下SAME和VALID两种方式(这很重要!!!)
#池化层:
def max_pool_2x2(x):
return tf.nn.max_pool(x,ksize=[1,2,2,1],strides=[1,2,2,1],padding="SAME")
#ksize窗口大小|过滤器大小,stride步幅,pdd填充,x输入数据
#ksize[0]=ksize=[3]=1(默认),ksize[1]代表x方向长,ksize[2]代表y方向长
x_text=tf.reshape(x,[-1,10,30,1]) #改变x的形状,[1*300,通道数1]变为[10*30,通道数1],-1表示batch(一个批次)数量未知,1表示通道数为1
#第一层:::
W_conv1=weight_variable([2,6,1,10])
# 卷积的权重张量形状是[2,6,1,10],前两个维度是采样窗口的大小,接着是输入的通道数目,最后是输出的通道数目(表示使用多少卷积核),这里使用10个卷积核从一个平面抽取特征,得到10个通道
# 10就是指卷积核的数量,每种卷积只对某些特征敏感,获取的特征有限。
# 因此将多种不同的卷积核分别对图像进行处理,就能获得更多的特征。
# 每个卷积核按照规则扫描完图像后,就输出一张特征图像(feature map),
# 因此10也指输出的特征图
b_conv1=bias_variable([10])
#对于每一个输出通道都有一个对应的偏置量,前面因为每张图片生成10个特征,这里也要对应10个偏置值
h_conv1=tf.nn.relu(conv2d(x_text,W_conv1)+b_conv1)
#这里的卷积层不改变大小,即数据仍然为10*30,但因为使用了10个卷积核进行特征抽取,产生了10个通道
h_pool1=max_pool_2x2(h_conv1)
#池化得到10个5*15的平面
#第二层:::
W_conv2=weight_variable([2,2,10,20])
#2x2的采样窗口,20个卷积核从10个平面抽取特征,输出20个特征平面
#输入的是10个2*15的矩阵,这层要输出的矩阵个数为20
b_conv2=bias_variable([20])
#每一个卷积核对应一个偏置量
h_conv2=tf.nn.relu(conv2d(h_pool1,W_conv2)+b_conv2)
#这里的卷积层不改变数据大小
h_pool2=max_pool_2x2(h_conv2)
#池化输出20个3*8的矩阵
#连接层:::初始化第一个全连接层
W_fc1=weight_variable([3*8*20,500])
# 上一层(卷积层)传入3*8*20个神经元,我们设置全连接层有500个神经元
b_fc1=bias_variable([500])
h_pool2_flat=tf.reshape(h_pool2,[-1,3*8*20])
#把池化层2的输出扁平化化为1维,-1表示batch数量未知
h_fc1=tf.nn.relu(tf.matmul(h_pool2_flat,W_fc1)+b_fc1)
# 第一个全连接层的输出。得到一个长度为500的向量
keep_prob=tf.placeholder("float")
h_fc1_drop=tf.nn.dropout(h_fc1,keep_prob)
#dropout方法能在运行中自动忽略一些神经元,防止过拟合
#初始化第二个全连接层:
W_fc2=weight_variable([500,3])
#500对应上一层的输出,3对应这一场的输出为3类
b_fc2=bias_variable([3])
prediction=tf.nn.softmax(tf.matmul(h_fc1_drop,W_fc2)+b_fc2)
#计算概率
cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_,logits=prediction))
#计算 logits 和 labels 之间的 softmax 交叉熵。
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
#学习率为1e-4,使用梯度下降的方式最小化交叉熵
correct_prediction = tf.equal(tf.argmax(prediction,1), tf.argmax(y_,1))
#求正确率
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
with tf.Session() as sess: #启动图
sess.run(tf.initialize_all_variables()) #初始化全部变量
for i in range(120): #我们共1200个训练数据,这里我们10个为1批次batch 进行
TrainData_batch = train_dataN[i * 10:(i + 1) * 10]
label_batch = train_labelN[i * 10:(i + 1) * 10]
sess.run(train_step,feed_dict={x:TrainData_batch,y_:label_batch,keep_prob:0.7})
#keep_prob:0.7 只有70%的神经元工作
"""
if i%4==0: #训练每进行4个批次,我们就传入一个批次的测试数据,检测当前的正确率
t=int(i/4)
TestData_batch=test_dataN[t * 10:(t + 1) * 10]
TestLabel_batch=test_labelN[t * 10:(t + 1) * 10]
#pre=sess.run([prediction,y_],feed_dict={x:TrainData_batch,y_:TestLabel_batch,keep_prob:1.0})
#print(pre) pre为预测结果和真实标签,大家可以打印出来看看
acc=sess.run(accuracy,feed_dict={x:TrainData_batch,y_:TestLabel_batch,keep_prob:1.0})
print("Iter "+str(t)," accuracy= "+str(acc))
"""
print(sess.run(accuracy,feed_dict={x:test_dataN,y_:test_labelN,keep_prob:1.0}))
#再训练完成后,一次性将所有测试数据传入进行测试,得到总的正确率。
#注意!使用该方式,则不能使用使用上方 检测当前的正确率,不然会造成正确率偏大(因为测试数据也经过了训练)
3.运行结果:
通过测试,我得到的测试数据总的正确率为70%~80%左右。
而每进行4个批次,传入一个批次的测试数据时正确率总时很低,并且没有呈现明显的递增变化,这可能因为测试和训练的数据过少造成:
4.需要注意的点:
以下仅仅是我在学习过程中产生的思考和总结,并不代表正确答案,大家如果有更好解答或其它理解可以在评论区讨论。
1.通过CNN的方式实现文本分类是否具有可靠性?
我认为可靠性并不如CNN对图片的分类,因为文本是1维数据,为了通过卷积神经网络,需要将其变化为多维度,而在变化后(比如1x300变为10x30),这样的变化有很多种。而无论怎么改变,原本是1维的数据,在多维度上究竟有何联系?我认为,这样意义不大的变换并不会带来什么更好的结果。
对此,我设定了一个简单的神经网络,不使用卷积,对上述文本进行了分类实验
其代码如下(讲解就不再给出了):
import dataN
import tensorflow as tf
train_dataN,train_labelN,test_dataN,test_labelN=dataN.dataone()
sess = tf.InteractiveSession()
x = tf.placeholder("float", shape=[None, 300])
y_ = tf.placeholder("float", shape=[None, 3])
W = tf.Variable(tf.zeros([300, 3]))
b = tf.Variable(tf.zeros([3]))
sess.run(tf.global_variables_initializer())
y = tf.nn.softmax(tf.matmul(x, W) + b)
cross_entropy = -tf.reduce_sum(y_ * tf.log(y))
train_step = tf.train.GradientDescentOptimizer(0.001).minimize(cross_entropy)
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
for i in range(120):
TrainData_batch = train_dataN[i * 10:(i + 1) * 10]
label_batch = train_labelN[i * 10:(i + 1) * 10]
train_step.run(feed_dict={x: TrainData_batch, y_: label_batch})
for t in range(30):
TestData_batch = test_dataN[t * 10:(t + 1) * 10]
TestLabel_batch = test_labelN[t * 10:(t + 1) * 10]
result = sess.run(correct_prediction, feed_dict={x: TestData_batch, y_: TestLabel_batch})
print(result)
对测试数据的运行结果如下:
我们可以发现,通过简单的神经网络训练,反而得到了更优的效果。但这并不能完全说明上述猜测,或许多层卷积网络有更好的文本分类方式,在之后的学习中或许能解此疑惑。
2.关于卷积层和池化层的输出大小:
这里就不进行讲解了,在了解卷积和池化的原理,和padding方法SAME和VALID后,我们就能自己推导得出
3.卷积层和全连接层的作用:
卷积取的是局部特征,全连接就是把以前的局部特征重新通过权值矩阵组装成完整的图。(来自知乎)
4.全连接层神经元个数对结果的影响:
以下结果仅针对上述代码和数据:
2048个神经元,正确率:75%~85%
1024个神经元,正确率:75%~85%
500个神经元,正确率:70%~80%
100个神经元,正确率:60%~80%
50个神经元,正确率:50%~70%
神经元是否是越多越好呢?
在测试到4096个神经元时,结果出现了不稳定线性,有的很高超过了85%,有的甚至不到50%
在询问了一位大佬后,得到了这样的结果:
神经元数量的设置就是玄学,并不是越高或者越低越好,正确率也不一定与神经元数量呈正比,往往通过多次调试进行选择。
5.关于数据:
在数据传入之前,一定要对数据进行乱序处理!
在未经过乱序处理或者打乱不完全或有规则的打乱,神经网络往往会学习到这些规律性变化,对于满足这些规律的测试文本,往往能获得到惊人的效果。
在之前,我尝试过使用【第一类文本,第二类文本,第三类文本,第一类文本…】的有规律性方式打乱训练文本和测试文本,再经过简单的神经网络(无卷积),这时其正确率就已经达到了98%以上,这样的结果明显是不和常规的,仅仅对有此规律的文本起作用。