freeCodeCamp.org

What Is Model Distillation? A Guide for Developers

7.1内容质量
What Is Model Distillation? A Guide for Developers

TL;DR · AI 摘要

What Is Model Distillation? A Guide for Developers October 21, 2026 / llm Mene-Ejegi Ogbemi Over time, knowledge has bee...

核心要点

  • 主题聚焦:What Is Model Distillation? A Guide for Develope
  • 来源:freeCodeCamp.org,建议结合原文判断细节。
  • AI 分析暂不可用,本条为保底评分与摘要。
#AI#编程#安全#开源
打开原文

什么是模型蒸馏?开发者的指南

2026年10月21日

/

#llm

Mene-Ejegi Ogbemi

随着时间推移,知识以多种形式传承。在古代美索不达米亚(今伊拉克)发明书写系统之前,法律、历史、神话、歌曲和其他信息都是通过口头方式代代相传。这种传承通过指导或讲故事(尤其是民间传说和历史事件)实现。

另一种常见的知识传递方式是师徒传承,无论是正式还是非正式形式。纵观历史,我们都能看到这种模式:《圣经》记载耶稣有十二位门徒,他从传道开始到殉难期间每天教导他们。苏格拉底教导柏拉图,柏拉图随后在雅典创办学院,教授亚里士多德二十年。亚里士多德在科学、逻辑和政治领域掀起革命,此类传承延续至今。

21世纪的一个典型案例是深度学习AI的传承脉络。在20世纪人工智能寒冬时期,当人们认为神经网络是死胡同时,这种师徒传承模式依然维系着人工智能的梦想。

被誉为人工智能之父的杰弗里·辛顿曾领导实验室,培养了扬·乐昆、伊利亚·苏茨克弗等弟子。扬·乐昆后来发明了卷积神经网络(CNN),伊利亚·苏茨克弗则在离开Safe Superintelligence Inc.前共同创立了OpenAI。

虽然人类之间的知识传递模式我们很熟悉,但机器之间的知识传递又如何呢?这种技术被称为"模型蒸馏"。

在本文中,我将详细解析模型蒸馏的概念。我们将探讨其使用的技术,并通过PyTorch从零开始演示实现过程。

最终你将理解如何训练更小更高效的模型,以及蒸馏技术与其他压缩方法(如量化和剪枝)的对比。

本文内容

  • 先决条件
  • 什么是模型蒸馏?为什么需要模型蒸馏?
  • 模型蒸馏技术 基于响应的蒸馏 基于特征的蒸馏 注意力蒸馏 自蒸馏
  • 如何在PyTorch中实现知识蒸馏
  • 如何评估结果
  • 其他模型压缩方法 量化 剪枝 低秩分解
  • 结论 参考资料

先决条件

要跟随本文内容,你需要具备以下条件:

  • GitHub仓库:项目的完整实现代码可在此处获取,建议克隆仓库以便跟随操作。
  • Python和面向对象编程基础(例如类的使用)。
  • 深度学习基础知识概览。你应该理解epoch、前向传播、损失函数等概念。
  • PyTorch基础:虽然我会逐行解释代码,但了解PyTorch张量和训练循环的基础知识会更有帮助。
  • 开发环境:你的机器上需要安装Python。我将使用Visual Studio Code作为编辑器,但你可以选择任何喜欢的编辑器或IDE。
  • 硬件:本项目将使用相对较小的数据集,因此标准CPU即可满足需求。

什么是模型蒸馏?

模型蒸馏(或知识蒸馏)是一种将大型高精度模型所学到的行为传递给更小、计算效率更高的模型的方法,这种小型模型更适合实际部署。

知识蒸馏是一种压缩技术,其中较小的模型(称为学生模型)学习模仿较大且更复杂的模型(教师模型)的输出。目标是使学生模型达到接近教师模型的性能水平,但参数数量显著减少,从而在部署时更加高效。(来源)

为什么需要模型蒸馏?

在撰写本文时,闭源和开源模型都训练了数万亿个参数。这些是模型从训练数据中自动学习的内部配置变量。如此庞大的参数数量使得在没有充足资金的情况下运行变得极其困难且成本高昂。模型消耗大量能源,并需要昂贵的硬件。

通过蒸馏,我们可以将这些前沿模型的学习成果转移到更小、更高效的模型中,使它们更易于使用且成本更低。

模型蒸馏技术

使用的蒸馏技术取决于所传递的知识类型。一些方法仅传递最终预测结果,而其他方法则传递内部表示、注意力图、推理步骤,甚至教师模型的全部行为。

以下是一些技术:

基于响应的蒸馏

该方法最初由 Geoffrey Hinton 在其论文《Distilling the knowledge in a neural network》中提出,这是经典的教师到学生的蒸馏方法。在深入探讨其工作原理之前,需要了解一些关于模型训练的基本概念:硬标签和软标签。

硬标签严格处理固定的分类目标。当一张狗的图片输入模型并被要求预测它是狗、猫还是马时,模型会预测它是狗,结果表示为 Dog(1)、马(0)、猫(0)。图片在多大程度上像猫或马的特征概率不会被考虑。

但我们还有软标签,它处理概率。当使用软标签的相同图片输入模型时,它可以预测图片有 0.85 的概率是狗,0.10 的概率是猫,0.5 的概率是马。

在基于响应的蒸馏中,学生模型不是使用硬标签进行训练,而是从教师模型输出的概率(软标签)中学习。学生模型可以学习图片是狗,但也能学习到图片略微像猫,几乎像马。

这种额外信息称为暗知识。它使较小的模型能够复制更大、更精确的教师模型的行为,同时保持高性能。

基于响应的蒸馏的一些优势是易于实现、非常稳定,并且在图像分类中效果良好。但有一些缺点,主要问题是忽略了网络内部发生的情况。

也就是说,基于响应的蒸馏仅关注输出层(概率),因此学生模型与教师模型之间存在“黑箱”关系。它学习教师模型的决策,但不学习教师模型如何得出结论。使用之前的例子,它学习到图片是狗、猫和马的概率,但不学习得出该结论的底层原理或工作机制。

基于特征的蒸馏

该技术传递较大模型的内部特征表示,而不是仅传递最终输出。

一个简单的思考方式是,想象你是一位优秀数学导师的学生。导师给你一道数学题并展示最终结果。你可能会最终猜出正确的公式,但你主要只是在学习如何记忆输出结果。

但如果导师要求你查看他们的笔记本并观察他们的解题过程,你就会学习到导师确切的思维过程。

在基于特征的蒸馏中,训练期间学生模型需要生成与导师模型匹配的隐藏层激活值,使用均方误差(Mean Squared Error, MSE)进行衡量。通过均方误差,学生模型不仅会因正确猜测最终输出而获得奖励,如果其内部功能系统与教师模型不匹配,还会受到惩罚。

该方法在卷积神经网络、视觉变换器、目标检测等领域被广泛使用。

该方法的缺点在于,由于学生模型需要在每次训练迭代中提取并存储教师模型的内部特征图,内存和功耗会迅速增加。这使得运行成本非常高。

注意力蒸馏

在这种方法中,学生模型不会复制所有神经元,而是观察大型模型关注的位置并复制这些神经元。

在变换器模型中(这类模型试图理解整个句子而非单独关注单词),注意力图表明模型在预测时认为哪些输入标记或区域最为重要。学生模型会尝试重现这些图或教师模型的分布。这告诉学生模型在解决问题时应该“关注”哪些位置。

注意力蒸馏的缺点是训练时间和硬件资源消耗较大。这是因为需要计算均方误差(MSE)和Kullback-Leibler(KL)散度等指标。这些指标作为损失函数,能准确告诉模型其预测结果或内部表示与教师模型的差异程度。在大规模注意力张量上进行这些计算会显著增加资源消耗。

自蒸馏

模型也可以通过自身进行训练。这涉及从早期版本向后期版本的知识迁移,也可能涉及网络中不同层之间的知识迁移。

其优势在于模型无需增大体积就能变得更好,从而降低使用大型模型训练学生模型的成本。

如何在PyTorch中实现知识蒸馏

为了展示模型蒸馏在实际中的运作方式,我们将使用PyTorch,通过MNIST数据集和分类器训练教师模型,然后训练学生模型。

该数据集包含约70,000张手写数字的灰度图像。我们将先训练教师模型,然后在训练学生模型时冻结教师模型,使用教师模型提供的硬标签和软目标进行训练。

需要注意的是,MNIST数据集是一个相对较小的数据集,因此我们的蒸馏收益可能不会很明显。但这个项目展示了蒸馏的工作流程,并将为你理解如何处理更大规模的数据集打下良好基础。

要运行此项目,你需要以下Python库:

| 包名 | 用途 | |--------------|------------------------------| | torch | 我们将使用的构建和训练神经网络的核心深度学习框架 | | torchvision| 提供MNIST等数据集和图像工具 | | numpy | 数值计算支持 |

数值计算和数组操作。

code
matplotlib

绘制图表和可视化图像/结果。

code
tqdm

在训练过程中显示进度条。

对于本项目,我将使用 Visual Studio Code 编辑器,但你也可以选择其他任何代码编辑器。

这是我们的项目结构。下面我将结合代码解释各个文件夹和文件的作用。

code
knowledge-distillation/
│
├── data/
│
├── models.py
│
├── engine.py
│
├── losses.py
│
├── train_teacher.py
│
├── train_student.py
│
├── train_distilled_student.py
│
├── evaluate.py
│
├── visualize.py
│
├── utils.py
│
├── plots.py
│
├── requirements.txt
│
└── README.md

首先在我们的目录结构中是 data 文件夹。其中包含我们的 MNIST 数据集。

models.py 文件定义了项目中使用的所有神经网络架构。它包含教师网络类和学生网络类。每个类定义了网络层、前向传播以及数据在网络中的流动方式。

code
import torch
import torch.nn as nn
import torch.nn.functional as F

class TeacherNet(nn.Module):

    def __init__(self):
        super().__init__()

        self.flatten = nn.Flatten()

        self.fc1 = nn.Linear(28 * 28, 1024)
        self.fc2 = nn.Linear(1024, 512)
        self.fc3 = nn.Linear(512, 256)
        self.fc4 = nn.Linear(256, 128)
        self.fc5 = nn.Linear(128, 10)

    def forward(self, x):

        x = self.flatten(x)

        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = F.relu(self.fc3(x))
        x = F.relu(self.fc4(x))

        logits = self.fc5(x)

        return logits

class StudentNet(nn.Module):
    """
    Smaller neural network (Student)
    """

    def __init__(self):
        super().__init__()

        self.flatten = nn.Flatten()

        self.fc1 = nn.Linear(28 * 28, 128)
        self.fc2 = nn.Linear(128, 10)

    def forward(self, x):

        x = self.flatten(x)

        x = F.relu(self.fc1(x))

        logits = self.fc2(x)

        return logits

utils.py 文件负责准备数据集并回答问题:“数据集来自哪里?”在此我们下载 MNIST 数据集,对图像进行归一化处理,并创建训练和测试数据加载器。

code
from torchvision import datasets, transforms
from torch.utils.data import DataLoader

def get_dataloaders(batch_size=64):
    """
    下载 MNIST(如需)并返回
    训练和测试的 DataLoaders。
    """

    transform = transforms.ToTensor()

    train_dataset = datasets.MNIST(
        root="data",
        train=True,
        download=True,
        transform=transform
    )

    test_dataset = datasets.MNIST(
        root="data",
        train=False,
        download=True,
        transform=transform
    )

    train_loader = DataLoader(
        train_dataset,
        batch_size=batch_size,
        shuffle=True
    )

    test_loader = DataLoader(
        test_dataset,
        batch_size=batch_size,
        shuffle=False
    )

    return train_loader, test_loader

engine.py 文件包含可复用的学习算法,帮助实现训练过程。它接收 model.py 中的代码并进行训练。

code
import torch
from tqdm import tqdm

def train_one_epoch(
    model,
    dataloader,
    criterion,
    optimizer,
    device,
):
    model.train()

    running_loss = 0

    progress = tqdm(dataloader)

    for images, labels in progress:
python
images = images.to(device)
labels = labels.to(device)

optimizer.zero_grad()

outputs = model(images)

loss = criterion(outputs, labels)

loss.backward()

optimizer.step()

running_loss += loss.item()

progress.set_description(
    f"Loss {loss.item():.4f}"
)

return running_loss / len(dataloader)

def evaluate(model, dataloader, device):

    model.eval()

    correct = 0
    total = 0

    with torch.no_grad():

        for images, labels in dataloader:

            images = images.to(device)
            labels = labels.to(device)

            outputs = model(images)

            _, predicted = torch.max(outputs, dim=1)

            total += labels.size(0)

            correct += (predicted == labels).sum().item()

    accuracy = 100 * correct / total

    return accuracy

def predict(model, images, device):

    model.eval()

    with torch.no_grad():

        images = images.to(device)

        outputs = model(images)

        _, predictions = torch.max(outputs, dim=1)

    return predictions

def train_distillation_epoch(
    teacher,
    student,
    dataloader,
    optimizer,
    device,
    loss_fn,
    temperature=4.0,
    alpha=0.5,
):
    """
    使用固定教师模型训练学生模型
    """

    teacher.eval()
    student.train()

    running_loss = 0

    progress = tqdm(dataloader)

    for images, labels in progress:

        images = images.to(device)
        labels = labels.to(device)

        optimizer.zero_grad()

        # 教师模型预测(不计算梯度)
        with torch.no_grad():
            teacher_logits = teacher(images)

        # 学生模型预测
        student_logits = student(images)

        # 组合损失
        loss = loss_fn(
            student_logits=student_logits,
            teacher_logits=teacher_logits,
            labels=labels,
            temperature=temperature,
            alpha=alpha,
        )

        loss.backward()

        optimizer.step()

        running_loss += loss.item()

        progress.set_description(
            f"Distill Loss {loss.item():.4f}"
        )

    return running_loss / len(dataloader)

losses.py 文件定义了模型如何衡量其错误。由于神经网络通过最小化损失进行学习,基础可视化使用了 CrossEntropyLoss,而知识蒸馏则引入了 distillation_loss() 函数。

在该文件中,我们通过交叉熵和k1散度结合了硬标签和软目标。这就是 Geoffrey Hinton 的算法实现之处。

python
import torch
import torch.nn as nn
import torch.nn.functional as F

cross_entropy = nn.CrossEntropyLoss()

def distillation_loss(
    student_logits,
    teacher_logits,
    labels,
    temperature=4.0,
    alpha=0.5,
):
    """
    Geoffrey Hinton 的知识蒸馏损失函数

    参数
    ----------
    student_logits : 学生模型输出
    teacher_logits : 教师模型输出
    labels         : 真实标签
    temperature    : 控制概率分布的软度
    alpha          : 硬损失和软损失之间的权重
    """

    # 硬标签损失
    hard_loss = cross_entropy(student_logits, labels)

    # 教师模型的软目标
    teacher_probs = F.softmax(
        teacher_logits / temperature,
        dim=1
    )

    # 学生模型的软预测
    student_log_probs = F.log_softmax(
        student_logits / temperature,
        dim=1
    )
python
# KL散度
soft_loss = F.kl_div(
    student_log_probs,
    teacher_probs,
    reduction="batchmean"
)

# 最终蒸馏损失
loss = (
    alpha * hard_loss
    +
    (1 - alpha)
    * (temperature ** 2)
    * soft_loss
)

return loss

train_teacher.py 文件用于训练教师网络。首次运行后,我们不再重新训练教师网络。

python
import torch
import torch.optim as optim

from models import TeacherNet
from utils import get_dataloaders
from engine import train_one_epoch, evaluate
from losses import cross_entropy

device = torch.device(
    "cuda" if torch.cuda.is_available() else "cpu"
)

train_loader, test_loader = get_dataloaders()

teacher = TeacherNet().to(device)

optimizer = optim.Adam(
    teacher.parameters(),
    lr=0.001,
)

epochs = 10

for epoch in range(epochs):

    loss = train_one_epoch(
        teacher,
        train_loader,
        cross_entropy,
        optimizer,
        device,
    )

    accuracy = evaluate(
        teacher,
        test_loader,
        device,
    )

    print(
        f"Epoch {epoch+1}/{epochs}"
        f" | Loss: {loss:.4f}"
        f" | Accuracy: {accuracy:.2f}%"
    )

torch.save(
    teacher.state_dict(),
    "teacher.pth",
)

print("\nTeacher saved successfully.")

train_student.py 文件用于训练学生网络。它与 train_teacher.py 的结构相同,但使用了更小的网络且未实现教师指导。这给出了学生在没有教师指导时的基线准确率。

python
import torch
import torch.optim as optim

from models import StudentNet
from utils import get_dataloaders
from engine import train_one_epoch, evaluate
from losses import cross_entropy

device = torch.device(
    "cuda" if torch.cuda.is_available() else "cpu"
)

train_loader, test_loader = get_dataloaders()

student = StudentNet().to(device)

optimizer = optim.Adam(
    student.parameters(),
    lr=0.001,
)

epochs = 10

for epoch in range(epochs):

    loss = train_one_epoch(
        student,
        train_loader,
        cross_entropy,
        optimizer,
        device,
    )

    accuracy = evaluate(
        student,
        test_loader,
        device,
    )

    print(
        f"Epoch {epoch+1}/{epochs}"
        f" | Loss: {loss:.4f}"
        f" | Accuracy: {accuracy:.2f}%"
    )

torch.save(
    student.state_dict(),
    "student_baseline.pth",
)

print("\nBaseline student saved.")

train_distilled_student.py 是这个代码的核心。在此文件中,我们通过标签和教师的知识共同训练学生网络。此处实现了基于响应的知识蒸馏。

python
import torch
import torch.optim as optim

from models import TeacherNet, StudentNet
from utils import get_dataloaders
from engine import (
    train_distillation_epoch,
    evaluate,
)
from losses import distillation_loss

device = torch.device(
    "cuda" if torch.cuda.is_available() else "cpu"
)

train_loader, test_loader = get_dataloaders()

# 加载教师网络
teacher = TeacherNet().to(device)
teacher.load_state_dict(
    torch.load(
        "teacher.pth",
        map_location=device,
    )
)

teacher.eval()

# 学生网络
student = StudentNet().to(device)

optimizer = optim.Adam(
    student.parameters(),
    lr=0.001,
)

epochs = 10

for epoch in range(epochs):
python
loss = train_distillation_epoch(
    teacher,
    student,
    train_loader,
    optimizer,
    device,
    distillation_loss,
    temperature=3,
    alpha=0.7,
)

accuracy = evaluate(
    student,
    test_loader,
    device,
)

print(
    f"Epoch {epoch+1}/{epochs}"
    f" | Loss: {loss:.4f}"
    f" | Accuracy: {accuracy:.2f}%"
)

torch.save(
    student.state_dict(),
    "student_distilled.pth",
)

print("\nDistilled student saved.")

evaluate.py 用于衡量训练模型的性能。此处我们仅加载模型、加载测试数据、进行预测并计算准确率。

python
import torch

from models import TeacherNet
from utils import get_dataloaders
from engine import evaluate

device = torch.device(
    "cuda" if torch.cuda.is_available() else "cpu"
)

_, test_loader = get_dataloaders()

teacher = TeacherNet().to(device)

teacher.load_state_dict(
    torch.load(
        "teacher.pth",
        map_location=device,
    )
)

accuracy = evaluate(
    teacher,
    test_loader,
    device,
)

print(
    f"Teacher Accuracy: {accuracy:.2f}%"
)

visualize.py 文件用于可视化我们的研究结果。我们使用 Python 库 matplotlib 来展示教师模型、基线学生模型和蒸馏学生模型的性能对比。

python
import matplotlib.pyplot as plt

models = [
    "Teacher",
    "Baseline\nStudent",
    "Distilled\nStudent"
]

accuracy = [98.20, 97.86, 97.91]

plt.figure(figsize=(7,5))
plt.bar(models, accuracy)

plt.title("Knowledge Distillation Performance Comparison")
plt.ylabel("Accuracy (%)")

for i, v in enumerate(accuracy):
    plt.text(i, v + 0.02, f"{v:.2f}%", ha="center")

plt.ylim(97.5, 98.5)

plt.tight_layout()
plt.savefig("accuracy_comparison.png", dpi=300)
plt.show()

以下是程序的整体工作流程,帮助您理解各模块如何协同工作:

code
utils.py
                          │
                 Load MNIST Dataset
                          │
                          ▼
                     models.py
             TeacherNet / StudentNet
                          │
                          ▼
                     losses.py
  Cross Entropy + Distillation Loss Function
                          │
                          ▼
                     engine.py
    Generic Training and Evaluation Functions
             ┌────────────┼────────────┐
             │            │            │
             ▼            ▼            ▼
  train_teacher.py  train_student.py  distilled_student.py
             │            │            │
             ▼            ▼            ▼
   teacher.pth  student_baseline.pth  distilled.pth
                          │
                          ▼
                     evaluate.py
              Compute Final Accuracy
                          │
                          ▼
                    visualize.py
     Generate Charts and Experimental Figures

如何评估结果

现在,让我们检查并评估实验结果。这些是运行每个网络(教师网络、学生网络和蒸馏学生网络)10个周期后的截图。

教师网络:经过10个完整周期后,我们达到了98.20%的准确率。

学生网络:经过10个完整周期后,我们达到了97.74%的准确率。对于没有教师模型的更小网络来说,这是一个相当不错的表现。

蒸馏学生网络:经过10个完整训练周期后,我们达到了98.20%的准确率。相比未使用教师模型知识时的97.74%,模型性能有了明显提升。

请查看下图了解各模型的表现情况:

减少模型规模的其他方法

现在我们已经通过实际代码示例了解了模型蒸馏的概念。接下来让我们探讨开发者们采用的其他三种减少大语言模型规模的方法,这些方法在降低延迟、内存占用和计算成本方面各有特点,并与蒸馏技术存在差异。

以下是三种主要方法:

量化

量化通过严格降低神经网络中数值权重和激活值所使用的底层数据类型的位宽来实现效率提升。它针对的是内存带宽瓶颈,而非模型架构本身。

简单来说,量化可以将32位数字转换为8位数字,从而节省约75%的文件存储空间。

以教科书印刷为例,量化与蒸馏的区别在于:蒸馏会总结教科书内容,而量化则会完整印刷内容,但使用低分辨率字体以节省纸张和墨水。

剪枝

剪枝基于大语言模型中存在大量冗余且影响较小参数的原理。通过移除弱连接,模型可以变得更小更快。

以教科书为例,剪枝过程就像撕掉你认为不必要的章节或页面。

低秩分解

这是一种利用线性代数简化深度学习架构中矩阵乘法的数学过程。

以教科书为例,这个过程相当于将复杂句子改写为含义相同的更简单、更短的公式。

选择使用哪种方法取决于你要解决的瓶颈。量化通常适用于内存挑战,而剪枝和蒸馏则专门用于减少计算时间。

结论

知识传递对人工智能的重要性不亚于其在人类历史中的地位。正如亚里士多德在柏拉图思想基础上进行创新,当今高效的可部署神经网络依赖于从庞大计算密集型教师模型中提取的基础"暗知识"。

虽然训练万亿参数的前沿模型需要巨大的硬件和资金投入,但知识蒸馏确保了这些技术进步的实用价值不会被巨大的计算瓶颈所限制。

正如我们在PyTorch MNIST实验中所展示的,即使简单的基于响应的蒸馏方法,也能让小型网络通过学习大型教师模型的软概率,实现超出其规模的性能表现。

当与量化和剪枝等其他优化技术结合使用时,蒸馏为在边缘设备、手机和消费级硬件上部署高性能AI提供了清晰的实现路径。

感谢阅读!欢迎访问我的YouTube频道,那里有更多关于AI的视频内容。

参考资料:

  • 什么是机器学习模型或大语言模型的蒸馏?
  • AI洞察:英国科学、创新和技术部关于模型蒸馏的说明
  • 大语言模型的模型蒸馏:更小更快AI的实践指南
  • Geoffrey Hinton 的神经网络知识蒸馏
  • 关于知识蒸馏你需要知道的一切

来自合作伙伴的推荐课程

(在新标签页中打开)

ADVERTISEMENT

  • Coursera 上的 掌握神经网络与模型正则化 Erhan Guven 5周,每周3小时 中级 证书 查看课程(在新标签页中打开)
  • Coursera 上的 提升AI推理速度与精度 Starweaver 和 Ritesh Vajariya 3周,每周1小时 中级 证书 查看课程(在新标签页中打开)
  • Coursera 上的 深度学习PyTorch Laurence Moroney 4.8(满分5分)(130人评价) 8周,每周10小时 中级 证书 查看课程(在新标签页中打开)
  • edX 上的 掌握生成式AI:微调Transformer Joseph Santarcangelo 2周,每周2-4小时 中级 证书 查看课程(在新标签页中打开)

Class Central 上的更多PyTorch课程

→

阅读更多文章。

如果你读到这里,请感谢作者以表达你的支持。说声谢谢

免费学习编程。freeCodeCamp的开源课程已帮助超过40,000人成为开发者。立即开始