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

Bonita ·
更新时间:2024-09-20
· 964 次阅读

说明 本文是方法记录,不是完整的项目过程(在我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得到词向量。
作者:Matt_sh



cnn 分类 rnn 文本分类

需要 登录 后方可回复, 如果你还没有账号请 注册新账号
相关文章