基于Python的自然语言处理系列(11):使用卷积神经网络(CNN)进行文本分类

        在前面的文章中,我们介绍了如何使用RNN和LSTM进行文本分类。在本篇文章中,我们将引入另一种强大的深度学习方法——卷积神经网络(CNN)。CNN通常用于图像处理,但它也能够在文本分类中表现出色。通过捕捉文本中的n-grams(如二元组、三元组等),CNN可以识别出有助于分类的重要特征。

1. 卷积神经网络在文本分类中的应用

        CNN在图像处理中通常使用2D卷积层,但对于文本,文本数据是一维的。我们可以将文本视为一个“序列”,然后通过多个大小不同的卷积核来提取特征。每个卷积核可以理解为一种特定的模式检测器,它在文本中搜索相关的n-grams。我们将使用不同大小的卷积核,来捕捉二元组(bi-gram)、三元组(tri-gram)等不同长度的特征。

        例如,一个大小为2的卷积核将覆盖连续的两个单词,模拟二元组。通过对这些n-grams进行卷积操作,并结合池化层的作用,我们可以提取出对分类任务至关重要的特征。

2. 数据加载与预处理

        首先,我们使用TorchText库加载AG_NEWS数据集,并进行预处理,使用与之前相同的流程,包括使用spacy进行标记化、构建词汇表,并加载预训练的FastText词嵌入。

from torchtext.datasets import AG_NEWS
from torchtext.data.utils import get_tokenizer
from torchtext.vocab import FastText

# 加载数据集
train, test = AG_NEWS()

# 使用spacy进行标记化
tokenizer = get_tokenizer('spacy', language='en_core_web_sm')

# 构建词汇表
def yield_tokens(data_iter):
    for _, text in data_iter:
        yield tokenizer(text)

vocab = build_vocab_from_iterator(yield_tokens(train), specials=['<unk>', '<pad>'])
vocab.set_default_index(vocab["<unk>"])

# 引入FastText预训练词嵌入
fast_vectors = FastText(language='simple')
fast_embedding = fast_vectors.get_vecs_by_tokens(vocab.get_itos()).to(device)

3. 卷积神经网络模型设计

        我们设计了一个简单的CNN模型。它由一个嵌入层、多个卷积层、池化层以及全连接层组成。卷积层将捕捉不同大小的n-grams特征,而池化层则用于提取每个特征中最显著的部分。

模型结构

  • 嵌入层:将输入的文本序列转化为词嵌入矩阵。
  • 卷积层:使用不同大小的卷积核来捕捉n-grams特征。
  • 池化层:通过最大池化操作选取卷积输出中的最重要信息。
  • 全连接层:将池化后的特征拼接起来,进行分类。
import torch.nn as nn
import torch.nn.functional as F

class CNN(nn.Module):
    def __init__(self, input_dim, emb_dim, output_dim, dropout, n_filters, filter_sizes):
        super().__init__()
        self.embedding = nn.Embedding(input_dim, emb_dim, padding_idx=pad_idx)
        
        # 多个不同大小的卷积核
        self.convs = nn.ModuleList([
            nn.Conv2d(in_channels=1, 
                      out_channels=n_filters, 
                      kernel_size=(fs, emb_dim)) 
            for fs in filter_sizes
        ])
        
        self.fc = nn.Linear(len(filter_sizes) * n_filters, output_dim)
        self.dropout = nn.Dropout(dropout)
        
    def forward(self, text):
        embedded = self.embedding(text)  # 嵌入
        embedded = embedded.unsqueeze(1)  # 扩展维度以适配Conv2D
        
        # 卷积和池化操作
        conved = [F.relu(conv(embedded)).squeeze(3) for conv in self.convs]
        pooled = [F.max_pool1d(conv, conv.shape[2]).squeeze(2) for conv in conved]
        
        # 拼接池化后的结果
        cat = self.dropout(torch.cat(pooled, dim=1))
        return self.fc(cat)

        在这个模型中,我们使用了三种不同大小的卷积核(如3、4、5),来捕捉不同长度的n-grams。通过卷积和最大池化操作,我们提取出了这些n-grams中的关键信息,并将它们拼接在一起后通过全连接层进行分类。

4. 模型训练与评估

        我们使用Adam优化器进行模型训练,并在训练过程中计算模型的损失和准确率。以下是完整的训练与评估代码:

import torch.optim as optim

# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=1e-3)

# 计算准确率
def accuracy(preds, y):
    predicted = torch.max(preds.data, 1)[1]
    batch_corr = (predicted == y).sum()
    acc = batch_corr / len(y)
    return acc

# 训练函数
def train(model, loader, optimizer, criterion, loader_length):
    epoch_loss = 0
    epoch_acc = 0
    model.train()
    
    for i, (label, text) in enumerate(loader): 
        label = label.to(device)
        text = text.to(device)
                
        # 前向传播
        predictions = model(text).squeeze(1)
        
        # 计算损失和准确率
        loss = criterion(predictions, label)
        acc  = accuracy(predictions, label)
        
        # 反向传播
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        
        epoch_loss += loss.item()
        epoch_acc += acc.item()
                        
    return epoch_loss / loader_length, epoch_acc / loader_length

# 评估函数
def evaluate(model, loader, criterion, loader_length):
    epoch_loss = 0
    epoch_acc = 0
    model.eval()
    
    with torch.no_grad():
        for i, (label, text) in enumerate(loader): 
            label = label.to(device)
            text = text.to(device)

            predictions = model(text).squeeze(1)
            
            loss = criterion(predictions, label)
            acc  = accuracy(predictions, label)

            epoch_loss += loss.item()
            epoch_acc += acc.item()
        
    return epoch_loss / loader_length, epoch_acc / loader_length

        我们可以使用5个epoch训练模型,并保存最佳的模型。

num_epochs = 5
best_valid_loss = float('inf')

for epoch in range(num_epochs):
    train_loss, train_acc = train(model, train_loader, optimizer, criterion, len(train_loader))
    valid_loss, valid_acc = evaluate(model, valid_loader, criterion, len(valid_loader))
    
    if valid_loss < best_valid_loss:
        best_valid_loss = valid_loss
        torch.save(model.state_dict(), 'best-cnn-model.pt')
    
    print(f'Epoch {epoch+1} | Train Loss: {train_loss:.3f}, Train Acc: {train_acc*100:.2f}%')
    print(f'Valid Loss: {valid_loss:.3f}, Valid Acc: {valid_acc*100:.2f}%')

5. 测试与预测

        训练完成后,我们可以使用模型对新的文本进行预测。

def predict(text):
    with torch.no_grad():
        output = model(text).squeeze(1)
        predicted = torch.max(output.data, 1)[1]
        return predicted

test_str = "Google is facing financial challenges."
text = torch.tensor(text_pipeline(test_str)).unsqueeze(0).to(device)
prediction = predict(text)
print(f'预测结果: {prediction.item()}')

结语

        在本文中,我们通过引入卷积神经网络(CNN),展示了如何高效地进行文本分类。CNN通过不同大小的卷积核捕捉文本中的n-grams特征,结合最大池化层提取最重要的信息,从而实现了高效的特征提取和分类。然而,虽然CNN在处理短文本任务中表现出色,它在处理长序列或依赖较长上下文的任务时可能有所不足。

        为了更好地处理这些复杂的上下文和长依赖关系问题,我们可以引入序列到序列(seq2seq)模型。seq2seq模型通过编码器和解码器结构,能够将一个序列映射为另一个序列,这对于处理翻译、摘要生成等任务非常有效。在seq2seq模型中,编码器将输入序列转换为一个固定长度的上下文向量,解码器根据该向量生成输出序列。这种结构不仅适用于自然语言生成任务,也能够帮助处理长文本的分类问题。

        敬请期待下一篇文章,届时我们将详细介绍seq2seq模型的工作原理及其在自然语言处理中的应用!

如果你觉得这篇博文对你有帮助,请点赞、收藏、关注我,并且可以打赏支持我!

欢迎关注我的后续博文,我将分享更多关于人工智能、自然语言处理和计算机视觉的精彩内容。

谢谢大家的支持!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

会飞的Anthony

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值