王晓华DeepSeek开发AI应用入门书《DeepSeek原生应用与智能体开发实践》全文分享~持续更新-优快云博客
DeepSeek原生应用与智能体开发实践(人工智能技术丛书)【行情 报价 价格 评测】-京东
目录
10.4.2 Patch Embedding与Position Embedding
DeepSeek中的混合专家(MoE)是其核心技术之一,该技术通过构建多个专业化的子模型(即“专家”)来处理不同的任务或数据子集。每个专家模型都专注于处理特定类型的输入数据,从而实现对复杂任务的高效处理。MoE模型(即MoE架构)还包含一个门控网络,它根据输入数据的特征动态选择最合适的专家模型进行处理,这种动态路由机制确保了模型在处理各种输入数据时能够自适应地选择合适的专家,提高了整体性能和效率。MoE模型如图10-1所示。

图10-1 MoE模型
我们将探讨如何把MoE模型巧妙地融入注意力机制中,具体做法是用它来替换传统的前馈层。这一创新设计旨在充分利用MoE模型在处理多样化信息时的优势,从而进一步提升注意力机制的效能。我们相信,通过这种方式的改进,注意力机制将能够在文本生成等任务中发挥出更加强大的潜力,为自然语言处理领域带来新的突破。
10.1 MoE 架 构
前馈层(Feed-forward Layer)在神经网络中扮演着数据处理与特征转换的关键角色。它通过线性变换,将输入数据与权重矩阵相乘并加上偏置项,进而提取出输入数据的特征。随后,通过非线性激活函数对线性变换的结果进行处理,引入非线性特性,使得神经网络能够学习和逼近复杂的函数关系。
这种前馈层的设计保证了数据在神经网络中的单向流动,从输入层逐步传递到输出层,无反馈或循环结构,从而简化了计算过程并提高了处理效率。前馈层广泛应用于各类神经网络架构中,对于实现特征提取、信息转换以及最终的任务输出都至关重要,是构建高效、准确的神经网络模型的基础组件。
10.1.1 MoE模型的基本结构
MoE模型(下文也用MoE来代表MoE模型)是一种深度学习架构,它通过集成多个专家模型(即子模型)来提升整体模型的预测性能和效率。这种架构主要由两部分组成:门控网络(路由器机制)和多个专家网络。每个专家网络专门处理输入数据的一个子集或特定特征,而门控网络则负责根据输入动态地选择合适的专家模型进行处理,他们之间使用专门的负载平衡与优化对资源进行调配。
MoE模型的优势包括提高模型的灵活性、性能以及计算资源的高效利用,尤其在处理复杂或多样化任务时表现突出。然而,设计和训练MoE模型也面临一些挑战,如平衡专家的数量和质量,以及优化门控网络的决策能力。MoE模型在自然语言处理、计算机视觉和推荐系统等领域有着广泛的应用 前景。
MoE模型是一种深度学习中的集成学习方法,它通过组合多个专家模型(即子模型)来形成一个整体模型,旨在实现高效计算与优异性能的平衡。MoE模型的基本结构如图10-2所示。

图10-2 MoE模型的基本结构
其基本结构可以归纳为以下几个关键要点。
1)专家网络(Expert Networks)
- MoE模型包含多个专家网络,每个专家网络都是一个独立的模型,负责处理某个特定的子 任务。
- 这些专家网络可以是小型的多层感知机(MLP)或者更复杂的结构,如Transformers等,各自在其擅长的领域内进行训练和优化。
2)门控网络(Gating Network)
- 门控网络是MoE模型中的另一个关键组件,负责根据输入数据的特征动态地决定哪个专家模型应该被激活以生成最佳预测。
- 门控网络通常输出一个概率分布,表示每个专家模型被选中的概率。这个概率分布可以通过Softmax函数来计算,确保所有专家的权重之和为1。
3)稀疏性(Sparsity)
- 在MoE模型中,对于给定的输入,通常只有少数几个专家模型会被激活,这种特性使得MoE模型具有很高的稀疏性。
- 稀疏性带来了计算效率的提升,因为只有特定的专家模型对当前输入进行处理,减少了不必要的计算开销。
4)输出组合(Output Combination)
- 被激活的专家模型会各自产生输出,这些输出随后会被加权求和,得到MoE模型的最终输出结果。
- 加权求和的过程根据门控网络输出的概率分布来进行,确保每个专家的贡献与其被选中的概率成正比。
MoE模型通过动态地选择和组合多个专家模型来处理输入数据,实现了高效计算与优异性能的平衡。这种结构使得MoE模型能够根据不同的任务和数据,灵活地调整其计算复杂度和模型容量,从而在各种深度学习应用场景中展现出强大的潜力。
10.1.2 MoE模型中的“专家”与“调控”代码实现
MoE模型作为一种深度学习结构,其核心思想在于通过集成多个专门化的网络组件(即“专家”)来提升模型的表示能力和泛化性能。MoE的两个主要组成部分(专家和门控网络)相互协作,共同实现这一目标。
1. 专家
首先是专家部分。在MoE架构中,传统的前馈神经网络层被扩展为一组可选择的专家网络。这些专家通常也是由前馈神经网络构成,但每个专家都专注于处理特定类型的数据或特征。这种设计使得模型能够在不同的情境下调用最合适的专家,从而更精确地捕捉数据的复杂性和多样性。通过专家的专门化学习,MoE能够在训练过程中更有效地分配计算资源,提高模型的效率和性能。
我们实现了一个专家层,代码如下所示。
class Expert(torch.nn.Module):
def __init__(self, n_embd):
super().__init__()
self.net = torch.nn.Sequential(
torch.nn.Linear(n_embd, 4 * n_embd),
torch.nn.ReLU(),
torch.nn.Linear(4 * n_embd, n_embd),
torch.nn.Dropout(0.1),
)
def forward(self, x):
return self.net(x)
2. 门控网络
门控网络无疑是MoE架构中的核心组件,其重要性在于它掌控着在推理和训练过程中专家的选择权。简而言之,门控网络充当着智能调度员的角色,根据输入数据的特性,精准地调配各个专家的参与程度。
在最基础的操作层面,门控网络的运作可以概括为以下步骤:
首先,我们接收输入数据(X),这个数据可能是一个特征向量,包含了待处理任务的关键信息。如图10-3所示。
接下来,我们将这个输入数据与路由器的权重矩阵(W)进行矩阵乘法运算。这一过程实质上是在对输入数据进行线性变换,目的是提取出对于后续专家选择至关重要的特征。如图10-4所示。

图10-3 门控网络

图10-4 门控网络的调控
通过这种计算方式,门控网络能够为每个专家生成一个相应的得分或者称为“门控值”。这些门控值反映了在当前输入情境下,各个专家对于任务处理的适合程度。基于这些门控值,我们可以进一步应用Softmax函数等策略,来确定每个专家在最终输出中的贡献权重,从而实现MoE架构中灵活且高效的专家组合与调用。
在最后阶段,我们将利用门控网络(即路由器)生成的得分,与每个专家的输出进行结合,以选出最合适的专家贡献。具体来说,我们将每个专家的输出与其对应的门控得分相乘,这一步骤实质上是在对每个专家的预测结果进行加权。加权后的专家输出随后被相加,形成MoE模型的最终输出,如图10-5所示。
现在,让我们将整个过程综合起来,以更全面地理解输入数据在门控路由器和专家之间的流动路径,如图10-6所示。

图10-5 门控网络与专家模型的计算

图10-6 门控路由与专家模型的选择
- 输入数据的接收:首先,模型接收输入数据,这些数据可能是文本、图像或其他类型的特征向量,包含待处理任务的关键信息。
- 路由器的处理:输入数据随后被传递给路由器(即门控网络)。在这里,数据与路由器的权重矩阵进行矩阵乘法运算,生成一组得分。这些得分反映了在当前输入情境下,各个专家对于任务处理的适合程度。
- 专家的激活与输出:基于路由器的得分,一部分专家会被激活,而其余专家则保持休眠状态。被激活的专家会独立地处理输入数据,并生成各自的预测结果或输出向量。
- 输出的加权与合并:每个被激活的专家的输出都会与其对应的门控得分相乘,进行加权处理。随后,这些加权后的输出被相加,形成MoE模型的最终预测结果。
通过以上方式,MoE架构能够在不同的输入情境下动态地选择合适的专家组合,以实现更高效、更精确的数据处理与预测。我们实现了对于门控机制与专家模型的稀疏MoE,其代码如下所示。
import torch
# 定义一个专家模型,它是一个简单的全连接网络
class Expert(torch.nn.Module):
def __init__(self, n_embd):
super().__init__()
self.net = torch.nn.Sequential(
torch.nn.Linear(n_embd, 4 * n_embd), # 线性层,将维度扩大4倍
torch.nn.ReLU(), # ReLU激活函数
torch.nn.Linear(4 * n_embd, n_embd), # 线性层,恢复原始维度
torch.nn.Dropout(0.1), # Dropout层,防止过拟合
)
def forward(self, x):
return self.net(x) # 前向传播
# 定义一个Top-K路由器,用于选择前K个最佳专家
class TopkRouter(torch.nn.Module):
def __init__(self, n_embed, num_experts, top_k):
super(TopkRouter, self).__init__()
self.top_k = top_k # 选择前K个专家
self.linear = torch.nn.Linear(n_embed, num_experts) # 线性层,输出为专家数量
def forward(self, mh_output):
logits = self.linear(mh_output) # 通过线性层得到每个专家的得分
# 选择得分最高的前K个专家及其索引
top_k_logits, indices = logits.topk(self.top_k, dim=-1)
# 创建一个与logits形状相同且全为-inf的张量
zeros = torch.full_like(logits, float('-inf'))
# 将前K个专家的得分填充到zeros中对应的位置
sparse_logits = zeros.scatter(-1, indices, top_k_logits)
# 对sparse_logits进行softmax操作,使得得分转换为概率分布
router_output = torch.nn.functional.softmax(sparse_logits, dim=-1)
return router_output, indices # 返回专家的概率分布和索引
# 定义一个稀疏的混合专家(MoE)模型
class SparseMoE(torch.nn.Module):
def __init__(self, n_embed, num_experts, top_k):
super(SparseMoE, self).__init__()
self.router = TopkRouter(n_embed, num_experts, top_k) # 路由器,用于选择专家
# 创建一个专家列表,每个专家都是一个Expert实例
self.experts = torch.nn.ModuleList([Expert(n_embed) for _ in range(num_experts)])
self.top_k = top_k # 选择前K个专家
def forward(self, x):
# 通过路由器得到专家的概率分布和索引
gating_output, indices = self.router(x)
final_output = torch.zeros_like(x) # 初始化最终输出为与输入形状相同的全零张量
# 将输入和路由器的输出展平,以便后续处理
flat_x = x.view(-1, x.size(-1))
flat_gating_output = gating_output.view(-1, gating_output.size(-1))
# 遍历每个专家,根据其概率分布对输入进行处理
for i, expert in enumerate(self.experts):
# 找出当前专家是前K个专家的token
expert_mask = (indices == i).any(dim=-1)
flat_mask = expert_mask.view(-1) # 展平操作
# 如果当前专家对至少一个token是前K个专家之一
if flat_mask.any():
# 选出这些token的输入
expert_input = flat_x[flat_mask]
# 将这些token输入给当前专家进行处理
expert_output = expert(expert_input)
# 获取当前专家对这些token的概率分布
gating_scores = flat_gating_output[flat_mask, i].unsqueeze(1)
# 根据概率分布对专家的输出进行加权
weighted_output = expert_output * gating_scores
# 将加权后的输出累加到最终输出中对应的位置
final_output[expert_mask] += weighted_output.squeeze(1)
return final_output # 返回最终输出
上面代码实现了一个稀疏的混合专家(Sparse Mixture of Experts,Sparse MoE)模型,其中包含一个路由器和多个专家。路由器负责根据输入选择前K个最佳专家,而每个专家则是一个简单的全连接网络。最终输出是所有选定专家的加权输出之和。
10.1.3 使用MoE模型还是经典的前馈层
MoE模型通过动态地选择与组合多个专家模型以处理输入数据,巧妙地实现了高效计算与卓越性能的平衡。此结构赋予了MoE模型独特的灵活性,使其能根据不同的任务需求和数据特性,智能地调整计算复杂度与模型容量。这种自适应的特性让MoE模型在多样化的深度学习应用场景中展现出令人瞩目的潜力。
我们期望以专家模型作为前馈层的替代,这样的设计选择带来了多重优势。
首先,通过引入专家模型,我们能够显著提升网络的表达能力和泛化性能。每个专家可以专注于处理特定类型的数据或任务,从而实现更精细化的特征学习和更高效的参数利用。
其次,这种替代方案有助于实现更灵活的资源分配。在传统的前馈层中,计算资源是均匀分配的,而在MoE模型中,我们可以根据实际需要动态地激活不同的专家组合。这种动态性不仅优化了计算效率,还使得模型能够在处理复杂任务时展现出更强的应对能力。
使用专家模型替代前馈层还有助于简化模型设计和训练过程。通过模块化的设计思路,我们可以更容易地构建、扩展和维护深度学习模型。同时,专家之间的独立性也为并行计算和分布式训练提供了便利,从而进一步加速了深度学习应用的开发进程。
在具体的模型架构选择过程中,MoE系统与经典前馈神经网络在硬件资源利用效率上存在显著差异。具体而言,MoE通过引入条件计算机制实现了参数规模的大幅扩展,但其复杂的路由策略和并行专家网络设计导致硬件资源需求激增。在推理阶段,受限于输入数据的动态路由特性,仅部分专家网络会被激活,这使得GPU/TPU等加速器的计算单元无法处于满负荷状态,整体硬件利用率较经典前馈层降低约30%~40%。相比之下,经典前馈网络凭借高度优化的矩阵运算结构和参数共享机制,在相同硬件条件下能实现更高的计算密度,尤其在边缘设备等资源受限的场景中,其紧凑的架构设计与硬件特性形成更优匹配,资源利用效率可提升多倍。
这种效率差异进一步影响了模型部署策略:在追求极致性能的数据中心场景或者使用专业计算显卡(例如H100计算卡),开发者需在MoE的模型容量优势与硬件集群规模之间寻求平衡点,通过专家并行和流水线调度等技术缓解利用率问题;而在移动端/物联网等算力敏感场景,经典前馈结构的硬件亲和性使其成为更稳妥的选择(例如家用5090或者4090系列显卡)。值得注意的是,最新研究开始探索动态MoE架构,通过可重构专家网络和自适应路由算法,使系统在保持模型容量的同时,能根据硬件负载动态调整激活的专家数量,这种混合设计有望在未来弥合两种架构的效率鸿沟。
10.2 基于MoE模型的情感分类实战
在上一节中,我们已经对MoE模型做了详尽的介绍。接下来,我们将进入实战环节,利用MoE模型来完成评论情感分类任务。我们将以前面章节中的情感分类任务为蓝本,借鉴其框架与流程,作为本节实战内容的基础。
在模型构建环节,我们将采取一个创新性的举措:将原本的注意力层替换为MoE层。这一调整的目的是借助MoE层所特有的机制,来提升模型在处理复杂情感分类任务时的表现与准确性。
10.2.1 基于MoE模型的评论情感分类实战
为了完成这次基于MoE模型的评论情感分类实战,我们将遵循第4章中情感分类任务的设计思路,对训练主体部分进行构建。在模型设计方面,我们只需将注意力层替换为MoE层,即可开始我们的实战演练。单独的MoE层结构如图10-7所示。

图10-7 单独的MoE层结构
通过这样的调整,我们期待MoE模型能够在评论情感分类任务上展现出卓越的性能。完整训练代码如下所示。
import torch
from 第十章 import baseMOE
from tqdm import tqdm
class Classifier(torch.nn.Module):
def __init__(self):
super(Classifier, self).__init__()
self.embedding_layer = torch.nn.Embedding(3120, 312)
self.encoder = torch.nn.ModuleList([baseMOE.SparseMoE(312,4,2) for _ in range(3)])
self.logits = torch.nn.Linear(14976, 2)
def forward(self, x):
embedding = self.embedding_layer(x)
for layer in self.encoder:
embedding = layer(embedding)
embedding = torch.nn.Flatten()(embedding)
logits = self.logits(embedding)
return logits
from torch.utils.data import DataLoader
import get_dataset
BATCH_SIZE = 128
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
model = Classifier().to(DEVICE)
train_dataset = get_dataset.TextSamplerDataset(get_dataset.token_list, get_dataset.label_list)
train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)
optimizer = torch.optim.AdamW(model.parameters(), lr=2e-4)
lr_scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=1200, eta_min=2e-5, last_epoch=-1)
criterion = torch.nn.CrossEntropyLoss(ignore_index=-100)
# 假设验证数据集已经准备好
val_dataset = get_dataset.TextSamplerDataset(get_dataset.val_token_list, get_dataset.val_label_list)
val_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False)
for epoch in range(12):
# 训练阶段
model.train()
pbar = tqdm(train_loader, total=len(train_loader))
for token_inp, label_inp in pbar:
token_inp = token_inp.to(DEVICE)
label_inp = label_inp.to(DEVICE).long()
logits = model(token_inp)
loss = criterion(logits, label_inp)
optimizer.zero_grad()
loss.backward()
optimizer.step()
lr_scheduler.step() # 执行优化器
pbar.set_description(f"epoch:{epoch + 1}, train_loss:{loss.item():.5f}, lr:{lr_scheduler.get_last_lr()[0] * 1000:.5f}")
# 验证阶段
model.eval()
total_val_loss = 0
correct = 0
with torch.no_grad():
for token_inp, label_inp in val_loader:
token_inp = token_inp.to(DEVICE)
label_inp = label_inp.to(DEVICE).long()
logits = model(token_inp)
loss = criterion(logits, label_inp)
total_val_loss += loss.item()
# 计算准确率
_, predicted = torch.max(logits, 1)
correct += (predicted == label_inp).sum().item()
avg_val_loss = total_val_loss / len(val_loader)
val_accuracy = correct / len(val_dataset)
print(f'Epoch {epoch + 1}, Validation Loss: {avg_val_loss:.5f}, Validation Accuracy: {val_accuracy:.5f}')
此时经过12轮的模型训练,最终结果如下:
...
Epoch 10, Validation Loss: 0.47300, Validation Accuracy: 0.80937
Epoch 11, Validation Loss: 0.53913, Validation Accuracy: 0.80312
Epoch 12, Validation Loss: 0.55707, Validation Accuracy: 0.81250
可以看到,此时的结果提高约为两个百分点,具体请读者运行代码自行验证。
另外,值得关注的是,在MoE的框架下,MoE层通常可以划分为两种类型:稀疏专家混合模型(Sparse Mixture of Experts)与密集专家混合模型(Dense Mixture of Experts)。它们的对比如图10-8所示。

图10-8 稀疏MoE与密集MoE的对比
这两种模型都依赖于路由器机制来选择合适的专家进行处理,但它们在专家的选择上有所不同。稀疏MoE模型在每次前向传播时仅激活少数几个专家,这种策略有助于提升计算效率和模型的专注度,使得每个被选中的专家都能充分发挥其专长。相比之下,密集MoE模型则会考虑所有的专家,但会根据输入的不同以不同的权重分布来选择各个专家。这种全面考虑的策略虽然计算成本相对较高,但能够更全面地整合各个专家的意见,从而在处理某些复杂任务中展现出更优越的性能。
10.2.2 MoE模型中负载平衡的实现
在上面的代码实现中,针对专家的负载均衡,我们使用了TopkRouter进行设置,这是一种对路由器进行负载平衡的方法,其使用了一个简单的扩展策略,称为KeepTopK。这种策略的核心思想是,通过动态选择负载最低或性能最优的K个专家节点来处理请求,从而确保系统的稳定性和响应速度。
具体来说,KeepTopK策略会实时监控各个专家节点的负载情况,并根据预设的评估标准,如响应时间、CPU使用率、内存占用率等,对节点进行排序。当新的请求到达时,TopkRouter会根据当前的排序结果,将请求路由到性能最佳的K个节点之一。这种方法不仅能够有效平衡负载,减少某些节点的过载风险,还能确保用户请求得到快速且可靠的处理。整体计算结果说明如下。
(1)计算所有的输出权重,如图10-9所示。

图10-9 加载了KeepTopK运算的输出

(3)将这些专家权重设为-∞时,Softmax操作后的输出概率将变为 0,如图10-11所示。
图10-10 去除额外的专家

图10-11 经过Softmax计算后输出概率值为0
(4)此时通过KeepTopK策略,会将每个token路由到若干选定的专家。这种方法被称为Token选择策略(Token Choice),如图10-12所示,它允许一个给定的token被路由到一个专家(图10-12左图),或者被分配给多个专家(图10-12右图)。
而对于路由器(Router)或门控网络(Gate Network),这一组件在MoE架构中扮演着至关重要的角色,负责决定哪些数据(通常以token的形式)应该被发送到哪些专家进行处理。路由器网络根据输入数据的特性,动态地生成一个分配方案,确保每个token都能被路由到最合适的专家。这种动态路由机制使得MoE能够在处理不同输入时展现出高度的灵活性和适应性。通过优化路由器网络的设计,MoE可以在保持计算效率的同时,最大化地利用各个专家的专长,从而提升模型的整体性能。

图10-12 token选择策略
选择单个专家可以提升我们在计算时的速度。而选择多个专家可以对各个专家的贡献进行加权,并将其整合起来,从而提高一定的准确性。至于选择哪种方式还需要在实际中进行权衡和处理。
在混合专家模型的实际使用中,我们本质上追求的是一种均衡状态,即避免所有token都集中于某一组“热门”的Expert上。为了实现这一目标,我们需要在系统中引入一种机制,以确保token的分配既不过于集中,也不过于分散。因此,我们采用了一种策略,那就是在来自门控线性层的logits上添加标准正态噪声,其代码如下所示。
class NoisyTopkRouter(torch.nn.Module):
def __init__(self, n_embed, num_experts, top_k):
super(NoisyTopkRouter, self).__init__()
self.top_k = top_k
self.topkroute_linear = torch.nn.Linear(n_embed, num_experts)
# add noise
self.noise_linear = torch.nn.Linear(n_embed, num_experts)
def forward(self, mh_output):
# mh_ouput is the output tensor from multihead self attention block
logits = self.topkroute_linear(mh_output)
# Noise logits
noise_logits = self.noise_linear(mh_output)
# Adding scaled unit gaussian noise to the logits
noise = torch.randn_like(logits) * torch.nn.functional.softplus(noise_logits)
noisy_logits = logits + noise
top_k_logits, indices = noisy_logits.topk(self.top_k, dim=-1)
zeros = torch.full_like(noisy_logits, float('-inf'))
sparse_logits = zeros.scatter(-1, indices, top_k_logits)
router_output = torch.nn.functional.softmax(sparse_logits, dim=-1)
return router_output, indices
在具体使用上,我们可以直接替换稀疏注意力层的对应代码,替换后的新的稀疏混合专家模型如下所示。
# 定义一个稀疏的混合专家(MoE)模型
class SparseMoE(torch.nn.Module):
def __init__(self, n_embed, num_experts, top_k):
super(SparseMoE, self).__init__()
self.router = NoisyTopkRouter(n_embed, num_experts, top_k) # 路由器,用于选择专家
# 创建一个专家列表,每个专家都是一个Expert实例
self.experts = torch.nn.ModuleList([Expert(n_embed) for _ in range(num_experts)])
self.top_k = top_k # 选择前K个专家
def forward(self, x):
# 通过路由器得到专家的概率分布和索引
gating_output, indices = self.router(x)
final_output = torch.zeros_like(x) # 初始化最终输出为与输入形状相同的全零张量
# 将输入和路由器的输出展平,以便后续处理
flat_x = x.view(-1, x.size(-1))
flat_gating_output = gating_output.view(-1, gating_output.size(-1))
# 遍历每个专家,根据其概率分布对输入进行处理
for i, expert in enumerate(self.experts):
# 找出当前专家是前K个专家的token
expert_mask = (indices == i).any(dim=-1)
flat_mask = expert_mask.view(-1) # 展平操作
# 如果当前专家对至少一个token是前K个专家之一
if flat_mask.any():
# 选出这些token的输入
expert_input = flat_x[flat_mask]
# 将这些token输入给当前专家进行处理
expert_output = expert(expert_input)
# 获取当前专家对这些token的概率分布
gating_scores = flat_gating_output[flat_mask, i].unsqueeze(1)
# 根据概率分布对专家的输出进行加权
weighted_output = expert_output * gating_scores
# 将加权后的输出累加到最终输出中对应的位置
final_output[expert_mask] += weighted_output.squeeze(1)
return final_output # 返回最终输出
读者可以自行学习验证。
这种噪声的引入,实际上为模型注入了一种随机性,使得token在选择Expert时不再完全依赖于原始的logits值。通过这种方式,我们可以有效地打破可能存在的“热门”Expert的垄断地位,让其他相对“冷门”的Expert也有机会得到token的分配。这不仅提高了模型的整体鲁棒性,还有助于防止模型在训练过程中出现过早收敛或陷入局部最优解的问题。
此外,KeepTopK策略还具备灵活性和可扩展性,可以根据实际运行情况进行动态调整。例如,在高峰期,系统可以自动增加K的值,以容纳更多的处理节点,从而提升模型整体的处理能力。而在低峰期,则可以减小K值,以节省资源并提高能效。
10.3 加载MoE架构的注意力模型
MoE模型的主要作用是作为前馈神经网络层(Feedforward Neural Network,FFNN)的替代品。通过引入这种替代方案,我们获得了诸多显著的好处。
首先,MoE通过集成多个“专家”子模型,显著增强了模型的表达能力和容量。相较于单一的前馈层,这种结构能够更细致地捕捉数据的复杂特征,从而在处理高度非线性问题时展现出更优越的性能。
其次,MoE模型通过条件计算实现了高效的资源利用。在传统的FFNN中,所有神经元在每次前向传播时都会被激活,这可能导致计算资源的浪费。而在MoE中,只有部分专家会被选择性地激活,这种稀疏性不仅降低了计算成本,还使得模型能够更专注于当前任务相关的特征,如图10-13所示。

图10-13 针对不同部位判定的MoE层
此外,MoE还具备出色的可扩展性。随着数据量的增长和任务的复杂化,我们可以通过增加专家数量来轻松扩展模型,而无须从头开始训练整个网络。这种灵活性使得MoE能够轻松应对不断变化的应用场景和需求。
最后,通过引入专家之间的竞争机制,MoE还能够促进模型内部的多样性和鲁棒性。不同的专家可以学习到不同的数据特征和表示方式,从而增强了模型对噪声和异常值的抗干扰能力。这种特性使得MoE在处理实际应用中的复杂数据时表现出更高的稳定性和可靠性。
10.3.1 注意力机制中的前馈层不足
在前面探讨注意力机制的章节中,我们曾经提及,在注意力层之后通常会紧接一个标准的前馈神经网络(FFNN)。这个前馈神经网络在模型中的作用举足轻重,它使模型能够充分利用注意力机制所生成的上下文信息,并将这些信息进一步转化和提炼,从而捕捉到数据中更为复杂和深层次的关系。注意力层之后接上FFNN的架构如图10-14所示。

图10-14 注意力层之后接上FFNN的架构
然而,随着模型对数据处理需求的提升,FFNN的规模也呈现出迅速增长的趋势。为了有效地学习并表达这些复杂的数据关系,FFNN通常需要对接收到的输入进行显著的维度扩展。这意味着,在实践中,我们需要运用一个庞大的全连接层(Fully Connected Layer,FCL)来完成这一关键任务。
具体来看,传统的前馈层是由一系列串联的全连接层所构成,这些层级结构通过调整内部神经元的数量来实现对输入信息的逐层抽象与处理。传统的前馈层如图10-15所示。

图10-15 传统的前馈层
这种设计虽然在一定程度上提升了模型的表达能力,但同时也带来了计算资源和存储空间的挑战。因此,在构建和应用这类模型时,我们需要精心权衡其性能与资源消耗之间的平衡。
在传统的注意力模型中,FFNN被称为密集模型(Dense Model),因为它的所有参数(包括权重和偏置项)都会被激活。所有参数都被用于计算输出,没有任何部分被遗弃。如果我们仔细观察密集模型,可以发现输入在某种程度上激活了所有参数。密集模型如图10-16所示。
相比之下,稀疏模型(Sparse Model)在运行时仅激活其总参数集中的一小部分,这种特性使其与专家混合模型紧密相连。为了更直观地阐述这一点,我们可以设想将一个密集模型(即传统意义上的全连接模型)分解为多个独立的部分,这些部分在MoE的框架中被称作“专家”。每个专家都负责处理特定类型的数据或特征,并在训练过程中专注于学习其专长领域内的知识。稀疏模型如图10-17所示。

图10-16 密集模型

图10-17 稀疏模型
稀疏性的概念采用了条件计算的思想。在传统的稠密模型中,所有的参数都会对所有输入数据进行处理。相比之下,稀疏性允许我们仅针对整个系统的某些特定部分执行计算。这意味着并非所有参数都会在处理每个输入时被激活或使用,而是根据输入的特定特征或需求,只有部分参数集合被调用和运行。
在模型运行时,我们不再像密集模型那样同时激活所有参数,而是根据输入数据的特性动态地选择并激活一部分专家。这种机制使得MoE能够在不同情境下调用最合适的专家组合,从而更精确地捕捉数据的复杂性和多样性。同时,由于每次只激活部分专家,MoE在计算效率和资源消耗方面相较于密集模型具有显著优势。这种可选择的稀疏专家模型如图10-18所示。
在图10-19和图10-20中,根据颜色的深浅可以看到,对于输入的文本内容,其中的每个词汇或者字母(根据token划分)都被不同的专家所关注。

图10-18 可选择的稀疏专家模型

图10-19 不同专家所关注的部分

图10-20 每个注意力中的专家
这种灵活性使得MoE模型能够更精细地捕捉和响应数据中的复杂模式,从而提升了模型的整体性能和泛化能力。同时,由于每个专家都专注于其特定领域的知识表示,这种结构也有助于实现模型内部的知识分工和模块化,进一步提高了模型的解释性和可维护性。
10.3.2 MoE可作为前馈层的替代
在前面的工作中,我们已经成功设计并实现了MoE模型的程序代码,更进一步地,我们利用这个独立且完整的MoE模型完成了情感分类的实战演练。通过实战结果,我们清晰地观察到,MoE模型本身便具备出色的特征抽取能力,可以作为一个高效的特征抽取层来使用。在情感分类任务中,MoE模型展现出了良好的性能,准确地完成了情感分类任务。
传统的注意力机制中的前馈层,在很大程度上,其核心功能和作用可由特定的MoE模型来有效替代。可替代注意力模型中前馈层的MoE架构如图10-21所示。

图10-21 可替代注意力模型中前馈层的MoE架构
在上一小节中,我们已经对MoE模型做了详细的阐述,尽管可以将专家模型理解为被分解的密集模型的隐藏层,但事实上这些专家模型本身往往就是功能完备的前馈神经网络层(FFNN),使用专家模型作为前馈层,如图10-22、图10-23所示。

图10-22 可作为FFNN的完整MoE模型

图10-23 使用专家模型作为前馈层
一般注意力模型在使用过程中,由于存在多个模块的叠加,给定的文本在生成之前会依次通过这些不同模块中的多个专家。不同的token在传递过程中可能会被不同的专家所处理,这导致了在模型内部形成了多样化的处理“路径”。叠加注意力模块中的专家路径示意如图10-24所示。

图10-24 叠加注意力模块中的专家路径
如果我们更新对解码器块的视觉呈现,现在它将展现出一个包含多个FFNN的架构,其中每个FFNN都代表一个特定的“专家”。这种设计意味着,在推理阶段,解码器块内拥有多个可供选择的FFNN专家,模型能够根据输入数据的特性动态地选择合适的专家来处理信息。
而不同的专家在混合专家模型(MoE)中选择关注的token各不相同,这种差异性使得随着输入文本的变化,模型会动态地选择不同的“路径”进行处理。换言之,每个专家都专注于捕获输入数据的特定特征或模式,从而根据输入内容的不同,整体模型的关注路径也会相应地调整,如图10-25所示。
我们可以观察到,这种随着输入内容变化而变化的关注路径,实际上为模型提供了一种灵活且动态的特征选择机制。这种机制使得模型能够在处理不同输入时,更加精准地聚焦于关键信息,从而提升了模型的泛化能力和处理复杂任务的能力。

图10-25 输入路径不同选择的专家不同
更重要的是,这种动态选择关注点的特性,正是我们所期望的。它意味着模型可以根据具体任务需求和输入数据的特性,自适应地调整其关注重点,以实现更加高效和准确的特征抽取和分类。这种灵活性不仅增强了模型的实用性,也为深度学习领域的发展注入了新的活力。
10.3.3 结合MoE的注意力机制
接下来,我们需要完成结合MoE的注意力机制,在这里我们仅仅通过替换Encoder部分的原始FFNN层,即可完成MoE的替换,代码如下所示。
from 第十章 import baseMOE
class EncoderBlock(torch.nn.Module):
def __init__(self, d_model, num_heads):
super(EncoderBlock, self).__init__()
self.d_model = d_model
self.num_heads = num_heads
self.attention_norm = torch.nn.RMSNorm(d_model)
self.self_attention = attention_moudle.MultiHeadAttention(d_model, num_heads)
#self.ffn = feedforward_layer.Swiglu(d_model)
#读者可以替换FFNN部分完成
self.ffn = baseMOE.SparseMoE(d_model,4,2)
def forward(self, embedding):
residual = embedding
embedding = self.attention_norm(embedding)
embedding = self.self_attention(embedding)
embedding = self.ffn(embedding)
return embedding + residual
读者可以自行尝试运行代码。
10.4 基于MoE与自注意力的图像分类
在前一节中,我们详细阐述了整合MoE的注意力模型,探讨了其如何提升模型的表示能力和泛化性能,并且完成了基于序列的评论情感分类实战。在本节中,我们将进一步探讨基于MoE与注意力机制的图像分类方法(准确率有极大提高),揭示这一组合如何在图像识别领域发挥重要作用。
在图像分类任务中,模型的挑战在于如何准确捕捉图像中的关键信息,并据此做出正确的类别判断。MoE与注意力机制的结合,为这一问题提供了有效的解决方案。具体来说,通过引入MoE,我们可以将多个专家模型集成到一个框架中,每个专家模型负责处理图像中的特定部分或特征。这种分而治之的策略,使得模型能够更精细地理解图像内容,提高图像分类的准确性。
同时,注意力机制的引入,进一步增强了模型对关键信息的聚焦能力。在图像分类过程中,注意力机制可以引导模型自动关注到图像中最具判别性的区域,从而忽略不相关或冗余的信息。这种机制与MoE的结合,使得每个专家模型能够在其擅长的领域内发挥最大的作用,共同提升模型整体的分类性能。
10.4.1 基于注意力机制的ViT模型
Vision Transformer(ViT)模型是最新提出的、将注意力机制应用在图像分类中的模型。Vision Transformer算法会将整幅图像拆分成小图像块,然后把这些小图像块的线性映射序列作为注意力模块的输入数据送入网络,再进行图像分类的训练。使用ViT进行图像分块如图10-26所示。

图10-26 使用ViT进行图像分块
Vision Transformer是注意力机制在图像识别领域的一项开创性的应用,其舍弃了传统基于卷积神经网络的图像识别模式,采用了全新的注意力架构来处理图像数据。这种架构的核心思想是自注意力机制,它允许模型在同一序列中的不同位置之间建立相互依赖的关系,从而实现对图像特征的全局捕捉和长距离依赖的处理。与传统的卷积神经网络相比,Vision Transformer具有以下几个显著优势:
- 长距离依赖处理:传统卷积神经网络在处理局部特征时表现出色,但在处理长距离依赖方面相对较弱。而Vision Transformer通过自注意力机制,可以有效地捕捉到图像中不同位置之间的依赖关系,从而提高模型在处理长距离依赖任务时的性能。
- 可解释性:虽然深度学习模型通常被认为是“黑箱”,但Vision Transformer在一定程度上具有可解释性。通过对模型的中间层输出进行分析,我们可以了解到模型在不同层次上关注的图像特征。这有助于我们理解模型的工作原理,并在需要时进行调试和优化。
- 并行计算能力:由于注意力架构天然具有并行计算能力,因此在处理大量图像数据时,Vision Transformer可以充分利用GPU资源,实现高效的计算。
- 全局感知:Vision Transformer通过自注意力机制,可以在不同层次的特征之间建立起关联关系,从而实现对图像全局信息的感知。这使得模型在处理复杂图像任务时,能够更好地捕捉到图像的整体结构和语义信息。
- 易于迁移学习:由于Vision Transformer摒弃了传统的卷积神经网络结构,因此可以很容易地将其预训练好的权重迁移到其他任务上。这使得模型具有更强的泛化能力,可以在不同的图像识别任务中取得良好的效果。
Vision Transformer的整体结构如图10-27所示。

图10-27 Vision Transformer的整体结构
从图中可以看到,与前面章节讲解的编码器类似,Vision Transformer也是由同样的组件所构成:
- Patch Embedding:将整幅图像拆分成小图像块,然后把这些小图像块的线性映射序列作为Transformer的输入送入网络。
- Position Embedding:由于Transformer没有循环结构,因此需要添加位置编码来保留输入序列中的位置信息。
- Transformer Encoder:使用多头自注意力机制(Multi-head Self-Attention)对每个小图像块映射后的向量进行加权求和,得到新的向量。
- 分类器:最后使用一个全连接层对每个小图像块的向量进行分类。
其中最重要的是Patch Embedding和Position Embedding,下面我们将对其展开讲解。
10.4.2 Patch Embedding与Position Embedding
Patch Embedding又称为图像分块映射,在Transformer结构中,需要输入的是一个二维矩阵(L, D),其中L是sequence的长度,D是sequence中每个向量的维度,因此需要将三维的图像矩阵转换为二维的矩阵。
图像的输入不是一个一个的字符,而是一个一个的像素。假设每个像素有C个通道,图像有宽W和高H,因此一幅图像的所有数据可以用一个大小为[H,W,C]的张量来无损地表示。例如,在MNIST数据集中,数据的大小就是28×28。但是一个一个像素输入Transformer粒度太细了,一幅最小的图像也要768(28×28)个token,因此,一般把图像切成一些小块(patch)当作token输入。同时,patch的大小[h,w]必须是能够被图像的宽和高整除的,如图10-28所示。

图10-28 Patch Embedding图像转化
这些图像的token意义上等价于文本的token,都是原来信息的序列表示。不同的是,文本的token是通过分词算法分到的特定字库中的序号,这些序号即字典的index。也就是说,文本的token是一个数字;而图像的一个token(patch)是一个矩阵。
具体来看,如果输入图像大小为28×28×1,这是一个单通道的灰度图,则Patch Embedding将图像分为固定大小的patch,patch大小为4×4,则每幅图像会生成28×28 / 4×4=49个patch,这个数值可以作为映射后序列长度,值为49,而每个patch的大小则是4×4×1=16。因此每幅图像完成Patch Embedding映射后的矩阵大小为[49,16],图像映射为49个token,每个token维度为16。

而对于多通道(一般通道数位3)的彩色图像的处理,首先需要对原始输入图像作切块处理。假设输入的图像大小为224×224,我们将图像切成一个个固定大小为16×16的方块,每一个小方块就是一个patch,那么每幅图像中patch的个数为(224×224)/(16×16) = 196个。切块后,我们得到了196个[16, 16, 3]的patch,之后把这些patch送入Flattened Patches层,这个层的作用是将输入序列展平。所以输出后也有196个token,每个token的维度经过展平后为16×16×3 = 768,所以输出的维度为[196, 768]。
有的时候为了针对项目目标的分类特殊性,还需要加上一个特殊字符cls,因此最终的维度是[token数+1,token维度]。到目前为止,已经通过Patch Embedding将一个视觉问题转化为了一个序列处理问题。
下面我们以多通道的彩色图像处理为例,完成Patch Embedding的计算。在实际代码实现中,只需通过卷积和展平操作即可实现Patch Embedding。使用的卷积核大小为16×16,步长(stride)为16,卷积核个数为768,卷积后再展平,size变化为[224, 224, 3]→[14, 14, 768]→[196, 768]。 代码如下:
class PatchEmbed(torch.nn.Module):
def __init__(self, img_size=224, patch_size=16, in_c=3, embed_dim=768):
super().__init__()
"""
此函数用于初始化相关参数
:param img_size: 输入图像的大小
:param patch_size: 一个patch的大小
:param in_c: 输入图像的通道数
:param embed_dim: 输出的每个token的维度
:param norm_layer: 指定归一化方式,默认为None
"""
patch_size = (patch_size, patch_size) # 16 -> (16, 16)
self.img_size = img_size = (img_size, img_size) # 224 -> (224, 224)
self.patch_size = patch_size
self.grid_size = (img_size[0] // patch_size[0], img_size[1] // img_size[1]) # 计算原始图像被划分为(14, 14)个小块
self.num_patches = self.grid_size[0] * self.grid_size[1] # 计算patch的个数为14×14=196个
# 定义卷积层
self.proj = torch.nn.Conv2d(in_channels=in_c, out_channels=embed_dim, kernel_size=patch_size, stride=patch_size)
# 定义归一化方式
self.norm = torch.nn.LayerNorm(embed_dim)
def forward(self,image):
"""
此函数用于前向传播
:param x: 原始图像
:return: 处理后的图像
"""
B, C, H, W = image.shape
# 检查图像高、宽和预先设定是否一致,不一致则报错
assert H == self.img_size[0] and W == self.img_size[
1], f"Input image size ({H}*{W}) doesn't match model ({self.img_size[0]}*{self.img_size[1]})."
# 对图像依次进行卷积、展平和调换处理: [B, C, H, W] -> [B, C, HW] -> [B, HW, C]
x = self.proj(image).flatten(2).transpose(1, 2)
# 归一化处理
x = self.norm(x)
return x
在上面代码中,我们使用卷积层完成Patch Embedding的操作,即将输入的图像格式做了转化。
图像的每个patch和文本一样,也有先后顺序,是不能随意打乱的,所以需要再给每个token添加位置信息,有时候还需要添加一个特殊字符class token。Vision Transformer模型中使用了一个可训练的向量作为位置向量的参数,添加在图像处理后的矩阵上,而对于位置Embedding来说最终要输入到Transformer Encoder的序列维度为[197, 768]。代码如下所示。
self.num_tokens = 1
self.pos_embed = torch.nn.Parameter(torch.zeros(1, num_patches + self.num_tokens, embed_dim))
x = x + self.pos_embed
需要注意,这里self.num_tokens为额外添加的、作为分类指示器的class token提供了位置编码。
10.4.3 可视化的Vision-MoE的详解
Vision-MoE(V-MoE)是将混合专家(MoE)机制融入注意力图像模型中的一个典范案例。该模型创新性地将Vision Transformer(ViT)架构中原本密集的前馈神经网络(FFNN)层替换为稀疏的MoE层。这一变革旨在通过动态路由机制,在推理时仅激活对特定输入最相关的专家子集,从而大幅提升计算效率与模型性能。Vision-MoE模型如图10-29所示。

图10-29 Vision-MoE模型
V-MoE的设计不仅优化了计算资源的分配,减少了不必要的计算开销,还通过引入专家间的多样性和互补性,增强了模型处理复杂视觉任务的能力。这种方法鼓励模型在面对多样化图像数据时,能够更灵活地调整其内部表示,捕捉更加精细和丰富的特征信息。
此外,V-MoE框架还允许研究者根据实际应用需求,灵活调整专家的数量和类型,以及专家间的连接方式,这为探索更加高效和定制化的视觉模型提供了新的视角。例如,在大语言模型的成功启发下,V-MoE可以尝试引入大规模专家库,结合自注意力机制和MoE的优势,推动图像理解和生成任务的边界。
对于在实际中操作V-MoE,由于路由器的设置,某些图像token会被丢弃(不被选择),如图10-30所示。

图10-30 路由对每个token的去留做出决定
具体在操作上,MoE网络会为每个图像块分配重要性得分,并优先处理这些得分较高的图像块,从而避免溢出图像块的丢失,如图10-31所示。
但是对于选择的结果来说,即使token数量减少,我们仍然能够看到重要的图像块被成功路由,如图10-32所示。
可以看到,通过V-MoE,这就像是所有图像块被选择后,将保留后的图像token重新组合成一个新的图像块被发送到每个专家。生成输出后,它们再次与路由矩阵相乘,如图10-33所示。

图10-31 MoE网络对每个图像token进行评分

图10-32 不同选择比例下被保留的图像token

图10-33 经过MoE选择的ViT模型
随着研究的深入,V-MoE的应用场景也在不断拓展,从基础图像分类、目标检测到高级语义分割、图像生成等领域,都展现出其巨大的潜力。未来,V-MoE有望成为连接深度学习理论与实际应用的重要桥梁,为人工智能在视觉领域的发展注入新的活力。同时,如何进一步优化MoE的路由策略、平衡模型复杂度与性能之间的关系,以及探索MoE与其他先进网络结构的融合方式,将是该领域研究的关键方向。
10.4.4 V-MoE模型的实现
最后我们将实现V-MoE模型,与前面设置的注意力模型相同,我们首先需要完成ViTBlock的设计,与注意力block相同,只需要将前馈层进行替换,代码如下所示。
from 第五章 import baseMOE
class VITBlock(torch.nn.Module):
def __init__(self, d_model, num_heads):
super(VITBlock, self).__init__()
self.d_model = d_model
self.num_heads = num_heads
self.attention_norm = torch.nn.RMSNorm(d_model)
self.self_attention = attention_moudle.MultiHeadAttention(d_model, num_heads)
self.ffn = baseMOE.SparseMoE(d_model,4,2)
def forward(self, embedding):
residual = embedding
embedding = self.attention_norm(embedding)
embedding = self.self_attention(embedding)
embedding = self.ffn(embedding)
return embedding + residual
基于此完成的ViT模型如下:
class VIT(torch.nn.Module):
def __init__(self,dim = 312):
super(VIT, self).__init__()
self.patch_embedding = PatchEmbed()
self.position_embedding = torch.nn.Parameter(torch.rand(1, 16, dim))
self.vit_layers = VITBlock(d_model=312, num_heads=6)
self.logits_layer = torch.nn.Linear(4992,10)
def forward(self, x):
embedding = self.patch_embedding(x) + self.position_embedding
embedding = self.vit_layers(embedding)
embedding = torch.nn.Flatten()(embedding)
logits = self.logits_layer(embedding)
return logits
10.4.5 基于图像识别模型V-MoE的训练与验证
最后就是完成基于图像识别模型V-MoE的训练与验证,与上一节的处理方法类似,我们同样使用MNIST数据集,之后将模型部分进行替换,代码如下所示。
from torch.utils.data import DataLoader
from get_dataset import MNIST
import vmoe_model
from tqdm import tqdm
import torch
DEVICE='cuda' if torch.cuda.is_available() else 'cpu' # 设备
BATCH_SIZE = 128
dataset=MNIST() # 数据集
model = vmoe_model.VIT().to(DEVICE)
optimizer =torch.optim.AdamW(model.parameters(),lr=1e-5) # 优化器
loss_fn = torch.nn.CrossEntropyLoss()
dataloader=DataLoader(dataset,batch_size=BATCH_SIZE,shuffle=True) # 数据加载器
for epoch in range(36):
pbar = tqdm(dataloader, total=len(dataloader))
for imgs, labels in pbar:
imgs, labels = imgs.to(DEVICE), labels.to(DEVICE) #将数据和标签移动到指定的设备上
# 前向传播
outputs = model(imgs)
loss = loss_fn(outputs, labels)
# 反向传播和优化
optimizer.zero_grad() # 清空梯度
loss.backward() # 反向传播计算梯度
optimizer.step() # 更新模型参数
# 更新进度条信息
pbar.set_description(f"Epoch {epoch + 1}/{36}, Loss: {loss.item():.4f}")
model.eval() # 设置模型为评估模式
val_dataset = MNIST(is_train=False) # 验证数据集
val_dataloader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False) # 验证数据加载器,通常不需要打乱
correct = 0 # 正确预测的计数
total = 0 # 总样本数
with torch.no_grad(): # 不需要计算梯度,节省内存和计算资源
for imgs, labels in tqdm(val_dataloader, total=len(val_dataloader)):
imgs, labels = imgs.to(DEVICE), labels.to(DEVICE) #将数据和标签移动到指定的设备上
# 前向传播
outputs = model(imgs)
_, predicted = torch.max(outputs.data, 1) # 得到预测结果
# 计算准确率
total += labels.size(0) # 更新总样本数
correct += (predicted == labels).sum().item() # 更新正确预测的计数
# 计算并打印准确率
accuracy = 100 * correct / total
print(f'Validation Accuracy of the model on the test images: {accuracy:.2f}%')
经过36轮的训练,可以得到如下结果:
Epoch 1/36, Loss: 1.2793: 100%|██████████| 469/469 [00:17<00:00, 27.44it/s]
Epoch 2/36, Loss: 0.8980: 100%|██████████| 469/469 [00:16<00:00, 29.11it/s]
Epoch 3/36, Loss: 0.5704: 100%|██████████| 469/469 [00:16<00:00, 29.24it/s]
...
Epoch 34/36, Loss: 0.1492: 100%|██████████| 469/469 [00:15<00:00, 30.04it/s]
Epoch 35/36, Loss: 0.1521: 100%|██████████| 469/469 [00:15<00:00, 29.57it/s]
Epoch 36/36, Loss: 0.0992: 100%|██████████| 469/469 [00:15<00:00, 30.46it/s]
100%|██████████| 79/79 [00:01<00:00, 50.53it/s]
Validation Accuracy of the model on the test images: 97.30%
此时,在经历了相同的训练轮次之后,我们的模型在验证集上展现出了令人瞩目的性能,取得了约97%的准确率。这一成绩相较于我们之前采用的SENet图像识别任务而言,V-MoE模型无疑实现了质的飞跃。这种显著的提升不仅证明了V-MoE模型在图像识别领域的优越性,更凸显出其在处理复杂图像任务时的强大潜力。
进一步深入分析,V-MoE模型之所以能够取得如此出色的表现,主要归功于其独特的网络结构和优化算法。该模型通过引入更高效的特征提取方式和更精细的分类策略,显著提升了图像识别的准确度和效率。此外,V-MoE还具备更强的泛化能力,能够更好地适应不同的图像场景和识别需求。
展望未来,我们有信心认为V-MoE模型将在图像识别领域发挥更加重要的作用。随着技术的不断进步和应用的不断深化,我们相信V-MoE将助力更多创新应用的诞生,推动整个图像识别行业的持续发展。
10.4.6 使用已有的库实现MoE
前面我们实现的MoE库,实际上对于模型的程序设计来说,使用已经实现好的MOE库来完成模型的设计也是可行的。读者可以安装已有的库,代码如下所示。
pip install st_moe_pytorch
这是使用已完成的MoE模型的基本内容。在具体使用上,我们可以使用MoE并加载11.4节中讲解的SwiGLU激活函数来共同实现前馈层,代码如下所示。
import torch
from st_moe_pytorch import MoE
from st_moe_pytorch import SparseMoEBlock
class Swiglu(torch.nn.Module):
def __init__(self, hidden_size=384, add_bias_linear=False):
super(Swiglu, self).__init__()
self.add_bias = add_bias_linear
self.hidden_size = hidden_size
self.dense_h_to_4h = torch.nn.Linear(
hidden_size,
hidden_size * 4,
bias=self.add_bias
)
def swiglu(x):
x = torch.chunk(x, 2, dim=-1)
return torch.nn.functional.silu(x[0]) * x[1]
self.activation_func = swiglu
self.dense_4h_to_h = torch.nn.Linear(
hidden_size * 2,
hidden_size,
bias=self.add_bias
)
def forward(self, hidden_states):
intermediate_parallel = self.dense_h_to_4h(hidden_states)
intermediate_parallel = self.activation_func(intermediate_parallel)
output = self.dense_4h_to_h(intermediate_parallel)
return output
class MOE(torch.nn.Module):
def __init__(self,dim = 512):
"""
from:https://github.com/lucidrains/st-moe-pytorch
"""
super(MOE, self).__init__()
self.moe = MoE(
dim=dim, # MoE模型的维度
num_experts=16, # 专家数量,增加此值可以在不增加计算量的情况下增加模型的参数
gating_top_n=2, # 默认选择前两个专家,但也可以选择更多(论文中测试了3个,但使用了较低的阈值)
threshold_train=0.2, # 训练时,决定一个token是否被路由到第二个及以后专家的阈值,对于两个专家路由,0.2是最优的,对于3个显然应该更低
threshold_eval=0.2, # 评估时的阈值,与train时的意义相同
capacity_factor_train=1.25, # 每个批次的专家有固定的容量。我们需要一些额外的容量以防路由不完全平衡
capacity_factor_eval=2., # 评估时的容量因子,capacity_factor_*的值应设置为 >=1
balance_loss_coef=1e-2, # 辅助专家平衡损失的乘数
router_z_loss_coef=1e-3, # 路由z损失的权重
)
self.moe_block = SparseMoEBlock(
self.moe,
add_ff_before=True,
add_ff_after=True
)
self.norm = torch.nn.RMSNorm(dim)
self.moe_linear = torch.nn.Linear(dim,dim,bias=False)
self.activity_layer = Swiglu(hidden_size = dim)
def forward(self, x):
x = self.norm(x)
enc_out = self.moe_block(x)[0]
enc_out = self.activity_layer(enc_out)#torch.nn.functional.gelu(enc_out)
enc_out = self.moe_linear(enc_out)
return enc_out
从上面代码可以看到,我们通过载入已完成的模块从而直接实现MoE。而在具体使用上,我们同样可以直接替换注意力Block中的前馈层,以完成新架构的注意力模型。这一点请读者自行尝试。
10.5 本 章 小 结
在本章中,我们深入探讨了混合专家模型的精妙架构,并详细阐述了如何巧妙地将这一模型融入注意力机制之中,具体做法是通过替换传统注意力模块中的前馈神经网络层来实现。这一系列的理论推导与技术创新,不仅拓宽了模型设计的视野,更为提升深度学习模型的性能开辟了新路径。
通过演示一系列精心设计的实战案例,我们直观且有力地见证了混合专家模型在注意力机制中的显著成效。这些案例涵盖了从自然语言处理到图像识别等多个领域,无一不展现出,在引入了MoE的注意力框架下,模型的预测准确性实现了质的飞跃。这一提升不仅体现在数值上的精度增加,更反映在模型对于复杂、多样数据的理解能力和泛化性能的显著增强上。
更重要的是,混合专家模型的应用还为解决大规模数据集上的训练难题提供了新的策略。通过动态地选择不同专家来处理不同部分的数据,MoE模型不仅有效缓解了计算资源的压力,还促进了模型对于特征的精细捕捉与高效利用,从而在保持高效训练的同时,大幅提升了模型的最终性能。
此外,我们还探讨了混合专家模型在实际应用中可能面临的挑战,如模型复杂度的增加、专家选择机制的设计优化,以及如何在保持模型性能的同时,进一步降低计算和存储成本等。这些讨论不仅为我们深入理解MoE模型的运作机制提供了更多视角,也为后续的研究与应用指明了方向。
可以看到,随着深度学习技术的持续演进,混合专家模型与注意力机制的深度融合将有望引领更多领域的突破性进展。我们期待看到,通过不断的探索与创新,这一强大的组合能够解锁更多未知的应用场景,为人工智能的发展注入新的活力与可能性。

1129



被折叠的 条评论
为什么被折叠?



