从零开始手搓Transformer#Datawhale组队学习Task1#

news2024/9/20 3:31:51

从零开始手搓Transformer

目录

缩放点积注意力DotProductAttention

多头注意力Multi-Head Attention

位置编码Position Encoder

前馈神经网络FFN

残差连接和层归一化(Add&Norm)

编码器Encoder

解码器Decoder

编码器-解码器Encoder-Decoder

训练函数:

 依照Transformer架构来实例化编码器-解码器模型

参考文献:

tiny-universe/content/TinyTransformer at main · datawhalechina/tiny-universe (github.com)

《attention is all you need》

《动手学深度学习PyTorch》

一些简介:Transformer 是一种在自然语言处理(NLP)领域具有里程碑意义的模型架构,首次在2017年的论文《Attention is All You Need》中提出。该架构摒弃了传统的递归神经网络(RNN)和卷积神经网络(CNN),完全依赖于自注意力机制(Self-Attention Mechanism),使得模型能够并行处理序列数据,大幅提升了训练效率。Transformer 主要由编码器(Encoder)和解码器(Decoder)两部分组成,每一部分都由多个结构相同的层堆叠而成。编码器负责将输入序列转换为中间表示,而解码器则基于这些表示生成输出序列。每个层内部包含多头自注意力(Multi-Head Self-Attention)模块和前馈神经网络(Feed-Forward Network),并通过残差连接(Residual Connection)和层归一化(Layer Normalization)技术增强了模型的表达能力和训练稳定性。Transformer 不仅在机器翻译任务上取得了卓越的性能,还广泛应用于文本摘要、问答系统等众多NLP任务中。

动手学深度学习

手搓Transformer

Transoformer的组成:Transformer由编码器-解码器架构组成,每个部分都使用了多头注意力机制(Multi-head)和前馈神经网络(FFN),叠加位置编码模块,嵌入层等。

代码实现+知识讲解

缩放点积注意力DotProductAttention

Q:什么是注意力机制?

A:注意力机制(Attention Mechanism)是一种在序列到序列(Seq2Seq)模型中引入的重要机制,它的核心思想是让模型能够关注输入序列的不同部分,而不是平均对待所有输入。通过这种方式,模型可以更好地处理长距离依赖,并且在处理序列数据时更具灵活性。

Q:注意力机制都有哪些形式?

A:主要包括1)加性注意力(additive attention);2)缩放点积注意力(scaled dot‐product attention)

Q:缩放点积注意力如何计算?

A:主要包括1)计算注意力得分;2)应用Softmax函数;3)加权求和Value向量。参考公式如下图所示:

 代码实现:

class DotProductAttention(nn.Module):
    def __init__(self,dropout,**kwargs):
        super(DotProductAttention,self).__init__(**kwargs)
        self.dropout = nn.Dropout(dropout)
    # 前向计算
    def forward(self,queries,keys,values,valid_lens=None):
        # 获取查询向量的维度d
        d = queries.shape[-1]
        # 计算注意力得分,并缩放
        scores = torch.bmm(queries,keys.transpose(1,2))/torch.sqrt(d)
        # 应用掩码并计算注意力权重
        self.attention_weights = masked_softmax(scores,valid_lens)
         # 应用 dropout 并加权求和 values
        return torch.bmm(self.dropout(self.attention_weights),values)

masked_softmax函数(实现带掩码的缩放点积注意力计算):

def sequence_mask(X, valid_lens, value=0):
    # 获取序列的最大长度
    max_len = X.size(1)
    # 创建一个范围张量,形状为 (1, max_len)
    row_vector = torch.arange(0, max_len, device=X.device).unsqueeze(0)
    # 将范围张量扩展为 (batch_size, max_len)
    # 将row_vector与valid_lens进行比较,生成一个布尔掩码矩阵mask
    mask = row_vector < valid_lens.unsqueeze(1)
    # 将掩码应用于输入张量 X
    Y = X.clone()
    # 将Y中对应mask为False的位置(即超出有效长度的位置)设置为 value
    Y[~mask] = value
    return Y

 

def masked_softmax(X,valid_lens):
    """"实现带掩码的softmax函数"""
    # 如果没有提供有效长度,则直接返回softmax结果
    if valid_lens is None:
        return nn.functional.softmax(X,dim=-1)
    else:
        shape = X.shape
        # 如果valid_lens是一维的,则将其重复以匹配X的第二维度
        if valid_lens.dim()==1:
            valid_lens = torch.repeat_interleave(valid_lens,shape[1])
        else:
            valid_lens = valid_lens.reshape[-1]
        # 将X展平为二维,并应用sequence_mask函数
        X = sequence_mask(X.reshape(-1,shape[-1]),valid_lens,value=-1e6)
        # 将展平后的结果恢复原始形状,并应用softmax
        return nn.functional.softmax(X,dim=-1)

多头注意力Multi-Head Attention

Q:什么是多头注意力?

A:多头注意力是指使用多个头head来独立计算注意力,然后再把这些独立计算的注意力合到一起,不同的头,可能会关注输入的不同部分,可以学习到不同的特征。

Q:多头注意力如何实现?

A:多头注意力的完整实现步骤如下:

  1. 输入投影:通过线性层W_qW_kW_v将输入的查询、键和值投影到不同的子空间。
  2. 分裂头部:使用transpose_qkv函数将投影后的张量分裂成多个头。
  3. 计算注意力权重:使用点积注意力机制计算每个头的注意力权重。
  4. 合并头部:使用transpose_output函数将多头的结果拼接在一起。
  5. 输出投影:通过线性层W_o将拼接后的输出投影回原始维度。

代码部分(关于代码部分的详细讲解参考注释):

疑问or内部细节:

1.需要保证num_hideens/num_heads==0,即在多头注意力机制中,确实需要确保num_hiddens能够被num_heads整除,这样才能保证每个头的隐藏维度大小是整数。如果num_hiddens不能被num_heads整除,就会出现问题。(定位:transpose_qkv函数)

如何解决:

方法一:调整num_hiddensnum_heads

最简单的方法是在设计模型时确保num_hiddens能够被num_heads整除。这意味着你需要选择合适的num_hiddensnum_heads的组合。

方法二:动态调整

如果无法预先确定num_hiddensnum_heads的组合,可以在模型内部进行动态调整。例如,可以向上取整到最接近的能够整除的大小。

 # 确保num_hiddens可以被num_heads整除
 assert num_hiddens % num_heads == 0, "num_hiddens must be divisible by num_heads"

2.valid_lens 代码疑问,为什么在torch.repeat_interleave中使用dim=0,即沿着0维repeat?

 # 如果valid_lens不是None,则重复以适应多头数量
 # 沿着批次维度(即dim=0)重复valid_lens,重复num_heads次
 if valid_lens is not None:
    valid_lens = torch.repeat_interleave(valid_lens,repeats=self.num_heads,dim=0)

dim=0指的是沿着张量的第一维(即最外层的维度,批次维度)进行操作,在多头注意力机制中,我们需要将输入数据分成多个头,并且每个头都独立进行注意力计算。因此,如果原来的有效长度是针对整个批次的,那么在多头注意力中,每个头都需要对应一个有效长度。

为了让每个头都拥有自己的有效长度,我们需要将原来的valid_lens沿着批次维度进行重复,使得每个头都有相同的有效长度信息。

位置编码Position Encoder

Q:为什么要进行位置编码?

A:为了使用序列的顺序信息,通过在输入表示中添加位置编码(positional encoding)来注入绝对的或相对的位置信息。位置编码可以通过学习得到也可以直接固定得到。

Q:位置编码都有哪些实现形式呢?列举几种常见的并简述应用。

A:位置编码包括:1)固定式位置编码(Fixed Positional Encoding),Transformer原始论文提出并广泛应用;2)可学习位置编码(Learnable Positional Encoding),BERT使用可学习位置编码;3)相对位置编码(Relative Positional Encoding);4)组合式位置编码(Combined Positional Encoding)等。

Q:Transformer原始论文中,位置编码是如何实现的?

A:使用的是固定位置编码,即正弦余弦函数。位置编码是根据位置和维度计算得出的,不随训练更新。

Q:图像处理领域,Transformer架构使用的位置编码?

A:图像领域主要使用的包括1)可学习位置编码(如ViT);2)二维位置编码(2D Positional Encoding)

代码实现:(基于上述公式实现)

class PositionalEncoding(nn.Module):
    """"位置编码的是实现"""
    def __init__(self,num_hiddens,dropout,max_len=1000):
        super(PositionalEncoding,self).__init__()
        self.dropout = nn.Dropout(dropout)
        # 初始化编码矩阵,全0向量,用于存放位置编码
        self.P = torch.zeros(1,max_len,num_hiddens)
        # 计算位置和频率,X表示序列中每个位置的索引,freqs计算了每个维度的频率因子
        freqs = torch.arrange(0,num_hiddens,2,dtype=torch.float32)/num_hiddens
        X = torch.arange(max_len,dtype=torch.float32).reshape(-1,1)/torch.pow(10000,freqs)
        # 应用正弦函数和余弦函数,根据维度的奇偶性。
        self.P[:,:,0::2] = torch.sin(X)
        self.P[:,:,1::2] = torch.cos(X)
    
    # 前向计算
    def forward(self,X):
        # 将位置编码矩阵加到输入嵌入上
        X = X + self.P[:,:X.shape[1],:].to(X.device)
        return self.dropout(X)

疑问or内部细节:

关于P的维度的理解,即

“self.P[:,:,0::2] = torch.sin(X)”、“self.P[:,:,1::2] = torch.cos(X)”、“X = X + self.P[:,:X.shape[1],:].to(X.device)”这三条代码。

解释:

已知self.P 的形状为 (1, max_len, num_hiddens),这里:

  • 第一维度(第一个 :):表示批次维度,这里只有一个批次,所以是全部选取。
  • 第二维度(第二个 :):表示序列长度维度,这里也是全部选取。
  • 第三维度(0::2 和 1::2):表示隐藏维度。
  • self.P[:, :, 0::2] 表示选取 self.P 中所有位置的所有偶数隐藏维度。
  • self.P[:, :, 1::2] 表示选取 self.P 中所有位置的所有奇数隐藏维度。
  • self.P[:, :X.shape[1], :]:我们选取 self.P 的前 X.shape[1] 个位置(即 X 的序列长度),并且选取所有的隐藏维度。

嵌入层embedding

层归一化LayerNorm

前馈神经网络FFN

Q:前馈神经网络的本质是什么?

A:多层感知机MLP

代码实现:

class PositionWiseFFN(nn.Module):
    """"基于位置的前馈神经网络"""
    def __init__(self,ffn_num_input,ffn_num_hiddens,ffn_num_outputs,**kwargs):
        super(PositionWiseFFN,self).__init__(**kwargs)
        self.dense1 = nn.Linear(ffn_num_input, ffn_num_hiddens)
        self.relu = nn.ReLU()
        self.dense2 = nn.Linear(ffn_num_hiddens, ffn_num_outputs)
    def forward(self,X):
        return self.dense2(self.relu(self.dense1(X)))

残差连接和层归一化(Add&Norm)

Q:什么是残差连接?

A:残差连接(Residual Connection)是一种在深度神经网络中使用的连接方式,它通过在层之间添加“跳跃连接”(skip connections)来改善深层网络的训练。这种连接方式最早是在 ResNet(残差网络)中提出的,后来被广泛应用于各种深度学习架构中,包括 Transformer。

Q:残差连接有什么作用?

A:1)解决梯度消失/梯度爆炸问题;2)加速收敛;3)提高模型性能

Q:什么是层归一化?

A:层归一化是一种重要的技术,它通过对输入数据进行归一化来减少内部协变量偏移,从而提高模型的训练效率和性能。

Q:层归一化和批量归一化的区别?简要解释。

A:BatchNorm: 一批次样本按每一个特征维度进行归一化;LayerNorm: 同一个样本的不同特征归一化。

代码实现:

class AddNorm(nn.Module):
    """"定义残差连接和层归一化"""
    def __init__(self,normalized_shape,dropout,**kwargs):
        super(AddNorm,self).__init__(**kwargs)
        self.dropout = nn.Dropout(dropout)
        self.ln = nn.LayerNorm(normalized_shape)
    
    def forward(self,X,Y):
        return self.ln(self.dropout(Y)+X)

编码器Encoder

Q:Transformer编码器的组成?

A:Transformer编码器由多个编码器块Encoder Block堆叠而成,每个编码器块包括1)多头注意力;2)残差连接和层归一化;3)前馈神经网络等。

代码实现:

# 定义EncoderBlock类
class EncoderBlock(nn.Module):
    """"编码器块"""
    def __init__(self,key_size,query_size,value_size,num_hiddens,
                 norm_shape,ffn_num_input, ffn_num_hiddens,
                 num_heads,dropout, use_bias=False, **kwargs):
        super(EncoderBlock,self).__init__(**kwargs)
        self.attention = MultiHeadAttention(key_size, query_size, value_size, num_hiddens, num_heads, dropout,use_bias)
        self.addnorm1 = AddNorm(norm_shape, dropout)
        self.ffn = PositionWiseFFN(ffn_num_input, ffn_num_hiddens, num_hiddens)
        self.addnorm2 = AddNorm(norm_shape, dropout)
    
    def forward(self,X,valid_lens):
        Y = self.addnorm1(X, self.attention(X, X, X, valid_lens))
        return self.addnorm2(Y, self.ffn(Y))

# 定义TransformerEncoder类  
class TransformerEncoder(EncoderBlock):
    """Transformer编码器"""
    def __init__(self, vocab_size, key_size, query_size, 
                 value_size,num_hiddens, norm_shape, ffn_num_input, 
                 ffn_num_hiddens,num_heads, num_layers, dropout, use_bias=False, **kwargs):
        super(TransformerEncoder, self).__init__(**kwargs)
        self.num_hiddens = num_hiddens
        self.embedding = nn.Embedding(vocab_size, num_hiddens)
        self.pos_encoding = PositionalEncoding(num_hiddens, dropout)
        self.blks = nn.Sequential()
        for i in range(num_layers):
            self.blks.add_module("block"+str(i),
                                 EncoderBlock(key_size, query_size, value_size, num_hiddens,
                                              norm_shape, ffn_num_input, ffn_num_hiddens,
                                              num_heads, dropout, use_bias))
            def forward(self, X, valid_lens, *args):
                # 因为位置编码值在-1和1之间,
                # 因此嵌入值乘以嵌入维度的平方根进行缩放,
                # 然后再与位置编码相加。
                X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens))
                self.attention_weights = [None] * len(self.blks)
                for i, blk in enumerate(self.blks):
                    X = blk(X, valid_lens)
                    self.attention_weights[i] = blk.attention.attention.attention_weights
                    return X

解码器Decoder

Q:什么是解码器,解码器的作用是什么?

A:Transformer解码器同样是由多个解码器块(DecoderBlock)堆叠而成,Transformer 的解码器模块负责将解码器的输入(通常是编码器的输出和先前生成的目标序列的一部分)转换为目标序列的预测。

Q:解码器块包含哪些组件?

A:解码器块相比编码器块,多了一层Masked多头注意力,然后接Add&&Norm,然后是一层编码器-解码器多头注意力,然后接一层Add&&Norm,再然后是FFN,接Add&&Norm。

代码实现:

class DecoderBlock(nn.Module):
    """解码器中第i个块"""
    def __init__(self, key_size, query_size, value_size, num_hiddens,
                 norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,
                 dropout, i, **kwargs):
        super(DecoderBlock, self).__init__(**kwargs)
        self.i = i
        self.attention1 = MultiHeadAttention(
            key_size, query_size, value_size, num_hiddens, num_heads, dropout)
        self.addnorm1 = AddNorm(norm_shape, dropout)
        self.attention2 = MultiHeadAttention(
            key_size, query_size, value_size, num_hiddens, num_heads, dropout)
        self.addnorm2 = AddNorm(norm_shape, dropout)
        self.ffn = PositionWiseFFN(ffn_num_input, ffn_num_hiddens,
                                   num_hiddens)
        self.addnorm3 = AddNorm(norm_shape, dropout)

    def forward(self, X, state):
        enc_outputs, enc_valid_lens = state[0], state[1]
        # 训练阶段,输出序列的所有词元都在同一时间处理,
        # 因此state[2][self.i]初始化为None。
        # 预测阶段,输出序列是通过词元一个接着一个解码的,
        # 因此state[2][self.i]包含着直到当前时间步第i个块解码的输出表示
        if state[2][self.i] is None:
            key_values = X
        else:
            key_values = torch.cat((state[2][self.i], X), axis=1)
        state[2][self.i] = key_values
        if self.training:
            batch_size, num_steps, _ = X.shape
            # dec_valid_lens的开头:(batch_size,num_steps),
            # 其中每一行是[1,2,...,num_steps]
            dec_valid_lens = torch.arange(
                1, num_steps + 1, device=X.device).repeat(batch_size, 1)
        else:
            dec_valid_lens = None

        # 自注意力
        X2 = self.attention1(X, key_values, key_values, dec_valid_lens)
        Y = self.addnorm1(X, X2)
        # 编码器-解码器注意力。
        # enc_outputs的开头:(batch_size,num_steps,num_hiddens)
        Y2 = self.attention2(Y, enc_outputs, enc_outputs, enc_valid_lens)
        Z = self.addnorm2(Y, Y2)
        return self.addnorm3(Z, self.ffn(Z)), state
class TransformerDecoder(DecoderBlock):
    def __init__(self, vocab_size, key_size, query_size, value_size,
                 num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens,
                 num_heads, num_layers, dropout, **kwargs):
        super(TransformerDecoder, self).__init__(**kwargs)
        self.num_hiddens = num_hiddens
        self.num_layers = num_layers
        self.embedding = nn.Embedding(vocab_size, num_hiddens)
        self.pos_encoding = PositionalEncoding(num_hiddens, dropout)
        self.blks = nn.Sequential()
        for i in range(num_layers):
            self.blks.add_module("block"+str(i),
                DecoderBlock(key_size, query_size, value_size, num_hiddens,
                             norm_shape, ffn_num_input, ffn_num_hiddens,
                             num_heads, dropout, i))
        self.dense = nn.Linear(num_hiddens, vocab_size)

    def init_state(self, enc_outputs, enc_valid_lens, *args):
        return [enc_outputs, enc_valid_lens, [None] * self.num_layers]

    def forward(self, X, state):
        X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens))
        self._attention_weights = [[None] * len(self.blks) for _ in range (2)]
        for i, blk in enumerate(self.blks):
            X, state = blk(X, state)
            # 解码器自注意力权重
            self._attention_weights[0][
                i] = blk.attention1.attention.attention_weights
            # “编码器-解码器”自注意力权重
            self._attention_weights[1][
                i] = blk.attention2.attention.attention_weights
        return self.dense(X), state

    @property
    def attention_weights(self):
        return self._attention_weights

编码器-解码器Encoder-Decoder

代码实现:

class EncoderDecoder(nn.Module):
    """编码器-解码器架构的基类"""
    def __init__(self, encoder, decoder, **kwargs):
        super(EncoderDecoder, self).__init__(**kwargs)
        self.encoder = encoder
        self.decoder = decoder
        def forward(self, enc_X, dec_X, *args):
            enc_outputs = self.encoder(enc_X, *args)
            dec_state = self.decoder.init_state(enc_outputs, *args)
            return self.decoder(dec_X, dec_state)

训练函数:

代码实现:

#@save
def train_seq2seq(net, data_iter, lr, num_epochs, tgt_vocab, device):
    """训练序列到序列模型"""
    def xavier_init_weights(m):
        if type(m) == nn.Linear:
            nn.init.xavier_uniform_(m.weight)
        if type(m) == nn.GRU:
            for param in m._flat_weights_names:
                if "weight" in param:
                    nn.init.xavier_uniform_(m._parameters[param])
        
    net.apply(xavier_init_weights)
    net.to(device)
    optimizer = torch.optim.Adam(net.parameters(), lr=lr)
    loss = d2l.MaskedSoftmaxCELoss()
    net.train()
    animator = d2l.Animator(xlabel='epoch', ylabel='loss',
                                                xlim=[10, num_epochs])
    for epoch in range(num_epochs):
        timer = d2l.Timer()
        metric = d2l.Accumulator(2) # 训练损失总和,词元数量
        for batch in data_iter:
            optimizer.zero_grad()
            X, X_valid_len, Y, Y_valid_len = [x.to(device) for x in batch]
            bos = torch.tensor([tgt_vocab['<bos>']] * Y.shape[0],
                                                   device=device).reshape(-1, 1)
            dec_input = torch.cat([bos, Y[:, :-1]], 1) # 强制教学
            Y_hat, _ = net(X, dec_input, X_valid_len)
            l = loss(Y_hat, Y, Y_valid_len)
            l.sum().backward() # 损失函数的标量进行“反向传播”
            d2l.grad_clipping(net, 1)
            num_tokens = Y_valid_len.sum()
            optimizer.step()
            with torch.no_grad():
                    metric.add(l.sum(), num_tokens)
        if (epoch + 1) % 10 == 0:
                        animator.add(epoch + 1, (metric[0] / metric[1],))
    print(f'loss {metric[0] / metric[1]:.3f}, {metric[1] / timer.stop():.1f} 'f'tokens/sec on {str(device)}')

 依照Transformer架构来实例化编码器-解码器模型

num_hiddens, num_layers, dropout, batch_size, num_steps = 32, 2, 0.1, 64, 10
lr, num_epochs, device = 0.005, 200, d2l.try_gpu()
ffn_num_input, ffn_num_hiddens, num_heads = 32, 64, 4
key_size, query_size, value_size = 32, 32, 32
norm_shape = [32]

train_iter, src_vocab, tgt_vocab = d2l.load_data_nmt(batch_size, num_steps)

encoder = TransformerEncoder(
    len(src_vocab), key_size, query_size, value_size, num_hiddens,
    norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,
    num_layers, dropout)
decoder = TransformerDecoder(
    len(tgt_vocab), key_size, query_size, value_size, num_hiddens,
    norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,
    num_layers, dropout)
net = d2l.EncoderDecoder(encoder, decoder)
d2l.train_seq2seq(net, train_iter, lr, num_epochs, tgt_vocab, device)

时间仓库,多有不周,还请见谅,有空的时候再来重新修改润色。

喜欢的小伙伴,点赞收藏关注吧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2147750.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

pg入门3—详解tablespaces2

pg默认的tablespace的location为空&#xff0c;那么如果表设置了默认的tablespace&#xff0c;数据实际上是存哪个目录的呢? 在 PostgreSQL 中&#xff0c;如果你创建了一个表并且没有显式指定表空间&#xff08;tablespace&#xff09;&#xff0c;或者表空间的 location 为…

ISSTA 2024盛大开幕:中国学者的录取数和投稿量均位列第一

随着夏日的尾声&#xff0c;全球软件测试领域的专家和学者齐聚在奥地利维也纳。共同参与这场科技盛宴——ISSTA 2024。这场国际会议正如火如荼地进行中&#xff0c;吸引了来自世界各地的专业人士参与。 会议实况&#xff1a; 9月16日与17日&#xff0c;大会安排了丰富的社交活…

Qt:懒汉单例(附带单例使用和内存管理)

前言 本文主要写懒汉单例以及单例的释放&#xff0c;网上很多教程只有单例的创建&#xff0c;但是并没有告诉我们单例的内存管理&#xff0c;这就很头疼。 正文 以下是两种懒汉单例的写法 1. 懒汉式单例&#xff08;多线程不安全&#xff0c;但是在单线程里面是安全的&…

【828华为云征文|华为云Flexus X实例部署指南:轻松搭建可道云KODBOX项目】

文章目录 华为云 Flexus X 实例&#xff1a;引领高效云服务的新时代部署【可道云KODBOX】项目准备工作具体操作指南服务器环境确认宝塔软件商店操作域名解析可道云KODBOX登录页效果验证 总结 华为云 Flexus X 实例&#xff1a;引领高效云服务的新时代 在云计算领域&#xff0c…

【UE5】使用2DFlipbook图作为体积纹理,实现实时绘制体积纹理【第一篇】

这是一篇对“Creating a Volumetric Ray Marcher-Shader Bits”的学习心得 文章时间很早&#xff0c;因此这里针对UE5对原文做出兼容性修正&#xff08;为避免累赘不做出注明。链接如上&#xff0c;有需要自行学习&#xff09; 以及最后对Custom做可能的蓝图移植&#xff0c;做…

虚拟机与物理机的文件共享

之前往虚拟机里传文件都是直接拖拽或者借助工具上传&#xff0c;都不太方便&#xff0c;倘若物理机的文件直接能在虚拟机里读取使用&#xff0c;那多好啊~ 1 虚拟机设置 注意文件夹名称不要中文/空格 2 验证Kali下分享文件夹功能是否启用 vmware-hgfsclient 3 创建挂载目录…

【踩坑】utools黑框和chrome白屏

记录一下bug&#xff0c;后面找到解决方案再同步 刚开机会黑框&#xff0c;但是输入wx能正常打开&#xff0c;功能一切正常 过一段时间会恢复正常 chrome会白屏 过一段时间恢复正常&#xff0c;大概是utools恢复正常时间的三倍 猜测是前两天关机时提示xx应用阻止关机&#…

旷视轻量化网络shufflenet算法解读

目录 预备知识 1. 回顾MobileNet V1的核心思想---深度可分离卷积 2.ShuffleNet主要有两个创新点 2.1 分组卷积与11分组卷积 2.2 channel Shuffle&#xff08;通道重排&#xff09; 2.3 通道重排过程 3. ShuffleNet网络结构 3.1 ShuffleNet unit 3.2 不同分组数的Shu…

Mamba YOLO World

论文地址&#xff1a;https://arxiv.org/pdf/2409.08513v1 代码地址&#xff1a; GitHub - Xuan-World/Mamba-YOLO-World: Mamba-YOLO-World: Marrying YOLO-World with Mamba for Open-Vocabulary Detection 开集检测&#xff08;OVD&#xff09;旨在检测预定义类别之外的物体…

上海做网站公司发展分析报告

上海网站公司发展分析报告 一、行业概况 上海作为中国的经济、金融和科技中心&#xff0c;其网站开发行业在近年来迅速发展。根据统计数据&#xff0c;当地的网站公司数量逐年增加&#xff0c;涵盖了企业网站、电子商务平台、移动应用开发等多个领域。随着互联网技术不断演进&…

维钧团队与广东能源集团携手共创未来

2024 年 9 月 19日&#xff0c;能源与投资领域传出一则令人振奋的消息——多元化投资团队维钧团队与广东能源集团正式达成合作&#xff0c;如同璀璨星辰交汇&#xff0c;开启了双方共同发展的壮丽新篇章。 维钧团队&#xff0c;以其敏锐专业的投资眼光、灵活多元的投资策略和卓…

大牛直播SDK核心音视频模块探究

技术背景 视沃科技旗下”大牛直播SDK”&#xff0c;始于2015年&#xff0c;致力于传统行业极致体验的音视频直播技术解决方案&#xff0c;产品涵盖跨平台的实时RTMP推流、RTMP/RTSP直播播放(支持RTSP|RTMP H.265&#xff0c;Enhanced RTMP H.265)、GB28181设备接入、推送端播放…

redis为什么不使用一致性hash

Redis节点间通信时&#xff0c;心跳包会携带节点的所有槽信息&#xff0c;它能以幂等方式来更新配置。如果采用 16384 个插槽&#xff0c;占空间 2KB (16384/8);如果采用 65536 个插槽&#xff0c;占空间 8KB (65536/8)。 今天我们聊个知识点为什么Redis使用哈希槽而不是一致性…

Llama 3.1 Omni:颠覆性的文本与语音双输出模型

你可能听说过不少关于语言模型的进展,但如果告诉你,有一种模型不仅能生成文本,还能同时生成语音,你会不会觉得特别酷?今天咱们就来聊聊一个相当前沿的项目——Llama 3.1 Omni模型。这个模型打破了传统的文字生成边界,直接让文本和语音同时输出,实现了真正的"多模态…

【mac】MacOS无法打开XXX文件,因为无法验证开发者的问题解决

博主介绍&#xff1a; 大家好&#xff0c;我是想成为Super的Yuperman&#xff0c;互联网宇宙厂经验&#xff0c;17年医疗健康行业的码拉松奔跑者&#xff0c;曾担任技术专家、架构师、研发总监负责和主导多个应用架构。 技术范围&#xff1a; 目前专注java体系&#xff0c;以及…

OpenCv(一)

计算机视觉和机器视觉的区别 计算机视觉&#xff08;Computer Vision&#xff09;和机器视觉&#xff08;Machine Vision&#xff09;是两个密切相关但又有区别的领域。两者在应用、技术和目标上都有所不同。 **计算机视觉&#xff1a;**主要是研究如何使计算机能够理解和处理…

单细胞代谢组学数据分析利器---SCMeTA

今天继续进行新的分享&#xff0c;为了支持单细胞代谢研究的严谨性和可重复性 &#xff0c;今天分享基于时间序列的单细胞代谢数据分析处理工作流程&#xff0c;名为 SCMeTA。它保留了可扩展的接口和插件系统&#xff0c;以适应来自各种仪器的数据。对从 QE-Orbitrap MS 获取的…

2024最新的软件测试面试八股文(答案+文档)

一、软件测试基础面试题 1、阐述软件生命周期都有哪些阶段? 常见的软件生命周期模型有哪些? 软件生命周期是指一个计算机软件从功能确定设计&#xff0c;到开发成功投入使用&#xff0c;并在使用中不断地修改、增补和完善&#xff0c;直到停止该软件的使用的全过程(从酝酿到…

黑马头条APP手工测试项目

1.app有关概念 APP测试范围&#xff1a; 业务功能测试 专项测试&#xff1a;兼容性测试 、安装/卸载/升级测试、交叉事件测试 、push消息推送测试、性能测试、其他测试&#xff08;用户体验、权限/边界、权限&#xff09; 功能测试测试对象&#xff1a; 功能点&#xff08;单…

Linux系统通过libgpiod读写GPIO

本文介绍Linux系统通过libgpiod读写GPIO。 从Linux 4.8后&#xff0c;官方不再推荐使用sysfs操作GPIO&#xff0c;目前&#xff0c;libgpiod是操作GPIO的首选方法。本文以Raspberry Pi 4开发板为例简要介绍通过libgpiod读写GPIO。 1.libgpiod简介 libgpiod是用于Linux环境下…