- Published on
使用imdb数据集对distilbert-base-uncased进行全量微调的情感分类模型
- Authors

- Name
- 卢翔宇
- @y9840836216317
了解了dataset库的基本使用
获取数据集的基本信息
get_dataset_split_names("ShengbinYue/DISC-Law-SFT"), \
get_dataset_split_names("cornell-movie-review-data/rotten_tomatoes")
# 根据split_names返回的列表信息,可以在load_dataset指定split参数来加载特定数据集
(['train'], ['train', 'validation', 'test'])
configs1 = get_dataset_config_names("ShengbinYue/DISC-Law-SFT")
configs2 = get_dataset_config_names("PolyAI/minds14")
# 可以在load_data中指定name参数来加载特定的子数据集
(['all', 'cs-CZ', 'de-DE', 'en-AU', 'en-GB', 'en-US', 'es-ES', 'fr-FR', 'it-IT', 'ko-KR', 'nl-NL', 'pl-PL', 'pt-PT', 'ru-RU', 'zh-CN'], ['default'])
加载数据集
dataset1 = load_dataset("json", data_files="DISC-Law-SFT/DISC-Law-SFT-Pair-QA-released.jsonl", split="train")
对数据集进行操作
类似于对列表的操作,比如切片,索引之类的
dataset[0]
dataset[-1]
dataset[3:6]
dataset['input'][1]
创建迭代数据集
iterable_dataset = load_dataset("json", data_files="DISC-Law-SFT/DISC-Law-SFT-Pair-QA-released.jsonl", split="train", streaming=True)
#iterable_dataset = dataset1.to_iterable_dataset():这样创建也行
for example in iterable_dataset:
print(example)
break
{'id': 'legal_question_answering_0', 'input': '违章停车与违法停车是否有区别?', 'output': '对违反道路交通安全法律、法规关于机动车停放、临时停车规定的,可以指出违法行为,并予以口头警告,令其立即驶离。机动车驾驶人不在现场或者虽在现场但拒绝立即驶离,妨碍其他车辆、行人通行的处二十元以上二百元以下罚款。现在人们大多是称作违法停车,因此在法律责任上也会更多一些,不要以为违反交通规章制度问题不大,不要认为违法停车是罚款而已。'}
可迭代数据集它不像 Dataset 那样预先处理和存储所有数据。相反,它基于一个 Python 的迭代器 (iterator) 或生成器 (generator)。当你开始遍历 IterableDataset 时(例如在一个 for 循环中),它才会调用底层的生成器函数,逐一地生成或拉取数据。适合在超大数据集和流式数据(比如不断增长的服务器日志)以及复杂的动态数据采样策略中使用。
对迭代数据集中的数据进行操作
# “获取 iterable_dataset 这个可迭代对象的第一个元素。”
# iterable_dataset:可迭代对象 (Iterable)
# iter(...):获取迭代器 (Iterator)
# next(...):从迭代器中取下一个元素
next(iter(iterable_dataset)), \
next(iter(iterable_dataset['id']))
({'id': 'legal_question_answering_0', 'input': '违章停车与违法停车是否有区别?', 'output': '对违反道路交通安全法律、法规关于机动车停放、临时停车规定的,可以指出违法行为,并予以口头警告,令其立即驶离。机动车驾驶人不在现场或者虽在现场但拒绝立即驶离,妨碍其他车辆、行人通行的处二十元以上二百元以下罚款。现在人们大多是称作违法停车,因此在法律责任上也会更多一些,不要以为违反交通规章制度问题不大,不要认为违法停车是罚款而已。'}, 'legal_question_answering_0')
# 获取子集
list(iterable_dataset.take(3))
# list(iterable_dataset.take(3:6))迭代数据集不能这样进行这样切片
打印出来的内容是数据集的前三项
利用transformers库进行模型读取和分词
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
model_id = "./Qwen3-4B-Instruct-2507"
# 加载模型
# 特定的模型有特定的加载器,比如Qwen3是文笔生成模型,就用AutoModelForCausalLM,别的类型的模型像是分类之类的就用别的加载器
model = AutoModelForCausalLM.from_pretrained(
model_id,
device_map="auto",
quantization_config=BitsAndBytesConfig(load_in_8bit=True),
# 原模型的参数是16位的版本,这里为了减小硬件的压力使用BitsAndBytesConfig把16位精度的参数转化为8位
)
# 加载模型对应的分词器
tokenizer = AutoTokenizer.from_pretrained(model_id)
# 调用分词器进行分词
tokenizer(dataset1[0]['input'])
{'input_ids': [114952, 101531, 57218, 100872, 101531, 107189, 102665, 11319], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1]}
# 对整个数据集进行分词
def encode(examples):
return tokenizer(examples["input"], examples["output"], truncation=True, padding="max_length")
dataset1 = dataset1.map(encode, batched=True, batch_size=16) # 设置较小的batch_size,以减少并发处理的数据量,进而防止爆显存
利用map函数可以批量对数据集进行分词,但这个操作很吃显存......今天没跑成
了解了fastapi如何设置基本请求
class Item(BaseModel):
name: str
price: float
is_offer: Union[bool, None] = None
@app.put("/items/{item_id}") # 使用注解器声明请求类型
async def update_item(item_id: int, item: Item):
return {"item_price": item.price, "item_id": item_id}
代码中的Item继承了Pydantic中的BaseModel,这样做可以让FastAPI 利用类型提示 (item_id: int) 自动进行数据转换和验证,比如我的URL是/items/55,其中55是字符型变量,pydantic可以自动把这个字符变量转化为整型
fastapi拥有/docs和/redoc,/docs是一个很强大的工具,有点类似postman,不过是fastapi内置的,可以通过docs发送请求,直接在网页端完成测试。 redoc更像是一个简化版的docs,它像是一个api手册,不包含发送请求的功能
下面是微调过程中发现的一些问题: 使用map函数对数据集进行批量分词时要注意分词的对象必须是文本,如果是标签或者其他类型的数据的话就会报错
def tokenize_dataset(dataset):
return tokenizer(dataset["text"], padding="max_length", truncation=True) # 只有文本需要分词,标签是数字所以不能进行分词
dataset = dataset.map(tokenize_dataset)
相比于[[2025-10-2]],今天新增了训练的过程
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
training_args = TrainingArguments(
output_dir="./distilbert-imdb-classify",
learning_rate=2e-5,
per_device_train_batch_size=8,
per_device_eval_batch_size=8,
num_train_epochs=2,
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=dataset["train"],
eval_dataset=dataset["test"],
processing_class=tokenizer,
data_collator=data_collator,
)
TrainingArguments用于设置模型的超参数,对于微调任务来说epochs为2足矣,DataCollatorWithPadding主要用于填充,可以传入相对于的分词器来确定填充规则 trainer.train()用于开始训练模型 trainer.evaluate()用于评估模型在测试集上的表现
训练结果 6250/6250 21:45, Epoch 2/2]
| Step | Training Loss |
|---|---|
| 500 | 0.373200 |
| 1000 | 0.314200 |
| 1500 | 0.300600 |
| 2000 | 0.278300 |
| 2500 | 0.265100 |
| 3000 | 0.255000 |
| 3500 | 0.203500 |
| 4000 | 0.172700 |
| 4500 | 0.151400 |
| 5000 | 0.159600 |
| 5500 | 0.161600 |
| 6000 | 0.160800 |
| TrainOutput(global_step=6250, training_loss=0.2303094775390625, metrics={'train_runtime': 1306.3977, 'train_samples_per_second': 38.273, 'train_steps_per_second': 4.784, 'total_flos': 6623369932800000.0, 'train_loss': 0.2303094775390625, 'epoch': 2.0})更多训练的结果数据可以通过向Trainer中设置某个参数来实现 | |
| 评估结果 |
3125/3125 03:08
{'eval_loss': 0.28910955786705017, 'eval_runtime': 188.3729, 'eval_samples_per_second': 132.715, 'eval_steps_per_second': 16.589, 'epoch': 2.0}
评估的结果和训练结果的损失值接近,模型拟合程度较好,这里除了损失值还有一些其他参数可以衡量模型性能
保存并使用模型
trainer.save_model("./my_sentiment_classifier")
tokenizer.save_pretrained("./my_sentiment_classifier")
mymodel = AutoModelForSequenceClassification.from_pretrained("./my_sentiment_classifier")
mytokenizer = AutoTokenizer.from_pretrained("./my_sentiment_classifier")
myclassifier = pipeline("sentiment-analysis", model=mymodel, tokenizer=mytokenizer)
results = myclassifier(["I like it", "I hate this movie", "I hate it", "what the fuck", "I hate this", "shit film"])
Device set to use cuda:0
[{'label': 'LABEL_1', 'score': 0.9973964691162109}, {'label': 'LABEL_0', 'score': 0.9931268692016602}, {'label': 'LABEL_1', 'score': 0.5851984620094299}, {'label': 'LABEL_0', 'score': 0.9392573833465576}, {'label': 'LABEL_0', 'score': 0.9865531325340271}, {'label': 'LABEL_0', 'score': 0.9954352974891663}]
这里不知道为什么对“I hate it”预测错了