当前位置:   article > 正文

ALbert和Bert简单解读_albert的向量长度

albert的向量长度

Albert和Bert代码简单介绍

这篇文章将借助Albert的代码将Albert和Bert简单介绍一下。

一 区别(Albert变化)

  • 词嵌入向量参数的因式分解
 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倍。
  • 1
  • 2
  • 3
  • 4
  • 跨层参数共享
可以选择共享selfattention,feedforward,或者两个都共享,他们默认都是全部共享。
  • 1
  • NSP–> NOP
bert 的两个句子是从同一语料库随机抽取,Albert认为这种方式对机器识别太过简单,所以采用首先获得的两个句子都是有关联的,然后50%的几率使他们从texta,textb-->textb,texta
  • 1
  • 其他变化
1)去掉了dropout
    最大的模型,训练了1百万步后,还是没有过拟合训练数据。说明模型的容量还可以更大,就移除了dropout
2)使用LAMB做为优化器(优化器只是可以加快训练速度)
3)采用n-gram
	Bert采用的随机mask某个单字,而Albert采用n-gram是mask片段(最大三个单词),但是我获得的中文训练模型并不是采用这中方法。采用的全词Mask(好像是和哈工大的WWM是一样的)
  • 1
  • 2
  • 3
  • 4
  • 5

二 代码简读

pytorch代码来源

https://github.com/huggingface/transformers

上面网站团队这几天已经把许多模型形成了接口,需要的可以自己看看
  • 1
分词部分

(这部分Bert和Albert相同)
对于中文,只是简单的将单个字分开,想详细了解的可以看看下面的网站,讲的还是挺好的。
–> https://blog.csdn.net/u010099080/article/details/102587954

主模型部分

以下的代码都不是完整的,我去掉了一下不是很重要的,例如归一化和正则的都去掉了,还有一些实际使用中并没使用的功能,例如,代码中还有不经过feedforward的实现。

Embedding层介绍

对应的transformer下图所示的位置
Bert输入
这地方,我们看到,一共两个输入,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 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
Selfattention

接下来就是Bert的比较重要的模块
selfattention
大家可能已经看了许多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
  • 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
第一次的add
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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
attention

将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
  • 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
feed层

就是一个简单的线性变化

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
封装self 和 feed层

,就是神经网络的一层,这里面实现的权重共享,这里是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
  • 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
取出cls

就是把输出的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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
预训练的训练方式

这下面的模型都只是预先处理,后面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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/573116
推荐阅读
相关标签
  

闽ICP备14008679号