深度学习笔记(8)预训练模型
文章目录
- 深度学习笔记(8)预训练模型
- 一、预训练模型构建
- 一、微调模型,训练自己的数据
- 1.导入数据集
- 2.数据集处理方法
- 3.完形填空训练
- 使用分词器将文本转换为模型的输入格式
- 参数 return_tensors="pt" 表示返回PyTorch张量格式
- 执行模型预测
一、预训练模型构建
加载模型和之前一样,用别人弄好的
# 导入warnings模块,用于忽略后续代码中可能出现的警告信息
import warnings
# 设置warnings模块,使其忽略所有警告
warnings.filterwarnings("ignore")
# 从transformers库中导入AutoModelForMaskedLM类,该类用于预训练的掩码语言模型
from transformers import AutoModelForMaskedLM
# 指定模型检查点,这里使用的是distilbert-base-uncased模型
model_checkpoint = "distilbert-base-uncased"
# 使用from_pretrained方法加载预训练的模型,该方法将从指定的检查点加载模型
model = AutoModelForMaskedLM.from_pretrained(model_checkpoint)
咱们的任务就是去预测MASK到底是个啥
text = "This is a great " # 定义一个文本字符串
# 从指定的模型检查点加载分词器
# model_checkpoint 是之前定义的模型检查点路径,用于加载与模型配套的分词器
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
# 使用分词器将文本转换为模型的输入格式
# 参数 return_tensors="pt" 表示返回PyTorch张量格式
inputs = tokenizer(text, return_tensors="pt")
# inputs 现在是一个包含模型输入的张量或字典,可以用于模型推理
{'input_ids': tensor([[ 101, 2023, 2003, 1037, 2307, 103, 1012, 102]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1]])}
下面的代码可以看到mask的id是103
tokenizer.mask_token_id
103
一、微调模型,训练自己的数据
1.导入数据集
from datasets import load_dataset # 从datasets库中导入load_dataset函数
imdb_dataset = load_dataset("imdb") # 使用load_dataset函数加载IMDB数据集
这段代码是使用 datasets 库来加载 IMDB 数据集。IMDB 数据集是一个用于情感分析的经典数据集,包含了两类电影评论:正面和负面。
本身是带标签的 正面和负面
- 0 表示negative
- 1表示positive
先查看下数据集的数据
sample = imdb_dataset["train"].shuffle(seed=42).select(range(3)) # 从训练数据中随机选择3个样本
for row in sample:
print(f"\n'>>> Review: {row['text']}'") # 打印样本的文本内容
print(f"'>>> Label: {row['label']}'") # 打印样本的标签
其中一个如下
'>>> Review: This movie is a great. The plot is very true to the book which is a classic written by Mark Twain. The movie starts of with a scene where Hank sings a song with a bunch of kids called "when you stub your toe on the moon" It reminds me of Sinatra's song High Hopes, it is fun and inspirational. The Music is great throughout and my favorite song is sung by the King, Hank (bing Crosby) and Sir "Saggy" Sagamore. OVerall a great family movie or even a great Date movie. This is a movie you can watch over and over again. The princess played by Rhonda Fleming is gorgeous. I love this movie!! If you liked Danny Kaye in the Court Jester then you will definitely like this movie.'
'>>> Label: 1'
但是我们要做完形填空,标签是没用的
2.数据集处理方法
这里文本长度要统一
- 计算每一个文本的长度(word_ids)
- 指定chunk_size,然后将所有数据按块进行拆分,比如每块128个,句子是700字节,要分成128,128,。。。。这种
先定义个函数,这样下面使用可以直接调用
def tokenize_function(examples):
# 调用分词器(tokenizer)的函数,传入输入的文本数据集
result = tokenizer(examples["text"])
# 如果分词器支持快速模式,则生成单词索引(word_ids)
if tokenizer.is_fast:
result["word_ids"] = [result.word_ids(i) for i in range(len(result["input_ids"]))]
# 返回转换后的结果
return result
进行文本处理
tokenized_datasets = imdb_dataset.map(
tokenize_function, # 应用tokenize_function函数到每个样本
batched=True, # 是否将数据分成批次处理
remove_columns=["text", "label"] # 要从数据集中移除的列
)
首先,imdb_dataset.map() 方法被用来应用 tokenize_function 函数到 imdb_dataset 的 train 部分。这个方法会对数据集中的每个样本应用指定的函数,并返回一个新的数据集,其中包含应用函数后的结果。
batched=True 参数告诉 map 方法将输入数据分成批次进行处理。这通常是为了提高效率,尤其是在处理大型数据集时。
remove_columns=[“text”, “label”] 参数告诉 map 方法在处理数据时移除指定的列。在这个例子中,它移除了 text 和 label 列,因为 text 列已经被处理为模型的输入,而 label 列不再需要,因为咱们是完形填空任务,不需要标签。
然后进行切分
tokenizer.model_max_length
chunk_size = 128
tokenizer.model_max_length 是一个属性,它表示模型能够接受的最大输入长度。这个属性通常用于序列标注任务,以确保输入的长度不超过模型的最大接受长度。
chunk_size 是一个参数,用于指定将输入序列分割成小块的大小。这个参数通常用于处理过长的输入序列,以便将其分割成多个小块,然后分别处理这些小块。
因为上限是512 所以你切分要是64 128 256这种
切分的时候也可以先看下文本长度
# 看看每一个都多长
tokenized_samples = tokenized_datasets["train"][:3]
for idx, sample in enumerate(tokenized_samples["input_ids"]):
print(f"'>>> Review {idx} length: {len(sample)}'")
'>>> Review 0 length: 363'
'>>> Review 1 length: 304'
'>>> Review 2 length: 133'
这里看出128切分比较合适
先拿着三个试试
concatenated_examples = {
k: sum(tokenized_samples[k], []) for k in tokenized_samples.keys()#计算拼一起有多少个,
}
total_length = len(concatenated_examples["input_ids"])
print(f"'>>> Concatenated reviews length: {total_length}'")
'>>> Concatenated reviews length: 800'
下面进行切分
chunks = {
# 使用字典推导式(dict comprehension)创建一个新的字典,其中键是序列的键(k),值是分割后的块
k: [t[i : i + chunk_size] for i in range(0, total_length, chunk_size)]
# 遍历concatenated_examples字典中的每个键值对
for k, t in concatenated_examples.items()
}
for chunk in chunks["input_ids"]:
# 打印每个chunk的长度
print(f"'>>> Chunk length: {len(chunk)}'")
'>>> Chunk length: 128'
'>>> Chunk length: 128'
'>>> Chunk length: 128'
'>>> Chunk length: 128'
'>>> Chunk length: 128'
'>>> Chunk length: 128'
'>>> Chunk length: 32'
最后那个不够数,直接给他丢弃
def group_texts(examples):
# 将所有的文本实例拼接到一起
concatenated_examples = {k: sum(examples[k], []) for k in examples.keys()}
# 计算拼接后的总长度
total_length = len(concatenated_examples[list(examples.keys())[0]])
# 使用整除运算符(//)计算每个chunk的长度,然后乘以chunk_size,以确保不会出现多余的文本
total_length = (total_length // chunk_size) * chunk_size
# 根据计算出的总长度,对拼接后的文本进行切分
result = {
# 使用字典推导式(dict comprehension)创建一个新的字典,其中键是原始字典的键(k),值是切分后的块
k: [t[i : i + chunk_size] for i in range(0, total_length, chunk_size)]
# 遍历concatenated_examples字典中的每个键值对
for k, t in concatenated_examples.items()
}
# 如果完型填空任务需要使用标签,则将标签复制到结果字典中
result["labels"] = result["input_ids"].copy()
# 返回分割后的结果
return result
使用整除运算符(//)计算每个chunk的长度,然后乘以chunk_size这个就是说如果小于128 整除后就得0,就没了
train: Dataset({
features: ['input_ids', 'attention_mask', 'word_ids', 'labels'],
num_rows: 61291
})
test: Dataset({
features: ['input_ids', 'attention_mask', 'word_ids', 'labels'],
num_rows: 59904
})
unsupervised: Dataset({
features: ['input_ids', 'attention_mask', 'word_ids', 'labels'],
num_rows: 122957
})
})
会发现数据量大了
3.完形填空训练
from transformers import DataCollatorForLanguageModeling # 从transformers库中导入DataCollatorForLanguageModeling类
data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm_probability=0.15) # 创建一个数据收集器
然后再举个例子看看mask长啥样
samples = [lm_datasets["train"][i] for i in range(2)] # 从训练数据中选择2个样本
for sample in samples:
_ = sample.pop("word_ids") # 移除样本中的"word_ids"键,因为发现没啥用
print(sample)
for chunk in data_collator(samples)["input_ids"]:
print(f"\n'>>> {tokenizer.decode(chunk)}'")#tokenizer.decode(chunk)是为了让被隐藏的更明显点,所以直接decode出来
print(len(chunk))
训练过程
from transformers import TrainingArguments # 从transformers库中导入TrainingArguments类
batch_size = 64 # 定义训练和评估时的批次大小
# 计算每个epoch打印结果的步数
logging_steps = len(downsampled_dataset["train"]) // batch_size
model_name = model_checkpoint.split("/")[-1] # 从模型检查点路径中提取模型名称
training_args = TrainingArguments(
output_dir=f"{model_name}-finetuned-imdb", # 指定输出目录,其中包含微调后的模型和日志文件
overwrite_output_dir=True, # 是否覆盖现有的输出目录
evaluation_strategy="epoch", # 指定评估策略,这里为每个epoch评估一次
learning_rate=2e-5, # 学习率
weight_decay=0.01, # 权重衰减系数
per_device_train_batch_size=batch_size, # 每个GPU的训练批次大小
per_device_eval_batch_size=batch_size, # 每个GPU的评估批次大小
logging_steps=logging_steps, # 指定每个epoch打印结果的步数
num_train_epochs=1, # 指定训练的epoch数量
save_strategy='epoch', # 指定保存策略,这里为每个epoch保存一次模型
)
生成的模型
from transformers import Trainer # 从transformers库中导入Trainer类
trainer = Trainer(
model=model, # 指定要训练的模型实例
args=training_args, # 指定训练参数对象
train_dataset=downsampled_dataset["train"], # 指定训练数据集
eval_dataset=downsampled_dataset["test"], # 指定评估数据集
data_collator=data_collator, # 指定数据收集器
)
评估标准使用困惑度
import math # 导入math模块,用于计算对数和指数
eval_results = trainer.evaluate() # 使用Trainer的evaluate方法评估模型
print(f">>> Perplexity: {math.exp(eval_results['eval_loss']):.2f}") # 打印评估结果中的对数
人话就是你不得在mask那挑啥词合适吗,平均挑了多少个才能答对
训练模型
trainer.train()
eval_results = trainer.evaluate()
print(f">>> Perplexity: {math.exp(eval_results['eval_loss']):.2f}")
发现困惑度降低了
from transformers import AutoModelForMaskedLM
model_checkpoint = "./distilbert-base-uncased"
model = AutoModelForMaskedLM.from_pretrained("./distilbert-base-uncased-finetuned-imdb/checkpoint-157")
加载自己的模型
import torch # 导入torch库,用于处理张量
使用分词器将文本转换为模型的输入格式
参数 return_tensors=“pt” 表示返回PyTorch张量格式
inputs = tokenizer(text, return_tensors=“pt”)
执行模型预测
# model 是一个预训练的BERT模型实例
# **inputs 表示将inputs字典中的所有键值对作为关键字参数传递给model
token_logits = model(**inputs).logits
# 找到遮蔽词在输入中的索引
# inputs["input_ids"] 是模型输入的词汇索引张量
# tokenizer.mask_token_id 是遮蔽词的词汇索引
mask_token_index = torch.where(inputs["input_ids"] == tokenizer.mask_token_id)[1]
# 获取遮蔽词的预测logits
# mask_token_index 是遮蔽词在输入中的索引张量
# token_logits 是模型输出的预测logits张量
mask_token_logits = token_logits[0, mask_token_index, :]
# 找到前5个最可能的替换词
# torch.topk 函数用于找到最大k个值及其索引
# dim=1 表示在第二个维度(即词汇维度)上进行排序
top_5_tokens = torch.topk(mask_token_logits, 5, dim=1).indices[0].tolist()
# 打印每个最可能的替换词及其替换后的文本
for token in top_5_tokens:
# 使用 tokenizer.decode 方法将索引转换为文本
# text 是原始文本
# tokenizer.mask_token 是遮蔽词
# [token] 是替换词的索引列表
print(f"'>>> {text.replace(tokenizer.mask_token, tokenizer.decode([token]))}'")
'>>> This is a great deal.'
'>>> This is a great idea.'
'>>> This is a great adventure.'
'>>> This is a great film.'
'>>> This is a great movie.'
可以看到结果和上面的通用的不一样了,这里是film movie这些针对你的训练数据的了--------