前面我们学习了多层感知机和卷积神经网络。

多层感知机适合处理表格特征,卷积神经网络适合处理图片。所谓“表格特征”,就是每个样本能用一行固定长度的字段来描述,像 Excel 里的一行数据。

比如房价预测里的面积、楼层、朝向、房龄、地段评分;或风控里的年龄、收入、负债比例、信用评分。这些字段没有先后顺序,拼成一个固定长度的向量就可以直接输入 MLP。
它们有一个共同特点:通常把每个样本当成相对独立的对象来看。

例如:

  • 一张猫图和一张狗图,顺序调换后不影响分类;
  • 一条房价数据和另一条房价数据,通常可以分别预测;
  • 一张手写数字图片,不需要知道上一张图片是什么。

但现实里还有很多数据不能打乱顺序。顺序一变,意思就变了。

  • 股票价格:今天的走势会影响明天的判断。
  • 语音信号:前一个音节会影响后一个音节的理解。
  • 文本句子:“我喜欢你”和“你喜欢我”字一样,顺序不同,意思不同。
  • 天气记录:连续几天的温度变化,比某一天单独的温度更有信息。

这些数据有一个共同名字:序列数据(Sequence Data)

这一篇,我们先不急着讲 RNN,而是先搞懂两件事:

  1. 序列数据到底特殊在哪里?
  2. 文本这种人类语言,怎样变成神经网络能处理的数字?

一、什么是序列数据?

序列数据就是一串按顺序排列的数据。

这个顺序通常代表时间、位置或逻辑关系。我们常用下标表示它:

x1, x2, x3, ..., xt

其中 xt 表示第 t 个时间步的数据。

举几个例子:

场景 一个时间步可能是什么
股票预测 某一天的价格
天气预测 某一天的温度、湿度
语音识别 某一小段声音信号
文本生成 一个字、一个词或一个 token

序列数据最重要的特点是:

当前数据往往和过去的数据有关。

比如你看到一句话:

我今天晚上想吃

你会自然猜后面可能是“火锅”“面条”“烧烤”,而不太可能是“显卡”。原因不是“吃”这个字单独决定的,而是前面整句话提供了上下文。


二、序列预测:用过去猜未来

序列模型常见任务是:根据前面的内容预测后面的内容。

比如:

已知:x1, x2, x3, ..., xt
预测:x(t+1)

这类模型叫 自回归模型(Autoregressive Model)。名字听起来很学术,其实意思很简单:

用自己过去的历史,预测自己的下一步。

股票预测、天气预测、文本续写都属于这个思路。

1. 最直接的方法:看固定长度的历史

假设我们要预测明天的温度,可以只看过去 3 天:

输入:前3天温度
输出:明天温度

如果写成机器学习任务,它就像普通表格数据:

前3天 前2天 前1天 明天
20 21 23 24
21 23 24 22

这时用多层感知机也能做,因为我们把过去几天当作几个普通特征输入了。

2. 问题:历史到底看多长?

只看过去 3 天可能不够。文本尤其明显。

比如:

虽然这本书前面铺垫了很多复杂人物关系,但最后真正改变主角命运的人是

想预测下一个词,可能要理解很远之前的人名和情节。固定只看前 3 个词、前 5 个词就不够用了。

于是我们需要一种模型:

它能按顺序读取数据,并且把读过的信息压缩成某种“记忆”。

这就是 RNN 的动机。不过在进入 RNN 之前,我们先处理一个更基础的问题:文字怎么喂给神经网络?


三、机器不认识文字,只认识数字

神经网络本质上做的是矩阵运算。它不能直接理解:

深度学习很好玩

我们必须把文本变成数字。这个过程叫 文本预处理

一条基本流水线通常包括:

读取文本 -> 词元化 -> 建立词表 -> 转成数字序列 -> 切成小批量

下面一步一步看。


四、词元化:把句子切成小块

词元(Token) 是文本被切分后的基本单位。

词元化就是把一段文本拆成一个个 token。

常见切法有两种。

1. 按词切

英文可以按单词切:

deep learning is fun

切成:

["deep", "learning", "is", "fun"]

优点是语义比较完整;缺点是词表会很大,而且没见过的新词不好处理。

2. 按字符切

中文入门教学里,经常按字切:

深度学习很好玩

切成:

["深", "度", "学", "习", "很", "好", "玩"]

优点是简单、词表小;缺点是单个字的信息有限,需要模型自己从上下文里组合含义。

小白学习 RNN 时,推荐先从字符级文本开始,因为它更容易实现。


五、建立词表:给每个 token 一个编号

切成 token 后,机器还是不认识。我们要给每个 token 分配一个整数编号,这个表叫 词表(Vocabulary)

比如:

"<unk>" -> 0
"深"    -> 1
"度"    -> 2
"学"    -> 3
"习"    -> 4

<unk> 表示未知 token。遇到词表里没有的内容,就统一映射到它。

下面是一份简化版词表代码:

class Vocab:
    def __init__(self, tokens):
        # 初始化“编号 -> token”的列表,先放一个未知词占位
        self.idx_to_token = ['<unk>']
        # 初始化“token -> 编号”的字典,未知词的编号固定为 0
        self.token_to_idx = {'<unk>': 0}

        # 去重并排序,保证编号分配稳定可复现
        for token in sorted(set(tokens)):
            # 把 token 追加到列表末尾
            self.idx_to_token.append(token)
            # token 的编号就是它在列表中的位置
            self.token_to_idx[token] = len(self.idx_to_token) - 1

    def __getitem__(self, tokens):
        # 如果输入的是单个 token,直接返回编号
        if not isinstance(tokens, (list, tuple)):
            # get 的默认值 0 表示词表里没有时返回 <unk>
            return self.token_to_idx.get(tokens, 0)
        # 如果输入是一组 token,就递归地转换成编号列表
        return [self.__getitem__(token) for token in tokens]

    def to_tokens(self, indices):
        # 如果输入的是单个编号,直接返回对应 token
        if not isinstance(indices, (list, tuple)):
            return self.idx_to_token[indices]
        # 如果输入是一组编号,就逐个转回 token 列表
        return [self.idx_to_token[index] for index in indices]

有了词表,文本就可以转换成数字序列:

["深", "度", "学", "习"] -> [1, 2, 3, 4]

注意:这些数字只是编号,不代表大小关系。编号 4 的“习”并不比编号 1 的“深”更大、更重要。

这个细节很重要,下一篇和第三篇会继续用到。


六、把长序列切成训练样本

一本书可能有几十万字,我们不能一次性全部塞进模型。训练时通常会把长文本切成很多小片段。

比如原始序列是:

[1, 2, 3, 4, 5, 6, 7, 8]

如果每次看 4 个 token,可以构造:

输入: [1, 2, 3, 4]
标签: [2, 3, 4, 5]

这是什么意思?

模型读到 1 时,希望预测 2;读到 2 时,希望预测 3;读到 3 时,希望预测 4

也就是:

每个位置都在练习“根据当前和过去,预测下一个 token”。

生活化例子:

想象你在听一句话:“我今天晚上想吃”。

  • 你听到“我”时,会猜下一词可能是“今天”。
  • 你听到“我 今天”时,会猜下一词可能是“晚上”。
  • 你听到“我 今天 晚上”时,会猜下一词可能是“想吃”。

这就是“输入序列”和“标签序列”错开一位的意义:每个位置都在训练“下一步该接什么”。

1. 随机采样

随机采样就是从长文本中随机抽取片段。

优点:打乱程度高,训练样本更随机。
缺点:相邻批次之间可能没有连续关系,隐状态不方便延续。
这里的“隐状态”可以理解为 RNN 的“短期记忆本”,它保存了上一段序列的上下文信息。如果下一批数据是随机切出来的、和上一批不相邻,那么继续沿用上一批的隐状态就会把“错误的上下文”带进来,反而干扰学习,所以就不方便延续。

2. 顺序分区

顺序分区就是按原文顺序切片。

优点:更符合文本原本顺序,适合让 RNN 的状态连续传递。
缺点:实现时要更注意批量之间的状态管理。

入门阶段,你只要知道:

随机采样更像打散练习题,顺序分区更像按原文一路读下去。


七、小结

这一篇我们为 RNN 打了地基,你需要记住:

  1. 序列数据的关键是“顺序有意义”,不能随便打乱。
  2. 自回归模型就是用过去预测未来。
  3. 文本必须先经过词元化和词表映射,才能变成神经网络能处理的数字。
  4. token 的整数编号只是身份标记,不代表数值大小。
  5. 训练语言模型时,常把长文本切成“输入序列”和“向后错一位的标签序列”。

如果用一句话收尾:

RNN 要解决的问题,是让模型一边读序列,一边记住前面发生过什么。

下一篇,我们正式认识 RNN 的核心:隐状态。它就像模型随身携带的一本“短期记忆本”,让机器不再只看当前 token,而能结合前文做判断。

(注:文档部分内容参考《动手学深度学习》)
《动手学深度学习》循环神经网络:https://zh.d2l.ai/chapter_recurrent-neural-networks/index.html

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐