自然语言处理中,文本多分类是最常见的需求之一。如果标注数据量大且样本均衡,任选一个bert模型都能达到非常好的准确度。但实际应用中往往面临的是数据量小,标签不均衡,标注错误等各种预想之外但又普遍存在的问题。如何根据实际情况解决问题,获得不错的效果才是我们需要研究的。
现有一个多分类问题,供讨论研究可行的方法。一批标注数据有100个标签,标签为文本。标签不均衡问题非常严重,有的标签样本量数千条数据,有的标签样本量从个位数到数十个。如何使用现有数据训练模型,用于后续数据的标签预测。由于样本较少的类别本身很难达到较高的准确性,以下方法测试仅说明如何在给定数据条件下,找到一个相对较优的方法。示例数据如下:
句子 | 类别 |
---|---|
This is the first test. | label1 |
The description of ach sentence describe belong different subject. | label2 |
… | … |
文本分类问题数据简单,统计标签类别和数目,根据实际需求尝试合并样本过于少的标签,清洗列表进行标准化。
直接使用 simpletransformers中的多分类模型,标签数目设置为多分类的标签数目。以下为官方示例代码。
from simpletransformers.classification import ClassificationModel, ClassificationArgs
# 将标签类别 用数字标签代替
train_data = [
["Aragorn was the heir of Isildur", 1],
["Frodo was the heir of Isildur", 0],
["Pippin is stronger than Merry", 2],
]
train_df = pd.DataFrame(train_data)
train_df.columns = ["text", "labels"]
# 修改模型训练参数
model_args = ClassificationArgs(num_train_epochs=1)
# 初始化模型
model = ClassificationModel(
'bert','bert-base-cased',
num_labels=3, # 设置类别数目
args=model_args)
# 训练模型
model.train_model(train_df)
经测试,此方法对本例中样本数目较少的类别准确度非常差。
句子对分类本身适用于判断两个句子的关系是相似,蕴含还是相反。此处由于标签也是文本,可将分类问题转换认为是句子对的关系判断(虽然句子对一个是相对短的文本)。后续有新样本时,直接与已有的所有样本进行匹配,将得分最高的匹配样本的标签作为新样本的预测标签(与聚类相似),将分类问题转换为搜索问题,这样即使部分标签的训练数据非常少,仍然可以发挥作用。
(1)使用simpletransformers
train_data = [
["Aragorn was the heir of Isildur", "Gimli fought with a battle axe", 1,],
["Frodo was the heir of Isildur", "Legolas was an expert archer", 0,],
]
train_df = pd.DataFrame(train_data)
train_df.columns = ["text_a", "text_b", "labels"]
model_args = ClassificationArgs(num_train_epochs=1)
model = ClassificationModel("roberta", "roberta-base")
model.train_model(train_df)
对于我们的问题,将类别表示为”text_b“。构建正例使用配对的真实类别。构建负例,使用非配对的其他类别。每个正例可随机生成20个负例,构建训练集进行模型训练。总体准确率有明显提升。
(2)使用 Sentence-Transformer
sentence_transformers中提供了更多丰富的损失函数,能够更准确有效的训练模型,提升准确度。构建三元组训练数据,使用 BatchSemiHard 作为损失函数,训练模型,使得锚样本与正样本的距离远小于锚样本与负样本之间的距离。损失函数的原理和详细介绍可参考其他资料。
关于模型评价,由于本质是分类问题,使用BinaryClassificationEvaluator直接计算准确率 而不是EmbeddingSimilarityEvaluator计算的相关性。
model = SentenceTransformer(model_name)
train_loss = losses.BatchSemiHardTripletLoss(model=model)
num_epochs = 3
warmup_steps = math.ceil(len(train_dataloader) * num_epochs * 0.1) # 10% of train data for warm-up
model.fit(train_objectives=[(train_dataloader, train_loss)],
evaluator=dev_evaluator,
epochs=num_epochs,
evaluation_steps=int(len(train_dataloader) * 0.1),
warmup_steps=warmup_steps,
output_path=model_save_path,
use_amp=True ### Set to True, if your GPU supports FP16 operations
)
预训练模型使用multilingual-mpnet-base-v2,经过微调训练,模型在测试数据上总体准确率为90.2%。
另外也尝试了其他损失函数,如 ProxyAnchorLoss ,此函数不是 sentence_transformers 中的损失函数,需要 在sentence_transformers安装包中存放损失函数的目录losses 中新建文件,封装该损失函数,之后可导入使用,使用方法与上述BatchSemiHardTripletLoss 相同。使用该损失函数训练模型,总体准确率提升1.1%至91.3%。