Transfomer代码实现与细节理解

文章目录

  • 0. 简单理解
  • 1. 总体架构
    • 1.1 Encoder
    • 1.2 Decoder
  • 2. 实现代码
    • 2.1 数据预处理
    • 2.2 模型参数
    • 2.2 Positional Encoding 位置编码
    • 2.3 Pad mask 填充的掩码
    • 2.4 Subsequence Mask 解码器的自注意力掩码
    • 2.6 Scaled Dot Product Attention
    • 2.7 MultiHead Attention
    • 2.8 FeedForward Networks
    • 2.9 Encoder Layer
    • 2.10 Encoder
    • 2.11 DecoderLayer
    • 2.12 Decoder
    • 2.13 Transformer
  • 3 一些解释说明
    • 3.1 如何理解文中的Q、K、V
    • 3.2 如何理解这个模型?
  • Reference
  • 感受

0. 简单理解

在原论文Attention is all you need当中所提出的Transformer架构将过去的RNN/CNN 架构全部替换为Attention结构,提高了在NLP领域对于全文的理解能力和并行度,并且在其它领域该架构也有广泛的应用,本文作者为深度学习领域小白,尽可能用通俗的语言来描述这一架构,如果文中有错误或者理解不到位的地方,敬请读者指出

这里的并行度是指之前的RNN模型,当前时刻的输出依赖于前一个时刻的输出,并不能进行并行计算,这里的Transformer模型可以一次抓取整个句子的信息,主要运算时矩阵乘,所以有很强的并行性

1. 总体架构

1.1 Encoder

Transformer的总体架构如下图所示,左侧部分为编码器encoder,它负责将我们的翻译目标token(最小语义块)拆解成一个个的keyvalue, 类似于变形金刚当中的拆解. 它的输入为翻译目标的句子,由于不同的token长度不同,所以我们需要进行padding操作,但是padding的部分我们不用关心,所以需要进行掩码操作,这个掩码其实是一个上三角矩阵,将我们不需要关心的位置置为True即可.

它有两个子层,一个是多头自注意力层,另外一个是前向层.Transfomer当中的Encoder由N个这样的结构堆叠而成(原文中为6).
Encoder层的输出为keyvalue,提供给Decoder层使用

1.2 Decoder

右侧部分为Decoder层,它由三个子层堆叠而成,分别是带掩码的自注意层、多头注意力层以及一个全连接层(dropout = 0.1).
需要注意的是,在第二个子层当中,Multi-head Attention的K、V来自Encoder的输出,这部分不理解可以跳转到文末的附录当中查看

在这里插入图片描述

2. 实现代码

2.1 数据预处理

Transfomer当中的输入输出都是将句子中的token用映射来表示, 输入/输出 所有的token会用一个词典来记录,我们的inputs就是记录句子的单词在词典当中对应的下标即可

sentence = [
    # enc_input   dec_input    dec_output
    ['ich mochte ein bier P','S i want a beer .', 'i want a beer . E'],
    ['ich mochte ein cola P','S i want a coke .', 'i want a coke . E'],
]

# 词典,padding用0来表示
# 源词典
src_vocab = {'P':0, 'ich':1,'mochte':2,'ein':3,'bier':4,'cola':5}
src_vocab_size = len(src_vocab) # 6
# 目标词典(包含特殊符)
tgt_vocab = {'P':0,'i':1,'want':2,'a':3,'beer':4,'coke':5,'S':6,'E':7,'.':8}
# 反向映射词典,idx ——> word
idx2word = {v:k for k,v in tgt_vocab.items()}
tgt_vocab_size = len(tgt_vocab) # 9

src_len = 5 # 输入序列enc_input的最长序列长度,其实就是最长的那句话的token数
tgt_len = 6 # 输出序列dec_input/dec_output的最长序列长度

# 这个函数把原始输入序列转换成token表示
def make_data(sentence):
    enc_inputs, dec_inputs, dec_outputs = [],[],[]
    for i in range(len(sentence)):
        enc_input = [src_vocab[word] for word in sentence[i][0].split()]
        dec_input = [tgt_vocab[word] for word in sentence[i][1].split()]
        dec_output = [tgt_vocab[word] for word in sentence[i][2].split()]
        #这里的enc_inputs、dec_inputs存放的都是词典的序列号 在给出的sentence当中已经对句子进行了填充 相同位置(encode decode)都是相同长度    
        enc_inputs.append(enc_input)
        dec_inputs.append(dec_input)
        dec_outputs.append(dec_output)
    

    
    # LongTensor是专用于存储整型的,Tensor则可以存浮点、整数、bool等多种类型
    return torch.LongTensor(enc_inputs),torch.LongTensor(dec_inputs),torch.LongTensor(dec_outputs)

enc_inputs, dec_inputs, dec_outputs = make_data(sentence)
# 使用Dataset加载数据
class MyDataSet(Data.Dataset):
    def __init__(self,enc_inputs, dec_inputs, dec_outputs):
        super(MyDataSet,self).__init__()
        self.enc_inputs = enc_inputs
        self.dec_inputs = dec_inputs
        self.dec_outputs = dec_outputs
    
    #构造一个DataSet的常用步骤 实现__len__和__getitem__两个函数
    def __len__(self):
        # 我们前面的enc_inputs.shape = [2,5],所以这个返回的是2
        return self.enc_inputs.shape[0] 
    
    # 根据idx返回的是一组 enc_input, dec_input, dec_output
    def __getitem__(self, idx):
        return self.enc_inputs[idx], self.dec_inputs[idx], self.dec_outputs[idx]

# 构建DataLoader
loader = Data.DataLoader(dataset=MyDataSet(enc_inputs,dec_inputs, dec_outputs),batch_size=2,shuffle=True)

2.2 模型参数

# 用来表示一个词的向量长度   //词的原始长度
d_model = 512

# FFN的隐藏层神经元个数
d_ff = 2048

# 分头后的q、k、v词向量长度,依照原文我们都设为64
# 原文:queries and kes of dimention d_k,and values of dimension d_v .所以q和k的长度都用d_k来表示
d_k = d_v = 64

# Encoder Layer 和 Decoder Layer的个数
n_layers = 6

# 多头注意力中head的个数,原文:we employ h = 8 parallel attention layers, or heads
n_heads = 8

2.2 Positional Encoding 位置编码

#输入的embedding 构造一个5000个token * d_model 的矩阵 取句子到的位置上的就行
class PositionalEncoding(nn.Module):
    def __init__(self, d_model, dropout=0.1, max_len=5000): # dropout是原文的0.1,max_len原文没找到
        '''max_len是假设的一个句子最多包含5000个token'''
        super(PositionalEncoding, self).__init__()
        self.dropout = nn.Dropout(p=dropout)
        # 开始位置编码部分,先生成一个max_len * d_model 的矩阵,即5000 * 512
        # 5000是一个句子中最多的token数,512是一个token用多长的向量来表示,5000*512这个矩阵用于表示一个句子的信息
        pe = torch.zeros(max_len, d_model)
        pos = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1) # pos:[max_len,1],即[5000,1]
        # 先把括号内的分式求出来,pos是[5000,1],分母是[256],通过广播机制相乘后是[5000,256]
        div_term = pos / pow(10000.0,torch.arange(0, d_model, 2).float() / d_model)   #2i <= d_model
        # 再取正余弦
        pe[:, 0::2] = torch.sin(div_term)
        pe[:, 1::2] = torch.cos(div_term)
        # 一个句子要做一次pe,一个batch中会有多个句子,所以增加一维用来和输入的一个batch的数据相加时做广播
        pe = pe.unsqueeze(0) # [5000,512] -> [1,5000,512] 
        # 将pe作为固定参数保存到缓冲区,不会被更新
        self.register_buffer('pe', pe) #跟随模型一起被保存
        
        
    def forward(self, x):
        '''x: [batch_size, seq_len, d_model]'''
        # 5000是我们预定义的最大的seq_len,就是说我们把最多的情况pe都算好了,用的时候用多少就取多少
        x = x + self.pe[:, :x.size(1), :]
        return self.dropout(x) # return: [batch_size, seq_len, d_model], 和输入的形状相同
        

在这里插入图片描述

2.3 Pad mask 填充的掩码

# 为enc_input和dec_input做一个mask,把占位符P的token(就是0) mask掉
# 返回一个[batch_size, len_q, len_k]大小的布尔张量,True是需要mask掉的位置
def get_attn_pad_mask(seq_q, seq_k):
    batch_size, len_q = seq_q.size()
    batch_size, len_k = seq_k.size()
    # seq_k.data.eq(0)返回一个等大的布尔张量,seq_k元素等于0的位置为True,否则为False
    # 然后扩维以保证后续操作的兼容(广播)
    pad_attn_mask = seq_k.data.eq(0).unsqueeze(1) # pad_attn_mask: [batch_size,1,len_k]
    # 要为每一个q提供一份k,所以把第二维度扩展了q次
    # 另注意expand并非真正加倍了内存,只是重复了引用,对任意引用的修改都会修改原始值
    # 这里是因为我们不会修改这个mask所以用它来节省内存
    return pad_attn_mask.expand(batch_size, len_q, len_k) # return: [batch_size, len_q, len_k]
    # 返回的是batch_size个 len_q * len_k的矩阵,内容是True和False,
    # 第i行第j列表示的是query的第i个词对key的第j个词的注意力是否无意义,若无意义则为True,有意义的为False(即被padding的位置是True)

2.4 Subsequence Mask 解码器的自注意力掩码

# 用于获取对后续位置的掩码,防止在预测过程中看到未来时刻的输入
# 原文:to prevent positions from attending to subsequent positions
def get_attn_subsequence_mask(seq):
    """seq: [batch_size, tgt_len]"""
    # batch_size个 tgt_len * tgt_len的mask矩阵
    attn_shape = [seq.size(0), seq.size(1), seq.size(1)]
    # np.triu 是生成一个 upper triangular matrix 上三角矩阵,k是相对于主对角线的偏移量
    # k=1意为不包含主对角线(从主对角线向上偏移1开始)
    subsequence_mask = np.triu(np.ones(attn_shape), k=1)
    subsequence_mask = torch.from_numpy(subsequence_mask).byte() # 因为只有0、1所以用byte节省内存
    return subsequence_mask  # return: [batch_size, tgt_len, tgt_len]
#这个函数就是生成一个上三角矩阵 其中当前位置之后的都被置为1,即不可见

2.6 Scaled Dot Product Attention

计算公式如下所示
A t t e n t i o n ( Q , K , V ) = s o f t m a x ( Q K T d k ) V Attention(Q,K,V) = softmax(\frac{QK^T}{\sqrt{d_k}})V Attention(Q,K,V)=softmax(dk QKT)V
之所以需要除以一个 d k {\sqrt{d_k}} dk 是因为如果模型的数据量很大,softmax后的分布比较极端,会导致梯度消失,训练难以进行
在这里插入图片描述

class ScaledDotProductionAttention(nn.Module):
    '''
     input Q,K,V attn_mask
     retrun context: [batch_size, n_heads, len_q, d_v]
    '''
    def __init__(self):
        super(ScaledDotProductionAttention, self).__init__()

    def forward(self, Q, K, V, attn_mask):
        '''
        Q: [batch_size, n_heads, len_q, d_k]
        K: [batch_size, n_heads, len_k, d_k]
        V: [batch_size, n_heads, len_v(=len_k), d_v] 全文两处用到注意力,一处是self attention,另一处是co attention,前者不必说,后者的k和v都是encoder的输出,所以k和v的形状总是相同的
        attn_mask: [batch_size, n_heads, seq_len, seq_len]
        '''
        # 1) 计算注意力分数QK^T/sqrt(d_k)
        scores = torch.matmul(Q, K.transpose(-1, -2)) / np.sqrt(d_k)  # scores: [batch_size, n_heads, len_q, len_k]
        # 2)  进行 mask 和 softmax
        # mask为True的位置会被设为-1e9, soft之后为True的地方权重会特别小 以至于忽略不计
        scores.masked_fill_(attn_mask, -1e9)
        attn = nn.Softmax(dim=-1)(scores)  # attn: [batch_size, n_heads, len_q, len_k]
        # 3) 乘V得到最终的加权和
        context = torch.matmul(attn, V)  # context: [batch_size, n_heads, len_q, d_v]
        '''
        得出的context是每个维度(d_1-d_v)都考虑了在当前维度(这一列)当前token对所有token的注意力后更新的新的值,
        换言之每个维度d是相互独立的,每个维度考虑自己的所有token的注意力,所以可以理解成1列扩展到多列

        返回的context: [batch_size, n_heads, len_q, d_v]本质上还是batch_size个句子,
        只不过每个句子中词向量维度512被分成了8个部分,分别由8个头各自看一部分,每个头算的是整个句子(一列)的512/8=64个维度,最后按列拼接起来
        '''
        return context # context: [batch_size, n_heads, len_q, d_v]

2.7 MultiHead Attention

在这里插入图片描述

class MultiHeadAttention(nn.Module):
    def __init__(self):
        super(MultiHeadAttention, self).__init__()
        self.W_Q = nn.Linear(d_model, d_model)
        self.W_K = nn.Linear(d_model, d_model)
        self.W_V = nn.Linear(d_model, d_model)
        self.concat = nn.Linear(d_model, d_model)

    def forward(self, input_Q, input_K, input_V, attn_mask):
        '''
        input_Q: [batch_size, len_q, d_model] len_q是作为query的句子的长度,比如enc_inputs(2,5,512)作为输入,那句子长度5就是len_q
        input_K: [batch_size, len_k, d_model]
        input_V: [batch_size, len_v(len_k), d_model]
        attn_mask: [batch_size, seq_len, seq_len]
        '''
        residual, batch_size = input_Q, input_Q.size(0)

        # 1)linear projection [batch_size, seq_len, d_model] ->  [batch_size, n_heads, seq_len, d_k/d_v]
        #这里的[batch_size, n_heads, len_q, d_k]顺序是multi-head attention的固定顺序 这里的transpose(1,2)可以忽略
        Q = self.W_Q(input_Q).view(batch_size, -1, n_heads, d_k).transpose(1, 2) # Q: [batch_size, n_heads, len_q, d_k]
        K = self.W_K(input_K).view(batch_size, -1, n_heads, d_k).transpose(1, 2) # K: [batch_size, n_heads, len_k, d_k]
        V = self.W_V(input_V).view(batch_size, -1, n_heads, d_v).transpose(1, 2) # V: [batch_size, n_heads, len_v(=len_k), d_v]

        # 2)计算注意力
        # 自我复制n_heads次,为每个头准备一份mask
        attn_mask = attn_mask.unsqueeze(1).repeat(1, n_heads, 1, 1)  # attn_mask: [batch_size, n_heads, seq_len, seq_len]
        context = ScaledDotProductionAttention()(Q, K, V, attn_mask) # context: [batch_size, n_heads, len_q, d_v]

        # 3)concat部分
        context = torch.cat([context[:,i,:,:] for i in range(context.size(1))], dim=-1)
        output = self.concat(context)  # [batch_size, len_q, d_model]
        return nn.LayerNorm(d_model).cuda()(output + residual)  # output: [batch_size, len_q, d_model]

        '''        
        最后的concat部分,网上的大部分实现都采用的是下面这种方式(也是哈佛NLP团队的写法)
        context = context.transpose(1, 2).reshape(batch_size, -1, d_model)
        output = self.linear(context)
        但是我认为这种方式拼回去会使原来的位置乱序,于是并未采用这种写法,两种写法最终的实验结果是相近的
        '''

在这里插入图片描述

2.8 FeedForward Networks

class PositionwiseFeedForward(nn.Module):
    def __init__(self):
        super(PositionwiseFeedForward, self).__init__()
        # 就是一个MLP
        self.fc = nn.Sequential(
            nn.Linear(d_model, d_ff),
            nn.ReLU(),
            nn.Linear(d_ff, d_model)
        )

    def forward(self, inputs):
        '''inputs: [batch_size, seq_len, d_model]'''
        residual = inputs
        output = self.fc(inputs)
        return nn.LayerNorm(d_model).cuda()(output + residual) # return: [batch_size, seq_len, d_model] 形状不变

2.9 Encoder Layer

class EncoderLayer(nn.Module):
    def __init__(self):
        super(EncoderLayer, self).__init__()
        #sublayer1
        self.enc_self_attn = MultiHeadAttention()
        #sublayer2
        self.pos_ffn = PositionwiseFeedForward()

    def forward(self, enc_inputs, enc_self_attn_mask):
        '''
        enc_inputs: [batch_size, src_len, d_model]
        enc_self_attn_mask: [batch_size, src_len, src_len]
        '''
        # Q、K、V均为 enc_inputs
        enc_ouputs = self.enc_self_attn(enc_inputs, enc_inputs, enc_inputs, enc_self_attn_mask) # enc_ouputs: [batch_size, src_len, d_model]
        enc_ouputs = self.pos_ffn(enc_ouputs) # enc_outputs: [batch_size, src_len, d_model]
        return enc_ouputs  # enc_outputs: [batch_size, src_len, d_model]

2.10 Encoder

class Encoder(nn.Module):
    def __init__(self):
        super(Encoder, self).__init__()
        # 直接调的现成接口完成词向量的编码,输入是类别数和每一个类别要映射成的向量长度
        self.src_emb = nn.Embedding(src_vocab_size, d_model)
        self.pos_emb = PositionalEncoding(d_model)
        self.layers = nn.ModuleList([EncoderLayer() for _ in range(n_layers)])

    def forward(self, enc_inputs):
        '''enc_inputs: [batch_size, src_len]'''
        enc_outputs = self.src_emb(enc_inputs) # [batch_size, src_len] -> [batch_size, src_len, d_model]
        enc_outputs = self.pos_emb(enc_outputs) # enc_outputs: [batch_size, src_len, d_model]
        # Encoder中是self attention,所以传入的Q、K都是enc_inputs
        enc_self_attn_mask = get_attn_pad_mask(enc_inputs, enc_inputs)  # enc_self_attn_mask: [batch_size, src_len, src_len]
        for layer in self.layers:
            enc_outputs = layer(enc_outputs, enc_self_attn_mask)
        return enc_outputs  # enc_outputs: [batch_size, src_len, d_model]

2.11 DecoderLayer

class DecoderLayer(nn.Module):
    def __init__(self):
        super(DecoderLayer, self).__init__()
        self.dec_self_attn = MultiHeadAttention()
        self.dec_enc_attn = MultiHeadAttention()
        self.pos_ffn = PositionwiseFeedForward()

    def forward(self, dec_inputs, enc_outputs, dec_self_attn_mask, dec_enc_attn_mask):
        '''
        dec_inputs: [batch_size, tgt_len, d_model]
        enc_outputs: [batch_size, src_len, d_model]
        dec_self_attn_mask: [batch_size, tgt_len, tgt_len]
        dec_enc_attn_mask: [batch_size, tgt_len, src_len] 前者是Q后者是K
        '''
        
        dec_outputs = self.dec_self_attn(dec_inputs, dec_inputs, dec_inputs, dec_self_attn_mask)
        #                                input_Q       input_K       input_V
        dec_outputs = self.dec_enc_attn(dec_outputs, enc_outputs, enc_outputs, dec_enc_attn_mask)
        dec_outputs = self.pos_ffn(dec_outputs)

        return dec_outputs # dec_outputs: [batch_size, tgt_len, d_model]

2.12 Decoder

class Decoder(nn.Module):
    def __init__(self):
        super(Decoder, self).__init__()
        self.tgt_emb = nn.Embedding(tgt_vocab_size, d_model)
        self.pos_emb = PositionalEncoding(d_model)
        self.layers = nn.ModuleList([DecoderLayer() for _ in range(n_layers)])


    def forward(self, dec_inputs, enc_inputs, enc_outputs):
        '''
        这三个参数对应的不是Q、K、V,dec_inputs是Q,enc_outputs是K和V,enc_inputs是用来计算padding mask的
        dec_inputs: [batch_size, tgt_len]
        enc_inpus: [batch_size, src_len]
        enc_outputs: [batch_size, src_len, d_model]
        '''
        dec_outputs = self.tgt_emb(dec_inputs)
        dec_outputs = self.pos_emb(dec_outputs).cuda()
        dec_self_attn_pad_mask = get_attn_pad_mask(dec_inputs, dec_inputs).cuda()
        dec_self_attn_subsequence_mask = get_attn_subsequence_mask(dec_inputs).cuda()
        # 将两个mask叠加,布尔值可以视为0和1,和大于0的位置是需要被mask掉的,赋为True,和为0的位置是有意义的为False
        dec_self_attn_mask = torch.gt((dec_self_attn_pad_mask +
                                       dec_self_attn_subsequence_mask), 0).cuda()
        # 这是co-attention部分,为啥传入的是enc_inputs而不是enc_outputs呢  确实 这里是不是错了...
        dec_enc_attn_mask = get_attn_pad_mask(dec_inputs, enc_inputs)

        for layer in self.layers:
            dec_outputs = layer(dec_outputs, enc_outputs, dec_self_attn_mask, dec_enc_attn_mask)

        return dec_outputs # dec_outputs: [batch_size, tgt_len, d_model]

2.13 Transformer

class Transformer(nn.Module):
    def __init__(self):
        super(Transformer, self).__init__()
        self.encoder = Encoder().cuda()
        self.decoder = Decoder().cuda()
        self.projection = nn.Linear(d_model, tgt_vocab_size).cuda()

    def forward(self, enc_inputs, dec_inputs):
        '''
        enc_inputs: [batch_size, src_len]
        dec_inputs: [batch_size, tgt_len]
        '''
        enc_outputs = self.encoder(enc_inputs)
        dec_outputs = self.decoder(dec_inputs, enc_inputs, enc_outputs)
        dec_logits = self.projection(dec_outputs) # dec_logits: [batch_size, tgt_len, tgt_vocab_size]

        # 解散batch,一个batch中有batch_size个句子,每个句子有tgt_len个词(即tgt_len行),
        # 现在让他们按行依次排布,如前tgt_len行是第一个句子的每个词的预测概率,
        # 再往下tgt_len行是第二个句子的,一直到batch_size * tgt_len行
        return dec_logits.view(-1, dec_logits.size(-1))  #  [batch_size * tgt_len, tgt_vocab_size]
        '''最后变形的原因是:nn.CrossEntropyLoss接收的输入的第二个维度必须是类别'''

3 一些解释说明

3.1 如何理解文中的Q、K、V

假想你有一个map/dict或者其他名字,一个key对应一个value,在检索的时候,给定query,如果query in map,就是query等于其中一个key,就返回对应的value。这个方法太hard了,有就是有,没有就是没有。对于qkv都是向量的情况,这种方法不可行,只能让它变soft,那就是算一算querykey的关系,按照比例对value加和,这和max变成softmax有异曲同工之妙

论文当中的Q、K、V都是根据输入的序列来确定的,但是由序列映射到的K、Q、V的值映射的那个权重是进行学习权重矩阵 W Q 、 W K 、 W V W^{Q} 、W^K、W^V WQWKWV, 多头注意力就是将一个任务分配给八个人去做,如果有一个人(一组参数)不靠谱,对整个模型影响也不大
在这里插入图片描述

3.2 如何理解这个模型?

举个简单的例子来说明,比如你想翻译我爱中国这句话到英文I love China, 那么你训练的时候,Encoder就会对I love China进行一个"编码"操作,将其中的token如ilovechina拆分为一个个的向量K、V,然后翻译的时候会计算向量比如说作为Q和向量ilovechina之间的相似度,计算得到最后和i的相似度最大,softmax出来的概率最大,所以会被翻译为i
A t t e n t i o n ( Q , K , V ) = s o f t m a x ( Q K T d k ) V Attention(Q,K,V) = softmax(\frac{QK^T}{\sqrt{d_k}})V Attention(Q,K,V)=softmax(dk QKT)V

Reference

[1] Attention is all you need paper
[2] 李沐-Transformer
[3] 耿直哥-bilibili
[4] https://github.com/BoXiaolei/MyTransformer_pytorch

感受

这是第二遍阅读这篇论文,之前看过对里面的内容不求甚解,这次通过阅读代码以及重新阅读论文有了很多新的理解和领悟,当然由于最近其它的任务太多,没有进一步深究也是非常遗憾,等下周结束手头的事情应该可以进一步更新这篇文章.

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用IT-Tools+Cpolar在Windows搭建自己的IT工具箱并实现远程在线使用

文章目录 1. 使用Docker本地部署it-tools2. 本地访问it-tools3. 安装cpolar内网穿透4. 固定it-tools公网地址 本篇文章将介绍如何在Windows上使用Docker本地部署IT- Tools&#xff0c;并且同样可以结合cpolar实现公网访问。 在前一篇文章中我们讲解了如何在Linux中使用Docker搭…

异地组网怎么安装?

异地组网安装是指在不同地域的多个设备之间建立网络连接&#xff0c;以便实现数据传输和协同工作的过程。在如今的数字化时代&#xff0c;异地组网安装已经成为了许多企业和组织所必需的一项技术。 天联的使用场景 在异地组网安装中&#xff0c;天联是一种常用的工具。它具有以…

北京大学快手发布统一的图文视频生成大模型Video-LaVIT

随着多模态大语言模型&#xff08;LLMs&#xff09;的新发展&#xff0c;人们越来越关注如何将它们从图像-文本数据扩展到更具信息量的真实世界视频。与静态图像相比&#xff0c;视频为有效的大规模预训练带来了独特的挑战&#xff0c;因为需要对其时空动态进行建模。 针对视频…

vs配置opencv运行时“发生生成错误,是否继续并运行上次的成功生成”BUG解决办法

vs“发生生成错误&#xff0c;是否继续并运行上次的成功生成” 新手在用vs配置opencv时遇到这个错误时&#xff0c;容易无从下手解决。博主亲身经历很有可能是release/debug模式和配置文件不符的问题。 在配置【链接器】→【输入】→【附加依赖项】环节&#xff0c;编辑查看选择…

MySQL数据库max_allowed_packet参数

如上图所示的报错&#xff0c;我在提交接口的时候出现了这个错误&#xff1a; MySqlConnector.MySqlException:Error submitting 4MB packet;ensure max_allowed_packet is greater than 4MB.在MySQL数据库中&#xff0c;有一个参数叫max_allowed_packet&#xff0c;这个参数会…

013_NaN_in_Matlab中的非数与调试方法

Matlab中的非数与调试方法 是什么&#xff1f; Matlab编程&#xff08;计算器使用&#xff09;中经常有个错误给你&#xff0c;这句话里可能包含一个关键词NaN。大部分学生都有过被 NaN 支配的痛苦记忆。 NaN 是 Not a Number 的缩写&#xff0c;表示不是一个数字。在 Matla…

Apache Zeppelin 命令执行漏洞复现(CVE-2024-31861)

0x01 产品简介 Apache Zeppelin 是一个让交互式数据分析变得可行的基于网页的开源框架&#xff0c;Zeppelin提供了数据分析、数据可视化等功能&#xff0c; 0x02 漏洞概述 Apache Zeppelin 中代码生成控制不当&#xff08;“代码注入”&#xff09;漏洞。攻击者可以使用 She…

锁策略总结

锁策略 悲观锁和乐观锁 乐观锁和悲观锁不是具体类型的锁而是指两种不同的对待加锁的态度&#xff0c;这两个锁面对锁冲突的态度是相反的。 乐观锁&#xff1a;认为不存在很多的并发操作&#xff0c;因此不需要加锁。悲观锁&#xff1a;认为存在很多并发操作&#xff0c;因此需…

python实现简单的车道线检测

描述 python实现简单的车道线检测&#xff0c;本文章将介绍两种简单的方法 颜色阈值区域掩模canny边缘检测霍夫变换 这两种方法都能实现简单的车道线检测demo&#xff0c;注意仅仅是demo 下面的图片是用到的测试图片 方法1&#xff1a;颜色阈值&#xff08;Color Selection…

jpa使用Querydsl需要规避的一些坑

在使用Spring Data JPA时&#xff0c;通常会使用Querydsl来构建类型安全的查询。在Querydsl中&#xff0c;为了区分实体类与Querydsl查询类&#xff0c;习惯上会给查询类的前缀添加一个"Q"&#xff0c;表示该类是一个查询类。这样做可以有效地避免实体类与查询类之间…

并发学习28--多线程 Fork、Join线程池

概念 使用 import java.util.concurrent.ForkJoinPool; import java.util.concurrent.RecursiveTask;public class TC51 {public static void main(String[] args) {//递归到最小不可分解单元&#xff0c;再进行计算ForkJoinPool pool new ForkJoinPool(5);pool.invoke(new My…

「 典型安全漏洞系列 」14.NoSQL注入漏洞详解

NoSQL注入是一个漏洞&#xff0c;攻击者能够干扰应用程序对NoSQL数据库进行的查询&#xff0c;本文我们将研究如何测试一般的NoSQL漏洞&#xff0c;然后重点研究如何利用MongoDB中的漏洞&#xff08;MongoDB是最流行的NoSQL数据库&#xff09;。 1. 什么是NoSQL注入 NoSQL注入…

Langchain入门到实战-第二弹

Langchain入门到实战 Langchain快速入门官网地址Langchain概述Langchain调用大模型更新计划 Langchain快速入门 官网地址 声明: 由于操作系统, 版本更新等原因, 文章所列内容不一定100%复现, 还要以官方信息为准 https://python.langchain.com/Langchain概述 LangChain是一个…

thinkphp 框架封装curl请求

tp6 或者 tp8框架 在框架的app/common.php 文件里加一些方法就可以 app\common.php 在这个文件里加 以下代码 就可以实现基于 curl的请求方法 (记得要开启 php的curl扩展) 查看方法 cmd里输入 php -m if (!function_exists(get)) {/*** 发送get请求* param string $url 请求…

【御控物联】 Java JSON结构转换(2):对象To对象——属性重组

文章目录 一、JSON结构转换是什么&#xff1f;二、案例之《JSON对象 To JSON对象》三、代码实现四、在线转换工具五、技术资料 一、JSON结构转换是什么&#xff1f; JSON结构转换指的是将一个JSON对象或JSON数组按照一定规则进行重组、筛选、映射或转换&#xff0c;生成新的JS…

Stable Diffusion之文生图模型训练

1、数据准备 提前准备好一组相关的照片。 在线的图片处理网站 BIRME - Bulk Image Resizing Made Easy 2.0 (Online & Free) 将图片转成统一大小&#xff0c;支持批量处理&#xff0c;效率高 2、生成提示词 进入stable diffusion webui页面 旧版直接使用 train/proproc…

【服务器配置】ngnix环境配置

ngnix环境配置 概述 Nginx&#xff08;发音为 “engine X”&#xff09;是一个高性能的 HTTP 和反向代理服务器&#xff0c;也可用作邮件代理服务器。它以其稳定性、丰富的功能集、简单的配置和低资源消耗而闻名。Nginx 被广泛用于提高网站的可扩展性和性能&#xff0c;因为它…

Android使用shape属性绘制边框内渐变色

目录 先上效果图实现方法shape属性介绍代码结果 先上效果图 这是使用AndroidStudio绘制的带有渐变色的边框背景色 实现方法 项目中由于UI设计需求&#xff0c;需要给按钮、控件设置带有背景色效果的。以下是UI效果图。 这里我们使用shape属性来绘制背景效果。 shape属性介…

JS-32-jQuery01-jQuery的引入

一、初识jQuery jQuery是JavaScript世界中使用最广泛的一个库。鉴于它如此流行&#xff0c;又如此好用&#xff0c;所以每一个入门JavaScript的前端工程师都应该了解和学习它。 jQuery是一个优秀的JS函数库。 &#xff08;对BOM和DOM的封装&#xff09; jQuery这么流行&#x…

IJKPLAYER源码分析-总体概述

1 前言 前面系列文章&#xff0c;对IJKPLAYER源码所涉及到的各个技术点、模块、关键技术及其实现原理&#xff0c;做了较为详细的梳理。但窃以为那只是微观层面的梳理&#xff0c;还不够完整不够透彻&#xff0c;本文拟尝试从宏观框架架构的视角加以补充。 算是抛砖引玉。 2 架…