AI小项目4-用Pytorch从头实现Transformer(详细注解)

目录

  • 一、前期准备工作
    • 学习如何读AI论文
    • 读Transformer原始论文
    • 用Pytorch从头实现Transformer
  • 二、我的完整代码实现
    • 1.导入库
    • 2.基本组件
      • 创建词嵌入
      • 位置嵌入
      • 自注意力
    • 3.编码器
    • 4.解码器
    • 5.完整架构
    • 6.简单测试一下代码
      • 创建模型和准备简单的训练数据
      • 训练一次(前向传播)
      • 推理
  • 三、bug修复
    • 位置编码部分,发现参考代码的两处bug
  • 四、背景知识补充
    • 残差连接(Residual Connection)
    • 层归一化(Layer Normalization)
      • 1. **归一化的对象**
      • 2. **适用场景**
      • 总结
      • 图解
    • 自回归(Autoregression,AR)
    • 注意力机制(Attention Mechanism)
    • Self-attention(自注意力机制)
      • Self-attention的基本步骤
      • Self-attention的基本步骤图
      • Self-attention 的优势
      • Self-attention在Transformer中的应用
    • 点积(Dot-Product)
    • 全连接层(Fully Connected Layer,简称 FC 层)
    • 多头注意力(Multi-Head Attention)
      • 多头注意力的基本步骤
      • 图解
      • 多头注意力的优势
      • 多头注意力的公式总结
      • 多头注意力的实际应用
    • Position-wise Feed-Forward Networks(位置前馈网络,简称 FFN)
      • 为什么 Transformer 需要 Position-wise Feed-Forward Networks?
      • Position-wise Feed-Forward Networks 的工作原理
    • L2范数(L2 norm)
      • L2范数的作用和应用
      • L2范数与其他范数的区别
      • 总结
    • Positional Encoding(位置编码)
      • 为什么需要 Positional Encoding?
      • Positional Encoding 的原理
      • 解释
      • 加法操作
      • Positional Encoding 的性质
      • 总结

一、前期准备工作

学习如何读AI论文

看了B站李沐老师的下面几个视频

如何读论文【论文精读·1】
9年后重读深度学习奠基作之一:AlexNet【论文精读·2】
AlexNet论文逐段精读【论文精读】

读Transformer原始论文

我是看B站李沐老师的Transformer论文精读视频,结合原作论文和chatgpt来学习的。chatgpt用于查一些背景知识。

原作论文作为参考,自己读当然OK,但是如果有大神帮你解读,对于小白来说,站在巨人的肩膀上效率可能更高,比如大神会提到经典论文中哪些部分是现在还常用的,哪些部分已经过时或者不重要。

李沐老师的视频:Transformer论文逐段精读【论文精读】
原作论文:Attention Is All You Need

用Pytorch从头实现Transformer

去kaggle或者github上找一份不错的范例代码来参考学习

我参考的代码kaggle地址:Transformer from scratch using pytorch


二、我的完整代码实现

1.kaggle上有我的完整代码,这里贴一份是为了方便那些不会科学上网打不开kaggle的读者。
2.代码有不懂的地方,可以看下背景知识补充部分

我的kaggle代码地址

先看一下论文中完整的transformer架构图:
在这里插入图片描述

1.导入库

import torch.nn as nn
import torch
import torch.nn.functional as F
import math,copy,re
import warnings
import pandas as pd
import numpy as np
import seaborn as sns
import torchtext
import matplotlib.pyplot as plt
warnings.simplefilter("ignore")
print(torch.__version__)

2.基本组件

创建词嵌入

  • 这个 Embedding 类用于将词汇索引转换为相应的嵌入向量,以便输入到神经网络中。
  • nn.Embedding 层通过查表的方式,将每个词汇的索引映射到一个固定维度的嵌入向量上。
class Embedding(nn.Module):def __init__(self, vocab_size, embed_dim):"""Args:vocab_size: 词汇表大小embed_dim: 嵌入维度"""super(Embedding, self).__init__()self.embed = nn.Embedding(vocab_size, embed_dim)def forward(self, x):"""Args:x: 输入向量,通常是一个包含词汇索引的张量(整数表示词汇在词汇表中的位置)out: 嵌入向量"""out = self.embed(x)return out

位置嵌入

这个 PositionalEmbedding 模块通过对每个位置进行正弦和余弦编码,为每个输入序列的嵌入向量提供了位置信息,使模型能够感知序列中词汇的顺序。

class PositionalEmbedding(nn.Module):def __init__(self,max_seq_len,embed_model_dim):"""Args:max_seq_len: 输入序列的最大长度embed_model_dim: 嵌入的维度"""super(PositionalEmbedding, self).__init__()self.embed_dim = embed_model_dim# 生成位置嵌入矩阵# 为每个位置计算对应的正弦和余弦编码pe = torch.zeros(max_seq_len,self.embed_dim)for pos in range(max_seq_len):for i in range(0,self.embed_dim,2):# i = 0,2,4,6 ...pe[pos, i] = np.sin(pos/(10000**(i/self.embed_dim)))if i+1 < self.embed_dim:pe[pos, i+1] = np.cos(pos/(10000**(i/self.embed_dim)))# 扩展维度以适应批量输入# 使用unsqueeze(0)在第一个维度上增加一个维度,使pe的形状变为(1, max_seq_len, embed_dim)。pe = pe.unsqueeze(0)# 将 pe 注册为 buffer,防止它在训练过程中被更新# 缓冲区的作用:在模型保存和加载时,缓冲区会一同保存,但不会作为模型参数参与训练。self.register_buffer('pe', pe)def forward(self, x):"""Args:x: 输入的词嵌入张量,形状通常为(batch_size, seq_len, embed_dim)Returns:x: 输出"""# 使输入嵌入向量的值相对较大x = x * math.sqrt(self.embed_dim)# 获取输入序列的长度seq_len = x.size(1)# 提取对应长度的位置嵌入,将位置嵌入与词嵌入相加,融合位置信息x = x + torch.autograd.Variable(self.pe[:, :seq_len, :], requires_grad=False)return x

自注意力

这个 MultiHeadAttention 类实现了多头注意力机制(Multi-Head Attention),是 Transformer 中的一个重要组件。多头注意力机制允许模型通过多个注意力头(heads)从不同的子空间中关注不同的部分。

论文中多头注意力机制的图

在这里插入图片描述

class MultiHeadAttention(nn.Module):def __init__(self, embed_dim=512, n_heads=8):"""Args:embed_dim: 嵌入向量的维度n_heads: 注意力头的数量"""super(MultiHeadAttention, self).__init__()self.embed_dim = embed_dim  # 512维嵌入向量self.n_heads = n_heads  # 8个注意力头self.single_head_dim = int(self.embed_dim / self.n_heads)  # 每个注意力头的维度 = 512 / 8 = 64# 定义线性变换矩阵,用于生成 Query, Key 和 Value 矩阵self.query_matrix = nn.Linear(self.single_head_dim, self.single_head_dim, bias=False)self.key_matrix = nn.Linear(self.single_head_dim, self.single_head_dim, bias=False)self.value_matrix = nn.Linear(self.single_head_dim, self.single_head_dim, bias=False)# 输出线性层,作用是将多头注意力的输出拼接并变换回原始嵌入维度self.out = nn.Linear(self.n_heads * self.single_head_dim, self.embed_dim)def forward(self, key, query, value, mask=None):"""Args:key: Key 向量query: Query 向量value: Value 向量mask: 用于屏蔽不需要计算注意力的部分Returns:output: 多头注意力机制的输出"""batch_size = key.size(0)seq_length = key.size(1)seq_length_query = query.size(1)# 重塑 key, query 和 value 的维度,使其适应多头注意力的格式# key、query、value 的输入维度为 (batch_size, seq_length, embed_dim),例如 (32, 10, 512) 表示 32 个序列,每个序列长度为 10,每个词的嵌入维度为 512。# 使用view()而不是reshape()的理由:view() 不会创建新张量,只是修改现有张量的元数据(假设内存布局是连续的),因此速度快,内存开销低。key = key.view(batch_size, seq_length, self.n_heads, self.single_head_dim)  # (32, 10, 8, 64)query = query.view(batch_size, seq_length_query, self.n_heads, self.single_head_dim)value = value.view(batch_size, seq_length, self.n_heads, self.single_head_dim)# 线性变换得到 Q, K, V 矩阵k = self.key_matrix(key)  # (32, 10, 8, 64)q = self.query_matrix(query)  # (32, 10, 8, 64)v = self.value_matrix(value)  # (32, 10, 8, 64)# 交换维度,适应矩阵乘法的形状q = q.transpose(1, 2)  # (32, 8, 10, 64)k = k.transpose(1, 2)  # (32, 8, 10, 64)v = v.transpose(1, 2)  # (32, 8, 10, 64)# 计算注意力分数k_adjusted = k.transpose(-1, -2)  # 调整 Key 矩阵以便于与 Query 相乘 (32, 8, 64, 10)product = torch.matmul(q, k_adjusted)  # 矩阵乘法得到注意力分数 (32, 8, 10, 10)# 如果提供了 mask,则屏蔽某些位置的注意力分数if mask is not None:product = product.masked_fill(mask == 0, float("-1e20"))# 对 Key 的维度进行缩放,缓解梯度消失问题product = product / math.sqrt(self.single_head_dim)  # 缩放因子 √64# 计算注意力权重scores = F.softmax(product, dim=-1)  # (32, 8, 10, 10)# 将注意力权重与 Value 相乘,得到加权后的输出scores = torch.matmul(scores, v)  # (32, 8, 10, 64)# 将多头的输出拼接起来concat = scores.transpose(1, 2).contiguous().view(batch_size, seq_length_query, self.single_head_dim * self.n_heads)  # (32, 10, 512)# 通过线性层输出output = self.out(concat)  # (32, 10, 512)return output

3.编码器

论文中编码器的图

在这里插入图片描述

TransformerBlock 是 Transformer 编码器的基本单元,它由多头注意力机制、残差连接、归一化层、前馈神经网络和 dropout 组成。

class TransformerBlock(nn.Module):def __init__(self, embed_dim, expansion_factor=4, n_heads=8):super(TransformerBlock, self).__init__()"""Args:embed_dim: 嵌入向量的维度expansion_factor: 前馈网络的扩展因子,用于扩展中间层的维度n_heads: 注意力头的数量"""self.attention = MultiHeadAttention(embed_dim, n_heads)  # 多头注意力机制self.norm1 = nn.LayerNorm(embed_dim)  # 第一个归一化层(LayerNorm)self.norm2 = nn.LayerNorm(embed_dim)  # 第二个归一化层self.feed_forward = nn.Sequential(  # 前馈神经网络部分nn.Linear(embed_dim, expansion_factor * embed_dim),  # 线性层,维度从 embed_dim 扩展到 expansion_factor * embed_dimnn.ReLU(),  # 激活函数,引入非线性nn.Linear(expansion_factor * embed_dim, embed_dim)  # 再次将维度缩小到原始的 embed_dim)self.dropout1 = nn.Dropout(0.2)  # 第一个 dropout,用于防止过拟合self.dropout2 = nn.Dropout(0.2)  # 第二个 dropoutdef forward(self, key, query, value):"""Args:key: Key 向量query: Query 向量value: Value 向量Returns:norm2_out: 经过 Transformer Block 的输出"""attention_out = self.attention(key, query, value)  # 计算多头注意力输出attention_residual_out = attention_out + value  # 残差连接,添加 Value 向量norm1_out = self.dropout1(self.norm1(attention_residual_out))  # 归一化 + dropoutfeed_fwd_out = self.feed_forward(norm1_out)  # 前馈网络计算feed_fwd_residual_out = feed_fwd_out + norm1_out  # 残差连接norm2_out = self.dropout2(self.norm2(feed_fwd_residual_out))  # 归一化 + dropoutreturn norm2_out

TransformerEncoder 类由多个 TransformerBlock 组成,它负责对输入序列进行编码。输入经过词嵌入层和位置编码层后,进入多个 TransformerBlock 层进行处理。

class TransformerEncoder(nn.Module):"""Transformer 编码器Args:seq_len : 输入序列的长度vocab_size: 词汇表大小embed_dim: 嵌入向量的维度num_layers: 编码器的层数expansion_factor: 前馈网络中的扩展因子n_heads: 多头注意力机制中的头数Returns:out: 编码器的输出"""def __init__(self, seq_len, vocab_size, embed_dim, num_layers=2, expansion_factor=4, n_heads=8):super(TransformerEncoder, self).__init__()self.embedding_layer = Embedding(vocab_size, embed_dim)  # 词嵌入层self.positional_encoder = PositionalEmbedding(seq_len, embed_dim)  # 位置编码# 多层 Transformer Blockself.layers = nn.ModuleList([TransformerBlock(embed_dim, expansion_factor, n_heads) for i in range(num_layers)])def forward(self, x):embed_out = self.embedding_layer(x)  # 词嵌入out = self.positional_encoder(embed_out)  # 加上位置编码for layer in self.layers:out = layer(out, out, out)  # 通过多个 TransformerBlockreturn out  # 输出维度为 (batch_size, seq_len, embed_dim)

4.解码器

论文中解码器的图

在这里插入图片描述
这个 DecoderBlock 类是 Transformer 解码器(Decoder)中的一个基本模块,主要用于处理输入的解码序列,同时结合编码器(Encoder)输出的信息。它包含了多头自注意力机制、前馈神经网络和残差连接等部分。

class DecoderBlock(nn.Module):def __init__(self, embed_dim, expansion_factor=4, n_heads=8):super(DecoderBlock, self).__init__()"""Args:embed_dim: 嵌入向量的维度expansion_factor: 前馈网络中的扩展因子n_heads: 多头注意力头的数量"""self.attention = MultiHeadAttention(embed_dim, n_heads=n_heads)  # 解码器中的自注意力机制self.norm = nn.LayerNorm(embed_dim)  # 归一化层,用于稳定网络训练self.dropout = nn.Dropout(0.2)  # dropout,用于防止过拟合self.transformer_block = TransformerBlock(embed_dim, expansion_factor, n_heads)  # 解码器中的 transformer 模块def forward(self, key, query, x, mask):"""Args:key: Key 向量,通常来自编码器的输出query: Query 向量,来自解码序列x: 输入解码序列mask: 遮掩掩码,用于屏蔽未来信息Returns:out: 解码器块的输出"""# 对解码器输入进行自注意力计算,传入 mask 防止查看未来信息attention = self.attention(x, x, x, mask=mask)  # 输出大小为 (batch_size, seq_len, embed_dim)# 残差连接,并通过归一化和 dropoutvalue = self.dropout(self.norm(attention + x))# 将处理后的 `value` 传入 TransformerBlock,与来自编码器的 `key` 和 `query` 进行进一步处理out = self.transformer_block(key, query, value)return out

这个 TransformerDecoder 类是 Transformer 解码器的完整实现,它由词嵌入层、位置编码层、多个解码块(DecoderBlock),以及最终的全连接层组成。解码器的主要作用是生成输出序列,例如机器翻译中的目标语言序列。

class TransformerDecoder(nn.Module):# 初始化方法定义了解码器的主要组件,包括嵌入层、位置编码层、多个 DecoderBlock 层和一个全连接层。def __init__(self, target_vocab_size, embed_dim, seq_len, num_layers=2, expansion_factor=4, n_heads=8):super(TransformerDecoder, self).__init__()"""  Args:target_vocab_size: 目标词汇表的大小embed_dim: 嵌入向量的维度seq_len : 输入序列的长度num_layers: 解码器层的数量expansion_factor: 前馈网络中的扩展因子n_heads: 多头注意力头的数量"""# 词嵌入层self.word_embedding = nn.Embedding(target_vocab_size, embed_dim)# 位置编码层self.position_embedding = PositionalEmbedding(seq_len, embed_dim)# 多个解码层(DecoderBlock)self.layers = nn.ModuleList([DecoderBlock(embed_dim, expansion_factor=expansion_factor, n_heads=n_heads) for _ in range(num_layers)])# 输出层,全连接层将嵌入维度转换为词汇表大小self.fc_out = nn.Linear(embed_dim, target_vocab_size)# dropout 用于防止过拟合self.dropout = nn.Dropout(0.2)# 前向传播过程中,输入序列首先经过嵌入层和位置编码层,然后通过多个 DecoderBlock 进行处理,最后通过全连接层输出每个位置上词汇的概率分布。def forward(self, x, enc_out, mask):"""Args:x: 来自目标序列的输入向量(目标词汇的嵌入)enc_out: 来自编码器的输出mask: 自注意力机制的掩码,用于防止模型看到未来信息Returns:out: 解码器的输出向量"""# 词嵌入 + 位置编码x = self.word_embedding(x)  # 词嵌入 (batch_size, seq_len, embed_dim) -> (32, 10, 512)x = self.position_embedding(x)  # 位置编码 (32, 10, 512)x = self.dropout(x)  # 加入 dropout,防止过拟合# 多层解码器块for layer in self.layers:x = layer(enc_out, x, enc_out, mask)  # 在每层解码器中,将编码器的输出 `enc_out` 和解码器的 `x` 进行结合# 全连接层,生成词汇表大小的输出概率分布out = F.softmax(self.fc_out(x), dim=-1)  # (batch_size, seq_len, target_vocab_size)return out

5.完整架构

最后,我们将组装所有子模块并创建整个 Transformer 架构。

这个 Transformer 类实现了一个完整的 Transformer 模型,它由编码器TransformerEncoder)和解码器TransformerDecoder)组成,用于处理序列到序列的任务,比如机器翻译。模型的前向传播和推理过程都得到了实现。

class Transformer(nn.Module):# 在初始化方法中,定义了编码器、解码器以及它们的超参数,如嵌入维度、词汇表大小、序列长度等。def __init__(self, embed_dim, src_vocab_size, target_vocab_size, seq_length, num_layers=2, expansion_factor=4, n_heads=8):super(Transformer, self).__init__()"""  Args:embed_dim: 嵌入向量的维度src_vocab_size: 源语言的词汇表大小target_vocab_size: 目标语言的词汇表大小seq_length: 输入序列的长度num_layers: 编码器和解码器的层数expansion_factor: 前馈网络中的扩展因子n_heads: 多头注意力头的数量"""self.target_vocab_size = target_vocab_size# 初始化 Transformer 编码器self.encoder = TransformerEncoder(seq_length, src_vocab_size, embed_dim, num_layers=num_layers, expansion_factor=expansion_factor, n_heads=n_heads)# 初始化 Transformer 解码器self.decoder = TransformerDecoder(target_vocab_size, embed_dim, seq_length, num_layers=num_layers, expansion_factor=expansion_factor, n_heads=n_heads)# 这是一个用于生成目标序列掩码(`mask`)的函数。解码器在生成序列时不能看到未来的信息,因此需要掩码来屏蔽未来的词。def make_trg_mask(self, trg):"""Args:trg: 目标序列Returns:trg_mask: 目标序列的掩码"""batch_size, trg_len = trg.shape# 返回一个下三角矩阵,其中上三角部分被屏蔽trg_mask = torch.tril(torch.ones((trg_len, trg_len))).expand(batch_size, 1, trg_len, trg_len)return trg_mask# `decode` 函数用于推理过程中的解码,它逐步生成序列中的下一个词def decode(self, src, trg):"""推理过程中逐步生成序列Args:src: 编码器的输入trg: 解码器的输入Returns:out_labels: 生成的序列标签"""trg_mask = self.make_trg_mask(trg)  # 生成解码器掩码enc_out = self.encoder(src)  # 通过编码器处理源序列out_labels = []  # 保存生成的输出序列batch_size, seq_len = src.shape[0], src.shape[1]out = trg  # 目标序列的初始输入for i in range(seq_len):# 逐步通过解码器out = self.decoder(out, enc_out, trg_mask)  # (batch_size, seq_len, vocab_size)# 取出最后一个时间步的输出out = out[:, -1, :]  # (batch_size, vocab_size)# 获取输出的最大概率词汇索引out = out.argmax(-1)  # (batch_size,)# 保存当前步的预测结果out_labels.append(out.item())# 将预测出的词添加到输入序列中,继续下一个时间步的预测out = torch.unsqueeze(out, axis=0)return out_labels  # 返回生成的序列# `forward` 函数用于训练阶段,处理完整的源序列和目标序列。def forward(self, src, trg):"""Args:src: 编码器的输入trg: 解码器的输入Returns:outputs: 解码器的输出,包含每个词汇的概率分布"""trg_mask = self.make_trg_mask(trg)  # 生成目标序列掩码enc_out = self.encoder(src)  # 通过编码器处理源序列outputs = self.decoder(trg, enc_out, trg_mask)  # 通过解码器处理目标序列return outputs  # 返回目标词汇的概率分布

6.简单测试一下代码

创建模型和准备简单的训练数据

创建了一个 Transformer 模型,并准备了源序列 (src) 和目标序列 (target) 作为输入

# 输入的参数
src_vocab_size = 11
target_vocab_size = 11
num_layers = 6
seq_length= 12# 训练数据
# 模拟的源序列和目标序列,包含sos和eos tokens
# `src` 和 `target` 序列中都包括特殊的开始符(`sos`,用 0 表示)和结束符(`eos`,用 1 表示)。
src = torch.tensor([[0, 2, 5, 6, 4, 3, 9, 5, 2, 9, 10, 1], [0, 2, 8, 7, 3, 4, 5, 6, 7, 2, 10, 1]])
target = torch.tensor([[0, 1, 7, 4, 3, 5, 9, 2, 8, 10, 9, 1], [0, 1, 5, 6, 2, 4, 7, 6, 2, 8, 10, 1]])# 打印输入的形状
print(src.shape, target.shape)# 创建Transformer模型
model = Transformer(embed_dim=512, src_vocab_size=src_vocab_size, target_vocab_size=target_vocab_size, seq_length=seq_length,num_layers=num_layers, expansion_factor=4, n_heads=8)# 打印模型以确认其结构
print(model)

训练一次(前向传播)

通过源序列 src 和目标序列 target 进行一次前向传播的训练。
这个 out 是解码器对每个时间步的词汇预测。它的形状通常是 (batch_size, seq_len, target_vocab_size)。其中每个时间步都会生成一个词汇表大小的概率分布。

out = model(src, target)
print(out.shape)
print(out)

输出为

torch.Size([2, 12, 11])
out是一个3维的概率分布,比较长就不贴了,就像下面这样:
tensor([[[0.0306, 0.0768, 0.1866, 0.0364, 0.0384, 0.0591, 0.0850, 0.1164,0.1425, 0.1635, 0.0647],…]]]

推理

在推理过程中,模型会通过编码器处理 src,然后在每个时间步上通过解码器逐步生成下一个词。out 是模型生成的目标序列,包含生成的词汇索引。

# 创建模型
model = Transformer(embed_dim=512, src_vocab_size=src_vocab_size, target_vocab_size=target_vocab_size, seq_length=seq_length, num_layers=num_layers, expansion_factor=4, n_heads=8)# 源序列 src (1 个句子,长度为 12)
src = torch.tensor([[0, 2, 5, 6, 4, 3, 9, 5, 2, 9, 10, 1]])# 目标序列的初始部分 trg,初始为 <sos> token (0)
trg = torch.tensor([[0]])# 打印输入的形状
print("Source shape:", src.shape)  # (1, 12)
print("Target shape (initial):", trg.shape)  # (1, 1)# 调用模型的推理函数进行解码
out = model.decode(src, trg)# 打印输出结果
print("Generated output:", out)

结果如下:

Source shape: torch.Size([1, 12])
Target shape (initial): torch.Size([1, 1])
Generated output: [9, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1, 1]


三、bug修复

位置编码部分,发现参考代码的两处bug

原代码:

pe[pos, i] = math.sin(pos / (10000 ** ((2 * i)/self.embed_dim)))
pe[pos, i + 1] = math.cos(pos / (10000 ** ((2 * (i + 1))/self.embed_dim)))

参考论文的数学公式应该修改如下:

pe[pos, i] = np.sin(pos/(10000**(i/self.embed_dim)))
if i+1 < self.embed_dim:pe[pos, i+1] = np.cos(pos/(10000**(i/self.embed_dim)))

原代码:

self.pe[:,:seq_len]

因为 self.pe 的维度是 (1, max_seq_len, embed_dim),那么 self.pe[:, :seq_len] 是错误的,因为它忽略了第三个维度(嵌入维度)。应该修改如下:

self.pe[:, :seq_len, :]

四、背景知识补充

残差连接(Residual Connection)

残差连接(Residual Connection) 是一种神经网络架构设计,它允许输入信息直接跳过某些层,并与这些层的输出相加。具体公式为:
y = F ( x ) + x y = F(x) + x y=F(x)+x
其中, x x x 是输入, F ( x ) F(x) F(x) 是经过若干层的非线性变换后的输出, y y y 是最终的输出。

残差连接的引入是为了缓解深层神经网络中的梯度消失梯度爆炸问题,确保信息能够在深层网络中顺利传播,使得模型在增加层数时仍然能够有效训练,避免性能下降。

图解:
在这里插入图片描述
图片来源:https://en.wikipedia.org/wiki/Residual_neural_network


层归一化(Layer Normalization)

批量归一化(Batch Normalization)和层归一化(Layer Normalization)都是加速神经网络训练并提高模型稳定性的正则化技术,但它们在操作方式和适用场景上有所不同。以下是它们之间的主要区别:

1. 归一化的对象

  • 批量归一化(Batch Normalization, BN)

    • 归一化的对象是同一神经元在不同样本上的激活值。
    • 具体来说,BN 在整个批次(batch)的样本中,对每个神经元的输出值进行归一化。
    • 例如,如果批次大小是 32,那么对某个特定神经元来说,它的32个输出会被一起归一化。
  • 层归一化(Layer Normalization, LN)

    • 归一化的对象同一个样本同一层的所有神经元的激活值。
    • LN 对每个样本在该层所有神经元的输出进行归一化,适用于小批次或者单样本的情况,不依赖批次大小。

2. 适用场景

  • 批量归一化

    • 通常用于卷积神经网络(CNN)和全连接层(Dense Layer)中,在图像分类等任务中表现良好。
    • 需要较大的批次(batch size),以确保对每一层的归一化效果足够好。
    • 在训练和推理阶段有区别:训练时计算当前批次的均值和方差;推理时使用在训练过程中累积的均值和方差。
  • 层归一化

    • 更适合于序列模型(如RNN、LSTM、Transformer),特别是在自然语言处理(NLP)任务中,因为这类模型处理的是时间序列数据,批次可能较小,甚至可能批次大小为1。
    • 不依赖于批次大小,在训练和推理阶段操作相同,这对批次大小变化敏感的任务非常有用。

总结

归一化方式归一化对象适用场景依赖批次大小训练与推理的差异
批量归一化同一神经元在不同样本上的激活值卷积神经网络、全连接层,适合大批次
层归一化同一层的所有神经元的激活值RNN、LSTM、Transformer等序列模型,适合小批次

图解

在这里插入图片描述
图片来源:https://medium.com/@bhavtoshrath.umn/batch-normalization-vs-layer-normalization-c76bb3cbf388


自回归(Autoregression,AR)

自回归(Autoregression,AR) 是一种时间序列模型,用于通过历史数据预测未来值。自回归模型假设当前的时间序列值是过去一段时间内观测值的线性组合。它常用于预测金融市场、经济指标、气象数据等具有时间依赖性的序列。

下图是用自回归来预测时间序列(飞机乘客数)

在这里插入图片描述
图片来源:https://towardsdatascience.com/how-to-forecast-time-series-using-autoregression-1d45db71683


注意力机制(Attention Mechanism)

注意力机制最早在机器翻译任务中被引入,它的核心思想是:在处理一个输入序列(例如一句话)时,不是对每个输入都平等地看待,而是根据任务的需要,动态地决定哪些部分更重要。

具体来说,假设我们在做机器翻译任务,翻译一段英文句子到中文。传统模型会把整个句子编码成一个固定的向量,然后生成翻译的句子。但对于长句子,固定的向量可能无法有效保留所有信息,导致翻译质量下降。

注意力机制则不一样。它允许模型在生成每个输出词时,根据当前的输出词需求,有选择性地“关注”输入句子的不同部分。比如,当翻译一个句子中的主语时,模型会自动关注与主语相关的词,而忽略其他无关的词。

这种机制可以通过一个权重矩阵实现,权重决定了输入的哪些部分被更多地“关注”。权重由模型学习得来,表示模型认为输入序列中哪些词对于当前输出最重要。

简而言之,注意力机制让模型在处理信息时,像人一样,有能力灵活地聚焦在关键信息上,而不是被全部信息平均分散精力,从而提高模型的表现。

示例图:
在这里插入图片描述
图片来源:https://www.scaler.com/topics/deep-learning/attention-mechanism-deep-learning/


Self-attention(自注意力机制)

Self-attention(自注意力机制)是一种深度学习模型中用于计算输入序列内部各元素之间依赖关系的机制。它允许模型在处理序列数据(如句子)时,动态关注序列中各个元素(如词)与其他元素的关系。这种机制在模型处理长距离依赖或捕捉全局信息时非常有效,尤其在Transformer模型中至关重要。

Self-attention的基本步骤

假设我们有一个长度为 n n n 的输入序列,每个元素(如词)表示为一个向量。

  1. 输入序列表示:每个输入向量 x i x_i xi 会使用三个不同的权重矩阵 W Q , W K , W V W^Q, W^K, W^V WQ,WK,WV 将其分别映射成三个新的向量,分别是:
    • Query 向量: q i = W Q x i q_i = W^Q x_i qi=WQxi
    • Key 向量: k i = W K x i k_i = W^K x_i ki=WKxi
    • Value 向量: v i = W V x i v_i = W^V x_i vi=WVxi

其中 W Q , W K , W V W^Q, W^K, W^V WQ,WK,WV 都是可训练学习的参数矩阵。

  1. 计算相似性(权重):对于序列中的每个元素,计算它的 Query 向量与其他所有元素的 Key 向量的点积,衡量它们的相似性。这就是注意力分数(Attention Score)。具体计算如下:

Attention Score i j = q i ⋅ k j \text{Attention Score}_{ij} = q_i \cdot k_j Attention Scoreij=qikj

  1. 归一化:将所有的注意力分数通过Softmax函数归一化,得到该元素与其他元素的注意力权重。这样保证所有权重的和为1,方便后续计算:

Attention Weight i j = exp ⁡ ( Attention Score i j ) ∑ j exp ⁡ ( Attention Score i j ) \text{Attention Weight}_{ij} = \frac{\exp(\text{Attention Score}_{ij})}{\sum_{j} \exp(\text{Attention Score}_{ij})} Attention Weightij=jexp(Attention Scoreij)exp(Attention Scoreij)

  1. 加权求和:用每个元素的注意力权重去加权相应的 Value 向量,得到最终的输出表示。即对于每个输入 x i x_i xi,它的输出是所有 v j v_j vj 向量的加权和:

Output i = ∑ j Attention Weight i j ⋅ v j \text{Output}_i = \sum_{j} \text{Attention Weight}_{ij} \cdot v_j Outputi=jAttention Weightijvj

Self-attention的基本步骤图

在这里插入图片描述
图片来源:https://sebastianraschka.com/blog/2023/self-attention-from-scratch.html

Self-attention 的优势

  • 捕捉长距离依赖:相比于卷积层或循环神经网络,Self-attention 可以直接建模序列中任意位置元素的关系,不管它们距离多远。
  • 并行计算:由于Self-attention 不依赖序列的顺序,因此可以并行计算,从而提升计算效率。
  • 全局感知:每个元素在生成输出时,能够参考整个输入序列的信息,而不仅仅是局部信息。

Self-attention在Transformer中的应用

Transformer模型使用Self-attention机制来处理输入序列(如句子),特别是在编码器和解码器中广泛使用。通过多头注意力机制(Multi-head Attention),模型可以从多个角度关注不同的特征,使模型更加灵活和强大。

总的来说,Self-attention 为序列中的每个元素提供了灵活且全局的上下文信息,是NLP任务(如机器翻译、文本生成等)中非常重要的工具。


点积(Dot-Product)

Dot-Product(点积)是线性代数中的一个基本运算,主要用于向量之间的操作。点积的结果是一个标量值,它反映了两个向量之间的相似度或关联性。

对于两个维度相同的向量 a = [ a 1 , a 2 , … , a n ] \mathbf{a} = [a_1, a_2, \dots, a_n] a=[a1,a2,,an] b = [ b 1 , b 2 , … , b n ] \mathbf{b} = [b_1, b_2, \dots, b_n] b=[b1,b2,,bn],它们的点积可以表示为:

a ⋅ b = a 1 b 1 + a 2 b 2 + ⋯ + a n b n = ∑ i = 1 n a i b i \mathbf{a} \cdot \mathbf{b} = a_1 b_1 + a_2 b_2 + \dots + a_n b_n = \sum_{i=1}^{n} a_i b_i ab=a1b1+a2b2++anbn=i=1naibi

通俗解释:点积就是将两个向量的每个对应元素相乘,然后把这些乘积加在一起,最终得到一个数值。

图解:

点积可以理解为向量的投影

在这里插入图片描述
图片来源:https://www.cuemath.com/algebra/dot-product/

应用场景:

  1. 向量相似度:在深度学习的注意力机制中,点积常被用来计算两个向量(如词向量)之间的相似度。点积越大,表示这两个向量越“相似”。
  2. 投影:点积也可以理解为一个向量在另一个向量方向上的投影,反映了两个向量的方向关系。

在注意力机制(如Transformer架构)中,Scaled Dot-Product Attention 计算的是 Query 向量和 Key 向量之间的点积,用来衡量输入序列中不同部分的相关性或重要性。


全连接层(Fully Connected Layer,简称 FC 层)

全连接层(Fully Connected Layer,简称 FC 层)是神经网络中的一种基本层,通常用于最后的输出或分类任务。它的特点是:每个输入节点都与下一层的每个输出节点完全连接,因此被称为“全连接”。

假设你有一个输入向量 x = [ x 1 , x 2 , … , x n ] \mathbf{x} = [x_1, x_2, \dots, x_n] x=[x1,x2,,xn],全连接层通过一个权重矩阵 W \mathbf{W} W 和一个偏置向量 b \mathbf{b} b 将输入映射到输出。输出向量 y \mathbf{y} y 可以表示为:

y = W x + b \mathbf{y} = \mathbf{W} \mathbf{x} + \mathbf{b} y=Wx+b

其中:

  • W \mathbf{W} W 是权重矩阵,每个元素表示从输入节点到输出节点的连接强度。
  • b \mathbf{b} b 是偏置向量,帮助调整输出。
  • x \mathbf{x} x 是输入向量, y \mathbf{y} y 是输出向量。

通俗解释:
全连接层就像一个“黑盒子”,它接收输入(向量),然后通过每个输入与每个输出之间的加权连接,将输入信息转化为一个新的输出。这个过程类似于对输入进行线性组合,再通过激活函数处理得到最终结果。

图解:
**工作原理:**
图片来源:https://indiantechwarrior.com/fully-connected-layers-in-convolutional-neural-networks/

应用场景:

  1. 分类任务:在图像分类、自然语言处理等任务的最后一层,通常会用全连接层将中间特征映射到分类标签。
  2. 回归任务:全连接层也可以用于预测连续值,比如房价预测等。

在深度学习网络的早期阶段,全连接层通常位于网络的末端,负责整合之前层提取的特征,最终进行预测或分类。


多头注意力(Multi-Head Attention)

多头注意力(Multi-Head Attention) 是 Transformer 模型中的一个核心机制,它扩展了 Self-Attention 的功能,通过引入多个“头”来同时从不同的子空间中提取信息。这样做的好处是,模型可以从不同的角度、不同的特征层次上理解序列中的依赖关系,使得模型更具表达能力和灵活性。

我们可以把子空间理解为一组特征的组合,每个子空间会强调输入序列中的某些特定的特征或关系。对于每个输入序列(比如句子中的词向量),模型会通过不同的线性变换矩阵,将输入映射到不同的子空间中。这些子空间的不同在于它们关注的重点信息不同,从而使得每个注意力头能够捕捉到不同的上下文关系或语义特征。

多头注意力的基本步骤

  1. 线性变换:对于每个输入向量 x i x_i xi,我们首先通过线性变换生成 Query ( Q Q Q)、Key ( K K K)、Value ( V V V) 向量,这一步与单头注意力类似。假设我们使用 h h h 个注意力头,则每个头都会生成一组独立的 Q Q Q, K K K, V V V 向量。

    每个头的 Q h Q_h Qh K h K_h Kh V h V_h Vh 的维度通常比原始向量要小,以减少计算量。

  2. 独立计算每个头的注意力:对于每个头,单独进行 Self-Attention 计算。具体来说,对于第 h h h 个头,计算 Query 和 Key 的点积,再通过 Softmax 归一化,得到注意力权重,然后用这些权重加权 Value 向量,计算出当前头的输出。

    对第 h h h 个头,计算方式为:

    Attention h ( Q h , K h , V h ) = softmax ( Q h K h T d k ) V h \text{Attention}_h(Q_h, K_h, V_h) = \text{softmax}\left(\frac{Q_h K_h^T}{\sqrt{d_k}}\right) V_h Attentionh(Qh,Kh,Vh)=softmax(dk QhKhT)Vh

    其中 d k d_k dk 是 Key 向量的维度,用来缩放点积结果,避免数值过大。

  3. 拼接头的输出:每个头都会产生一个独立的输出向量,我们将所有 h h h 个头的输出拼接起来,形成一个新的向量。这个步骤可以让不同的注意力头捕捉到输入序列中不同方面的信息。

    拼接后的向量为:

    Concat ( Attention 1 , Attention 2 , … , Attention h ) \text{Concat}( \text{Attention}_1, \text{Attention}_2, \dots, \text{Attention}_h ) Concat(Attention1,Attention2,,Attentionh)

  4. 线性变换:最后,将拼接后的向量通过一个线性变换,得到最终的输出。这个线性变换的作用是将拼接后的多头输出映射回到与输入相同的维度,以便后续层继续处理。

图解

在这里插入图片描述
图片来源:Attention Is All You Need

多头注意力的优势

  1. 多角度信息提取:单个头只能关注输入序列中的某一方面或某个层次的特征,而多头注意力可以并行地从不同子空间中捕捉序列中的不同依赖关系。比如,一个头可能关注局部上下文,另一个头可能关注更远距离的依赖关系。

  2. 提高模型的学习能力:通过多头注意力,模型能够从多个角度学习不同的语义或模式,使得模型具备更强的泛化能力。

  3. 并行计算:多个注意力头可以并行计算,从而提高训练和推理的效率。

多头注意力的公式总结

假设有 h h h 个注意力头,输入向量 X X X,多头注意力的计算可以总结为:

MultiHead ( Q , K , V ) = Concat ( head 1 , … , head h ) W O \text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, \dots, \text{head}_h) W^O MultiHead(Q,K,V)=Concat(head1,,headh)WO

其中,每个 head i \text{head}_i headi 表示第 i i i 个头的计算:

head i = Attention ( Q W i Q , K W i K , V W i V ) \text{head}_i = \text{Attention}(QW_i^Q, KW_i^K, VW_i^V) headi=Attention(QWiQ,KWiK,VWiV)

W i Q W_i^Q WiQ, W i K W_i^K WiK, W i V W_i^V WiV 是每个头的线性变换权重矩阵, W O W^O WO 是拼接后的线性变换权重矩阵。

多头注意力的实际应用

在 Transformer 模型中,多头注意力被广泛应用于编码器和解码器结构中,尤其在自然语言处理任务(如机器翻译、文本生成等)中效果显著。它通过同时捕捉不同层次、不同角度的依赖关系,使得模型可以更好地理解序列中的复杂信息结构。


Position-wise Feed-Forward Networks(位置前馈网络,简称 FFN)

Position-wise Feed-Forward Networks(位置前馈网络,简称 FFN) 是 Transformer 架构中的一个重要组成部分,它位于每个注意力层(Self-Attention 层)之后,主要用于对每个位置的表示进行进一步的非线性变换,从而提升模型的表达能力。

为什么 Transformer 需要 Position-wise Feed-Forward Networks?

Transformer 的核心之一是 Self-Attention,它能够捕捉输入序列中各个词之间的依赖关系。不过,Self-Attention 是一种 线性操作,虽然可以捕捉序列中的全局关系,但缺少对输入进行 非线性变换 的能力,而深度神经网络中的非线性特征是非常关键的,因为它能帮助模型学习更复杂的表示和关系。

为了弥补这一不足,Position-wise Feed-Forward Networks 被加入到 Transformer 中。它通过对每个位置的向量单独应用非线性变换,使得每个位置的表示更具丰富性和灵活性。

Position-wise Feed-Forward Networks 的工作原理

每个位置的 FFN 是独立的,即对每个输入位置的向量单独进行操作,不同位置之间没有共享权重。具体计算过程如下:

  1. 对每个位置的向量 x i x_i xi,首先通过一个线性变换(矩阵乘法),然后通过一个非线性激活函数(通常是 ReLU)。
  2. 再通过一个第二个线性变换,最后得到该位置的输出。

公式表示如下:

FFN ( x i ) = W 2 ⋅ ReLU ( W 1 ⋅ x i + b 1 ) + b 2 \text{FFN}(x_i) = W_2 \cdot \text{ReLU}(W_1 \cdot x_i + b_1) + b_2 FFN(xi)=W2ReLU(W1xi+b1)+b2

其中:

  • W 1 W_1 W1 W 2 W_2 W2 是两个不同的权重矩阵,分别用于线性变换。
  • b 1 b_1 b1 b 2 b_2 b2 是偏置项。
  • ReLU \text{ReLU} ReLU 是激活函数,增加非线性能力。

这个操作独立应用于每个输入序列位置的向量,所以叫做 Position-wise,因为每个位置的处理方式相同但独立。


L2范数(L2 norm)

L2范数(L2 norm) 是向量或矩阵的一种度量方式,它表示向量的“长度”或“欧几里得距离”。在机器学习和深度学习中,L2范数常用于正则化、梯度计算和度量向量相似性等任务。

L2范数有时也被称为欧几里得范数,定义为向量中所有元素的平方和的平方根。对于一个向量 x = [ x 1 , x 2 , … , x n ] \mathbf{x} = [x_1, x_2, \dots, x_n] x=[x1,x2,,xn],其 L2范数定义为:

∥ x ∥ 2 = x 1 2 + x 2 2 + ⋯ + x n 2 = ∑ i = 1 n x i 2 \|\mathbf{x}\|_2 = \sqrt{x_1^2 + x_2^2 + \dots + x_n^2} = \sqrt{\sum_{i=1}^{n} x_i^2} x2=x12+x22++xn2 =i=1nxi2

L2范数的作用和应用

  1. 度量向量的长度:L2范数衡量向量在空间中的“长度”或“大小”。它可以用来评估向量的规模或者在不同空间中的距离。

  2. 向量正则化:L2范数在机器学习中的常见应用是L2正则化(也叫 Ridge 正则化)。它通过在损失函数中加入 L2 范数惩罚项,防止模型过拟合。正则化项形式为:

    Regularization Term = λ ∥ w ∥ 2 2 = λ ∑ i = 1 n w i 2 \text{Regularization Term} = \lambda \|\mathbf{w}\|_2^2 = \lambda \sum_{i=1}^{n} w_i^2 Regularization Term=λw22=λi=1nwi2

    其中 λ \lambda λ 是正则化系数, w \mathbf{w} w 是模型的权重向量。L2正则化惩罚大权重值,鼓励模型找到更平滑、泛化能力更强的解。

  3. 梯度更新中的归一化:在深度学习中,向量有时会被归一化为单位长度(即 L2 范数为 1)。这种 L2归一化 有助于消除不同特征之间尺度差异对梯度更新的影响。

  4. 距离度量:L2范数可以用来衡量两个向量之间的欧几里得距离。在神经网络中,特别是词向量、特征向量或嵌入向量的比较中,L2范数用于计算相似度或距离。

L2范数与其他范数的区别

  • L1范数:L1范数是向量元素绝对值的和,它更适合用于稀疏表示(如L1正则化)。L2范数则倾向于平滑化权重,抑制过大的权重值。

    对于向量 x = [ x 1 , x 2 , … , x n ] \mathbf{x} = [x_1, x_2, \dots, x_n] x=[x1,x2,,xn],L1范数定义为:

    ∥ x ∥ 1 = ∣ x 1 ∣ + ∣ x 2 ∣ + ⋯ + ∣ x n ∣ \|\mathbf{x}\|_1 = |x_1| + |x_2| + \dots + |x_n| x1=x1+x2++xn

  • L∞范数:L∞范数是向量中元素的最大绝对值,它更多用于极值问题的度量,而 L2 范数则强调向量整体的长度。

总结

L2范数 是一种衡量向量大小的常用工具,广泛应用于机器学习的正则化、距离计算、梯度归一化等任务中。它通过计算向量元素平方和的平方根,提供了对向量规模的度量,帮助模型更好地处理数值规模、正则化和相似性评估等问题。


Positional Encoding(位置编码)

Positional Encoding(位置编码) 是 Transformer 模型中的一个关键机制,用于在模型中引入位置信息。因为 Transformer 模型的结构不同于循环神经网络(RNN)或卷积神经网络(CNN),它没有序列顺序感知能力,即模型并不知道输入的序列中词与词之间的相对位置。为了让模型能够理解输入序列中词的顺序和相对位置信息,位置编码被引入。

为什么需要 Positional Encoding?

Transformer 依赖于 Self-Attention(自注意力机制),而自注意力机制并不保留输入序列的位置信息,它仅根据输入序列中的词语间的相似性来决定哪些词要被“关注”。这意味着模型在处理输入时,缺少序列顺序的感知。

与 RNN 不同,RNN 是依次处理输入序列的,因此天然保留了顺序信息。而 Transformer 模型是并行处理整个输入序列,所以为了弥补这一不足,需要为每个词引入位置信息,即通过 Positional Encoding 来告诉模型每个词在序列中的位置。

Positional Encoding 的原理

Positional Encoding 将位置信息嵌入到词向量中,使得模型能够识别序列中的位置信息。这些位置编码是一些加到词嵌入上的向量。每个输入向量被加上一个与其位置相关的编码向量,最终输入模型的向量不仅包含了词语的语义信息,还包含了它在序列中的位置。

具体来说,位置编码是通过特定的数学函数生成的,公式如下:

对于序列中的第 p o s pos pos 个位置,第 i i i 维的 Positional Encoding 定义为:

P E ( p o s , 2 i ) = sin ⁡ ( p o s 1000 0 2 i / d m o d e l ) PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i / d_{model}}}\right) PE(pos,2i)=sin(100002i/dmodelpos)

P E ( p o s , 2 i + 1 ) = cos ⁡ ( p o s 1000 0 2 i / d m o d e l ) PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i / d_{model}}}\right) PE(pos,2i+1)=cos(100002i/dmodelpos)

其中:

  • p o s pos pos 表示输入序列中的位置。
  • i i i 表示向量的维度索引。
  • d m o d e l d_{model} dmodel 是词嵌入的维度。

解释

  • 正弦和余弦函数:位置编码的值基于正弦(sine)和余弦(cosine)函数。这些函数的周期性质使得不同位置的编码产生不同的向量,同时也让位置编码保留了一定的相对位置关系。例如,靠近的词语在向量空间中的位置编码也会比较相近,反映了词之间的相对距离。

  • 指数缩放:公式中的 1000 0 2 i / d m o d e l 10000^{2i / d_{model}} 100002i/dmodel 是一个指数缩放因子,确保不同维度上的编码具有不同的频率,使得不同维度能够捕捉到不同粒度的位置信息。

加法操作

在 Transformer 中,词嵌入向量与位置编码相加,形成模型的最终输入。这样做的目的是在保持原有词嵌入语义信息的基础上,赋予每个词语其位置信息,使得模型可以既理解每个词的含义,也能知道它们在句子中的顺序。

Positional Encoding 的性质

  1. 序列长度无关:位置编码公式与序列的长度无关,因此模型可以处理任意长度的输入序列。

  2. 捕捉相对位置:正弦和余弦函数的周期性帮助模型在较大范围内捕捉相对位置信息。模型不仅能了解词语在句子中的绝对位置,还能通过编码向量之间的差异感知词与词之间的相对距离。

  3. 通用性:由于位置编码通过固定的函数生成,无需额外学习参数,因此位置编码在不同任务和数据上都可以适用。

总结

Positional Encoding 通过为每个输入词加上基于其位置的编码,赋予了 Transformer 模型位置信息,使其能够理解序列中的顺序。它利用正弦和余弦函数生成的周期性编码,在保证模型并行计算的同时,能够有效捕捉到输入词序列中的相对和绝对位置关系。


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

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

相关文章

Istio下载及安装

Istio 是一个开源的服务网格&#xff0c;用于连接、管理和保护微服务。以下是下载并安装 Istio 的步骤。 官网文档&#xff1a;https://istio.io/latest/zh/docs/setup/getting-started/ 下载 Istio 前往Istio 发布页面下载适用于您的操作系统的安装文件&#xff0c;或者自动…

Python数据分析与可视化(Python绘图详解)

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

6张图掌握提示词工程师工作范围与工作技巧(提示词原理篇)

在人工智能的疆域中&#xff0c;提示词工程师扮演着至关重要的角色。他们精心设计的话语&#xff0c;是引导AI模型理解人类需求、激发创造力的关键。如同指挥官的号令&#xff0c;提示词工程师的每一个提问&#xff0c;都让AI的潜力得到释放&#xff0c;让技术与智慧的对话更加…

群晖NAS使用Docker本地部署网页版Ubuntu系统并实现无公网IP远程访问

文章目录 前言1. 下载Docker-Webtop镜像2. 运行Docker-Webtop镜像3. 本地访问网页版Linux系统4. 群晖NAS安装Cpolar工具5. 配置异地访问Linux系统6. 异地远程访问Linux系统7. 固定异地访问的公网地址 前言 本文旨在详细介绍如何在群晖NAS部署docker-webtop&#xff0c;并结合c…

电力电塔电线缺陷检测数据集 voc yolo

电力 电塔电线缺陷检测数据集 10000张 带标注 voc yolo 电力电塔电线缺陷检测数据集 数据集描述 该数据集旨在用于电力电塔和电线的缺陷检测任务&#xff0c;涵盖多种常见的缺陷类型。数据集包含了大量的图像及其对应的标注信息&#xff0c;可用于训练计算机视觉模型&#x…

Linux:进程(一)

目录 一、概念的理解 二、指令操作 一、概念的理解 在许多地方对进程的定义都是这样的一句话&#xff1a;加载到内存中的程序、正在运行的程序、进程可以排队 而要真正理解进程是什么&#xff0c;这一句话的解释远远不够。 在定义进程之前&#xff0c;先来理解程序&#xff0…

21、Tomato

难度 低(个人认为中) 目标 root权限 一个flag 使用VMware启动 kali 192.168.152.56 靶机 192.168.152.66 信息收集 端口信息收集 可以看到有个ftp服务&#xff0c;2211实际是ssh协议端口&#xff0c;80、8888是一个web服务 web测试 80端口显示一个tomato 查看源码给了一些…

MATLAB系列06:复数数据、字符数据和附加画图类

MATLAB系列06&#xff1a;复数数据、字符数据和附加画图类 6. 复数数据、字符数据和附加画图类6.1 复数数据6.1.1 复变量&#xff08; complex variables&#xff09;6.1.2 带有关系运算符的复数的应用6.1.3 复函数&#xff08; complex function&#xff09;6.1.4 复数数据的作…

【FPGA】编程方式

FPGA编程方式 1 什么是PLD&#xff1f;2 什么是颗粒度&#xff1f;3 可编程逻辑器件的编程方式有哪些&#xff1f;3.1 SRAM 编程技术3.2 Flash/EEPROM 编程技术3.3 反熔丝编程技术3.4 编程技术比较 参考资料 1 什么是PLD&#xff1f; 可编程逻辑器件 英文全称为&#xff1a;pr…

SpringBoot 数据库表结构文档生成

官方地址&#xff1a;https://github.com/pingfangushi/screw screw 螺丝钉&#xff0c;支持以下数据库 MySQL MariaDB TIDB Oracle SqlServer PostgreSQL Cache DB&#xff08;2016&#xff09; 生产文档支持 html word markdown 开始 添加依赖 <!-- 螺丝钉 --><…

c语言面试字符串复制

1&#xff0c;下面这个函数的打印是什么&#xff1a; #include<stdio.h> #include<string.h>int main() {char str0[5], str1[] "welcome";strcpy(str0, str1);printf("str0:%s\r\n",str0);printf("str1:%s\r\n",str1); } larkla…

nginx实现https安全访问的详细配置过程

文章目录 前言什么是 HTTP&#xff1f;什么是 HTTPS&#xff1f;HTTP 和 HTTPS 的区别为什么 HTTPS 被称为安全的&#xff1f;配置过程配置自签名证书 前言 首先我们来简单了解一下什么是http和https以及他们的区别所在. 什么是 HTTP&#xff1f; HTTP&#xff0c;全称为“超…

notepad++的json查看

json文件查看 因为接触到3dtile模型&#xff0c;所以经常需要和json打交道&#xff0c;但是很多模型是下面这种情况&#xff0c;不好阅读&#xff0c;所以可以使用notepad的插件查看 正常打开是这样的 加载notepad插件 搜索json下载安装就可以了 如果网络抽象&#xff0c;下载…

Hive企业级调优[3]—— Explain 查看执行计划

Explain 查看执行计划 Explain 执行计划概述 EXPLAIN 命令呈现的执行计划由一系列 Stage 组成。这些 Stage 之间存在依赖关系&#xff0c;每一个 Stage 可能对应一个 MapReduce Job 或者一个文件系统的操作等。如果某 Stage 对应了一个 MapReduce Job&#xff0c;则该 Job 在 …

Apache James配置连接达梦数据库

项目场景&#xff1a; Apache James配置连接达梦数据库&#xff0c;其他配置中不存在的数据库也可参考此方案。 配置步骤 1、把需要的jar包导入到James 把DmJdbcDriver18.jar复制到下面lib目录下 james-2.3.2\lib 2、 修改连接配置 james-2.3.2\apps\james\SAR-INF\confi…

C# 技巧在 foreach 循环中巧妙获取索引

目录 前言 使用 LINQ 和扩展方法 直接在 LINQ 查询中使用 使用 LINQ 的 Select() 与 Enumerable.Range() 总结 最后 前言 在C#中foreach 循环是处理集合的常见方式&#xff0c;因其简洁性和易读性而广受青睐。 但是在某些情况下&#xff0c;我们需要同时获取集合中元素的…

[深度学习]神经网络

1 人工神经网络 全连接神经网络 2 激活函数 隐藏层激活函数由人决定输出层激活函数由解决的任务决定: 二分类:sigmoid多分类:softmax回归:不加激活(恒等激活identify)2.1 sigmoid激活函数 x为加权和小于-6或者大于6,梯度接近于0,会出现梯度消失的问题即使取值 [-6,6] ,…

头戴式蓝牙耳机性价比高的有哪些?四款高能性价比机型对比推荐

在当今科技日新月异的时代&#xff0c;头戴式蓝牙耳机已经成为了我们日常生活中不可或缺的一部分&#xff0c;无论是通勤路上、健身房内还是家中休闲时&#xff0c;一副优质的头戴式蓝牙耳机都能为我们带来沉浸式的听觉体验&#xff0c;那么头戴式蓝牙耳机性价比高的有哪些&…

模版结构体没有可用成员(C3203)

没有typedef模版结构体而导致。 并且_tables[index]无法访问HashData内部的成员。

Windows系统使用PHPStudy搭建Cloudreve私有云盘公网环境远程访问

文章目录 1、前言2、本地网站搭建2.1 环境使用2.2 支持组件选择2.3 网页安装2.4 测试和使用2.5 问题解决 3、本地网页发布3.1 cpolar云端设置3.2 cpolar本地设置 4、公网访问测试5、结语 1、前言 自云存储概念兴起已经有段时间了&#xff0c;各互联网大厂也纷纷加入战局&#…