Pytorch框架

本文介绍了PyTorch框架下如何制作自定义数据集`MyDataset`,并使用`DataLoader`进行批量处理。讲解了Tensorboard的使用,展示了如何添加图像和 scalar 到日志。此外,还涵盖了照片格式转换、模型构建(VGG16)、损失函数和优化器的使用,以及模型的保存与加载。最后,讨论了在CPU和GPU上训练模型的方法,并给出了一个使用GPU训练的示例代码。

Pytorch框架

  • Pytorch官方文档:https://pytorch.org/docs/stable/index.html

(一)MyDataset制作

制作自己的MyDataSet以供dataloader使用,必须继承Dataset重写 init,getitem 函数。

import numpy as np
from tensorboardX.utils import make_grid
from torch.utils.data import Dataset, DataLoader
from PIL import Image
import os
from torchvision import transforms
from torch.utils.tensorboard import SummaryWriter

writer = SummaryWriter("logs")

class MyDataset(Dataset):
    def __init__(self, root_dir, image_dir, label_dir, transform):
        self.root_dir = root_dir #根目录名
        self.image_dir = image_dir #照片存储文件夹名
        self.label_dir = label_dir #标签存储文件夹名
        self.label_path = os.path.join(self.root_dir, self.label_dir)#标签存储路径
        self.image_path = os.path.join(self.root_dir, self.image_dir)#照片存储路径
        self.image_list = os.listdir(self.image_path)#列出该路径下的所有文件(照片)
        self.label_list = os.listdir(self.label_path)#列出该路径下的所有文件(标签)
        self.transform = transform #照片格式转换器
        # 因为label 和 Image文件名相同,进行一样的排序,可以保证取出的数据和label是一一对应的
        self.image_list.sort()
        self.label_list.sort()

    def __getitem__(self, idx):
        img_name = self.image_list[idx] #获取指定位置文件(照片)名
        label_name = self.label_list[idx] #获取指定位置文件(标签)名
        img_item_path = os.path.join(self.root_dir, self.image_dir, img_name)#照片路径
        label_item_path = os.path.join(self.root_dir, self.label_dir, label_name)#标签路径
        img = Image.open(img_item_path).convert("RGB")#转化为三通道

        with open(label_item_path, 'r') as f:
            label = f.readline() #读取标签文件标签

        # img = np.array(img)
        img = self.transform(img) #将照片转化成想要的格式一般是tensor类型
        sample = {'img': img, 'label': label}
        return sample #返回需要的样本

    def __len__(self):
        assert len(self.image_list) == len(self.label_list) #照片数应当与标签数相同
        return len(self.image_list) #返回样本数

#测试MyDataset数据集
if __name__ == '__main__':
        for epoch in range(10):
            #构造照片格式转化器
            transform = transforms.Compose([transforms.Resize((32,32)), transforms.ToTensor()])
            root_dir = "dataset/train"
            image_dir_name = "ants_image"
            label_dir_name = "ants_label"
            #构建数据集
            train_dataset = MyDataset(root_dir, image_dir_name, label_dir_name, transform)

            # transforms = transforms.Compose([transforms.Resize(256, 256)])
            #dataloader建立线程与样本时间的关系,shuffle:洗牌,num_workers:线程
            dataloader = DataLoader(train_dataset, batch_size=8, shuffle=True,num_workers=2)

            for index , sample in enumerate(dataloader): #返回一个元组 (0, sample[0])...
                 print(type(sample))
                 print(index, sample['img'].shape)
                 writer.add_image("train", make_grid(np.array(sample['img'])), index)
writer.close()

(二)DataLoader使用

import torchvision
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from tensorboardX.utils import make_grid
import numpy as np
# 准备的测试数据集
test_data = torchvision.datasets.CIFAR10("./dataset/CIFAR10", train=False, transform=torchvision.transforms.ToTensor())
test_loader = DataLoader(dataset=test_data, batch_size=24, shuffle=True, num_workers=2, drop_last=False)
writer = SummaryWriter("dataloader")

if __name__ == '__main__':
    print(test_data[0]['img'].shape)#更改数据集__getitem__返回字典
    for index, sample in enumerate(test_loader):  # 返回一个元组 (0, sample[0])...
        print(type(sample))
        print(index, sample['img'].shape)
        writer.add_image("train", make_grid(np.array(sample['img'])), index)
#     step=0
#     for sample in test_loader: #更改数据集__getitem__返回字典
#         print(type(sample["img"]))
#         writer.add_image("train", make_grid(np.array(sample['img'])), step)
#         step=step+1
writer.close()

(三)Tensorboard使用

from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter("logs")#日志存储文件夹
# title名,图像(torch.Tensor, numpy.array),下标,图像格式(高、宽、通道Channel)
writer.add_image("train", array_or_tensor_img, 1, dataformats='HWC')
for i in range(100):
    writer.add_scalar("y=2x", 3*i, i)
writer.close()

(四)Transform使用

照片格式转化,Resize默认三个通道同时变换。

transform = transforms.Compose([transforms.Resize((32,32)), transforms.ToTensor()])

(五)Model搭建

import torch
from torch import nn

# 搭建神经网络
class VGG16(nn.Module):
    def __init__(self):
        super(VGG16, self).__init__()
        self.model = nn.Sequential(
# 3inchaneel->32outchaneel:5x5kernelUnion[int, Tuple[int, int]],
# stride: Union[int, Tuple[int, int]] = 1, padding: Union[str, int, Tuple[int, int]] = 0,
# dilation: Union[int, Tuple[int, int]] = 1
            nn.Conv2d(3, 32, 5, 1, 2),
            nn.MaxPool2d(2), # help查看使用方法
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Flatten(), #摊平
            nn.Linear(64*4*4, 64),
            nn.Linear(64, 10)
        )
    def forward(self, x):
        x = self.model(x)
        return x

if __name__ == '__main__':
    VGG16 = VGG16()
    input = torch.ones((64, 3, 32, 32))
    output = VGG16(input)
    print(output.shape)

(六)Loss&Optimizer使用

import torch
from torch.nn import L1Loss
from torch import nn
from torch.nn import Sequential, Conv2d, MaxPool2d, Flatten, Linear

inputs = torch.tensor([1, 2, 3], dtype=torch.float32)
targets = torch.tensor([1, 2, 5], dtype=torch.float32)

inputs = torch.reshape(inputs, (1, 1, 1, 3))#1 batch_size,1chaneel,1array,3elemen
targets = torch.reshape(targets, (1, 1, 1, 3))#1 batch_size,1chaneel,1array,3elemen

loss = L1Loss(reduction='sum')
result = loss(inputs, targets)

loss_mse = nn.MSELoss()
result_mse = loss_mse(inputs, targets)
# print(result)
# print(result_mse)
x = torch.tensor([0.1, 0.2, 0.3]) # torch.Size([3])
# print(x.shape)
y = torch.tensor([1])# target.shape:torch.Size([1, 3])  (N)
x = torch.reshape(x, (1,3))# input.shape:torch.Size([1])  (N,C)
# print(x.shape)
# print(y.shape)
loss_cross = nn.CrossEntropyLoss()
result_cross = loss_cross(x, y)
print(result_cross)
from torch.optim.lr_scheduler import StepLR

loss = nn.CrossEntropyLoss()
VGG16 = VGG16()
optim = torch.optim.SGD(VGG16.parameters(), lr=0.01)#模型参数,学习率
scheduler = StepLR(optim, step_size=5, gamma=0.1)#等间隔衰减学习率 StepLR, 将学习率调整为 lr*gamma
for epoch in range(20):
    running_loss = 0.0
    for data in dataloader: #Dataset return targets,imgs
        imgs, targets = data
        outputs = VGG16(imgs)
        result_loss = loss(outputs, targets)
        optim.zero_grad()
        result_loss.backward()
        scheduler.step()
        running_loss = running_loss + result_loss
    print(running_loss)

(七)Save&Load模型

保存模型:torch.save(model ,“model.pth”)&torch.save(model.state_dict() , “model.pth”)

加载模型:不管哪一种形式加载模型,都要先加载模型原型。

from model_save import *
import torchvision

vgg16 = torchvision.models.vgg16(pretrained=False)
# 保存方式1,模型结构+模型参数
torch.save(vgg16, "vgg16_method1.pth")
# 保存方式2,模型参数(官方推荐)
torch.save(vgg16.state_dict(), "vgg16_method2.pth")

#方式1,加载模型
model = torchvision.models.vgg16(pretrained=False)
model = torch.load("vgg16_method1.pth")
# 方式2,加载模型
vgg16 = torchvision.models.vgg16(pretrained=False)
vgg16.load_state_dict(torch.load("vgg16_method2.pth"))

(八)CPU&GPU训练

指定训练设备主要在三个地方指定,1、数据加载,2、模型加载,3损失函数

import torch
import torchvision
from torch.utils.tensorboard import SummaryWriter
from torch import nn
from torch.utils.data import DataLoader

# 准备数据集
train_data = torchvision.datasets.CIFAR10(root="../data", train=True, transform=torchvision.transforms.ToTensor(),download=True)
test_data = torchvision.datasets.CIFAR10(root="../data", train=False, transform=torchvision.transforms.ToTensor(),download=True)

# length 长度
train_data_size = len(train_data)
test_data_size = len(test_data)
# 如果train_data_size=10, 训练数据集的长度为:10
print("训练数据集的长度为:{}".format(train_data_size))
print("测试数据集的长度为:{}".format(test_data_size))

# 利用 DataLoader 来加载数据集
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)

# 创建网络模型
class VGG16(nn.Module):
    def __init__(self):
        super(VGG16, self).__init__()
        self.model = nn.Sequential(
            nn.Conv2d(3, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Flatten(),
            nn.Linear(64*4*4, 64),
            nn.Linear(64, 10)
        )

    def forward(self, x):
        x = self.model(x)
        return x
VGG16 = VGG16()
if torch.cuda.is_available():
    VGG16 = VGG16.cuda()#<-------------------------------模型加载cuda

# 损失函数
loss_fn = nn.CrossEntropyLoss()
if torch.cuda.is_available():
    loss_fn = loss_fn.cuda()#<-------------------------------损失函数加载cuda
    
# 优化器
# learning_rate = 0.01
# 1e-2=1 x (10)^(-2) = 1 /100 = 0.01

learning_rate = 1e-2
optimizer = torch.optim.SGD(VGG16.parameters(), lr=learning_rate)

# 设置训练网络的一些参数
# 记录训练的次数
total_train_step = 0
# 记录测试的次数
total_test_step = 0
# 训练的轮数
epoch = 10

# 添加tensorboard
writer = SummaryWriter("../logs_train")

for i in range(epoch):
    print("-------第 {} 轮训练开始-------".format(i+1))

    # 训练步骤开始
    VGG16.train()
    for data in train_dataloader:
        targets,imgs = data
        if torch.cuda.is_available():
            imgs = imgs.cuda()#<-------------------------------数据加载cuda
            targets = targets.cuda()#<-------------------------------数据加载cuda
        outputs = VGG16(imgs)
        loss = loss_fn(outputs, targets)

        # 优化器优化模型
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        total_train_step = total_train_step + 1
        if total_train_step % 100 == 0:
            print("训练次数:{}, Loss: {}".format(total_train_step, loss.item()))
            writer.add_scalar("train_loss", loss.item(), total_train_step)

    # 测试步骤开始
    VGG16.eval()
    total_test_loss = 0
    total_accuracy = 0
    with torch.no_grad():
        for data in test_dataloader:
            imgs, targets = data
            if torch.cuda.is_available():
                imgs = imgs.cuda()#<-------------------------------数据加载cuda
                targets = targets.cuda()#<-------------------------------数据加载cuda
            outputs = VGG16(imgs)
            loss = loss_fn(outputs, targets)
            total_test_loss = total_test_loss + loss.item()
            accuracy = (outputs.argmax(1) == targets).sum()
            total_accuracy = total_accuracy + accuracy

    print("整体测试集上的Loss: {}".format(total_test_loss))
    print("整体测试集上的正确率: {}".format(total_accuracy/test_data_size))
    writer.add_scalar("test_loss", total_test_loss, total_test_step)
    writer.add_scalar("test_accuracy", total_accuracy/test_data_size, total_test_step)
    total_test_step = total_test_step + 1

    torch.save(VGG16.state_dict(), "VGG16_{}.pth".format(i))
    print("模型已保存")

writer.close()
import torch
import torchvision
from torch.utils.tensorboard import SummaryWriter
from torch import nn
from torch.utils.data import DataLoader

# if torch.cuda.is_available():#<-------------------------------指定cuda
#     device = torch.device("cuda")
# else :
#     device = torch.device("cpu")
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")


# 准备数据集
train_data = torchvision.datasets.CIFAR10(root="dataset/CIFAR10", train=True, transform=torchvision.transforms.ToTensor(),
                                          download=False)
test_data = torchvision.datasets.CIFAR10(root="dataset/CIFAR10", train=False, transform=torchvision.transforms.ToTensor(),
                                         download=False)

# length 长度
train_data_size = len(train_data)
test_data_size = len(test_data)
# 如果train_data_size=10, 训练数据集的长度为:10
print("训练数据集的长度为:{}".format(train_data_size))
print("测试数据集的长度为:{}".format(test_data_size))


# 利用 DataLoader 来加载数据集
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)

# 创建网络模型
class VGG(nn.Module):
    def __init__(self):
        super(VGG, self).__init__()
        self.model = nn.Sequential(
            nn.Conv2d(3, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Flatten(),
            nn.Linear(64*4*4, 64),
            nn.Linear(64, 10)
        )

    def forward(self, x):
        x = self.model(x)
        return x
VGG = VGG()
VGG.to(device)#<-------------------------------模型加载cuda

# 损失函数
loss_fn = nn.CrossEntropyLoss()
loss_fn.to(device)#<-------------------------------损失函数加载cuda
# 优化器
# learning_rate = 0.01
# 1e-2=1 x (10)^(-2) = 1 /100 = 0.01
learning_rate = 1e-2
optimizer = torch.optim.SGD(VGG.parameters(), lr=learning_rate)

# 设置训练网络的一些参数
# 记录训练的次数
total_train_step = 0
# 记录测试的次数
total_test_step = 0
# 训练的轮数
epoch = 10

# 添加tensorboard
writer = SummaryWriter("logs")

for i in range(epoch):
    print("-------第 {} 轮训练开始-------".format(i+1))

    # 训练步骤开始
    VGG.train()
    for data in train_dataloader:
        # targets,imgs= data
        targets,imgs = data[0].to(device), data[1].to(device)
        # imgs.to(device)#<-------------------------------数据加载cuda
        # targets.to(device)#<-------------------------------数据加载cuda
        outputs = VGG(imgs)
        loss = loss_fn(outputs, targets)


        # 优化器优化模型
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        total_train_step = total_train_step + 1
        if total_train_step % 100 == 0:
            print("训练次数:{}, Loss: {}".format(total_train_step, loss.item()))
            writer.add_scalar("train_loss", loss.item(), total_train_step)
    torch.save(VGG.state_dict(), "VGG_{}.pth".format(i+1))
    print("第{}轮模型已保存".format(i+1))

    print("-------第 {} 轮测试开始-------".format(i + 1))
    # 测试步骤开始
    VGG.eval()
    total_test_loss = 0
    total_accuracy = 0
    with torch.no_grad():
        for data in test_dataloader:
            targets,imgs = data
            imgs.to(device)#<-------------------------------数据加载cuda
            targets.to(device)#<-------------------------------数据加载cuda
            outputs = VGG(imgs)
            loss = loss_fn(outputs, targets)
            total_test_loss = total_test_loss + loss.item()
            accuracy = (outputs.argmax(1) == targets).sum()
            total_accuracy = total_accuracy + accuracy

    print("整体测试集上的Loss: {}".format(total_test_loss))
    print("整体测试集上的正确率: {}".format(total_accuracy/test_data_size))
    writer.add_scalar("test_loss", total_test_loss, total_test_step)
    writer.add_scalar("test_accuracy", total_accuracy/test_data_size, total_test_step)
    total_test_step = total_test_step + 1

writer.close()

(九)开源项目分析

Readme文件、args声明函数

def parse_args():
    parser = argparse.ArgumentParser(description='escrebtion help..')
    parser.add_argument('--root_dir', default="../rootdir",help='the path for source data')
    parser.add_argument('--window_size', default='70_70',help='The size of kernel, format: h_w')
    parser.add_argument('--output_folder',default="../outputdir", help='descrebtion help.....')
    parser.add_argument('--mode', default="train", help='descrebtion help...........')
    args = parser.parse_args()
    return args

(十)快捷键使用

常用快捷键

  • Ctrl + Space 基本的代码完成(类、方法、属性)

  • Ctrl + Alt + Space 快速导入任意类

  • Ctrl + Shift + Enter 语句完成

  • Ctrl + P 参数信息(在方法中调用参数)

  • Ctrl + Q 快速查看文档 ,使用help()拥有更加详细的信息

  • F1 Web帮助文档主页

  • Shift + F1 选中对象的Web帮助文档

  • Ctrl + 悬浮/单击鼠标左键 简介/进入代码定义

  • Ctrl + Z 撤销上次操作

  • Ctrl + Shift + Z 重做,恢复上次的撤销

  • Ctrl + F1 显示错误描述或警告信息

  • Alt + Insert 自动生成代码

  • Ctrl + O 重新方法

  • Ctrl + Alt + T 选中

  • Ctrl + / 行注释/取消注释,在python中多行注释用三个单引号’’‘多行代码’’'或者三个双引号""“多行代码”""

  • Ctrl + Shift + / 块注释

  • Ctrl + W 选中增加的代码块

  • Ctrl + Shift + W 回到之前状态

  • Ctrl + Shift + ]/[ 选定代码块结束、开始

  • Alt + Enter 快速修正

  • Ctrl + Alt + L 代码格式化

  • Ctrl + Alt + O 优化导入

  • Ctrl + Alt + I 自动缩进

  • Tab / Shift + Tab 缩进、不缩进当前行

  • Ctrl+X/Shift+Delete 剪切当前行或选定的代码块到剪贴板

  • Ctrl+C/Ctrl+Insert 复制当前行或选定的代码块到剪贴板

  • Ctrl+V/Shift+Insert 从剪贴板粘贴

  • Ctrl + Shift + V 从最近的缓冲区粘贴

  • Ctrl + D 复制选定的区域或行

  • Ctrl + Y 删除选定的行

  • Ctrl + Shift + J 添加智能线

  • Ctrl + Enter 智能线切割

  • Shift + Enter 另起一行

  • Ctrl + Shift + U 在选定的区域或代码块间切换

  • Ctrl + Delete 删除到字符结束

  • Ctrl + Backspace 删除到字符开始

  • Ctrl + Numpad+/- 展开/折叠代码块(当前位置:函数、注释等)

  • Ctrl + Shift + Numpad+/- 展开/折叠所有代码块

  • Ctrl + F4 关闭运行的选项卡

  • 2、查找/替换(Search/Replace)

  • F3 下一个

  • Shift + F3 前一个

  • Ctrl + R 替换

  • Ctrl + Shift + R 全局替换

  • Ctrl + Shift + F 全局查找(可以在整个项目中查找某个字符串什么的,如查找某个函数名)

  • 连续敲击两次Shift键 查找函数

  • 3、运行(Running)

  • Alt + Shift + F10 运行模式配置

  • Alt + Shift + F9 调试模式配置

  • Shift + F10 运行

  • Shift + F9 调试

  • Ctrl + Shift + F10 运行编辑器配置

  • Ctrl + Alt + R 运行manage.py任务

  • 4、调试(Debugging)

  • F8 跳过

  • F7 进入

  • Shift + F8 退出

  • Alt + F9 运行游标

  • Alt + F8 验证表达式

  • Ctrl + Alt + F8 快速验证表达式

  • F9 恢复程序

  • Ctrl + F8 断点开关

  • Ctrl + Shift + F8 查看断点

  • 5、导航(Navigation)

  • Ctrl + N 跳转到类

  • Ctrl + Shift + N 跳转到符号

  • Alt + Right/Left 跳转到下一个、前一个编辑的选项卡(代码文件)

  • Alt + Up/Down跳转到上一个、下一个方法

  • F12 回到先前的工具窗口

  • Esc 从工具窗口回到编辑窗口

  • Shift + Esc 隐藏运行的、最近运行的窗口

  • Ctrl + Shift + F4 关闭主动运行的选项卡

  • Ctrl + G 查看当前行号、字符号

  • Ctrl + E 在当前文件弹出最近使用的文件列表

  • Ctrl+Alt+Left/Right 后退、前进

  • Ctrl+Shift+Backspace 导航到最近编辑区域(差不多就是返回上次编辑的位置)

  • Alt + F1 查找当前文件或标识

  • Ctrl+B / Ctrl+Click 跳转到声明

  • Ctrl + Alt + B 跳转到实现

  • Ctrl + Shift + I 查看快速定义

  • Ctrl + Shift + B 跳转到类型声明

  • Ctrl + U 跳转到父方法、父类

  • Alt + Up/Down 跳转到上一个、下一个方法

  • Ctrl + ]/[ 跳转到代码块结束、开始

  • Ctrl + F12 弹出文件结构

  • Ctrl + H 类型层次结构

  • Ctrl + Shift + H 方法层次结构

  • Ctrl + Alt + H 调用层次结构

  • F2 / Shift + F2 下一条、前一条高亮的错误

  • F4 / Ctrl + Enter 编辑资源、查看资源

  • Alt + Home显示导航条F11 书签开关

  • Ctrl + Shift + F11 书签助记开关

  • Ctrl + #[0-9] 跳转到标识的书签

  • Shift + F11 显示书签

  • 6、搜索相关(Usage Search)

  • Alt + F7/Ctrl + F7 文件中查询用法

  • Ctrl + Shift + F7 文件中用法高亮显示

  • Ctrl + Alt + F7 显示用法

  • 7、重构(Refactoring)

  • Alt + Delete 安全删除

  • Shift + F6 重命名文件

  • Ctrl + F6 更改签名

  • Ctrl + Alt + N 内联

  • Ctrl + Alt + M 提取方法

  • Ctrl + Alt + V 提取属性

  • Ctrl + Alt + F 提取字段

  • Ctrl + Alt + C 提取常量

  • Ctrl + Alt + P 提取参数

  • 8、控制VCS/Local History

  • Ctrl + K 提交项目

  • Ctrl + T 更新项目

  • Alt + Shift + C 查看最近的变化

  • Alt + BackQuote(’) VCS快速弹出

  • 9、模版(Live Templates)

  • Ctrl + Alt + J 当前行使用模版

  • Ctrl + J 插入模版

  • 10、基本(General)

  • Alt + #[0-9] 打开相应的工具窗口

  • Ctrl + Alt + Y 同步

  • Ctrl + Shift + F12 最大化编辑开关

  • Alt + Shift + F 添加到最喜欢

  • Alt + Shift + I 根据配置检查当前文件

  • Ctrl + BackQuote(’) 快速切换当前计划

  • Ctrl + Alt + S 打开设置页

  • Ctrl + Shift + A 查找编辑器里所有的动作

  • Ctrl + Tab 在窗口间进行切换

help (torch.reshape)

Example:
        >>> a = torch.arange(4.)
        >>> torch.reshape(a, (2, 2))
        tensor([[ 0.,  1.],
                [ 2.,  3.]])
        >>> b = torch.tensor([[0, 1], [2, 3]])
        >>> torch.reshape(b, (-1,))#~=nn.Flatten()
        tensor([ 0,  1,  2,  3])
        >>> b.getitem()
        [ 0,  1,  2,  3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值