BERT中文文本分类与聚类的工程实践:从模型选型到生产部署

1次阅读
没有评论

共计 2692 个字符,预计需要花费 7 分钟才能阅读完成。

image.webp

背景痛点:中文文本处理的特殊挑战

中文文本处理相比英文有显著差异,主要面临以下挑战:

BERT 中文文本分类与聚类的工程实践:从模型选型到生产部署

  • 分词歧义:例如 ” 南京市长江大桥 ” 可以切分为 ” 南京 / 市长 / 江大桥 ” 或 ” 南京市 / 长江 / 大桥 ”
  • 领域术语:医疗、金融等垂直领域存在大量专业术语,通用词表覆盖不足
  • 标注稀缺:高质量标注数据获取成本高,尤其在小语种和垂直领域
  • 语义复杂度:同义词、近义词、多义词现象普遍,且受上下文影响大

传统方法如 TF-IDF、Word2Vec 存在明显局限:

  1. 无法建模词语的深层语义关系
  2. 对未登录词 (OOV) 处理能力弱
  3. 需要复杂的特征工程

技术选型:预训练模型对比

模型 参数量 中文支持 训练速度 下游任务表现
BERT-base 110M ★★★★ 中等 ★★★★
ALBERT 12M ★★★★ ★★★☆
RoBERTa 110M ★★★★ ★★★★☆
Electra 110M ★★★☆ 中等 ★★★★

选型建议:

  • 计算资源充足时首选 RoBERTa
  • 追求推理速度选 ALBERT
  • 平衡型选择 BERT 或 Electra

核心实现

1. 环境准备

# 安装必要库
pip install transformers torch pandas

2. 文本预处理 Pipeline

import re
import jieba
from transformers import BertTokenizer

# 清洗函数
def clean_text(text):
    text = re.sub(r'[\s\n\r\t]+', ' ', text)  # 合并空白符
    text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9,。?!]', '', text)  # 去除非中英文数字和标点
    return text.strip()

# 加载 BERT 分词器
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')

# 完整处理流程
def process_text(text, max_len=128):
    text = clean_text(text)
    # 使用 BERT 原生分词
    inputs = tokenizer(
        text,
        max_length=max_len,
        padding='max_length',
        truncation=True,
        return_tensors='pt'
    )
    return inputs

3. Fine-tuning 代码示例

from transformers import BertForSequenceClassification, Trainer, TrainingArguments
from sklearn.model_selection import train_test_split

# 加载预训练模型
model = BertForSequenceClassification.from_pretrained(
    'bert-base-chinese',
    num_labels=10  # 假设 10 分类
)

# 训练参数配置
training_args = TrainingArguments(
    output_dir='./results',
    num_train_epochs=3,
    per_device_train_batch_size=32,
    warmup_steps=500,
    weight_decay=0.01,
    logging_dir='./logs',
    logging_steps=100,
    evaluation_strategy='steps',
    load_best_model_at_end=True,
    fp16=True  # 启用混合精度
)

# 自定义早停回调
from transformers import EarlyStoppingCallback
early_stop = EarlyStoppingCallback(early_stopping_patience=2)

# 创建 Trainer
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=val_dataset,
    callbacks=[early_stop]
)

# 开始训练
trainer.train()

进阶优化

领域自适应

  1. 收集领域文本构建自定义词表
  2. 使用 MLM 任务继续预训练
    from transformers import BertForMaskedLM
    
    mlm_model = BertForMaskedLM.from_pretrained('bert-base-chinese')
    
    # 继续预训练代码框架
    for batch in domain_dataloader:
        inputs = tokenizer(batch, return_tensors='pt', padding=True)
        outputs = mlm_model(**inputs, labels=inputs['input_ids'])
        loss = outputs.loss
        loss.backward()
        optimizer.step()

知识蒸馏

  1. 训练大模型(教师)
  2. 用小模型 (学生) 模仿教师输出
  3. 关键实现:
    # 使用 logits 和 hidden_states 作为监督信号
    student_loss = (teacher_logits - student_logits)**2
                + 0.5*MSE(teacher_hidden, student_hidden)

生产考量

GPU 内存优化

  • 使用梯度检查点
    model.gradient_checkpointing_enable()
  • 动态批处理
  • 混合精度推理

ONNX 加速

# 转换模型
torch.onnx.export(model, inputs, "model.onnx")

# 使用 ONNX Runtime 推理
import onnxruntime
sess = onnxruntime.InferenceSession("model.onnx")
outputs = sess.run(None, {"input_ids": inputs})

实测效果:
– CPU 推理速度提升 2 - 3 倍
– GPU 推理提升 30%-50%

避坑指南

类别不平衡解决方案

  1. 重采样(过采样少数类 / 欠采样多数类)
  2. 类别权重调整
    weights = torch.tensor([1.0, 5.0, 3.0])  # 假设 3 分类
    loss_fn = nn.CrossEntropyLoss(weight=weights)
  3. Focal Loss

聚类维度灾难预防

  1. 先使用 BERT 提取特征
  2. 用 PCA 降维(保留 95% 方差)
  3. 适当调整聚类算法参数

互动讨论

在多义词处理中,比如 ” 苹果 ” 既可指水果也可指科技公司,大家在实际业务中会采用哪些策略来区分不同场景下的语义?欢迎在评论区分享你的实战经验!

正文完
 0
评论(没有评论)