#手写代码# 用Bert+CNN解决文本分类问题

1 配置文件

首先定义一个配置文件类,类里边存放Bert和CNN的一些超参数

class Config(object):
    '''
    配置参数
    '''
    def __init__(self,dataset):

        # 模型名称
        self.model_name='Bert CNN Model'
        # 训练集,测试集,检验集,类别,模型训练结果保存路径
        # self.train_path=dataset+'/data/dev.txt'
        # self.test_path=dataset+'/data/dev.txt'
        # self.dev_path=dataset+'/data/dev.txt'

        #数据集路径
        self.train_path=dataset+'/data/train.txt'
        self.test_path=dataset+'/data/test.txt'
        self.dev_path=dataset+'/data/dev.txt'

        # python 数据结构保存路径
        self.datasetpkl=dataset+'/data/dataset.pkl'
        # 类别路径
        self.class_list=[x.strip() for x in open(dataset+'/data/class.txt').readlines()]
        self.save_path=dataset+'/saved_dict/'+self.model_name+'.ckpt'

        # 配置使用检测GPU
        self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
        # 若超过1000还没有提升就提前结束训练
        self.require_improvement=1000
        # 类别数
        self.num_classes = len(self.class_list)

        # 整体训练次数
        self.num_epoch=3
        # batch大小
        self.batch_size=128
        #每个序列最大token数
        self.pad_size=32
        #学习率
        self.learning_rate = 1e-5

        self.bert_path='bert_pretrain'

        # bert 分词器
        self.tokenizer=BertTokenizer.from_pretrained(self.bert_path) #定义分词器
        self.hidden_size=768  # Bert模型 token的embedding维度 = Bert模型后接自定义分类器(单隐层全连接网络)的输入维度

        # 每个n-gram的卷积核数量
        self.num_filters=256

        # 卷积核在序列维度上的尺寸 = n-gram大小 卷积核总数量=filter_size*num_filters
        self.filter_size=(2,3,4)

        self.dropout=0.1

在这个配置文件中,分别定义了一下内容:

  1. 测试集,训练集,开发集的原始数据存放路径
  2. 测试集,训练集,开发集转化成python内的数据结构后的存放路径
  3. 类别列表
  4. 模型训练使用的硬件(CPU还是GPU)
  5. 损失函数超过多少次没有提升提前结束训练
  6. epoch数(整个数据集循环训练多少轮)
  7. batch_size
  8. 序列最大token数
  9. 学习率
  10. 模型的保存路径(本质上是保存的模型参数)
  11. 分词器
  12. Bert模型的隐含层节点数(经过Bert模型后,词向量的维度)
  13. 每组n-gram的卷积核数量
  14. 每组卷积核在序列维度上的尺寸
  15. dropout比例

2 定义模型

我们自定义的模型要继承自 nn.Module

class Model(nn.Module):
    def __init__(self,config):
        super(Model,self).__init__()
        self.bert=BertModel.from_pretrained(config.bert_path)  #从路径加载预训练模型
        for param in self.bert.parameters():
            param.requires_grad = True # 使参数可更新
        self.convs=nn.ModuleList(
            # 输入通道数,输出通道数(卷积核数),卷积核维度
            [nn.Conv2d(1,config.num_filters,(k,config.hidden_size)) for k in config.filter_size]    #(k,config.hidden_size)  n-gram,embedding维度
        )
        self.dropout=nn.Dropout(config.dropout)
        self.fc=nn.Linear(config.num_filters*len(config.filter_size),config.num_classes ) #输入的最后一个维度,输出的最后一个维度 全连接层只改变数据的最后一个维度 由输入最后的一个维度转化为 类别数

    def conv_and_pool(self,x,conv):
        x=conv(x)   #[batch_size,channel_num,pad_size,embedding_size(1)]
        x=F.relu(x)
        x=x.squeeze(3) #[batch_size,channel_num,pad_size]
        x=F.max_pool1d(x,x.size(2)) #经过卷积之后,x
        x = x.squeeze(2)  # [batch_size,channel_num]
        return x

    def forward(self,x):
        context=x[0] #128*32 batch_size*seq_length
        mask=x[2]   #128*32 batch_size*seq_length

        # 第一个参数 是所有输入对应的输出  第二个参数 是 cls最后接的分类层的输出
        encoder_out,pooled = self.bert(context,attention_mask=mask,output_all_encoded_layers=False) # output_all_encoded_layers 是否将bert中每层(12层)的都输出,false只输出最后一层【128*768】
        out = encoder_out.unsqueeze(1)  #增加一个维度,[batch_size,channel_num,pad_size,embedding_num]  ->  [batch_size,channel_num,pad_size,embedding_num]
        out = torch.cat([self.conv_and_pool(out,conv) for conv in self.convs],1)
        out=self.fc(out) # 128*10
        return out

2.1 init(self,config)函数

init(self,config)函数中主要进行了如下操作:

  1. 加载预训练bert模型
  2. 将Bert模型中的参数设置为可更新
  3. 定义卷积核,具体过程请参考下文
  4. 定义dropout层
  5. 定义全连接网络层

使用 nn.Conv2d() 函数定义卷积核,nn.Conv2d() 函数的主要参数如下:

参数名称作用
in_channels输入数据的通道数(这里可以理解为使用了多少 word embedding方法)
out_channels输出通道数,表示使用了多少个卷积核
kernel_size卷积核尺寸,[n-gram大小,word_embedding大小(bert隐含层节点数)]

自定义卷积核时,每种n-gram对应一组卷积核(每组卷积核数量相同),上述定义卷积核的代码中,首先遍历每个配置类中的 filter_size,得到每组局卷积核在0维(序列维度上的长度),然后将bert隐含层节点数作为每组局卷积核1维上的长度,从而定义每组卷积核的尺寸(每组卷积核内的卷积核尺寸相同)

2.1 conv_and_pool()函数

conv_and_pool()函数内主要执行了以下过程:

  1. 在输入数据的序列维度上进行滑动,从而进行一维卷积
  2. 通过relu激活函数
  3. 删除embedding_size维度,在embedding_size维度上,卷积核尺寸=embedding_size尺寸,因此embedding_size为1,可以删除
  4. 在数据的序列维度上进行一维最大池化(将一个序列中所有token合并,形成句向量)
  5. 经过上一步的操作后,数据在序列上的维度变为1,因此删除此序列维度
  6. 返回卷积后的二维数据,两个维度分别为 [batch_size,channel_num]

关于PyTorch中一维卷积和二维卷积的区别请参考此文章:深入理解 PyTorch 的一维卷积和二维卷积,一维池化和二维池化

2.3 forward(self,x)函数

forward(self,x)函数是Bert中一个特殊文章函数,forward(self,x)函数详细解析请看此文章

这里输入数据的结构为 [输入的token序列,序列真实长度,mask序列],输入数据的格式和数据预处理部分相关,在上一篇文章中已经讲解过数据预处理的代码,这里不做赘述

def forward(self,x):
    context=x[0] #128*32 batch_size*seq_length
    mask=x[2]   #128*32 batch_size*seq_length

    # 第一个参数 是所有输入对应的输出  第二个参数 是 cls最后接的分类层的输出
    encoder_out,pooled = self.bert(context,attention_mask=mask,output_all_encoded_layers=False) # output_all_encoded_layers 是否将bert中每层(12层)的都输出,false只输出最后一层【128*768】
    out = encoder_out.unsqueeze(1)  #增加一个维度,[batch_size,channel_num,pad_size,embedding_num]  ->  [batch_size,channel_num,pad_size,embedding_num]
    out = torch.cat([self.conv_and_pool(out,conv) for conv in self.convs],1)
    out=self.fc(out) # 128*10 通道数=n-gram数*每种n-gram内的卷积数
    return out

forward(self,x)函数函数内主要执行了以下过程:

  1. 输入数据并得到预训练Bert模型的每个token对应的输出
  2. 增加一个维度,用于存储通道数(每个卷积核的输出对应一个通道)
  3. 遍历所有卷积核并依次对数据进行卷积,将不同卷积核得到的数据存储的不同通道中
  4. 将卷积后的数据放入全连接网络并返回输出数据 [batch_size,分类数]

关于bert模块两个返回值的深度解析请参考此文章 ->从源码层面,深入理解 Bert 框架

### TextCNNBERT 和 LSTM 的对比及应用场景 #### 1. **模型概述** TextCNN 是一种基于卷积神经网络 (Convolutional Neural Network, CNN) 的文本分类方法,主要通过提取局部特征并利用池化操作获取全局信息[^3]。 BERT 则是一种双向 Transformer 编码器模型,能够捕捉上下文中的语义关系,适用于多种自然语言处理任务,如情感分析、问答系统等[^1]。 LSTM(Long Short-Term Memory)属于循环神经网络 (Recurrent Neural Network, RNN),擅长建模时间序列数据或具有顺序特性的输入,常用于文本生成、机器翻译等领域。 --- #### 2. **核心特点** ##### (1)TextCNN - 基于滑动窗口的卷积核捕获短距离依赖关系。 - 使用最大池化层聚合重要特征,适合快速提取固定长度表示。 - 训练速度快,参数较少,尤其适配硬件资源有限场景。 - 对长程依赖关系的支持较弱,可能遗漏远距离上下文关联。 ##### (2)BERT - 双向编码允许其理解词语在不同位置上的含义变化。 - 预训练阶段采用大规模无标注语料库,微调时迁移能力强。 - 支持动态调整 token 表征,适应复杂句法结构。 - 参数量庞大,推理成本高,部署难度较大。 ##### (3)LSTM - 设计初衷解决传统 RNN 中梯度消失问题,具备记忆单元控制信息流动。 - 能够有效跟踪长时间跨度内的状态转移模式。 - 结合注意力机制可进一步增强性能表现,但在某些情况下可能导致过拟合现象发生。 - 实现较为灵活,既可以单独作为主干框架也可以嵌入其他体系之中发挥作用。 --- #### 3. **适用场景** | 特性/模型 | TextCNN | BERT | LSTM | |------------|---------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------| | 输入形式 | 单词级 embedding 序列 | Tokenized 文本 | 向量化后的单词或者字符 | | 输出目标 | 主要针对二元或多类别标签预测 | 多种下游任务包括但不限于分类丶回归丶抽取式 QA | 时间维度上连续变量估计以及离散事件检测 | | 效果评价标准 | 准确率(Accuracy), F1-Score | GLUE/MRPC/SQuAD 等公开评测榜单成绩 | BLEU/WER/CER | | 场景举例 | 新闻主题划分;垃圾邮件过滤 | 情感倾向判断;实体链接识别 | 手写文字辨认;语音信号解码 | --- #### 4. **优缺点总结** ##### (1)TextCNN - **优点**: 架构简单高效,易于理解和扩展;计算开销较低,便于实时在线服务支持。 - **缺点**: 局部感受野局限性强,难以覆盖整个文档范围内的深层次交互规律。 ##### (2)BERT - **优点**: 综合考虑前后文影响因素,提供更加精准的语言表达能力;通用性强,几乎可以无缝衔接各类 NLP 子领域需求。 - **缺点**: 初始化耗时较长,运行内存占用多;对于特定行业专用术语缺乏针对性优化可能会降低泛化水平。 ##### (3)LSTM - **优点**: 自然契合序列型数据特性描述方式,特别适合涉及因果链条推导的任务类型。 - **缺点**: 当面对极长历史记录追踪要求时容易陷入效率瓶颈;相比静态映射方案调试过程更为繁琐冗长。 --- ```python import torch.nn as nn class TextCNN(nn.Module): def __init__(self, vocab_size, embed_dim=100, num_classes=2): super(TextCNN, self).__init__() self.embedding = nn.Embedding(vocab_size, embed_dim) ... def bert_model(): from transformers import BertTokenizer, BertForSequenceClassification tokenizer = BertTokenizer.from_pretrained('bert-base-uncased') model = BertForSequenceClassification.from_pretrained('bert-base-uncased', num_labels=2) class LSTMModel(nn.Module): def __init__(self, input_size, hidden_size, output_size): super(LSTMModel, self).__init__() self.lstm = nn.LSTM(input_size, hidden_size, batch_first=True) ... ``` ---
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

energy_百分百

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

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

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

打赏作者

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

抵扣说明:

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

余额充值