NLP文本分类入门学习及TextCnn实践笔记——代码实现(二)

    技术2022-07-13  89

    本篇主要介绍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。

    这三处改完了,准备好训练样本,就可以开始模型训练了。

     

     

    后续篇章将介绍模型训练和服务化封装的内容。

    Processed: 0.010, SQL: 9