一文读懂循环神经网络(RNN)—语言模型+读取长序列数据(2)

目录

读取长序列数据

[为什么需要 "读取长序列数据"?](#为什么需要 “读取长序列数据”?)

读取长序列数据的核心方法

[1. 滑动窗口(Sliding Window)](#1. 滑动窗口(Sliding Window))

[2. 分段截取(Segmentation)](#2. 分段截取(Segmentation))

[3. 滚动生成(Rolling Generation)](#3. 滚动生成(Rolling Generation))

[4. 关键信息采样](#4. 关键信息采样)

读取长序列数据的核心挑战

[随机抽样(Random Sampling)](#随机抽样(Random Sampling))

1.核心原理

2.具体操作

3.优缺点

4.适用场景

[顺序分区(Sequential Partitioning)](#顺序分区(Sequential Partitioning))

1.核心原理

2.具体操作

3.优缺点

4.适用场景

4种核心方法的区别

完整代码

实验结果

第一次运行结果

第二次运行结果


读取长序列数据

读取长序列数据 指的是对超出模型单次处理能力(或固定长度限制)的超长序列进行读取、拆分和预处理的过程。其核心目标是将长序列转换为模型可接受的格式,同时尽可能保留序列中的时序关系和上下文信息

为什么需要 "读取长序列数据"?

  1. 模型输入长度限制: 大多数序列模型(如 RNN、Transformer)对输入长度有固定限制(例如,早期 Transformer 的输入长度上限为 512 个词元)。若原始序列(如一篇万字文章、1 小时的语音)远超此限制,无法直接输入模型。

  2. 计算资源约束: 即使模型支持变长输入,超长序列会导致计算量和内存占用呈指数级增长(例如,自注意力机制的时间复杂度为 \(O(n^2)\),n 为序列长度),实际训练或推理时难以承受。

  3. 保留时序关系: 长序列的核心价值在于其内部的时序依赖(如文本中的上下文关联、时间序列中的长期趋势)。读取时需避免破坏关键依赖,否则会导致模型性能下降。

读取长序列数据的核心方法

处理长序列的核心思路是 "拆分",但需根据任务需求选择合适的拆分策略,常见方法包括:

1. 滑动窗口(Sliding Window)
  • 原理:用固定长度的 "窗口" 在长序列上滑动,每次截取窗口内的子序列作为样本,窗口间可重叠(保留部分上下文)。
  • 示例:对长度为 1000 的文本,用长度为 100 的窗口,步长为 50 滑动,可得到 19 个样本(窗口位置:[0-99], [50-149], ..., [900-999])。
  • 适用场景:时间序列预测(如预测未来温度需保留近期趋势)、文本分类(需捕捉局部上下文)。
  • 优点:保留局部时序关系,样本数量多;
  • 缺点:窗口外的远距离依赖可能被割裂。
2. 分段截取(Segmentation)
  • 原理:将长序列按固定长度直接分割为不重叠的子序列(类似 "分块")。
  • 示例:将 1000 个词的文本按 200 个词一段,分为 5 段(无重叠)。
  • 适用场景:对局部信息依赖较强的任务(如语音识别中的短句分割、长文档的段落级分类)。
  • 优点:简单高效,无冗余;
  • 缺点:可能切断段落中间的关键依赖(如句子被拆分为两段)。
3. 滚动生成(Rolling Generation)
  • 原理:对超长序列,每次用前序子序列的输出作为 "记忆",辅助处理下一段子序列(类似人类 "分段阅读并记忆上下文")。
  • 示例:用 RNN 处理 10000 词文本时,先处理前 1000 词并保存隐藏状态,再用该隐藏状态初始化模型,处理接下来的 1000 词,以此类推。
  • 适用场景:长文本生成(如小说续写)、实时数据流处理(如股票实时行情)。
  • 优点:可处理无限长序列,保留长期记忆;
  • 缺点:误差可能累积(前序处理的偏差会影响后续结果)。
4. 关键信息采样
  • 原理:对超长序列,只抽取关键部分(如摘要、峰值点),忽略冗余信息。
  • 示例:在长文本中提取关键词或句子组成短序列;在高频时间序列中保留峰值和谷值点。
  • 适用场景:对全局趋势而非细节敏感的任务(如长文档摘要、异常检测)。
  • 优点:大幅降低序列长度,保留核心信息;
  • 缺点:可能丢失重要细节,依赖有效的采样策略。

读取长序列数据的核心挑战

  1. 平衡长度与信息保留:拆分过短会丢失上下文,过长则增加计算负担。
  2. 处理时序断裂:拆分点可能位于关键依赖处(如句子中间、事件转折点),导致语义割裂。
  3. 动态适配模型:不同模型(如 RNN 对长距离依赖敏感,Transformer 对局部依赖更高效)需匹配不同的拆分策略。

随机抽样(Random Sampling)

1.核心原理
  • 用固定长度的 "窗口" 在长序列上随机滑动,截取不重叠(或少量重叠)的子序列作为样本。
  • 子序列的起始位置随机打乱,打破原始序列的连续性,降低样本间的相关性。
2.具体操作
  • 设定窗口长度(num_steps)和批量大小(batch_size)。
  • 从序列中随机选择起始点,生成多个子序列,组成批量数据。
  • 标签为子序列向右偏移 1 位的结果(预测下一个元素)。

示例

对序列 [0,1,2,...,34],用窗口长度 5 随机抽样,可能得到子序列:
[3,4,5,6,7][18,19,20,21,22][10,11,12,13,14]

3.优缺点
  • 优点 :样本随机性高,训练时梯度波动小,适合并行计算。
  • 缺点:破坏长距离时序依赖(如子序列前后的关联被割裂)。
4.适用场景

对长期依赖要求不高的任务(如文本分类、短期时间序列预测)。

顺序分区(Sequential Partitioning)

1.核心原理
  • 将长序列按固定长度分割为连续的子序列,保留原始时序顺序,子序列间可连续拼接。
  • 按 "批次" 划分序列:先将序列均匀分为 batch_size 个连续片段,再从每个片段中按顺序截取子序列。
2.具体操作
  • 设定窗口长度(num_steps)和批量大小(batch_size)。
  • 将序列分为 batch_size 个并行的连续子序列(如序列分为 2 段:[0,1,...,17][18,19,...,34])。
  • 从每个子序列中按顺序截取窗口,组成批量(确保同批次样本在原始序列中位置对齐)。

示例

对序列 [0,1,2,...,34],分 2 个批次,窗口长度 5,可能得到:

  • 第 1 批:[0,1,2,3,4][18,19,20,21,22]
  • 第 2 批:[5,6,7,8,9][23,24,25,26,27]
3.优缺点
  • 优点 :保留时序连续性,适合捕捉长期依赖(子序列可拼接为完整原始序列)。
  • 缺点:样本相关性高,训练时梯度可能震荡(同批次样本来自相邻区域)。
4.适用场景

对时序依赖敏感的任务(如语言生成、长文本翻译、长期时间序列预测)。

4种核心方法的区别

方法 核心逻辑 关键特点 典型场景
随机抽样 随机截取子序列,打破顺序 随机性高,丢失长期依赖 文本分类、短期预测
顺序分区 连续截取子序列,保留顺序 时序完整,样本相关性高 语言生成、长期预测
滑动窗口 重叠截取,保留局部上下文 平衡信息与效率 语音识别、段落理解
滚动生成 迭代处理,延续隐藏状态 支持无限序列,误差累积 实时数据流、超长文本处理

完整代码

python 复制代码
"""
文件名: 8.4 读取长序列数据
作者: 墨尘
日期: 2025/7/14
项目名: dl_env
备注: 实现长序列数据的两种读取方式(随机抽样和顺序分区),将超长序列拆分为小批量子序列,
      适配序列模型的训练需求
"""
import random
import torch
import collections  # 备用:用于统计(本代码未直接使用)
import re  # 备用:用于文本处理(本代码未直接使用)
from d2l import torch as d2l  # 提供辅助功能(本代码未直接使用)
# 手动显示图像相关库(本代码未涉及绘图,仅保留配置)
import matplotlib.pyplot as plt
import matplotlib.text as text


# -------------------------- 1. 随机抽样读取长序列 --------------------------
def seq_data_iter_random(corpus, batch_size, num_steps):  #@save
    """
    使用随机抽样生成小批量子序列(打破原始序列顺序,适合并行训练)

    参数:
        corpus: 长序列数据(1D列表或数组,如[0,1,2,...,34])
        batch_size: 批量大小(每个批次包含的样本数)
        num_steps: 每个子序列的长度(模型单次处理的序列长度)

    生成器返回:
        X: 输入子序列(批量),形状为(batch_size, num_steps)
        Y: 标签子序列(批量),形状为(batch_size, num_steps),其中Y[i]是X[i]向右偏移1位的结果
    """
    # 步骤1:随机偏移起始位置,避免总是从序列开头抽样(增加随机性)
    # 偏移范围为[0, num_steps-1],确保初始偏移不超过子序列长度
    corpus = corpus[random.randint(0, num_steps - 1):]

    # 步骤2:计算可生成的子序列总数
    # 减1是因为Y需要比X右移1位(最后一个元素没有标签)
    num_subseqs = (len(corpus) - 1) // num_steps  # 整数除法,确保子序列完整

    # 步骤3:生成所有子序列的起始索引
    # 从0开始,每隔num_steps取一个索引(如num_steps=5时,索引为0,5,10,...)
    initial_indices = list(range(0, num_subseqs * num_steps, num_steps))

    # 步骤4:随机打乱起始索引(核心:打破原始序列的顺序,避免样本相关性过高)
    random.shuffle(initial_indices)

    # 辅助函数:根据起始索引pos,返回长度为num_steps的子序列
    def data(pos):
        return corpus[pos: pos + num_steps]

    # 步骤5:按批量生成样本
    num_batches = num_subseqs // batch_size  # 总批次数 = 子序列总数 // 批量大小
    for i in range(0, batch_size * num_batches, batch_size):
        # 当前批次的起始索引列表(从打乱的索引中取batch_size个)
        initial_indices_per_batch = initial_indices[i: i + batch_size]
        # 生成输入X:每个元素是长度为num_steps的子序列
        X = [data(j) for j in initial_indices_per_batch]
        # 生成标签Y:每个元素是X中对应子序列右移1位的结果(预测下一个元素)
        Y = [data(j + 1) for j in initial_indices_per_batch]
        # 返回当前批次的X和Y(转换为张量)
        yield torch.tensor(X), torch.tensor(Y)


# -------------------------- 2. 顺序分区读取长序列 --------------------------
def seq_data_iter_sequential(corpus, batch_size, num_steps):  #@save
    """
    使用顺序分区生成小批量子序列(保留原始序列顺序,适合捕捉长期依赖)

    参数:
        corpus: 长序列数据(1D列表或数组)
        batch_size: 批量大小
        num_steps: 每个子序列的长度

    生成器返回:
        X: 输入子序列(批量),形状为(batch_size, num_steps)
        Y: 标签子序列(批量),形状为(batch_size, num_steps),Y是X右移1位的结果
    """
    # 步骤1:随机偏移起始位置(与随机抽样类似,增加随机性)
    offset = random.randint(0, num_steps)

    # 步骤2:计算有效序列长度(确保能被batch_size整除,便于均匀分区)
    # 总有效长度 = ((原始长度 - 偏移 - 1) // batch_size) * batch_size
    # 减1是因为Y需要右移1位,确保X和Y长度相同
    num_tokens = ((len(corpus) - offset - 1) // batch_size) * batch_size

    # 步骤3:生成输入X和标签Y,并重塑为(batch_size, 总长度//batch_size)
    # X:从偏移开始,取num_tokens个元素
    Xs = torch.tensor(corpus[offset: offset + num_tokens])
    # Y:比X右移1位,同样取num_tokens个元素
    Ys = torch.tensor(corpus[offset + 1: offset + 1 + num_tokens])
    # 重塑为二维:每行是一个样本,列数为总长度//batch_size
    Xs, Ys = Xs.reshape(batch_size, -1), Ys.reshape(batch_size, -1)

    # 步骤4:按顺序生成批次(保留序列顺序)
    num_batches = Xs.shape[1] // num_steps  # 总批次数 = 每行长度 // num_steps
    for i in range(0, num_steps * num_batches, num_steps):
        # 从每行中截取第i到i+num_steps列,作为当前批次的输入X
        X = Xs[:, i: i + num_steps]
        # 对应的标签Y(同样截取,与X对齐)
        Y = Ys[:, i: i + num_steps]
        yield X, Y


# -------------------------- 3. 测试两种读取方法 --------------------------
if __name__ == '__main__':
    # 生成测试序列:0到34(长度35的序列)
    my_seq = list(range(35))
    print("测试序列:", my_seq)
    print("序列长度:", len(my_seq))

    # 超参数:批量大小=2,每个子序列长度=5
    batch_size = 2
    num_steps = 5

    # 测试1:随机抽样读取
    print("\n===== 随机抽样生成的批量 =====")
    for X, Y in seq_data_iter_random(my_seq, batch_size, num_steps):
        print("X(输入):")
        print(X)
        print("Y(标签,X右移1位):")
        print(Y)
        print("-" * 50)
        # 只打印3个批次(避免输出过长)
        break

    # 测试2:顺序分区读取
    print("\n===== 顺序分区生成的批量 =====")
    for X, Y in seq_data_iter_sequential(my_seq, batch_size, num_steps):
        print("X(输入):")
        print(X)
        print("Y(标签,X右移1位):")
        print(Y)
        print("-" * 50)
        # 只打印3个批次
        break

实验结果

第一次运行结果
第二次运行结果
相关推荐
Rvelamen24 分钟前
LLM-SECURITY-PROMPTS大模型提示词攻击测评基准
人工智能·python·安全
【本人】43 分钟前
Django基础(一)———创建与启动
后端·python·django
AI technophile1 小时前
OpenCV计算机视觉实战(15)——霍夫变换详解
人工智能·opencv·计算机视觉
JNU freshman2 小时前
计算机视觉 之 数字图像处理基础(一)
人工智能·计算机视觉
SHIPKING3932 小时前
【python】基于pygame实现动态粒子爱心
开发语言·python·pygame
鹧鸪云光伏2 小时前
鹧鸪云重构光伏发电量预测的精度标准
人工智能·无人机·光伏·光伏设计·光伏模拟
九章云极AladdinEdu2 小时前
摩尔线程MUSA架构深度调优指南:从CUDA到MUSA的显存访问模式重构原则
人工智能·pytorch·深度学习·机器学习·语言模型·tensorflow·gpu算力
IT信息技术学习圈2 小时前
AI交互中的礼貌用语:“谢谢“的效用与代价分析
人工智能·交互
kk_stoper3 小时前
如何通过API查询实时能源期货价格
java·开发语言·javascript·数据结构·python·能源
java1234_小锋3 小时前
【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - 架构搭建
python·自然语言处理·flask