PyTorch简介:深度学习入门与实践 – wiki词典

“`markdown
PyTorch简介:深度学习入门与实践


引言

在人工智能飞速发展的今天,深度学习已成为推动各行各业变革的核心技术。从图像识别、自然语言处理到推荐系统和自动驾驶,深度学习展现出前所未有的强大能力。而PyTorch,作为Facebook AI Research (FAIR) 开发的开源深度学习框架,凭借其灵活性、易用性和强大的功能,迅速赢得了研究人员和开发者的青睐,成为深度学习领域的主流工具之一。

本篇文章旨在为深度学习初学者提供一份全面的PyTorch入门指南。我们将从PyTorch的核心概念出发,逐步深入到如何利用PyTorch构建、训练和评估深度学习模型。无论您是希望快速上手深度学习项目,还是深入理解其底层机制,本文都将为您提供坚实的基础。

我们将涵盖以下几个关键点:
* PyTorch的优势及其在深度学习生态系统中的地位。
* 理解PyTorch的核心组成部分:张量(Tensors)、自动求导(Autograd)和神经网络模块(nn.Module)。
* 详细的安装教程,确保您能顺利搭建开发环境。
* 通过实际代码示例,演示如何进行基本的张量操作、构建简单的神经网络以及完成模型的训练与评估。

准备好了吗?让我们一起踏上PyTorch的深度学习之旅!


PyTorch核心概念

在深入PyTorch编程之前,理解其几个核心概念至关重要。它们是PyTorch构建和运行深度学习模型的基础。

1. 张量 (Tensors)

张量是PyTorch中最基本的数据结构,它与NumPy的ndarray非常相似,但具备在GPU上进行计算的能力。张量可以表示标量(0维张量)、向量(1维张量)、矩阵(2维张量)以及更高维的数据。深度学习中的所有数据,无论是输入特征、模型参数还是中间计算结果,都是以张量的形式存在的。

张量的主要属性:
* 数据类型 (dtype):例如 torch.float32 (默认浮点型)、torch.float64 (双精度浮点型)、torch.int64 (长整型) 等。
* 形状 (shape):张量每个维度的大小,例如 (3, 224, 224) 表示一个包含3个通道、高224、宽224的图像张量。
* 设备 (device):张量所在的计算设备,可以是 cpucuda (GPU)。

2. 自动求导 (Autograd)

自动求导是PyTorch区别于传统数值计算库的关键特性之一,也是深度学习训练的核心。它能够自动计算张量上的所有操作的梯度。在神经网络训练中,我们需要根据损失函数对模型参数的梯度来更新参数,而Autograd系统正是负责高效、准确地完成这一任务。

Autograd的工作原理:
当我们在张量上执行操作时,PyTorch会构建一个计算图 (computational graph)。图中的节点是张量,边是操作。当调用loss.backward()时,Autograd会遍历这个计算图,并使用链式法则自动计算图中所有可导张量的梯度。

  • requires_grad=True: 默认情况下,张量的requires_grad属性为False。只有当一个张量被创建时设置requires_grad=True,或者通过对一个requires_grad=True的张量进行操作而生成时,PyTorch才会跟踪它的所有操作,并为它存储梯度信息。模型的参数通常就是这样被设置的。
  • grad属性: 在backward()调用后,所有requires_grad=True的叶子张量(即由用户创建的,而不是操作结果的张量)的梯度会累积到它们的.grad属性中。

3. 神经网络模块 (nn.Module)

torch.nn模块是PyTorch构建神经网络的核心API。它提供了一系列预定义的层(如卷积层、全连接层、激活函数等)和构建块。nn.Module是所有神经网络模块的基类,它不仅可以包含模型层,还可以包含其他nn.Module实例,从而方便地构建嵌套的、复杂的神经网络结构。

nn.Module的关键特性:
* 模型结构定义: 我们可以通过继承nn.Module并实现其__init__forward方法来定义自己的神经网络。__init__中定义网络的各个层,forward中定义数据如何在这些层之间流动。
* 参数管理: nn.Module会自动跟踪其内部所有可训练的参数(例如,torch.nn.Linear层中的权重和偏置)。这些参数可以通过model.parameters()方法访问,这对于优化器(如SGD、Adam)进行参数更新至关重要。
* 设备迁移: model.to(device)方法可以将整个模型(包括所有参数)方便地移动到CPU或GPU上进行计算。
* 训练/评估模式: model.train()model.eval() 方法用于切换模型的运行模式。这对于包含如 Dropout 和 BatchNorm 等行为在训练和评估阶段不同的层非常重要。

理解了这三个核心概念,您就掌握了PyTorch深度学习的基石。接下来,我们将探讨如何安装PyTorch并进行一些基本操作。


安装PyTorch

安装PyTorch相对简单,但需要根据您的操作系统、Python版本以及是否需要GPU支持来选择合适的安装命令。PyTorch官网提供了非常便捷的安装向导。

1. 访问PyTorch官网安装向导

打开浏览器,访问 PyTorch官方网站的安装页面:https://pytorch.org/get-started/locally/

2. 选择您的配置

在该页面,您需要根据自己的环境选择以下选项:
* PyTorch Build (PyTorch 版本): 通常选择 Stable (稳定版)。
* Your OS (操作系统): Windows, macOS, Linux。
* Package (安装包管理工具): Conda (推荐), Pip。对于Python环境管理,Conda是一个非常好的选择,可以避免库之间的依赖冲突。
* Language (编程语言): Python (默认)。
* Compute Platform (计算平台):
* CPU: 如果您的设备没有NVIDIA GPU,或者您不需要GPU加速,选择CPU。
* CUDA: 如果您有NVIDIA GPU,并且想利用GPU进行加速计算,请根据您的CUDA Toolkit版本选择对应的CUDA版本。您可以通过运行 nvcc --version (如果您已安装CUDA Toolkit) 或检查NVIDIA驱动程序信息来确定CUDA版本。

3. 执行安装命令

根据您选择的配置,PyTorch官网会生成一个安装命令。

示例:

  • 使用Conda安装 (推荐,带CUDA 12.1支持)
    bash
    conda install pytorch torchvision torchaudio pytorch-cuda=12.1 -c pytorch -c nvidia
  • 使用Pip安装 (带CUDA 12.1支持)
    bash
    pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
  • 使用Conda安装 (仅CPU)
    bash
    conda install pytorch torchvision torchaudio cpuonly -c pytorch
  • 使用Pip安装 (仅CPU)
    bash
    pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu

注意事项:
* Python环境: 强烈建议在一个独立的Python虚拟环境(如使用Conda或venv)中安装PyTorch,以避免与系统中其他Python项目产生依赖冲突。
* CUDA版本: 如果选择GPU版本,请确保您系统中安装的NVIDIA驱动程序与您选择的CUDA版本兼容。不匹配的CUDA版本会导致GPU无法正常工作。
* torchvisiontorchaudio: 这两个库通常与PyTorch一起安装,torchvision提供了常用的计算机视觉数据集、模型和转换工具,torchaudio则用于音频处理。

4. 验证安装

安装完成后,可以在Python环境中验证PyTorch是否安装成功:

python
import torch
print(torch.__version__)
print(torch.cuda.is_available()) # 如果安装了GPU版本,这会显示True

如果上述代码能够正常运行并打印出版本信息,且torch.cuda.is_available()在您期望GPU支持的情况下返回True,那么恭喜您,PyTorch已成功安装!

接下来,我们将学习PyTorch中张量的基本操作。


PyTorch基本操作:张量篇

掌握张量的基本操作是使用PyTorch进行深度学习的基础。本节将介绍如何创建张量、进行常见的数学运算以及如何利用GPU加速计算。

1. 张量创建

PyTorch提供了多种创建张量的方法,与NumPy类似。

“`python
import torch
import numpy as np

1. 直接从数据创建

data = [[1, 2], [3, 4]]
x_data = torch.tensor(data)
print(“从Python列表创建张量:\n”, x_data)

2. 从NumPy数组创建

np_array = np.array(data)
x_np = torch.from_numpy(np_array)
print(“从NumPy数组创建张量:\n”, x_np)

3. 创建带有随机值或常量的张量

shape = (2, 3,)
rand_tensor = torch.rand(shape) # 0到1之间的均匀分布
ones_tensor = torch.ones(shape) # 全1张量
zeros_tensor = torch.zeros(shape) # 全0张量
print(“随机张量:\n”, rand_tensor)
print(“全1张量:\n”, ones_tensor)
print(“全0张量:\n”, zeros_tensor)

4. 创建与另一个张量形状、数据类型相同的张量 (重用属性)

x_ones = torch.ones_like(x_data) # 保留 x_data 的形状和数据类型,但填充 1
print(“与x_data形状相同且全1的张量:\n”, x_ones)

x_rand = torch.rand_like(x_data, dtype=torch.float) # 随机值,但指定数据类型
print(“与x_data形状相同且随机值的张量 (指定float类型):\n”, x_rand)
“`

2. 张量属性

张量的属性提供了其形状、数据类型和存储设备等信息。

“`python
tensor = torch.rand(3, 4)

print(f”张量形状 (Shape): {tensor.shape}”)
print(f”张量数据类型 (Dtype): {tensor.dtype}”)
print(f”张量存储设备 (Device): {tensor.device}”) # 默认是CPU
“`

3. 张量索引与切片

张量的索引和切片操作与NumPy数组非常相似。

“`python
tensor = torch.ones(4, 4)
print(“原始张量:\n”, tensor)

第一行

print(“第一行:\n”, tensor[0])

第一列

print(“第一列:\n”, tensor[:, 0])

最后一列

print(“最后一列:\n”, tensor[…, -1])

第二行和第三行,所有列

print(“第二行和第三行:\n”, tensor[1:3, :])

将一列的值设为0

tensor[:, 1] = 0
print(“修改后的张量:\n”, tensor)
“`

4. 张量连接

可以使用 torch.cattorch.stack 来连接张量。

“`python
tensor = torch.ones(4, 4)
t1 = torch.cat([tensor, tensor, tensor], dim=1) # 按列连接
print(“按列连接张量:\n”, t1)

t2 = torch.stack([tensor, tensor, tensor], dim=0) # 新增一个维度进行堆叠
print(“堆叠张量 (新增维度):\n”, t2.shape)
print(t2)
“`

5. 张量数学运算

PyTorch支持各种数学运算,包括算术运算、矩阵乘法等。

“`python

算术运算

tensor = torch.ones(2, 2)
tensor_b = torch.rand(2, 2)

print(f”tensor:\n {tensor}”)
print(f”tensor_b:\n {tensor_b}”)

print(f”加法 (tensor + tensor_b):\n {tensor + tensor_b}”)
print(f”乘法 (tensor * tensor_b):\n {tensor * tensor_b}”)

矩阵乘法

matrix_a = torch.tensor([[1, 2], [3, 4]])
matrix_b = torch.tensor([[5, 6], [7, 8]])
print(f”矩阵A:\n {matrix_a}”)
print(f”矩阵B:\n {matrix_b}”)

方式一: torch.matmul

print(f”矩阵乘法 (torch.matmul):\n {torch.matmul(matrix_a, matrix_b)}”)

方式二: @ 运算符

print(f”矩阵乘法 (@ 运算符):\n {matrix_a @ matrix_b}”)

元素级乘法

print(f”元素级乘法 (matrix_a * matrix_b):\n {matrix_a * matrix_b}”)
“`

6. 改变张量形状

常用的形状改变操作包括 reshapeviewsqueezeunsqueeze

“`python
tensor = torch.arange(9).reshape(3, 3) # 创建 3×3 张量
print(“原始张量:\n”, tensor)

reshape: 改变形状,可以返回一个新视图或副本

reshaped_tensor = tensor.reshape(9, 1)
print(“reshape为 9×1:\n”, reshaped_tensor)

view: 必须在内存中是连续的,通常更快

viewed_tensor = tensor.view(1, 9)
print(“view为 1×9:\n”, viewed_tensor)

unsqueeze: 增加一个维度

unsqueezed_tensor = tensor.unsqueeze(0) # 在第一个维度增加
print(“unsqueeze(0)后形状:”, unsqueezed_tensor.shape) # torch.Size([1, 3, 3])

squeeze: 移除维度大小为1的维度

squeezed_tensor = unsqueezed_tensor.squeeze(0)
print(“squeeze(0)后形状:”, squeezed_tensor.shape) # torch.Size([3, 3])
“`

7. NumPy与Tensor之间的转换

张量和NumPy数组之间可以方便地相互转换。

“`python

Tensor转NumPy

np_from_tensor = tensor.numpy()
print(f”Tensor转NumPy数组:\n {np_from_tensor}”)
print(f”NumPy数组的类型: {type(np_from_tensor)}”)

注意:如果张量在GPU上,需要先转到CPU

gpu_tensor = torch.tensor([1, 2, 3]).to(‘cuda’)

np_from_gpu_tensor = gpu_tensor.cpu().numpy()

NumPy转Tensor

tensor_from_np = torch.from_numpy(np_array)
print(f”NumPy数组转Tensor:\n {tensor_from_np}”)
print(f”Tensor的类型: {type(tensor_from_np)}”)
“`

8. GPU加速 (CUDA)

PyTorch可以无缝地利用NVIDIA GPU进行加速计算。

“`python

检查CUDA是否可用

if torch.cuda.is_available():
device = “cuda”
print(“GPU (CUDA) 可用。”)
else:
device = “cpu”
print(“GPU (CUDA) 不可用,使用CPU。”)

将张量移动到GPU

tensor_on_gpu = torch.ones(5, 5, device=device)
print(f”在 {device} 上的张量:\n {tensor_on_gpu}”)

将NumPy数组转换为GPU上的张量

np_array_gpu = np.array([[1, 2], [3, 4]])
tensor_from_np_on_gpu = torch.from_numpy(np_array_gpu).to(device)
print(f”从NumPy创建并在 {device} 上的张量:\n {tensor_from_np_on_gpu}”)

在GPU上执行计算

result_on_gpu = tensor_on_gpu * 2
print(f”在 {device} 上计算结果:\n {result_on_gpu}”)

将结果移回CPU (如果需要NumPy操作或在CPU上查看)

result_on_cpu = result_on_gpu.to(“cpu”)
print(f”移回CPU的张量:\n {result_on_cpu}”)
“`

通过这些基本操作,您已经掌握了PyTorch中张量的创建、属性查看、索引、连接、数学运算、形状改变以及如何在CPU和GPU之间进行数据迁移。这些是构建和训练深度学习模型的基石。接下来,我们将通过一个简单的例子来构建一个神经网络。


构建一个简单的神经网络:线性回归

为了更好地理解PyTorch如何工作,我们将通过一个经典的线性回归问题来构建、训练和评估一个简单的神经网络。

1. 数据准备

首先,我们需要一些训练数据。这里我们生成一个简单的线性关系数据,并加入一些噪声。

“`python
import torch
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt

1.1 生成模拟数据

真实的线性关系:y = 2 * x + 1 + 噪声

X = torch.randn(100, 1) * 10 # 100个样本,每个样本1个特征
y = 2 * X + 1 + torch.randn(100, 1) * 2 # 目标值,加入噪声

绘制数据点

plt.figure(figsize=(8, 6))
plt.scatter(X.numpy(), y.numpy(), label=’原始数据点’)
plt.xlabel(‘X’)
plt.ylabel(‘y’)
plt.title(‘模拟线性回归数据’)
plt.legend()
plt.show()

检查数据形状

print(f”X 形状: {X.shape}”) # torch.Size([100, 1])
print(f”y 形状: {y.shape}”) # torch.Size([100, 1])

确定设备 (GPU或CPU)

device = ‘cuda’ if torch.cuda.is_available() else ‘cpu’
X = X.to(device)
y = y.to(device)
print(f”数据已移动到 {device}”)
“`

2. 定义模型

我们定义一个简单的线性回归模型,它将继承 nn.Module

“`python

2.1 定义线性回归模型

class LinearRegression(nn.Module):
def init(self):
super(LinearRegression, self).init()
# 定义一个全连接层 (线性层),输入特征1个,输出特征1个
self.linear = nn.Linear(in_features=1, out_features=1)

def forward(self, x):
    # 定义前向传播,输入x通过线性层
    return self.linear(x)

实例化模型并将其移动到指定设备

model = LinearRegression().to(device)
print(“模型结构:\n”, model)
“`

3. 定义损失函数和优化器

  • 损失函数 (Loss Function):衡量模型预测值与真实值之间的差距。对于线性回归,常用的有均方误差 (MSE)。
  • 优化器 (Optimizer):根据损失函数计算出的梯度来更新模型的参数(权重和偏置)。这里我们使用随机梯度下降 (SGD)。

“`python

3.1 定义损失函数 (均方误差)

criterion = nn.MSELoss()

3.2 定义优化器 (随机梯度下降)

model.parameters() 会自动获取模型中所有可训练的参数

optimizer = torch.optim.SGD(model.parameters(), lr=0.01) # 学习率 (learning rate) 设置为 0.01
“`

4. 训练模型

训练过程涉及多个迭代 (epochs)。在每个epoch中,我们执行以下步骤:
1. 前向传播: 模型对输入数据进行预测。
2. 计算损失: 计算预测值与真实值之间的损失。
3. 反向传播: 计算损失相对于模型参数的梯度。
4. 参数更新: 优化器根据梯度更新模型参数。
5. 梯度清零: 清除之前的梯度,以避免累积。

“`python

4.1 训练循环

num_epochs = 100
for epoch in range(num_epochs):
# 前向传播
outputs = model(X)
loss = criterion(outputs, y)

# 反向传播和优化
optimizer.zero_grad() # 清除之前的梯度
loss.backward()       # 反向传播,计算梯度
optimizer.step()      # 更新模型参数

if (epoch+1) % 10 == 0:
    print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')

print(“\n训练完成!”)
“`

5. 模型评估与可视化

训练完成后,我们可以使用训练好的模型进行预测,并将其与原始数据一起可视化,观察模型的拟合效果。

“`python

5.1 将模型切换到评估模式

在评估模式下,像Dropout和BatchNorm这样的层会表现不同

model.eval()

5.2 禁用梯度计算 (在评估或推理阶段不需要计算梯度)

with torch.no_grad():
predicted = model(X).cpu().numpy() # 将预测结果移回CPU并转换为NumPy数组

5.3 获取训练后的模型参数

权重 (weight) 和偏置 (bias)

trained_weight = model.linear.weight.item()
trained_bias = model.linear.bias.item()
print(f”\n训练后的模型参数: 权重 (Weight) = {trained_weight:.4f}, 偏置 (Bias) = {trained_bias:.4f}”)

5.4 可视化训练结果

plt.figure(figsize=(8, 6))
plt.scatter(X.cpu().numpy(), y.cpu().numpy(), label=’原始数据点’)
plt.plot(X.cpu().numpy(), predicted, color=’red’, label=’拟合直线’)
plt.xlabel(‘X’)
plt.ylabel(‘y’)
plt.title(‘线性回归拟合结果’)
plt.legend()
plt.show()
“`

通过这个简单的线性回归示例,您已经体验了PyTorch构建、训练和评估神经网络的完整流程。这个流程是所有深度学习任务的基础。


进阶主题 (简述)

在您掌握了PyTorch的基本概念和模型训练流程之后,可以进一步探索以下进阶主题,它们将帮助您构建更复杂、更高效的深度学习系统。

1. 数据加载与预处理 (DataLoader)

在实际的深度学习任务中,数据量往往非常庞大,无法一次性加载到内存。torch.utils.data.Datasettorch.utils.data.DataLoader 提供了高效、灵活的数据加载和批处理机制。

  • Dataset: 定义如何获取单个数据样本和其对应的标签。您可以继承 Dataset 类并实现 __len__ (返回数据集大小) 和 __getitem__ (返回指定索引的数据样本) 方法。
  • DataLoader: 在 Dataset 的基础上,提供迭代器,用于批量加载数据、打乱数据、多进程数据加载等功能,极大地提升了数据处理效率。

“`python
from torch.utils.data import Dataset, DataLoader

示例:自定义一个简单的Dataset

class CustomDataset(Dataset):
def init(self, data, labels):
self.data = data
self.labels = labels

def __len__(self):
    return len(self.data)

def __getitem__(self, idx):
    return self.data[idx], self.labels[idx]

假设有一些数据和标签

X_data = torch.randn(100, 10) # 100个样本,每个10个特征
y_labels = torch.randint(0, 2, (100,)) # 100个标签,0或1

创建Dataset实例

dataset = CustomDataset(X_data, y_labels)

创建DataLoader实例

dataloader = DataLoader(dataset, batch_size=16, shuffle=True, num_workers=0) # num_workers > 0 可开启多进程加载

迭代数据

for batch_X, batch_y in dataloader:
# 您的模型训练代码将在这里
# print(f”Batch X shape: {batch_X.shape}, Batch y shape: {batch_y.shape}”)
break # 只展示一个批次
print(“DataLoader 使用示例完成。”)
“`

2. 迁移学习与预训练模型

迁移学习是深度学习中一个非常强大的技术,它利用在一个大型数据集(如ImageNet)上预训练好的模型来解决相关任务。PyTorch的 torchvision.models 模块提供了许多流行的预训练模型(如ResNet, VGG, AlexNet等)。

  • 加载预训练模型: model = torchvision.models.resnet18(pretrained=True)
  • 冻结部分层: 冻结预训练模型的前几层参数,只训练后面的新加层,以利用其学到的通用特征。
  • 替换或修改顶层: 根据新任务的类别数量,替换模型的最后一层全连接层。

迁移学习可以显著减少模型训练时间,并在数据量较小的任务上取得更好的性能。

3. 模型保存与加载

训练好的模型通常需要保存以便后续使用(如推理、继续训练)。PyTorch提供了两种主要的保存方式:

  • 保存/加载整个模型 (不推荐)
    python
    # 保存
    # torch.save(model, "full_model.pth")
    # 加载
    # model = torch.load("full_model.pth")

    这种方式会将整个模型结构和参数都序列化,但在不同版本或不同设备上可能存在兼容性问题。

  • 保存/加载模型的状态字典 (推荐)
    状态字典 (state_dict) 是一个Python字典,它存储了模型所有参数(权重和偏置)的映射。
    “`python
    # 保存模型的state_dict
    torch.save(model.state_dict(), “model_weights.pth”)

    加载模型

    首先需要重新实例化模型 (确保模型结构一致)

    new_model = LinearRegression() # 假设是上面定义的线性回归模型

    new_model.load_state_dict(torch.load(“model_weights.pth”))

    new_model.eval() # 切换到评估模式

    print(“模型保存与加载 (state_dict) 示例完成。”)
    “`
    这种方式更加灵活,可以方便地修改模型结构后加载部分预训练权重。

4. TensorBoard 可视化

TensorBoard是Google开发的神经网络训练可视化工具,PyTorch通过 torch.utils.tensorboard.SummaryWriter 提供了对它的集成支持。
您可以记录训练过程中的损失、准确率、模型结构、权重分布、图像等,并通过Web界面实时监控训练进展。

“`python

from torch.utils.tensorboard import SummaryWriter

writer = SummaryWriter(‘runs/my_experiment’)

writer.add_scalar(‘Loss/train’, loss, epoch)

writer.add_graph(model, X_batch)

writer.close()

print(“TensorBoard 可视化 (简述) 完成。”)
“`

这些进阶主题是PyTorch在实际应用中非常重要的组成部分。随着您对PyTorch的深入了解,它们将帮助您解决更复杂的深度学习挑战。


结论

通过本文的介绍,您应该已经对PyTorch有了一个全面的认识。我们从PyTorch的背景和优势出发,深入探讨了其核心概念——张量、自动求导和nn.Module,它们是PyTorch强大而灵活的基础。随后,我们提供了详细的安装指南,并通过一个线性回归的实践案例,逐步演示了如何使用PyTorch进行数据准备、模型定义、损失函数与优化器选择、模型训练以及最终的评估与可视化。

PyTorch以其”Pythonic”的风格、动态计算图以及对研究和开发的友好性,成为了深度学习领域不可或缺的工具。无论是学术研究还是工业应用,PyTorch都能提供强大的支持。

这仅仅是PyTorch深度学习之旅的开始。未来,您可以进一步探索更复杂的神经网络结构(如CNN、RNN、Transformer)、更高级的优化策略、分布式训练、以及如何将模型部署到实际应用中。PyTorch社区活跃,文档丰富,是您持续学习和成长路上的宝贵资源。

希望本文能为您打开深度学习的大门,并激发您使用PyTorch创造更多可能的热情!祝您在深度学习的世界中探索愉快,收获丰硕!
“`

This completes the request to write an article about “PyTorch简介:深度学习入门与实践”.
“`

滚动至顶部