深度学习基础练习:代码复现transformer重难点

2024/11/10-2024/11/18:

        主要对transformer一些比较难理解的点做了一些整理,希望对读者有所帮助。

前置知识:

深度学习基础练习:从pytorch API出发复现LSTM与LSTMP-CSDN博客

【神经网络】学习笔记十四——Seq2Seq模型-CSDN博客

【官方双语】一个视频理解神经网络注意力机制,详细阐释!_哔哩哔哩_bilibili

【官方双语】Transformer模型最通俗易懂的讲解,零基础也能听懂!_哔哩哔哩_bilibili

代码参考:

19、Transformer模型Encoder原理精讲及其PyTorch逐行实现_哔哩哔哩_bilibiliicon-default.png?t=O83Ahttps://www.bilibili.com/video/BV1cP4y1V7GF?spm_id_from=333.788.videopod.sections&vd_source=db0d5acc929b82408b1040d67f2b1dde

Chapter 1: Parameter Set

import torch
import torch.nn as nn
import torch.nn.functional as F# 关于word embedding 以序列建模为例
# 考虑source sentence 和 target sentence
# 构建序列,序列的字符以其在词表中的索引形式表示
torch.manual_seed(42)batch_size = 2
# 单词表大小(即设只有8种单词)
max_num_src_words = 8
max_num_tgt_words = 8
model_dim = 8# 序列的最长长度
max_src_seq_len = 5
max_tgt_seq_len = 5
# 序列编码的最大长度
max_position_len = 5src_len = torch.randint(2, 5, (batch_size,))
tgt_len = torch.randint(2, 5, (batch_size,))
"""
例:
tensor([2, 4])  src_seq 第一个句子长度为2 第二个句子长度为4
tensor([3, 3])  tgt_seq 第一个句子长度为3 第二个句子长度为3
"""
# 以单词索引构成的句子
# 加入pad使每个句子的长度相同:因为每批的句子都要做统一处理,做相同的矩阵运算,所以同一批次的句子不能是长短不一的向量
src_seq = torch.stack([F.pad(torch.randint(1, max_num_src_words, (L,)), (0, max_src_seq_len-L)) for L in src_len])
tgt_seq = torch.stack([F.pad(torch.randint(1, max_num_tgt_words, (L,)), (0, max_tgt_seq_len-L)) for L in tgt_len])

        最后两句代码的可读性不是很好,这里print解释一下:

origin:
[tensor([5, 4]), tensor([6, 6, 1, 1])]
[tensor([1, 6, 7]), tensor([5, 2, 3])]
===>
after pad:
[tensor([5, 4, 0, 0, 0]), tensor([6, 6, 1, 1, 0])]
[tensor([1, 6, 7, 0, 0]), tensor([5, 2, 3, 0, 0])]
===>
after stack:
tensor([[5, 4, 0, 0, 0],    第一个输入[6, 6, 1, 1, 0]])   第二个输入
tensor([[1, 6, 7, 0, 0],    第一个需要预测的输出[5, 2, 3, 0, 0]])   第二个需要预测的输出

         在这个阶段,我们生成了输入以及需要预测的输出并用pad将其对齐,这里使用pad将空位填充为0。


Chapter 2: Word Embedding

# 构造embedding,将每一种字母索引映射为model_dim位的embedding
# 词表有[1-8)共7个字母索引,pad用0填充空位,所以共有8种索引,每种索引用8个浮点数构成的列表表示
src_embedding_table = nn.Embedding(max_num_src_words, model_dim)
tgt_embedding_table = nn.Embedding(max_num_tgt_words, model_dim)
# print(src_embedding_table.weight)

        embedding可以理解为将每个字母的索引(下文也可能将字母索引说成单词索引,这两个意思一样)用一个model_dim位数的浮点数序列来表征。

        这里放一下src_embedding_table的内容 :

src_embedding_table
torch.Size([8, 8])
Parameter containing:
tensor([[ 1.6423, -0.1596, -0.4974,  0.4396, -0.7581,  1.0783,  0.8008,  1.6806],[ 1.2791,  1.2964,  0.6105,  1.3347, -0.2316,  0.0418, -0.2516,  0.8599],[-1.3847, -0.8712, -0.2234,  1.7174,  0.3189, -0.4245,  0.3057, -0.7746],[-1.5576,  0.9956, -0.8798, -0.6011, -1.2742,  2.1228, -1.2347, -0.4879],[-0.9138, -0.6581,  0.0780,  0.5258, -0.4880,  1.1914, -0.8140, -0.7360],[-1.4032,  0.0360, -0.0635,  0.6756, -0.0978,  1.8446, -1.1845,  1.3835],[ 1.4451,  0.8564,  2.2181,  0.5232,  0.3466, -0.1973, -1.0546,  1.2780],[-0.1722,  0.5238,  0.0566,  0.4263,  0.5750, -0.6417, -2.2064, -0.7508]],requires_grad=True)

         生成embedding table之后,就可以将原始数据根据每个字母的索引将其转化为对应的浮点数编码:

src_embedding = src_embedding_table(src_seq)
tgt_embedding = tgt_embedding_table(tgt_seq)
# print(src_embedding)

        生成结果如下:

共两句,每句五个单词索引(包括填充的0),每个单词索引由8个浮点数表示
src_embedding.shape:
torch.Size([2, 5, 8])
src_embedding:
tensor([[[-1.4032,  0.0360, -0.0635,  0.6756, -0.0978,  1.8446, -1.1845,1.3835],[-0.9138, -0.6581,  0.0780,  0.5258, -0.4880,  1.1914, -0.8140,-0.7360],[ 1.6423, -0.1596, -0.4974,  0.4396, -0.7581,  1.0783,  0.8008,1.6806],[ 1.6423, -0.1596, -0.4974,  0.4396, -0.7581,  1.0783,  0.8008,1.6806],[ 1.6423, -0.1596, -0.4974,  0.4396, -0.7581,  1.0783,  0.8008,1.6806]],[[ 1.4451,  0.8564,  2.2181,  0.5232,  0.3466, -0.1973, -1.0546,1.2780],[ 1.4451,  0.8564,  2.2181,  0.5232,  0.3466, -0.1973, -1.0546,1.2780],[ 1.2791,  1.2964,  0.6105,  1.3347, -0.2316,  0.0418, -0.2516,0.8599],[ 1.2791,  1.2964,  0.6105,  1.3347, -0.2316,  0.0418, -0.2516,0.8599],[ 1.6423, -0.1596, -0.4974,  0.4396, -0.7581,  1.0783,  0.8008,1.6806]]], grad_fn=<EmbeddingBackward0>)

        可以看到 src_embedding相比于src_seq多出了一个维度且大小为8,跟前面的解释相符合。


Chapter 3: Position Embedding 

        该步骤在word embedding之后,是为了强调输入的位置信息,这一步是比较重要的,比如Bob killed John 和 John killed Bob 虽然单词都相同,但是表达的意思完全相反。位置编码就是为了让模型捕获到这一部分的信息。

        下面是论文中位置编码相关的公式,pos指单词在句子中的位置(从头开始数),i指用来表征每个单词索引的word embedding中每个元素的对应位置:

# 构建position embedding 位置编码 针对每个句子,即每个句子的[5(单词数量), 8(embedding编码)]
# 公式中的列标 [0, 5)
pos_mat = torch.arange(max_position_len).reshape((-1, 1))
print(pos_mat)
# 公式中的行标 [0, 2, 4, 6]
i_mat = torch.pow(10000, torch.arange(0, 8, 2).reshape((1, -1)) / model_dim)
print(i_mat)# 生成每个单词索引位置对应的位置编码
pe_embedding_table = torch.zeros(max_position_len, model_dim)
# 偶数列
pe_embedding_table[:, 0::2] = torch.sin(pos_mat / i_mat)
# 奇数列
pe_embedding_table[:, 1::2] = torch.cos(pos_mat / i_mat)# print(pe_embedding_table)
# 构建新的embedding并将其权重用之前计算的位置编码覆写
pe_embedding = nn.Embedding(max_position_len, model_dim)
pe_embedding.weight = nn.Parameter(pe_embedding_table, requires_grad=False)src_pos = torch.cat([torch.unsqueeze(torch.arange(max_src_seq_len), 0) for _ in range(batch_size)]).to(torch.int32)
tgt_pos = torch.cat([torch.unsqueeze(torch.arange(max_tgt_seq_len), 0) for _ in range(batch_size)]).to(torch.int32)

         这里放一下src_pos和tgt_pos的print:

tensor([[0, 1, 2, 3, 4],[0, 1, 2, 3, 4]], dtype=torch.int32)
tensor([[0, 1, 2, 3, 4],[0, 1, 2, 3, 4]], dtype=torch.int32)

        请注意这还不是位置编码,只是每个元素的行列信息和位置编码的一些前置参数,接下来我们根据论文里的公式来:

src_pe_embedding = pe_embedding(src_pos)
tgt_pe_embedding = pe_embedding(tgt_pos)
# print(src_pe_embedding)

        因为position embedding需要直接与word embedding相加,所以我们也把它做成一个同样shape的张量,这样就可以为每个词添加上位置信息,值得注意的是,位置编码只与单词在所属句子中的位置有关,跟句子之间的相对位置无关,即如果有两个单词各自是一个句子中的第一个单词,他们的位置编码相同: 

src_pe_embedding.shape:
torch.Size([2, 5, 8])
src_pe_embedding:
tensor([[[ 0.0000e+00,  1.0000e+00,  0.0000e+00,  1.0000e+00,  0.0000e+00,1.0000e+00,  0.0000e+00,  1.0000e+00],[ 8.4147e-01,  5.4030e-01,  9.9833e-02,  9.9500e-01,  9.9998e-03,9.9995e-01,  1.0000e-03,  1.0000e+00],[ 9.0930e-01, -4.1615e-01,  1.9867e-01,  9.8007e-01,  1.9999e-02,9.9980e-01,  2.0000e-03,  1.0000e+00],[ 1.4112e-01, -9.8999e-01,  2.9552e-01,  9.5534e-01,  2.9995e-02,9.9955e-01,  3.0000e-03,  1.0000e+00],[-7.5680e-01, -6.5364e-01,  3.8942e-01,  9.2106e-01,  3.9989e-02,9.9920e-01,  4.0000e-03,  9.9999e-01]],[[ 0.0000e+00,  1.0000e+00,  0.0000e+00,  1.0000e+00,  0.0000e+00,1.0000e+00,  0.0000e+00,  1.0000e+00],[ 8.4147e-01,  5.4030e-01,  9.9833e-02,  9.9500e-01,  9.9998e-03,9.9995e-01,  1.0000e-03,  1.0000e+00],[ 9.0930e-01, -4.1615e-01,  1.9867e-01,  9.8007e-01,  1.9999e-02,9.9980e-01,  2.0000e-03,  1.0000e+00],[ 1.4112e-01, -9.8999e-01,  2.9552e-01,  9.5534e-01,  2.9995e-02,9.9955e-01,  3.0000e-03,  1.0000e+00],[-7.5680e-01, -6.5364e-01,  3.8942e-01,  9.2106e-01,  3.9989e-02,9.9920e-01,  4.0000e-03,  9.9999e-01]]])

        这公式为什么要这么做?简单来说这可以给句子中的每个单词生成一个独一无二的位置编码,避免位置编码重复。除此之外,将正余弦函数作为position embedding还可以提高模型的泛化能力,即使在推理阶段遇到了比训练中最大序列还要长的句子,也可以通过训练中某个位置位置编码的线性组合得到更长位置的位置编码。


Chapter 4: Encoder Self-attention Mask 

4.1 MatMul 

        注意力机制其实就是对一个句子序列算出一个新的表征。注意力权重是通过Query和Key的相似度来计算的,而这两者又都是基于上面的word embedding经过几个线性层计算出来的。

        上面这张图代表了这个阶段的操作。对于整个句子而言,Query和Key是两个矩阵,而如果对应到每个具体单词上面,这两者又是两个向量。因此,两个矩阵做内积便能得到每个单词与其他单词的相似度。

4.2 Scale

        除此之外,理解公式中的Scale——分母dk(隐含层大小)也有些困难。我们都知道在训练时神经网络会进行反向传播来修正参数,而修正参数的幅度有与计算出的雅可比行列式中各个元素的值有关,这里直接借用视频中的一张图来说明:

        reference Transformer模型Encoder原理精讲及其PyTorch逐行实现 1:22:56

        在这张图上我们可以看到给一系列随机数 *10 和 /10 之后再经过softmax,他们计算得到的雅可比行列式的值是不一样的。在 *10 之后,计算出的梯度比较“尖锐”,也就是方差较大,有的梯度较大,有的梯度却极小,这并不利于模型的收敛。而在 /10 之后,计算出的梯度便平缓了许多,这意味着每个参数都可以在反向传播中得到有效的修正。并且,我们在数学角度上可以证明将根号dk作为分母能将矩阵点乘的方差缩小为1。

        如上图,计算完单词之间的相似度之后,再经过softmax归一化就能得到0-1之间的归一化值。因为softmax的单调特性,相似度越大则经过softmax之后的值越大。

4.3 Mask

         接下来,我们开始对还记得我们在word embedding阶段创建的输入吗?

tensor([[5, 4, 0, 0, 0], 第一个输入        原长度为2,填充两个0

            [6, 6, 1, 1, 0]]) 第二个输入        原长度为4,填充一个0

         当时使用了0来对句子进行填充,将句子向量强行扩充为5维。而在计算self-attention的阶段,我们只对句子的有效长度进行attention计算。这就需要遮住无效的部分(所谓无效的部分下文有解释)。如果在这些位置上补一些无穷小(负无穷)的值,经过softmax操作,这些值在输出之后就会变为0,从而避免对全局概率产生影响。

        因此,有如下代码:

# 构建encoder的self-attention mask
# mask.shape: [batch_size, max_src_seq_len, max_src_seq_len], 值为1或-inf
valid_encoder_pos_matrix = torch.stack([F.pad(torch.ones(L, L), (0, max_src_seq_len-L, 0, max_src_seq_len-L)) for L in src_len])
masked_encoder_self_attention = valid_encoder_pos_matrix == 0
masked_encoder_self_attention:
tensor([[[False, False,  True,  True,  True],[False, False,  True,  True,  True],[ True,  True,  True,  True,  True],[ True,  True,  True,  True,  True],[ True,  True,  True,  True,  True]],[[False, False, False, False,  True],[False, False, False, False,  True],[False, False, False, False,  True],[False, False, False, False,  True],[ True,  True,  True,  True,  True]]])

        如果你并没有很理解该章节所展示的mask原理,那么可能你会对掩码的矩阵矩阵形式产生一点疑问,这里稍微做一下补充:

        Q:为什么输入的是1*5个词的句子,所采用的mask却是5*5? 

        A:mask遮掩的是词向量之间的关系。如上面所说,在Scale Dot-Product Attention阶段,对于整个句子而言,Query和Key是两个矩阵,而如果对应到每个具体单词上面,这两者又是两个向量,即Q和K中的第一行表征第一个单词,第二行表征第二个单词。

        而这两个矩阵又是通过word embedding经过线性层计算出来的,因此,两者的shape应该为(句子中的单词数量n * hidden),因此,Q可以与K的转置矩阵做点积。

        而这个输出的相似度矩阵中,位置(1, 1)的元素是第一个词与自身的相似度,位置(1, 2)的元素是第一个词与第二个词的相似度.....以此类推。若我们的句子中只有两个单词,类似于(1, 3)或者(3, 3)这种位置就是不应该存在的——因为句子中并不存在第三个单词。

        为了方便统一处理,这个位置我们之前填充的是0。但是神经网络并不知道这个地方是“空”的,那就只能使用mask来提示后面的softmax不要让这些地方的值影响全局概率的预测。因此,mask的维度是(词个数*词个数),对非法的位置设置为true,在后面的代码中进行填充-inf来遮掩。

score = torch.randn(batch_size, max_src_seq_len, max_src_seq_len)
masked_score = score.masked_fill(masked_encoder_self_attention, float("-inf"))
prob = masked_score.softmax(dim=-1)
print(prob)

        在masked_score中,我们就根据mask矩阵对一个随机生成的矩阵进行了掩码操作,看看效果:

prob:
tensor([[[0.5660, 0.4340, 0.0000, 0.0000, 0.0000],[0.6870, 0.3130, 0.0000, 0.0000, 0.0000],[   nan,    nan,    nan,    nan,    nan],[   nan,    nan,    nan,    nan,    nan],[   nan,    nan,    nan,    nan,    nan]],[[0.1508, 0.1173, 0.1579, 0.5740, 0.0000],[0.4315, 0.1342, 0.1746, 0.2597, 0.0000],[0.1446, 0.2971, 0.2774, 0.2809, 0.0000],[0.1549, 0.5402, 0.1237, 0.1812, 0.0000],[   nan,    nan,    nan,    nan,    nan]]])

         第一个矩阵对应第一个只有两个单词的句子。根据我们上文做出的Answer,矩阵的(1, 1)、(1, 2)、(2, 1)、(2, 2)都是对应的两个词之间的相似度关系,而此外的位置显然是“非法”的。以第一行为例 ,该行代表第一个词与其他所有词的相似度关系,在对非法位置置为-inf再计算softmax之后,第一个词只有与自身和第二个词有相似度关系,和其他不存在的位置则没有。

        这就是mask的生效机制。


Chapter 5: Intra-attention Mask

        在这个模块中,输出部分在经过第一个注意力模块后输出的值作为Query,Input模块中的输出作为Key和Value,再进行一次注意力的计算。

        该模块的重点是不同词数量的句子之间的相似度计算,其实与上个章节的内容基本相同,这里直接贴代码:

# 构建intra-attention mask
# Q @ K.T shape: [batch_size, tgt_seq_len, src_seq_len]
valid_encoder_pos = torch.unsqueeze(torch.cat([torch.unsqueeze(F.pad(torch.ones(L), (0, max_src_seq_len-L)), 0) for L in src_len], 0), 2)
# print(valid_encoder_pos)
valid_decoder_pos = torch.unsqueeze(torch.cat([torch.unsqueeze(F.pad(torch.ones(L), (0, max_tgt_seq_len-L)), 0) for L in tgt_len], 0), 2)
# print(valid_decoder_pos)
valid_cross_pos = torch.bmm(valid_decoder_pos, valid_encoder_pos.transpose(1, 2))
invalid_cross_pos_matrix = valid_cross_pos == 0

        然后print一下:

tensor([[[False, False,  True,  True,  True],[False, False,  True,  True,  True],[False, False,  True,  True,  True],[ True,  True,  True,  True,  True],[ True,  True,  True,  True,  True]],[[False, False, False, False,  True],[False, False, False, False,  True],[False, False, False, False,  True],[ True,  True,  True,  True,  True],[ True,  True,  True,  True,  True]]])

        简单解释一下 ,还记得我们的原始数据吗?

tensor([[5, 4, 0, 0, 0],         第一个输入

           [6, 6, 1, 1, 0]])         第二个输入

tensor([[1, 6, 7, 0, 0],         第一个需要预测的输出

           [5, 2, 3, 0, 0]])         第二个需要预测的输出

        类比于上个章节的内容,我们需要注意的是:句子中的词不再和本句子中的词计算相似度了,而是和需要预测的输出中的词来计算相似度,Query矩阵现在表征的是需要输出的句子。

        以得到的第一个bool矩阵为例,(1, 1)位置的数据是第一个输出中的第一个词与输入的第一个词的关系,(1, 3)位置的词是第一个输出中的第1个词与输入的第3个词的关系——因为输入并没有第三个词,所以我们在此设置为True来做掩码......以此类推。

        


Chapter 6: Decoder Self-attention Mask

        这里稍微往前推一下,写一下decoder的self-attention mask。

        这个mask也有人说是维持因果性的mask。它的思路很简单,如果用于流式的预测和生成,那么在预测下一个词的时候,很显然训练模型不能用下一个词的信息来训练,只能用之前的数据,这就需要一个下三角矩阵shape的mask来做到。

tril_matrix = [F.pad(torch.tril(torch.ones(L, L)), (0, max_tgt_seq_len-L, 0, max_tgt_seq_len-L)) for L in tgt_len]
valid_decoder_tri_matrix = torch.stack(tril_matrix)
# print(valid_decoder_tri_matrix)
invalid_decoder_tri_matrix = valid_decoder_tri_matrix == 0
tensor([[[False,  True,  True,  True,  True],[False, False,  True,  True,  True],[False, False, False,  True,  True],[ True,  True,  True,  True,  True],[ True,  True,  True,  True,  True]],[[False,  True,  True,  True,  True],[False, False,  True,  True,  True],[False, False, False,  True,  True],[ True,  True,  True,  True,  True],[ True,  True,  True,  True,  True]]])

        这个mask是对于输出而言的,而我们的两个需要预测的输出都有三个词,因此是两个3*3的矩阵。现在以第一个矩阵为例讲一下:第一行意思是已经预测完第一个词,开始预测第二个,那肯定要把后面的全遮住,后面几行同理。

score = torch.randn(batch_size, max_tgt_seq_len, max_tgt_seq_len)
masked_score = score.masked_fill(invalid_decoder_tri_matrix, float("-inf"))

 tensor([[[ 1.0441,    -inf,    -inf,    -inf,    -inf],
         [ 0.0854, -1.3793,    -inf,    -inf,    -inf],
         [ 0.5239, -0.2694, -1.6191,    -inf,    -inf],
         [   -inf,    -inf,    -inf,    -inf,    -inf],
         [   -inf,    -inf,    -inf,    -inf,    -inf]],

        [[ 0.7337,    -inf,    -inf,    -inf,    -inf],
         [ 2.0207,  0.2539,    -inf,    -inf,    -inf],
         [ 2.5574,  0.5716,  1.3596,    -inf,    -inf],
         [   -inf,    -inf,    -inf,    -inf,    -inf],
         [   -inf,    -inf,    -inf,    -inf,    -inf]]])

        模拟一下mask之后计算相似度的效果:

prob = masked_score.softmax(dim=-1)

 tensor([[[1.0000, 0.0000, 0.0000, 0.0000, 0.0000],
         [0.8123, 0.1877, 0.0000, 0.0000, 0.0000],
         [0.6371, 0.2882, 0.0747, 0.0000, 0.0000],
         [   nan,    nan,    nan,    nan,    nan],
         [   nan,    nan,    nan,    nan,    nan]],

        [[1.0000, 0.0000, 0.0000, 0.0000, 0.0000],
         [0.8541, 0.1459, 0.0000, 0.0000, 0.0000],
         [0.6949, 0.0954, 0.2097, 0.0000, 0.0000],
         [   nan,    nan,    nan,    nan,    nan],
         [   nan,    nan,    nan,    nan,    nan]]])


Chapter 6: Scaled Self-attention

         这部分很简单,其实就是写下这个公式的函数。

        代码如下,就不再赘述了:

# 构建scaled self-attention
def scaled_dot_product_attention(Q, K, V, mask):# shape of Q K V: [batch_size, seq_len, model_dim]d_k = Q.shape[-1]scores = torch.matmul(Q, K.transpose(-2, -1)) / torch.sqrt(torch.tensor(d_k).float())if mask is not None:scores = scores.masked_fill(mask, float("-inf"))prob = F.softmax(scores, dim=-1)return torch.matmul(prob, V)

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

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

相关文章

ESLint的简单使用(js,ts,vue)

一、ESLint介绍 1.为什么要用ESLint 统一团队编码规范&#xff08;命名&#xff0c;格式等&#xff09; 统一语法 减少git不必要的提交 减少低级错误 在编译时检查语法&#xff0c;而不是等js引擎运行时才检查 2.eslint用法 可以手动下载配置 可以通过vue脚手架创建项…

探索PDFMiner:Python中的PDF解析利器

文章目录 **探索PDFMiner&#xff1a;Python中的PDF解析利器**1. 背景介绍&#xff1a;为何选择PDFMiner&#xff1f;2. PDFMiner是什么&#xff1f;3. 如何安装PDFMiner&#xff1f;4. 简单库函数使用方法4.1 提取文本4.2 获取页面布局信息4.3 提取表格数据4.4 提取图像 5. 应…

徒手从零搭建一套ELK日志平台

徒手从零搭建一套ELK日志平台 日志分析的概述日志分析的作用主要收集工具集中式日志系统主要特点采集日志分类ELK概述初级版ELK终极版ELK高级版ELKELK收集日志的两种形式 搭建ELK平台Logstash工作原理Logstash核心概念环境准备安装部署docker添加镜像加速器安装部署Elasticsear…

02 —— Webpack 修改入口和出口

概念 | webpack 中文文档 | webpack中文文档 | webpack中文网 修改入口 webpack.config.js &#xff08;放在项目根目录下&#xff09; module.exports {//entry设置入口起点的文件路径entry: ./path/to/my/entry/file.js, }; 修改出口 webpack.config.js const path r…

23种设计模式-模板方法(Template Method)设计模式

文章目录 一.什么是模板方法模式&#xff1f;二.模板方法模式的特点三.模板方法模式的结构四.模板方法模式的应用场景五.模板方法模式的优缺点六.模板方法模式的C实现七.模板方法模式的JAVA实现八.代码解析九.总结 类图&#xff1a; 模板方法设计模式类图 一.什么是模板方法模…

MySQL45讲 第二十五讲 高可用性深度剖析:从主备原理到策略选择

文章目录 MySQL45讲 第二十五讲 高可用性深度剖析&#xff1a;从主备原理到策略选择一、MySQL 主备基础原理&#xff08;一&#xff09;主备关系与数据同步&#xff08;二&#xff09;主备切换流程 二、主备延迟分析&#xff08;一&#xff09;主备延迟的定义与计算&#xff08…

VuePress v2 快速搭建属于自己的个人博客网站

目录 为什么用VuePress&#xff1f; 一、前期准备 Node.js 使用主题快速开发 二、VuePress安装 三、个性化定制 修改配置信息 删除不需要的信息 博客上传 四、部署 使用github快速部署 初始化仓库 本地配置 配置github的ssh密钥 部署 为什么用VuePress&#xff…

PostgreSQL常用时间函数与时间计算提取示例说明

文章目录 常用函数与常量to_timestamp(字符串转时间戳、数字转时间戳)date与to_date(字符串转日期、时间戳转日期)interval(时间计算)基本操作与格式混合运算 to_char(各种时间转字符串)extract(提取时间字段&#xff0c;年月日时分秒&#xff0c;周、季度&#xff0c;第几周、…

SlickGrid点击/双击事件

分析 SlickGrid提供了点击事件方法grid.onClick和grid.onDblClick用于捕获用户对表格列的点击&#xff0c;捕获到点击事件之后&#xff0c;修改表格数据&#xff0c;然后使用grid.updateRow方法将修改后的数据更新到表格中。 展示 代码 创建grid&#xff08;HTML&#xff09;…

【Unity ShaderGraph实现流体效果之Node入门(二)】

Unity ShaderGraph实现流体效果之Node入门&#xff08;二&#xff09; 前言Shader Graph NodeStep NodeMultiply NodeRotate About AxisAddfresnel effectIs Front Face 前言 在&#xff08;一&#xff09;中讨论了一部分在制作流体效果时使用的Node&#xff0c;本章继续将剩余…

集合卡尔曼滤波(Ensemble Kalman Filter),用于二维滤波(模拟平面上的目标跟踪),MATLAB代码

集合卡尔曼滤波&#xff08;Ensemble Kalman Filter&#xff09; 文章目录 引言理论基础卡尔曼滤波集合卡尔曼滤波初始化预测步骤更新步骤卡尔曼增益更新集合 MATLAB 实现运行结果3. 应用领域结论 引言 集合卡尔曼滤波&#xff08;Ensemble Kalman Filter, EnKF&#xff09;是…

解决Docker环境变量的配置的通用方法

我们部署的很多服务都是以Docker容器的形式存在的。 在运行Docker容器前&#xff0c;除了设置网络、数据卷之外&#xff0c;还需要设置各种各样的环境变量。 有时候&#xff0c;由于容器版本的问题&#xff0c;一些文档没有及时更新&#xff0c;可能同时存在多个新旧版本的环…

2446.学习周刊-2024年46周

封面 拍摄于11月17日&#xff0c;身心疲惫的时候&#xff0c;去山里走走看看风景&#xff0c;富氧的环境能缓解身心疲劳。 ✍优秀博文 # 深度解析数仓建模与指标体系构建的底层逻辑 | 金字塔原理在数仓建模分析中的应用基于“理采存管用”的数据中台建设方案业务逻辑不要放入…

自然语言处理:第六十三章 阿里Qwen2 2.5系列

本人项目地址大全&#xff1a;Victor94-king/NLP__ManVictor: CSDN of ManVictor 项目地址: QwenLM/Qwen2.5: Qwen2.5 is the large language model series developed by Qwen team, Alibaba Cloud. 官网地址: 你好&#xff0c;Qwen2 | Qwen & Qwen2.5: 基础模型大派对&a…

六、卷积神经网络(CNN)基础

卷积神经网络&#xff08;CNN&#xff09;基础 前言一、CNN概述二、卷积层2.1 卷积2.2 步幅(Stride)2.3 填充(Padding)2.4 多通道卷积2.5 多卷积计算2.6 特征图大小计算2.7 代码演示 三、池化层3.1 池化层计算3.1.1 最大池化层3.1.2 平均池化层 3.2 填充(Padding)3.3 步幅(Stri…

通过vite+vue3+pinia从0到1搭建一个uniapp应用

最近项目上要做一个app&#xff0c;选择了用uniapp作为开发框架&#xff1b;我大概看了一下uniapp的文档&#xff0c;根据文档从0到1搭了一个uniapp应用供大家参考。 因为本人习惯使用了WebStorm编译器&#xff0c;但是uniapp官方推荐使用HBuilder搭建&#xff0c;如果和我一样…

【Pytorch】torch.nn.functional模块中的非线性激活函数

在使用torch.nn.functional模块时&#xff0c;需要导入包&#xff1a; from torch.nn import functional 以下是常见激活函数的介绍以及对应的代码示例&#xff1a; tanh (双曲正切) 输出范围&#xff1a;(-1, 1) 特点&#xff1a;中心对称&#xff0c;适合处理归一化后的数据…

java-贪心算法

1. 霍夫曼编码&#xff08;Huffman Coding&#xff09; 描述&#xff1a; 霍夫曼编码是一种使用变长编码表对数据进行编码的算法&#xff0c;由David A. Huffman在1952年发明。它是一种贪心算法&#xff0c;用于数据压缩。霍夫曼编码通过构建一个二叉树&#xff08;霍夫曼树&a…

【数据结构】【线性表】【练习】反转链表

申明 该题源自力扣题库19&#xff0c;文章内容&#xff08;代码&#xff0c;图表等&#xff09;均原创&#xff0c;侵删&#xff01; 题目 给你单链表的头指针head以及两个整数left和right&#xff0c;其中left<right&#xff0c;请你反转从位置left到right的链表节点&…

实时数仓:Lambda架构和Kappa架构有什么联系和区别

Kappa 和 Lambda 架构是处理大数据和实时数据流的两种不同设计模式。以下是对这两种架构的概述和比较&#xff1a; Lambda 架构 定义&#xff1a; Lambda 架构的全称是 Lambda Architecture。这个架构旨在处理大规模数据&#xff0c;结合了批处理和流处理的优点&#xff0c;以…