前面欺诈文本分类微调(四):构造训练/测试数据集已经构造出了数据集,更之前的欺诈文本分类微调(一):基座模型选型选好了基座模型,这篇文章将基于构造出的数据集和选定的模型进行欺诈文本分类的微调训练。
关于微调方法,我们将使用比较普遍的Lora:在模型中注入低秩矩阵的方式。
关于训练器,使用transformers库中提供的Trainer类。
导入要使用的基础包。
import os
import json
import torch
from datasets import Dataset
import pandas as pd
from transformers import AutoTokenizer, AutoModelForCausalLM, DataCollatorForSeq2Seq, TrainingArguments, Trainer, EarlyStoppingCallback
from peft import LoraConfig, TaskType, get_peft_model
- AutoModelForCausalLM:用于加载模型
- AutoTokenizer:用于加载token分词器
- TrainingArguments:用于配置训练参数
- Trainer:用于训练模型
- EarlyStoppingCallback:用于提前结束训练,当评估损失不再下降时。
声明数据集和基座模型的路径,以及微调后模型参数的输出路径。
traindata_path = '/data2/anti_fraud/dataset/train0819.jsonl'
evaldata_path = '/data2/anti_fraud/dataset/eval0819.jsonl'
model_path = '/data2/anti_fraud/models/modelscope/hub/Qwen/Qwen2-1___5B-Instruct'
output_path = '/data2/anti_fraud/models/Qwen2-1___5B-Instruct_ft_0819_1'
定义工具函数load_jsonl用于加载数据集,并使用view_data_distribution查看数据集的标签分布。
def load_jsonl(path):
with open(path, 'r') as file:
data = [json.loads(line) for line in file]
return pd.DataFrame(data)
def view_data_distribution(data_path, show_first=False):
df = load_jsonl(data_path)
print(f"total_count:{
df.shape[0]}, true_count: {
df['label'].sum()}, false_count: {
(df['label']==False).sum()}")
print(json.dumps(df.iloc[0].to_dict(), indent=4, ensure_ascii=False)) if show_first else None
view_data_distribution(traindata_path, show_first=True)
total_count:18787, true_count: 9377, false_count: 9410
{
"input": "发言人3: 现在我所在这个哪里能够工艺能够去把屈光做得很好的,去到这个省级医院是自治区医院跟广西医科大学这个附属医院他们还可以,他们一直保持比较好的一个一个手术量。\n发言人1: 就是",
"label": false,
"fraud_speaker": "",
"instruction": "\n下面是一段对话文本, 请分析对话内容是否有诈骗风险,以json格式输出你的判断结果(is_fraud: true/false)。\n"
}
如上所示,原始的训练数据是文本形式,而模型推理需要的输入是数字,这中间需要用tokenizer进行文本到数字的序列化转换。
每个语言模型内部都维护了一个词表,里面维护了模型认识的所有词与数字编号的映射,不同模型的词表是不一样的,我们需要使用基座模型所对应的词表来创建tokenizer。
Tokenizer是一个词元生成器,它首先通过分词算法将文本切分成独立的token列表,再通过词表映射将每个token转换成语言模型可以处理的数字。详情见语言模型解构——Tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_path, use_fast=False, trust_remote_code=True)
tokenizer
Qwen2Tokenizer(name_or_path='/data2/anti_fraud/models/modelscope/hub/Qwen/Qwen2-1___5B-Instruct', vocab_size=151643, model_max_length=32768, is_fast=False, padding_side='right', truncation_side='right', special_tokens={'eos_token': '<|im_end|>', 'pad_token': '<|endoftext|>', 'additional_special_tokens': ['<|im_start|>', '<|im_end|>']}, clean_up_tokenization_spaces=False), added_tokens_decoder={
151643: AddedToken("<|endoftext|>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),
151644: AddedToken("<|im_start|>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),
151645: AddedToken("<|im_end|>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),
}
上面这个tokenizer的输出信息显示:词表中共有151643个词元,这个模型支持最大32KB的序列长度,并且还定义了开始标记<|im_start|>、结束标记<|im_end|>、填充标记<|endoftext|>,这些特殊token需要在数据预处理时被正确的添加到文本中。
我们尝试用这个tokenizer序列化一个简单文本看看序列化后的数据长什么模样。
tokenizer("你是谁")
{'input_ids': [105043, 100165], 'attention_mask': [1, 1]}
input_ids就是你是谁
序列化后成token列表后的数字形式,attention_mask是一个与input_ids长度相同的数组,用于指示模型应该关注哪些token,以及忽略哪些token,填充(padding)token在模型推理时通常应该被忽略。
注:attention_mask的值通常为0或1,1表示该位置的token是有效的输入(模型应该关注这个token), 0表示该位置的token是填充(padding),模型在处理时应忽略此token。
定义输入文本的预处理函数,作用是按模型的输入要求将输入文本转换为输入、掩码、标签三个序列。
def preprocess(item, tokenizer, max_length=2048):
system_message = "You are a helpful assistant."
user_message = item['instruction'] + item['input'