当前位置: 代码网 > 服务器>软件设计>开源 > HuggingFace 自然语言处理

HuggingFace 自然语言处理

2024年08月03日 开源 我要评论
是一个开源社区,提供了统一的AI 研发框架、工具集、可在线加载的数据集仓库和预训练模型仓库。准备数据集,定义模型,训练,测试;每个部分都提供了相应的工具集⽂字是⼀个抽象的概念,不是计算机擅长处理的数据单元,计算机擅长处理的是数字运算,所以需要把抽象的⽂字转换为数字,让计算机能够做数学运算。为了把抽象的⽂字数字化,需要⼀个字典把⽂字或者词对应到某个数字。⼀个⽰意的字典如下:# 字典:这只是⼀个⽰意的字典,所以只有 11 个词,在实际项⽬中的字典可能会有成千上万个词vocab = {


一、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 transformerstransformers 双向编码表⽰);或者 只使⽤ 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_beamshypotheses,最后选出整体概率最高或者平均得分最大(除以各自的 token 数)的 hypotheses 。下面以 num_beams=2 为例:
    • 从下图中可以看到,在第一步的时候,我们除了选择概率最高的『机』字以外,还保留了概率第二高的『桨』字。在第二步的时候两个 beam 分别选择了『起』和『框』。这时我们发现『飞机起』这一序列的概率为 0.2,而『飞桨框』序列的概率为 0.32。我们找到了整体概率更高的序列。在我们这个示例中继续解下去,得到的最终结果为『飞桨框架』
    • 相比 greedy searchbeam 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、重复输出,且不终止是怎么回事?

在这里插入图片描述在这里插入图片描述


十一、参考资料

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com