zl程序教程

您现在的位置是:首页 >  工具

当前栏目

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

笔记学习入门代码 实现 实践 分类 文本
2023-09-11 14:16:33 时间

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

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

 

 

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