NLP任务微调笔记

NLP数据集没有像CV一样大量标号的数据集,所以NLP一般是自监督的,有两种自监督的模型:

  • LM:语言模型,预测下一个词

  • MLM:带掩码的语言模型,完形填空

每种预训练模型基于不同的技术也分为两种:

  • Word embddings:

    • LSTM之类的传统的
  • Transformer based pretrained model:

    • Bert:基于encoder,针对Bert的微调,把最后一层重新设计随机初始化,做分类只需要拿出一个然后训练,做QA只需要输出答案的位置
    • GPT:基于decoder
    • T5:基于encoder和decode

如何做分词

数据集一般是用Unicode进行编码,是对世界所有语言的一种编码,但是我们需要将这些文字变成数字编码的向量,就需要分词

1
"我是一个用于测试输出unicode编码的文字".encode('utf-8')#可以看到每个字对应的unicode编码 

分词Tokenizer:就是把文字变成数字,数字就是token,每个词变成一个编号

Tokenizer分类

Tokenizer有多种,其中subword-based是现在最常用的

  1. Word-based Toeknizers:把每个词一一对应分开,然后将例如“don‘t”引入特殊规则划分为“do”和“not”,这就导致虽然符合人的自然语言知觉,但是规则太多,此表很大,所以一般限制到一万个词,其余的词标记为未知
  2. Character-baesd :把一个单词变成一个个字母,love变成l,o,v,e,这样token序列长,单个token信息量低模型性能差,中文的词表更长。
  3. subword-based Tokenizer:这个合理,dog就是dog,但是dogs为dog+s。tokenization变成token和ization

常用的subword tokenizer

然后subword tokenizer也分为很多类

  1. Byte-pair encoding(GPT使用的p50k):BPE方法,首先进行词频统计,然后构建基本此表,根据词表切分词,统计相邻token同时出现的频率,取出新的频率高的组合token替换原先的小token,这样就可以构成新的词表,最后就会获得一个充满高频词单元的词表。看视频21分了解。有个gihub仓库miniBPE不错。同时还有一些出现也很高但是组合到一起没有意义的,那可以用加个正则表达删除这种组合
  2. Btye-level BPE(GPT2使用的):BPE的词表太大,改进的方法将字节byte看作基本token
  3. wordpiece:和BPE类似,每个单词除了第一个字母都会加一个#作为前缀,然后利用联合概率进行token合并,反正就更科学了
  4. sentencePiece(google):
  5. unigram(Bigbird,T5,XLNet):初始化一个很大的词表,尝试删减然后计算unigram loss,然后使unigramloss下降的不大就山调,逐步删减

每个模型都有对应的分词器,每个模型的tokenizer不一样,不能随便换,可查看https://huggingface.co/spaces/Xenova/the-tokenizer-playground看tokenizer的分词效果

image-20250303114108026

构建词表

词表(Vocabulary)是通过使用 Tokenizer 对训练语料进行处理后获得的词(或子词、字符等)与 token 的对应关系。词表中已经定义了 token 与 ID 的映射关系。

如果你的模型微调任务中已经有一个预训练的 对应Tokenizer,那么这个 Tokenizer 其实包含了对应的词表,那么不需要重新构建词表!

预训练的 Tokenizer 通常是在大规模语料上训练的,已经包含了足够大的词表(例如 30,000 到 100,000 个 token)。

如果你想训练一个新模型,但是没有tokenizer,那么就可以使用词表下面介绍怎么使用词表:

  • 使用预训练Embedding的词表,如Glove,word2Vec
    • GloVe 的词表主要用于训练词向量(word embeddings),而不是直接用于模型的输入。
  • 使用数据集统计词表:使用from_dataset函数从数据集构建一个Vocab词表
  • 使用Subword训练得到的词表,直接就获得了,不需要训练获得词表

如何使用别人训练的词表word2Vec生成input

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from gensim.models import KeyedVectors
# 加载预训练的 Word2Vec 模型
word2vec_model = KeyedVectors.load_word2vec_format("path/to/word2vec.bin", binary=True)
# 获取词表
vocab = word2vec_model.key_to_index # 单词到索引的映射
word_vectors = word2vec_model.vectors # 词向量矩阵
def tokenize(text):
# 简单的空格分词,转换为小写
return text.lower().split()python

# 示例数据集
data = [
"I love NLP.",
"This is a test sentence.",
"How are you?"
]
# 对数据集中的每个句子进行分词
tokenized_data = [tokenize(text) for text in data]
print(tokenized_data)

import numpy as np
# 获取词向量的维度
embedding_dim = word_vectors.shape[1]
# 将单词映射到词向量
def map_tokens_to_vectors(tokens, vocab, word_vectors, embedding_dim):
vectors = []
for token in tokens:
if token in vocab:
# 如果单词在词表中,获取对应的词向量
vectors.append(word_vectors[vocab[token]])
else:
# 如果单词不在词表中,使用全零向量
vectors.append(np.zeros(embedding_dim))
return np.array(vectors)

# 对分词后的数据进行映射
vectorized_data = [map_tokens_to_vectors(tokens, vocab, word_vectors, embedding_dim) for tokens in tokenized_data]

如何embedding

embedding:做完分词后获得是一堆数字,embedding现在都成为transformer算法,不用自己来写这部分数据预处理的一部分,所以不用写了

Embedding layer的作用是将离散的 token ID 映射为连续的向量表示。这些向量通常是可训练的,模型会在训练过程中学习如何将每个 token ID 映射到一个有意义的向量空间中。比如说一个句子里的一个词的向量 是 这个词的token + 词的位置编码

image-20250303113929963

将数据变为数据集(太重点的)

AI需要的是问答对数据集,也就是一个数据集里要有问题和答案

1
2
3
4
5
{
‘instruction’:这里要有问题
‘input’:这里要有一个数据,
‘output’:这里需要获得输出是什么
}

有各种各样的数据集,然后需要用在不同的任务中,其中任务可以包括以下几种,基础模型都是bigbirdpegasus,但是是用于不同任务的

任务类型 数据需要包含的集列
BigBirdPegasusForConditionalGeneration input_text, target_text
BigBirdPegasusForSequenceClassification input_text, label
BigBirdPegasusForQuestionAnswering context, question, answer
BigBirdPegasusDecoderWrapper input_text, target_text
BigBirdPegasusForCausalLM因果语言模型(CLM) input_text, target_text,下一个词是target_text
Masked Language Modeling掩码语言模型(MLM,比如bert) input_text, target_text,缺少的词是target_text

然后每一种任务的模型需要不同类型的数据集进行训练,需要数据集有对应不同的columes,比如说BigBirdPegasusForConditionalGeneration这个条件生成任务的模型需要数据集包括输入文本(input text)和目标文本(target text),具体格式如下。

1
2
3
4
data = [
{"input_text": "The quick brown fox jumps over the lazy dog.", "target_text": "The fox is quick."},
{"input_text": "In a galaxy far, far away...", "target_text": "A long time ago..."},
]

有一些数据集是已经针对这些任务特化好的,比如说SQuAD (Stanford Question Answering Dataset)是针对ConditionalGeneration任务的,不过需要将这个数据集变成我们需要的格式。SQuAD 数据集中的一个例子如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"data": [
{
"title": "Super_Bowl_50",
"paragraphs": [
{
"context": "Super Bowl 50 was an American football game...",
"qas": [
{
"question": "Where did Super Bowl 50 take place?",
"id": "56be4db0acb8001400a502ec",
"answers": [
{
"text": "Santa Clara, California",
"answer_start": 269
}
]
}
]
}
]
}
]
}

databricks-dolly-15k数据集是针微调任务的,牛逼吧,专门用于微调各种任务的,可以用于问答、分类、摘要各种任务,只需要你自己构造好。数据格式如下:

按照你的需要变成不同的格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
DatasetDict({
train: Dataset({
features: ['instruction', 'context', 'response', 'category'],
num_rows: 15011
})
})
# instruction 用户的指令或任务描述(例如:“写一篇关于气候变化的短文。”)。
# context 可选的上下文信息,为模型提供额外的背景知识(例如:“气候变化是全球变暖的主要原因。”)。
# response 模型应生成的理想响应(例如:“气候变化是当今世界面临的最大挑战之一...”)。。
{
"instruction": "写一篇关于气候变化的短文。",
"context": "气候变化是全球变暖的主要原因。",
"response": "气候变化是当今世界面临的最大挑战之一。全球变暖导致极端天气事件频发,海平面上升,生态系统受到严重威胁。为了应对气候变化,各国需要采取紧急措施,减少温室气体排放,推动可持续发展。"
}

但是还有一些数据集是非特化的,需要自己构建成需要的数据集格式,比如wikitext只是wiki上的文字一段一段的,这种就根需要自己对数据集进行特化处理

1
{'text': " The ship was assigned to the Austro @-@ Hungarian Fleet 's 1st Battle Squadron after her 1911 commissioning . In 1912 , Zrínyi and her two sister ships conducted two training cruises into the eastern Mediterranean Sea . On the second cruise into the Aegean Sea , conducted from November to December , Zrínyi and her sister ships were accompanied by the cruiser SMS Admiral Spaun and a pair of destroyers . After returning to Pola , the entire fleet mobilized for possible hostilities , as tensions flared in the Balkans . \n"}

数据集特化处理工具

我们完全可以自己把一个非特化的数据集转化为特化的数据集,比如把wikitext的每一句话拿出来,然后一句话的最后一个词就是要获得的结果,作为target_ids。但是有更好的工具可以直接把数据集转化为想要的特化的数据集比如dataset中的**DataCollatorForLanguageModeling**

DataCollatorForLanguageModeling** 是 Hugging Face 提供的一个工具,专门用于处理语言模型训练数据。它可以自动将数据集(如 Wikitext-103-v1)划分为 input_idsattention_masklabels,并且支持因果语言模型(Causal Language Model, CLM)和掩码语言模型(Masked Language Model, MLM)的训练。

DataCollatorForLanguageModeling 的作用

  1. 动态填充:将批次中的序列填充到相同长度。
  2. 生成 labels:对于因果语言模型,labelsinput_ids 的偏移版本(即下一个 token 的预测目标)。
  3. 支持 MLM:如果你使用的是掩码语言模型(如 BERT),它还可以随机掩码 token。
  4. 除了生成input_ids和target_ids之外,还会生成attention_mask ,指示哪些 token 是实际数据,哪些是填充 token。

DataCollatorForLanguageModeling的工作原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 首先已经有两句连续的话了
# "The quick brown fox"
# "Jumps over the lazy dog"
# CLM模型的作用是根据之前的文字生成下一个文字
# 首先经过tokenizer
input_ids_1 = [1996, 4248, 2829, 4419] # "The quick brown fox"
input_ids_2 = [12987, 345, 1996, 1234, 2345] # "Jumps over the lazy dog"
# 然后经过datacollatrorForlanguageodeling之后,-100是用来填充的,可以看到,这样label,也就是target_ids的变化是预测的下一句话
input_ids = [
[1996, 4248, 2829, 4419, -100], # "The quick brown fox"
[12987, 345, 1996, 1234, 2345] # "Jumps over the lazy dog"
]

labels = [
[4248, 2829, 4419, -100, -100], # 预测下一个 token
[345, 1996, 1234, 2345, -100] # 预测下一个 token
]

下面讲解DataCollatorForLanguageModeling` 怎么调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
from datasets import load_dataset
# 加载 Wikitext 数据集
dataset = load_dataset("wikitext", "wikitext-103-v1")
# 过滤空行
dataset = dataset.filter(lambda x: x["text"].strip() != "")
from transformers import DataCollatorForLanguageModeling
tokenizer = AutoTokenizer.from_pretrained("gpt2")
# 对数据集进行 Tokenization
def tokenize_function(examples):
return tokenizer(examples["text"], truncation=True, max_length=512)
tokenized_dataset = dataset.map(tokenize_function, batched=True, remove_columns=["text"])
# 初始化 DataCollator
data_collator = DataCollatorForLanguageModeling(
tokenizer=tokenizer,
mlm=False, # 使用因果语言模型
return_tensors="pt" # 返回 PyTorch 张量
)

# 然后按理说可以用于训练,但是这里讲三个方式

# 1. 创建 DataLoader,这个DataLoader的作用是为了传入模型使用的,dataloader需要将数据划分为不同的batch,并且打乱顺序等
from torch.utils.data import DataLoader
train_loader = DataLoader(
tokenized_dataset["train"],
batch_size=8,
collate_fn=data_collator,
shuffle=True
)

# 2. 可以不使用 DataLoader,直接将 Tokenizer 处理后的数据使用 DataCollatorForLanguageModeling 转换为模型训练所需的格式,然后进行训练。不过,这种方式通常适用于小规模数据集或自定义训练循环的场景。
batch_size = 8
batches = []
for i in range(0, len(tokenized_dataset["train"]), batch_size):
batch = tokenized_dataset["train"][i:i + batch_size]
batches.append(data_collator(batch["input_ids"]))
optimizer = AdamW(model.parameters(), lr=5e-5)
for epoch in range(3): # 训练 3 个 epoch
for batch in batches:
outputs = model(input_ids=batch["input_ids"], labels=batch["labels"])
loss = outputs.loss
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(f"Loss: {loss.item()}")

# 3. 如果你需要微调,也可以不使用dataloader,直接将DataCollatorForLanguageModeling处理过的数据传入trainer即可

除了 DataCollatorForLanguageModeling,huggingface的transformer还有许多其他 DataCollator 工具,可以根据任务需求将数据集转换为模型训练所需的格式。以下是一些常用的 DataCollator 及其适用场景:详细可看这个各种datacollator

DataCollator 类型 适用任务
DataCollatorWithPadding 变长序列任务(如文本分类)。
DataCollatorForTokenClassification 序列标注任务(如命名实体识别)。
DataCollatorForSeq2Seq 序列到序列任务(如机器翻译)。
DataCollatorForSOP 句子顺序预测任务(如 ALBERT)。
DataCollatorForWholeWordMask 全词掩码任务(如 BERT)。
DataCollatorForPermutationLanguageModeling 排列语言模型任务(如 XLNet)。
DataCollatorForNextSentencePrediction 下一句预测任务(如 BERT)。
DataCollatorForImageCaptioning 图像描述生成任务。

也可以自己定义,继承DataCollator

Mindspore也有datacontrol的工具不过叫做mindspore.dataset.GeneratorDataset,并且需要自己定义,但是也没有transformers里面的工具智能

1
2
3
4
5
def data_generator():
for i in range(100):
yield (np.random.randn(32, 32, 3), np.random.randint(0, 10)

dataset = ds.GeneratorDataset(source=data_generator, column_names=["image", "label"])

除此之外mindspore和mindNLP只支持一些基本操作比如map,padding之类的