本篇主要介绍TextCnn针对中文的分本分类的代码实现。下一篇计划讲模型训练及线上文本分类。
代码基于开源代码 https://github.com/dennybritz/cnn-text-classification-tf
建议对NLP文本分类或CNN不了解的先阅读我的上一篇blog及以下的大神blog :
NLP文本分类入门学习及TextCnn实践笔记(一)
https://blog.csdn.net/wangyueshu/article/details/106493048
参考的大神blog:
NLP文本分类常用模型总结:https://blog.csdn.net/liuchonge/article/details/77140719
Cnn原理讲解:https://zhuanlan.zhihu.com/p/77634533?from_voters_page=true
代码理解参考大神:http://blog.csdn.net/liuchonge/article/details/60333323
正文:
原TextCnn的开源代码中处理的是英文的文本分类。上篇中讲过,要做文本分类主要几步:文本预处理,词嵌入(分词、词向量和word embedding),特征学习。而中英文的文本分类差别主要就集中在文本预处理、词嵌入上。
根据大神的代码分析,需要调整的主要是三个文件data_helper.py、train.py和text_cnn.py。看代码可以从train.py看起,模型训练的入口,方便将整体逻辑串联起来。我理解的与中文化有关的部分代码流程如下图:
第一步 数据清洗。
先对数据进行清洗,也就是文本预处理,去除掉不必要的表情、符号、链接等,并进行同义词替换,在我的应用场景中这一应用对模型的效果非常重要,这是后话。
第二步 生成词典及样本向量。
代码对正负训练样本(n条正样本,m条负样本)中涉及到的所有词生成一个词典,格式如下,每个词都有自己的一个唯一编号。
{'<UNK>': 0, '春季': 1, '落下': 2, '已': 3, '你': 4, '刀塔': 5, '先峰': 6, '夏季': 7, '彩票': 8, '棋牌': 9, '来': 10, '自己': 11, '各种': 12, '得': 13, '等等': 14, '王者': 15,'喜欢':16,...}
然后每一个样本就可以生成一个样本向量,向量长度就是训练样本中那个包含词最多的样本的词个数,即最大文本长度(不是字节长度,是词个数)。
假设最大文本长度是6,正样本“你喜欢刀塔”的样本向量就是[4, 16, 5,0,0,0],不够长的部分拿0补齐。
关于VocabularyProcessor的理解请参考:https://www.jianshu.com/p/db400a569730
这里涉及到一个关键问题就是“词”是怎么来的,也就是分词。后面说。
第三步 词向量张量。
有了词典,也有了样本向量。本个词还需要一个向量表示,也就是词向量。词向量携带的信息越充分越好,这里采用wordvec2。然后对照着词典,这个词向量张量每行就分别是0向量、词“春季”的向量、“落下”的向量...。如果词找不到词向量则用0向量。
对于分词及词向量的理解参见上篇。
第四步 词嵌入。
通过词典和词向量张量。只要有样本向量,就能通过编号对应到这个样本顺次由什么词组成,词的向量是什么。然后通过tensorflow提供的embedding函数完成了词嵌入。
原理说完了。我们具体看看代码。
1. data_helper.py
文件中包含了三个方法clean_str、load_data_and_labels、batch_iter。
clean_str:数据清洗方法,这里实现符号等无用信息剔除,同义词替换等信息矫正等。
这里就针对中文重写clean_str。就是一些正则匹配和替换。
load_data_and_labels:调用数据清洗方法,加上标签信息返回。
batch_iter:按照训练参数,打散数据,生成训练用的一个个数据包。
这里涉及两个核心参数,batch_size,num_epochs,影响这训练模式和训练时长。
batch_size:一次训练多少条样本,模型中默认64条。
num_epochs:训练纪元数,即一个样本要参与多少次训练。
假设正负样本总共有4000条。最后的step数就是(4000/64)*200=12500步。
2. train.py
原代码:
# Build vocabulary
max_document_length = max([len(x.split(" ")) for x in x_text])
vocab_processor = learn.preprocessing.VocabularyProcessor(max_document_length)
x = np.array(list(vocab_processor.fit_transform(x_text)))
修改后的代码
# Build vocabulary
max_document_length = max([len(jiebafenci.jiebafenci(x)) for x in x_text])
vocab_processor = learn.preprocessing.VocabularyProcessor(max_document_length, tokenizer_fn=jiebafenci.jiebafenci_fn)
x = np.array(list(vocab_processor.fit_transform(x_text)))
这三条语句做的事情就是上面原理部分讲到的生成词典(前两行代码)和样本向量(第3行代码)
可以看到中英文核心的差别就是在分词上。
英文空格间隔就是不同的词,所以分词就是x.split(" ")。但中文博大精深,需要专业的分词,这里使用的是jieba分词,并引入自定义词典和停止词(这两个配置对于后续的模型调优非常重要,后话)
另外,jiebafenci的方法要自己实现。计算词个数的分词方法和VocabularyProcessor传参的分词方法不能用一个,后者可参照默认的tokenizer_fn实现。
3. text_cnn.py
原代码:
# Embedding layer
with tf.device('/cpu:0'), tf.name_scope("embedding"):
self.W = tf.Variable(
tf.random_uniform([vocab_size, embedding_size], -1.0, 1.0),
name="W")
self.embedded_chars = tf.nn.embedding_lookup(self.W, self.input_x)
self.embedded_chars_expanded = tf.expand_dims(self.embedded_chars, -1)
修改之后的代码:
z = np.zeros((1, 300), dtype=float) #word2vec的词向量是300纬
m = z#初始化词表,第一个0向量对应'<UNK>'
#加载词向量
w2v_model = gensim.models.KeyedVectors.load_word2vec_format('./model/sgns.weibo.word')
#样本生成的词库-词向量索引张量
self.W = tf.Variable(
tf.random.uniform([len(vocab_processor.vocabulary_), embedding_size], -1.0, 1.0),
name="W")
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
#先将每个词的词向量放到numpy中
non_count=0
for (k, v) in vocab_processor.vocabulary_._mapping.items():
if k != '<UNK>':
try:
vec = w2v_model.get_vector(str(k).strip())
v = np.mat(vec)
m = np.concatenate((m, v), axis=0)
except:
print("can not find ", k, "word2vec value")
non_count=non_count+1
m = np.concatenate((m, z), axis=0)
#加载到张量中
print("找不到词向量的词个数:",non_count)
self.W.load(m)
# Embedding layer
with tf.device('/cpu:0'), tf.name_scope("embedding"):
# self.W = tf.Variable(
# tf.random_uniform([self.vocab_size, embedding_size], -1.0, 1.0),
# name="W")
self.embedded_chars = tf.nn.embedding_lookup(self.W, self.input_x)
self.embedded_chars_expanded = tf.expand_dims(self.embedded_chars, -1)
这一部分实现的是词向量张量的生成。中英文处理的差异主要在词向量上。
原文中英文词向量是使用的tensorflow生成的随机向量。因为只要词向量唯一,就可以初步满足词向量的使用需求,但是词义和词距离的信息就完全没有。
因此中文的场景中引入了wordvec2的词向量(模型文件自己下载,上篇文里有)。代码需要完成的就是为词典中每一个词查到词向量,然后拼成词向量张量。wordvec2中找不到词向量的就用0向量代替。
关键点:
embedding_dim: 词嵌入向量纬度。原文中选取的128。这个值要跟词向量的纬度一致,我选用的wordvec2中词向量的纬度是300,因此这个模型参数要改成300。
这三处改完了,准备好训练样本,就可以开始模型训练了。
后续篇章将介绍模型训练和服务化封装的内容。