赞
踩
这篇文章将借助Albert的代码将Albert和Bert简单介绍一下。
O(V * H) to O(V * E + E * H) V:字典个数。 E:输入层。 H:隐藏层。
如以ALBert_xxlarge为例,V=30000, H=4096, E=128
那么原先参数为V * H= 30000 * 4096 = 1.23亿个参数,现在则为V * E + E * H = 30000*128+128*4096 = 384万 + 52万 = 436万,
词嵌入相关的参数变化前是变换后的28倍。
可以选择共享selfattention,feedforward,或者两个都共享,他们默认都是全部共享。
bert 的两个句子是从同一语料库随机抽取,Albert认为这种方式对机器识别太过简单,所以采用首先获得的两个句子都是有关联的,然后50%的几率使他们从texta,textb-->textb,texta
1)去掉了dropout
最大的模型,训练了1百万步后,还是没有过拟合训练数据。说明模型的容量还可以更大,就移除了dropout
2)使用LAMB做为优化器(优化器只是可以加快训练速度)
3)采用n-gram
Bert采用的随机mask某个单字,而Albert采用n-gram是mask片段(最大三个单词),但是我获得的中文训练模型并不是采用这中方法。采用的全词Mask(好像是和哈工大的WWM是一样的)
https://github.com/huggingface/transformers
上面网站团队这几天已经把许多模型形成了接口,需要的可以自己看看
(这部分Bert和Albert相同)
对于中文,只是简单的将单个字分开,想详细了解的可以看看下面的网站,讲的还是挺好的。
–> https://blog.csdn.net/u010099080/article/details/102587954
以下的代码都不是完整的,我去掉了一下不是很重要的,例如归一化和正则的都去掉了,还有一些实际使用中并没使用的功能,例如,代码中还有不经过feedforward的实现。
对应的transformer下图所示的位置
这地方,我们看到,一共两个输入,input Embedding和PositionEncoding,但是实际上来说一共有三层,还有一个token_type_embedding ,这三个是干嘛的呢,分别解释。
input embedding:就是我们的文本输入(Albert和Bert会有不同,见上面的不同1),将每个单词对应一个随机初始化的字典。这个维度是(批量大小,句子长度,单词向量长度)。
Position Encoding: 这个是为了下面做attention的时候,对于近处和远处的单词有个区分,注意这里论文中说的是用一个三角函数计算出来的,但是实际代码中还是随机初始化的。维度同input
token_type_embedding:Bert的一种预训练方式就是将输入两个句子,而这个就是做这个分别的,如果是两个句子就是(0,0…0, 1, 1…1),0表示第一个句子,1表示第二个句子。维度也是同input。
最后输入就是三个embedding相加。
class AlbertEmbeddings(nn.Module): def __init__(self, config): super(AlbertEmbeddings, self).__init__() self.word_embeddings = nn.Embedding(config.vocab_size, config.embedding_size, padding_idx=0) self.word_embeddings_2 = nn.Linear(config.embedding_size, config.hidden_size, bias=False) # 在此处Bert只有一个word_embeddings ,正是Albert的因式分解的变化,下面是Bert的 self.word_embeddings = nn.Embedding(config.vocab_size, config.hidden_size, padding_idx=0) def forward(self, input_ids, token_type_ids=None, position_ids=None): seq_length = input_ids.size(1) # 位置嵌入的信息是随机初始化的 if position_ids is None: position_ids = torch.arange(seq_length, dtype=torch.long, device=input_ids.device)# position_ids = position_ids.unsqueeze(0).expand_as(input_ids) # 完成从随机的字典中取E的向量,再转化成H,我这E=128,H=312 words_embeddings = self.word_embeddings(input_ids) words_embeddings = self.word_embeddings_2(words_embeddings) position_embeddings = self.position_embeddings(position_ids) token_type_embeddings = self.token_type_embeddings(token_type_ids) embeddings = words_embeddings + position_embeddings + token_type_embeddings return embeddings
接下来就是Bert的比较重要的模块
大家可能已经看了许多Bert的详解了,我这里用维度变化来解释一下。
1.首先看这张图,input的输入是(batch_size , seq, hidden)我假设embedding层的输入三个分别是(128,256,312)
2. 将input与Wq, Wk, Wv 点积,代码中可以看到,W的维度是(hidden,config.hidden_size / config.num_attention_heads * self.attention_head_size)其实还是(hidden,hidden)即(312,312),生成的Q,K,V也即图示。
3. 为什么要生成Q,K,V呢,就是为了attention,为什么要attention(还没写,占个坑)。如何进行attention,将每一个单词的Q与所有单词的K进行点积,但是我们说Bert是多头注意力,如何实现呢,在其他的讲解Bert的文章中,他们都说是将Q,K,V分解,然后计算然后累加,我一开始预想的是先分解,再累加,但是实际上维度变化可以一起完成。我假设头的个数是12,将Q,K,V在hidden维度上进行分解,这里我理解是将每一个单词在不同维度的信息能够进行“相互交流“。最后结果为(128,256,12, 26)
4. 紧接着为了让每个单词可以在字向量维度进行"交流",将K的维度变化.
5. 之后Q*K=(128, 12,256,256) ,在代码我们看到在除以√K以后进行了attention_scores + attention_mask,这是在干嘛呢,在我们输入的句子的时候,句子的长度是不一样的,通常是采用最长的句子长度作为seq_length,其他没有这么长的进行padding,但是在后面需要进行softmax,获得自注意力矩阵:
就是每一行表示每个字对其他字的关注程度,每一行的和都是1.(这是我随便写的)
但是softmax函数是这样的,上面的e0会等于1,也就是那些padding的会对上面的分数产生影响,为了避免这个,需要加一个很的的负数,使得ei 接近0.
6.处理完之后,将结果与V进行点积,再进行维度变化,就恢复了维度.
class BertSelfAttention(nn.Module): def __init__(self, config): super(BertSelfAttention, self).__init__() #多头的个数是自己定义的,我这是12, self.num_attention_heads = config.num_attention_heads#头数是12 self.attention_head_size = int(config.hidden_size / config.num_attention_heads)#312/12 self.all_head_size = self.num_attention_heads * self.attention_head_size#多头总个数 #q,v,k初始化都是[H,H]即[312,312] self.query = nn.Linear(config.hidden_size, self.all_head_size) self.key = nn.Linear(config.hidden_size, self.all_head_size) self.value = nn.Linear(config.hidden_size, self.all_head_size) self.dropout = nn.Dropout(config.attention_probs_dropout_prob) #分解并进行转置来实现q,k,v可以相乘,[batch_size, seq_length, H]--> #[batch,seq_length,num_heads,hidding/num_heads]-->[batch,num_heads, seq,hidden/num_heads] def transpose_for_scores(self, x): new_x_shape = x.size()[:-1] + (self.num_attention_heads, self.attention_head_size) x = x.view(*new_x_shape) return x.permute(0, 2, 1, 3) def forward(self, hidden_states, attention_mask, head_mask=None): #只取了q的代码来说明,完成q*w mixed_query_layer = self.query(hidden_state) #用上面的函数对q,k,v分解成多头 query_layer = self.transpose_for_scores(mixed_query_layer) #Q K 求点积 attention_scores = torch.matmul(query_layer, key_layer.transpose(-1, -2)) #除以K的,开平方根归一为标准正态分布 attention_scores = attention_scores / math.sqrt(self.attention_head_size) #下面的attention_mask 是为了填充句子长度不一,他的维度是[batch_size, 1, 1, seq_length] #后面有这个实现的函数 attention_scores = attention_scores + attention_mask attention_probs = nn.Softmax(dim=-1)(attention_scores) #乘以V[batch_size, length, H] context_layer = torch.matmul(attention_probs, value_layer) #这地方把q*k的结果也获得了,但实际并没有使用 outputs = (context_layer, attention_probs) if self.output_attentions else (context_layer,) return outputs
class BertSelfOutput(nn.Module): def __init__(self, config): super(BertSelfOutput, self).__init__() self.dense = nn.Linear(config.hidden_size, config.hidden_size)#【H,H】 self.LayerNorm = BertLayerNorm(config.hidden_size, eps=config.layer_norm_eps) #残差连接并且归一化 def forward(self, hidden_states, input_tensor): #进行一个线性变化 hidden_states = self.dense(hidden_states) hidden_states = self.dropout(hidden_states) #这个preln不知道是在干啥,这个在Bert里并没有,且Albert的下游任务中并没有使用这个,我猜测是某个预训练的方式,如果是ln_type是这个的后面没有进行feedforward, if self.ln_type == 'preln': # preln hidden_states = hidden_states + input_tensor else: # postln,直接将embedding的输出和self层的输出相加 hidden_states = self.LayerNorm(hidden_states + input_tensor) return hidden_states
将self和add封装一下,这里有Bert中没有对多头进行随机减少,但是下游任务没有使用
class BertAttention(nn.Module): def __init__(self, config): super(BertAttention, self).__init__() self.self = BertSelfAttention(config)#获得selfAttention self.output = BertSelfOutput(config)#add self.pruned_heads = set()#<class 'set'>: set() self.ln_type = config.ln_type #这个类其他的就是封装一下self和add层,这下面的函数也是Bert没有的,用来随机化减少头的个数 def prune_heads(self, heads): if len(heads) == 0: return mask = torch.ones(self.self.num_attention_heads, self.self.attention_head_size) heads = set(heads) - self.pruned_heads # Convert to set and emove already pruned heads for head in heads: # Compute how many pruned heads are before the head and move the index accordingly head = head - sum(1 if h < head else 0 for h in self.pruned_heads) mask[head] = 0 mask = mask.view(-1).contiguous().eq(1) index = torch.arange(len(mask))[mask].long() # 只保留上面随机产生的index中有的头 self.self.query = prune_linear_layer(self.self.query, index) self.self.key = prune_linear_layer(self.self.key, index) self.self.value = prune_linear_layer(self.self.value, index) self.output.dense = prune_linear_layer(self.output.dense, index, dim=1) # 保存 self.self.num_attention_heads = self.self.num_attention_heads - len(heads) self.self.all_head_size = self.self.attention_head_size * self.self.num_attention_heads self.pruned_heads = self.pruned_heads.union(heads) def forward(self, input_tensor, attention_mask=None, head_mask=None): if self.ln_type == 'preln': hidden_state = self.output.LayerNorm(input_tensor)#对输入先归一再输入 self_outputs = self.self(hidden_state, attention_mask, head_mask) else: self_outputs = self.self(input_tensor, attention_mask, head_mask)#进行self-attention attention_output = self.output(self_outputs[0], input_tensor)#进行残差连接 outputs = (attention_output,) + self_outputs[1:] # 加入selfattention结果,如果需要可以取出来 return outputs
就是一个简单的线性变化
class BertIntermediate(nn.Module):
def __init__(self, config):
super(BertIntermediate, self).__init__()
#y = wx+b
self.dense = nn.Linear(config.hidden_size, config.intermediate_size)
self.intermediate_act_fn = config.hidden_act#激活函数gelu或swish
def forward(self, hidden_states):
hidden_states = self.dense(hidden_states)#torch.Size([8, 480, 1248])
hidden_states = self.intermediate_act_fn(hidden_states)#torch.Size([8, 480, 1248])
return hidden_states
,就是神经网络的一层,这里面实现的权重共享,这里是Albert与Bert不同的地方,Bert是没有这个权重共享的.
class BertLayer(nn.Module): def __init__(self, config): super(BertLayer, self).__init__() self.ln_type = config.ln_type#'postln' #处理共享机制,最小的Albert使用了4层网络 if config.share_type == 'ffn': #这里就是如果共享feed层就只创造一个feed层 self.attention = ([BertAttention(config) for _ in range(config.num_hidden_layers)]) self.intermediate = BertIntermediate(config) self.output = BertOutput(config) elif config.share_type == 'attention': self.attention = BertAttention(config) self.intermediate = nn.ModuleList([BertIntermediate(config) for _ in range(config.num_hidden_layers)]) self.output = nn.ModuleList([BertOutput(config) for _ in range(config.num_hidden_layers)]) else: self.attention = BertAttention(config)#获得attention的输出 self.intermediate = BertIntermediate(config) self.output = BertOutput(config) #分享函数 def forward(self, hidden_states, attention_mask, layer_num, head_mask=None): if isinstance(self.attention, nn.ModuleList): # 处理attention attention_outputs = self.attention[layer_num](hidden_states, attention_mask, head_mask) else: attention_outputs = self.attention(hidden_states, attention_mask, head_mask)#经过这 #因为所有的输出都加了一个attention list,到这里处理完self 和 残差连接 attention_output = attention_outputs[0] #如果是这个就不用feed层 if self.ln_type == 'preln': if isinstance(self.intermediate, nn.ModuleList): # share attention 自注意力机制 attention_output_pre = self.output[layer_num].LayerNorm(attention_output) else: attention_output_pre = self.output.LayerNorm(attention_output) else: attention_output_pre = attention_output #处理feed forward, 输出层 if isinstance(self.intermediate, nn.ModuleList): # share attention intermediate_output = self.intermediate[layer_num](attention_output_pre) layer_output = self.output[layer_num](intermediate_output, attention_output) else: intermediate_output = self.intermediate(attention_output_pre) layer_output = self.output(intermediate_output, attention_output) outputs = (layer_output,) + attention_outputs[1:] return outputs
就是把输出的cls向量取出来做分类任务,解释是,经过attention机制,cls也获得了句子中所有字的信息
class BertPooler(nn.Module):
def __init__(self, config):
super(BertPooler, self).__init__()
self.dense = nn.Linear(config.hidden_size, config.hidden_size)#312,312
self.activation = nn.Tanh()#激活函数
def forward(self, hidden_states):
#就是简单的把cls取出来
first_token_tensor = hidden_states[:, 0]
pooled_output = self.dense(first_token_tensor)
pooled_output = self.activation(pooled_output)
return pooled_output
这下面的模型都只是预先处理,后面LM要进行soft,而NOP也要进行个预测,比较简单,就不贴出来了。
class AlbertLMPredictionHead(nn.Module):
def __init__(self, config):
super(AlbertLMPredictionHead, self).__init__()
self.transform = BertPredictionHeadTransform(config)
#上面是创建一个线性映射层, 把transformer block输出的[batch_size, seq_len, H]映射为[batch_size, seq_len, vocab_size], 也就是把最后一个维度映射成字典中字的数量, 获取MaskedLM的预测结果
self.project_layer = nn.Linear(config.hidden_size, config.embedding_size, bias=False)
self.decoder = nn.Linear(config.embedding_size, config.vocab_size, bias=False)
self.bias = nn.Parameter(torch.zeros(config.vocab_size))
def forward(self, hidden_states):
hidden_states = self.transform(hidden_states)
hidden_states = self.project_layer(hidden_states)
hidden_states = self.decoder(hidden_states) + self.bias
return hidden_states
class AlbertPreTrainingHeads(nn.Module):
def __init__(self, config):
super(AlbertPreTrainingHeads, self).__init__()
#调用上面类进行LM训练
self.predictions = AlbertLMPredictionHead(config)
#映射成2,进行NOP的训练
self.seq_relationship = nn.Linear(config.hidden_size, 2)
#
def forward(self, sequence_output, pooled_output):
prediction_scores = self.predictions(sequence_output)
seq_relationship_score = self.seq_relationship(pooled_output)
return prediction_scores, seq_relationship_score
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。