目錄
讀取長序列數據
為什么需要 “讀取長序列數據”?
讀取長序列數據的核心方法
1. 滑動窗口(Sliding Window)
2. 分段截取(Segmentation)
3. 滾動生成(Rolling Generation)
4. 關鍵信息采樣
讀取長序列數據的核心挑戰
隨機抽樣(Random Sampling)
1.核心原理
2.具體操作
3.優缺點
4.適用場景
順序分區(Sequential Partitioning)
1.核心原理
2.具體操作
3.優缺點
4.適用場景
4種核心方法的區別
完整代碼
實驗結果
第一次運行結果
第二次運行結果
讀取長序列數據
讀取長序列數據指的是對超出模型單次處理能力(或固定長度限制)的超長序列進行讀取、拆分和預處理的過程。其核心目標是將長序列轉換為模型可接受的格式,同時盡可能保留序列中的時序關系和上下文信息。
為什么需要 “讀取長序列數據”?
模型輸入長度限制: 大多數序列模型(如 RNN、Transformer)對輸入長度有固定限制(例如,早期 Transformer 的輸入長度上限為 512 個詞元)。若原始序列(如一篇萬字文章、1 小時的語音)遠超此限制,無法直接輸入模型。
計算資源約束: 即使模型支持變長輸入,超長序列會導致計算量和內存占用呈指數級增長(例如,自注意力機制的時間復雜度為?\(O(n^2)\),n?為序列長度),實際訓練或推理時難以承受。
保留時序關系: 長序列的核心價值在于其內部的時序依賴(如文本中的上下文關聯、時間序列中的長期趨勢)。讀取時需避免破壞關鍵依賴,否則會導致模型性能下降。
讀取長序列數據的核心方法
處理長序列的核心思路是 “拆分”,但需根據任務需求選擇合適的拆分策略,常見方法包括:
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. 關鍵信息采樣
- 原理:對超長序列,只抽取關鍵部分(如摘要、峰值點),忽略冗余信息。
- 示例:在長文本中提取關鍵詞或句子組成短序列;在高頻時間序列中保留峰值和谷值點。
- 適用場景:對全局趨勢而非細節敏感的任務(如長文檔摘要、異常檢測)。
- 優點:大幅降低序列長度,保留核心信息;
- 缺點:可能丟失重要細節,依賴有效的采樣策略。
讀取長序列數據的核心挑戰
- 平衡長度與信息保留:拆分過短會丟失上下文,過長則增加計算負擔。
- 處理時序斷裂:拆分點可能位于關鍵依賴處(如句子中間、事件轉折點),導致語義割裂。
- 動態適配模型:不同模型(如 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種核心方法的區別
方法 核心邏輯 關鍵特點 典型場景 隨機抽樣 隨機截取子序列,打破順序 隨機性高,丟失長期依賴 文本分類、短期預測 順序分區 連續截取子序列,保留順序 時序完整,樣本相關性高 語言生成、長期預測 滑動窗口 重疊截取,保留局部上下文 平衡信息與效率 語音識別、段落理解 滾動生成 迭代處理,延續隱藏狀態 支持無限序列,誤差累積 實時數據流、超長文本處理
完整代碼
"""
文件名: 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_sizeXs, Ys = Xs.reshape(batch_size, -1), Ys.reshape(batch_size, -1)# 步驟4:按順序生成批次(保留序列順序)num_batches = Xs.shape[1] // num_steps # 總批次數 = 每行長度 // num_stepsfor i in range(0, num_steps * num_batches, num_steps):# 從每行中截取第i到i+num_steps列,作為當前批次的輸入XX = 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,每個子序列長度=5batch_size = 2num_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