文本CNN-中文酒店评论的二分类

    今天想温习一下NLP,正巧手上有个酒店评论的数据集,于是就做一下酒店评论的二分类吧。

    下面开始写程序。

    

1.读取语料

    我使用的语料'谭松波--酒店评论语料',有10000条数据。数据集的每条评论放在一个.txt文件里面的,通过下面的程序读取出来

#获取训练语料
import os
neg_dir=r'comments/10000/neg'#负面评论
pos_dir=r'comments/10000/pos'#正面评论
texts=[]
labels=[]
for fname in os.listdir(pos_dir):
    if(fname[-4:]=='.txt'):
        f=open(os.path.join(pos_dir,fname),encoding='utf-8')
        t=f.read()
        texts.append(t.strip().replace('\n',''))
        f.close()
        labels.append(1)
for fname in os.listdir(neg_dir):
    if(fname[-4:]=='.txt'):
        f=open(os.path.join(neg_dir,fname),encoding='utf-8')
        texts.append(f.read())
        f.close()
        labels.append(0)


2.读取停用词

    语料文本里面有一些就是没什么用而且还影响准确度的词语,即停用词。常见的停用词网络上都有整理,我这里有个汇总的文本,将其中的停用词读取出来。

with open('stopwords.txt', encoding='utf-8') as file:
    words = file.readlines()
stop_words = [word.strip().replace('\n', '') for word in words]


3.文本预处理

    原始的语料是一段文本,需要处理后才能放进神经网络模型里面。第一步就是分词,统一训练数据的长度。因为英文单词之间都有空格,故英文语料的分词很简单。中文的分词就要麻烦一些,常见的中文分词方法有:

  • 基于规则的分词方法

  • 基于统计的分词方法

  • 基于语义的分词方法

  • 基于理解的分词方法

    这里采用'结巴分词'进行分词,jieba分词主要是基于统计词典,构造一个前缀词典;然后利用前缀词典对输入句子进行切分,得到所有的切分可能,根据切分位置,构造一个有向无环图;通过动态规划算法,计算得到最大概率路径,也就得到了最终的切分形式。

    使用结巴分词进行切分之后,再去掉停用词,然后使用keras的Tokenizer用来对文本中的词进行统计计数,生成文档词典,以支持基于词典位序生成文本的向量表示。 texts_to_sequences()函数得到序号表示的文本序列,再用pad_sequences()统一文本序列的长度。

import jieba
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
import numpy as np

MAX_WORDS=100000 #使用100000个词语
MAX_LEN=100 #每条评论的长度

#将句子分词并用空格隔开
textList=[' '.join([w for w in jieba.cut(text) if w not in stop_words]) for text in texts]
tokenizer=Tokenizer(num_words=MAX_WORDS)
tokenizer.fit_on_texts(texts=textList)
sequences=tokenizer.texts_to_sequences(texts=textList)
word_index=tokenizer.word_index
data=pad_sequences(sequences=sequences,maxlen=MAX_LEN)
labels=np.asarray(labels)


4.划分训练集、验证集、测试集

    所用的数据集总长10000条,按照60%,20%,20%的比例划分训练集、验证集和测试集。

#各数据集数量
TRAIN_SAMPLES=6000
VALIDATION_SAMPLES=2000
TEST_SAMPLES=2000
#打乱数据集
indices=np.arange(data.shape[0])
np.random.shuffle(indices)
data=data[indices]
labels=labels[indices]

x_train=data[:TRAIN_SAMPLES]
y_train=labels[:TRAIN_SAMPLES]

x_val=data[TRAIN_SAMPLES:TRAIN_SAMPLES+VALIDATION_SAMPLES]
y_val=labels[TRAIN_SAMPLES:TRAIN_SAMPLES+VALIDATION_SAMPLES]

x_test=data[TRAIN_SAMPLES+VALIDATION_SAMPLES:]
y_test=labels[TRAIN_SAMPLES+VALIDATION_SAMPLES:]


5.读取预训练的词嵌入文件

    我使用的中文词向量是github上面的一个项目:https://github.com/Embedding/Chinese-Word-Vectors,包含了通过各种语料库训练得到的词向量,我这个程序里面使用的是基于知乎问答的中文词向量。

embeddings_index={}
f=open('sgns.zhihu.word',encoding='utf-8')
for line in f:
    values=line.split()
    word=values[0]#第一个是单词
    coefs=np.asarray(values[1:],dtype='float32')#后面都是系数
    embeddings_index[word]=coefs
f.close()


6.准备预训练的词嵌入矩阵

    把之前分词器里面的单词变换成词向量,并组成词向量矩阵。

EMBEDDING_DIM=300#词向量的长度
embedding_matrix=np.zeros((MAX_WORDS,EMBEDDING_DIM))
for word,i in word_index.items():
    word_vector=embeddings_index.get(word)
    if(word_vector is not None):#若是未登录词,则词向量为初始值0
        embedding_matrix[i]=word_vector


7.定义模型

    模型的架构如下:

a.png

就是在全连接层前面加上一层CNN,效果更好。

from keras.models import Sequential
from keras.layers import *

model=Sequential()
model.add(Embedding(MAX_WORDS,EMBEDDING_DIM,input_length=MAX_LEN))#词嵌入层

model.add(Conv1D(256,3,activation='relu'))
model.add(Dropout(rate=0.3))
model.add(MaxPooling1D(3,padding='valid'))

model.add(Flatten())
model.add(Dense(256,activation='relu'))
model.add(Dense(64,activation='relu'))
model.add(Dense(1,activation='sigmoid'))
model.summary()


8.把词嵌入矩阵载入到词嵌入层中

model.layers[0].set_weights([embedding_matrix])
model.layers[0].trainable=False#冻结


9.编译并训练模型

model.compile(optimizer='rmsprop',loss='binary_crossentropy',metrics=['acc'])
history=model.fit(x_train,y_train,epochs=20,batch_size=1024,validation_data=(x_val,y_val))
model.save_weights('hotel_comments_classify.h5')

训练结果如下:

Train on 6000 samples, validate on 2000 samples
Epoch 1/30
6000/6000 [==============================] - 6s 1ms/step - loss: 0.4524 - acc: 0.7933 - val_loss: 0.3368 - val_acc: 0.8625
Epoch 25/30
6000/6000 [==============================] - 5s 874us/step - loss: 0.0410 - acc: 0.9922 - val_loss: 1.5001 - val_acc: 0.8690
Epoch 26/30
6000/6000 [==============================] - 5s 874us/step - loss: 0.0379 - acc: 0.9915 - val_loss: 1.4396 - val_acc: 0.8820
Epoch 27/30
6000/6000 [==============================] - 5s 871us/step - loss: 0.0537 - acc: 0.9897 - val_loss: 1.3027 - val_acc: 0.8810
Epoch 28/30
6000/6000 [==============================] - 5s 873us/step - loss: 0.0407 - acc: 0.9918 - val_loss: 1.4220 - val_acc: 0.8885
Epoch 29/30
6000/6000 [==============================] - 5s 875us/step - loss: 0.0433 - acc: 0.9908 - val_loss: 1.2236 - val_acc: 0.8885
Epoch 30/30
6000/6000 [==============================] - 5s 873us/step - loss: 0.0337 - acc: 0.9935 - val_loss: 1.2207 - val_acc: 0.8945

    可以看到验证精度最高在0.8945,精度不算很高。这是由于我的超参随便调模型过于简单外加数据集太小的缘故。

    若是去掉CNN层,结果如下:

Train on 6000 samples, validate on 2000 samples
Epoch 1/30
6000/6000 [==============================] - 8s 1ms/step - loss: 0.4893 - acc: 0.7792 - val_loss: 0.4644 - val_acc: 0.7890
Epoch 28/30
6000/6000 [==============================] - 8s 1ms/step - loss: 4.7123e-04 - acc: 0.9997 - val_loss: 1.9811 - val_acc: 0.8400
Epoch 29/30
6000/6000 [==============================] - 8s 1ms/step - loss: 4.7289e-04 - acc: 0.9997 - val_loss: 1.9799 - val_acc: 0.8415
Epoch 30/30
6000/6000 [==============================] - 8s 1ms/step - loss: 4.7282e-04 - acc: 0.9997 - val_loss: 1.9763 - val_acc: 0.842521

    可以看到其验证精度只有0.84,比加上CNN层时要低的多。


10.测试模型

model.evaluate(x_test,y_test)
2000/2000 [==============================] - 1s 562us/step
[1.269636719284812, 0.89]
 测试集上的精度与验证集差不多。


代码和数据集放在我的github上面:https://github.com/chenjianqu/NLP。中文词向量:https://github.com/Embedding/Chinese-Word-Vectors


参考文献

[1] .结巴分词原理介绍.https://blog.csdn.net/baidu_33718858/article/details/81073093.2018-7-17

下一篇:

首页 所有文章 机器人 计算机视觉 自然语言处理 机器学习 编程随笔 关于