全部文章

5.新闻主题分类任务-案例(老教程)

学习目标

  • 了解新闻主题分类任务及相关数据。
  • 掌握使用浅层网络构建新闻主题分类器的实现过程。

任务说明

新闻主题分类任务:

以新闻报道文本为输入,判断其所属主题(互斥类型),属于典型的文本分类问题。
本任务假设类别之间是互斥的,即每篇新闻只能属于一个主题。

数据集简介

AG_NEWS 是 Torchtext 内置的一个新闻分类数据集,包含 ​​4 类新闻​​(世界、体育、商业、科技),广泛用于文本分类任务。​

  • ​来源​​: AG News Corpus (来自新闻网站)
  • ​类别​​: 4 类 (WorldSportsBusinessSci/Tech)
  • ​样本数​​: 训练集 120,000 条,测试集 7,600 条
  • ​字段​​:
    • label: 类别编号 (1-4)
    • text: 新闻文本

数据文件结构:

data/
├── ag_news_csv.tar.gz
└── ag_news_csv/
    ├── classes.txt  # 标签含义:World、Sports、Business、Sci/Tech
    ├── readme.txt   # 数据集英文说明
    ├── test.csv     # 验证数据(7600条)
    └── train.csv    # 训练数据(12万条)

​(1) 直接加载原始数据​

下面是依赖的工具包安装命令:(由于存在兼容问题,所以指定版本)

pip install torch==2.0.1 torchtext==0.15.2 torchvision==0.15.2 torchaudio==2.0.2 --index-url https://download.pytorch.org/whl/cu118
pip install portalocker>=2.0.0
portalocker 是一个用于文件锁定的 Python 包,torchtext 在加载数据集时需要它来确保数据文件的完整性。
 
import torch
from torchtext.datasets import AG_NEWS
import os
# 定义数据下载路径, 当前文件夹下的data文件夹
load_data_path = "./data"
if not os.path.isdir(load_data_path):
    os.mkdir(load_data_path)

# 加载训练集和测试集
train_dataset, test_dataset = AG_NEWS(root=load_data_path, split=('train', 'test'))

# 查看前5条训练数据
for i, (label, text) in enumerate(train_dataset):
    if i >= 5:
        break
    print(f"Label: {label}, Text: {text[:50]}...")

 打印结果:

Label: 3, Text: Wall St. Bears Claw Back Into the Black (Reuters) ...
Label: 3, Text: Carlyle Looks Toward Commercial Aerospace (Reuters...
Label: 3, Text: Oil and Economy Cloud Stocks' Outlook (Reuters) Re...
Label: 3, Text: Iraq Halts Oil Exports from Main Southern Pipeline...
Label: 3, Text: Oil prices soar to all-time record, posing new men...

数据预处理​

​(1) 分词与构建词表​

from torchtext.data.utils import get_tokenizer
from torchtext.vocab import build_vocab_from_iterator

# 定义分词器(英文用空格分词)
tokenizer = get_tokenizer('basic_english')

def yield_tokens(data_iter):
    for _, text in data_iter:
        yield tokenizer(text)

# 构建词表
vocab = build_vocab_from_iterator(yield_tokens(train_dataset), specials=['<unk>'])
vocab.set_default_index(vocab['<unk>'])  # 设置默认未知词索引

# 查看词表大小
print(f"词表大小: {len(vocab)}")
词表大小: 95811

(2) 文本数值化

# 将文本转换为词索引
text_pipeline = lambda x: vocab(tokenizer(x))
label_pipeline = lambda x: int(x) - 1  # 标签转为0-3

# 示例
text = "Apple launches new iPhone"
indices = text_pipeline(text)
print(f"文本: {text} → 词索引: {indices}")
文本: Apple launches new iPhone → 词索引: [295, 1003, 23, 0]

(3)封装为 DataLoader​

from torch.utils.data import DataLoader
import torch

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

def collate_batch(batch):
    label_list, text_list = [], []
    for (_label, _text) in batch:
        label_list.append(label_pipeline(_label))
        processed_text = torch.tensor(text_pipeline(_text), dtype=torch.int64)
        text_list.append(processed_text)
    return torch.tensor(label_list, dtype=torch.int64).to(device), torch.stack(text_list).to(device)

# 创建 DataLoader
train_loader = DataLoader(train_data, batch_size=8, shuffle=True, collate_fn=collate_batch)

案例实现步骤

第一步:构建带有 Embedding 层的文本分类模型

  • 模型结构:
    • Embedding层:将词汇索引映射为稠密向量
    • 平均池化层:将变长文本转换为固定长度表示
    • 全连接层:输出类别预测
import torch
import torch.nn as nn
import torch.nn.functional as F

# 配置参数
BATCH_SIZE = 16
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

class TextSentiment(nn.Module):
    """文本分类模型"""
    def __init__(self, vocab_size, embed_dim, num_class):
        super().__init__()
        # 嵌入层(sparse=True表示稀疏更新梯度)
        self.embedding = nn.Embedding(vocab_size, embed_dim, sparse=True)
        # 线性层
        self.fc = nn.Linear(embed_dim, num_class)
        # 初始化权重
        self.init_weights()

    def init_weights(self):
        """初始化权重(均匀分布)"""
        initrange = 0.5
        self.embedding.weight.data.uniform_(-initrange, initrange)
        self.fc.weight.data.uniform_(-initrange, initrange)
        self.fc.bias.data.zero_()

    def forward(self, text):
        """前向传播过程"""
        # 词嵌入
        embedded = self.embedding(text)
        # 调整维度以适配batch处理
        c = embedded.size(0) // BATCH_SIZE
        embedded = embedded[:BATCH_SIZE * c]
        # 平均池化(将每个batch的词向量平均为一个向量)
        embedded = embedded.transpose(1, 0).unsqueeze(0)
        embedded = F.avg_pool1d(embedded, kernel_size=c)
        # 输出分类结果
        return self.fc(embedded[0].transpose(1, 0))

# 实例化模型
VOCAB_SIZE = len(train_dataset.get_vocab())  # 词汇总数
EMBED_DIM = 32  # 词嵌入维度
NUN_CLASS = len(train_dataset.get_labels())  # 类别总数
model = TextSentiment(VOCAB_SIZE, EMBED_DIM, NUN_CLASS).to(device)

第二步:对数据进行 batch 处理

  • 实现generate_batch函数:
    • 输入:batch大小的样本列表(每个样本是文本和标签的元组)
    • 输出:拼接后的文本张量和标签张量
def generate_batch(batch):
    """将batch数据转换为样本张量和标签张量"""
    # 提取标签并转换为张量
    label = torch.tensor([entry[1] for entry in batch])
    # 提取样本并拼接为张量
    text = [entry[0] for entry in batch]
    text = torch.cat(text)
    return text, label

# 调用示例
batch = [(torch.tensor([3, 23, 2, 8]), 1), (torch.tensor([3, 45, 21, 6]), 0)]
print(generate_batch(batch))
# 输出:(tensor([ 3, 23, 2, 8, 3, 45, 21, 6]), tensor([1, 0]))

第三步:构建训练与验证函数

  • 训练函数train()
    • 使用DataLoader加载数据
    • 前向传播、计算损失、反向传播、参数更新
    • 计算训练损失和准确率
  • 验证函数valid()
    • 禁用梯度计算
    • 计算验证损失和准确率
from torch.utils.data import DataLoader

def train(train_data):
    """模型训练函数"""
    train_loss = 0
    train_acc = 0
    # 数据加载器(按batch处理)
    data = DataLoader(train_data, batch_size=BATCH_SIZE, shuffle=True, collate_fn=generate_batch)
    for i, (text, cls) in enumerate(data):
        optimizer.zero_grad()  # 梯度清零
        output = model(text)   # 模型输出
        loss = criterion(output, cls)  # 计算损失
        train_loss += loss.item()
        loss.backward()        # 反向传播
        optimizer.step()       # 参数更新
        train_acc += (output.argmax(1) == cls).sum().item()  # 计算准确率
    scheduler.step()  # 调整学习率
    # 返回平均损失和准确率
    return train_loss / len(train_data), train_acc / len(train_data)

def valid(valid_data):
    """模型验证函数"""
    loss = 0
    acc = 0
    data = DataLoader(valid_data, batch_size=BATCH_SIZE, collate_fn=generate_batch)
    with torch.no_grad():  # 关闭梯度计算
        for text, cls in data:
            output = model(text)
            loss += criterion(output, cls).item()
            acc += (output.argmax(1) == cls).sum().item()
    return loss / len(valid_data), acc / len(valid_data)

第四步:进行模型训练和验证

  • 关键设置:
    • 训练轮数:10
    • 损失函数:交叉熵损失
    • 优化器:SGD(初始学习率4.0)
    • 学习率调度:StepLR(每步衰减0.9)
  • 数据划分:
    • 训练集:95%原始训练数据
    • 验证集:5%原始训练数据
import time
from torch.utils.data.dataset import random_split

# 训练配置
N_EPOCHS = 10
min_valid_loss = float('inf')
criterion = torch.nn.CrossEntropyLoss().to(device)  # 交叉熵损失
optimizer = torch.optim.SGD(model.parameters(), lr=4.0)  # SGD优化器
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, 1, gamma=0.9)  # 学习率衰减

# 划分训练集和验证集(95%训练,5%验证)
train_len = int(len(train_dataset) * 0.95)
sub_train_, sub_valid_ = random_split(train_dataset, [train_len, len(train_dataset) - train_len])

# 训练循环
for epoch in range(N_EPOCHS):
    start_time = time.time()
    train_loss, train_acc = train(sub_train_)
    valid_loss, valid_acc = valid(sub_valid_)
    # 计算耗时
    secs = int(time.time() - start_time)
    mins = secs // 60
    secs %= 60
    # 打印训练信息
    print(f'Epoch: {epoch + 1} | time in {mins} minutes, {secs} seconds')
    print(f'\tLoss: {train_loss:.4f} (train) \t/ Acc: {train_acc * 100:.1f}% (train)')
    print(f'\tLoss: {valid_loss:.4f} (valid) \t/ Acc: {valid_acc * 100:.1f}% (valid)')

训练过程输出示例:

Epoch:1 | time in 0 minutes,36 seconds
    Loss:0.0592(train) | Acc:63.9%(train)
    Loss:0.0005(valid) | Acc:69.2%(valid)

第五步:查看 embedding 层嵌入的词向量

  • 通过模型state_dict获取Embedding层的权重矩阵
# 打印嵌入层权重(词向量矩阵)
print(model.state_dict()['embedding.weight'])
# 输出示例:
# tensor([[ 0.4401, -0.4177, -0.4161, ..., 0.2497, -0.4657, -0.1861],
#         [-0.2574, -0.1952,  0.1443, ..., -0.4687, -0.0742,  0.2606],
#         ...])

小节总结

  • 新闻主题分类任务:判断新闻文本所属主题(互斥类别),属于文本分类问题。
  • 数据:使用AG_NEWs数据集,包含 4 个主题,训练集 12 万条、验证集 7600 条。
  • 实现步骤:
    1. 构建含 Embedding 层的分类模型(嵌入层 + 线性层)。
    2. 定义generate_batch函数处理 batch 数据。
    3. 实现训练与验证函数,计算损失和准确率。
    4. 划分数据集并进行多轮训练,动态调整学习率。
    5. 查看嵌入层的词向量矩阵,分析词汇嵌入结果。

关键知识点

  1. ​文本分类流程​​:数据准备→模型构建→训练验证→评估
  2. ​Embedding层​​:将离散词汇索引映射为连续向量表示
  3. ​批处理技术​​:将变长文本统一处理为固定维度的批数据
  4. ​平均池化​​:处理变长文本的常用方法
  5. ​学习率调度​​:StepLR实现学习率衰减

扩展思考

  • 可以尝试不同的池化方法(如最大池化)
  • 可以实验更复杂的模型结构(如加入RNN/CNN)
  • 可以调整超参数(如Embedding维度、批大小等)观察效果变化

这个案例完整展示了使用PyTorch实现文本分类任务的全流程,特别适合初学者理解NLP任务的基本处理方法和深度学习模型的构建方式。

 

posted @ 2025-07-10 22:34  指尖下的世界  阅读(16)  评论(0)    收藏  举报