PyTorch工作流程

PyTorch的工作流程总览:

1.准备好数据
2.建立模型
3.将模型拟合到数据(训练)
4.做出预测和评估模型(推理)
5.保存和加载模型
import torch
from torch import nn 
import matplotlib.pyplot as plt

# 显示我自己pytorch的版本号
torch.__version__
'1.7.1+cpu'

1、数据(准备和加载)

机器学习是一个由两部分组成的:
1.将您的数据(无论它是什么)转换为数字(表示)。
2.选择或构建模型以尽可能最好地学习表示。
# 创建已知的参数
weight = 0.7
bias = 0.3

# 创建数据
start = 0
end = 1
step = 0.02
X = torch.arange(start, end, step).unsqueeze(dim=1)
y = weight * X + bias

X[:10], y[:10],X.shape,y.shape
(tensor([[0.0000],
         [0.0200],
         [0.0400],
         [0.0600],
         [0.0800],
         [0.1000],
         [0.1200],
         [0.1400],
         [0.1600],
         [0.1800]]),
 tensor([[0.3000],
         [0.3140],
         [0.3280],
         [0.3420],
         [0.3560],
         [0.3700],
         [0.3840],
         [0.3980],
         [0.4120],
         [0.4260]]),
 torch.Size([50, 1]),
 torch.Size([50, 1]))
# 现在我们将着手构建一个可以学习X(特征)和y(标签)之间关系模型
将数据分为:                                                                                数据总量          使用频率
1.训练集:模型从这些数据中学习(比如你在学期中学习的课程)                                    6080%           总是
2.验证集:该模型会根据这些数据进行调整(例如您在期末考试前参加的练习考试)                    1020%           经常但不总是
3.测试集:模型会根据这些数据进行评估,以测试它学到了什么(比如你在学期末参加的期末考试)      1020%           总是
现在,我们将使用一个训练集和测试集,这意味着我们将有一个数据集供我们的模型学习和评估。
在处理真实数据时,此步骤通常在项目开始时完成(测试集应始终与所有其他数据分开)。
我们希望我们的模型在训练数据上学习,然后在测试数据上对其进行评估,以了解它对未见示例的泛化程度。
# 分开创建训练集和测试集。
train_split = int(0.8 * len(X)) # 百分之80的数据被用作训练集,  
X_train, y_train = X[:train_split], y[:train_split]
X_test, y_test = X[train_split:], y[train_split:]#百分之20的数据被用作测试集

len(X_train), len(y_train), len(X_test), len(y_test)
(40, 40, 10, 10)
我们有 40 个训练样本(X_train& y_train)和 10 个测试样本(X_test& y_test)
X_train我们创建的模型将尝试学习&之间的关系y_train,然后我们将评估它在X_test和上学习的内容y_test。
但现在我们的数据只是页面上的数字。
让我们创建一个函数来可视化它。
def plot_predictions(train_data=X_train, 
                     train_labels=y_train, 
                     test_data=X_test, 
                     test_labels=y_test, 
                     predictions=None):
  """
  Plots training data, test data and compares predictions.
  """
  plt.figure(figsize=(10, 7))

  # Plot training data in blue
  plt.scatter(train_data, train_labels, c="b", s=4, label="Training data")
  
  # Plot test data in green
  plt.scatter(test_data, test_labels, c="g", s=4, label="Testing data")

  if predictions is not None:
    # Plot the predictions in red (predictions were made on the test data)
    plt.scatter(test_data, predictions, c="r", s=4, label="Predictions")

  # Show the legend
  plt.legend(prop={"size": 14});
def plot_predictions(train_data=X_train, 
                     train_labels=y_train, 
                     test_data=X_test, 
                     test_labels=y_test, 
                     predictions=None):
    plt.figure(figsize=(10, 7))

  # 用蓝色绘制训练数据集
    plt.scatter(train_data, train_labels, c="b", s=4, label="Training data")
  
  # 用绿色绘制测试数据集
    plt.scatter(test_data, test_labels, c="g", s=4, label="Testing data")

    if predictions is not None:
    # 用红色绘制预测数据集(predictions were made on the test data)
        plt.scatter(test_data, predictions, c="r", s=4, label="Predictions")

  # Show the legend
    plt.legend(prop={"size": 14});
plot_predictions();

2、建立模型

class LinearRegressionModel(nn.Module): # <PyTorch中几乎所有的东西都是nn.Module(可以把它看作是神经网络的乐高积)
    def __init__(self):
        super().__init__() 
        self.weights = nn.Parameter(torch.randn(1, # <- 以随机权重开始(这将随着模型的学习而调整))
            requires_grad=True, # <- 我们可以用梯度下降法来更新这个值吗?
            dtype=torch.float # <- PyTorch 默认float32 
        ))

        self.bias = nn.Parameter(torch.randn(1, # <- 以随机偏差开始(这将随着模型的学习而得到调整)
            requires_grad=True, # <- 我们可以用梯度下降法来更新这个值吗?
            dtype=torch.float # <- PyTorch 默认float32 
        ))

    # 前向义了模型中的计算
    def forward(self, x: torch.Tensor) -> torch.Tensor: # <- "x "是输入数据(如训练/测试特征)
        return self.weights * x + self.bias
# Pytorch模型构建要点
1、torch.nn                   包含计算图的所有构建块(本质上是以特定方式执行的一系列计算)。
2、torch.nn.Parameter                                 存储可以与 一起使用的张量nn.Module。
如果requires_grad=True梯度(用于通过梯度下降更新模型参数)是自动计算的,这通常被称为“autograd”。
3、torch.nn.Module            所有神经网络模块的基类,神经网络的所有构建块都是子类。
如果你在 PyTorch 中构建神经网络,你的模型应该子类化nn.Module. 需要forward()实现一个方法。
4、torch.optim                包含各种优化算法(这些告诉模型中存储的参数nn.Parameter如何最好地改变以改善梯度下降,进而减少损失)。
5def forward()               所有nn.Module子类都需要一个forward()方法,
这定义了将在传递给特定数据的数据上发生的计算nn.Module(例如上面的线性回归公式)。
# Pytorch模型的内容

现在我们已经解决了这些问题,让我们使用我们创建的类创建一个模型实例,并使用.parameters().

torch.manual_seed(42)

model_0 = LinearRegressionModel()

list(model_0.parameters())
[Parameter containing:
 tensor([0.3367], requires_grad=True),
 Parameter containing:
 tensor([0.1288], requires_grad=True)]
练习:
尝试改变torch.manual_seed()上面两个单元格的值,看看权重和偏差值会发生什么变化。

因为我们的模型从随机值开始,所以现在它的预测能力很差。
# 我们还可以使用 获取模型的状态
model_0.state_dict()
OrderedDict([('weights', tensor([0.3367])), ('bias', tensor([0.1288]))])

使用预测torch.inference_mode()

为了检查这一点,我们可以将测试数据传递给它X_test,看看它的预测有多接近y_test。

with torch.no_grad():          #在更高的版本中可以使用torch.no_grad() 低版本中是torch.inference_model():
    y_preds = model_0(X_test)     
print(f"Number of testing samples: {len(X_test)}") 
print(f"Number of predictions made: {len(y_preds)}")
print(f"Predicted values:\n{y_preds}")
Number of testing samples: 10
Number of predictions made: 10
Predicted values:
tensor([[0.3982],
        [0.4049],
        [0.4116],
        [0.4184],
        [0.4251],
        [0.4318],
        [0.4386],
        [0.4453],
        [0.4520],
        [0.4588]])
注意每个测试样本有一个预测值。

这是因为我们使用的数据类型。对于我们的直线,一个X值映射到一个y值。

但是,机器学习模型非常灵活。您可以将 100 个X值映射到一个、两个、三个或 10 个y值。这完全取决于你在做什么。

我们的预测仍然是页面上的数字,让我们plot_predictions()使用我们在上面创建的函数将它们可视化。
y_test-y_preds
tensor([[0.4618],
        [0.4691],
        [0.4764],
        [0.4836],
        [0.4909],
        [0.4982],
        [0.5054],
        [0.5127],
        [0.5200],
        [0.5272]])
这些预测看起来很糟糕......

这是有道理的,但当您记得我们的模型只是使用随机参数值进行预测时。

3、训练模型

现在我们的模型正在使用随机参数进行预测以进行计算,它基本上是(随机)猜测。

为了解决这个问题,我们可以更新它的内部参数(我也将参数称为模式)、我们随机设置的值weights和值,以更好地代表数据。biasnn.Parameter()torch.randn()

我们可以对此进行硬编码(因为我们知道默认值weight=0.7和bias=0.3),但其中的乐趣在哪里?

很多时候,您不知道模型的理想参数是什么。

相反,编写代码以查看模型是否可以尝试自行解决它们会更有趣。

3.1 在 PyTorch 中创建损失函数和优化器

1.损失函数:
衡量你的模型预测与真实标签相比错误程度,越低越好, 位置:在torch.nn,    回归问题的平均绝对误差(MAE)torch.nn.L1Loss(),
二元分类问题的二元交叉熵(torch.nn.BCELoss)

2.优化器:
告诉你的模型如何更新其内部参数最好地降低损失,位置:在torch.optim, 随机梯度下降(torch.optim.SGD),Adam优化器(torch.optim.Adam())
让我们创建一个损失函数和一个优化器,我们可以使用它来帮助改进我们的模型。
根据您正在处理的问题类型将取决于您使用的损失函数和优化器。
但是,也有一些常见的值,已知它们可以很好地工作,例如 SGD(随机梯度下降)或 Adam 优化器。
以及用于回归问题(预测数字)的 MAE(平均绝对误差)损失函数或用于分类问题(预测一件事或另一件事)的二元交叉熵损失函数。
对于我们的问题,因为我们要预测一个数字,所以让我们使用torch.nn.L1Loss()PyTorch 中的 MAE(位于 下)作为我们的损失函数。

# 创建一个损失函数
loss_fn = nn.L1Loss() # MAE loss is same as L1Loss

# 创建一个优化器
optimizer = torch.optim.SGD(params=model_0.parameters(), # parameters of target model to optimize
                            lr=0.01) # learning rate 

3.2、Pytorch的训练循环

1.前向传播
2.计算损失
3.优化梯度
4.反向传播
5.更新优化器
1.forward pass
该模型遍历所有的训练数据,执行其forward()函数计算   代码示例:model(x_train)
2.calculate the loss
将模型的输出(预测)与基本事实进行比较并评估它们的错误程度。 代码示例:loss = loss_fn(y_pred,y_train)
3.optimizer zero grad
优化器梯度设置为零(默认情况下会累积),因此可以针对特定的训练步骤重新计算它们。 代码示例:optimizer.zero_grad()
4.loss backward
计算每个要更新的模型参数(每个参数带有requires_grad=True)的损失梯度。这被称为反向传播,因此是“向后”。代码示例:loss.backward()
5.update optimizetion
更新requires_grad=True关于损失梯度的参数以改进它们。optimizer.step()
torch.manual_seed(42)


epochs = 100


train_loss_values = []
test_loss_values = []
epoch_count = []

for epoch in range(epochs):
    ### Training

    # Put model in training mode (this is the default state of a model)
    model_0.train()

    # 1. Forward pass on train data using the forward() method inside 
    y_pred = model_0(X_train)
    # print(y_pred)

    # 2. Calculate the loss (how different are our models predictions to the ground truth)
    loss = loss_fn(y_pred, y_train)

    # 3. Zero grad of the optimizer
    optimizer.zero_grad()

    # 4. Loss backwards
    loss.backward()

    # 5. Progress the optimizer
    optimizer.step()

    ### Testing

    # Put the model in evaluation mode
    model_0.eval()

    with torch.no_grad():
      # 1. Forward pass on test data
      test_pred = model_0(X_test)

      # 2. Caculate loss on test data
      test_loss = loss_fn(test_pred, y_test.type(torch.float)) # predictions come in torch.float datatype, so comparisons need to be done with tensors of the same type

      # Print out what's happening
      if epoch % 10 == 0:
            epoch_count.append(epoch)
            train_loss_values.append(loss.detach().numpy())
            test_loss_values.append(test_loss.detach().numpy())
            print(f"Epoch: {epoch} | MAE Train Loss: {loss} | MAE Test Loss: {test_loss} ")
Epoch: 0 | MAE Train Loss: 0.31288138031959534 | MAE Test Loss: 0.48106518387794495 
Epoch: 10 | MAE Train Loss: 0.1976713240146637 | MAE Test Loss: 0.3463551998138428 
Epoch: 20 | MAE Train Loss: 0.08908725529909134 | MAE Test Loss: 0.21729660034179688 
Epoch: 30 | MAE Train Loss: 0.053148526698350906 | MAE Test Loss: 0.14464017748832703 
Epoch: 40 | MAE Train Loss: 0.04543796554207802 | MAE Test Loss: 0.11360953003168106 
Epoch: 50 | MAE Train Loss: 0.04167863354086876 | MAE Test Loss: 0.09919948130846024 
Epoch: 60 | MAE Train Loss: 0.03818932920694351 | MAE Test Loss: 0.08886633068323135 
Epoch: 70 | MAE Train Loss: 0.03476089984178543 | MAE Test Loss: 0.0805937647819519 
Epoch: 80 | MAE Train Loss: 0.03132382780313492 | MAE Test Loss: 0.07232122868299484 
Epoch: 90 | MAE Train Loss: 0.02788739837706089 | MAE Test Loss: 0.06473556160926819 
# 绘制损失函数曲线
plt.plot(epoch_count, train_loss_values, label="Train loss")
plt.plot(epoch_count, test_loss_values, label="Test loss")
plt.title("Training and test loss curves")
plt.ylabel("Loss")
plt.xlabel("Epochs")
plt.legend();
好的!损失曲线显示损失随着时间的推移而下降。请记住,损失是衡量模型错误程度的指标,因此越低越好。

但是为什么损失会下降呢?

好吧,由于我们的损失函数和优化器,模型的内部参数 (weights和bias) 得到了更新,以更好地反映数据中的潜在模式。

让我们检查一下我们的模型.state_dict(),看看我们的模型与我们为权重和偏差设置的原始值有多接近。
# 展示我们的模型学习参数
print("The model learned the following values for weights and bias:")
print(model_0.state_dict())
print("\nAnd the original values for weights and bias are:")
print(f"weights: {weight}, bias: {bias}")
The model learned the following values for weights and bias:
OrderedDict([('weights', tensor([0.5784])), ('bias', tensor([0.3513]))])

And the original values for weights and bias are:
weights: 0.7, bias: 0.3

4、使用经过训练的Pytorch模型进行预测

使用 PyTorch 模型进行预测(也称为执行推理)时,需要记住三件事:

1.将模型设置为评估模式 ( model.eval())2.使用推理模式上下文管理器 ( ) 进行预测with torch.inference_mode(): ...3.所有预测都应使用同一设备上的对象进行(例如,仅在 GPU 上的数据和模型或仅在 CPU 上的数据和模型)。
# 1. 用训练好的模型来评估
model_0.eval()

# 2. Setup the inference mode context manager
with torch.no_grad():
  # 3. Make sure the calculations are done with the model and data on the same device
  # in our case, we haven't setup device-agnostic code yet so our data and model are
  # on the CPU by default.
  # model_0.to(device)
  # X_test = X_test.to(device)
  y_preds = model_0(X_test)
y_preds
tensor([[0.8141],
        [0.8256],
        [0.8372],
        [0.8488],
        [0.8603],
        [0.8719],
        [0.8835],
        [0.8950],
        [0.9066],
        [0.9182]])
# 好的!我们已经用训练有素的模型做了一些预测,现在它们看起来如何?
plot_predictions(predictions=y_preds)

5、保存和加载Pytorch模型

# 对于在 PyTorch 中保存和加载模型,您应该了解三种主要方法
1.torch.save
2.torch.load
3.torch.nn.Module.load_state_dict

5.1 保存Pytorch模型的state_dict()

更推荐的方法是保存和加载模型的state_dict()

步骤:
1.我们将创建一个目录,用于保存模型以models使用 Python中的pathlib模块调用
2.我们将创建一个文件路径来保存模型。
3.我们将调用torch.save(obj, f)        obj是目标模型state_dict(),并且f是保存模型的文件名

一般命名结尾是以.pt,或者.pth,举例:saved_model_01.pth.
from pathlib import Path

# 1. 创建一个模型文件夹
MODEL_PATH = Path("models")
MODEL_PATH.mkdir(parents=True, exist_ok=True)

# 2. 创建文件保存路径
MODEL_NAME = "01_pytorch_workflow_model_0.pth"
MODEL_SAVE_PATH = MODEL_PATH / MODEL_NAME

# 3. 保存模型的状态参数 
print(f"Saving model to: {MODEL_SAVE_PATH}")
torch.save(obj=model_0.state_dict(), # only saving the state_dict() only saves the models learned parameters
           f=MODEL_SAVE_PATH) 
Saving model to: models\01_pytorch_workflow_model_0.pth

5.2 加载保存的PyTorch模型state_dict()

# 由于我们现在保存的是一个模型的state_dict(),它是一个学习参数的字典
用torch.nn.Module.load_state_dict(torch.load(f))
loaded_model_0 = LinearRegressionModel()

# Load the state_dict of our saved model (this will update the new instance of our model with trained weights)
loaded_model_0.load_state_dict(torch.load(f=MODEL_SAVE_PATH))
<All keys matched successfully>
# pytorch的推理规则
# 1.把加载的模型放到评估模式中
loaded_model_0.eval()

# 2. 使用推理模式的上下文管理器进行预测
with torch.no_grad():
    loaded_model_preds = loaded_model_0(X_test)
y_preds == loaded_model_preds
tensor([[True],
        [True],
        [True],
        [True],
        [True],
        [True],
        [True],
        [True],
        [True],
        [True]])
看起来加载的模型预测与之前的模型预测(保存前的预测)相同。这表明我们的模型正在按预期保存和加载。

6、pytorch模型训练全过程

torch.manual_seed(42)

# 设置epochs的数量 
epochs = 1000 

# Put data on the available device
# Without this, error will happen (not all model/data on device)
X_train = X_train.to(device)
X_test = X_test.to(device)
y_train = y_train.to(device)
y_test = y_test.to(device)

for epoch in range(epochs):
    #训练
    model_1.train() # train mode is on by default after construction

    # 1. 前向传播
    y_pred = model_1(X_train)

    # 2. 计算损失
    loss = loss_fn(y_pred, y_train)

    # 3. 梯度归零优化
    optimizer.zero_grad()

    # 4. 反向传播
    loss.backward()

    # 5. 一步步的更新参数
    optimizer.step()

    #评估
    model_1.eval() # put the model in evaluation mode for testing (inference)
    # 1. Forward pass
    with torch.no_grad():
        test_pred = model_1(X_test)
        # 2. Calculate the loss
        test_loss = loss_fn(test_pred, y_test)

    if epoch % 100 == 0:
        print(f"Epoch: {epoch} | Train loss: {loss} | Test loss: {test_loss}")
Epoch: 0 | Train loss: 0.5551779866218567 | Test loss: 0.5739762187004089
Epoch: 100 | Train loss: 0.006215679459273815 | Test loss: 0.014086711220443249
Epoch: 200 | Train loss: 0.0012645035749301314 | Test loss: 0.013801807537674904
Epoch: 300 | Train loss: 0.0012645035749301314 | Test loss: 0.013801807537674904
Epoch: 400 | Train loss: 0.0012645035749301314 | Test loss: 0.013801807537674904
Epoch: 500 | Train loss: 0.0012645035749301314 | Test loss: 0.013801807537674904
Epoch: 600 | Train loss: 0.0012645035749301314 | Test loss: 0.013801807537674904
Epoch: 700 | Train loss: 0.0012645035749301314 | Test loss: 0.013801807537674904
Epoch: 800 | Train loss: 0.0012645035749301314 | Test loss: 0.013801807537674904
Epoch: 900 | Train loss: 0.0012645035749301314 | Test loss: 0.013801807537674904

7、打印我们学习到的参数,并将它们与我们的硬编码的原始参数进行比较

from pprint import pprint # pprint = pretty print, see: https://docs.python.org/3/library/pprint.html 
print("The model learned the following values for weights and bias:")
pprint(model_1.state_dict())
print("\nAnd the original values for weights and bias are:")
print(f"weights: {weight}, bias: {bias}")
The model learned the following values for weights and bias:
OrderedDict([('linear_layer.weight', tensor([[0.6968]])),
             ('linear_layer.bias', tensor([0.3025]))])

And the original values for weights and bias are:
weights: 0.7, bias: 0.3

8、做出预测

# 将模型转为评估模式
model_1.eval()

# 在测试集上做预测
with torch.no_grad():
    y_preds = model_1(X_test)
y_preds
tensor([[0.8600],
        [0.8739],
        [0.8878],
        [0.9018],
        [0.9157],
        [0.9296],
        [0.9436],
        [0.9575],
        [0.9714],
        [0.9854]])
# 很多数据科学库例如(pandas numpy matplotlib)无法使用存储在GPU上的数据,在尝试使用这些库之一中的函数时。
# 我们可以使用调用.cpu()目标张量以在CPU上返回目标张量的副本。

9、怎么将GPU的数据在CPU上可视化预测数据

plot_predictions(predictions=y_preds.cpu())

9.1、保存和加载模型

from pathlib import Path

# 1. 创建模型文件夹
MODEL_PATH = Path("models")
MODEL_PATH.mkdir(parents=True, exist_ok=True)

# 2. 创建模型保存路径 
MODEL_NAME = "01_pytorch_workflow_model_1.pth"
MODEL_SAVE_PATH = MODEL_PATH / MODEL_NAME

# 3. 保存模型状态字典
print(f"Saving model to: {MODEL_SAVE_PATH}")
torch.save(obj=model_1.state_dict(), # 只保存它的state_dict() 只保存模型的学习参数。
           f=MODEL_SAVE_PATH) 
Saving model to: models\01_pytorch_workflow_model_1.pth

9.2、实例化一个新的实例

loaded_model_1 = LinearRegressionModelV2()

# 加载模型的参数字典
loaded_model_1.load_state_dict(torch.load(MODEL_SAVE_PATH))

# 将模型放入目标设备(假如你的数据在GPU, 你的模型也必须在 GPU 中去做预测)
loaded_model_1.to(device)

print(f"Loaded model:\n{loaded_model_1}")
print(f"Model on device:\n{next(loaded_model_1.parameters()).device}")
Loaded model:
LinearRegressionModelV2(
  (linear_layer): Linear(in_features=1, out_features=1, bias=True)
)
Model on device:
cpu

9.3、评估载入的模型

loaded_model_1.eval()
with torch.no_grad():
    loaded_model_1_preds = loaded_model_1(X_test)
y_preds == loaded_model_1_preds
tensor([[True],
        [True],
        [True],
        [True],
        [True],
        [True],
        [True],
        [True],
        [True],
        [True]])

10、pytorch 的中文文档的地址:

https://pytorch-cn.readthedocs.io/zh/latest/

11、pytorch.nn的文档

1.torch.nn.Parameters(模块参数)
2.torch.nn.Contain(模块容器)
3.torch.nn.Conv1d(卷积层)
4.toch.nn.MaxPool1d(池化层)
5.torch.nn.激活函数 
6.torch.nn.BatchNorm1d,()#批归一化
7.torch.nn.RNN(),torch.nn.LSTM(),torch.nn.GRU(),torch.nn.RNNCell(),torch.nn.LSTMCell(),torch.nn.GRUCell()#
8.torch.nn.Linear(每个输入样本的大小,每个输出样本的大小,bias:True,就是学习偏置,False:这层不会学习偏置。
# 丢弃层
9.torch.nn.Dropout(p=0.5,inplace=False)#p是将元素置为0的概率,inplace:True,会在原地执行操作。
# 距离函数
10.torch.nn.PairwiseDistance(p=2,eps=1e-06)#按照批次计算向量V1,V2之间的距离。
# 损失函数
11.               torch.L1Loss(),#创建一个衡量输入x(模型预测输出)和目标y之间差的绝对值的平均值的标准
                  torch.nn.MSELoss(),#创建一个衡量输入x(模型预测输出)和目标y之间均方误差标准。
                  torch.nn.CrossEntropyLoss(),#当训练一个多类分类器的时候
                  torch.nn.NLLLoss(),#负的log likelihood loss损失。用于训练一个n类分类器。
                  torch.nn.KLDivLoss(),#KL 散度损失,KL散度常用来描述两个分布的距离,并在输出分布的空间上执行直接回归是有用的。
                  torch.nn.BCELoss(),#
                  torch.nn.MarginRankingLoss(),
                  torch.nn.HingeEmbeddingLoss(),#通常用来测量两个输入是否相似
# 多设备并行计算
12.
net = torch.nn.DataParallel(model,device_ids=[0,1,2])
output = net(input_var)
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值