【PyTorch深度学习入门】:新手指南,避开环境配置的常见陷阱
发布时间: 2024-12-07 00:20:15 阅读量: 4 订阅数: 11
PyTorch深度学习入门手册:PyTorch深度学习常用函数库解析及其应用指南
![【PyTorch深度学习入门】:新手指南,避开环境配置的常见陷阱](https://opengraph.githubassets.com/e3c87bf94cf98029e01def5ee08c6ba1f0d35992810af05b774e8a2d0f5e85d3/pytorch/pytorch)
# 1. PyTorch深度学习基础
## 1.1 了解PyTorch的历史与架构
PyTorch是由Facebook的人工智能研究团队于2016年推出的一个开源机器学习库。它基于Torch,并且拥有一个直观和灵活的API设计,使得其在学术界和工业界都得到了广泛的认可和应用。作为动态计算图(define-by-run approach)的代表,PyTorch允许开发者以一种更为直观和动态的方式构建和调试深度学习模型。
## 1.2 初识PyTorch的核心概念
PyTorch中的核心概念包括张量(Tensor)、自动微分(Autograd)、神经网络模块(nn.Module)等。张量是PyTorch中的数据结构,可以看作是多维数组。自动微分是PyTorch能够提供梯度计算和反向传播的基石。而nn.Module是构建神经网络的基石,它将层、参数、前向传播函数封装在一起,使得模型定义更加模块化和可重用。
```python
import torch
# 张量创建示例
x = torch.tensor([1., 2., 3.], requires_grad=True)
```
## 1.3 PyTorch与深度学习
PyTorch支持深度学习的各个方面,包括但不限于图像处理、自然语言处理(NLP)、强化学习等。通过灵活的API,PyTorch允许开发者自定义复杂模型架构,以及进行高效的计算图操作。另外,PyTorch社区提供了大量的预训练模型和工具,方便开发者在此基础上进行迁移学习和项目开发。
```python
# 通过预训练模型进行迁移学习
model = torch.hub.load('pytorch/vision:v0.6.0', 'resnet50', pretrained=True)
```
通过本章的学习,您将对PyTorch有一个全面的了解,为后续章节中环境搭建、模型构建和实战项目打下坚实的基础。
# 2. PyTorch环境搭建与配置
### 2.1 PyTorch的安装选项
当准备在个人计算机或服务器上进行PyTorch深度学习项目时,一个流畅且符合需求的开发环境是不可或缺的。PyTorch提供了多种安装选项,可根据不同的操作系统、硬件设备以及使用习惯,选择最合适的安装方法。
#### 2.1.1 选择合适的安装方法
安装PyTorch的第一步是选择最符合个人需求的安装方法。以下是常见的几种安装选项:
- **命令行安装**:对于熟悉命令行操作的用户来说,使用conda或pip安装PyTorch是最直接的方法。通过指定不同的channel、版本和平台,可以快速地安装所需的PyTorch版本。比如,对于Linux系统,可以使用以下命令:
```bash
conda install pytorch torchvision torchaudio -c pytorch
```
或者
```bash
pip install torch torchvision torchaudio
```
- **预编译的二进制文件**:PyTorch提供了预编译的二进制文件,用户可以直接下载并安装,无需编译。这适用于不想等待长时间编译过程的用户。
- **Docker容器**:使用Docker可以快速搭建起一个隔离且一致的开发环境,容器中预装了PyTorch和所需依赖。使用Docker的安装命令可以非常方便地复制并部署相同的环境到不同的机器上。
- **源代码编译**:对于需要最新功能或特定功能的用户,从源代码编译PyTorch是可行的选项。这需要一定的编译时间和对编译过程的理解。
#### 2.1.2 安装过程中可能遇到的问题及解决方案
在安装PyTorch的过程中,可能会遇到各种问题,下面列举了一些常见的问题及其解决方案:
- **版本兼容问题**:确保所安装的PyTorch版本与CUDA版本兼容。不兼容的版本会导致运行时错误。可以使用PyTorch提供的CUDA版本兼容性表来核对。
- **依赖冲突**:在使用conda或pip安装时,可能会遇到依赖包冲突的问题。使用虚拟环境可以有效避免这种情况。
- **安装速度慢**:国内用户在使用国外服务器下载安装包时可能会遇到下载速度慢的问题。可以考虑使用国内镜像源,比如清华大学的镜像源。
### 2.2 环境配置的最佳实践
为了提供一个高效且稳定的开发环境,用户可以遵循一些最佳实践来配置PyTorch环境。
#### 2.2.1 创建虚拟环境和隔离依赖
创建虚拟环境是隔离Python依赖和避免冲突的有效手段。推荐使用conda或Python的venv模块来创建虚拟环境。
使用conda创建环境的命令如下:
```bash
conda create -n pytorch_env python=3.8
conda activate pytorch_env
```
使用Python的venv创建环境的命令如下:
```bash
python -m venv pytorch_env
source pytorch_env/bin/activate # 在Linux或macOS下
pytorch_env\Scripts\activate # 在Windows下
```
#### 2.2.2 检查PyTorch与CUDA版本的兼容性
检查PyTorch是否支持所使用的CUDA版本是一项重要的步骤。可以通过访问PyTorch官方网站或者查看GitHub上的兼容性文档来确认所需的PyTorch版本。命令行中可以通过以下命令快速检查当前系统支持的CUDA版本:
```bash
nvcc --version
```
### 2.3 配置高级选项
随着项目的深入,用户可能会需要更多的高级配置来满足特定的开发和部署需求。
#### 2.3.1 使用Docker容器隔离开发环境
Docker容器为开发者提供了一种将应用及其运行环境打包的方法。使用Docker配置PyTorch环境可以帮助用户创建可重复且一致的开发环境,提高开发效率。下面是一个简单的Dockerfile示例,展示如何创建一个带有PyTorch的Docker镜像:
```Dockerfile
# 使用带有CUDA支持的基础镜像
FROM nvidia/cuda:10.2-cudnn7-devel-ubuntu18.04
# 安装依赖
RUN apt-get update && apt-get install -y \
build-essential \
cmake \
curl \
libjpeg-dev \
libpng-dev \
python3 \
python3-pip \
python-opencv \
wget
# 安装Python和PyTorch
RUN pip install torch torchvision
# 设置工作目录
WORKDIR /app
```
使用该Dockerfile,可以通过以下命令构建和运行Docker容器:
```bash
docker build -t pytorch_container .
docker run -it --rm --gpus all -v ${PWD}:/app pytorch_container
```
#### 2.3.2 配置Jupyter Notebook进行PyTorch开发
Jupyter Notebook提供了一种交互式的编程方式,非常适合进行深度学习实验。配置Jupyter Notebook时,需要确保安装了ipykernel包,并将相应的kernel添加到Jupyter中,以便在Notebook中运行Python代码。下面是配置Jupyter Notebook的步骤:
1. 首先,确保已经安装了Jupyter Notebook:
```bash
pip install notebook
```
2. 安装ipykernel,并创建一个内核:
```bash
pip install ipykernel
python -m ipykernel install --user --name pytorch_env --display-name "Python (PyTorch)"
```
3. 启动Jupyter Notebook:
```bash
jupyter notebook
```
通过这些步骤,用户就可以在Jupyter Notebook中享受PyTorch的便利,进行代码的编写和实验的执行。
# 3. PyTorch基本概念和工具
在本章节中,我们将深入探讨PyTorch的核心组件,如张量操作、自动梯度、计算图和反向传播。此外,我们还将介绍数据处理和加载工具,包括Dataset和DataLoader的使用,以及数据增强和预处理技巧。最后,我们会探讨如何构建和训练模型,包括序列模型的定义和实现,以及训练循环和验证过程。
## 3.1 PyTorch的核心组件
### 3.1.1 张量操作和自动梯度
PyTorch中的基本数据结构是张量(Tensor),它类似于NumPy的多维数组,但张量可以使用GPU进行加速计算。PyTorch使用动态计算图,使得自动梯度计算变得简单。自动梯度是深度学习中非常重要的特性,因为它允许模型根据损失函数自动计算梯度,并使用这些梯度来更新网络的权重。
下面是一个简单的张量操作和自动梯度计算的例子:
```python
import torch
# 创建一个张量
x = torch.tensor(2.0, requires_grad=True)
# 进行一些操作
y = x ** 2 + 3 * x + 5
# 计算y关于x的导数
y.backward()
# 输出x的梯度
print(x.grad) # 输出: tensor(10.)
```
在这个例子中,我们首先创建了一个需要梯度的张量`x`。接着我们定义了一个关于`x`的函数`y`,并通过调用`backward()`方法来自动计算`y`关于`x`的导数。最后,我们打印出了`x`的梯度。
### 3.1.2 计算图和反向传播
计算图是PyTorch中实现自动梯度计算的关键组件。每个操作都是计算图的一个节点,而张量是节点之间的边。计算图以动态的方式构建,可以捕捉所有操作的顺序和依赖关系。
下面是一个计算图构建和反向传播的例子:
```python
# 创建一个张量
x = torch.randn(3, requires_grad=True)
y = torch.randn(3)
# 定义操作
z = x * y
# 反向传播
z.mean().backward()
# 打印梯度
print(x.grad) # 输出: tensor([y[0].item(), y[1].item(), y[2].item()])
```
在这个例子中,我们首先创建了一个需要梯度的张量`x`和一个普通张量`y`。然后我们定义了一个操作`z`,它是`x`和`y`的逐元素乘积。调用`z.mean().backward()`将计算`z`关于`x`的平均值的梯度,并通过链式法则反向传播到`x`。最后我们打印出`x`的梯度。
## 3.2 数据处理和加载工具
### 3.2.1 Dataset和DataLoader的使用
PyTorch提供了`Dataset`类和`DataLoader`类,用于帮助用户在深度学习任务中加载和处理数据。`Dataset`类负责存储数据集并定义如何检索它们的单个元素。`DataLoader`则提供了批量、打乱数据以及多线程加载数据的功能。
下面是一个自定义`Dataset`类和使用`DataLoader`的例子:
```python
from torch.utils.data import Dataset, DataLoader
import os
import pandas as pd
# 自定义Dataset类
class MyDataset(Dataset):
def __init__(self, csv_file, transform=None):
"""
Args:
csv_file (string): CSV文件路径
transform (callable, optional): 一个可选的转换函数
"""
self.dataset_frame = pd.read_csv(csv_file)
self.transform = transform
def __len__(self):
return len(self.dataset_frame)
def __getitem__(self, idx):
if torch.is_tensor(idx):
idx = idx.tolist()
img_name = os.path.join(self.dataset_frame.iloc[idx, 0])
image = io.imread(img_name)
label = self.dataset_frame.iloc[idx, 1]
if self.transform:
image = self.transform(image)
return image, label
# 定义转换操作
transform = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
])
# 实例化数据集
dataset = MyDataset(csv_file='path/to/file.csv', transform=transform)
# 使用DataLoader加载数据
data_loader = DataLoader(dataset, batch_size=4, shuffle=True, num_workers=4)
# 迭代DataLoader
for images, labels in data_loader:
# 进行训练/验证操作...
```
在这个例子中,我们首先定义了一个继承自`Dataset`的`MyDataset`类,用于从CSV文件加载数据。我们还定义了一个转换操作的组合,该组合包括调整图像大小、居中裁剪以及转换为张量。接着实例化我们的数据集,并使用`DataLoader`进行批量、打乱以及多线程加载。
### 3.2.2 数据增强和预处理技巧
数据增强是提高模型泛化能力的一个重要手段,它通过对训练数据应用一系列随机变换来增加数据的多样性。而预处理则是指在训练模型之前对数据进行标准化、归一化等操作。PyTorch提供了一系列的数据变换工具,使得这些操作变得简单。
下面是一个数据增强的例子:
```python
from torchvision import transforms
import random
# 定义一系列数据增强操作
data_transforms = transforms.Compose([
transforms.RandomHorizontalFlip(),
transforms.RandomRotation(10),
transforms.RandomResizedCrop(224),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
# 应用数据增强
transformed_image = data_transforms(image)
```
在这个例子中,我们首先定义了一系列的数据增强操作,包括随机水平翻转、随机旋转、随机裁剪以及将图像转换为张量并进行标准化。然后我们应用这些变换到一个图像上。
## 3.3 模型构建和训练
### 3.3.1 序列模型的定义和实现
序列模型(也称为循环神经网络,RNN)是处理序列数据(如时间序列、文本、语音等)的强大工具。PyTorch通过`nn.Module`类提供了序列模型的基础结构。
下面是一个简单RNN模型定义和使用的例子:
```python
import torch.nn as nn
class SimpleRNN(nn.Module):
def __init__(self, input_size, hidden_size, num_layers, output_size):
super(SimpleRNN, self).__init__()
self.hidden_size = hidden_size
self.num_layers = num_layers
# 定义RNN层
self.rnn = nn.RNN(input_size, hidden_size, num_layers, batch_first=True)
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
# 初始化隐藏状态
h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
# 前向传播RNN
out, _ = self.rnn(x, h0)
# 只取最后一个时间步的输出
out = out[:, -1, :]
out = self.fc(out)
return out
# 实例化模型
model = SimpleRNN(input_size=10, hidden_size=20, num_layers=2, output_size=1)
# 使用模型进行前向传播
output = model(input_tensor)
```
在这个例子中,我们定义了一个简单的RNN模型,它包含一个RNN层和一个全连接层。`forward`方法中,我们初始化了一个隐藏状态,并将输入数据和隐藏状态传入RNN层。最后,我们获取最后一个时间步的输出,并通过一个全连接层生成最终的输出。
### 3.3.2 训练循环和验证过程
训练循环是深度学习模型学习过程的核心部分,它包括前向传播、计算损失、反向传播和更新模型权重。验证过程通常与训练循环分开进行,用于评估模型在未见数据上的性能。
下面是一个包含训练循环和验证过程的例子:
```python
# 定义损失函数和优化器
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# 训练循环
for epoch in range(num_epochs):
model.train()
for inputs, targets in train_loader:
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, targets)
loss.backward()
optimizer.step()
# 打印每个epoch的损失
print(f'Epoch {epoch}/{num_epochs}, Loss: {loss.item()}')
# 验证过程
model.eval()
with torch.no_grad():
for inputs, targets in validation_loader:
outputs = model(inputs)
loss = criterion(outputs, targets)
# 打印验证集上的平均损失
print(f'Validation Loss: {loss.item()}')
```
在这个例子中,我们首先定义了均方误差损失函数和Adam优化器。然后我们进入了训练循环,模型被设置为训练模式,遍历训练数据加载器`train_loader`,计算损失,执行反向传播,并更新模型参数。在每个epoch后,我们打印了训练损失。最后,我们进入验证模式,计算验证集上的损失。
在进行模型训练和验证时,我们通常需要记录损失和准确率等指标,以便于监控模型训练的过程,调整超参数,以及评估模型性能。
# 4. PyTorch实战项目入门
## 4.1 图像分类项目概述
在深度学习领域,图像分类是入门级的项目同时也是研究热点。构建一个高效的图像分类系统,对于理解深度学习的整个工作流程有着重要的意义。这一小节会介绍如何选择合适的预训练模型和准备数据集。
### 选择合适的预训练模型
选择预训练模型是图像分类任务中的第一步。这些模型通常在大量的数据集上进行训练,比如ImageNet,已经学会提取有效的特征。在PyTorch中,模型主要由torchvision.models提供。包含但不限于AlexNet、VGG、ResNet和MobileNet等。
```python
import torchvision.models as models
# 加载预训练的ResNet模型
resnet = models.resnet18(pretrained=True)
# 冻结模型参数,仅使用模型进行特征提取
for param in resnet.parameters():
param.requires_grad = False
# 修改模型最后的全连接层,以适应新数据集的分类任务
num_features = resnet.fc.in_features
resnet.fc = torch.nn.Linear(num_features, num_classes)
```
### 数据集的加载和预处理
数据预处理是深度学习中至关重要的步骤,直接影响模型的表现。使用PyTorch提供的数据加载工具,可以轻松地加载和预处理数据。
```python
from torchvision import transforms, datasets
from torch.utils.data import DataLoader
# 定义预处理操作
data_transforms = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])
# 加载数据集
train_dataset = datasets.ImageFolder(root='path/to/train', transform=data_transforms)
test_dataset = datasets.ImageFolder(root='path/to/test', transform=data_transforms)
# 创建数据加载器
train_loader = DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(dataset=test_dataset, batch_size=64, shuffle=False)
```
## 4.2 训练过程中的调试技巧
调试是训练模型不可或缺的一部分。正确地跟踪和监控训练过程,有助于提升模型的准确率并减少训练时间。
### 跟踪和监控训练过程
在训练过程中,通常需要跟踪和监控损失函数值和准确率。我们可以利用PyTorch的tensorboardX库来可视化训练过程。
```python
from tensorboardX import SummaryWriter
# 创建SummaryWriter来记录数据
writer = SummaryWriter(log_dir='runs/experiment_1')
# 在训练循环中记录数据
for epoch in range(num_epochs):
for batch_idx, (data, target) in enumerate(train_loader):
# 前向传播、反向传播、优化器步骤...
# 记录损失和准确率
writer.add_scalar('Loss/train', loss.item(), batch_idx)
writer.add_scalar('Accuracy/train', accuracy.item(), batch_idx)
# 验证过程
for data, target in validation_loader:
# 计算验证损失和准确率...
writer.add_scalar('Loss/validation', val_loss.item(), epoch)
writer.add_scalar('Accuracy/validation', val_accuracy.item(), epoch)
# 关闭SummaryWriter
writer.close()
```
### 调整超参数和防止过拟合
超参数的调整对于优化模型性能至关重要。使用交叉验证、正则化或早停法等技术可以有效防止模型过拟合。
```python
# 使用L2正则化
reg = torch.nn.L2Loss(size_average=False)
loss += reg(model.parameters(), lambda_param)
# 早停法
patience = 10
trigger_times = 0
for epoch in range(epochs):
if trigger_times >= patience:
break
# 训练和验证模型
# ...
# 如果验证准确率没有提高,则增加trigger_times
if val_acc < best_val_acc:
trigger_times += 1
else:
trigger_times = 0
```
## 4.3 项目部署和优化
完成模型训练后,下一个重要步骤是将模型部署到生产环境中,并进行性能优化,以满足实时应用的需求。
### 模型的保存和加载
为了方便模型的部署和未来使用,我们需要将训练好的模型保存到磁盘,并且能够重新加载。
```python
# 保存模型
torch.save(model.state_dict(), 'model.pth')
# 加载模型
model = TheModelClass(*args, **kwargs)
model.load_state_dict(torch.load('model.pth'))
model.eval()
```
### 使用GPU加速推理和部署
在有GPU的环境下,通过使用torch.cuda来进行推理,可以大幅减少响应时间。
```python
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model = model.to(device)
# 使用GPU进行推理
with torch.no_grad():
input_data = input_data.to(device)
output = model(input_data)
```
随着章节内容的深入,我们已经逐步深入到了PyTorch的实战应用。从选择预训练模型到构建数据集,再到调试和优化模型,每个环节都是为了达到更好的分类准确率。在实际应用中,需要将理论知识与项目实践相结合,不断优化流程,提高模型性能。
# 5. 深入理解PyTorch高级特性
## 5.1 自定义模块和函数
### 5.1.1 深入理解nn.Module和nn.Function
PyTorch 的核心是神经网络模块 `nn.Module`,它为构建和训练复杂神经网络提供了一个灵活的架构。自定义模块通常继承 `nn.Module`,并实现 `__init__` 和 `forward` 方法。通过重写这两个方法,我们可以创建具有特定行为的自定义层或整个模型。以下是一个简单的自定义模块示例:
```python
import torch
import torch.nn as nn
import torch.nn.functional as F
class CustomLayer(nn.Module):
def __init__(self, in_features, out_features):
super(CustomLayer, self).__init__()
self.weight = nn.Parameter(torch.randn(in_features, out_features))
self.bias = nn.Parameter(torch.randn(out_features))
def forward(self, x):
return F.relu(torch.matmul(x, self.weight) + self.bias)
# 实例化自定义层并进行前向传播
layer = CustomLayer(10, 5)
output = layer(torch.randn(3, 10))
```
### 5.1.2 编写自定义的损失函数和数据集
自定义损失函数需要继承 `nn.Module` 类,并实现 `__init__` 和 `forward` 方法。例如,我们可以编写一个简单的自定义均方误差损失函数:
```python
class CustomMSELoss(nn.Module):
def __init__(self):
super(CustomMSELoss, self).__init__()
def forward(self, input, target):
return torch.mean((input - target) ** 2)
```
自定义数据集需要继承 `torch.utils.data.Dataset` 类,并实现 `__len__` 和 `__getitem__` 方法。例如,我们可以创建一个简单的自定义数据集来加载图像和标签:
```python
from torch.utils.data import Dataset
class CustomImageDataset(Dataset):
def __init__(self, image_paths, labels):
self.image_paths = image_paths
self.labels = labels
def __len__(self):
return len(self.image_paths)
def __getitem__(self, idx):
image_path = self.image_paths[idx]
image = PIL.Image.open(image_path) # PIL 是 Python Imaging Library
label = self.labels[idx]
return image, label
```
## 5.2 并行计算与多GPU训练
### 5.2.1 深度学习中的并行策略
在 PyTorch 中,我们可以利用多种并行策略,例如使用多GPU训练、模型并行化和数据并行化。数据并行是 PyTorch 中常用的并行策略,它允许多个 GPU 同时处理不同的数据批次。例如:
```python
import torch.nn as nn
import torch.nn.parallel
class Model(nn.Module):
# ... 定义模型结构 ...
model = Model().cuda() # 将模型移动到 GPU
model = nn.DataParallel(model) # 数据并行化
```
### 5.2.2 利用多GPU进行模型训练和推理
当使用数据并行化时,PyTorch 会自动将输入数据分散到不同的 GPU 上。在训练循环中,你需要确保梯度在各 GPU 上同步。这可以通过调用 `model.module.zero_grad()` 或者使用 `model.zero_grad(set_to_none=True)` 来实现。以下是一个简单的训练循环示例:
```python
# 使用多个 GPU 训练模型
# optimizer: 优化器实例
for data in data_loader:
inputs, targets = data[0].cuda(), data[1].cuda() # 将数据移动到 GPU
optimizer.zero_grad() # 清除之前的梯度
outputs = model(inputs) # 前向传播
loss = loss_function(outputs, targets) # 计算损失
loss.backward() # 反向传播计算梯度
optimizer.step() # 更新权重
```
## 5.3 调试和性能分析
### 5.3.1 PyTorch中的调试工具和方法
为了调试 PyTorch 程序,我们可以使用 Python 的标准调试工具,例如 pdb。此外,PyTorch 提供了 `torch.autograd` 模块来分析计算图,这对于理解模型中复杂的梯度流动非常有帮助。例如,我们可以使用 `torch.autograd.profiler` 来分析代码的性能瓶颈:
```python
import torch.autograd.profiler as profiler
with profiler.profile(with_stack=True) as prof:
# 运行模型的训练和推理代码
# ...
print(prof.key_averages().table(sort_by="self_cpu_time_total"))
```
### 5.3.2 分析和优化模型性能的策略
性能分析通常涉及检查模型中耗时的操作,并找出瓶颈。我们可以利用 `torch.utils.bottleneck` 工具来分析模型的性能。它会运行一个小型的训练循环,并报告瓶颈。此外,通过减少模型中不必要的操作、优化计算图和合理利用 GPU 的内存和带宽,我们可以显著提高模型的性能。在进行性能优化时,遵循以下策略通常是有益的:
- 减少内存占用,例如通过减少批量大小或使用更轻量级的数据结构。
- 使用高效的卷积算法和算子融合技术。
- 利用 `torch.jit` 脚本或追踪来转换模型到更优化的表示形式。
- 分析和优化数据加载和预处理流程。
- 使用量化和剪枝技术减少模型的大小和推理时间。
通过这些步骤,我们可以有效地调试和优化 PyTorch 模型,确保其高效运行。
0
0