当前位置:   article > 正文

昇思25天学习打卡营第10天|NLP-RNN实现情感分类

昇思25天学习打卡营第10天|NLP-RNN实现情感分类

打卡

目录

打卡

任务说明

流程

数据准备与加载

加载预训练词向量(分词)

数据集预处理

模型构建

Embedding

RNN(循环神经网络) + LSTM

全连接层

损失函数与优化器

训练逻辑

评估指标和逻辑

模型训练与保存

模型加载与测试

自定义输入测试

代码


任务说明

使用MindSpore实现一个基于RNN网络的情感分类模型

流程

数据准备与加载

1、从 https://mindspore-website.obs.myhuaweicloud.com/notebook/datasets/aclImdb_v1.tar.gz 下载数据集。注意,可用tqdm库对下载百分比进行可视化、用IO的方式可安全地下载临时文件,而后保存至指定的路径并返回。如下,是下载的数据集展示。

2、将IMDB数据集加载至内存并构造为迭代对象后,使用 mindspore.dataset 提供的Generatordataset 接口加载数据集迭代对象,并进行下一步的数据处理,例子如下,其中 IMDBData 类是 IMDB 数据集加载器,imdb_train 是构建的一个 Generatordataset 对象。

  1. import mindspore.dataset as ds
  2. def load_imdb(imdb_path):
  3. imdb_train = ds.GeneratorDataset(
  4. IMDBData(imdb_path, "train"),
  5. column_names=["text", "label"],
  6. shuffle=True,
  7. num_samples=10000)
  8. imdb_test = ds.GeneratorDataset(
  9. IMDBData(imdb_path, "test"),
  10. column_names=["text", "label"],
  11. shuffle=False)
  12. return imdb_train, imdb_test
  13. imdb_train, imdb_test = load_imdb(imdb_path)

加载预训练词向量(分词)

Glove( Global Vectors for Word Representation ) 词向量作为Embedding,是一种无监督学习算法。从 'https://mindspore-website.obs.myhuaweicloud.com/notebook/datasets/glove.6B.zip' 下载数据集。如下图所示。


预训练词向量是对输入单词的数值化表示,通过nn.Embedding层,采用查表的方式,输入单词对应词表中的index,获得对应的表达向量。 

由于数据集中可能存在词表没有覆盖的单词,因此需要加入<unk>标记符;同时由于输入长度的不一致,在打包为一个batch时需要将短的文本进行填充,因此需要加入<pad>标记符。 完成后的词表长度为原词表长度+2。mindspore.dataset.text.Vocab 用于创建用于训练NLP模型的Vocab,Vocab是数据集中可能出现的所有Token的集合,保存了各Token与其ID之间的映射关系,其中的函数 from_list(word_listspecial_tokens=Nonespecial_first=True) 从给定Token列表创建Vocab, special_tokens 表示追加到Vocab中的Token列表;tokens_to_ids(tokens) 查找指定Token对应的ID。

示例代码如下,根据输出,对应的词表大小 400002 ,向量长度为100。

  1. import zipfile
  2. import numpy as np
  3. def load_glove(glove_path):
  4. glove_100d_path = os.path.join(cache_dir, 'glove.6B.100d.txt')
  5. if not os.path.exists(glove_100d_path):
  6. glove_zip = zipfile.ZipFile(glove_path)
  7. glove_zip.extractall(cache_dir)
  8. embeddings = []
  9. tokens = []
  10. with open(glove_100d_path, encoding='utf-8') as gf:
  11. for glove in gf:
  12. word, embedding = glove.split(maxsplit=1)
  13. tokens.append(word)
  14. embeddings.append(np.fromstring(embedding, dtype=np.float32, sep=' '))
  15. # 添加 <unk>, <pad> 两个特殊占位符对应的embedding
  16. embeddings.append(np.random.rand(100))
  17. embeddings.append(np.zeros((100,), np.float32))
  18. vocab = ds.text.Vocab.from_list(tokens, special_tokens=["<unk>", "<pad>"], special_first=False)
  19. embeddings = np.array(embeddings).astype(np.float32)
  20. return vocab, embeddings
  21. glove_path = download('glove.6B.zip', 'https://mindspore-website.obs.myhuaweicloud.com/notebook/datasets/glove.6B.zip')
  22. vocab, embeddings = load_glove(glove_path) #
  23. print(len(vocab.vocab())) # 400002
  24. print(np.shape(embeddings)) ## (400002, 100) 比原始文件多两行
  25. idx = vocab.tokens_to_ids('the')
  26. embedding = embeddings[idx]
  27. print(f"idx={idx}, embedding={embedding}")

代码运行结果例子。

数据集预处理

  • 通过Vocab将所有的 Token 处理为index id。
  • 将文本序列统一长度,不足的使用<pad>补齐,超出的进行截断。

  1. 首先针对token 到 index id 的查表操作,使用 mindspore.dataset.text.Lookup(vocab, unknown_token=None, data_type=mstype.int32) 接口,将前文构造的词表加载,并指定 unknown_token 
  2. 其次为文本序列统一长度操作,使用 dataset.transforms.PadEnd(pad_shape, pad_value=None) 接口,此接口定义最大长度和补齐值(pad_value),这里取最大长度为500,填充值对应词表中 <pad> 的 index id。
  3. 由于后续模型训练的需要,同时要将label数据转为float32格式。
  4. 接着,手动将IMDB数据集分割为训练和验证两部分,比例取0.7, 0.3。
  5. 最后,通过 batch(batch_size, drop_remainder=False, num_parallel_workers=None, **kwargs) 接口指定数据集的 batch 大小,,并设置是否丢弃无法被batch size整除的剩余数据。

代码例子

  1. import mindspore as ms
  2. # 根据词表,将分词标记(token)映射到其索引值(id)。
  3. lookup_op = ds.text.Lookup(
  4. vocab, # 词表对象,用于存储分词和索引的映射。
  5. unknown_token='<unk>' # 备用词汇,用于要查找的单词不在词汇表时进行替换。 如果单词不在词汇表中,则查找结果将替换为 unknown_token 的值。 如果单词不在词汇表中,且未指定 unknown_token ,将抛出运行时错误。默认值: None ,不指定该参数。
  6. )
  7. # 对输入Tensor进行填充,要求 pad_shape 与输入Tensor的维度保持一致。
  8. pad_op = ds.transforms.PadEnd(
  9. [500], ## 指定填充的shape。设置为较小的维数时该维度的元素将被截断。
  10. pad_value=vocab.tokens_to_ids('<pad>') ## 用于填充的值。默认 None ,表示不指定填充值。 当指定为默认值,输入Tensor为数值型时默认填充 0 ,输入Tensor为字符型时填充空字符串。
  11. )
  12. type_cast_op = ds.transforms.TypeCast(ms.float32)
  13. imdb_train = imdb_train.map(operations=[lookup_op, pad_op], input_columns=['text'])
  14. imdb_train = imdb_train.map(operations=[type_cast_op], input_columns=['label'])
  15. imdb_test = imdb_test.map(operations=[lookup_op, pad_op], input_columns=['text'])
  16. imdb_test = imdb_test.map(operations=[type_cast_op], input_columns=['label'])
  17. imdb_train, imdb_valid = imdb_train.split([0.7, 0.3])
  18. imdb_train = imdb_train.batch(64, drop_remainder=True)
  19. imdb_valid = imdb_valid.batch(64, drop_remainder=True)

模型构建

  • 结构:nn.Embedding -> nn.RNN -> nn.Dense
  • 其中,nn.Embedding层加载Glove词向量,RNN 层做特征提取,nn.Dense 层将特征转化为与分类数量相同的size,用于后续进行模型优化训练。
  • 这里使用能够一定程度规避RNN梯度消失问题的变种LSTM(Long short-term memory)做特征提取层。

Embedding

mindspore.nn.Embedding(vocab_size, embedding_size, use_one_hot=False, embedding_table='normal', dtype=mstype.float32, padding_idx=None)

用于存储词向量并使用索引进行检索,根据输入Tensor中的id,从 embedding_table 中查询对应的 embedding 向量。当输入为id组成的序列时,输出为对应embedding向量构成的矩阵。当 use_one_hot 等于True时,x的类型必须是mindpore.int32。

  • vocab_size (int) - 词典的大小。如上文,对应的词表大小 400002 。

  • embedding_size (int) - 每个嵌入向量的大小。如上文,向量长度为100。

  • use_one_hot (bool) - 指定是否使用one-hot形式。默认值: False 。

  • embedding_table (Union[Tensor, str, Initializer, numbers.Number]) - embedding_table的初始化方法。当指定为字符串,字符串取值请参见类 mindspore.common.initializer 。默认值: "normal" 。

  • dtype (mindspore.dtype) - x的数据类型。默认值: mstype.float32 。

  • padding_idx (int, None) - 将 padding_idx 对应索引所输出的嵌入向量用零填充。默认值: None 。该功能已停用。

RNN(循环神经网络) + LSTM

循环神经网络(Recurrent Neural Network, RNN)是一类以序列(sequence)数据为输入,在序列的演进方向进行递归(recursion)且所有节点(循环单元)按链式连接的神经网络。

RNN的结构拆解:

 RNN单个Cell的结构简单,因此也造成了梯度消失(Gradient Vanishing)问题,具体表现为RNN网络在序列较长时,在序列尾部已经基本丢失了序列首部的信息。为了克服这一问题,LSTM(Long short-term memory)被提出,通过门控机制(Gating Mechanism)来控制信息流在每个循环步中的留存和丢弃。选择LSTM变种而不是经典的RNN做特征提取,来规避梯度消失问题,可以获得更好的模型效果。

mindspore.nn.LSTM(*args, **kwargs) 

长短期记忆(LSTM)网络,根据输入序列和给定的初始状态计算输出序列和最终状态。在LSTM模型中,有两条管道连接两个连续的Cell,一条是Cell状态管道,另一条是隐藏状态管道。将两个连续的时间节点表示为 t−1 和 t。指定在 t 时刻输入 $x_t$ , t-1 时刻的隐藏状态 $h_{t-1}$  和Cell状态 $c_{t-1}$

t 时刻的Cell状态 $c_{t}$ 和隐藏状态 $h_{t}$ 使用门控机制计算得到。

输入门  $i_t$ 计算出候选值。遗忘门  $f_t$决定是否让  $h_{t-1}$ 学到的信息通过或部分通过。

输出门  $o_t$ 决定哪些信息输出。

候选Cell状态 $ \tilde{c_{t}} $ 是用当前输入计算的。

最后,使用遗忘门、输入门、输出门计算得到当前时刻的Cell状态 $c_t$和隐藏状态 $h_t$

如下公式,

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/喵喵爱编程/article/detail/851812
推荐阅读
相关标签