Python与深度学习库PyTorch进阶

在这里插入图片描述

一、从零到英雄:PyTorch快速入门指南

什么是PyTorch?为什么选择它作为你的深度学习伙伴?

想象一下,你是一位厨师,正在厨房里准备一道复杂的菜肴。你需要各种工具和食材来完成这道菜。在深度学习的世界中,PyTorch就是你的多功能厨房,它不仅提供了丰富的工具和材料,还让你能够以一种灵活且直观的方式进行创作。

PyTorch是由Facebook的人工智能研究团队开发的一个开源深度学习框架。它以其动态计算图、易用性和强大的社区支持而闻名。相比于静态计算图的TensorFlow,PyTorch允许你在运行时定义网络结构,这意味着你可以像写普通Python代码一样编写神经网络,这让调试和迭代变得非常方便。

安装PyTorch:让Python环境拥抱未来科技

安装PyTorch就像给你的电脑安装了一个超级引擎。你可以通过pip轻松安装PyTorch:

pip install torch torchvision torchaudio

确保你已经安装了最新版本的Python(建议3.7或更高版本)以及必要的依赖项。如果你想要使用GPU加速,还需要安装CUDA相关的库,并选择合适的PyTorch版本。例如,如果你有NVIDIA GPU并且安装了CUDA 11.0,可以这样安装:

pip install torch torchvision torchaudio -f https://download.pytorch.org/whl/cu110/torch_stable.html

第一个神经网络:用几行代码点亮机器学习的火花

让我们从最简单的神经网络开始——一个用于二分类任务的全连接层网络。假设我们有一个数据集,其中每个样本有两个特征,目标是将这些样本分为两类。

import torch
import torch.nn as nn
import torch.optim as optim

# 创建一个简单的全连接层网络
class SimpleNet(nn.Module):
    def __init__(self):
        super(SimpleNet, self).__init__()
        self.fc = nn.Linear(2, 1)  # 输入维度为2,输出维度为1

    def forward(self, x):
        return torch.sigmoid(self.fc(x))

# 实例化模型
model = SimpleNet()

# 定义损失函数和优化器
criterion = nn.BCELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)

# 假设我们有一些训练数据
inputs = torch.tensor([[0.5, 0.2], [0.8, 0.9]], dtype=torch.float32)
labels = torch.tensor([0, 1], dtype=torch.float32).view(-1, 1)

# 训练模型
for epoch in range(100):  # 进行100次迭代
    optimizer.zero_grad()  # 清除梯度
    outputs = model(inputs)  # 前向传播
    loss = criterion(outputs, labels)  # 计算损失
    loss.backward()  # 反向传播
    optimizer.step()  # 更新参数

print("训练完成!")

这段代码展示了如何创建一个简单的神经网络,并对其进行训练。虽然这是一个非常基础的例子,但它涵盖了构建神经网络的基本步骤:定义网络结构、初始化模型、设置损失函数和优化器,然后进行前向传播和反向传播。

张量的力量:如何使用张量进行高效计算

在PyTorch中,一切操作都围绕着张量(Tensors)。张量可以看作是多维数组,但它们不仅仅是存储数据的容器;它们还支持大量的数学运算,这些运算是深度学习的核心。

下面是一个例子,展示如何创建张量并进行基本的数学运算:

import torch

# 创建一个张量
x = torch.tensor([[1, 2], [3, 4]], dtype=torch.float32)
print("原始张量:")
print(x)

# 对张量进行加法运算
y = x + 2
print("\n加法后的张量:")
print(y)

# 对张量进行乘法运算
z = x * 2
print("\n乘法后的张量:")
print(z)

# 对张量进行矩阵乘法
a = torch.tensor([[5, 6], [7, 8]], dtype=torch.float32)
b = torch.matmul(x, a)
print("\n矩阵乘法后的张量:")
print(b)

在这个例子中,我们创建了一个2x2的张量x,并对它进行了加法、乘法和矩阵乘法运算。这些基本的张量操作是构建复杂神经网络的基础。

二、构建你的第一个深度学习项目:手写数字识别实战

数据集探秘:MNIST数据集初体验

MNIST数据集是深度学习领域中最著名的数据集之一,包含了大量手写数字的图像。每张图片都是28x28像素的灰度图,标签是从0到9的整数。这个数据集非常适合用来测试和学习新的算法。

我们可以使用torchvision库来加载MNIST数据集,并进行预处理:

import torch
from torchvision import datasets, transforms

# 定义数据转换
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.1307,), (0.3081,))
])

# 加载训练数据
train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=64, shuffle=True)

# 加载测试数据
test_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform)
test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=1000, shuffle=False)

# 查看一个批次的数据
images, labels = next(iter(train_loader))
print(f"Batch size: {images.size()}")  # 输出: Batch size: torch.Size([64, 1, 28, 28])

这段代码首先定义了数据转换,包括将图像转换为张量和归一化。然后,我们分别加载了训练集和测试集,并创建了数据加载器。最后,我们打印出一个批次的数据大小,可以看到每个批次包含64个样本,每个样本是一个28x28的灰度图像。

搭建模型:定义卷积神经网络架构

对于MNIST这样的图像分类任务,卷积神经网络(CNN)是非常有效的。下面是一个简单的CNN架构,它包含两个卷积层、两个最大池化层和两个全连接层:

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

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 32, kernel_size=3)  # 输入通道数为1,输出通道数为32
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3)  # 输入通道数为32,输出通道数为64
        self.dropout1 = nn.Dropout2d(0.25)
        self.dropout2 = nn.Dropout2d(0.5)
        self.fc1 = nn.Linear(9216, 128)  # 输入维度为9216,输出维度为128
        self.fc2 = nn.Linear(128, 10)  # 输入维度为128,输出维度为10

    def forward(self, x):
        x = self.conv1(x)
        x = F.relu(x)
        x = self.conv2(x)
        x = F.relu(x)
        x = F.max_pool2d(x, 2)
        x = self.dropout1(x)
        x = torch.flatten(x, 1)
        x = self.fc1(x)
        x = F.relu(x)
        x = self.dropout2(x)
        x = self.fc2(x)
        output = F.log_softmax(x, dim=1)
        return output

# 实例化模型
model = Net()
print(model)

这段代码定义了一个简单的CNN模型。它首先通过两个卷积层提取特征,然后通过最大池化层减少特征图的尺寸,接着使用Dropout层防止过拟合,最后通过两个全连接层输出分类结果。

训练模型:调整参数,见证奇迹的发生

有了数据集和模型后,下一步就是训练模型。我们需要定义损失函数、优化器,并进行多个epoch的训练:

import torch.optim as optim

# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 训练模型
num_epochs = 5
for epoch in range(num_epochs):
    for batch_idx, (data, target) in enumerate(train_loader):
        optimizer.zero_grad()  # 清除梯度
        output = model(data)  # 前向传播
        loss = criterion(output, target)  # 计算损失
        loss.backward()  # 反向传播
        optimizer.step()  # 更新参数

        if batch_idx % 100 == 0:
            print(f"Epoch [{epoch+1}/{num_epochs}], Step [{batch_idx+1}/{len(train_loader)}], Loss: {loss.item():.4f}")

print("训练完成!")

在这段代码中,我们设置了交叉熵损失函数和Adam优化器。然后,我们在指定的epoch数量内对每个批次的数据进行前向传播、计算损失、反向传播和参数更新。每隔100个批次,我们会打印当前的损失值,以便观察训练过程中的变化。

测试模型:评估性能,精益求精

训练完成后,我们需要在测试集上评估模型的性能。这可以通过计算准确率来实现:

# 在测试集上评估模型
model.eval()
test_loss = 0
correct = 0

with torch.no_grad():
    for data, target in test_loader:
        output = model(data)
        test_loss += criterion(output, target).item()  # 累加批量损失
        pred = output.argmax(dim=1, keepdim=True)  # 获取最大概率对应的类别
        correct += pred.eq(target.view_as(pred)).sum().item()

test_loss /= len(test_loader.dataset)

print(f'\nTest set: Average loss: {test_loss:.4f}, Accuracy: {correct}/{len(test_loader.dataset)} ({100. * correct / len(test_loader.dataset):.0f}%)\n')

这段代码首先将模型设置为评估模式,然后遍历测试集中的所有数据,计算预测结果,并统计正确的预测数量。最后,我们计算平均损失和准确率,并打印出来。通过这种方式,我们可以了解模型在未见过的数据上的表现。

三、自动求导的艺术:Autograd带你飞

Autograd简介:轻松实现梯度下降

在深度学习中,梯度下降是优化模型参数的关键方法。手动计算梯度不仅繁琐而且容易出错。幸运的是,PyTorch提供了一个强大的自动求导系统——Autograd,它能够自动计算任意可微分函数的梯度。

下面是一个简单的例子,展示如何使用Autograd计算一个简单函数的梯度:

import torch

# 创建一个张量,并设置requires_grad=True来追踪其计算历史
x = torch.tensor(2.0, requires_grad=True)

# 定义一个函数
y = x ** 2 + 3 * x + 1

# 计算梯度
y.backward()

# 打印梯度
print(f"x的梯度: {x.grad}")

在这个例子中,我们创建了一个标量张量x,并通过设置requires_grad=True来告诉PyTorch我们需要跟踪它的计算历史。然后,我们定义了一个关于x的二次函数y。调用y.backward()后,PyTorch会自动计算y关于x的梯度,并将其存储在x.grad中。

动态计算图的魅力:灵活应对复杂场景

与静态计算图不同,PyTorch的Autograd系统是基于动态计算图的。这意味着你可以在运行时动态地构建计算图,这为复杂的控制流和循环结构提供了极大的灵活性。

下面是一个更复杂的例子,展示了如何在循环中使用Autograd:

import torch

# 创建一个张量,并设置requires_grad=True
x = torch.tensor(2.0, requires_grad=True)

# 初始化一个累积变量
accumulated = torch.tensor(0.0, requires_grad=True)

# 循环计算累积和
for i in range(5):
    accumulated = accumulated + x ** (i + 1)

# 计算梯度
accumulated.backward()

# 打印梯度
print(f"x的梯度: {x.grad}")

在这个例子中,我们通过一个循环来累加x的幂次和。尽管计算图是在循环中动态构建的,Autograd仍然能够正确地计算梯度。

自定义损失函数:不再受限于现成工具

有时候,标准的损失函数可能无法满足特定需求。幸运的是,Autograd允许你自定义损失函数,并自动计算其梯度。下面是一个自定义损失函数的例子:

import torch

# 定义一个自定义损失函数
def custom_loss(output, target):
    diff = output - target
    return (diff ** 2).mean()

# 创建一些示例数据
output = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
target = torch.tensor([1.5, 2.5, 3.5])

# 计算损失
loss = custom_loss(output, target)

# 计算梯度
loss.backward()

# 打印梯度
print(f"输出张量的梯度: {output.grad}")

在这个例子中,我们定义了一个简单的均方误差损失函数custom_loss,并在计算损失后调用backward()来计算梯度。这样,即使使用自定义损失函数,我们也可以利用Autograd的强大功能。

实践案例:创建自己的优化器

除了使用PyTorch内置的优化器外,你还可以根据需要创建自己的优化器。下面是一个简单的自定义优化器的例子:

class MyOptimizer:
    def __init__(self, parameters, learning_rate=0.01):
        self.parameters = list(parameters)
        self.learning_rate = learning_rate

    def step(self):
        with torch.no_grad():
            for param in self.parameters:
                param -= self.learning_rate * param.grad

    def zero_grad(self):
        for param in self.parameters:
            if param.grad is not None:
                param.grad.zero_()

# 创建一个简单的线性模型
model = nn.Linear(1, 1)
optimizer = MyOptimizer(model.parameters(), learning_rate=0.01)

# 生成一些示例数据
x = torch.tensor([[1.0], [2.0], [3.0]])
y = torch.tensor([[2.0], [4.0], [6.0]])

# 训练模型
for _ in range(100):
    optimizer.zero_grad()
    output = model(x)
    loss = (output - y).pow(2).mean()
    loss.backward()
    optimizer.step()

print("训练完成!")

在这个例子中,我们定义了一个名为MyOptimizer的类,它实现了基本的梯度下降算法。我们使用这个自定义优化器来训练一个简单的线性模型。通过这种方式,你可以根据具体需求定制优化策略,从而更好地适应不同的应用场景。

四、加速你的训练:利用GPU提升效率

GPU与CPU的较量:为何GPU是深度学习的首选?

在深度学习领域,GPU(图形处理单元)已经成为不可或缺的工具。这是因为GPU专为并行计算设计,具有数千个核心,可以同时执行大量计算任务。相比之下,传统的CPU(中央处理器)虽然在单线程性能上更强,但在处理大规模并行任务时就显得力不从心了。

当你在处理大型数据集和复杂的神经网络时,使用GPU可以显著加快训练速度。这对于科研人员和工程师来说至关重要,因为它意味着可以更快地迭代模型,节省宝贵的时间。

设置PyTorch以支持CUDA:让你的代码跑得更快

要让PyTorch利用GPU进行计算,首先需要确保你的系统已经安装了CUDA和cuDNN。然后,在代码中只需要几行配置就可以让模型和数据转移到GPU上运行。

以下是如何检查是否有可用的GPU设备,并将模型和数据移动到GPU上的示例:

import torch
import torch.nn as nn

# 检查是否有可用的GPU设备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device: {device}")

# 创建一个简单的模型
model = nn.Sequential(
    nn.Linear(10, 5),
    nn.ReLU(),
    nn.Linear(5, 1)
)

# 将模型移动到GPU
model.to(device)

# 创建一些示例数据
data = torch.randn(10, 10).to(device)
target = torch.randn(10, 1).to(device)

# 前向传播
output = model(data)
print(f"Output on {device}: {output}")

在这段代码中,我们首先检查是否有可用的GPU设备,并将模型和数据移动到该设备上。通过这种方式,我们可以充分利用GPU的强大计算能力。

数据并行处理:充分利用多GPU资源

如果你有多个GPU,可以使用PyTorch的数据并行功能来进一步加速训练。数据并行允许你将一批数据分散到多个GPU上进行并行处理,从而提高训练速度。

下面是一个使用DataParallel模块进行数据并行处理的例子:

import torch
import torch.nn as nn
from torch.nn.parallel import DataParallel

# 检查是否有可用的GPU设备
if torch.cuda.device_count() > 1:
    print(f"{torch.cuda.device_count()} GPUs available. Using DataParallel.")
    model = DataParallel(model)

# 将模型移动到GPU
model.to(device)

# 创建一些示例数据
data = torch.randn(10, 10).to(device)
target = torch.randn(10, 1).to(device)

# 前向传播
output = model(data)
print(f"Output on {device}: {output}")

在这段代码中,我们使用DataParallel模块将模型包装起来,并将其移动到多个GPU上。这样,一批数据会被自动分割并分配到各个GPU上进行并行处理。

性能优化技巧:小改变带来大不同

除了使用GPU和数据并行外,还有一些其他的小技巧可以帮助你进一步提升训练效率:

  • 混合精度训练:使用半精度浮点数(FP16)代替全精度浮点数(FP32),可以显著减少内存占用和计算时间。
  • 梯度累积:对于显存较小的情况,可以通过梯度累积来模拟更大的批次大小。
  • 异步数据加载:使用DataLoader的多进程加载数据,可以减少数据加载的等待时间。
  • 模型剪枝和量化:通过剪枝和量化技术减少模型的参数量,从而加快推理速度。

五、迁移学习与微调:站在巨人的肩膀上

迁移学习基础:理解何时以及如何应用

迁移学习是一种有效的方法,它利用预先训练好的模型来解决新问题。这种方法特别适用于那些数据量较少的任务,因为预训练模型已经在大规模数据集上学习到了丰富的特征表示。

迁移学习通常涉及以下几个步骤:

  1. 选择一个预训练模型。
  2. 冻结预训练模型的部分层,以保留学到的特征。
  3. 添加新的层或修改现有层以适应新任务。
  4. 在新任务的数据集上微调模型。

预训练模型的优势:节省时间和计算资源

预训练模型通常是在大规模数据集上训练得到的,如ImageNet。这些模型已经学习到了许多有用的特征,可以直接应用于新的任务,而不需要从头开始训练。这不仅节省了大量的时间和计算资源,还能提高模型在新任务上的性能。

微调预训练模型:为特定任务定制化调整

微调是指在新任务的数据集上对预训练模型进行少量的训练。通常,我们会冻结预训练模型的大部分层,只对顶层进行微调,或者添加新的全连接层来进行分类或其他任务。

下面是一个微调预训练ResNet模型的例子:

import torch
import torch.nn as nn
import torchvision.models as models

# 加载预训练的ResNet模型
model = models.resnet18(pretrained=True)

# 冻结预训练模型的所有层
for param in model.parameters():
    param.requires_grad = False

# 替换最后的全连接层
num_features = model.fc.in_features
model.fc = nn.Linear(num_features, 10)  # 假设我们有10个类别

# 将模型移动到GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)

# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.fc.parameters(), lr=0.001)

# 加载数据集
# ...

# 微调模型
num_epochs = 10
for epoch in range(num_epochs):
    for inputs, labels in dataloader:
        inputs, labels = inputs.to(device), labels.to(device)
        
        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

    print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}")

print("微调完成!")

在这个例子中,我们加载了预训练的ResNet-18模型,并冻结了所有的层。然后,我们替换了最后的全连接层以适应新的分类任务,并在新任务的数据集上进行了微调。

实战演练:基于ImageNet的图像分类任务微调

假设我们要在一个小型图像分类数据集上进行微调,比如CIFAR-10。我们可以使用预训练的ResNet模型作为起点,并在CIFAR-10数据集上进行微调。

import torch
import torch.nn as nn
import torch.optim as optim
import torchvision.transforms as transforms
import torchvision.datasets as datasets
import torchvision.models as models
import torch.utils.data as data

# 加载预训练的ResNet模型
model = models.resnet18(pretrained=True)

# 冻结预训练模型的所有层
for param in model.parameters():
    param.requires_grad = False

# 替换最后的全连接层
num_features = model.fc.in_features
model.fc = nn.Linear(num_features, 10)  # CIFAR-10有10个类别

# 将模型移动到GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)

# 定义数据转换
transform = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])

# 加载CIFAR-10数据集
train_dataset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
train_loader = data.DataLoader(train_dataset, batch_size=64, shuffle=True)

test_dataset = datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
test_loader = data.DataLoader(test_dataset, batch_size=64, shuffle=False)

# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.fc.parameters(), lr=0.001, momentum=0.9)

# 微调模型
num_epochs = 10
for epoch in range(num_epochs):
    model.train()
    running_loss = 0.0
    for inputs, labels in train_loader:
        inputs, labels = inputs.to(device), labels.to(device)
        
        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        
        running_loss += loss.item()
    
    print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {running_loss/len(train_loader):.4f}")

print("微调完成!")

# 评估模型
model.eval()
correct = 0
total = 0
with torch.no_grad():
    for inputs, labels in test_loader:
        inputs, labels = inputs.to(device), labels.to(device)
        outputs = model(inputs)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

print(f"Accuracy of the network on the 10000 test images: {100 * correct / total:.2f}%")

在这个例子中,我们加载了预训练的ResNet-18模型,并对其进行了微调以适应CIFAR-10数据集。我们冻结了预训练模型的所有层,只对新的全连接层进行训练。通过这种方式,我们可以在短时间内获得一个高性能的图像分类模型。


嘿!欢迎光临我的小小博客天地——这里就是咱们畅聊的大本营!能在这儿遇见你真是太棒了!我希望你能感受到这里轻松愉快的氛围,就像老朋友围炉夜话一样温馨。


这里不仅有好玩的内容和知识等着你,还特别欢迎你畅所欲言,分享你的想法和见解。你可以把这里当作自己的家,无论是工作之余的小憩,还是寻找灵感的驿站,我都希望你能在这里找到属于你的那份快乐和满足。
让我们一起探索新奇的事物,分享生活的点滴,让这个小角落成为我们共同的精神家园。快来一起加入这场精彩的对话吧!无论你是新手上路还是资深玩家,这里都有你的位置。记得在评论区留下你的足迹,让我们彼此之间的交流更加丰富多元。期待与你共同创造更多美好的回忆!


欢迎来鞭笞我:master_chenchen


【内容介绍】

  • 【算法提升】:算法思维提升,大厂内卷,人生无常,大厂包小厂,呜呜呜。卷到最后大家都是地中海。
  • 【sql数据库】:当你在海量数据中迷失方向时,SQL就像是一位超级英雄,瞬间就能帮你定位到宝藏的位置。快来和这位神通广大的小伙伴交个朋友吧!
    【微信小程序知识点】:小程序已经渗透我们生活的方方面面,学习了解微信小程序开发是非常有必要的,这里将介绍微信小程序的各种知识点与踩坑记录。- 【python知识】:它简单易学,却又功能强大,就像魔术师手中的魔杖,一挥就能变出各种神奇的东西。Python,不仅是代码的艺术,更是程序员的快乐源泉!
    【AI技术探讨】:学习AI、了解AI、然后被AI替代、最后被AI使唤(手动狗头)

好啦,小伙伴们,今天的探索之旅就到这里啦!感谢你们一路相伴,一同走过这段充满挑战和乐趣的技术旅程。如果你有什么想法或建议,记得在评论区留言哦!要知道,每一次交流都是一次心灵的碰撞,也许你的一个小小火花就能点燃我下一个大大的创意呢!
最后,别忘了给这篇文章点个赞,分享给你的朋友们,让更多的人加入到我们的技术大家庭中来。咱们下次再见时,希望能有更多的故事和经验与大家分享。记住,无论何时何地,只要心中有热爱,脚下就有力量!


对了,各位看官,小生才情有限,笔墨之间难免会有不尽如人意之处,还望多多包涵,不吝赐教。咱们在这个小小的网络世界里相遇,真是缘分一场!我真心希望能和大家一起探索、学习和成长。虽然这里的文字可能不够渊博,但也希望能给各位带来些许帮助。如果发现什么问题或者有啥建议,请务必告诉我,让我有机会做得更好!感激不尽,咱们一起加油哦!


那么,今天的分享就到这里了,希望你们喜欢。接下来的日子里,记得给自己一个大大的拥抱,因为你真的很棒!咱们下次见,愿你每天都有好心情,技术之路越走越宽广!
在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值