手撕transformer(代码)

序言:总结一下之前transformer的手撕代码demo。顺便附带一些简单的面试问题.

transformer网络结构:

transformer流程简述:

transformer流程:

①先进行数据前处理Tokenizer符号化,nn.Embedding词嵌入,对齐。

②然后Positional Encoding位置编码(生成正弦/余弦位置编码)

③进行Encoder编码

        1,多头注意力: QKA同源,多头分割,计算注意力加权,注意力权重 * V值矩阵,合并输出

        2,残差连接后做LN

        3,前馈网络,等于做了高效的全连接

        4,再残差连接做LN

        5,输出K,V给Decoder用

④进行Decoder解码。

        1,多头注意力: QKA同源

        2,残差连接后做LN

        3,交叉多头注意力:Decoder的自注意力输出,与来自于EncoderK,V。

        4,残差连接后做LN

        5,前馈网络

⑤FC全连接,softmax分类。

transformer模块代码(demo,实测可运行)

# Written on June 2, 2025, by sn
# just a mode for understand transformer

import torch
import numpy as np
import torch.nn as nn

class MultiHeadAttention(nn.Module):
    """多头注意力机制"""

    def __init__(self, d_k: int, d_v: int, d_model: int, num_heads: int, p: float = 0.):
        """
        Args:
            d_k: 每个头的键/查询维度
            d_v: 每个头的值维度
            d_model: 模型维度
            num_heads: 注意力头数
            p: dropout概率
        """
        super(MultiHeadAttention, self).__init__()
        self.d_model = d_model
        self.d_k = d_k
        self.d_v = d_v
        self.num_heads = num_heads
        self.dropout = nn.Dropout(p)

        # 线性投影层
        self.W_Q = nn.Linear(d_model, d_k * num_heads)
        self.W_K = nn.Linear(d_model, d_k * num_heads)
        self.W_V = nn.Linear(d_model, d_v * num_heads)
        self.W_out = nn.Linear(d_v * num_heads, d_model)

        # 使用He初始化
        nn.init.normal_(self.W_Q.weight, mean=0, std=np.sqrt(2.0 / (d_model + d_k)))
        nn.init.normal_(self.W_K.weight, mean=0, std=np.sqrt(2.0 / (d_model + d_k)))
        nn.init.normal_(self.W_V.weight, mean=0, std=np.sqrt(2.0 / (d_model + d_v)))
        nn.init.normal_(self.W_out.weight, mean=0, std=np.sqrt(2.0 / (d_model + d_v)))

    pass

    def forward(self, Q: torch.Tensor, K: torch.Tensor, V: torch.Tensor,
                attn_mask: torch.Tensor = None, **kwargs) -> torch.Tensor:
        """
        Args:
            Q: 查询矩阵 (N, q_len, d_model)
            K: 键矩阵 (N, k_len, d_model)
            V: 值矩阵 (N, k_len, d_model)
            attn_mask: 注意力掩码 (N, q_len, k_len)
        Returns:
            多头注意力输出 (N, q_len, d_model)
        """
        '''1. 获取输入形状和参数'''
        N = Q.size(0)                           # batch_size
        q_len, k_len = Q.size(1), K.size(1)     # 查询和键的长度
        d_k, d_v = self.d_k, self.d_v           # 每个头的键/值维度
        num_heads = self.num_heads              # 头数

        '''2. 线性投影 + 多头分割 '''
        # Q.shape: (N, q_len, d_model)->
        # W_Q(Q): (N, q_len, d_k * num_heads)->view: (N, q_len, num_heads, d_k)->transpose: (N, num_heads, q_len, d_k)
        Q = self.W_Q(Q).view(N, -1, num_heads, d_k).transpose(1, 2)
        K = self.W_K(K).view(N, -1, num_heads, d_k).transpose(1, 2)
        V = self.W_V(V).view(N, -1, num_heads, d_v).transpose(1, 2)

        '''3. 处理注意力掩码'''
        if attn_mask is not None:
            assert attn_mask.size() == (N, q_len, k_len), "掩码形状应为(batch, q_len, k_len)"
        # 将掩码广播到多头维度:(N, q_len, k_len)
        # -> unsqueeze(1): (N, 1, q_len, k_len) -> expand: (N, num_heads, q_len, k_len)
        attn_mask = attn_mask.unsqueeze(1).expand(-1, num_heads, -1, -1)

        '''4. 计算缩放点积注意力'''
        # 计算注意力分数: Q * K^T / sqrt(d_k)  scores形状: (N, num_heads, q_len, k_len)
        # Q 和 K 相乘可以 衡量两个向量的相似度,
        scores = torch.matmul(Q, K.transpose(-1, -2)) / np.sqrt(d_k)
        # 应用掩码(将需要屏蔽的位置设为极小的负值,softmax后接近0)
        if attn_mask is not None:
            scores.masked_fill_(attn_mask, -1e4)
        # Softmax归一化得到注意力权重
        attns = torch.softmax(scores, dim=-1)  # 形状: (N, num_heads, q_len, k_len)

        ''' 5. 计算加权和 '''
        output = torch.matmul(attns, V)     # 注意力权重 * 值矩阵

        '''6. 合并多头输出'''
        # output形状: (N, num_heads, q_len, d_v) tips:contiguous()内存连续
        # -> transpose(1,2): (N, q_len, num_heads, d_v)-> view: (N, q_len, num_heads * d_v) [合并所有头的输出]
        output = output.transpose(1, 2).contiguous().view(N, -1, d_v * num_heads)

        '''7. 最终线性投影'''
        # output形状: (N, q_len, num_heads * d_v) -> W_out: (N, q_len, d_model)
        end_output = self.W_out(output)
        return end_output

    pass



class PoswiseFFN(nn.Module):
    """位置前馈网络"""

    def __init__(self, d_model: int, d_ff: int, p: float = 0.):
        """
        Args:
            d_model: 模型维度
            d_ff: 前馈网络隐藏层维度
            p: dropout概率
        """
        super(PoswiseFFN, self).__init__()
        self.d_model = d_model
        self.d_ff = d_ff
        self.conv1 = nn.Conv1d(d_model, d_ff, 1)  # 等价于(高效)全连接层 并升维
        self.conv2 = nn.Conv1d(d_ff, d_model, 1)  # 降维保持维度一致
        self.relu = nn.ReLU(inplace=True)
        self.dropout = nn.Dropout(p=p)

    def forward(self, X: torch.Tensor) -> torch.Tensor:
        """
        Args:
            X: 输入张量 (N, seq_len, d_model)
        Returns:
            输出张量 (N, seq_len, d_model)
        """
        out = self.conv1(X.transpose(1, 2))  # (N, d_ff, seq_len)
        out = self.relu(out)
        out = self.conv2(out).transpose(1, 2)          # (N, d_model, seq_len)
        out = self.dropout(out)
        return out


class EncoderLayer(nn.Module):
    """编码器层"""
    def __init__(self, dim: int, n: int, dff: int, dropout_posffn: float, dropout_attn: float):
        """
        Args:
            dim: 输入维度
            n: 注意力头数
            dff: 前馈网络隐藏层维度
            dropout_posffn: 前馈网络dropout概率
            dropout_attn: 注意力dropout概率
            多头:并行计算多个注意力子空间,提升模型的表达能力、泛化能力和对复杂依赖关系的捕捉能力
        """
        super(EncoderLayer, self).__init__()
        assert dim % n == 0, "模型维度必须能被头数整除"
        hdim = dim // n         # 每个头的维度
        '''1, Multi-HeadAttention多头注意力构建'''
        self.multi_head_attn = MultiHeadAttention(hdim, hdim, dim, n, dropout_attn)
        '''2, Add&Norm 链接和LN'''
        self.norm1 = nn.LayerNorm(dim)
        self.norm2 = nn.LayerNorm(dim)
        '''3,Feed Forward 前馈网络 '''
        self.poswise_ffn = PoswiseFFN(dim, dff, p=dropout_posffn)
    pass

    def forward(self, enc_in: torch.Tensor, attn_mask: torch.Tensor = None) -> torch.Tensor:
        """
        Args:
            enc_in: 编码器输入 (N, seq_len, dim)
            attn_mask: 注意力掩码
        Returns:
            编码器输出 (N, seq_len, dim)
        """
        '''part one'''
        residual = enc_in   # 保存residual用于做残差连接
        x = self.multi_head_attn(enc_in, enc_in, enc_in, attn_mask)   # Multi-HeadAttention 多头注意力处理
        out = self.norm1(residual + x)    # Add & Norm 残差连接后LN
        '''part two'''
        residual = out     # 保存part one处理后的residual用于做后面的残差连接
        x = self.poswise_ffn(out)     # 前馈神经网络处理
        end_out = self.norm2(residual + x)   # Add & Norm 残差连接后LN
        return end_out
    pass

def pos_sinusoid_embedding(seq_len: int, d_model: int) -> torch.Tensor:
    """
    生成正弦/余弦位置编码
    Args:
        seq_len: 序列长度
        d_model: 模型维度
    Returns:
        形状为(seq_len, d_model)的位置编码矩阵
    """
    embeddings = torch.zeros((seq_len, d_model))
    for i in range(d_model):
        # 偶数位置用sin,奇数位置用cos
        f = torch.sin if i % 2 == 0 else torch.cos
        # 计算位置编码值
        embeddings[:, i] = f(torch.arange(0, seq_len) / np.power(1e4, 2 * (i // 2) / d_model))
    return embeddings.float()

class Encoder(nn.Module):
    '''编码器'''
    def __init__(
            self, dropout_emb: float, dropout_posffn: float, dropout_attn: float,
            num_layers: int, enc_dim: int, num_heads: int, dff: int, tgt_len: int,):
        """
        Args:
            dropout_emb: 嵌入层dropout概率
            dropout_posffn: 前馈网络dropout概率
            dropout_attn: 注意力dropout概率
            num_layers: 编码器层数
            enc_dim: 编码器维度
            num_heads: 注意力头数
            dff: 前馈网络隐藏层维度
            tgt_len: 最大序列长度
        """
        super().__init__()
        '''1,Positional Encoding位置编码 (使用预训练的正弦/余弦编码)'''
        self.pos_emb = nn.Embedding.from_pretrained(pos_sinusoid_embedding(tgt_len, enc_dim), freeze=True)
        self.emb_dropout = nn.Dropout(dropout_emb)  # 嵌入层dropout(可选)
        '''2, Encoder x N 正式构建6个编码器 '''
        self.layers = nn.ModuleList([
            EncoderLayer(enc_dim, num_heads, dff, dropout_posffn, dropout_attn) for _ in range(num_layers)
        ])
        pass

    def forward(self,X: torch.Tensor, X_lens: torch.Tensor, mask: torch.Tensor = None):
        """
        Args:
            X: 输入序列 (N, seq_len, d_model)
            X_lens: 每个序列的实际长度
            mask: 注意力掩码
        Returns:
            编码器输出 (N, seq_len, d_model)
        """
        batch_size, seq_len, d_model = X.shape  # X参数获取
        out = X + self.pos_emb(torch.arange(seq_len, device=X.device))  # 添加位置编码
        out = self.emb_dropout(out)             # 应用dropout
        for layer in self.layers:               # 遍历编码器
            out = layer(out, mask)              # 6次编码
        return out
        pass

class DecoderLayer(nn.Module):
    """解码器层"""
    def __init__(self, dim: int, n: int, dff: int, dropout_posffn: float, dropout_attn: float):
        """
        Args:
            dim: 输入维度
            n: 注意力头数
            dff: 前馈网络隐藏层维度
            dropout_posffn: 前馈网络dropout概率
            dropout_attn: 注意力dropout概率
        """
        super(DecoderLayer, self).__init__()
        assert dim % n == 0, "模型维度必须能被头数整除"
        hdim = dim // n     # 每个头的维度

        '''1 多头注意力(自注意力和编码器-解码器注意力)'''
        self.dec_attn = MultiHeadAttention(hdim, hdim, dim, n, dropout_attn)        # 带掩码
        self.enc_dec_attn = MultiHeadAttention(hdim, hdim, dim, n, dropout_attn)    # 不带掩码

        '''2 前馈网络'''
        self.poswise_ffn = PoswiseFFN(dim, dff, p=dropout_posffn)

        # 层归一化
        self.norm1 = nn.LayerNorm(dim)
        self.norm2 = nn.LayerNorm(dim)
        self.norm3 = nn.LayerNorm(dim)

    def forward(
            self, dec_in: torch.Tensor, enc_out: torch.Tensor,
            dec_mask: torch.Tensor = None, dec_enc_mask: torch.Tensor = None,
            cache: torch.Tensor = None, freqs_cis: torch.Tensor = None) -> torch.Tensor:
        """
        Args:
            dec_in: 解码器输入 (N, seq_len, dim)
            enc_out: 编码器输出 (N, seq_len, dim)
            dec_mask: 解码器自注意力掩码
            dec_enc_mask: 编码器-解码器注意力掩码
            cache: 用于推理的缓存(未使用)
            freqs_cis: RoPE旋转位置编码(未使用)
        Returns:
            解码器输出 (N, seq_len, dim)
        """
        '''part one'''
        residual = dec_in   # 保存residual 用做残差连接
        x = self.dec_attn(dec_in, dec_in, dec_in, dec_mask)   # 带掩码多头注意力
        dec_out = self.norm1(residual + x)    # Add & Norm 残差连接后LN
        '''part two'''
        residual = dec_out  # 保存上次residual 用做下次残差连接
        x = self.enc_dec_attn(dec_out, enc_out, enc_out, dec_enc_mask)    # 不带掩码多头注意力 两个值来自于编码器,一个来自于解码器
        dec_out = self.norm2(residual + x)    # Add & Norm 残差连接后LN
        '''part three'''
        residual = dec_out  # 保存上次residual 用做下次残差连接
        x = self.poswise_ffn(dec_out)
        end_out = self.norm3(x + residual)
        return end_out


class Decoder(nn.Module):
    '''解码器'''
    def __init__(
            self, dropout_emb: float, dropout_posffn: float, dropout_attn: float,
            num_layers: int, dec_dim: int, num_heads: int, dff: int, tgt_len: int, tgt_vocab_size: int,
    ):
        """
        Args:
            dropout_emb: 嵌入层dropout概率
            dropout_posffn: 前馈网络dropout概率
            dropout_attn: 注意力dropout概率
            num_layers: 解码器层数
            dec_dim: 解码器维度
            num_heads: 注意力头数
            dff: 前馈网络隐藏层维度
            tgt_len: 目标序列最大长度
            tgt_vocab_size: 目标词汇表大小
        """
        super(Decoder, self).__init__()
        '''1 目标词嵌入'''
        self.tgt_emb = nn.Embedding(tgt_vocab_size, dec_dim)
        self.dropout_emb = nn.Dropout(p=dropout_emb)
        '''2 位置编码'''
        self.pos_emb = nn.Embedding.from_pretrained(pos_sinusoid_embedding(tgt_len, dec_dim), freeze=True)
        '''3, Decoder x N 正式构建6个解码器 '''
        self.layers = nn.ModuleList([
            DecoderLayer(dec_dim, num_heads, dff, dropout_posffn, dropout_attn) for _ in range(num_layers)
        ])

    def forward(
            self, labels: torch.Tensor, enc_out: torch.Tensor,
            dec_mask: torch.Tensor, dec_enc_mask: torch.Tensor,) -> torch.Tensor:
        """
        Args:
            labels: 目标序列 (N, seq_len)
            enc_out: 编码器输出 (N, seq_len, dim)
            dec_mask: 解码器自注意力掩码
            dec_enc_mask: 编码器-解码器注意力掩码
            cache: 用于推理的缓存(未使用)
        Returns:
            解码器输出 (N, seq_len, dec_dim)
        """
        tgt_emb = self.tgt_emb(labels)      # 目标词嵌入
        pos_emb = self.pos_emb(torch.arange(labels.size(1), device=labels.device))  # 加入位置编码
        dec_out = self.dropout_emb(tgt_emb + pos_emb)        # 应用dropout
        for layer in self.layers:                            # 遍历解码器
            dec_out = layer(dec_out, enc_out, dec_mask, dec_enc_mask)   # 6解编码

        return dec_out
        pass

def get_len_mask(b: int, max_len: int, feat_lens: torch.Tensor, device: torch.device) -> torch.Tensor:
    """
    生成长度掩码,用于屏蔽padding部分
    Args:
        b: batch size
        max_len: 序列最大长度
        feat_lens: 每个样本的实际长度
        device: 设备类型
    Returns:
        形状为(b, max_len, max_len)的布尔掩码,True表示需要屏蔽的位置
    """
    # 初始化为全1(需要屏蔽),然后将有效部分设为0
    attn_mask = torch.ones((b, max_len, max_len), device=device)
    for i in range(b):
        attn_mask[i, :, :feat_lens[i]] = 0  # 有效部分设为0
    return attn_mask.to(torch.bool)


def get_subsequent_mask(b: int, max_len: int, device: torch.device) -> torch.Tensor:
    """
    生成解码器的自回归掩码(防止看到未来信息)
    Args:
        b: batch size
        max_len: 序列最大长度
        device: 设备类型
    Returns:
        上三角矩阵(b, max_len, max_len),对角线以上为True(需要屏蔽)
    """
    return torch.triu(torch.ones((b, max_len, max_len), device=device), diagonal=1).to(torch.bool)


def get_enc_dec_mask(b: int, max_feat_len: int, feat_lens: torch.Tensor, max_label_len: int, device: torch.device):
    """
    生成编码器-解码器注意力掩码
    Args:
        b: batch size
        max_feat_len: 编码器输入最大长度
        feat_lens: 每个编码器输入的实际长度
        max_label_len: 解码器输入最大长度
        device: 设备类型
    Returns:
        形状为(b, max_label_len, max_feat_len)的掩码
    """
    attn_mask = torch.zeros((b, max_label_len, max_feat_len), device=device)  # (b, seq_q, seq_k)
    for i in range(b):
        attn_mask[i, :, feat_lens[i]:] = 1  # 将padding部分设为1(需要屏蔽)
    return attn_mask.to(torch.bool)


class Transformer(nn.Module):
    """Transformer模型"""
    def __init__(self, frontend: nn.Module, encoder: nn.Module, decoder: nn.Module, dec_out_dim: int, vocab: int):
        super().__init__()
        self.frontend = frontend    # 前端特征提取模型
        self.encoder = encoder      # 编码器模型
        self.decoder = decoder      # 解码器模型
        self.linear = nn.Linear(dec_out_dim, vocab)  # 全连接线性输出层
        pass

    def forward(self, X: torch.Tensor, X_lens: torch.Tensor, labels: torch.Tensor):

        '''输入校验'''
        X_lens = X_lens.long()  # 确保长度是整数(用于掩码生成)
        labels = labels.long()  # 确保标签是整数(用于Embedding)
        b = X.size(0)           # batch size 获取
        device = X.device       # 运行设备处理
        '''前端特征提取'''
        out = self.frontend(X)
        max_feat_len = out.size(1)      # 前端可能进行子采样,需重新计算 输入序列的最大长度
        max_label_len = labels.size(1)  # 需重新计算 输出序列的最大长度
        '''编码输出'''
        enc_mask = get_len_mask(b, max_feat_len, X_lens, device)    # 获取 生成长度掩码,用于屏蔽padding
        enc_out = self.encoder(out, X_lens, enc_mask)               # 编码输出
        '''解码输出'''
        dec_mask = get_subsequent_mask(b, max_label_len, device)    # 获取 自回归掩码, 用于屏蔽"未来"信息
        dec_enc_mask = get_enc_dec_mask(b, max_feat_len, X_lens, max_label_len, device)  # 获取 注意力掩码,
        dec_out = self.decoder(labels, enc_out, dec_mask, dec_enc_mask)     # 解码输出
        '''输出层'''
        end_out = self.linear(dec_out)      # 全连接输出层
        return end_out
        pass


if __name__ == '__main__':
    # Q1 传入的前处理层,编码器,解码器,隐藏层维度,词汇表大小是否合理。
    # Q2 前端特征提取时,重新计算max_feat_len和max_label_len原因
    # Q3 长度掩码 ,自回归掩码 , 注意力掩码 原文只有 多头注意力掩码
    # Q4 多头注意力中多头的具体实现

    '''测试配置'''
    batch_size = 16         # 每次训练输入的样本数量
    max_feat_len = 100      # 输入序列的最大长度(时间步数)
    fbank_dim = 80          # 输入特征的维度(如FBank特征维度)
    hidden_dim = 512        # Transformer隐藏层维度/嵌入维度(d_model)
    vocab_size = 26         # 输出词汇表大小
    max_label_len = 100     # 输出序列的最大长度

    # 生成随机输入特征 (batch_size, 时间步, 特征维度)
    # 形状说明:(16, 100, 80) - 16个样本,每个样本100帧,每帧80维FBank特征
    fbank_feature = torch.randn(batch_size, max_feat_len, fbank_dim)
    # 生成每个样本的实际长度 (batch_size,)
    # 值范围:1到max_feat_len之间的随机整数,表示每个样本的有效长度
    feat_lens = torch.randint(1, max_feat_len, (batch_size,))
    # 生成随机标签序列 (batch_size, 输出序列长度)
    # 值范围:0-25(对应26个字母),形状(16, 100)
    labels = torch.randint(0, 26, (batch_size, max_label_len))
    # 生成每个标签序列的实际长度 (batch_size,)
    # 值范围:1-10之间的随机整数,表示每个输出序列的有效长度
    label_lens = torch.randint(1, 10, (batch_size,))

    '''1,Input Embedding前处理层构建: 输入嵌入层 特征提取并与模型维度对齐 '''
    feature_extractor = nn.Linear(fbank_dim, hidden_dim)
    '''2,Encoder 编码器构建'''
    encoder = Encoder(
        dropout_emb=0.1, dropout_posffn=0.1, dropout_attn=0.,
        num_layers=6, enc_dim=hidden_dim, num_heads=8, dff=2048, tgt_len=2048
    )
    '''3,Decoder 解码器构建'''
    decoder = Decoder(
        dropout_emb=0.1, dropout_posffn=0.1, dropout_attn=0.,
        num_layers=6, dec_dim=hidden_dim, num_heads=8, dff=2048, tgt_len=2048, tgt_vocab_size=vocab_size
    )
    '''4,transformer实例化
       传入:前处理层,编码器,解码器,隐藏层维度,词汇表大小'''
    transformer = Transformer(feature_extractor, encoder, decoder, hidden_dim, vocab_size)
    '''transformer 前向传播测试'''
    logits = transformer(fbank_feature, feat_lens, labels)
    print("输出形状:([batch_size,max_label_len,vocab_size])", logits.shape)

transformer面试问题:

①为什么要进行位置编码?语言是带有时序关系的,所以需要我们加一个位置编码用来区分先后注意力机制结合词义+位置,正确捕捉语义。(举例:我 爱 你, 你 爱 我)

②核心模块(能力)?多头自注意力-->捕捉序列内部的长距离依赖关系,对于上下文的理解。

③怎么理解self-attion(普通描述):?" 今天我来面试 " ,进行分词(今天,我,来,面试)重复每个词和其他词之间的关系相关性打分。

⑧Q和K相乘softmax得到分数?点积运算 Q⋅K可以 衡量两个向量的相似度,点积值越大 → 向量方向越接近(相似度越高)

④为什么要(➗根号d)?1,首先进行缩放有利于softmax函数运算,避免梯度消失。独立同分布的随机变量点积的方差大致是根号d:起到了归一化的作用,更符合高斯分布。

⑤怎么理解多头?从多角度,词性,词义,等不同方法去捕捉多样性特征。多个独立的线性投影和注意力计算增强模型的非线性表达能力。并行执行,显著加快训练和推理速度。

⑥为什么要加Sequence Mask 和padding Mask?

Sequence Mask :整句输入确保Decoder在训练时只能基于已生成的部分预测下一个词。避免看到未来信息。

Padding Mask:处理批量训练中的因为需要对齐而变长序列,加入掩码避免无效计算。例子 1["I", "love", "NLP"] ,2["Hello", "world", ""]--> [1, 1, 1] , [1, 1, 0]

⑦CNN与transofmer提取特征的不同之处在哪?

CNN : 局部特征提取。参数量较少,速度快。权重共享。没有丰富长距离依赖信息。也没有时序。

transofmer: 全局特征提取,有丰富的长距离依赖,上下文信息。高质量的数据量大的情况,会效果很好。但参数量更大,这速度较慢。在CV领域,容易过拟合。

⑧为什么用LN而不是BN?在NLP任务中,句子长度不一致,通常需要对较短的句子进行填充,短句较多的时候BN填充太多0会降低长句的有效特征,LN对单个样本的所有特征进行归一化不会产生这种情况。而且BN会破坏位置编码信息。

⑨cnn与transofmer提取特征的不同之处在那儿?

CNN,局部特征提取。局全感知,权重共享。参数量较少。速度快。没有丰富长距离依赖信息。

Transofmer,全局特征提取。有丰富的长距离依赖,上下文信息。高质量的数据量大的情况,会效果很好。参数量更大,速度较慢。在CV领域,容易过拟合

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值