文本分类——Embedding、CNN、RNN练手

说明

  • 本文是方法记录,不是完整的项目过程(在我Jupyter上,数据前期预处理部分懒得搬了),也没有调参追求准确度(家里电脑跑不动)。
  • 参考任务来源于Kaggle,地址:电影评论情感分类
  • 本文参考了不同的资料来源,包括斯坦福CS224N的课程资料,网上博客,Keras官方文档等

任务核心部分

1.单词表示

1.1 理论部分

对大部分(或者所有)NLP任务,第一步都应该是如何将单词表示成符合模型所需要的输入。最直接的思路就是将单词(符号)变为词向量。

词向量的表示方法:

  • one-hot 编码:想法直接,但过于稀疏,且词与词之间正交,无法衡量词之间的相似度

  • 基于矩阵分解的方法:比如不同词窗的矩阵,进行SVD分解,得到相应的向量表示。但问题在于矩阵会很大,占用空间。而且加入新的文本需要重新计算,计算成本也高。

  • Word2vec:这部分内容有点多。这部分核心思想:

    Distributional semantics: A word’s meaning is given by the words that frequently appear close-by

    即使用词语的上下文来表示其语义。最直接的是N-gram的想法,接着就是word2vec。
    放一张听课截图:
    在这里插入图片描述
    还要补充给一个Glove,是将word2vec的想法以及co-currence matrix结合的一种方法。
    具体解释可以看这个链接,Glove介绍

    1.2 代码部分
    • 导入预训练的模型
from gensim.test.utils import datapath, get_tmpfile
from gensim.models import KeyedVectors
from gensim.scripts.glove2word2vec import glove2word2vec

glove_file = datapath('C:\\Users\\Administrator\\Documents\jupyter\\2019 最新斯坦福CS224n课件\\1、Introduction and Word Vectors\\glove.6B.100d.txt')
word2vec_glove_file = get_tmpfile("glove.6B.100d.word2vec.txt")
glove2word2vec(glove_file, word2vec_glove_file)
model = KeyedVectors.load_word2vec_format(word2vec_glove_file)
help(model)

这种方式,就可以通过model得到词向量,通过help也可以知道其他操作,计算相似度啥的。具体参照斯坦福课程资料第一讲。

  • Tensorflow中的文本预处理
from tensorflow.keras.preprocessing.text import Tokenizer 
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.utils import to_categorical

def word_encoder(text,label,max_,max_len = 50):
	'''
	输入:
	text:原始文本,一个个句子的列表。
	label:文本的标签
	max_:分词后根据词频取前max_个词
	max_len:得到序列的最大长度
	
	输出:
	文本序列、标签(one-hot)、序列对应的字典
	'''
    tokenizer = Tokenizer(nb_words=max_) #根据词频取top n个词
    tokenizer.fit_on_texts(text)         
    sequences = tokenizer.texts_to_sequences(text) #将文本变为序列
    data = pad_sequences(sequences, maxlen=max_len) #将序列映射到正整数,这里是对序列长度的限制,固定长度
    word_index = tokenizer.word_index  #得到正整数与单词对应的字典
    labels = to_categorical(np.asarray(label))
    print('Shape of data tensor:', data.shape)
    print('Shape of label tensor:', labels.shape)
    
    return data,labels,word_index

接下来就是在tf框架下,构建embedding层

  • Embedding

    根据数据量

    o 数据量较大:可以直接随机初始化embeddings,然后基于语料通过训练模型网络来对embeddings进行更新和学习。

    o 数据量较小:可以利用外部语料来预训练(pre-train)词向量,然后输入到Embedding层,用预训练的词向量矩阵初始化embeddings。(通过设置weights=[embedding_matrix])。

    根据训练方式

    o 静态(static)方式:训练过程中不再更新embeddings。实质上属于迁移学习,特别是在目标领域数据量比较小的情况下,采用静态的词向量效果也不错。(通过设置trainable=False)

    o 非静态(non-static)方式:在训练过程中对embeddings进行更新和微调(fine tune),能加速收敛。(通过设置trainable=True)

    Embedding层的说明
    (来源于keras以及网上的博客)

      keras.layers.Embedding(input_dim, output_dim, embeddings_initializer='uniform', embeddings_regularizer=None, 
                     activity_regularizer=None, embeddings_constraint=None, mask_zero=False, input_length=None)`
                     
      将正整数(下标)转换为具有固定大小的向量,如[[4],[20]]->[[0.25,0.1],[0.6,-0.2]] 
    
      ·input_dim: int > 0。词汇表大小, 即,最大整数 index + 1。
    
      ·output_dim: int >= 0。词向量的维度。
    
      ·embeddings_initializer: embeddings 矩阵的初始化方法 (详见 initializers)。
    
      ·embeddings_regularizer :embeddings matrix 的正则化方法 (详见 regularizer)。
    
      ·embeddings_constraint: embeddings matrix 的约束函数 (详见 constraints)。
    
      ·mask_zero: 是否把 0 看作为一个应该被遮蔽的特殊的 "padding" 值。这对于可变长的循环神经网络层
      			  十分有用。 如果设定为 True,那么接下来的所有层都必须支持 masking,否则就会抛出异常。 
      			  如果 mask_zero 为 True,作为结果,索引 0 就不能被用于词汇表中 
      			 (input_dim 应该与 vocabulary + 1 大小相同)。
    
      ·input_length: 输入序列的长度,当它是固定的时。 如果你需要连接 Flatten 和 Dense 层,
                     则这个参数是必须的 (没有它,dense 层的输出尺寸就无法计算)。
    

    静态方式

    import os
    import codecs
    from tensorflow.keras.layers import Embedding
    GLOVE_DIR = 'C:\\Users\\Administrator\\Documents\jupyter\\2019 最新斯坦福CS224n课件\\1、	Introduction and Word Vectors'
    #加载词向量
    embeddings_index = {}
    f = open(os.path.join(GLOVE_DIR, 'glove.6B.50d.txt'), 'r',encoding='utf-8')
    for line in f:
     values = line.split()
    	word = values[0]
    	coefs = np.asarray(values[1:], dtype='float32')
    	embeddings_index[word] = coefs
    f.close()
    #构建权重矩阵
    EMBEDDING_DIM = 50
    embedding_matrix = np.zeros((len(word_index) + 1, EMBEDDING_DIM))
    for word, i in word_index.items():
    	embedding_vector = embeddings_index.get(word)
    	if embedding_vector is not None:
       	 # words not found in embedding index will be all-zeros.
       	 embedding_matrix[i] = embedding_vector
    #生成embedding层
    embedding_layer = Embedding(len(word_index) + 1,
                            EMBEDDING_DIM,
                            weights=[embedding_matrix],
                            input_length=max_len,
                            trainable=False)
    

    代码前半部分是导入预训练好的词向量,并根据字典构建权重矩阵

    非静态方式

    embedding_layer = Embedding(len(word_index) + 1,
                            EMBEDDING_DIM,
                            input_length=max_len)
    

    其实区别就在于在训练过程中,是否需要更新embedding层的参数。如果不更新,就类似于迁移学习。更新,其实就是把embedding层作为网络的一部分。也就解释了,在其他人博客中看到的一种说法:embedding就是取出训练网络的第一层参数。【本质上还是要看word2vec思想】

2. 构建模型(CNN RNN)

2.1 理论部分(待更新)

这部分我现阶段只是简单的调用现有的模块,所以理论方面都是参考网上的资料,暂时只贴链接方便查阅。

  • CNN
    在经过embedding后,如果考虑使用卷积神经网络,需要对单词向量进行卷积与池化。

    这里需要注意的是:句子的embedding矩阵与像素矩阵不同,你对每个词不同纬度进行特征提取没有意义。所以一般采用的是一维卷积。

    一维卷积基本介绍:一维卷积

  • RNN
    看这个demo

2.2代码部分

使用tf2.0,因此是基于keras的框架写的。由于Sequential写法是从头到尾,不容易分阶段,所以使用Model写法。
参考形式

class MyModel(tf.keras.Model):

    def __init__(self):
        super().__init__()    
        # 此处添加初始化代码(包含 call 方法中会用到的层),例如
        # layer1 = tf.keras.layers.BuiltInLayer(...)
        # layer2 = MyCustomLayer(...)
        
	@tf.function
    def call(self, input):
        # 此处添加模型调用的代码(处理输入并返回输出),例如
        # x = layer1(input)
        # output = layer2(x)
        return output
1、model.layers,添加层信息;
2、model.compile,模型训练的BP模式设置;
3、model.fit,模型训练参数设置 + 训练;
4、evaluate,模型评估;
5、predict 模型预测

关于@tf.function:

用Tensorflow 2.0默认的Eager execution实现则很不一样,用户不再需要直接定义计算图或者通过tf.Session来执行代码,也不需要调用tf.global_variables_initializer去初始化变量或者通过tf.control_dependencies去执行计算图中没有包含的节点.

Tensorflow变得像普通的Python代码一样简单.这样的代码非常易读,但是也带来了执行效率低的问题,因为代码需要依赖Python的解释器来进行计算,无法对数据流以及计算图进行优化.
为了在代码可读性和代码速度之间保持平衡,Tensorflow 2.0引入了tf.function这一概念.Tensorflow 2.0中一个主要的改变就是移除tf.Session这一概念.这样可以帮助用户更好的组织代码,不用将tf.Session作为一个变量在Python函数中传来传去,我们可以用一个Python装饰符来进行加速,那就是@tf.function.

需要注意的是不是所有的函数都可以通过tf.function进行加速的.有的任务并不值得将函数转化为计算图形式,比如简单的矩阵乘法.然而,对于大量的计算,如对深度神经网络的优化,这一图转换能给性能带来巨大的提升.我们也把这样的图转化叫作tf.AutoGraph.在Tensorflow 2.0中,我们会自动的对被@tf.function装饰的函数进行AutoGraph优化.

参考keras介绍简单粗暴的tensorflow2@tf.function 介绍

具体实例

class cnn_rnn(Model):
    def __init__():
        super(self,lstm).__init__()
        self.embed =  embedding_layer
        self.conv1 =  Conv1D(filters = 16,kernel_size =  2, padding = 'valid',activation='relu')
        self.pool1 =  MaxPooling1D(pool_size = 2)
        self.lstm = LSTM(10) # LSTM(10,dropout=0.2, recurrent_dropout=0.2) lstm中也自带dropout
        self.dense1  = Dense(8, activation='relu')
        self.dense2 = Dense(5, activation='softmax')
        self.drop = Dropout(0.2)
        
    @tf.function    
    def call(self,input): #可以根据需要自行添加,也可以cnn+rnn
        emb = self.embed(input)
        drop1 = self.drop(emb)
        lstm = self.lstm(drop1)
        dense1 = self.dense1(lstm)
        drop2 = self.drop(dense1)
        pred = self.drop(drop2)
        return pred

model1 = cnn_rnn()
model1.compile(loss='categorical_crossentropy',
              optimizer='rmsprop',
              metrics=['acc'])
model1.fit(x_train, y_train, validation_data=(x_val, y_val),
         epochs=2, batch_size=3)
  • 由于机器问题,这段时间跑不动,等回去在更新跑的结果。
  • 此外,如果基于类方法写的,无法直接model.summary(),需要在fit过后才可以。
  • 还有个坑,自行训练word2vec得到词向量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值