1.數據集

1. 來源與簡介

  • 名稱:THUCNews
  • 發佈機構:清華大學自然語言處理與社會人文計算實驗室(THUNLP)
  • 規模:約 740 萬篇中文新聞文本(完整版),本次使用子集共計 65 000 條樣本
  • 任務類型:多分類文本分類,針對長文本新聞內容進行主題判別

2. 本次使用的子集

文件名

樣本數

説明

cnews.train.txt

50 000

訓練集

cnews.val.txt

5 000

驗證集

cnews.test.txt

10 000

測試集

每行格式通常為:

<類別標籤>\t<新聞正文文本>

3. 類別分佈

本次精選了其中 10 個主題類別:

  1. 體育
  2. 財經
  3. 房產
  4. 家居
  5. 教育
  6. 科技
  7. 時尚
  8. 時政
  9. 遊戲
  10. 娛樂

各類別樣本數大致均衡,均在 5 000~7 000 條左右,可有效避免類別極度傾斜。

4. 文本特點

  • 平均長度:每篇新聞正文常在 500~2 000 字之間,屬於中長文本範疇。
  • 內容風格:覆蓋新聞報道、評論、特寫、資訊等多種寫作風格。
  • 語言特點:專業術語、專有名詞較多,需做好詞表擴充或使用預訓練模型詞表。

2.DPCNN算法

文本分類之TextCNN與DPCNN_#分類

1. 算法定義與背景

Deep Pyramid Convolutional Neural Network(DPCNN)是一種針對長文本分類任務設計的深度卷積神經網絡,首次發表於 2016 年。它在傳統卷積神經網絡(CNN)基礎上引入金字塔式下采樣和殘差連接,旨在以更少的參數和計算開銷,高效捕獲長文本的全局與局部特徵。

2. 核心原理

2.1 區域嵌入(Region Embedding)

代碼位置:模型初始化中 self.region_conv = nn.Conv1d(...)

# 在 DPCNN.__init__ 中
self.region_conv = nn.Conv1d(
    in_channels=self.embedding_dim,
    out_channels=self.num_filters,
    kernel_size=3,
    padding=1
)
  • 作用:將詞向量在局部窗口內(3-gram)進行卷積運算,提取低層次 n-gram 特徵。
  • 輸入/輸出維度
  • 輸入:(batch_size, embedding_dim, seq_len)
  • 輸出:(batch_size, num_filters, seq_len)

2.2 卷積塊與殘差連接(ConvBlock + Residual)

代碼位置

for i in range(self.repeat_blocks):
    block = nn.Sequential(
        nn.ReLU(),
        nn.Conv1d(self.num_filters, self.num_filters, kernel_size=3, padding=1),
        nn.ReLU(),
        nn.Conv1d(self.num_filters, self.num_filters, kernel_size=3, padding=1),
    )
    self.conv_blocks.append(block)
  • 每個 ConvBlock 包含兩層帶 ReLU 的一維卷積。
  • 殘差連接
  • 第一個塊不下采樣: out = block(x); x = x + out
  • 後續塊先池化再卷積:
x = self.pool(x)
out = block(x)
x = x + out
  • 殘差結構保證深層網絡中梯度穩定傳遞,加速收斂。

2.3 金字塔下采樣(Pyramid Pooling)

代碼位置self.pool = nn.MaxPool1d(kernel_size=3, stride=2, padding=1)

  • 每隔一個 ConvBlock,通過池化將序列長度減半。金字塔式下采樣使感受野逐層擴大,兼具全局信息。
  • 下采樣後的序列長度 L' = ceil((L + 2*pad - kernel) / stride + 1) ≈ L/2

3. 模型結構流程(結合 forward 代碼)

def forward(self, x):
    # 1) Embedding: [B, L] -> [B, L, E]
    x = self.embedding(x)

    # 2) Dropout + 轉置: [B, L, E] -> [B, E, L]
    x = self.embed_dropout(x).transpose(1, 2)

    # 3) 區域卷積: [B, E, L] -> [B, F, L]
    x = self.region_conv(x)

    # 4) 首個殘差塊
    x = self.embed_dropout(x)
    for idx, block in enumerate(self.conv_blocks):
        if idx == 0:
            out = block(x)
            x = x + out
        else:
            # 5) 下采樣 + 殘差
            x = self.pool(x)
            out = block(x)
            x = x + out

    # 6) 全連接輸出
    logits = self.fc(x)
    return logits
  1. Embedding → Dropout:對輸入詞 ID 序列進行向量化並隨機失活。
  2. 區域卷積:提取初級局部特徵。
  3. 卷積+殘差
  • 第一個 ConvBlock:保持序列長度。
  • 後續塊:池化後卷積,序列長度逐步縮減。
  1. Flatten → FC:將最終特徵圖展平,輸出 num_classes 類別得分。

4. 代碼流程詳解

4.1 數據預處理與詞表

# 清洗與分詞
def clear_text(text):
    p = re.compile(r"[^一-龥0-9a-zA-Z\-…]")
    return p.sub('', text)

def tokenize(text):
    text = clear_text(text)
    segs = jieba.lcut(text)
    return [w for w in segs if w not in STOPWORDS_SET]
  1. 正則只保留中英文、數字和常用標點。
  2. jieba.lcut 精準分詞,去掉停用詞。
# 詞表構建或加載
def load_or_build_vocab(texts, force_rebuild=False):
    if os.path.exists(counter_path) and not force_rebuild:
        vocab = pickle.load(open(counter_path, 'rb'))
    else:
        vocab = build_vocab_from_iterator(map(tokenize, texts),
                                          max_tokens=total_words,
                                          specials=['<unk>','<pad>'])
        with open(counter_path, 'wb') as f:
            pickle.dump(vocab, f)
    vocab.set_default_index(vocab['<unk>'])
    return vocab
  • 保存詞表映射 word->index,並添加 <unk><pad>

4.2 數據加載與迭代

def load_data(path, train=False, vocab=None):
    texts, labels = read_data(path)
    if train:
        vocab = load_or_build_vocab(texts, force_rebuild=True)
    else:
        if vocab is None:
            vocab = pickle.load(open(counter_path, 'rb'))

    dataset = TextDataset(texts, labels, vocab, doc_maxlen)
    loader = DataLoader(dataset, batch_size=batch_size,
                        shuffle=train, collate_fn=collate_fn)
    return loader, vocab
  • 訓練階段重建詞表;驗證階段僅加載或複用。
  • TextDataset 中將文本切分、映射為固定長度 ID 序列。

4.3 訓練與驗證

def train_step(model, batch, optimizer):
    model.train()
    x, y = batch
    optimizer.zero_grad()
    logits = model(x.to(device))
    loss = loss_func(logits, y.to(device))
    loss.backward()
    optimizer.step()
    pred = logits.argmax(dim=1).cpu().numpy()
    acc = accuracy_score(y.numpy(), pred)
    return loss.item(), acc
  • 反向傳播:計算梯度並更新參數。
  • 指標:使用 accuracy_score 評估批准確率。
@torch.no_grad()
def validate_step(model, batch):
    model.eval()
    x, y = batch
    logits = model(x.to(device))
    loss = loss_func(logits, y.to(device))
    pred = logits.argmax(dim=1).cpu().numpy()
    acc = accuracy_score(y.numpy(), pred)
    return loss.item(), acc
  • 在驗證集上關閉梯度計算,加快速度並節省顯存。

4.4 完整訓練循環

for epoch in range(1, EPOCHS+1):
    # 訓練
    for batch in train_loader:
        train_loss, train_acc = train_step(model, batch, optimizer)
    # 驗證
    for batch in val_loader:
        val_loss, val_acc = validate_step(model, batch)
    # 日誌記錄 & 模型保存
    if val_acc > best_acc:
        torch.save(...)
  • 每輪完成後打印損失/準確率,保存最佳模型。

5. 優缺點與擴展

優點

  • 高效:金字塔下采樣顯著減少序列長度,降低計算開銷。
  • 易訓練:殘差連接緩解梯度消失。
  • 參數量少:相比 RNN/LSTM 速度更快。

缺點

  • 信息丟失:下采樣會丟棄部分細節。
  • 感受野固定:卷積核及層數需手工調優。

可擴展方向

  • 多通道卷積:引入不同窗口大小並行卷積。
  • 注意力機制:在下采樣後加入自注意力,補充全局依賴。
  • 層次化融合:結合 HAN、Transformer 架構,提高長依賴捕獲能力。

3.補充問題

DPCNN的超參數

  1. total_words = 20000 只保留訓練語料中出現頻率最高的 20,000 個詞,其餘詞都映射為 <unk>
  • 作用:控制詞表大小,減少稀有詞帶來的噪聲和計算開銷。
  • 對長文本的影響:即使文本很長,也只會按最大詞表截斷——所有低頻詞統一處理成 <unk>,保證序列長度和詞表維度都在可控範圍內。
  1. doc_maxlen = 500 每條文本被截斷或填充到 500 個詞(token)。
  • 截斷:若文本長度 > 500,則只保留前 500 個 token,丟棄後面的部分。
  • 填充:若文本長度 < 500,則在尾部補 <pad> 直至長度為 500。
  • 對長文本的影響:通過固定長度讓所有輸入張量尺寸統一。500 足以覆蓋大多數新聞文章主體,同時截掉過長尾部,兼顧效率與完整性。
  1. net_depth = 20 網絡的總層數(區域嵌入層 + 若干卷積塊 ×2 + 池化層),決定模型金字塔的高度。
  • 每兩個卷積塊後,下采樣一次;20 層可以支持約 9~10 次下采樣(實際到序列長度變為 1 時停止)。
  • 對長文本的影響:更多深層意味着能對序列進行更多次的半速下采樣,把原來 500 長度的序列,逐步縮減到幾十、幾級、直至 1,從而在最頂層獲得整個文本的全局表徵。
  1. batch_size = 1024 每批在顯存中同時處理 1024 條文本。
  • 對長文本的影響:雖然每條是 500 長度的張量,但大 batch size 能更高效利用 GPU 並行計算;如果顯存不足,可調小。
  1. 其他超參數
  • embedding_dim = 200:詞向量維度;影響每個詞的表達能力。
  • LR = 5e-4 & EPOCHS = 30:學習率和訓練輪數,影響收斂速度與最終效果。

DPCNN如何處理長文本

  • 固定長度截斷/填充:先把所有文本統一到 doc_maxlen = 500,保證輸入張量尺寸一致。
  • 區域嵌入(3-gram 卷積):在長度為 500 之上先做一次 1D 卷積,提取局部 n-gram 特徵。
  • 金字塔式下采樣
  • 每經過兩個卷積塊,就通過 MaxPool1d(kernel=3, stride=2, padding=1) 將序列長度減半。
  • 層層下采樣後,從 500 → ~250 → ~125 → … → 1(或很小),最後得到一個定長的特徵圖,融合了全局信息。
  • 殘差連接:每個卷積塊前後的輸入相加,保證即便文本很長,梯度也能順暢向底層傳遞,不會在深層網絡中消失。

這種“先定長截斷 + 多次半速下采樣 + 殘差加速”的策略,使 DPCNN 能高效地處理和表徵長文本,並在頂層快速聚合全局語義。

DPCNN就像是嵌套多層的漏斗

這個過程就像一個漏斗:

  • 頂部寬大(原始文本長度長、信息多),
  • 每經過一層卷積+池化,就壓縮一次長度、昇華語義,最終匯聚到底部的“分類表示”。

層級

序列長度(示意)

特徵維度(num_filters)

輸入文本

500

200(embedding_dim)

conv1×1

500

250

block1

500

250

pool1

250

250

block2

250

250

pool2

125

250

block3

125

250

pool3

62

250




blockN

1

250

FC輸出

-

10(類別數)