在上一篇文章中,我们探索了卷积神经网络(CNN)如何像"火眼金睛"一样高效地处理图像数据。然而,现实世界中的数据并非都是静态的图片,还有大量按顺序排列的数据,例如一段文字、一首乐曲、或者一段时间内的股票价格。这类数据被称为序列数据。
1. RNN是什么
对于序列数据,CNN那种"一视同仁"的局部感知能力就不太够用了,因为序列中的每个元素都可能与它之前的所有元素相关。这时,我们就需要一种新的网络结构------循环神经网络(Recurrent Neural Network, RNN)。
RNN就像一个拥有"记忆"的阅读者。当它处理序列中的一个元素时(比如一个单词),它不仅会关注这个单词本身,还会参考它对前面所有单词的"记忆"。这种循环往复、承前启后的机制,使得RNN在自然语言处理(NLP)、时间序列预测等领域取得了巨大成功。
本篇文章将带您走进RNN的世界,重点介绍在MindSpore中构建RNN模型所需的三个核心"零件"。
2. RNN的核心"零件"
处理序列数据,尤其是文本数据时,我们通常需要以下三个关键组件的配合。
2.1 词嵌入层 (nn.Embedding):文字到向量的桥梁
计算机无法直接理解"你好"、"世界"这样的文字。为了让神经网络能够处理文本,我们首先需要将每个字或词转换成一个固定长度的数字向量。这个过程就叫做词嵌入(Word Embedding)。
-
工作原理 :
nn.Embedding层就像一本"密码本"(或字典)。你首先要确定你的词汇表有多大(vocab_size),以及你想用多长的向量来表示一个词(embedding_size)。nn.Embedding层内部会维护一个vocab_size * embedding_size大小的查询表。当你输入一个词的索引(一个整数)时,它就会从表中查出对应的向量作为输出。 -
关键参数:
vocab_size(int): 词汇表的大小。例如,如果你的词典里有1000个不同的词,这个值就是1000。embedding_size(int): 用来表示一个词的向量的维度。
-
代码示例:
python
import mindspore
from mindspore import nn, Tensor
import numpy as np
# 假设我们的词汇表大小为1000,每个词用100维的向量表示
vocab_size = 1000
embedding_size = 100
# 定义一个Embedding层
embedding_layer = nn.Embedding(vocab_size, embedding_size)
# 假设我们有一句话,它包含4个词,其在词典中的索引分别是 1, 5, 99, 0
# 输入的shape通常是 (batch_size, sequence_length)
input_indices = Tensor([[1, 5, 99, 0]], mindspore.int32)
# 将索引输入Embedding层
output_vectors = embedding_layer(input_indices)
print("输入索引的尺寸:", input_indices.shape)
print("输出词向量的尺寸:", output_vectors.shape) # (1, 4, 100)
2.2 循环神经网络层 (nn.RNN):最初的"记忆"单元
nn.RNN是MindSpore中基础的循环神经网络层。
-
工作原理 :RNN的核心在于其内部的"循环"。对于一个长度为
L的序列,RNN层会按时间步从t=0到t=L-1依次处理每个元素的向量。在每个时间步t,它会接收两个输入:- 当前时间步的输入向量
x_t。 - 上一个时间步的隐藏状态
h_{t-1}(可以理解为"短期记忆")。
它将这两个输入融合,计算出当前时间步的输出
output_t和新的隐藏状态h_t,然后将h_t传递给下一个时间步。这个h_t就构成了RNN的"记忆"。 - 当前时间步的输入向量
-
关键参数:
input_size(int): 输入向量的维度(通常等于embedding_size)。hidden_size(int): 隐藏状态向量的维度。batch_first(bool): 如果为True,则输入和输出Tensor的维度顺序为(batch, seq_len, input_size),这通常更直观。
-
代码示例:
python
# 承接上例,词向量维度为100,我们设定RNN的隐藏层维度为32
input_size = 100
hidden_size = 32
seq_len = 4 # 序列长度
batch_size = 1 # 批量大小
# 定义一个RNN层
# batch_first=True 让输入的第一维是batch_size
rnn_layer = nn.RNN(input_size, hidden_size, batch_first=True)
# 将Embedding层的输出作为RNN的输入
# output_vectors 的 shape 是 (1, 4, 100)
output, hidden_state = rnn_layer(output_vectors)
print("RNN输出(所有时间步)的尺寸:", output.shape) # (1, 4, 32)
print("RNN最后一个时间步的隐藏状态尺寸:", hidden_state.shape) # (1, 1, 32)
局限性 :基础的RNN存在一个著名的问题------梯度消失/爆炸。当序列很长时,它很难学习到序列开头的"长期依赖"信息,就像一个人记性不好,读到后面就忘了前面讲了什么。
2.3 长短期记忆网络层 (nn.LSTM):更强大的"记忆"单元
为了解决RNN的"记性差"的问题,研究者们设计了长短期记忆网络(Long Short-Term Memory, LSTM)。LSTM是RNN的一种变体,也是目前应用最广泛的循环神经网络之一。
-
工作原理 :LSTM的核心在于其内部精巧的"门控机制"。相比于RNN简单的隐藏状态,LSTM引入了一个细胞状态(Cell State),可以看作是"长期记忆"。它还设计了三个"门"来控制信息的流动:
- 遗忘门(Forget Gate):决定从长期记忆中丢弃哪些信息。
- 输入门(Input Gate):决定将哪些新的信息存入长期记忆。
- 输出门(Output Gate):决定从长期记忆中输出哪些信息作为当前时间步的隐藏状态。
通过这套机制,LSTM能够有选择地记忆和遗忘,从而有效地捕捉长距离依赖关系。
-
关键参数 :与
nn.RNN基本相同。 -
代码示例:
python
# 定义一个LSTM层,参数与RNN类似
lstm_layer = nn.LSTM(input_size, hidden_size, batch_first=True)
# 将同样的词向量输入LSTM层
# LSTM的输出包含 output, (h_n, c_n)
output, (h_n, c_n) = lstm_layer(output_vectors)
print("LSTM输出(所有时间步)的尺寸:", output.shape) # (1, 4, 32)
print("LSTM最后一个时间步的隐藏状态尺寸:", h_n.shape) # (1, 1, 32)
print("LSTM最后一个时间步的细胞状态尺寸:", c_n.shape) # (1, 1, 32)
3. 组装一个简单的序列处理模型
现在,我们将这三个"零件"组装起来,构建一个可用于文本分类等任务的基础模型结构。
python
import mindspore
from mindspore import nn
class SimpleSentimentClassifier(nn.Cell):
def __init__(self, vocab_size, embedding_size, hidden_size, num_classes):
super(SimpleSentimentClassifier, self).__init__()
# 1. Embedding层
self.embedding = nn.Embedding(vocab_size, embedding_size)
# 2. LSTM层
self.lstm = nn.LSTM(embedding_size, hidden_size, batch_first=True)
# 3. 全连接层 (分类器)
self.fc = nn.Dense(hidden_size, num_classes)
def construct(self, x):
# x 的 shape: (batch_size, seq_len)
# 1. 经过Embedding层
# embedded 的 shape: (batch_size, seq_len, embedding_size)
embedded = self.embedding(x)
# 2. 经过LSTM层
# lstm_out shape: (batch_size, seq_len, hidden_size)
# h_n shape: (1, batch_size, hidden_size)
lstm_out, (h_n, c_n) = self.lstm(embedded)
# 3. 我们通常取最后一个时间步的输出来代表整句话的语义
# last_output shape: (batch_size, hidden_size)
last_output = lstm_out[:, -1, :]
# 4. 经过全连接层得到分类结果
# logits shape: (batch_size, num_classes)
logits = self.fc(last_output)
return logits
# 实例化网络
model = SimpleSentimentClassifier(vocab_size=1000,
embedding_size=100,
hidden_size=32,
num_classes=2) # 假设是二分类(如情感积极/消极)
print(model)
4. 总结
在本篇文章中,我们学习了处理序列数据的利器------循环神经网络,并掌握了其在MindSpore中的三个核心组件:
nn.Embedding:将离散的词符转换为密集的向量,是处理文本数据的第一步。nn.RNN:基础的循环网络层,通过传递隐藏状态来维持"短期记忆"。nn.LSTM:RNN的强大变体,通过精巧的门控机制实现了"长短期记忆",能更好地捕捉序列中的长距离依赖。
通过组合这三个组件,我们构建了一个简单的序列处理模型,为后续进行文本分类、机器翻译等NLP实战任务奠定了基础。
在下一篇文章中,我们将学习训练过程中的得力助手------回调函数(Callbacks),敬请期待!