Qwen2ForSequenceClassification文本分类实战和经验分享

本文主要使用Qwen2ForSequenceClassification实现文本分类任务。

文章首发于我的知乎:https://zhuanlan.zhihu.com/p/17468021019

一、实验结果和结论

这几个月,在大模型分类场景做了很多实验,攒了一点小小经验。

1、短文本

1)query情感分类,一般不如BERT
ps:结论和,https://segmentfault.com/a/1190000044485544#item-13,基本一致

2、长文本

1)通话ASR转译长文本,BERT截断512不如LLM

  • LLM没有截断(如果都阶段512,可能效果差不多)
  • 没有对比,BERT进行文本滑动窗口的版本

2)Base v.s. Instruct

  • 数据量小时,Base微调不如Instruct(Instruct模型有对齐税,但是微调数据量小时,效果还是比Base没见过指令微调样本的好)
    3)SFT v.s. LoRA
  • 数据量小时(总样本10K以下,每个标签需要视情况而定),SFT微调不如LoRA(SFT调参成本也更大)

3、分类场景的提升方案

1)生成式微调独有

  • 混合同领域相似数据类型不同业务数据,可以提升若干点
    • 数据分布不能差异太大,特别是文本长度,否则混入这种数据反而会让效果下滑(一个平均长度1.2K,一个平均长度5k)
    • 混入比例(接近2:1,不同场景需自行尝试)
    • 混入顺序(我使用的是随机采样,没验证是否分开先后训练顺序是否有影响)
  • 优化提示词(提示词中,增加各类别标签的精要描述;短文本可尝试few-shot)

2)分类头微调 + 生成式微调

  • 数据量大时(10K以上,平均每个标签样本充足),尝试微调Base,而不是微调Instruct
  • 数据增强:尝试无标注数据上跑的伪标签样本(提示词抽的标签 + 微调后的模型抽的标签)
  • 数据量大时,尝试SFT
  • LoRA微调时,加入LLM的embedding层(未验证过)
  • 尝试蒸馏更大模型到小模型(痛点:大模型难调参,训练成本更高,部署上线还是得小模型)
  • 尝试LoRA的变体
  • 尝试调参(试过optuna自动搜索,效果也不太好;一般就调lr, epoch, rank)

3)重量级

  • Base模型,领域数据增量预训练后,再进行指令微调
    • 方案待验证
      • 这边尝试了在Qwen2-7B-Instruct的领域数据指令微调后的模型,微调效果反而比直接微调Qwen2-7B-Instruct效果差些。由于不清楚该模型训练步骤细节,所以原因尚不明确)
    • 若验证成功,其好处是训出来的基座在各个领域任务上的微调都能提点

第一优先级,还是搞数据。其次,才是尝试各种方案的加加减减。

4、注意点

  • 学习率

    • 训练的参数量越大,学习率适当要调小
  • 标签噪声

    • 样本标注错误,需要在错误分析时进行剔除和校正
  • 分类业务规则

    • 复杂场景,需要提前确定好完备的标注规则,避免返工(那些模型可以做,那么模型不能做)

二、文本分类-从BERT到LLM

Qwen2ForSequenceClassification和BERTForSequenceClassification,逻辑上是一致的。都是在模型的输出层,加上一个Linear层,用来完成分类任务。

之前在,BERT上做的所有改动,都可以迁移到LLM上。譬如,BERT-CRF。

和BERTForSequenceClassification一样。

BertForSequenceClassification是一个已经实现好的用来进行文本分类的类,一般用来进行文本分类任务。

通过num_labels传递分类的类别数,从构造函数可以看出这个类大致由3部分组成,1个是Bert,1个是Dropout,1个是用于分类的线性分类器Linear。

class BertForSequenceClassification(BertPreTrainedModel):
    def __init__(self, config):
        super(BertForSequenceClassification, self).__init__(config)
        self.num_labels = config.num_labels
python
        self.bert = BertModel(config)
        self.dropout = nn.Dropout(config.hidden_dropout_prob)
        self.classifier = nn.Linear(config.hidden_size, self.config.num_labels)
        self.init_weights()

Bert用于提取文本特征进行Embedding,Dropout防止过拟合,Linear是一个弱分类器,进行分类,如果需要用更复杂的网络结构进行分类可以参考它进行改写。

forward()函数里面已经定义了损失函数,训练时可以不用自己额外实现,返回值包括4个内容

def forward(...):
    ...
    if labels is not None:
        if self.num_labels == 1:
            #  We are doing regression
            loss_fct = MSELoss()
            loss = loss_fct(logits.view(-1), labels.view(-1))
        else:
            loss_fct = CrossEntropyLoss()
            loss = loss_fct(logits.view(-1, self.num_labels), labels.view(-1))
        outputs = (loss,) + outputs
    return outputs  # (loss), logits, (hidden_states), (attentions)

接下来。看看Qwen2ForSequenceClassification。

Qwen2ForSequenceClassification(
  (model): Qwen2Model(
    (embed_tokens): Embedding(151936, 1024, padding_idx=151643)
    (layers): ModuleList(
      (0-23): 24 x Qwen2DecoderLayer(
        (self_attn): Qwen2SdpaAttention(
          (q_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (k_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (v_proj): Linear(in_features=1024, out_features=1024, bias=True)
          (o_proj): Linear(in_features=1024, out_features=1024, bias=False)
          (rotary_emb): Qwen2RotaryEmbedding()
        )
        (mlp): Qwen2MLP(
          (gate_proj): Linear(in_features=1024, out_features=2816, bias=False)
          (up_proj): Linear(in_features=1024, out_features=2816, bias=False)
          (down_proj): Linear(in_features=2816, out_features=1024, bias=False)
          (act_fn): SiLU()
        )
        (input_layernorm): Qwen2RMSNorm()
        (post_attention_layernorm): Qwen2RMSNorm()
      )
    )
    (norm): Qwen2RMSNorm()
  )
  (score): Linear(in_features=1024, out_features=3, bias=False)
)

Qwen2官方代码实现,内置三种模式:

  • single_label_classification 单标签分类
    • 损失为CrossEntropyLoss
    • 取单标签对应logit,算负对数似然
  • multi_label_classification 多标签分类
    • 损失为BCEWithLogitsLoss
    • 标签为muilti-hot, 预测logits计算sigmoid,实际取对应维度标签Logit,损失求和
  • regression 回归
    • 损失为MSELoss
    • 默认为单维度回归(回归可以作为奖励模型,预测打分)

这3种模式的输入标签不同。

class Qwen2ForSequenceClassification(Qwen2PreTrainedModel):
    def __init__(self, config):
        super().__init__(config)
        self.num_labels = config.num_labels
        self.model = Qwen2Model(config)
        self.score = nn.Linear(config.hidden_size
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值