上一篇博文已经分析了CNN如何应用在文本分类中:
https://blog.csdn.net/qq_43012160/article/details/96572537
这一篇我们来讲一讲怎么用keras实现一个CNN并用它来文本分类。
先放一张原理图:
链接:https://pan.baidu.com/s/1XWBOcCMvHRuZEGdkKDr-fQ
提取码:o7st
因为python是弱类型语言,很多时候我们在看到不熟悉的代码的时候(特别是没有注释的时候)看起来会非常的难受。就比如一个函数,因为没有C++java那样显式的参数表,有时候都不知道传什么参进去。其实通过pycharm是可以通过设置来看函数的参数表的:
从菜单打开Files->Settings,打开下图界面,勾上Parameter Info里的第一项设置
再打开下图界面,勾上Other里的Show quick documentation…
然后把鼠标放在想看到函数上,就有函数的参数表和介绍了:
import pandas as pd
import numpy as np
import jieba
from keras import models
from keras import layers
from keras.utils.np_utils import to_categorical
from keras.preprocessing.text import Tokenizer
from sklearn.preprocessing import LabelEncoder
from sklearn.feature_extraction.text import CountVectorizer
from gensim.models import word2vec
from keras.preprocessing.sequence import pad_sequences
from keras.layers import *
from keras.models import Model
from sklearn import metrics
from keras.models import load_model
#读入数据集
train_data = pd.read_csv('mytrain.csv', lineterminator='\n')
test_data=pd.read_csv('testCSV.csv', lineterminator='\n')
#数据的预处理:
#利用LabelEncoder对数据标签进行规格化处理
def encodeLabel(data):
listLable=[]
for lable in data['lable']:
listLable.append(lable)
#到这里都是把lable整合到一起,下面是规格化处理
le = LabelEncoder()
resultLable=le.fit_transform(listLable)
return resultLable
trainLable=encodeLabel(train_data)
testLable=encodeLabel(test_data)
#这里出来是所有review的集合:
def getReview(data):
listReview=[]
le = LabelEncoder()
for review in data['review']:
listReview.append(review)
return listReview
trainReview=getReview(train_data)
testReview=getReview(test_data)
因为后面是要用对每个词Embedding(这里选用的word2vec)的,所以肯定要对输入进来的句子进行分词,分词之后构成了一个不包括停止词的词袋vocab:
stoplist=[None,'.', ':', '-', '+', '/', ',','0','1','2','3','4','5','6','7','8','9','0']
#分词:
def wordCut(Review):
Mat=[]
for rec in Review:
seten=[]
sentence = list(map(lambda x: x.strip().lower() if len(x.strip().lower()) > 0 else None, jieba.cut(rec))) # 每句话里的单词拿出来
for wd in sentence:
if not(wd in stoplist):seten.append(wd)
Mat.append(seten)
return Mat
trainCut=wordCut(trainReview)
testCut=wordCut(testReview)
wordCut=trainCut+testCut
#求句子最大长度
maxLen=0
for sentence in wordCut:
length=0
for wd in sentence:
if not(wd in stoplist):length=length+1
if (length>maxLen):maxLen=length
#fit_on_texts函数可以将输入的文本中的每个词编号,编号是根据词频的,词频越大,编号越小
tokenizer=Tokenizer()
tokenizer.fit_on_texts(wordCut)
vocab = tokenizer.word_index # 得到每个词的编号,这里的vocab已经剔除掉stoplist了
然后就是喜闻乐见的Embedding(word2vec)了,word2vec本身也是使用前馈神经网络进行训练的,所以不妨在训练之后保存模型,以后用只要加载就好了:
#word2vec的训练:
# 设置词语向量维度
num_featrues = 300
# 保证被考虑词语的最低频度
min_word_count = 5
# 设置并行化训练使用CPU计算核心数量
num_workers =4
# 设置词语上下文窗口大小
context = 5
model = word2vec.Word2Vec(wordCut, workers=num_workers, size=num_featrues, min_count=min_word_count, window=context)
model.init_sims(replace=True)
# 输入一个路径,保存训练好的模型,其中./data/model目录事先要存在
model.save("E:/python/model/CNNw2vModel2")
print(model)
#加载模型,如果之前word2vec已经训练好了直接用这句就好了:
w2v_model = word2vec.Word2Vec.load("E:/python/model/CNNw2vModel2")
之后我们解决一下句子长短不一的问题:利用pad_sequences函数使句子规格化,这里maxLen是我统计的句子最大长度,超过自定义的maxlen长度的句子会被截短,不足的会在前面补0:
#特征数字编号
trainID = tokenizer.texts_to_sequences(trainCut)
testID = tokenizer.texts_to_sequences(testCut)
trainSeq=pad_sequences(trainID,maxlen=maxLen)
testSeq=pad_sequences(testID,maxlen=maxLen)
#标签的独热编码
trainCate = to_categorical(trainLable, num_classes=2) # 将标签转换为one-hot编码
testCate= to_categorical(testLable, num_classes=2) # 将标签转换为one-hot编码
因为要调用Embedding函数进行embedding,把词转化为词向量,而我们是使用word2vec方法,所以需要对Embedding函数的默认矩阵做一下自定义:
#利用训练后的word2vec自定义Embedding的训练矩阵,每行代表一个词(结合独热码和矩阵乘法理解)
embedding_matrix = np.zeros((len(vocab) + 1, 300))
for word, i in vocab.items():
try:
embedding_vector = w2v_model[str(word)]
embedding_matrix[i] = embedding_vector
except KeyError:
continue
#训练模型
def TextCNN_model_1(x_train_padded_seqs, trainCate, x_test_padded_seqs, testCate):
main_input = Input(shape=(maxLen,), dtype='float64')
# 词嵌入(使用预训练word2vec的词向量,自定义权重矩阵,300是输出的词向量维度)
embedder = Embedding(len(vocab) + 1, 300, input_length=maxLen, weights=[embedding_matrix], trainable=False)
embed = embedder(main_input)
# 卷积核个数为128,词窗大小分别为2,3,4,6
cnn1 = Conv1D(128, 2, padding='same', strides=1, activation='relu')(embed)
cnn1 = MaxPooling1D(pool_size=maxLen-1)(cnn1)
cnn2 = Conv1D(128, 3, padding='same', strides=1, activation='relu')(embed)
cnn2 = MaxPooling1D(pool_size=maxLen-2)(cnn2)
cnn3 = Conv1D(128, 4, padding='same', strides=1, activation='relu')(embed)
cnn3 = MaxPooling1D(pool_size=maxLen-3)(cnn3)
cnn4 = Conv1D(128, 6, padding='same', strides=1, activation='relu')(embed)
cnn4 = MaxPooling1D(pool_size=maxLen-5)(cnn4)
# 合并三个模型的输出向量
cnn = concatenate([cnn1, cnn2, cnn3,cnn4], axis=-1)
flat = Flatten()(cnn)
drop = Dropout(0.2)(flat)
#输出层第一个参数2是分类类别数
main_output = Dense(2, activation='softmax')(drop)
model = Model(inputs=main_input, outputs=main_output)
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
#epochs是迭代次数
model.fit(x_train_padded_seqs, trainCate, batch_size=800, epochs=7)
#保存模型
model.save("E:/python/model/TextCNN6")
#主程序调用训练模型:
TextCNN_model_1(trainSeq, trainCate, testSeq, testCate)
在这里插入图片描述
#预测与评估
mainModel = load_model('E:/python/model/TextCNN6')
result = mainModel.predict(testSeq) # 预测样本属于每个类别的概率
score = mainModel.evaluate(testSeq,
testCate,
batch_size=32)
print(score)
运行结果:
正确率只有60%左右,不太尽如人意,后面还要继续调参。
调用tensorflow和keras训练神经网络的时候,每训练一些数据,每进行一次迭代,控制台都会有相应的输出反馈,可以根据反馈的信息(loss是损失函数,acc是这组数据的准确率)进行CNN参数的优化。比如我原来是迭代的10次,后来跑了很多次(参数当然做过调整)发现基本上到第7、8次迭代正确率就不会上升了,后面就有可能过拟合了,于是就把迭代次数调到了7.
又比如设置卷积核窗口大小的时候,我最开始设置过16甚至84的窗口高度,后来想想实在不合常理,谁会说话的时候一个词和84个词之前的词有关键的逻辑关联呢(除了“但”、“不”这样的转折性关键词),所以就把窗口给缩小了,不过一些关键的转折性词可能确实会有这样的关系,后面可以再调调参试试。毕竟如果真没什么用也会被池化层maxPooling给筛掉。
本来我正确率就54%,给我这么一调调上了60%,虽然还是不高。。。