2021SC@SDUSC基于人工智能的多肽药物分析问题(十二)

基于人工智能的多肽药物分析问题(十二)

2021SC@SDUSC

1. 前言

上周预测模型已经分析到了IterBlock_w_Str这个层,并且已经详细解释了该层中的 str2str模型,以下图片是局部代码总览,本周,我们将继续分析余下部分内容

2. 代码分析

2.1 Str2MSA模型

class Str2MSA(nn.Module):
    def __init__(self, d_msa=64, d_state=32, inner_dim=32, r_ff=4,
                 distbin=[8.0, 12.0, 16.0, 20.0], p_drop=0.1):
        super(Str2MSA, self).__init__()
        self.distbin = distbin
        n_att_head = len(distbin)

        self.norm_state = LayerNorm(d_state)
        self.norm1 = LayerNorm(d_msa)
        self.attn = MaskedDirectMultiheadAttention(d_state, d_msa, n_att_head, d_k=inner_dim, dropout=p_drop) 
        self.dropout1 = nn.Dropout(p_drop,inplace=True)

        self.norm2 = LayerNorm(d_msa)
        self.ff = FeedForwardLayer(d_msa, d_msa*r_ff, p_drop=p_drop)
        self.dropout2 = nn.Dropout(p_drop,inplace=True)
    def forward(self, msa, xyz, state):
        dist = torch.cdist(xyz[:,:,1], xyz[:,:,1]) # (B, L, L)

        mask_s = list()
        for distbin in self.distbin:
            mask_s.append(1.0 - torch.sigmoid(dist-distbin))
        mask_s = torch.stack(mask_s, dim=1) # (B, h, L, L)
        
        state = self.norm_state(state)
        msa2 = self.norm1(msa)
        msa2 = self.attn(state, state, msa2, mask_s)
        msa = msa + self.dropout1(msa2)

        msa2 = self.norm2(msa)
        msa2 = self.ff(msa2)
        msa = msa + self.dropout2(msa2)

该模型中,定义了一些比较常规的神经网络层,我们可以着重看一下该神经网络各个层的顺序

输入的参数经过的第一层首先是LayerNorm这个自定义的模型,该模型对输入数据做了一些处理,使用到了torch.nn.Parameter的方法,将不属于经典深度学习网络模型的参数,变成了模型中可迭代训练的参数通常。因为一般来说我们的参数都是一些常见的结构(卷积、全连接等)里面的计算参数。而当我们的网络有一些其他的设计时,会需要一些额外的参数同样很着整个网络的训练进行学习更新,最后得到最优的值,经典的例子有注意力机制中的权重参数和positional embedding等。在该模型的后面就用到了注意力机制模型的参数迭代学习。然后再经过一个多注意力机制模型,然后是第二个LayerNorm模型,将参数转化为可迭代参数,然后经过dropout层防止过拟合现象。

MaskedDirectMultiheadAttention

上面的模型有一个未提到过的自定义模型,现在对他进行一个详细的介绍:

class MaskedDirectMultiheadAttention(nn.Module):
    def __init__(self, d_in, d_out, heads, d_k=32, dropout=0.1):
        super(MaskedDirectMultiheadAttention, self).__init__()
        self.heads = heads
        self.scaling = 1/math.sqrt(d_k)
        
        self.to_query = nn.Linear(d_in, heads*d_k)
        self.to_key   = nn.Linear(d_in, heads*d_k)
        self.to_value = nn.Linear(d_out, d_out)
        self.to_out   = nn.Linear(d_out, d_out)
        self.dropout = nn.Dropout(dropout, inplace=True)

    def forward(self, query, key, value, mask):
        batch, N, L = value.shape[:3] 

        q = self.to_query(query).view(batch, L, self.heads, -1).permute(0,2,1,3) 
        k = self.to_key(key).view(batch, L, self.heads, -1).permute(0,2,1,3) 
        v = self.to_value(value).view(batch, N, L, self.heads, -1).permute(0,3,1,2,4) 
        
        q = q*self.scaling
        attention = torch.matmul(q, k.transpose(-2, -1)) 
        attention = attention.masked_fill(mask < 0.5, torch.finfo(q.dtype).min)
        attention = F.softmax(attention, dim=-1) 
        attention = self.dropout(attention) 
        

        out = torch.einsum('bhij,bhnjk->bhnik', attention, v) 
        out = out.permute(0,2,3,1,4).contiguous().view(batch, N, L, -1)
        
        out = self.to_out(out)
        return out

Multi-Head Attention是transformer中的一种机制,虽然文中所示模型是一个自定义的模型,我们仍然可以借鉴transformer中的思想来进行学习分析,对于同一个文本,一个Attention获得一个表示空间,如果多个Attention,则可以获得多个不同的表示空间。基于这种想法,就有了Multi-Head Attention。换句话说,Multi-Head Attention为Attention提供了多个“representation subspaces”。因为在每个Attention中,采用不同的Query / Key / Value权重矩阵,每个矩阵都是随机初始化生成的。然后通过训练,将词嵌入投影到不同的“representation subspaces(表示子空间)”中。

Multi-Head Attention与经典的Attention一样,并不是一个独立的结构,自身无法进行训练。Multi-Head Attention也可以堆叠,形成深度结构。应用场景:可以作为文本分类、文本聚类、关系抽取等模型的特征表示部分。

Multi-Head Attention与Self-Attention的关系是:Multi-Head Attention的Attention可以是Self-Attention,当然也可以是经典的Attention。

img

而在我们的自定义模型中,我们也可以看到,第16~18行使用全连接层投影得到了相应的Query / Key / Value权重矩阵,之后利用这三个权重矩阵进行相应的数据处理,将attention变量从(B, h, L, L)这样的四元组,依次转化为了(B, h, L1, L2),最后在第三维加了常量变成五维变量。

2.2 FinalBlock模型

在IterativeFeatureExtractor模型中,最后一层调用的是FinalBlock模型,代码详见下方:

# 
self.final = FinalBlock(n_layer=n_layer, d_msa=d_msa, d_pair=d_pair,
                               n_head_msa=n_head_msa, n_head_pair=n_head_pair, r_ff=r_ff,
                               n_resblock=n_resblock, p_drop=p_drop,
                               performer_L_opts=performer_L_opts, performer_N_opts=performer_N_opts,
                               SE3_param=SE3_param)
class FinalBlock(nn.Module):
    def __init__(self, n_layer=1, d_msa=64, d_pair=128, n_head_msa=4, n_head_pair=8, r_ff=4,
                 n_resblock=1, p_drop=0.1, performer_L_opts=None, performer_N_opts=None,
                 SE3_param={'l0_in_features':32, 'l0_out_features':16, 'num_edge_features':32}):
        super(FinalBlock, self).__init__()
        
        self.msa2msa = MSA2MSA(n_layer=n_layer, n_att_head=n_head_msa, n_feat=d_msa,
                               r_ff=r_ff, p_drop=p_drop,
                               performer_N_opts=performer_N_opts,
                               performer_L_opts=performer_L_opts)
        self.msa2pair = MSA2Pair(n_feat=d_msa, n_feat_out=d_pair, n_feat_proj=32,
                                 n_resblock=n_resblock, p_drop=p_drop, n_att_head=n_head_msa)
        self.pair2pair = Pair2Pair(n_layer=n_layer, n_att_head=n_head_pair,
                                   n_feat=d_pair, r_ff=r_ff, p_drop=p_drop,
                                   performer_L_opts=performer_L_opts)
        self.pair2msa = Pair2MSA(n_layer=n_layer, n_att_head=4, 
                                 n_feat_in=d_pair, n_feat_out=d_msa, r_ff=r_ff, p_drop=p_drop)
        self.str2str = Str2Str(d_msa=d_msa, d_pair=d_pair, SE3_param=SE3_param, p_drop=p_drop)
        self.norm_state = LayerNorm(SE3_param['l0_out_features'])
        self.pred_lddt = nn.Linear(SE3_param['l0_out_features'], 1)
    def forward(self, msa, pair, xyz, seq1hot, idx):

        msa, att = self.msa2msa(msa)

        pair = self.msa2pair(msa, pair, att)

        pair = self.pair2pair(pair)
       
        msa = self.pair2msa(pair, msa)

        xyz, state = self.str2str(msa.float(), pair.float(), xyz.float(), seq1hot, idx, top_k=32)
        
        lddt = self.pred_lddt(self.norm_state(state))
        return msa, pair, xyz, lddt.squeeze(-1)

由于该模型中的各层都已经讲过了,那么我们直接看该模型的调用,从上述代码的27~40行我们可以看到,该模型的调用和之前的模型顺序类似:

该模型接受的输入包括初始的MSA数据,,通过MSA2MSA层处理MSA特征,在经过MSA2Pair层利用处理后的MSA更新残基对数据,之后是一个往返过程,在上一层利用MSA数据更新了残基对数据之后,再分别经过Pair2Pair和Pair2MSA两层最终将MSA的参数进行更新。最终是将msa数据和pair数据均返回。

至此,我们就已经分析完了IterativeFeatureExtractor这个模型,在我们最外层的模型RoseTTAFoldModule中,仅剩下最后一个DistanceNetwork模型,见下方

2.3 DistanceNetwork模型

class DistanceNetwork(nn.Module):
    def __init__(self, n_feat, n_block=1, block_type='orig', p_drop=0.0):
        super(DistanceNetwork, self).__init__()
        self.norm = LayerNorm(n_feat)
        self.proj = nn.Linear(n_feat, n_feat)
        self.drop = nn.Dropout(p_drop)
        #
        self.resnet_dist = ResidualNetwork(n_block, n_feat, n_feat, 37, block_type=block_type, p_drop=p_drop)
        self.resnet_omega = ResidualNetwork(n_block, n_feat, n_feat, 37, block_type=block_type, p_drop=p_drop)
        self.resnet_theta = ResidualNetwork(n_block, n_feat, n_feat, 37, block_type=block_type, p_drop=p_drop)
        self.resnet_phi = ResidualNetwork(n_block, n_feat, n_feat, 19, block_type=block_type, p_drop=p_drop)

    def forward(self, x):
        x = self.norm(x)
        x = self.drop(self.proj(x))
        x = x.permute(0,3,1,2).contiguous()
        
        logits_theta = self.resnet_theta(x)
        logits_phi = self.resnet_phi(x)

        x = 0.5 * (x + x.permute(0,1,3,2))
        logits_dist = self.resnet_dist(x)
        logits_omega = self.resnet_omega(x)

        return logits_dist, logits_omega, logits_theta, logits_phi

该模型的主要作用是根据序列对特征预测distance map,使用的是2D ResNet模型。

ResNet的引入:

ResNet是一种残差网络,咱们可以把它理解为一个子网络,这个子网络经过堆叠可以构成一个很深的网络。网络越深,咱们能获取的信息越多,而且特征也越丰富。但是根据实验表明,随着网络的加深,优化效果反而越差,测试数据和训练数据的准确率反而降低了。按道理,给网络叠加更多层,浅层网络的解空间是包含在深层网络的解空间中的,深层网络的解空间至少存在不差于浅层网络的解,因为只需将增加的层变成恒等映射,其他层的权重原封不动copy浅层网络,就可以获得与浅层网络同样的性能。**更好的解明明存在,为什么找不到?找到的反而是更差的解?**显然,这是个优化问题,反映出结构相似的模型,其优化难度是不一样的,且难度的增长并不是线性的,越深的模型越难以优化。**是由于网络的加深会造成梯度爆炸和梯度消失的问题。**目前针对这种现象已经有了解决的方法:对输入数据和中间层的数据进行归一化操作,这种方法可以保证网络在反向传播中采用随机梯度下降(SGD),从而让网络达到收敛。但是,这个方法仅对几十层的网络有用,当网络再往深处走的时候,这种方法就无用武之地了。

为了让更深的网络也能训练出好的效果,何凯明大神提出了一个新的网络结构——ResNet。这个网络结构的想法主要源于VLAD(残差的想法来源)和Highway Network(跳跃连接的想法来源)。

所以,总的来说,ResNet的主要作用是让更深的网络能够避免梯度爆炸和梯度消失。解决的是深度神经网络的“退化”问题。

接着回到我们的模型,调用该模型时接受(B, L, L, C)四维的输入,经过ResNet后返回相应数据。

下面是ResidualNetwork的具体定义代码

class ResidualNetwork(nn.Module):
    def __init__(self, n_block, n_feat_in, n_feat_block, n_feat_out, 
                 dilation=[1,2,4,8], block_type='orig', p_drop=0.15):
        super(ResidualNetwork, self).__init__()


        layer_s = list()
        # project to n_feat_block
        if n_feat_in != n_feat_block:
            layer_s.append(nn.Conv2d(n_feat_in, n_feat_block, 1, bias=False))
            if block_type =='orig': # should acitivate input
                layer_s.append(nn.InstanceNorm2d(n_feat_block, affine=True, eps=1e-6))
                layer_s.append(nn.ELU(inplace=True))

        # add resblocks
        for i_block in range(n_block):
            d = dilation[i_block%len(dilation)]
            if block_type == 'orig':
                res_block = ResBlock2D(n_feat_block, kernel=3, dilation=d, p_drop=p_drop)
            else:
                res_block = ResBlock2D_bottleneck(n_feat_block, kernel=3, dilation=d, p_drop=p_drop)
            layer_s.append(res_block)

        if n_feat_out != n_feat_block:
            # project to n_feat_out
            layer_s.append(nn.Conv2d(n_feat_block, n_feat_out, 1))
        
        self.layer = nn.Sequential(*layer_s)
    
    def forward(self, x):
        output = self.layer(x)
        return output

到此,RoseTTAFoldModule部分结束

3. 总结

本周已经结束了pyrosetta模式的预测分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值