CheaSim Blog

UNIFIEDQA Crossing Format Boundaries with a Single QA System 读书笔记

UNIFIEDQA: Crossing Format Boundaries with a Single QA System 读书笔记

针对不同形式的QA做了一个整合的工作。对于抽取式、多选式、对错形式的QA数据集,使用了一个单一的模型进行训练,并且测试。

使用的模型是T-5、BART。

阅读更多

基于BERT的知识库问答系统

毕设复习

由于是基于知识图谱的领域内问答系统,所以分为两个步骤,不是end2end。

  1. 命名实体识别
  2. 属性映射步骤

实体识别是为了找到问题中的实体,属性映射是为了找到实体对应在知识库中的属性。输出的结果是一个规则构成的“「实体」的「属性」是「尾实体」”

命名实体识别是通过BERT+CRF。

属性映射分为两步

  1. 通过规则,在知识库中找到实体的所有属性,之后和原句匹配,匹配成功作为属性输出
  2. 匹配不成果,将所有属性以“「问题」「属性」”计算分数,取匹配分数最高的作为答案输出。
阅读更多

NLP基础

nlp 深度学习基础

将每一个模型以

  1. 简单介绍
  2. 解决的问题
  3. 代码
  4. 优缺点
  5. 使用tips

来归类。比较现代的会分析分析。

阅读更多

如何在colab用pytorch_lightning白嫖TPU

如何在colab用pytorch_lightning白嫖TPU

首先

我们得熟悉pytorch_lightning以及colab的操作。在使用TPU之前,先挂载好我们的云端磁盘以及配置好pytorch需要的TPU环境。

阅读更多

HuggingFace PretrainTokenizer学习笔记

笔记

Transformers==4.0.0

由于每次调用bert等模型都需要使用模型的tokenizer,所以写个笔记,方便自己以及后人查阅,(其实看官方文档也可以,但是英文的看着头疼。有错误也请留言吧。

base : PreTrainedTokenizerBase

作为基类,该类有着所有tokenizer都具有的方法还有属性。

PreTrainedTokenizer

使用multiprocess 加速tokenize

仿照这个就完事了。partial可以固定函数中的参数,简直是专门为多进程准备的。

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
def squad_convert_example_to_features_init(tokenizer_for_convert: PreTrainedTokenizerBase):
global tokenizer
tokenizer = tokenizer_for_convert



features = []

threads = min(threads, cpu_count())
with Pool(threads, initializer=squad_convert_example_to_features_init, initargs= (tokenizer,)) as p:
annotate_ = partial(
tokenzier.encode_plus, # batch_encode_plus 不知道为什么不work
max_seq_length=max_seq_length,
doc_stride=doc_stride,
max_query_length=max_query_length,
padding_strategy=padding_strategy,
is_training=is_training,
)
features = list(
tqdm(
p.imap(annotate_, examples, chunksize=32),
total=len(examples),
desc="convert squad examples to features",
disable=not tqdm_enabled,
)
)

pytorch cheat_list

pytorch 操作小计

torch==1.7.0

tensor

torch.stack

将List[tensor]变成tensor。torch.stack(tensors,dim=0,out=None)Concatenates a sequence of tensors along a new dimension.

1
2
b = torch.randn(4)
a = torch.stack([b, b], dim = 0) # a.shape = (2,4)

torch.gather

torch.gather(input, dim, index, *, sparse_grad=False, out=None) → Tensor

英文解释为Gathers values along an axis specified by dim.

看到比较有道理的应用场景是,在变长序列中gather到最后一个或者说倒数第几个元素。一般变长序列为inputs = [[1,2,3,0,0], [2,3,4,5,0]]。这时候想获得最后一个元素就可以。需要注意的点是,输出的tensor和index是相同shape的。

1
2
3
4
5
6
inputs = torch.tensor([[1,2,3,0,0],
[2,3,4,5,0]])
index = torch.tensor([[2], [3]], dtype=torch.long)
last_inputs = torch.gather(inputs, dim=1, index)
"""tensor([[3],
[5]])"""

torch.expand

将tensor扩展维度,自动复制,十分好用。

1
2
3
4
5
6
7
8
9
10
11
12
a = torch.randint(1, 5, size=(2,3))
#tensor([[3, 1, 1],
# [1, 2, 4]])
a = a.unsqueeze(2).expand(2,3,3)
"""
tensor([[[3, 3, 3],
[1, 1, 1],
[1, 1, 1]],

[[1, 1, 1],
[2, 2, 2],
[4, 4, 4]]])"""

torch.repeat

repeat(*sizes) -> Tensor, 重复复制tensor在指定的维度上。其实有点类似于广播操作了?

1
2
3
x = torch.tensor([1, 2, 3]) # x.shape=[3]
x.repeat(4,2) # x.shape=[4,6]
x.repeat(4,2,1) # x.shape=[4,2,3]

torch.nn.functional

F.softmax

F.softmax(Tensor, dim=None) 对于多维度矩阵就是 einsum(‘ijk -> jk’, a) = torch.ones(a.shape[1:])

1
2
3
import torch.nn.functional as F
a = torch.randn(4,5)
a = F.softmax(a, dim = 0)

torch.nn

之前一直依赖着huggingface的模型加载from_pretrained,但其实在一般任务场景下,使用torch.load的时候会更多,所以记录一下torch.load方法的使用场景。

torch.load & torch.save

一般我们将模型的参数保存,而不会去保存整个模型的结构。这里如果需要部分加载参数,可以使用strict=False。这里需要注意加载的是字典dict,不是模型。

1
2
3
4
#model ... after training
torch.save(model.state_dict(), cached_file_path)
model_state = torch.load(cached_file_path)
model.load_state_dict(model_state, strict=False)

奇淫技巧

whole word mask

在bert或者其他语言模型中,对一段文本需要先进行tokenize分词操作,而分英文单词的时候,由于OOV问题,会将有些word分成token级别的,比如将trying分成try,##ing。而我们比如在建图或者以word为粒度的时候,就需要将token的输出平均给word了。那么如何操作呢?

1
2
3
4
5
6
7
8
9
encoder_output = encoder_outputs[i]  # [slen, bert_hidden_size]
word_num = 123
word_index=(torch.arange(word_num) + 1).unsqueeze(1).expand(-1, slen) # [mention_num, slen]
words = pos_id[i].unsqueeze(0).expand(mention_num, -1) # [mention_num, slen]
select_metrix = (mention_index == mentions).float() # [mention_num, slen]
# average word -> mention
word_total_numbers = torch.sum(select_metrix, dim=-1).unsqueeze(-1).expand(-1, slen) # [mention_num, slen]
select_metrix = torch.where(word_total_numbers > 0, select_metrix / word_total_numbers, select_metrix)
x = torch.mm(select_metrix, encoder_output)

Dialogue Relation Extraction with Document-level Heterogeneous Graph Attention Networks 论文解读

Dialogue Relation Extraction with Document-level Heterogeneous Graph Attention Networks 论文解读

论文传送门代码传送门

任务

从对话数据集中抽取出实体之间的联系。给定一段对话,输出指定两个实体之间的关系,总共有37中不同的关系。

模型

针对对话中不同的信息进行embedding,

  1. utterance ,对话中的使用LSTM模型进行encode。
  2. speaker
  3. argument
  4. entity-type
  5. word

由着四种embedding来构建图。之后拼接学到的argument embedding通过分类器输出关系。

ALBERT 更小但是更慢?

ALBERT 更小但是更慢?

最近由于参加阅读理解比赛,所以大量测试各种模型,惊奇地发现原本现在阅读理解比赛中SOTA的模型居然是不起眼并且以小模型闻名的ALBERT。这让我对这个“小”模型产生了好奇。从而写一下这份的论文笔记。

摘要

模型越大下游效果越强是众所周知的道理,但是由于硬件设备和显存所限,所以模型不能无限制得放大。这篇文章提出了一个全面领先BERT模型的ALBERT,在比BERT-LARGE参数小的情况下超过了它。

有何区别

1. embedding 参数减少

在从one-hot embedding到hidden size embedding有一个$V \times H$的全连接层,这里使用了一个trick,加了一个hidden layer,从而使得全连接层变成了$V\times E + E\times H$。这样子我们就可以用一个很大的$H$了,比如在xxlarge上就是$H=4096$。

2.层间参数共享

很简单,就是原来模型类似于$F(x) = f_n(f_{n-1}(…f_1(x)))$,但是现在变成了$F(x)=f(f(…f(x)))$。我也在想,虽然$f(x)$是一个非线性的,但这种形式是不是可以有函数去拟合$F(x)$,毕竟重复$f(x)$这不能优化吗? 去压缩ALBERT模型的大小。

3. SOP

提出了一个新的self supervised learning 的 objective,既SOP(sentence ordering objectives)。类似于BERT预测两个句子是否是连续的,ALBERT需要预测打乱句子的顺序。

并在在对比中,SOP对于RACE也就是阅读理解任务提高了2.3个点,很哇塞

实验

实验部分具体暂且不表。我理解的有几点

  1. 额外的领域内预训练是有益的,但是领域外可能会有害
  2. dropout在模型不会over-fit的情况下其实可以忽略,在batch normalization和dropout可能会损害模型的性能。
  3. hidden size 4096 有可能是ALBERT 性能强的主要原因。
  4. 虽然层间参数共享,理论上可以无限深,但是实验发现24层并没有12层效果好。特别宽也没有特别好,这都是玄学调参,很难人工判断。
  5. 按理来说fffff(x) 可能会导致每层之间的输出过于相似,但在这里实验发现,并没有。难道是embed layer就很强了? 猜测

改进点

  1. 稀疏矩阵优化,attention 魔改
  2. SOP是否可以泛用。

模型解读

主类

forward先经过embeddings层再经过encoder层。这里注意,默认输入是用了最后一个隐层所有token的输出再经过一个线性+tanh的操作。

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
class AlbertModel(AlbertPreTrainedModel):
config_class = AlbertConfig
load_tf_weights = load_tf_weights_in_albert
base_model_prefix = "albert"
def __init__(self, config, add_pooling_layer=True):
super().__init__(config)

self.config = config
self.embeddings = AlbertEmbeddings(config)
self.encoder = AlbertTransformer(config)
if add_pooling_layer:
self.pooler = nn.Linear(config.hidden_size, config.hidden_size)
self.pooler_activation = nn.Tanh()
else:
self.pooler = None
self.pooler_activation = None

self.init_weights()

def get_input_embeddings(self):
return self.embeddings.word_embeddings

def set_input_embeddings(self, value):
self.embeddings.word_embeddings = value

def _resize_token_embeddings(self, new_num_tokens):
old_embeddings = self.embeddings.word_embeddings
new_embeddings = self._get_resized_embeddings(old_embeddings, new_num_tokens)
self.embeddings.word_embeddings = new_embeddings
return self.embeddings.word_embeddings

def _prune_heads(self, heads_to_prune):
"""Prunes heads of the model.
heads_to_prune: dict of {layer_num: list of heads to prune in this layer}
ALBERT has a different architecture in that its layers are shared across groups, which then has inner groups.
If an ALBERT model has 12 hidden layers and 2 hidden groups, with two inner groups, there
is a total of 4 different layers.

These layers are flattened: the indices [0,1] correspond to the two inner groups of the first hidden layer,
while [2,3] correspond to the two inner groups of the second hidden layer.

Any layer with in index other than [0,1,2,3] will result in an error.
See base class PreTrainedModel for more information about head pruning
"""
for layer, heads in heads_to_prune.items():
group_idx = int(layer / self.config.inner_group_num)
inner_group_idx = int(layer - group_idx * self.config.inner_group_num)
self.encoder.albert_layer_groups[group_idx].albert_layers[inner_group_idx].attention.prune_heads(heads)

@add_start_docstrings_to_callable(ALBERT_INPUTS_DOCSTRING.format("batch_size, sequence_length"))
@add_code_sample_docstrings(
tokenizer_class=_TOKENIZER_FOR_DOC,
checkpoint="albert-base-v2",
output_type=BaseModelOutputWithPooling,
config_class=_CONFIG_FOR_DOC,
)
def forward(
self,
input_ids=None,
attention_mask=None,
token_type_ids=None,
position_ids=None,
head_mask=None,
inputs_embeds=None,
output_attentions=None,
output_hidden_states=None,
return_dict=None,
):
output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions
output_hidden_states = (
output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
)
return_dict = return_dict if return_dict is not None else self.config.use_return_dict

if input_ids is not None and inputs_embeds is not None:
raise ValueError("You cannot specify both input_ids and inputs_embeds at the same time")
elif input_ids is not None:
input_shape = input_ids.size()
elif inputs_embeds is not None:
input_shape = inputs_embeds.size()[:-1]
else:
raise ValueError("You have to specify either input_ids or inputs_embeds")

device = input_ids.device if input_ids is not None else inputs_embeds.device

if attention_mask is None:
attention_mask = torch.ones(input_shape, device=device)
if token_type_ids is None:
token_type_ids = torch.zeros(input_shape, dtype=torch.long, device=device)

extended_attention_mask = attention_mask.unsqueeze(1).unsqueeze(2)
extended_attention_mask = extended_attention_mask.to(dtype=self.dtype) # fp16 compatibility
extended_attention_mask = (1.0 - extended_attention_mask) * -10000.0
head_mask = self.get_head_mask(head_mask, self.config.num_hidden_layers)

embedding_output = self.embeddings(
input_ids, position_ids=position_ids, token_type_ids=token_type_ids, inputs_embeds=inputs_embeds
)
encoder_outputs = self.encoder(
embedding_output,
extended_attention_mask,
head_mask=head_mask,
output_attentions=output_attentions,
output_hidden_states=output_hidden_states,
return_dict=return_dict,
)

sequence_output = encoder_outputs[0]

pooled_output = self.pooler_activation(self.pooler(sequence_output[:, 0])) if self.pooler is not None else None

if not return_dict:
return (sequence_output, pooled_output) + encoder_outputs[1:]

return BaseModelOutputWithPooling(
last_hidden_state=sequence_output,
pooler_output=pooled_output,
hidden_states=encoder_outputs.hidden_states,
attentions=encoder_outputs.attentions,
)


考研随想

考研感想

感触

现在是12月25号,考完研的第三天。在今天之前的前一个星期里,我每天都是7点左右自然醒。而终于在今天,我睡到了九点,但依然没有睡饱。我想考研带给我了很多,不只是那摞起来像山一样高的辅导书,还有一些随之带来的习惯的改变。

考研开始

高考之后

最开始听说考研应该是高考出成绩之后吧。那个时候因为考的太差,是真的对自己的人生失去了信心。之后听说到了大学以后,还可以靠着考研”翻身“(哪有什么翻身)。之后就下定决心,在大学期间冲冲冲,以考研考上浙大为目标。

得知专业之后

高考分数出来之后,还要进行选学校和选专业,但是因为分是在是太低了。只能随便选选了。最后来到njtech的这个环境科学专业。本着干一行爱一行的心态,我在网上搜索了很多关于环境科学的信息。但是越搜索我的心是越来越拔凉拔凉的。作为环化生材四大坑之首,环境绝对算得上是以先辈们的血一般地事实展示了环境专业的不受待见。之后,在知乎的熏染之下,我选择了转专业!

大一

大一的过程可以总结为:守望先锋+刷绩点。除此之外,啥活动都没有参加。

转专业以后

终于在大一下的时候,以环境科学专业第一的成绩转入了计算机科学与技术学院。并且加入了在大一的时候就听说过的acm比赛。但是投身于acm比赛的过程中,我也忘记了我进入大学的初始动机—-考研浙大。

acm区域赛3铜

可能因为暑假的半学半玩,也可能是因为自己的水品不行智商不够。我们队在2018年的下半年,去了三场区域赛(包括一场EC-Final)全部都拿了铜奖。还记得第一个铜的时候,心里是低落的。但是在听说有学长保研南大,有数理学院的学长保研的浙大计算机直博,并且在网上看了一下,保研中打acm的还算是少数,我保研的心情又高涨了起来。

2019年4月份

4,5月份是acm邀请赛开始的时候,但也是保研申请学校夏令营的高峰期,这时候不管是哪里的学生,都在疯狂地给老师发邮件,咨询保研的事项。但是我由于acm还要打个邀请赛,精力确实也分了一些。结果就上了个上科大和浙软的夏令营。(其实还是因为acm牌子不够响,也没有啥子科研经历)

暑假

暑假的时候,去了浙软的夏令营,并且考研数学过了高数,英语单词也背了不少。结果夏令营当场没给我优秀营员,但是在后面的优秀营员名单中是有我的。不过我们学院保研要求的是对方学校的确保接受证明,这tm谁有啊。只能灰溜溜地准备去考研了。

8月

此时正是考研的冲刺期,但是我考浙大计科,怕是去送命了,(浙大计科408考4门专业课,就算是再扎实的基础,也要个长时间的准备,而且我的数学一直属于刷个绩点的水品,并不能应付考研这个高强度考试。)所以根据我看到的浙软合作企业的项目,我决定tm考浙软,软件就软件,起码也是个浙大。

8-12月

冲冲冲!疯狂复习,虽然有时候也松懈个1天,但是这四个月我的考研复习效率还是可以的。养成了一些学习的习惯,比如写感想,摘录错题。

考研中

转眼之间就到了考研的时间了,12月21日-22日。

酒店中凌乱

虽然我的考点离学校蛮近的,但是我还是选择了订了个酒店,既可以中午的时候躺一会儿,也可以防止意外。所以我在周四的晚上,还去试睡了一下。结果就tm出现意外了。

论酒店隔音不好+旁边是一对激情似火的情侣的住房体验

所以我毅然决然地选择周五这个最重要的睡觉时间在宿舍里睡。

周五

宿舍中辗转难眠,因为周六考的是政治和英语,所以有大量的需要背诵的内容。我选择了在10.30睡觉,之后早上6.20左右爬起来再过一遍。