文章目录
代码实现部分
1.数据集
Chinese dataset:ATEC-CCKS
数据集中是sentence pair形式存储的数据集,每行数据包括两个内容,一个是original sentence,一个是reference sentence。
然后,数据的最后一行是相似度判断一个sentence pair是不是相似的。
0表示不相似,1表示相似。
数据集示例如下:
我提前还款之后,还可以再借么 如果提前还清款项。可以在借款 还款后能否在借钱 1
蚂蚁花呗怎么申请临时额度 我的花呗固定额度不张临时额度老张,为什么不给涨固定的 0
为什么我还完钱借呗可以使用,过了一两天就不能用用了 为什么我的蚂蚁借呗用不了了?一直正常还款 0
没信用卡可以吗 换卡好了 1
花呗有额度,信用住自动扣款扣不了花呗 花呗没有信用住了嘛 0
2.模型部分
在论文中,作者将整个模型分为了三个部分,分别是数据增强、encoder和对比学习模块。
数据增强是使用cut off 和 token shuffle 和 dropout做数据增强(是在embedding matricx上的数据增强工作)。
encoder是使用bert的encoder,使用的average pooling做的constractive loss计算。
然后是对比学习模块,将增强后的samples作为正例,batch中的其余samples作为负例。
模型的总体框架
data_utils:是做的数据加载,中文数据和英文数据的加载
main:主函数运行
2.1 main 主文件
模型部分
模型是SentenceTransformer
在之后的model.fit以及其他过程中,都是使用的sentence transformer中的文件
在bert embedding中,是通过models.Transformer函数得到的Word 的embedding。
word_embedding_model = models.Transformer(args.model_name_or_path)
然后,通过models.Pooling得到的pooling model
数据加载部分
在对中文数据做相似度判断时,一般使用的是hfl/chinese-roberta-wwm-ext
model 的config 文件:
model 的加载
{
"architectures": [
"BertForMaskedLM"
],
"attention_probs_dropout_prob": 0.1,
"bos_token_id": 0,
"directionality": "bidi",
"eos_token_id": 2,
"hidden_act": "gelu",
"hidden_dropout_prob": 0.1,
"hidden_size": 768,
"initializer_range": 0.02,
"intermediate_size": 3072,
"layer_norm_eps": 1e-12,
"max_position_embeddings": 512,
"model_type": "bert",
"num_attention_heads": 12,
"num_hidden_layers": 12,
"output_past": true,
"pad_token_id": 0,
"pooler_fc_size": 768,
"pooler_num_attention_heads": 12,
"pooler_num_fc_layers": 3,
"pooler_size_per_head": 128,
"pooler_type": "first_token_transform",
"type_vocab_size": 2,
"vocab_size": 21128
}
在train setting设置时,是将config中的attention_probs_dropout_prob 和 hidden_dropout_prob的参数设置为0.0
然后,做了重写工作。
sentence embedding的得到
在获得sentence embedding时,使用的是SentenceTransformer模型。SentenceTransformer模型是为了将sentences或者text映射到对应的embedding。
train 数据
train_samples加载的函数使用的是load_chinese_tsv_data
在加载数据时,还使用到一个工具包是 InputExample。InputExample 是一个class,可以以字典形式存储相关的信息。
数据形式如下:
self.guid = guid
self.texts = [text.strip() for text in texts] if texts is not None else texts
self.texts_tokenized = texts_tokenized
self.label = label
最终的数据是以列表形式存储的。
数据的最终存储是:
sent1, sent2, label = line.strip().split(“\t”)
all_samples.append(InputExample(texts=[sent1]))
train_dataset加载的函数使用的是SentencesDataset
train_dataloader 加载的函数使用的是Dataloader
损失函数使用的是loss.AdvCLSoftmaxLoss
dev 数据
dev数据加载使用的是load_chinese_tsv_data
dev数据相似度的测评 EmbeddingSimilarityEvaluator
在使用EmbeddingSimilarityEvaluator时,是Evaluate a model based on the similarity of the embeddings by calculating the Spearman and Pearson rank correlation
选择的similarity function是COSINE函数
在得到句子的embedding上,使用的model.encode函数。
几种不同的相似度函数下的distance测量:
cosine_scores = 1 - (paired_cosine_distances(embeddings1, embeddings2))
manhattan_distances = -paired_manhattan_distances(embeddings1, embeddings2)
euclidean_distances = -paired_euclidean_distances(embeddings1, embeddings2)
dot_products = [np.dot(emb1, emb2) for emb1, emb2 in zip(embeddings1, embeddings2)]
几种相关系数下的计算(pearsonr/spearmanr)
eval_pearson_cosine, _ = pearsonr(labels, cosine_scores)
eval_spearman_cosine, _ = spearmanr(labels, cosine_scores)
eval_pearson_manhattan, _ = pearsonr(labels, manhattan_distances)
eval_spearman_manhattan, _ = spearmanr(labels, manhattan_distances)
eval_pearson_euclidean, _ = pearsonr(labels, euclidean_distances)
eval_spearman_euclidean, _ = spearmanr(labels, euclidean_distances)
eval_pearson_dot, _ = pearsonr(labels, dot_products)
eval_spearman_dot, _ = spearmanr(labels, dot_products)
以上这些函数是从已经预定好的库中引入的,是从已经定义好的python tool中导入的。
from sklearn.metrics.pairwise import paired_cosine_distances, paired_euclidean_distances, paired_manhattan_distances
from scipy.stats import pearsonr, spearmanr
model 的train
使用的是函数:model.fit()
在model.fit中,模型的参数有:
train_objectives: Iterable[Tuple[DataLoader, nn.Module]],
evaluator: SentenceEvaluator = None,
epochs: int = 1,
steps_per_epoch = None,
scheduler: str = 'WarmupLinear',
warmup_steps: int = 10000,
optimizer_class: Type[Optimizer] = transformers.AdamW,
optimizer_params : Dict[str, object]= {'lr': 2e-5, 'eps': 1e-6, 'correct_bias': False},
weight_decay: float = 0.01,
evaluation_steps: int = 0,
output_path: str = None,
save_best_model: bool = True,
max_grad_norm: float = 1,
use_amp: bool = False,
use_apex_amp: bool = False,
apex_amp_opt_level: str = None,
callback: Callable[[float, int, int], None] = None,
output_path_ignore_not_empty: bool = False,
early_stop_patience: Optional[int] = None
train_objectives,有两项,一项是dataloader,一项是loss。
在optimizer和schedule的参数更新设定,模型中做了如下定义:
更新参数
param_optimizer = list(loss_model.named_parameters())
no_decay = ['bias', 'LayerNorm.bias', 'LayerNorm.weight']
optimizer_grouped_parameters = [
{'params': [p for n, p in param_optimizer if not any(nd in n for nd in no_decay)], 'weight_decay': weight_decay},
{'params': [p for n, p in param_optimizer if any(nd in n for nd in no_decay)], 'weight_decay': 0.0}
]
optimizer = optimizer_class(optimizer_grouped_parameters, **optimizer_params)
scheduler_obj = self._get_scheduler(optimizer, scheduler=scheduler, warmup_steps=warmup_steps, t_total=num_train_steps)
optimizers.append(optimizer)
schedulers.append(scheduler_obj)
train process
for epoch in trange(epochs, desc="Epoch"):
training_steps = 0
for loss_model in loss_models:
loss_model.zero_grad()
loss_model.train()
for _ in trange(steps_per_epoch, desc="Iteration", smoothing=0.05):
for train_idx in range(num_train_objectives):
loss_model = loss_models[train_idx]
optimizer = optimizers[train_idx]
scheduler = schedulers[train_idx]
data_iterator = data_iterators[train_idx]
try:
data = next(data_iterator)
except StopIteration:
#logging.info("Restart data_iterator")
data_iterator = iter(dataloaders[train_idx])
data_iterators[train_idx] = data_iterator
data = next(data_iterator)
features, labels = batch_to_device(data, self._target_device)
if use_amp:
with autocast():
loss_value = loss_model(features, labels)
scale_before_step = scaler.get_scale()
scaler.scale(loss_value).backward()
scaler.unscale_(optimizer)
torch.nn.utils.clip_grad_norm_(loss_model.parameters(), max_grad_norm)
scaler.step(optimizer)
scaler.update()
skip_scheduler = scaler.get_scale() != scale_before_step
else:
loss_value = loss_model(features, labels)
self.tensorboard_writer.add_scalar(f"train_loss_{train_idx}", loss_value.item(), global_step=self.global_step)
if use_apex_amp:
with amp.scale_loss(loss_value, optimizer) as scaled_loss_value:
scaled_loss_value.backward()
torch.nn.utils.clip_grad_norm_(amp.master_params(optimizer), max_grad_norm)
else:
loss_value.backward()
torch.nn.utils.clip_grad_norm_(loss_model.parameters(), max_grad_norm)
optimizer.step()
optimizer.zero_grad()
if not skip_scheduler:
scheduler.step()
test数据集的完成类似于dev数据集。使用的是相同的函数
在进行evaluation过程时,使用的函数是eval_chinese_unsup(model_save_path, dataset_name, batch_size=16, main_similarity=SimilarityFunction.COSINE)
3.损失函数与优化器
模型的损失函数的定义
4.模型训练与推理
在模型的train阶段,是执行的model.fit函数
在模型的evaluation阶段,是执行的EmbeddingSimilarityEvaluator 类
5.代码运行示例
代码存放在本电脑的地址是:
E:\23年研文件\W3-5\对比学习\论文源码执行结果.pdf