文章目录
一、huggingface 简介
huggingface是一个开源社区,提供了统一的ai 研发框架、工具集、可在线加载的数据集仓库和预训练模型仓库。huggingface把研发大致分为以下几个部分:准备数据集,定义模型,训练,测试;每个部分都提供了相应的工具集

二、使用编码工具
2.1、编码⼯具⼯作流

2.1.1、定义字典
- ⽂字是⼀个抽象的概念,不是计算机擅长处理的数据单元,计算机擅长处理的是数字运算,所以需要把抽象的⽂字转换为数字,让计算机能够做数学运算。
- 为了把抽象的⽂字数字化,需要⼀个字典把⽂字或者词对应到某个数字。⼀个⽰意的字典如下:
# 字典:这只是⼀个⽰意的字典,所以只有 11 个词,在实际项⽬中的字典可能会有成千上万个词
vocab = {
'<sos>': 0, # start of seq
'<eos>': 1, # end of seq
'the': 2,
'quick': 3,
'brown': 4,
'fox': 5,
'jumps': 6,
'over': 7,
'a': 8,
'lazy': 9,
'dog': 10,
}
2.1.2、句子预处理
- 在句⼦被
分词之前,⼀般会对句⼦进⾏⼀些特殊的操作,例如把太长的句⼦截短,或在句⼦中添加⾸尾标识符等。
# 简单编码
sent = 'the quick brown fox jumps over a lazy dog'
sent = '<sos> ' + sent + ' <eos>'
print(sent) # <sos> the quick brown fox jumps over a lazy dog <eos>
2.1.3、分词
- 句⼦准备好了,接下来需要
把句⼦分成⼀个⼀个的词。对于中⽂来讲,这是个复杂的问题,但是对于英⽂来讲这个问题⽐较容易解决,因为英⽂有⾃然的分词⽅式,即以空格来分词,代码如下:
# 英⽂分词
words = sent.split()
print(words) # ['<sos>', 'the', 'quick', 'brown', 'fox', 'jumps', 'over', 'a', 'lazy', 'dog', '<eos>']
- 对于中⽂来讲,分词的问题⽐较复杂,因为中⽂所有的字是连在⼀起写的,不存在⼀个⾃然的分隔符号。有很多成熟的⼯具能够做中⽂分词,例如
jieba 分词、 ltp 分词等,但是在这里我们不会使⽤这些⼯具,因为huggingface的编码⼯具已经包括了分词这⼀步⼯作,由各个模型⾃⾏实现,对于调⽤者来讲这些⼯作是透明的,不需要关⼼具体的实现细节
2.1.4、编码
- 句⼦已按要求添加了⾸尾标识符,并且分割成了⼀个⼀个的单词,现在需要把这些抽象的单词映射为数字。因为已经定义好了字典,所以使⽤字典就可以把每个单词分别地映射为数字,代码如下:
# 编码为数字
encode = [vocab[i] for i in words]
print(encode) # [0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1]
2.2、编码工具的使用
2.2.1、基本编码函数
# 1、加载⼀个编码⼯具,这里使⽤ bert-base-chinese 的实现:在bert的实现中,中⽂分词处理⽐较简单,就是把每个字都作为⼀个词来处理
from transformers import berttokenizer
tokenizer = berttokenizer.from_pretrained(pretrained_model_name_or_path='bert-base-chinese',
cache_dir=none, force_download=false,)
# pretrained_model_name_or_path: 指定要加载的编码⼯具,⼤多数模型会把⾃己提交的编码⼯具命名为和模型⼀样的名字
# cache_dir: ⽤于指定编码⼯具的缓存路径,这里指定为none(默认值),也可以指定想要的缓存路径
# force_download: 为 true 时表明⽆论是否已经有本地缓存,都强制执⾏下载⼯作,建议设置为 false
# 2、准备实验数据
sents = [
'你站在桥上看⻛景',
'看⻛景的⼈在楼上看你',
'明⽉装饰了你的窗⼦',
'你装饰了别⼈的梦',
]
# 3、基本的编码函数
out = tokenizer.encode(
text=sents[0], # ⼀次编码⼀个或者⼀对句⼦,在这个例⼦中,编码了⼀对句⼦
text_pair=sents[1], # 如果只想编码⼀个句⼦,则可让 text_pair 传 none
truncation=true, # 当句⼦长度⼤于 max_length 时截断
padding='max_length', # 当句⼦长度不⾜ max_length 时,在句⼦的后⾯补充 pad(0),直到 max_length 长度
add_special_tokens=true, # 需要在句⼦中添加特殊符号,如逗号分隔符 sep
max_length=25, # 定义了 max_length 的长度
return_tensors=none, # 表明返回的数据类型为list格式,也可以赋值为 tf、pt、np,分别表⽰ tf、pytorch、numpy 数据格式
)
# 编码的输出为⼀个数字的list
print(out)
# 使⽤编码⼯具的decode()函数把这个list还原为分词前的句⼦,可看出编码工具对句⼦做了哪些预处理⼯作
print(tokenizer.decode(out))
# 运行结果如下:
[101, 872, 4991, 1762, 3441, 677, 4692, 7599, 3250, 102, 4692, 7599, 3250, 4368, 782, 1762, 3517, 677,
4692, 872, 102, 0, 0, 0, 0]
[cls] 你 站 在 桥 上 看 ⻛ 景 [sep] 看 ⻛ 景 的 ⼈ 在 楼 上 看 你 [sep] [pad] [pad] [pad] [pad]
[cls]=101
[sep]=102
[pad]=0
# 4、进阶的编码函数
out = tokenizer.encode_plus(
text=sents[0], # ⼀次编码⼀个或者⼀对句⼦,在这个例⼦中,编码了⼀对句⼦
text_pair=sents[1], # 如果只想编码⼀个句⼦,则可让 text_pair 传 none
truncation=true, # 当句⼦长度⼤于 max_length 时截断
padding='max_length', # 当句⼦长度不⾜ max_length 时,在句⼦的后⾯补充 pad(0),直到 max_length 长度
add_special_tokens=true, # 需要在句⼦中添加特殊符号,如逗号分隔符 sep
max_length=25, # 定义了 max_length 的长度
return_tensors=none, # 表明返回的数据类型为list格式,也可以赋值为 tf、pt、np,分别表⽰ tf、pytorch、numpy 数据格式
# 进阶参数
return_token_type_ids=true, # 因为编码的是两个句⼦,这个list⽤于表明编码结果中哪些位置是第1个句⼦,哪些位置是第2个句⼦。具体表现为,第2个句⼦的位置是1,其他位置是0
return_attention_mask=true, # ⽤于表明编码结果中哪些位置是 pad;pad 的位置是 0,其他位置是 1
return_special_tokens_mask=true, # ⽤于表明编码结果中哪些位置是特殊符号,具体表现为,特殊符号的位置是 1,其他位置是0
return_length=true, # 返回句子长度
)
# 返回一个字典
for k, v in out.items():
print(k, ':', v)
tokenizer.decode(out['input_ids'])



2.2.2、批量编码函数
#第2章/批量编码成对的句⼦
out = tokenizer.batch_encode_plus(
# 编码成对的句⼦,若需要编码的是⼀个⼀个的句⼦,则修改为 batch_text_or_text_pairs=[sents[0], sents[1]] 即可
batch_text_or_text_pairs=[(sents[0], sents[1]), (sents[2], sents[3])],
truncation=true, # 当句⼦长度⼤于 max_length 时截断
padding='max_length', # 当句⼦长度不⾜ max_length 时,在句⼦的后⾯补充 pad(0),直到 max_length 长度
add_special_tokens=true, # 需要在句⼦中添加特殊符号,如逗号分隔符 sep
max_length=25, # 定义了 max_length 的长度
return_tensors=none, # 表明返回的数据类型为list格式,也可以赋值为 tf、pt、np,分别表⽰ tf、pytorch、numpy 数据格式
# 进阶参数
return_token_type_ids=true, # 因为编码的是两个句⼦,这个list⽤于表明编码结果中哪些位置是第1个句⼦,哪些位置是第2个句⼦。具体表现为,第2个句⼦的位置是1,其他位置是0
return_attention_mask=true, # ⽤于表明编码结果中哪些位置是 pad;pad 的位置是 0,其他位置是 1
return_special_tokens_mask=true, # ⽤于表明编码结果中哪些位置是特殊符号,具体表现为,特殊符号的位置是 1,其他位置是0
return_length=true, # 返回句子长度
)
# input_ids 编码后的词
# token_type_ids 第1个句⼦和特殊符号的位置是0,第2个句⼦的位置是1
# special_tokens_mask 特殊符号的位置是1,其他位置是0
# attention_mask pad的位置是0,其他位置是1
# length 返回句⼦长度
for k, v in out.items():
print(k, ':', v)
tokenizer.decode(out['input_ids'][0])

2.2.3、向字典添加新词
# 获取字典
vocab = tokenizer.get_vocab()
print(type(vocab), len(vocab), '明⽉' in vocab) # (dict, 21128, false)
# 添加新词
tokenizer.add_tokens(new_tokens=['明⽉', '装饰', '窗⼦'])
# 添加新符号
tokenizer.add_special_tokens({'eos_token': '[eos]'})
# 编码新添加的词
out=tokenizer.encode(
text='明⽉装饰了你的窗⼦[eos]',
text_pair=none,
truncation=true,
padding='max_length',
add_special_tokens=true,
max_length=10,
return_tensors=none,
)
print(out)
tokenizer.decode(out)
# 输出如下:可以看到,“明⽉” 已经被识别为⼀个词,而不是两个词,新的特殊符号 [eos] 也被正确识别
[101, 21128, 21129, 749, 872, 4638, 21130, 21131, 102, 0]
'[cls] 明⽉ 装饰 了 你 的 窗⼦ [eos] [sep] [pad]'
三、使用数据集工具
3.1、数据集的加载和保存
# 1、在线加载数据集:由于 huggingface 把数据集存储在⾕歌云盘上,在国内加载时可能会遇到⽹络问题,可离线加载使用
from datasets import load_dataset
from datasets import load_from_disk
dataset = load_dataset(path='seamew/chnsenticorp')
print(dataset)
# 输出如下所示:
datasetdict({
train: dataset({
features: ['text', 'label'],
num_rows: 9600
})
validation: dataset({
features: ['text', 'label'],
num_rows: 0
})
test: dataset({
features: ['text', 'label'],})
num_rows: 1200
})
})
# 2、将数据集保存到本地磁盘
dataset.save_to_disk(dataset_dict_path='./data/chnsenticorp')
# 3、从磁盘加载数据集
dataset = load_from_disk('./data/chnsenticorp')
3.2、数据集基本操作
# 1、取出数据部分
dataset = dataset['train'] # 使⽤train数据⼦集做后续的实验
# 2、查看数据样例
for i in [12, 17, 20, 26, 56]:
print(dataset[i])
# 输出结果如下:字段 text 表⽰消费者的评论,字段 label 表明这是⼀段好评还是差评
{'text': '轻便,⽅便携带,性能也不错,能满⾜平时的⼯作需要,对出差⼈员来讲⾮常不错','label': 1}
{'text': '很好的地理位置,⼀塌糊涂的服务,萧条的酒店。', 'label': 0}
{'text': '⾮常不错,服务很好,位于市中⼼区,交通⽅便,不过价格也⾼!', 'label': 1}
{'text': '跟住招待所没什么太⼤区别。绝对不会再住第2次的酒店!', 'label': 0}
{'text': '价格太⾼,性价⽐不够好。我觉得今后还是去其他酒店⽐较好。', 'label': 0}
# 3、打乱数据顺序
shuffled_dataset=dataset.shuffle(seed=42)
# 4、将训练集切分训练集和测试集
dataset.train_test_split(test_size=0.1)
datasetdict({
train: dataset({
features: ['text', 'label'],
num_rows: 8640
})
test: dataset({
features: ['text', 'label'],
num_rows: 960
})
})
# 5、使⽤批处理加速
def f(data):
text=data['text']
text=['my sentence: ' + i for i in text]
data['text']=text
return data
maped_datatset=dataset.map(function=f,
batched=true,
batch_size=1000, # 以 1000 条数据为⼀个批次进⾏⼀次处理;把函数执⾏的次数削减约 1000 倍,提⾼了运⾏效率
num_proc=4) # 在 4 条线程上执⾏该任务
print(dataset['text'][20])
print(maped_datatset['text'][20])
# 6、将数据保存为 csv 或 json 格式
dataset.to_csv(path_or_buf='./data/chnsenticorp.csv')
dataset.to_json(path_or_buf='./data/chnsenticorp.json')
四、使用评价指标工具
# 1、列出可⽤的评价指标
from datasets import list_metrics
metrics_list = list_metrics()
print(len(metrics_list), metrics_list[:5])
# (51, ['accuracy', 'bertscore', 'bleu', 'bleurt', 'cer'])
# 2、加载⼀个评价指标:加载⼀个评价指标和加载⼀个数据集⼀样简单
# 将对应数据集和⼦集的名字输⼊load_metric()函数即可得到对应的评价指标,但并不是每个数据集都有对应的
# 评价指标,在实际使⽤时以满⾜需要为准则选择合适的评价指标即可。
from datasets import load_metric
metric = load_metric(path='glue', config_name='mrpc')
# 3、获取评价指标的使⽤说明:评价指标的 inputs_description 属性为⼀段⽂本,描述了评价指标的使⽤⽅法
print(metric.inputs_description)
# 4、计算⼀个评价指标
predictions=[0, 1, 0]
references=[0, 1, 1]
print(metric.compute(predictions=predictions, references=references))
# 输出:{'accuracy': 0.666666666666, 'f1': 0.666666666666}
五、使用管道工具
# 1、⽂本分类
from transformers import pipeline
classifier = pipeline("sentiment-analysis")
result = classifier("i hate you")[0]
print(result) # {'label': 'negative', 'score': 0.9991}
result = classifier("i love you")[0]
print(result) # {'label': 'positive', 'score': 0.9998}
# 2、阅读理解
from transformers import pipeline
question_answerer=pipeline("question-answering")
context=r"""
extractive question answering is the task of extracting an answer from a text
given a question. an example of a
question answering dataset is the squad dataset, which is entirely based on
that task. if you would like to fine-tune
a model on a squad task, you may leverage the examples/pytorch/question-
answering/run_squad.py script.
"""
result=question_answerer(question="what is extractive question answering?",context=context,)
print(result)
# 输出如下:
{'score': 0.61 'start': 34, 'end': 95, 'answer': 'the task of extracting an answer from a text given a question'}
# 3、完形填空: sentence是⼀个句⼦,其中某些词被<mask>符号替代了,表明这是需要让模型填空的空位
from transformers import pipeline
unmasker=pipeline("fill-mask")
from pprint import pprint
sentence='huggingface is creating a <mask> that the community uses to solve
nlp tasks.'
print(unmasker(sentence))
# 4、⽂本续写:⼊参为⼀个句⼦的开头,让text_generator接着往下续写,参数max_length=��表明要续写的长度
from transformers import pipeline
text_generator=pipeline("text-generation")
text_generator("as far as i am concerned, i will", max_length=50, do_sample=false)
# 输出如下:
[{'generated_text': 'as far as i am concerned, i will be the first to admit
that i am not a fan of the idea of a "free market." i think that the idea of a
free market is a bit of a stretch. i think that the idea'}]
# 5、命名实体识别(named entity recognition):找出⼀段⽂本中的⼈名、地名、组织机构名等
from transformers import pipeline
ner_pipe=pipeline("ner")
sequence = """hugging face inc. is a company based in new york city. its headquarters are in dumbo,
therefore very close to the manhattan bridge which is visible from the window."""
for entity in ner_pipe(sequence):
print(entity)
# 6、文本摘要:使⽤⽂本总结⼯具对这段长⽂本进⾏摘要
from transformers import pipeline
summerizer=pipeline("summerization")
# 7、翻译
from transformers import pipeline
translator=pipeline("translation_en_to_de")
sentence="hugging face is a technology company based in new york and paris"
translator(sentence, max_length=40)
# 8、qa:使用本地模型
from awq import autoawqforcausallm
from transformers import autotokenizer
from transformers import pipeline
model_name_or_path = "thebloke/codellama-7b-instruct-awq"
# load model
model = autoawqforcausallm.from_quantized(model_name_or_path, fuse_layers=true,
trust_remote_code=true, safetensors=true)
tokenizer = autotokenizer.from_pretrained(model_name_or_path, trust_remote_code=true)
prompt = "tell me about ai"
prompt_template=f'''[inst] write code to solve the following coding problem that obeys the constraints and passes the example test cases. please wrap your code answer using ```:
{prompt}
[/inst]
'''
pipe = pipeline(
"text-generation", # 指定任务类型
model=model,
tokenizer=tokenizer,
max_new_tokens=512, # 最大长度
do_sample=true,
temperature=0.7,
top_p=0.95,
top_k=40,
repetition_penalty=1.1
)
print(pipe(prompt_template)[0]['generated_text'])
- config 文件参数解释

六、使用训练工具
6.1、准备数据
6.1.1、加载编码工具
# 加载 tokenizer:编码⼯具和模型往往是成对使⽤的
from transformers import autotokenizer
tokenizer = autotokenizer.from_pretrained('hfl/rbt3')
# 编码句⼦
print(tokenizer.batch_encode_plus(['明⽉装饰了你的窗⼦', '你装饰了别⼈的梦'],truncation=true,))
# 输出如下图所示:

6.1.2、加载数据集
# 从磁盘加载数据集
from datasets import load_from_disk
dataset = load_from_disk('./data/chnsenticorp')
# 缩小数据规模,便于测试
dataset['train'] = dataset['train'].shuffle().select(range(2000))
dataset['test'] = dataset['test'].shuffle().select(range(100))
print(dataset)
datasetdict({
train: dataset({
features: ['text', 'label'],
num_rows: 2000
})
validation: dataset({
features: ['text', 'label'],
num_rows: 0
})
test: dataset({
features: ['text', 'label'],
num_rows: 100
})
})
# 批处理编码
def f(data):
return tokenizer.batch_encode_plus(data['text'], truncation=true)
dataset=dataset.map(f,
batched=true,
batch_size=1000,
num_proc=0,
remove_columns=['text'])
print(dataset)
# 运行结果如下
datasetdict({
train: dataset({
features: ['label', 'input_ids', 'token_type_ids', 'attention_mask'],
num_rows: 2000
})
validation: dataset({
features: ['text', 'label'],
num_rows: 0
})
test: dataset({
features: ['label', 'input_ids', 'token_type_ids', 'attention_mask'],
num_rows: 100
})
})
# 移除太长的句⼦:把数据集中长度超过 512 个词的句⼦过滤掉
# 也可以把超出长度的部分截断,留下符合模型长度要求的数据
def f(data):
return [len(i)<=512 for i in data['input_ids']]
dataset=dataset.filter(f, batched=true, batch_size=1000, num_proc=4)
# 自定义数据集
import torch
from datasets import load_from_disk
class dataset(torch.utils.data.dataset):
def __init__(self, split):
self.dataset = load_from_disk('./data/chnsenticorp')[split]
def __len__(self):
return len(self.dataset)
def __getitem__(self, i):
text = self.dataset[i]['text']
label = self.dataset[i]['label']
return text, label
dataset = dataset('train')
6.2、定义模型和训练工具
6.2.1、加载预训练模型
# 加载模型:
from transformers import automodelforsequenceclassification
model=automodelforsequenceclassification.from_pretrained('hfl/rbt3', num_labels=2)
# 模型试算:模拟⼀批数据并进行试算
data = {
'input_ids': torch.ones(4, 10, dtype=torch.long),
'token_type_ids': torch.ones(4, 10, dtype=torch.long),
'attention_mask': torch.ones(4, 10, dtype=torch.long),
'labels': torch.ones(4, dtype=torch.long)}
out = model(**data)
print(out['loss'], out['logits'].shape) # (tensor(0.3597, grad_fn=<nlllossbackward0>), torch.size([4, 2]))
6.2.2、定义评价函数
# 加载评价指标
from datasets import load_metric
metric = load_metric('accuracy')
# 定义评价函数
import numpy as np
from transformers.trainer_utils import evalprediction
def compute_metrics(eval_pred):
logits, labels = eval_pred
logits = logits.argmax(axis=�)
return metric.compute(predictions=logits, references=labels)
# 模拟输出
eval_pred = evalprediction(
predictions=np.array([[0, 1], [2, 3], [4, 5], [6, 7]]),
label_ids=np.array([1, 1, 0, 1]),
)
print(compute_metrics(eval_pred)) # {'accuracy': 0.75}
6.2.3、定义训练超参数
# 定义训练参数:huggingface使⽤trainingarguments对象来封装超参数
from transformers import trainingarguments
# 定义训练参数
args = trainingarguments(
output_dir='./output_dir', # 定义临时数据保存路径
evaluation_strategy='steps', # 定义测试执⾏的策略,可取值为no、epoch、steps
eval_steps=30, # 定义每隔多少个step执⾏⼀次测试
save_strategy='steps', # 定义模型保存策略,可取值为no、epoch、steps
save_steps=20, # 定义每隔多少个step保存⼀次
num_train_epochs=1, # 定义共训练几个轮次
learning_rate=1e-4, # 定义学习率
weight_decay=1e-2, # 加⼊参数权重衰减,防⽌过拟合
per_device_eval_batch_size=16, # 定义测试和训练时的批次⼤小
per_device_train_batch_size=16,
no_cuda=true, # 定义是否要使⽤gpu训练
)
6.2.4、定义训练器
from transformers import trainer
from transformers.data.data_collator import datacollatorwithpadding
# 定义训练器:需要传递要训练的模型、超参数对象、训练和验证数据集、评价函数,以及数据整理函数
trainer = trainer(
model=model,
args=args,
train_dataset=dataset['train'],
eval_dataset=dataset['test'],
compute_metrics=compute_metrics,
data_collator=datacollatorwithpadding(tokenizer),
)
6.2.5、数据整理函数介绍
- 数据整理函数使⽤了由
huggingface提供的datacollatorwithpadding对象,它能把⼀个
批次中长短不⼀的句⼦补充成统⼀的长度,长度取决于这个批次中最长的句⼦有多长 - 所有数据的长度⼀致后即可转换成矩阵,模型期待的数据类型也是矩阵,所以经过数据整理函数的处理之后,数据即被整理成模型可以直接计算的矩阵格式
# 测试数据整理函数
data_collator = datacollatorwithpadding(tokenizer)
data = dataset['train'][:5] # 获取⼀批数据
# 输出这些句⼦的长度
for i in data['input_ids']:
print(len(i))
data = data_collator(data) # 调⽤数据整理函数
# 查看整理后的数据
for k, v in data.items():
print(k, v.shape)
62
34
185
101
40
input_ids torch.size([5, 185])
token_type_ids torch.size([5, 185])
attention_mask torch.size([5, 185])
labels torch.size([5])
# 通过如下代码可以查看数据整理函数是如何对句⼦进⾏补长的
tokenizer.decode(data['input_ids'][0])

6.3、训练和测试
6.3.1、模型训练和测试
# 训练
trainer.train()
# 从某个存档⽂件继续训练
trainer.train(resume_from_checkpoint='./output_dir/checkpoint-90')
# 评价模型
trainer.evaluate()

6.3.2、模型的保存和加载
# ⼿动保存模型参数
trainer.save_model(output_dir='./output_dir/save_model')
# ⼿动加载模型参数
import torch
model.load_state_dict(torch.load('./output_dir/save_model/pytorch_model.bin'))
6.3.3、模型预测
model.eval()
# 从测试数据集中获取1个批次的数据⽤于预测
for i, data in enumerate(trainer.get_eval_dataloader()):
break
out = model(**data)
out = out['logits'].argmax(dim=1)
for i in range(8):
print(tokenizer.decode(data['input_ids'][i], skip_special_tokens =true))
print('label=', data['labels'][i].item())
print('predict=', out[i].item())
七、nlp 实战
7.1、中文情感分类
- 在自然语言处理中,
adamw优化器比adam效果要好 - 分类的类别太多(
上万)也容易出现梯度消失的问题,所以在下游任务的输出时不能使⽤softmax
函数激活

7.2、中⽂命名实体识别
7.2.1、数据标签定义


7.2.2、训练框架示例

7.2.3、两段式训练的思想

八、手动实现 transformer
- transformer 深度学习架构是通过继承许多⽅法而产⽣的,其中包括
上下⽂词嵌⼊、多头注意⼒机制、位置编码、并⾏体系结构、模型压缩、迁移学习、跨语⾔模型等。 - 在各种基于神经的⾃然语⾔处理⽅法中,transformer 架构逐渐演变为基于注意⼒的 “
编码器-解码器”体系结构,并持续发展到今天。现在,我们在⽂献中看到了这种体系结构的新的成功变体。⽬前研究已经发现了 只使⽤ transformer 架构中编码器部分(自编码 bert-like) 的出⾊模型,如 bert(bidirectionalencoder representations from transformers,transformers 双向编码表⽰);或者 只使⽤ transformer 架构中解码器部分(自回归 gpt-like) 的出⾊模型,如 gpt(generated pre-trained transformer,⽣成式的预训练 transformer);以及bart/t5-like(也被称作序列到序列的 transformer模型)





九、手动实现 bert
- bert 是基于 transformer 模型的改进模型,与 transformer 不同,bert 的设计并不是为了完成特定的具体任务,bert 的设计初衷就是要作为⼀个
通⽤的 backbone使⽤,即提取⽂本的特征向量,有了特征向量后就可以接⼊各种各样的下游任务,包括翻译任务、分类任务、回归任务等 - bert 模型的架构如下图所示:


十、文本生成任务解码策略


- 公开论文中梳理出的解码方案

10.1、greedy search(贪心搜索)
greedy search是指每一步解码都选取可能性最高的单词(i.e.argmax),把选取的单词补充到input中再继续下一步解码直到产生[eos]或者达到了事先定义的最大生成长度后停止解码。它的缺点也很明显:- 续写的内容还算通顺,但逻辑有些问题,并且很快就开始有了大量的重复
- 会遗漏隐藏在低概率单词后面的高概率单词

10.2、beam search(束搜索)
beam search是一种启发式图搜索算法,具有更大的搜索空间,可以减少遗漏隐藏在低概率单词后面的高概率单词的可能性,他会在每步保持最可能的num_beams个hypotheses,最后选出整体概率最高或者平均得分最大(除以各自的 token 数)的hypotheses。下面以num_beams=2为例:- 从下图中可以看到,在第一步的时候,我们除了选择概率最高的『机』字以外,还保留了概率第二高的『桨』字。在第二步的时候
两个 beam分别选择了『起』和『框』。这时我们发现『飞机起』这一序列的概率为0.2,而『飞桨框』序列的概率为0.32。我们找到了整体概率更高的序列。在我们这个示例中继续解下去,得到的最终结果为『飞桨框架』 - 相比
greedy search,beam search几乎总能找到整体概率更高的结果。当然由于它的搜索空间也不是无限的,它难以找到所谓的最优解
- 从下图中可以看到,在第一步的时候,我们除了选择概率最高的『机』字以外,还保留了概率第二高的『桨』字。在第二步的时候
beam search缺点:- 结果里还是会出现一些重复内容,一个简单的补救措施是引入
n-grams(即连续 n 个词的词序列) 惩罚。最常见的n-grams惩罚是确保每个n-gram都只出现一次,方法是如果看到当前候选词与其上文所组成的n-gram已经出现过了,就将该候选词的概率设置为0。我们可以通过设置no_repeat_ngram_size=2来试试,这样任意2-gram不会出现两次。但是惩罚太高,生成的文章会不达意,惩罚太少,容易出现大量循环的句子 - 缺乏随机性,对于相似的输入,可能生成相同的结果

- 结果里还是会出现一些重复内容,一个简单的补救措施是引入
10.3、sampling
-
sampling 简介:

-
temperature的本质是降低了采样随机性,该值越小随机性越低,当temperature=0时,解码的效果就等同与greedy search了- 当
temperature变大时,模型在生成文本时更倾向于比较少见的词汇。越大,重新缩放后的分布就越接近均匀采样(让大概率和小概率之间差别没那么明显)。 - 当
temperature变小时,模型在生成文本时更倾向于常见的词。越大,重新缩放后的分布就越接近我们最开始提到的贪婪生成方法(即总是去选择概率最高的那个词,让概率大的更大、让小的变的更小)


- 当
-
文本序列的概率分布:

10.3.1、top-k sampling
- 除了
temperature外,还有一个更简单更常用的方法可以避免生成离谱的结果,这就是 top-k sampling。top-k sampling 的原理如下图所示,可以看到,top-k sampling就是每一步取条件概率前 k 大(这里为 5)的结果,将他们的概率重新归一化后再进行采样,这样做是希望在 “得分高” 和 “多样性” 方面做一个折中。显然,当k=1时,其实就等价于贪心搜索。 - 通常来说,
加大 k会产生更多样化、有风险的结果,减小 k则会产生更通用、安全的结果 - top-k sampling 缺点:因为
k 值在整个解码中是固定的,所以在所有词的概率分布比较均匀时,top-k 会过滤掉很多合理的词,而在概率分布非常不平均时(比如前一两个词占据了绝大部分概率),top-k 又会将一些不合理的词纳入选择


10.3.2、top-p sampling
- 相比于 top-k sampling,
top-p sampling可以根据每步的概率分布动态调整采样范围。原理如下图所示,可以看到,p代表采样的阈值,每一步只保留概率最高(sorted)的且概率和刚好超过p的若干个token,下图第一步保留了 6 个;第二部保留了 3 个 - 在实际使用中,top-k 和 top-p sampling 可以同时使用,用于过滤掉一些概率排名很低的不合理的词。


10.4、重复输出,且不终止是怎么回事?


发表评论