首页
实用链接
图床推荐
友链
关于
Search
1
彻底卸载Cloudflare Tunnel(解决 cloudflared service uninstall 报错问题)
483 阅读
2
从零开始注册Hugging Face账号到部署网页应用
102 阅读
3
Debian 11.2 搭建 Typecho 个人博客教程
90 阅读
4
紫电猫8.8元随身WIFI刷Debian系统教程
88 阅读
5
Linux配置frps与frpc的四种隧道并设置开机启动
87 阅读
默认分类
教程
随笔
软件开发
笔记
登录
/
注册
Search
标签搜索
Datawhale
AI+X
Fun-Transformer
#Datewhale组队学习
隧道
Debian
Transformer
教程
随身wifi
frp
frpc
frps
内网穿透
Linux
toml
我的世界
Minecraft
MySQL
单片机
OLED
Simuoss
累计撰写
20
篇文章
累计收到
12
条评论
首页
栏目
默认分类
教程
随笔
软件开发
笔记
页面
实用链接
图床推荐
友链
关于
搜索到
9
篇与
的结果
2025-09-21
Typecho博客从本地部署到Docker部署(1.2.1->1.3.0)迁移成功,Congratulations!
前言24年8月,因为Wifi棒子断电重启后无法自动回连Wifi,博客站就迁移到了一块847工控板上。一开始一切运行正常,但到了25年初,工控板经常异常死机,死机时用手摸一下还会自动重启,怀疑是板子电气性能不佳,遂计划把博客站迁移到其他设备上。但是开始迁移时却被一些问题卡住了,届时正值找工作比较忙,于是就先作罢,博客站就此荒废了几个月的时间。这个周末和盒子里的847工控板大眼瞪小眼看了许久,决定把它收拾了,就有了本篇。预先准备我的博客数据库使用SQLite,所以至少应该保留类似66ba4f5eec6c5.db这样的数据库文件。文件的路径通常在typecho\usr下。如果你想保留之前的主题,还需要保留整个typecho\usr\themes文件夹运行博客的新机器上需要先装好docker,为了方便今后的数据迁移,我们使用docker部署使用Docker部署时,建议直接使用docker compose,方便后续维护。compose文件如下:services: typecho: image: joyqi/typecho:nightly-php7.4-apache container_name: typecho-server restart: always environment: - TYPECHO_SITE_URL=https://xxx.xxx ports: - 8634:80 volumes: - ./typecho:/app对比文档里的写法,我们去掉了头部过时的版本声明,修改了宿主机端口,并挂载出了整个/app(也就是Typecho应用程序根目录)。挂载出整个/app的好处有以下两点:如果你的域名使用https,就需要编辑config.inc.php;如果你想迁移主题包,还需要编辑typecho\usr\themes文件夹,那索性就都挂载出来。开始迁移将以上docker compose文件保存到你想放置Typecho的目录,文件名为docker-compose.yaml,然后同级运行命令:docker compose up -d # 如果你不是root,使用sudo docker compose up -d这时docker会从云端仓库把Typecho镜像拉下来,静待一段时间,直到命令行窗口显示:笔者近期家里没有合适的设备,就选择了Windows上使用Docker Desktop部署,之后买了新设备再迁移。在Linux上的回显也大同小异。此时,同目录的typecho文件夹下就是容器内部挂载出的/app目录了。进入typecho\usr,把之前的.db文件(通常类似66ba4f5eec6c5.db)复制过来,然后进入127.0.0.1:8634进行博客初始化。初始界面直接点击我准备好了,开始下一步即可。随后你会看到下面界面,这里按照之前博客的配置选,比如我之前是SQLite,就选SQLite。pdo驱动和原生驱动都可以正常运行,选哪个都行。数据库前缀一般没有人会改,如果你记不清了,可以上类似sqlite-viewer的网站查看,比如我这个db文件夹就是以typecho为前缀的:最下面的数据库文件路径,我们已经把原先的数据库文件放在了正确的地方,所以只要把这里的.db文件的名字改成我们之前名字就好。配置完成点确认开始安装,此时Typecho会提示安装程序检查到原有数据表已经存在,证明我们配对了。选择使用原有数据即可。不出意外的话,会出现如下界面。如果你也到了这里,恭喜你迁移基本成功!可以进入博客界面看看数据都在不在常见问题明明密码正确,但就是登录不进管理界面,点击登录总是被重新定向到登录页,原地tp问题解析:如果站点是通过HTTPS访问的(比如https://blog.simuoss.cn),但Typecho内部没有正确配置为使用安全连接,会导致登录后Session或Cookie处理异常,从而被重定向回登录页。解决方案:在Typecho根目录下的 config.inc.php 文件中,添加以下代码并保存:define('__TYPECHO_SECURE__', true);这行代码告诉Typecho当前环境是安全的(HTTPS),应该生成安全的Cookie。保存后,运行以下指令重启Typecho:docker compose restart # 如果你不是root,使用sudo docker compose restart此时重新登录,就能进管理后台了。进入后台后显示:[升级程序] 检测到新版本!但点升级后报500 SERVER ERROR或DATABASE QUERY ERROR此时先使用以下指令重启:docker compose restart # 如果你不是root,使用sudo docker compose restart如果还是报错,再看下面的解决方案。问题解析:在Docker容器中,Web服务器进程是以特定用户(通常是 www-data)身份运行的,但这个用户不一定有数据库的写入权限,所以升级修改数据库结构时就会失败。解决方案:使用下方命令进入typecho容器内部,并授予www-data用户所有权:docker exec -it typecho-server bash # 进入容器内部命令行。如果你不是root,使用sudo docker exec -it typecho-server bash chown -R www-data /app/usr/ # 将整个目录的所有者设置成`www-data`然后退出容器并重启服务:exit # 退出容器 docker compose restart # 如果你不是root,使用sudo docker compose restart重启后再点击完成升级按钮,即可安全升级到最新版本。如果不重启,直接点升级按钮,还是会报500 SERVER ERROR。这时候不用担心,重启后就可以正常进入管理后台了。如果你在进行这些操作之前已经点过完成升级按钮了,就需要在执行完上述流程之后,先删掉已经损坏的.db文件,重新复制来原先的.db文件,然后使用docker compose restart重启服务,重新进行升级流程。如何迁移主题?前文提到,只要把之前的typecho\usr\themes文件夹复制过来即可。复制来后重启服务,主题就回来了。docker compose restart # 如果你不是root,使用sudo docker compose restart参考链接:Typecho 登录返回 302 的解决方法致谢:阿里Qwen:Debug时帮我提供了很多思路
2025年09月21日
11 阅读
0 评论
0 点赞
2025-01-28
【2025.1 Datawhale AI+X 共学活动】Fun-Transformer —— Task5:项目实践(手写Transformer)
Datewhale组队学习从零实现注意力机制:NumPy 和 SciPy 的实践之旅注意力机制,作为现代深度学习中的核心组件之一,早已在自然语言处理、计算机视觉等领域大放异彩。它的魅力在于能够动态地聚焦于输入数据的关键部分,从而让模型更“聪明”地处理信息。今天,我们将抛开复杂的框架,仅用 NumPy 和 SciPy,从零实现一个通用的注意力机制,深入理解其背后的数学逻辑。1. 词向量:将词汇语义嵌入高维空间一切从词向量开始。词向量是单词在嵌入空间中的数学表示,它将离散的符号转化为连续的向量,从而能够进行数学运算。这里,定义了四个单词的词向量,每个向量的维度为3:import numpy as np from scipy.special import softmax word_1 = np.array([1, 0, 0]) word_2 = np.array([0, 1, 0]) word_3 = np.array([1, 1, 0]) word_4 = np.array([0, 0, 1]) words = np.array([word_1, word_2, word_3, word_4])这些词向量被堆叠成一个矩阵 words,形状为 (4, 3),每一行代表一个单词的嵌入。这一步的意义在于,将离散的符号转化为可计算的数学对象,为后续的注意力计算奠定基础。2. 权重矩阵:映射到查询、键和值注意力机制的核心在于查询(Query)、键(Key)和值(Value)的生成。这些概念听起来抽象,但本质上是将词向量映射到不同的空间中,以便计算它们之间的关系。通过随机初始化三个权重矩阵 W_Q、W_K 和 W_V,词向量被转换为查询、键和值:np.random.seed(42) W_Q = np.random.randint(3, size=(3, 3)) W_K = np.random.randint(3, size=(3, 3)) W_V = np.random.randint(3, size=(3, 3)) Q = words @ W_Q K = words @ W_K V = words @ W_V查询、键和值的生成过程,本质上是矩阵乘法。这一步的意义在于,将原始的词向量映射到不同的语义空间中,为后续的相似度计算做准备。3. 得分计算:量化相似度接下来,通过查询和键的点积,计算得分矩阵。每个得分表示一个查询向量与一个键向量的相似度:scores = Q @ K.T得分的计算是注意力机制的关键步骤之一。点积越大,表示两个向量越相似。这一步的意义在于,量化每个查询向量与所有键向量之间的关系,为后续的权重分配提供依据。4. 权重分配:Softmax 概率化得分矩阵虽然量化了相似度,但还需要将其转化为概率分布。这里,softmax 函数登场了。通过对得分进行缩放并应用 softmax,得到了注意力权重:weights = softmax(scores / np.sqrt(K.shape[1]), axis=1)softmax 的作用是将得分转化为概率分布,使得每个查询向量对所有键向量的权重之和为1。缩放操作则是为了保持数值稳定性,防止得分过大或过小导致计算溢出或下溢。5. 注意力输出:加权求和最后,通过加权求和的方式,计算注意力输出。每个注意力输出是所有值向量的加权和,权重由 softmax 函数计算得到:attention = weights @ V print(attention)注意力输出的计算是注意力机制的最终步骤。通过加权求和,模型能够动态地聚焦于输入序列中最相关的部分,从而生成更准确的输出。多头注意力机制:从单头到多头的进化注意力机制的核心思想是让模型能够动态地关注输入序列中的不同部分,从而更好地捕捉上下文信息。然而,单头注意力机制有一个明显的局限性:它只能从一个角度捕捉输入序列的信息。为了克服这一限制,多头注意力机制(Multi-Head Attention)应运而生。通过并行地计算多个注意力头,模型能够从不同的子空间中提取信息,从而更全面地理解输入数据。今天,我们将深入探讨多头注意力机制的实现,并通过 PyTorch 实现一个完整的 MultiHeadAttention 类。1. 多头注意力机制的核心思想多头注意力机制的核心在于将输入数据映射到多个子空间中,并在每个子空间中独立地计算注意力。具体来说,输入数据会被线性变换为多个查询(Query)、键(Key)和值(Value),然后在每个头上分别计算注意力。最后,所有头的输出会被拼接起来,并通过一个线性层映射回原始维度。这种设计的好处在于:多视角捕捉信息:每个头可以关注输入序列的不同部分,从而捕捉更丰富的特征。并行计算:多个头的计算可以并行进行,提高了计算效率。灵活性:通过调整头的数量,可以控制模型的复杂度和表达能力。2. 实现细节:从代码中理解多头注意力以下是一个完整的 MultiHeadAttention 类的实现,我们将逐行解析其关键部分。import math import torch import torch.nn as nn import torch.nn.functional as F class MultiHeadAttention(nn.Module): def __init__(self, heads, d_model, dropout=0.1): super().__init__() self.d_model = d_model # 模型的总维度 self.d_k = d_model // heads # 每个头的维度 self.h = heads # 头的数量 # 线性层,用于生成 Q、K、V self.q_linear = nn.Linear(d_model, d_model) self.k_linear = nn.Linear(d_model, d_model) self.v_linear = nn.Linear(d_model, d_model) # Dropout 层,用于正则化 self.dropout = nn.Dropout(dropout) # 输出线性层,用于将多头输出映射回模型维度 self.out = nn.Linear(d_model, d_model) def attention(self, q, k, v, mask=None): # 计算 Q 和 K 的点积,并除以 sqrt(d_k) 进行缩放 scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.d_k) # 如果提供了掩码,则将掩码对应的位置设置为负无穷 if mask is not None: scores = scores.masked_fill(mask == 0, -1e9) # 应用 softmax 计算注意力权重 scores = F.softmax(scores, dim=-1) # 应用 dropout scores = self.dropout(scores) # 将注意力权重与 V 相乘,得到输出 output = torch.matmul(scores, v) return output def forward(self, q, k, v, mask=None): batch_size = q.size(0) # 将输入通过线性层,并调整形状以进行多头计算 q = self.q_linear(q).view(batch_size, -1, self.h, self.d_k).transpose(1, 2) k = self.k_linear(k).view(batch_size, -1, self.h, self.d_k).transpose(1, 2) v = self.v_linear(v).view(batch_size, -1, self.h, self.d_k).transpose(1, 2) # 计算注意力输出 scores = self.attention(q, k, v, mask) # 将多头输出拼接,并调整形状以匹配模型维度 concat = scores.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model) # 通过输出线性层 output = self.out(concat) return output3. 关键步骤解析1. 初始化参数d_model:模型的总维度。d_k:每个头的维度,等于 d_model // heads。heads:头的数量。线性层 q_linear、k_linear、v_linear:用于将输入映射到查询、键和值。out:输出线性层,用于将多头输出映射回模型维度。2. 注意力计算点积计算:通过矩阵乘法计算查询和键的点积,并除以 sqrt(d_k) 进行缩放,以防止点积过大。掩码处理:如果提供了掩码,则将掩码对应的位置设置为负无穷,这样在 softmax 后这些位置的值为0。Softmax:将得分转换为概率分布。Dropout:对注意力权重进行随机丢弃,防止过拟合。加权求和:将注意力权重与值相乘,得到输出。3. 前向传播线性变换:将输入通过线性层,生成查询、键和值。形状调整:将输入调整为 (batch_size, heads, seq_len, d_k),以便进行多头计算。注意力计算:在每个头上独立计算注意力。拼接输出:将所有头的输出拼接起来,并通过输出线性层映射回模型维度。4. 测试与验证以下代码用于测试 MultiHeadAttention 类的功能:if __name__ == "__main__": heads = 4 d_model = 128 # d_model 必须是 heads 的整数倍 dropout = 0.1 model = MultiHeadAttention(heads, d_model, dropout) batch_size = 2 seq_len = 5 q = torch.rand(batch_size, seq_len, d_model) # Query k = torch.rand(batch_size, seq_len, d_model) # Key v = torch.rand(batch_size, seq_len, d_model) # Value output = model(q, k, v) print("Output shape:", output.shape) # 应该是 [batch_size, seq_len, d_model] loss = output.mean() loss.backward() print("Backward pass completed.")输出结果:Output shape: torch.Size([2, 5, 128]) Backward pass completed.欢迎各位大佬在评论区交流&批评指正!Datewhale组队学习参考链接Datawhale AI+X 共学活动 Fun-Transformer —— Task5:项目实践Attention Is All You Need
2025年01月28日
7 阅读
0 评论
0 点赞
2025-01-25
【2025.1 Datawhale AI+X 共学活动】Fun-Transformer —— Task4:Decoder
Datewhale组队学习*即将到来欢迎各位大佬在评论区交流&批评指正!Datewhale组队学习参考链接Datawhale AI+X 共学活动 Fun-Transformer —— Task4:DecoderAttention Is All You Need
2025年01月25日
5 阅读
0 评论
0 点赞
2025-01-21
【2025.1 Datawhale AI+X 共学活动】Fun-Transformer —— Task3:Encoder
Datewhale组队学习1. 编码器(Encoder)1.1 Encoder 工作流程输入阶段初始输入:输入数据(如文本中的单词)首先通过嵌入层(Input Embedding)转换为向量表示。为了捕捉序列中元素的位置信息,位置编码(Position Embedding)被添加到嵌入向量中。位置编码可以是固定的(如正弦余弦函数)或可学习的。第一个Encoder子模块接收嵌入和位置编码的组合输入。后续 Encoder 输入:除了第一个Encoder子模块外,其他Encoder子模块从前一个Encoder接收输入,形成链式结构。核心处理阶段多头自注意力层(Multi-Head Self-Attention):输入序列通过线性变换生成查询(Q)、键(K)、值(V)向量。通过缩放点积注意力(Scaled Dot-Product Attention)计算序列中不同位置之间的关联关系。注意力分数通过softmax归一化,加权求和后得到自注意力输出。前馈层(Feedforward Layer):自注意力层的输出经过一个全连接网络(通常包含两层线性变换和ReLU激活函数),提取更高层次的特征。前馈层的输出传递给下一个Encoder子模块(如果不是最后一个Encoder)。残差与归一化阶段残差连接(Residual Connection):自注意力层和前馈层的输入与输出通过残差连接相加(如输入为 $x$,输出为 $y$,则结果为 $x + y$)。残差连接缓解了深层网络中的梯度消失问题,使模型更容易训练。层归一化(Layer Normalization):在残差连接后,对输出进行层归一化,加速模型收敛并提高泛化能力。归一化操作对每一层的神经元输入进行标准化处理,使其均值为0,方差为1。1.2 Encoder 组成成分每个Encoder子模块包含以下部分:多头自注意力层(Multi-Head Self-Attention):捕捉序列内部关系。残差连接(Residual Connection):缓解梯度消失问题。层归一化(Layer Normalization):加速训练并提高稳定性。前馈全连接网络(Position-wise Feed-Forward Networks):对特征进行非线性变换。2. 多头自注意力(Multi-Head Self-Attention)2.1 缩放点积注意力(Scaled Dot-Product Attention)输入处理:输入序列通过线性变换生成查询(Q)、键(K)、值(V)向量:$$ Q = W_Q \cdot x, \quad K = W_K \cdot x, \quad V = W_V \cdot x $$其中,$W_Q$、$W_K$、$W_V$ 是可学习的权重矩阵。注意力分数计算:通过点积计算查询和键之间的相似度:$$ \text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V $$缩放因子 $\sqrt{d_k}$ 防止点积值过大,避免softmax进入饱和区。Mask机制:在训练时,mask用于遮蔽未来信息(如解码器中);在推理时,mask用于控制输出序列的生成。2.2 多头注意力机制(Multi-Head Attention)多头计算:输入序列被映射到多个子空间(头),每个头独立计算注意力:$$ \text{head}_i = \text{Attention}(QW_i^Q, KW_i^K, VW_i^V) $$所有头的输出拼接后通过线性变换得到最终输出:$$ \text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, \dots, \text{head}_h)W_O $$优点:并行化:多个头可以并行计算,提升效率。多样性:每个头关注输入序列的不同方面,增强模型表示能力。泛化性:捕捉局部和全局依赖关系,提高模型泛化能力。2.3 自注意力机制(Self-Attention)工作原理:查询(Q)、键(K)、值(V)均来自同一输入序列。通过自注意力机制,模型能够捕捉序列内部元素之间的关系(如句子中单词的语义关联)。优点:参数效率:参数数量较少,计算复杂度为 $O(n^2d)$。全局信息:一步计算即可捕捉序列的全局依赖关系。并行化:不依赖序列顺序,适合并行计算。缺点:计算量大:序列较长时,计算复杂度较高。位置信息缺失:自注意力机制本身无法捕捉序列顺序,需通过位置编码补充。3. 交叉注意力(Cross Attention)3.1 简述概念:交叉注意力允许一个序列(查询序列)关注另一个序列(键-值序列),建立两者之间的联系。序列维度要求:查询序列和键-值序列的维度必须相同。应用场景:多模态任务(如文本与图像对齐)、序列到序列模型(如机器翻译中的编码器-解码器交互)。3.2 操作查询序列(Q):定义输出的序列长度。键-值序列(K, V):提供输入信息,用于计算与查询序列的相似度。4. Cross Attention 和 Self Attention 的区别Self Attention:查询、键、值来自同一序列。用于捕捉序列内部关系(如句子中单词的语义关联)。适用于序列内部依赖建模。Cross Attention:查询来自一个序列,键和值来自另一个序列。用于跨序列信息交互(如编码器-解码器之间的信息传递)。适用于多模态任务和序列到序列模型。5. 前馈全连接网络(Position-wise Feed-Forward Networks)结构:包含两个全连接层和一个ReLU激活函数:$$ \text{FFN}(x) = \max(0, xW_1 + b_1)W_2 + b_2 $$第一层将输入维度从 $d_{model}$ 扩展到 $d_{ff}$(如2048),第二层将其还原为 $d_{model}$。作用:增强模型的非线性表达能力。对自注意力层输出的特征进行进一步变换。6. Add & Norm残差连接(Add):输入与子层输出相加,缓解梯度消失问题。层归一化(Norm):对残差连接的输出进行归一化,加速训练并提高稳定性。总结Encoder:通过多头自注意力和前馈层处理输入序列,残差连接和层归一化提高训练稳定性。多头自注意力:通过多个注意力头并行计算,增强模型表示能力。自注意力:捕捉序列内部关系,适用于序列内部依赖建模。交叉注意力:用于不同序列之间的信息交互,适用于多模态任务。前馈网络:增强模型的非线性表达能力。Add & Norm:提高训练稳定性和收敛速度。欢迎各位大佬在评论区交流&批评指正!Datewhale组队学习参考链接Datawhale AI+X 共学活动 Fun-Transformer —— Task3:EncoderAttention Is All You Need
2025年01月21日
9 阅读
0 评论
0 点赞
2025-01-15
【2025.1 Datawhale AI+X 共学活动】Fun-Transformer —— Task1:引言 (Seq2Seq、Encoder-Decoder、Attention机制)
Datewhale组队学习为什么我们需要 Seq2Seq ?历史背景首先我们要知道,在Seq2Seq出现之前,我们是如何处理输入输出数据的在 Seq2Seq 框架提出之前,深度神经网络在图像分类等问题上取得了非常好的效果。在其擅长解决的问题中,输入和输出通常都可以表示为固定长度的向量,如果长度稍有变化,会使用填充(Padding)等操作,其目的是将序列数据转换成固定长度的格式,以便于输入到需要固定长度输入的神经网络中。比如,我们有两个序列是输入数据:[1, 1, 4]和[1, 9, 1, 9]但模型只能处理长度为6的序列那就需要先对两个序列做填充:[1, 1, 4] -> [1, 1, 4, 0, 0, 0][1, 9, 1, 9] -> [1, 9, 1, 9, 0, 0]才能正确地输入模型如果要类比的话,这种填充操作有些类似以下概念:离心机配平后才能开机(除非你喜欢艺术)PC历史上出现过一种RDRAM内存,必须一次插满偶数个内存槽。如果你只买了一条内存条,还需要再买一条“空内存条”补到主板上,以防止信号反射。(空内存条也不便宜)一把手枪弹匣有10发子弹容量,但规定必须装满才能发射,而你只有3发子弹。所以你找来了7发塑料假子弹先塞进去,再放3发真子弹,这样就能开枪了。填充操作的缺陷这种填充操作当然不是没有代价的。比如第一个序列[1, 1, 4],我明明想处理的是[1, 1, 4],为什么非要输入[1, 1, 4, 0, 0, 0]不可?这样一来,如果我本来就想输入[1, 1, 4, 0, 0, 0],那岂不是这两个完全不同的序列,在模型看起来却完全一致吗?为了解决这个问题,我们还需要引入一个掩码的概念。比如对于[1, 1, 4]:原始序列:[1, 1, 4]填充序列:[1, 1, 4, 0, 0, 0]掩码序列:[1, 1, 1, 0, 0, 0](把原始序列的内容标记为1,后续填充的位置标记为0)这样就解决了[1, 1, 4]和[1, 1, 4, 0, 0, 0]的趋同问题,因为此时他们有不一样的掩码。类比到我们前文手枪的例子:要如何区分枪里到底是10颗真子弹,还是3颗真7颗假呢?我们可以提前告诉枪手,第1、2、3颗是真的,打完就可以停手了。这个“哪些子弹是真子弹”的信息,就是掩码这样的操作逻辑一定程度上合理,但明显可以更合理——为什么一定要填充呢?所以,Seq2Seq模型应运而生。Seq2Seq 的巧妙设计前面提到的填充操作之所以会存在,是因为模型的输入和输出表示为固定长度的向量,我们为了让数据正确进到模型,就需要对输入数据做填充和掩码的预处理。而Seq2Seq模型,即“序列到序列”,提供了一种直接输入任意长度序列,并输出期望序列的方式。模型直接能够读取一个长度可变的向量,无需进行预处理。这是如何实现的呢?其实非常简单:既然传统模型只能接受固定长度向量,那我们只需要在模型中加一步将任意长度的向量都转化为一个固定长度(维度)的向量,再拿这个定长向量去进行后续操作,不就好了吗?甜菜!Seq2Seq 的缺点虽然Seq2Seq的处理方式很巧妙,但是不可否认的是,它也有一些问题没有解决。首先,Seq2Seq模型的本质,还是最终生成了一个固定长度的向量。那这个向量能表达的意思的复杂程度,就严重受限于向量的长度(或者说维度)。如何理解这个问题?比如你和朋友有个约定,用一种特殊的交流方式传递信息。你们都买了一套12色水彩笔,规定不同的颜色搭配有着不同的意思。不过,受限于只有12色,所以你们只能表达出一些简单的意思,比如今天老地方见,或者下课之后一起去打球。你们对这种交流方式极其满意。班里的两个富哥发现了你们的游戏,并决定模仿。他们一人买了一桶128色水彩笔!复杂的颜色可以让他们表达出2024年12月31日,我们开车一起去xx景点跨年,晚上不通宵到6点谁也不许走这种复杂的语义。而你和朋友看着自己的12色水彩,欲哭无泪。另一个问题,Seq2Seq模型具有良好的模块化特性,能够与卷积神经网络(CNNs)、循环神经网络(RNNs)等神经网络架构无缝集成。这本来是好事。但是,由于循环神经网络(RNN)的固有特性,Seq2Seq模型在处理长序列时存在短期记忆限制,难以有效捕获和传递长期依赖性。其实这口锅应该让RNN来背为什么会出现这种问题呢?假设你在阅读一本侦探小说,开头提到了一些重要的信息:“罪犯的外套上有一个红色的线头。”但是在 300 页后,侦探揭开真相时需要用到这个线索。如果你忘了“红色线头”这个细节,可能就无法理解故事的结局。在机器学习中,这个“开头的信息”对应的是序列的早期数据,“故事结局的推理”对应的是模型对后续步骤的预测。如果模型忘了早期的重要信息,预测结果就可能出现错误。而 RNN 是一个时间步接一个时间步地处理序列数据,每次都将当前输入和之前的记忆(隐藏状态)结合起来,然后输出一个新的隐藏状态。这样会出现以下几点问题:信息传递过程的逐步稀释:早期的信息在隐藏状态中一层层传递,像是一瓶颜料滴入流水,逐渐被稀释。如果序列太长,早期信息可能会被完全“冲淡”。梯度消失问题:模型通过反向传播优化参数时,早期时间步的梯度(学习信号)会因为连乘关系而指数级减小,导致模型难以学会捕捉这些早期时间步的信息。隐藏状态容量有限:RNN 的隐藏状态是一个固定大小的向量,比如 256 维,但长序列的信息可能需要更大的容量才能完整存储,这就导致了一种“信息拥挤”的现象。最后,Seq2Seq模型还存在一个暴露偏差(Exposure Bias)问题。在Seq2Seq模型的训练过程中,经常采用“teacher forcing”策略,即在每个时间步提供真实的输出作为解码器的输入。然而,这种训练模式与模型在推理时的自回归生成模式存在不一致性,导致模型在测试时可能无法很好地适应其自身的错误输出。如何理解这句话呢?这需要借助一个理解来比喻Seq2Seq模型的训练过程:训练阶段(Teacher Forcing)就像你在学习开车时,有一位教练坐在副驾驶,一直告诉你前方的道路情况(真实的目标输出),比如“前方是直路”“前方有个右转弯”,你只需要按教练的指令操作方向盘,几乎不会出错。推理阶段(自回归生成)当你独自开车时,突然被蒙上了眼睛(没有真实的目标输出),只能靠自己上一秒的操作和记忆来判断接下来的路该怎么走。如果前一秒稍微偏了方向,后续可能就越走越偏,甚至撞车。Encoder-Decoder模型是什么与Seq2Seq模型的关系Seq2Seq模型,指的是一种“序列到序列”的处理思路。而Encoder-Decoder模型,是实现这种思路的具体方式。如果将Seq2Seq比作“健康的生活需要多吃水果”,那Encoder-Decoder就是:星期水果种类每日摄入量周一苹果 + 猕猴桃200克 + 150克周二香蕉 + 蓝莓150克 + 100克周三橙子 + 草莓200克 + 150克周四葡萄 + 梨150克 + 200克周五芒果 + 火龙果200克 + 150克周六桃子 + 柚子150克 + 200克周日哈密瓜 + 樱桃200克 + 150克编码器Encoder在做什么简单来说,Encoder是一个RNN,里面包含很多堆叠起来的循环单元(如 LSTM 或 GRU)。当我们输入一个序列,比如 “今天吃什么好” ,输入的句子会先被分解为一个序列:输入序列:[今天, 吃, 什么, 好]在实际模型中,每个词会被转换为对应的词向量(embedding),比如:今天 → 向量 [0.5, 0.1, 0.3, ...]吃 → 向量 [0.8, 0.2, 0.4, ...]什么 → 向量 [0.3, 0.7, 0.9, ...]好 → 向量 [0.6, 0.4, 0.2, ...]这些向量是输入到编码器的内容。假设编码器是一个 两层 LSTM,它会一层一层地处理输入。每个时间步的处理过程如下:时间步 1:处理“今天”输入词向量:[今天]初始隐藏状态:随机初始化为 [0, 0, 0, ...]输出隐藏状态:h1 = [0.6, 0.2, 0.1, ...]解释:编码器初步理解了“今天”的信息,并把它存储在隐藏状态 h1 中。时间步 2:处理“吃”输入词向量:[吃]隐藏状态:h1(包含“今天”的信息)输出隐藏状态:h2 = [0.7, 0.3, 0.4, ...]解释:编码器在隐藏状态中同时记录了“今天”和“吃”的信息。时间步 3:处理“什么”输入词向量:[什么]隐藏状态:h2(包含“今天”和“吃”的信息)输出隐藏状态:h3 = [0.8, 0.5, 0.6, ...]解释:编码器进一步整合了“今天、吃、什么”的上下文信息。时间步 4:处理“好”输入词向量:[好]隐藏状态:h3(包含“今天、吃、什么”的信息)输出隐藏状态:h4 = [0.9, 0.7, 0.8, ...]解释:最终隐藏状态 h4 汇总了整个句子的上下文信息。h4,就是 “今天吃什么好” 这句话的 意思,专业一些叫 语义。解码器Decoder又做了什么假设我们在这里是想实现一个中英互译,输入是中文 “今天吃什么好” ,输出是英文翻译。解码器接收上下文向量 h4(编码器输出)并开始生成目标序列 “What should I eat today?”。解码器的生成过程是逐步进行的,每个时间步生成一个单词。这里使用 RNN(如 LSTM 或 GRU)来逐步生成目标序列。在解码器开始生成序列之前,它的参数(权重和偏置)已经被初始化(也就是预先训练好了)。权重(Weights):连接不同神经网络层的参数,它们决定了层与层之间信息的传递方式。决定了解码器如何将输入(上下文向量、前一步的输出)映射到当前的输出。偏置(Biases):在神经网络层中加入的固定值,用于调节激活函数的输出。帮助解码器对模型的输出进行调整,使其能够灵活地生成更符合目标语言规律的翻译。随后,解码器开始基于h4这个语义来生成目标序列(翻译后的英文)时间步 1:生成第一个单词 "What"解码器接收到上下文向量 h4(包含了整个中文句子的理解)作为输入。解码器根据当前输入(h4)和其隐藏状态 h0(由h4初始化来的)预测第一个单词。输出:What(解码器预测的第一个单词)。类比:翻译官开始翻译时,首先从大意(上下文向量)出发,决定翻译的第一个单词。时间步 2:生成第二个单词 "should"解码器将上一步生成的单词 What 作为当前时间步的输入。结合上下文向量 h4 和前一步的隐藏状态,解码器生成第二个单词。输出:should(解码器预测的第二个单词)。类比:翻译官根据翻译的第一个单词(What)和对整句话的理解(h4),生成第二个单词。时间步 3:生成第三个单词 "I"解码器将上一步生成的单词 should 作为当前时间步的输入。根据上下文向量 h4 和前一步的隐藏状态,解码器生成第三个单词。输出:I(解码器预测的第三个单词)。类比:翻译官继续根据上下文和前一步的翻译来生成下一个单词。时间步 4:生成第四个单词 "eat"解码器输入前一个单词 I。结合上下文向量 h4 和前一步的隐藏状态,解码器生成第四个单词。输出:eat(解码器预测的第四个单词)。类比:翻译官根据已翻译的单词 What should I 以及对中文句子的理解(h4)生成下一个单词。时间步 5:生成第五个单词 "today?"解码器输入前一个单词 eat。结合上下文向量 h4 和前一步的隐藏状态,解码器生成第五个单词。输出:today?(解码器预测的第五个单词)。类比:翻译官最终将最后的单词“今天”翻译成英文,并加上疑问符号“?”。Encoder-Decoder 的应用我们前文提到,Encoder-Decoder是Seq2Seq的具体实现。所以,只要是能转化为序列的数据,都可以作为其应用场景。视频、音频、图像、文字,任何可以被编码成序列数据的实体都能够被Encoder-Decoder结构处理。机器翻译、对话机器人、诗词生成、代码补全、文章摘要(文本 – 文本)语音识别(音频 – 文本)图像描述生成(图片 – 文本)图片转视频(图片 - 视频)给视频配音(视频 - 音频)有太多可以想到的应用场景了!Encoder-Decoder 的缺陷其实也就是上面提过的,Seq2Seq模型的最经典的缺点:固定长度的向量,无法表达出复杂的信息。就像12色的水彩笔无法表达2024年12月31日,我们开车一起去xx景点跨年,晚上不通宵到6点谁也不许走一样。这样的操作逻辑一定程度上合理,但明显可以更合理——为什么一定要固定长度呢?这就是Attention机制带来的翻天覆地的大变革了。Attention机制Attention 解决定长向量问题的方法Attention:Seq2Seq,定长的能力是有极限的,我从短暂的人生当中学到一件事,越是固定维度,就越会发现定长向量的能力是有极限的,除非超越定长。Seq2Seq:你到底想说什么。Attention:所以我不做定长辣!Seq2Seq!Attention 模型的特点是 Encoder 不再将整个输入序列编码为固定长度的“向量C” ,而是编码成一个向量(Context vector)的序列(“C1”、“C2”、“C3”)。也就是,原先是将输入数据编码成单个变量(固定长度的向量C),而现在将输入数据编码成一整个数组!数组里面存着任意数量的定长向量,但是数组长度是可变的!这样,在产生每一个输出的时候,都能够做到充分利用输入序列携带的信息。Attention 的核心工作Attention Is All You Need Ура!!!!在传统的 Seq2Seq 模型中,编码器会将整个输入序列压缩成一个单一的 上下文向量,这个向量包含了输入序列的全部信息。解码器使用这个上下文向量生成目标序列。然而,上下文向量在编码长序列时存在信息丢失的问题,因为它只能携带一个固定长度的摘要,随着输入长度的增加,信息会逐渐被压缩,导致模型难以捕获远距离的依赖关系。Attention 机制的引入解决了这一问题,它允许解码器在生成每个目标单词时,都能够动态地关注输入序列的不同部分,而不是依赖于一个单一的上下文向量。在 Seq2Seq 中加入 Attention 后,模型的工作原理发生了变化:编码器的输出编码器在每个时间步都会生成一个隐藏状态(h1, h2, h3,...),这些隐藏状态反映了输入序列中每个位置的信息。解码器的 Attention 权重解码器生成每个目标单词时,首先会为输入序列的每个位置计算一个 注意力权重(Attention weight)。这个权重决定了当前解码器生成单词时,应该多大程度地关注输入序列中的哪个部分。权重计算:解码器的当前状态与编码器每个时间步的隐藏状态进行比较,计算它们之间的相似度。这个相似度即为 Attention 权重,表示当前目标单词与输入序列各个部分的关联程度。加权求和生成上下文向量解码器根据计算得到的 Attention 权重,对编码器的所有隐藏状态进行加权求和,得到一个新的 上下文向量。这个上下文向量是动态的,依赖于目标序列当前生成的单词,因此它包含了关于输入序列的更丰富、更具体的信息。生成目标单词解码器将这个新的上下文向量与当前的隐藏状态结合,生成目标序列中的下一个单词。这很像人类看图片的逻辑,当我们看下面这张熊猫图片的时候,我们将注意力集中在了图片的焦点上(熊猫)。在图片上,我们的视线聚焦在熊猫及其攀爬的树干,熊猫和树干在这个区域内非常清晰,在此之外的区域则相对模糊,表示其他景物(例如熊猫背后的山林)没有被“注意力”所关注。这样的图像就能够直观地展示注意力机制是如何在众多信息中挑选出关键部分进行重点处理的。 如上面所说的,我们的视觉系统就是一种 Attention机制,将有限的注意力集中在重点信息上,从而节省资源,快速获得最有效的信息。Attention 的3大优点之所以要引入 Attention 机制,主要是3个原因:参数少:模型复杂度跟 CNN、RNN 相比,复杂度更小,参数也更少。所以对算力的要求也就更小。速度快:Attention 解决了 RNN 不能并行计算的问题。Attention机制每一步计算不依赖于上一步的计算结果,因此可以和CNN一样并行处理。效果好:在 Attention 机制引入之前,有一个问题大家一直很苦恼:长距离的信息会被弱化,就好像记忆能力弱的人,记不住过去的事情是一样的。欢迎各位大佬在评论区交流&批评指正!Datewhale组队学习参考链接Datawhale AI+X 共学活动 Fun-Transformer —— Task1:引言Attention Is All You Need
2025年01月15日
13 阅读
0 评论
1 点赞
1
2