用变压器实现德-英语言翻译【02/8】: 位置编码

news2024/11/25 14:40:19

一、说明

        本文是“用变压器实现德-英语言翻译”系列的第二篇。它从头开始引入位置编码。然后,它解释了 PyTorch 如何实现位置编码。接下来是变压器实现。

二、技术背景

        位置编码用于为序列中的每个标记或单词提供相对位置。阅读句子时,每个单词都依赖于它周围的单词。例如,某些单词在不同的上下文中具有不同的含义,因此模型应该能够理解这些变体以及每个单词所依赖的上下文。一个例子是“树干”。在一个例子中,它可以用来指大象用它的鼻子喝水。在另一种情况下,它可能是指树干被照明击中。

        由于该模型使用长度d_model的嵌入向量来表示每个单词,因此任何位置编码都必须兼容。使用整数似乎很自然,第一个令牌接收 0,第二个令牌接收 1,依此类推。但是,这个数字会迅速增长,并且不容易添加到嵌入矩阵中。相反,为每个位置创建一个位置编码向量,这意味着可以创建一个位置编码矩阵来表示单词可以采取的所有可能位置。

        为了确保每个位置都有唯一的表示,“注意力是你所需要的一切”的作者使用正弦和余弦函数为序列中的每个位置生成一个唯一的向量。虽然这看起来很奇怪,但它有用有几个原因。首先,正弦和余弦的输出在 [-1, 1] 中,归一化。它不会像整数那样增长到无法管理的大小。其次,无需进行额外的培训,因为每个职位都会生成独特的表示形式。

 

        用于生成独特编码的方程看起来令人生畏,但它们只是正弦和余弦的修改版本。嵌入中的最大位置数或向量将由 L 表示:

         这实质上是说,对于每个位置编码向量,对于每两个元素,将偶数元素设置为等于 PE(k,2i),并将奇数元素设置为等于 PE(k, 2i+1)。然后,重复直到向量中有d_model个元素。

        每个编码向量具有与嵌入向量相同的维度(d_model)。这允许对它们求和。表示位置,从 0 开始到 L-1可以设置为的最大数字是 d_model除以 2,因为方程对嵌入中的每个元素交替。n可以设置为任何值,但原始论文建议10,000。在下图中,以下参数用于计算 6 标记序列的位置编码向量:

  • n = 10,000
  • L = 6
  • d_model = 4

         此图显示了最大值 如何设置为 1,正弦和余弦都使用它。k 随嵌入矩阵中的每一行而变化,从 0 开始到 5,这是最大长度 6。每个向量有 d_model = 4 个元素。

三、基本实现

 

        要了解这些独特的位置编码向量如何与嵌入向量一起工作,最好使用嵌入层文章中的示例。

        此实现将直接基于上一篇文章中的实现构建。下面的输出嵌入了三个序列,d_model为 4。

# set the output to 2 decimal places without scientific notation
torch.set_printoptions(precision=2, sci_mode=False)

# tokenize the sequences
tokenized_sequences = [tokenize(seq) for seq in sequences]

# index the sequences 
indexed_sequences = [[stoi[word] for word in seq] for seq in tokenized_sequences]

# convert the sequences to a tensor
tensor_sequences = torch.tensor(indexed_sequences).long()

# vocab size
vocab_size = len(stoi)

# embedding dimensions
d_model = 4

# create the embeddings
lut = nn.Embedding(vocab_size, d_model) # look-up table (lut)

# embed the sequence
embeddings = lut(tensor_sequences)

embeddings
tensor([[[-0.27, -0.82,  0.33,  1.39],
         [ 1.72, -0.63, -1.13,  0.10],
         [-0.23, -0.07, -0.28,  1.17],
         [ 0.61,  1.46,  1.21,  0.84],
         [-2.05,  1.77,  1.51, -0.21],
         [ 0.86, -1.81,  0.55,  0.98]],

        [[ 0.06, -0.34,  2.08, -1.24],
         [ 1.44, -0.64,  0.78, -1.10],
         [ 1.78,  1.22,  1.12, -2.35],
         [-0.48, -0.40,  1.73,  0.54],
         [ 1.28, -0.18,  0.52,  2.10],
         [ 0.34,  0.62, -0.45, -0.64]],

        [[-0.22, -0.66, -1.00, -0.04],
         [-0.23, -0.07, -0.28,  1.17],
         [ 1.44, -0.64,  0.78, -1.10],
         [ 1.78,  1.22,  1.12, -2.35],
         [-0.48, -0.40,  1.73,  0.54],
         [ 0.70, -1.35,  0.15, -1.44]]], grad_fn=<EmbeddingBackward0>)

        下一步是通过位置编码对每个序列中每个单词的位置进行编码。下面的函数遵循上面的定义。唯一值得一提的变化是 L 被标记为max_length。它通常设置为千中的极大值,以确保几乎每个序列都可以正确编码。这确保了相同的位置编码矩阵可以用于不同长度的序列。在添加之前可以将其切成适当的长度。

def gen_pe(max_length, d_model, n):

  # generate an empty matrix for the positional encodings (pe)
  pe = np.zeros(max_length*d_model).reshape(max_length, d_model) 

  # for each position
  for k in np.arange(max_length):

    # for each dimension
    for i in np.arange(d_model//2):

      # calculate the internal value for sin and cos
      theta = k / (n ** ((2*i)/d_model))       

      # even dims: sin   
      pe[k, 2*i] = math.sin(theta) 

      # odd dims: cos               
      pe[k, 2*i+1] = math.cos(theta)

  return pe

# maximum sequence length
max_length = 10
n = 100
encodings = gen_pe(max_length, d_model, n)

        编码的输出包含 10 个位置编码向量。

array([[ 0.    ,  1.    ,  0.    ,  1.    ],
       [ 0.8415,  0.5403,  0.0998,  0.995 ],
       [ 0.9093, -0.4161,  0.1987,  0.9801],
       [ 0.1411, -0.99  ,  0.2955,  0.9553],
       [-0.7568, -0.6536,  0.3894,  0.9211],
       [-0.9589,  0.2837,  0.4794,  0.8776],
       [-0.2794,  0.9602,  0.5646,  0.8253],
       [ 0.657 ,  0.7539,  0.6442,  0.7648],
       [ 0.9894, -0.1455,  0.7174,  0.6967],
       [ 0.4121, -0.9111,  0.7833,  0.6216]])

        如前所述,max_length设置为 10。虽然这超出了要求,但它确保如果另一个序列的长度为 7、8、9 或 10,则可以使用相同的位置编码矩阵。它只需要被切成适当的长度。下面,嵌入的seq_length为 <>,因此可以相应地对编码进行切片。

# select the first six tokens
seq_length = embeddings.shape[1]
encodings[:seq_length]
tensor([[ 0.00,  1.00,  0.00,  1.00],
        [ 0.84,  0.54,  0.10,  1.00],
        [ 0.91, -0.42,  0.20,  0.98],
        [ 0.14, -0.99,  0.30,  0.96],
        [-0.76, -0.65,  0.39,  0.92],
        [-0.96,  0.28,  0.48,  0.88]])

        由于所有三个序列的序列长度相同,因此只需要一个位置编码矩阵,并且可以使用 PyTorch 在所有三个序列上广播。此示例中的嵌入批处理的形状为 (3, 6, 4),位置编码在切片为 (10, 4) 之前形状为 (6, 4)。然后广播该矩阵以创建图像中看到的(3,6,4)编码矩阵。有关广播的更多信息,请阅读广播简单简介。

        这允许添加两个矩阵而不会出现任何问题。

embedded_sequence + encodings[:seq_length] # encodings[:6]

将位置编码添加到嵌入时,输出与部分开头的图像相同。

tensor([[[-0.27,  0.18,  0.33,  2.39],
         [ 2.57, -0.09, -1.03,  1.09],
         [ 0.68, -0.49, -0.08,  2.15],
         [ 0.75,  0.47,  1.50,  1.80],
         [-2.80,  1.12,  1.90,  0.71],
         [-0.10, -1.53,  1.03,  1.86]],

        [[ 0.06,  0.66,  2.08, -0.24],
         [ 2.28, -0.10,  0.88, -0.10],
         [ 2.69,  0.80,  1.32, -1.37],
         [-0.34, -1.39,  2.03,  1.50],
         [ 0.52, -0.83,  0.91,  3.02],
         [-0.62,  0.90,  0.03,  0.23]],

        [[-0.22,  0.34, -1.00,  0.96],
         [ 0.61,  0.47, -0.18,  2.16],
         [ 2.35, -1.06,  0.98, -0.12],
         [ 1.92,  0.23,  1.41, -1.40],
         [-1.24, -1.06,  2.12,  1.46],
         [-0.26, -1.06,  0.63, -0.56]]], grad_fn=<AddBackward0>)

        此输出将传递到模型的下一层,即下一篇文章将介绍的多头注意力。

        但是,由于此基本实现使用嵌套循环,因此效率不高,尤其是在使用较大的 d_model 和 max_length 值时。相反,可以使用更以 PyTorch 为中心的方法。

四、修改 PyTorch 的位置编码公式

 

        图片来源:Chili Math

        为了利用 PyTorch 的功能,需要使用对数规则修改原始方程,特别是除数。

        除数为:

        为了修改除数,通过取其指数将 带入分子中。然后,使用规则 7 将整个方程提高为 e 的指数。然后,使用规则 3 将指数拉出日志。然后简化以获得结果。

        这很重要,因为它可用于一次生成位置编码的所有除数。下面很明显,4 维嵌入只需要两个除数,因为除数每 2i 才会变化一次,其中 是维度。这在每个位置重复:

        由于只有 可以设置为的最大数字d_model除以 2,因此可以计算一次项:

d_model = 4
n = 100

div_term = torch.exp(torch.arange(0, d_model, 2) * -(math.log(n) / d_model))

        这个简短的代码片段可用于生成所需的所有除数。对于此示例,d_model设置为 4,设置为 100。输出是两个除数:

tensor([1.0000, 0.1000])

        从这里,可以利用 PyTorch 的索引功能,用几行代码创建整个位置编码矩阵。下一步是生成从 到 L-1 的每个位置。

max_length = 10

# generate the positions into a column matrix
k = torch.arange(0, max_length).unsqueeze(1)      
tensor([[0],
        [1],
        [2],
        [3],
        [4],
        [5],
        [6],
        [7],
        [8],
        [9]])

        使用位置和除数,可以轻松计算正弦和余弦函数的内部。

        通过将 和 div_term相乘,可以计算每个位置的输入。PyTorch 将自动广播矩阵以允许乘法。请注意,这是 Hadamard 乘积而不是矩阵乘法,因为相应的元素将相互乘法:

 

k*div_term

        此计算的输出可以在上图中看到。剩下要做的就是将输入插入 cos 和 sin 函数,并将它们适当地保存在矩阵中。

        这可以通过创建适当大小的空矩阵来开始:

# generate an empty tensor
pe = torch.zeros(max_length, d_model)
tensor([[0., 0., 0., 0.],
        [0., 0., 0., 0.],
        [0., 0., 0., 0.],
        [0., 0., 0., 0.],
        [0., 0., 0., 0.],
        [0., 0., 0., 0.],
        [0., 0., 0., 0.],
        [0., 0., 0., 0.],
        [0., 0., 0., 0.],
        [0., 0., 0., 0.]])

        现在,偶数列是罪恶的,可以用 pe[:, 0::2] 选择。这告诉 PyTorch 选择每一行和每一列偶数列。对于奇数列也可以这样做,它们是 cos: pe[:, 1::2]。再一次,这告诉 PyTorch 选择每一行和每一奇数列。由于 k*div_term 的结果中存储了所有必要的输入,因此可用于计算每个奇数和偶数列。

# set the odd values (columns 1 and 3)
pe[:, 0::2] = torch.sin(k * div_term)

# set the even values (columns 2 and 4)
pe[:, 1::2] = torch.cos(k * div_term)
     
# add a dimension for broadcasting across sequences: optional       
pe = pe.unsqueeze(0)  
tensor([[[ 0.00,  1.00,  0.00,  1.00],
         [ 0.84,  0.54,  0.10,  1.00],
         [ 0.91, -0.42,  0.20,  0.98],
         [ 0.14, -0.99,  0.30,  0.96],
         [-0.76, -0.65,  0.39,  0.92],
         [-0.96,  0.28,  0.48,  0.88],
         [-0.28,  0.96,  0.56,  0.83],
         [ 0.66,  0.75,  0.64,  0.76],
         [ 0.99, -0.15,  0.72,  0.70],
         [ 0.41, -0.91,  0.78,  0.62]]])

        这些值与使用嵌套的 for 循环获取的值相同。回顾一下,以下是所有代码:

max_length = 10
d_model = 4
n = 100

def gen_pe(max_length, d_model, n):
  # calculate the div_term
  div_term = torch.exp(torch.arange(0, d_model, 2) * -(math.log(n) / d_model))

  # generate the positions into a column matrix
  k = torch.arange(0, max_length).unsqueeze(1)

  # generate an empty tensor
  pe = torch.zeros(max_length, d_model)

  # set the even values
  pe[:, 0::2] = torch.sin(k * div_term)

  # set the odd values
  pe[:, 1::2] = torch.cos(k * div_term)

  # add a dimension       
  pe = pe.unsqueeze(0)        

  # the output has a shape of (1, max_length, d_model)
  return pe                           

gen_pe(max_length, d_model, n)  

虽然它更复杂,但这是 PyTorch 使用的实现,因为它增强了机器学习的性能。

五、变压器中的位置编码

        现在所有的艰苦工作都已经完成,实施很简单。它源自注释转换器和PyTorch。请注意,的默认值为 10,000,默认max_length为 5,000。

        此实现还包含 dropout,它以给定的概率 p 随机清零其输入的某些元素。这有助于正则化并防止神经元共同适应(过度依赖彼此)。输出也按¹⁄₍₁_p₎的系数进行缩放。而不是在本文中深入讨论它。有关详细信息,请参阅有关辍学层的文章。在转向变压器模型的其余部分之前,最好现在就熟悉它,因为它几乎位于其他每一层中。

class PositionalEncoding(nn.Module):
  def __init__(self, d_model: int, dropout: float = 0.1, max_length: int = 5000):
    """
    Args:
      d_model:      dimension of embeddings
      dropout:      randomly zeroes-out some of the input
      max_length:   max sequence length
    """
    # inherit from Module
    super().__init__()     

    # initialize dropout                  
    self.dropout = nn.Dropout(p=dropout)      

    # create tensor of 0s
    pe = torch.zeros(max_length, d_model)    

    # create position column   
    k = torch.arange(0, max_length).unsqueeze(1)  

    # calc divisor for positional encoding 
    div_term = torch.exp(                                 
            torch.arange(0, d_model, 2) * -(math.log(10000.0) / d_model)
    )

    # calc sine on even indices
    pe[:, 0::2] = torch.sin(k * div_term)    

    # calc cosine on odd indices   
    pe[:, 1::2] = torch.cos(k * div_term)  

    # add dimension     
    pe = pe.unsqueeze(0)          

    # buffers are saved in state_dict but not trained by the optimizer                        
    self.register_buffer("pe", pe)                        

  def forward(self, x: Tensor):
    """
    Args:
      x:        embeddings (batch_size, seq_length, d_model)
    
    Returns:
                embeddings + positional encodings (batch_size, seq_length, d_model)
    """
    # add positional encoding to the embeddings
    x = x + self.pe[:, : x.size(1)].requires_grad_(False) 

    # perform dropout
    return self.dropout(x)

        前向传递

        要执行正向传递,可以使用与之前相同的嵌入式序列。

embeddings
tensor([[[-0.27, -0.82,  0.33,  1.39],
         [ 1.72, -0.63, -1.13,  0.10],
         [-0.23, -0.07, -0.28,  1.17],
         [ 0.61,  1.46,  1.21,  0.84],
         [-2.05,  1.77,  1.51, -0.21],
         [ 0.86, -1.81,  0.55,  0.98]],

        [[ 0.06, -0.34,  2.08, -1.24],
         [ 1.44, -0.64,  0.78, -1.10],
         [ 1.78,  1.22,  1.12, -2.35],
         [-0.48, -0.40,  1.73,  0.54],
         [ 1.28, -0.18,  0.52,  2.10],
         [ 0.34,  0.62, -0.45, -0.64]],

        [[-0.22, -0.66, -1.00, -0.04],
         [-0.23, -0.07, -0.28,  1.17],
         [ 1.44, -0.64,  0.78, -1.10],
         [ 1.78,  1.22,  1.12, -2.35],
         [-0.48, -0.40,  1.73,  0.54],
         [ 0.70, -1.35,  0.15, -1.44]]], grad_fn=<EmbeddingBackward0>)

        嵌入序列后,可以创建位置编码矩阵。dropout 设置为 0.0 以便轻松查看嵌入和位置编码之间的相加。这些值与从头开始实现不同,因为 的默认值为 10,000 而不是 100。

d_model = 4
max_length = 10
dropout = 0.0

# create the positional encoding matrix
pe = PositionalEncoding(d_model, dropout, max_length)

# preview the values
pe.state_dict()
OrderedDict([('pe',
              tensor([[[ 0.00,  1.00,  0.00,  1.00],
                       [ 0.84,  0.54,  0.01,  1.00],
                       [ 0.91, -0.42,  0.02,  1.00],
                       [ 0.14, -0.99,  0.03,  1.00],
                       [-0.76, -0.65,  0.04,  1.00],
                       [-0.96,  0.28,  0.05,  1.00],
                       [-0.28,  0.96,  0.06,  1.00],
                       [ 0.66,  0.75,  0.07,  1.00],
                       [ 0.99, -0.15,  0.08,  1.00],
                       [ 0.41, -0.91,  0.09,  1.00]]]))])

        在添加它们之前,序列的形状为 (batch_size、seq_length、d_model),即 (3, 6, 4)。位置编码在切片和广播后具有相同的大小,因此前向传递的输出大小为 (batch_size、seq_length、d_model),仍然是 (3, 6, 4)。这表示嵌入在 3 维空间中的 6 个序列,每个序列 4 个令牌,带有位置编码以指示它们在序列中的位置。

pe(embeddings)
 tensor([[[-0.27,  0.18,  0.33,  2.39],
         [ 2.57, -0.09, -1.12,  1.10],
         [ 0.68, -0.49, -0.26,  2.17],
         [ 0.75,  0.47,  1.24,  1.84],
         [-2.80,  1.12,  1.55,  0.79],
         [-0.10, -1.53,  0.60,  1.98]],

        [[ 0.06,  0.66,  2.08, -0.24],
         [ 2.28, -0.10,  0.79, -0.10],
         [ 2.69,  0.80,  1.14, -1.35],
         [-0.34, -1.39,  1.76,  1.54],
         [ 0.52, -0.83,  0.56,  3.10],
         [-0.62,  0.90, -0.40,  0.35]],

        [[-0.22,  0.34, -1.00,  0.96],
         [ 0.61,  0.47, -0.27,  2.17],
         [ 2.35, -1.06,  0.80, -0.10],
         [ 1.92,  0.23,  1.15, -1.35],
         [-1.24, -1.06,  1.77,  1.54],
         [-0.26, -1.06,  0.20, -0.44]]], grad_fn=<AddBackward0>)

本系列的下一篇文章是多头注意力层。

请不要忘记点赞和关注更多!:)

引用

  1. 辍学层
  2. 位置编码概述
  3. PyTorch 的位置编码实现
  4. 带注释的变压器
  5. 变压器的位置编码

附录

可视化位置编码的唯一性

在得出结论之前,验证位置编码的唯一性并了解它们如何与更大的序列一起工作将是有益的。

使用 matplotlib,可以轻松地将向量相互比较。

def visualize_pe(max_length, d_model, n):
  plt.imshow(gen_pe(max_length, d_model, n), aspect="auto")
  plt.title("Positional Encoding")
  plt.xlabel("Encoding Dimension")
  plt.ylabel("Position Index")

  # set the tick marks for the axes
  if d_model < 10:
    plt.xticks(torch.arange(0, d_model))
  if max_length < 20:
    plt.yticks(torch.arange(max_length-1, -1, -1))
    
  plt.colorbar()
  plt.show()

# plot the encodings
max_length = 10
d_model = 4
n = 100

visualize_pe(max_length, d_model, n)

 

        每行表示一个位置编码向量,每列表示与嵌入组合时将添加到的相应维度。

        这也可以在较大的值 nd_model 和 max_length 中看到:

# plot the encodings
max_length = 1000
d_model = 512
n = 10000

visualize_pe(max_length, d_model, n)

参考资源:Positional Encoding. This article is the second in The… | by Hunter Phillips | Medium

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

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

相关文章

日本核污染水排海,有必要囤盐吗?

据央视新闻24日报道&#xff0c;当地时间8月24日13时&#xff0c;日本福岛第一核电站启动污水排海。消息一出&#xff0c;全球哗然。虽然事情已经过去了几天&#xff0c;但是&#xff0c;随着这一举动&#xff0c;大家就乱了阵脚&#xff0c;恐惧者有之&#xff0c;辱骂者有之&…

Nginx从入门到精通(超级详细)

文章目录 一、什么是Nginx1、正向代理2、反向代理3、负载均衡4、动静分离 二、centos7环境安装Nginx1、安装依赖2、下载安装包3、安装4、启动5、停止 三、Nginx核心基础知识1、nginx核心目录2、常用命令3、默认配置文件讲解4、Nginx虚拟主机-搭建前端静态服务器5、使用nignx搭建…

超声波俱乐部分享:AI冷静期,创业者们应该做什么?

8月26日&#xff0c;2023年第十一期超声波俱乐部内部分享会在北京望京举行。本期的主题是&#xff1a;AI冷静期&#xff0c;创业者们应该做什么&#xff1f; 到场的嘉宾有&#xff1a; 超声波创始人杨子超&#xff0c;超声波联合创始人、和牛商业创始人刘思雨&#xff0c;中国…

学习c++的第6天

#include <iostream> using namespace std; class Animal { public: virtual void perform()0; virtual ~Animal() { cout<<"Animal的析构函数"<<endl; } }; class Lion :public Animal { public : void perform() { cout<<"狮子…

41、springboot 整合 FreeMarker 模版技术

springboot 整合 FreeMarker 模版技术 ★ 整合FreeMarker的自动配置&#xff1a; FreeMarkerAutoConfiguration&#xff1a;负责整合Spring容器和获取FreeMarkerProperties加载的配置信息。FreeMarkerServletWebConfiguration/FreeMarkerReactiveWebConfiguration&#xff1a…

C++ 多级继承

所谓多级继承就是代代相传&#xff0c;几代人&#xff0c;后代继承祖辈的数据和方法。但是有三种不同的继承方式而已。 构造顺序&#xff0c;即基类先构造&#xff0c;其次代代相传&#xff0c;析构顺序则是从子代先析构&#xff0c;最后析构祖先。 构造:从祖宗开始&#xff0…

马上金九银十了,给大家一点面试方面的建议

哈喽大家好啊&#xff0c;我是Hydra。 好久不见&#xff0c;甚是想念。这段时间没有更新什么文章&#xff0c;其实是因为我跳了一波槽&#xff0c;出去面了一圈后&#xff0c;也顺利拿了不少架构岗位的offer。 正好马上要金九银十了&#xff0c;相信有不少小伙伴们估计也有跳…

1.2 数据库系统结构

思维导图&#xff1a; 学习目标&#xff1a; 学习数据库系统结构是一个结构性和系统性的过程。如果是我&#xff0c;我会采用以下策略&#xff1a; 1. **确定目标和动机**&#xff1a; - 明确为什么要学习数据库系统。是为了应对工作的需求、为了研究还是出于兴趣&#xf…

多用户商城系统常见的安全性和数据保护措施有哪些?

电子商务的迅速发展&#xff0c;越来越多的企业选择搭建多用户商城系统来扩展业务。然而&#xff0c;随之而来的是对数据安全和保护的日益关注。在选择多用户商城系统时&#xff0c;我们需要考虑一系列的安全性和数据保护措施&#xff0c;以确保商城系统的稳定性和用户数据的完…

【数据结构】带头双向循环链表---C语言版(单链表我们分手吧,不要再找我玩了!!!)

文章目录 &#x1f438;一、前言&#x1f438;二、链表的分类&#x1f344;1. 单向或者双向链表&#x1f344;2. 带头或者不带头链表&#x1f344;3. 循环或者非循环&#x1f344;4. 最常用链表 &#x1f438;三、带头双向循环链表详解&#x1f34e;创建带头双向循环链表⭕接口…

Power Pivot 实现数据建模

一、简介 Excel中的透视表适合小规模数据&#xff1b;如果想在稍微大一些的数据中进行高性能透视表分析&#xff0c;就要使用Power Pivot&#xff1b;再大一些数据&#xff0c;可能就需要大数据分析服务来进行分析。 Power Pivot&#xff0c;可以让没有技术背景的企业业务人员…

【前端demo】将二进制数转换为十进制数 原生实现

https://github.com/florinpop17/app-ideas 总结 文章目录 效果JavaScript实现进制转换原生代码遇到的问题 效果 二进制转换为十进制若输入为空或不是二进制&#xff0c;提示清空 JavaScript实现进制转换 parseInt parseInt(111,2)手动实现 bin是输入的字符串。 functio…

设备分配与回收、缓冲区管理

设备分配与回收 是什么&#xff1a;设备分配的任务是按照一定的策略&#xff0c;为提出I/O请求的进程分配合适的设备&#xff0c;确保CPU与I/O设备之间能正常通信&#xff0c;还应分配相应的控制器和通道。设备分配管理中的数据结构 设备控制表DCT&#xff1a;每个设备对应一张…

Spring Bean 生命周期顺序验证

看到一篇写的很好的 Spring Bean 生命周期的博客&#xff1a;一文读懂 Spring Bean 的生命周期&#xff0c;在此写个简单的 Bean 进行验证。 1. 创建Springboot项目 基于 springboot 的2.1.8.RELEASE 创建一个简单项目&#xff0c;只添加 spring-aop 包以引入spring依赖。 &…

关于流控RTS/CTS ,DTR/DSR的说明

最近在调试代码过程中遇到一些流控的问题&#xff0c;关于相关概念做了一些总结。 以9针脚232串口为例子&#xff1a; DCD:接受信号检出&#xff0c;也叫数据载波检出线&#xff08;Data Carrier detection&#xff0c;DCD&#xff09;&#xff0c;主要用于表示Modem已经接通通…

六、事务-2.事务操作

解决问题&#xff1a;要把转账的三步操作控制在一个事务之内 当前每一个SQL语句就是一个事务&#xff0c;默认MySQL的事务是自动提交的&#xff0c;也就是说&#xff0c;当执行一条DML语句&#xff0c;MySQL会立即隐式的提交事务。 一、方式一&#xff1a;修改当前窗口事务提…

全球化时代的文化代言人:海外网红如何影响消费行为?

随着全球化的推进&#xff0c;互联网和社交媒体的普及&#xff0c;海外网红在当今社会中扮演着越来越重要的角色。这些在网络平台上拥有大量粉丝的人物不仅仅是娱乐的代表&#xff0c;更成为了文化的代言人&#xff0c;影响着人们的消费行为。 从美妆产品到时尚潮流&#xff0…

我们到底在用Hibernate还是Spring Data JPA还是JPA???

Hibernate 和 JPA 和Spring Data JPA JPA JPA的全称是Java Persistence API&#xff0c; 即Java 持久化API&#xff0c;是SUN公司推出的一套基于ORM的规范 Hibernate Hibernate是一个JPA规范的具体实现&#xff0c;是ORM类型的框架&#xff0c;对象映射模型。 Hibernate 可以自…

ModuleNotFoundError: No module named ‘google‘

这个错误表明你的代码在执行过程中遇到了一个模块导入问题。根据报错信息&#xff0c;问题似乎出现在导入google.protobuf模块时&#xff0c;提示找不到google模块。 解决这个问题的一种可能方法是确保你的环境中安装了protobuf库&#xff0c;因为google.protobuf实际上是prot…

持续性能优化:确保应用保持高性能

在当今数字化时代&#xff0c;应用程序的性能已经成为用户体验和业务成功的关键因素之一。无论是Web应用、移动应用还是企业级软件&#xff0c;用户对于速度和响应性的要求越来越高。因此&#xff0c;持续性能优化已经成为保证应用在竞争激烈的市场中脱颖而出的重要策略。 什么…