从零开始学Python自然语言处理(十一)——keras实现textCNN

浏览: 2774

前文传送门:

从零开始学自然语言处理(十)—— 最大匹配算法分词

“说实在的,理论型的东西,我不太爱看。看得再多,知道是那么回事,但就是写不出来。如果有一个现成的例子让我看一下,我理解得会更快。”所以这次以英文文本分类为例,写一个textCNN的实例,同时加载预训练的词向量来提升模型性能。最后再分享一些实战经验和一些小trick。

通俗理解CNN

在textCNN的论文中,作者使用了三种尺寸的卷积核,尺寸分别是2、3、4。怎么理解这个卷积核呢?在文本上的卷积过程,其实可以看作是提取文本的n-gram特征,使用大小为2、3、4的卷积核就相当于在提取文本的2-gram,3-gram,4-gram特征,而且又因为每种卷积核的个数有多个(比如每种有128个),那么textCNN就可以提取丰富的n-gram特征。将这些卷积核提取的特征进行MaxPooling,再进行拼接等操作之后,最后加入一个全连接层,就可以实现分类任务了。

上代码!

    import pandas as pd
    import numpy as np
    import nltk
    from collections import Counter
    from sklearn.model_selection import train_test_split
    from keras.preprocessing.sequence import pad_sequences
    from gensim.models import KeyedVectors #使用gensim导入开源的词向量
    w2v_model = KeyedVectors.load_word2vec_format('w2v/GoogleNews-vectors-negative300.bin', binary=True) # 加载谷歌词向量

    读入数据。假设数据都保存在csv文件中。在train.csv文件中,只有两列,分别是text和label,在test文件中,只有一列text。

      train = pd.read_csv('data/train.csv')
      test = pd.read_csv('data/test.csv')

      为了进行词频统计, 需要将训练集和测试集拼接起来。(否则,如果只统计train里的词频,那么test集中某些词可能未出现在train中,模型没有见过这些词,会被当做未登录词处理。)

        test['label'] = np.nan   #先填充test集的label列
        table = pd.concat([train, test]) #使用pandas的concat方法进行拼接

        词频统计、建立词汇表

          min_count = 5       #最小词频
          max_count = 6000 #最大词频
          max_len = 150 #文本最大截断长度
          words_dict = [] #词汇表
          for txt in table['text']:
          words_dict+=nltk.tokenize.word_tokenize(txt) #使用nltk进行英文分词

          words_dict = dict(Counter(words_dict)) #使用Counter方法进行词频统计,得到words_dict字典,key为某个词,value是这个词在语料库中出现的频次

          接下来,删除低频词和高频词。并建立word和id的映射字典。

            words_dict = {k:v for k,v in words_dict.items() if v>min_count and v < max_count }
            word2id = {j:i+1 for i, j in enumerate(words_dict)} #+1是因为0是pad
            id2word = {v:k for k, v in word2id.items()}

            导入预训练的词向量

              word_embedding_matrix = np.zeros((len(word2id)+1, 300))    #使用numpy建立一个(词的个数*词向量维度)的大矩阵,
              for word in word2id: #如果语料中的词出现在预训练词向量中,就将矩阵中对应的位置赋值为对应的词向量
              if word in w2v_model:
              word_vec = w2v_model[word]
              word_embedding_matrix[word2id[word]] = word_vec

              构造训练数据

                train_data = []
                train_label = []
                for _, row in train.iterrows():
                txt = row['text']
                label = row['label']
                words=nltk.tokenize.word_tokenize(txt)
                sample = []
                for w in words:
                if w in word2id:
                one_data.append(word2id.get(w)) #如果词在word2id中,取出对应的id,否则跳过。
                train_data.append(sample)
                train_label.append(label)

                train_label = np.asarray(train_label)
                train_label = np.expand_dims(train_label,-1)
                #使用pad_sequences对不等长序列进行截断和补零。post表示从序列末尾进行补零或截断操作,如果设为pre,就是从序列开头进行操作。
                train_data = pad_sequences(maxlen=max_len, sequences=train_data, padding='post', truncating='post', value=0)

                现在开始搭建textCNN

                  from keras import Input
                  from keras.layers import Conv1D, MaxPool1D, Dense, Flatten, concatenate, Embedding, Dropout
                  from keras.models import Model

                  embedding_dim=300 #定义词向量维度
                  x_input = Input(shape=(max_len,))
                  x_emb = Embedding(input_dim=len(word2id)+1, output_dim=embedding_dim, input_length=max_len,
                  weights=[word_embedding_matrix], trainable=False)(x_input) #为embedding层赋值权重,并将trainable参数设为Fasle

                  pool_output = []
                  kernel_sizes = [2, 3, 4]
                  drop_rate = 0.5 #在这里,小编用的drop_rate是0.5。

                  for kernel_size in kernel_sizes: #使用一个for循环来将各个kernel_sizes的卷积核加入到模型中,并用MaxPool1D进一步提取特征
                  c = Conv1D(filters=256, kernel_size=kernel_size, strides=1)(x_emb)
                  p = MaxPool1D(pool_size=int(c.shape[1]))(c)
                  d = Dropout(drop_rate)(p)
                  pool_output.append(d)

                  pool_output = concatenate([p for p in pool_output]) #将每个pooling之后的结果进行拼接。

                  x_flatten = Flatten()(pool_output)
                  y = Dense(3, activation='softmax')(x_flatten) # 这里假设是3分类问题,使用softmax激活函数。如果是2分类,dense输出设为2,并将激活函数设为‘sigmoid’就可以了

                  model = Model([x_input], outputs=[y])
                  model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['accuracy']) #如果是2分类,loss设为sparse_binary_crossentropy
                  model.summary()

                  划分验证集

                    train_x, val_x, train_y, val_y = train_test_split(train_data, to_categorical_label, test_size = 0.2)

                    设置一些callbacks,包括早停和保存最优模型

                      import keras.callbacks as CB
                      import os
                      save_path = "../model"
                      file_path = "model_textCNN.h5" #用于存储最佳模型的路径
                      callbacks = [CB.EarlyStopping(monitor='val_loss', patience=5, verbose=0), #设置早停,当指标在5次之内没有提升时,停止训练
                      CB.ModelCheckpoint(os.path.join(save_path, file_path), monitor='val_loss', save_best_only=True, verbose=0)] #保存最优模型到指定路径

                      进行训练

                        model.fit(train_x, train_y, validation_data=(val_x, val_y),epochs=40, batch_size=128,verbose=1, callbacks=callbacks)

                        一些tricks

                        预训练词向量的使用

                        1.加载预训练词向量可以有效提升模型的效果。但是这个trainable参数设为True或False,却对结果的影响很大。
                        a)设置为True。收敛速度快,模型过拟合严重。      

                        b)设置为False。收敛速度较慢,模型效果最好。


                        以上结论是经验之谈,有可能是小编使用的数据集较小,容易过拟合。其实最好的方法是embedding层和其它层的学习率设为不一样,给embedding层一个很小的学习率,仅仅起到微调的作用,其它层设为较大的学习率。至于这个trick可不可以实现呢?答案是可以的,只不过操作起来比较复杂,高阶用户可以使用,小编还在学习中。2.dropout的使用
                        dropout真的是很神奇,操作简单而且能有效遏制过拟合。不要吝惜使用dropout,只要你感觉你的模型过拟合了,就使用dropout吧。其它什么花里胡哨的normalization虽然也有防止过拟合的作用,但是感觉有点用药过猛,稍不留神就让模型欠拟合,而且收敛起来很困难,自己的小霸王也等不起模型收敛。当然了,如果使用了normalization且让模型收敛了,这个模型绝对顶呱呱了。

                        3.模型是否训练好了的参考是什么?
                        通常我们在设置早停的时候,会设置一个监督指标,小编最常使用的是val_loss,因为loss是对模型效果检验较为准确的指标,loss一直在下降,就说明模型正在正确拟合你的数据。有的时候,acc在下降,loss也在下降,这个现象不要以为模型出了问题,要让它继续训练下去。
                        此外,我们还需要注意一点就是,train和val的loss相差不要太大,模型在训练的过程中,两者的loss越接近,才说明你模型训练的效果越好。

                        4.低频词和高频词的处理
                        低频词可以看作是NLP中的噪声;高频词可以看作是NLP中信息量较低的词。一般情况下,将这些词删掉可以提高模型性能。但这也不是绝对的,有的时候不删除这些词,效果偏偏会更好。一切还是要以实践为准;经验只适用于特定场合。

                        5.序列截断
                        序列截断也是NLP常用的操作。不同截断长度也对模型有较大影响。以小编的经验来看,截断的效果要好于不截断;截断长度越大,一般效果也越好。这是玄学,也是超参,需要我们自己去调。

                        结束语

                        理论学习可以帮助我们理解模型,但是实践才能检验我们是否真正学会。

                        关注“数据科学杂谈”公众号,带你零基础学习自然语言处理~

                        推荐 0
                        本文由 ID王大伟 创作,采用 知识共享署名-相同方式共享 3.0 中国大陆许可协议 进行许可。
                        转载、引用前需联系作者,并署名作者且注明文章出处。
                        本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责。本站是一个个人学习交流的平台,并不用于任何商业目的,如果有任何问题,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

                        0 个评论

                        要回复文章请先登录注册