背景
上一篇讲解到了抓包爬取哔哩哔哩课程的评论。接下来需要对这些评论文本进行情感分类——本质上就是一个文本二分类问题。于是使用较为熟悉的深度学习方法进行解决——搭建深度学习模型。
此处需要强调的是,需要获取中文分词的拼音,将中文分词和拼音均送入后续的神经网络,还需要在送入全连接层前进行拼接。
实现
第一步:制作数据集
制作数据集是一件很繁琐的事情,这里为了简化工作,暂且制作了一个由100条好评和100条差评组成的数据集。
第二步:预处理文本
由于我们只考虑中文评论文本,而很多评论中包含很多对分类结果没有意义的英文和数字,以及一些特殊字符(比如空格、换行等),因此编写一个对文本进行预处理的函数。另外预处理完成后,使用jieba
对中文评论文本进行分词,剔除其中的停用词,得到最终的中文分词结果后,再获取它们的拼音,得到最终的数据集。
剔除英文、数字并分词
使用正则表达式可以很方便地实现这一效果。具体参考了这篇博客:https://blog.csdn.net/Wuyeyu2001/article/details/127324156
def pre_filter(text):
'''参考: https://blog.csdn.net/Wuyeyu2001/article/details/127324156'''
text = re.sub('[a-zA-Z0-9]', '', text)
text = re.sub('\W', '', text)
return jieba.lcut(text)
剔除停用词
停用词可以理解为一些语气词、助词等实际意义较弱的词,需要在文本分类中进行去除。从网上下载已经总结出来的停用词文本文件:https://github.com/goto456/stopwords
然后进行读取,并将其中的词存储到列表中:
# 读取停用词
stop_words = []
with open('../data/stopwords/hit_stopwords.txt', 'r', encoding='utf-8') as f:
lines = f.readlines()
for line in lines:
stop_words.append(line.strip())
具体的剔除停用词,可以编写一个过滤器filter
,将存在与停用词表中的词过滤掉即可。
获取词汇拼音
需要使用一个强有力的包xpinyin
,按照如下语句调用即可很方便地获取目标语句的拼音(带音调):
p = Pinyin()
data = "哔哩 哔哩 好用 喜欢"
pinyin_data = p.get_pinyin(data, '', tone_marks='marks')
# pinyin_data: bìlī bìlī hǎoyòng xǐhuān
项目中只需要将data
替换成已经划分好的中文分词即可。
小结
综上,完整的文本预处理得到新的文本数据集的代码如下。
csv_file = "../data/dataset.csv"
content = pd.read_csv(csv_file, encoding='gbk').values
save_file = "../data/new_worddataset.csv"
save_pinyin_file = "../data/new_dataset.csv" # 最终得到的新数据集文件
p = Pinyin()
with open(save_file, "w", newline='') as f1, open(save_pinyin_file, "w", newline='', encoding='utf-8') as f2:
writer = csv.writer(f1)
writer.writerow(['content', 'label'])
pinyin_writer = csv.writer(f2)
pinyin_writer.writerow(['text', 'pinyin', 'label'])
for sample in content:
reply, label = sample[0], sample[1]
reply = pre_filter(reply)
data = list(filter(lambda w: w not in stop_words, reply))
data = ' '.join(data)
# 获取分词的拼音
pinyin_data = p.get_pinyin(data, '', tone_marks='marks')
writer.writerow([data, label])
# 将评论文本、拼音、标签一起写入.csv文件,成为完整的一条样本
pinyin_writer.writerow([data, pinyin_data, label])
原始的数据集长这样:
转化后的数据集长这样:
第三步:搭建深度学习模型
我们的深度学习模型由于需要进行文本分类,因此还需要额外一个嵌入(Embedding)层。此外还需要LSTM层和CNN层、全连接层。
嵌入
对词嵌入(word embedding)可以这样理解:计算机程序的处理对象通常是数值对象,而无法直接处理人类所使用的自然语言(字符串),比如计算机视觉中的图像处理,不也是把图像转化成一个一个的像素点数值吗?与此类似,将自然语言的“词汇”转化为可以被计算机程序所计算的数值的过程,就是所谓的词嵌入。更具体地,可以理解为,通过实现构建一个词典(词汇到向量的映射,每一个词汇对应一个数学向量,即词向量),然后处理自然语言时,每每在词典中查找每一个词汇,就将其替换为其所对应的词向量。
那么重点来了,如何得到这样的一个“词典”?一旦得到它,不就可以对用户输入的自然语言进行jieba
分词、查找词汇替换词向量、送入计算程序为计算机处理了?所以接下来看看构建词典的方法。
本项目中使用的技术是word2vec。也就是训练大规模的中文语料,得到一个大的词表,其中每一个词就对应着一个词向量。那么先将目光聚焦到当前构建词表的任务中,而不要去看原初的文本分类大任务。
下载中文语料库
开源的中文语料库特别多,随便到网络上下载一个即可,比如金庸的某小说也行。本项目中已经准备好了一份文本文件,其中的中文语料均已经分词完成。
调用gensim库进行训练
注意,我们的文本分类模型需要接受中文评论和它的拼音作为输入,那么也就是说,我们需要先分别训练一个中文词表和中文拼音词表。它们的训练大同小异,这里仅仅以中文词表的训练构建为例进行说明了。
导入所需要的包:gensim
from gensim.models import KeyedVectors,word2vec,Word2Vec
from gensim import utils
import jieba
创建一个语料处理类,实际上是一个可迭代对象,在其__iter__()
方法中,每次产出文本文件中的一行。
class MyCorpus:
def __init__(self, corpus_path):
self.corpus_path = corpus_path
def __iter__(self):
for line in open(self.corpus_path, encoding='utf-8'):
#对读取的句子进行简单的处理
yield utils.simple_preprocess(line)
然后使用上面定义好的语料处理类,读取准备好的中文语料文件;将读取的结果送入Word2Vec
对象,并指定词向量维度为$300$(一般称之为嵌入维度,embedding_dim)。Word2Vec
是gensim
包中很有用的word2vec训练工具。训练完成后,将模型保存下来(这里选择保存为.vector和.bin文件,当然也可以选择保存为其他格式的模型文件,只不过,.vector模型文件支持点开预览各个词以及对应的词向量)
sentences = MyCorpus(file_path)
model = Word2Vec(sentences=sentences, vector_size=300)
model.wv.save_word2vec_format('dl/word2vec.vector')
model.wv.save_word2vec_format('dl/word2vec.bin')
创建词向量权重矩阵
训练完成后,我们就得到了基于下载的中文语料的中文词向量。为了能够更方便地使用词嵌入,我们需要用到torch
提供的一个接口torch.nn.Embedding.from_pretrained()
导入已经训练完毕得到的词向量权重矩阵。
下面根据训练得到的word2vec模型,创建权重矩阵。
我们先导入之前保存好的模型文件word2vec.vector
:
word_dict = KeyedVectors.load_word2vec_format('./word2vec.vector')
然后获取全部的词,取出它们对应的词向量存入矩阵的同时,建立中文词到数值索引的映射:
word_list = word_dict.index_to_key # ['一个', '可以']
vocab_size = len(word_list)
word2index = {} # 词表,中文词到数值索引的映射表
embeddings = torch.zeros(size=(vocab_size+1, embed_dim)) # 嵌入词表的权重矩阵
for idx in range(len(word_list)):
word2index[word_list[idx]] = idx
embeddings[idx, :] = torch.from_numpy(word_dict[word_list[idx]]) # 填充
定义嵌入层代码如下
net = torch.nn.Embedding.from_pretrained(embeddings=embeddings, freeze=True)
这里的embeddings
就是上面填充得到的词向量权重文件。在嵌入层接受输入时,只需要根据之前建立的中文词导数值索引的映射,将输入的分词转化为它们对应的数值索引,然后送入嵌入层,即可转化为词向量,继续后续的程序了。