欢迎大家来到IT世界,在知识的湖畔探索吧!
上一篇文章中我们使用Softmax Loss损失函数微调训练了第一个Sentence Transformers模型,本文我们将使用性能更好的Multiple Negatives Ranking Loss(多负样本排列损失)函数来学习微调训练性能更好的Sentence Transformer模型。
2019年第一个Sentence Transformer模型SBERT出现后句子密集词向量模型就开始蓬勃发展。后续大量的模型的性能很快超过了SBERT,这很大的原因是Multiple Negatives Ranking Loss(多负样本排列损失)函数的使用。
同样的本文使用简单的介绍用两种方式来微调Sentence Transformer模型,第一种方法使用Pytorch编写代码进行训练。第二种方法使用Sentence Transformer提供的微调工具进行快速的微调,推荐使用第二种方法,它有更好的性能。
正如我们在关于softmax损失的文章中解释的那样,我们可以使用自然语言推理(NLI)数据集来微调句子转换模型。
在我们的例子中我们使用了部分斯坦福自然语言推理(SNLI)语料库,语料库中每个句子对由一个前提句和一个假设句组成,并被赋予一个标签:
0 — 含义关系,例如前提句暗示了假设句。
1 — 中性关系,前提句和假设句都可能是真实的,但它们之间不一定相关。
2 — 矛盾关系,前提句和假设句相互矛盾。
但在使用MNR损失进行微调时,我们将删除所有标签为中性或矛盾的行,只保留正相关的句子对。
我们将在每个步骤中将句子A(前提句,也称为锚点)和句子B(假设句,在标签为0时称为正例)输入到BERT中。
注意与softmax损失不同,我们不使用训练数据的标签label,代替的是我们为了使用MNR的多负样本,我们将通过每个batch的数据构建负样本。
数据准备:
首先我已经提前将nli数据集下载到了本地起名叫train.txt,train.txt的格式如下:
from datasets import load_dataset m_nli = load_dataset('csv', data_files='./snli_1.0/train.txt',split='train') dataset = m_nli.filter( lambda x: 1 if x['label'] == 0 else 0) print(len(dataset))
欢迎大家来到IT世界,在知识的湖畔探索吧!
代码中我们只保留了正相关的数据对,一共对
接下来我们继续使用transformer模型中的BertTokenizer对数据进行预处理,之前的文章介绍过使用Tokenizer是为了将文本数据转化为矩阵向量,方便使用pytorch进行数据训练,这里使用了BertTokenizer 类将’premise’和’hypothesis’列分别进行了处理转换为数学向量。
欢迎大家来到IT世界,在知识的湖畔探索吧!from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("E:\\huggingface\\sentence-transformers_bert-base-uncased\\") all_cols = ['label'] for part in ['premise', 'hypothesis']: dataset = dataset.map( lambda x: tokenizer( x[part], max_length=128, padding='max_length', truncation=True ), batched=True ) for col in ['input_ids', 'attention_mask']: dataset = dataset.rename_column( col, part+'_'+col ) all_cols.append(part+'_'+col) print(all_cols) print(dataset)
接着我们使用pytorch的DataLoader完成数据的载入
import torch dataset.set_format(type='torch', columns=all_cols) batch_size = 32 loader = torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=True)
这里对很多的概念做了简化,可以查看之前的文章详细了解
开始使用Pytorch训练模型
学习训练SBERT模型,我们不需要从头训练,我们使用BERT的预训练模型做迁移学习,这样将节省大量的时间和成本。
欢迎大家来到IT世界,在知识的湖畔探索吧!from transformers import BertModel model = BertModel.from_pretrained("E:\\huggingface\\sentence-transformers_bert-base-uncased\\")
使用MNR损失函数训练模型,同样使用孪生神经网络的方式,将数据对A和B分别送入孪生神经网络进行训练。
BERT模型输出512*768维的密集词向量。我们使用均值池化将它转化为的句子嵌入。这样使用孪生神经网络方法,我们每个步骤产生两个密集向量,句子A输出的a向量,正例B输出的p向量。
均值池化操作mean_pool的函数如下:
in_mask = attention_mask.unsqueeze(-1).expand( token_embeds.size()).float() pool = torch.sum(token_embeds * in_mask, 1) / torch.clamp(in_mask.sum(1), min=1e-9) return pool
使用平均池化函数我们就可以将我们的词向量输出从N*768转化为1*768维度
接下来我们将使用pytorch计算向量a和向量p的相似性
import torch cos_sim=torch.nn.CosineSimilarity() a=torch.randn(16,512) p=torch.randn(16,512) scores=[] for item in a: scores.append(cos_sim(item.reshape(1,item.shape[0]),p)) scores=torch.stack(scores) print(scores)
需要注意的是训练时是按照batch来进行训练的,这意味着我们将多个数据对同时送入模型进行计算 。
所以我们一个批次的句子对A和B输入孪生神经网络后,分别输出的向量a和p的维度是32*768,接下来使用CosineSimilarity类计算a和p的相似性。
scores=[] cos_sim = torch.nn.CosineSimilarity()scores = [] for item in a: scores.append(cos_sim(item.reshape(1, 768), p)) scores = torch.stack(scores)
输出scores的维度是32*32,到这里我们使用batch大小是32的批次获得了数据对的余弦相似性矩阵。如何使用batch为32的批次数据计算MNR损失呢
我们得到的32*32的相似度矩阵,对角线的绿色部分对应的同一个句子对,需要注意的是我们的数据集只保留了正相关的数据,因此我们需要让对角线同一个数据对的相似性非常高,非对角线的相似度非常低。
因此我们根据batch大小32可以构建一个类别数为32的label标签,相似度矩阵每一行代表一个类别,这样就实现了同一个句子对的相似度高,非同一个句子对相似度低的定义,而且正样本只有一个,而负样本有31个。
labels = torch.tensor(range(len(scores)), dtype=torch.long, device=scores.device) #输出: #tensor([ 0, 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], #device='cuda:0')
接下来我们根据相似度矩阵和类别标签,我们就可以定义交叉熵来进行训练工作了。
#定义交叉熵损失函数 loss_func = torch.nn.CrossEntropyLoss() loss_func(scores, labels)
虽然此处介绍了使用pytroch编写代码进行训练,但仅限于学习使用。这种方式效率和性能比较低下,在实际训练中推荐使用sentence transformers提供的训练工具,进行训练,这种方式更加简答同时训练出的模型性能更高。
使用sentence_transformer工具训练模型
正如我们之前提到的,使用 MNR 损失函数有一种更简单的方法来微调模型。sentence-transformers 库允许我们使用预训练的句子转换模型,并提供了一些便捷的训练工具。
载入数据的代码完全相同,不同的是我们不需要进行手动的token化;而是简单的使用(sentence-transformers)的InputExample库来完成这个操作。
from sentence_transformers import InputExample train_samples = [] for row in dataset: train_samples.append(InputExample( texts=[row['premise'], row['hypothesis']] ))
InputExample仅包含带有a和p句对的数据,然后将其送到NoDuplicatesDataLoader对象中。这个数据加载器确保每个批次都不包含重复数据,这在使用MNR损失函数对随机抽样的句对进行相似性排序时非常有用。
现在我们来定义模型。sentence-transformers库允许我们使用模块构建模型。我们只需要一个transformer模型(我们将再次使用bert-base-uncased)和一个平均池化模块。
from sentence_transformers import models, SentenceTransformer bert = models.Transformer('bert-base-uncased') pooler = models.Pooling( bert.get_word_embedding_dimension(), pooling_mode_mean_tokens=True ) model = SentenceTransformer(modules=[bert, pooler])
现在我们有了一个初始化的模型。在训练之前,唯一剩下的就是损失函数——MNR损失。
from sentence_transformers import losses loss = losses.MultipleNegativesRankingLoss(model)
有了这些,我们的数据加载器、模型和损失函数都准备好了。剩下的就是微调模型!
epochs = 1 warmup_steps = int(len(loader) * epochs * 0.1) model.fit( train_objectives=[(loader, loss)], epochs=epochs, warmup_steps=warmup_steps, output_path='./sbert_test_mnr2', show_progress_bar=False )
完成代码如下:
import torch from datasets import load_dataset m_nli = load_dataset('csv', data_files='./snli_1.0/train.txt',split='train') dataset = m_nli.filter( lambda x: 1 if x['label'] == 0 else 0) print(len(dataset)) from sentence_transformers import InputExample from tqdm.auto import tqdm # so we see progress bar train_samples = [] for row in tqdm(m_nli): train_samples.append(InputExample( texts=[row['premise'], row['hypothesis']] )) from sentence_transformers import datasets batch_size = 32 loader = datasets.NoDuplicatesDataLoader( train_samples, batch_size=batch_size) from sentence_transformers import models, SentenceTransformer bert = models.Transformer("E:\\huggingface\\sentence-transformers_bert-base-uncased\\") print(bert.get_word_embedding_dimension()) pooler = models.Pooling( bert.get_word_embedding_dimension(), pooling_mode_mean_tokens=True ) model = SentenceTransformer(modules=[bert, pooler]) from sentence_transformers import losses loss = losses.MultipleNegativesRankingLoss(model) epochs = 1 warmup_steps = int(len(loader) * epochs * 0.1) model.fit( train_objectives=[(loader, loss)], epochs=epochs, warmup_steps=warmup_steps, output_path='./sbert_test_mnr2', show_progress_bar=False )
代码中由于网络问题,我都是先将数据和模型下载到本地再导入的方式。
对比模型:
我们对比了使用MNR和Softmax损失函数微调的sentence transformer模型的性能,同时对比了更加强大的all-mp-base-v2模型。
测试数据集使用了huggingface提供的STS数据集,这个数据集中对每个数据对给了1-5的相似度打分。最后使用sentence-transformers的评估工具进行模型的评估,代码如下。
import datasets sts = datasets.load_dataset('glue', 'stsb', split='validation') print(sts) sts = sts.map(lambda x: {'label': x['label'] / 5.0}) from sentence_transformers import InputExample samples = [] for sample in sts: samples.append(InputExample( texts=[sample['sentence1'], sample['sentence2']], label=sample['label'] )) from sentence_transformers.evaluation import EmbeddingSimilarityEvaluator evaluator = EmbeddingSimilarityEvaluator.from_input_examples( samples, write_csv=False ) from sentence_transformers import SentenceTransformer #评估mnr调优的模型 model = SentenceTransformer('./sbert_test_mnr2') result=evaluator(model) print(result) #评估softmax调优的模型 model2= SentenceTransformer('./sbert_test_a') result=evaluator(model2) print(result) #评估all-mp-base-v2调优的模型 model2= SentenceTransformer('all-mp-base-v2') result=evaluator(model2) print(result)
模型评估性能如下:
Softmax:0.67
MNR:0.77
all-mp-base-v2:0.88
结论:
本文我们使用MNR多负样本排列损失学习训练一个新的sentence transformer模型,并和其他模型进行了对比,结果显示MNR损失函数有更高的性能。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/81135.html