【PyTorch基础】 -- 主要组成模块

二、PyTorch主要组成模块

1. 深度学习的步骤

1)数据预处理:通过专门的数据加载,通过批训练提高模型表现,每次训练读取固定数量的样本输入到模型中进行训练

2)深度神经网络搭建:逐层搭建,实现特定功能的层(如卷积层、池化层、批正则化层、LSTM层等)

3)损失函数和优化器的设定:保证反向传播能够在用户定义的模型结构上实现

4)模型训练:使用并行计算加速训练,将数据按批加载,放入GPU中训练,对损失函数反向传播回网络最前面的层,同时使用优化器调整网络参数

2. 导入模块及设置参数

  • 导入相关包
import os
import numpy as py
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
import torch.optim as optimizer
  • 设置参数:batch_size、初始学习率、训练次数、GPU配置
# set batch size
batch_size = 16 
# 初始学习率
lr = 1e-4
# 训练次数
max_epochs = 100
# 配置GPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

device(type='cuda', index=1)

3. 加载数据

img1

加载数据的两个工具类:

  • dataset:构造数据集(数据集应该支持索引,能够用下标操作快速把数据拿出来),定义好数据的格式和数据变换形式
  • dataloader :主要目标用来拿出一个mini-batch来供训练时快速使用,用iterative的方式不断读入批次数据。

torch.utils.data.DataLoader()

torch.utils.data.DataLoader(dataset, batch_size=1, shuffle=False, sampler=None, batch_sampler=None, num_workers=0, collate_fn=None, pin_memory=False, drop_last=False, timeout=0, worker_init_fn=None, multiprocessing_context=None)

功能:构建可迭代的数据装载器

  • 自定义Dataset类:实现__init_____getitem____len__函数

  • dataset: Dataset 类,决定数据从哪里读取以及如何读取

  • batchsize: 批大小

  • num_works:num_works: 是否多进程读取数据

  • sheuffle:每个 epoch 是否乱序

  • drop_last:当样本数不能被 batchsize 整除时,是否舍弃最后一批数据

Epoch、Iteration、Batchsize

  • Epoch:所有训练样本都已经输入到模型中,称为一个 Epoch
  • Iteration:一批样本输入到模型中,称为一个 Iteration
  • Batchsize:批大小,决定一个 iteration 有多少样本,也决定了一个 Epoch 有多少个Iteration

Dataset是一个抽象类,不能实例化,只能被其他子类所继承,我们需要定义我们自己的类来继承这个类

DataLoader是一个用来帮助我们在pytorch加载数据的类

两种方法构造数据集:

  • 第一种在init中把所有的数据都读到内存中,然后每次使用get_item时就把其中第i个样本传出去,适用于样本不大的情况。
  • 第二种,如果读取的是较大(10g)图像数据集,在init中把数据都读进来不可能,我们就在init中定义一个列表,每一条数据的文件名放在列表中,标签读到内存中(输出是简单的分类回归数值)或文件名放在列表里,然后get_item读取第i个文件,那x,y的第i个元素去读出来,然后返回,来保证内存的高效使用。(读取文件名,根据文件名加载文件)
import torch
from torch.utils.data import Dataset
from torch.utils.data import DataLoader

class DiabetesDataset(Dataset):
    def __init__(self):
        pass
	#将来实例化这个类之后,这个对象能够支持下标操作,可以通过一个索引
    #把里面的dataset[index]的第index条数据给拿出来
    def __getitem__(self, index):
        pass

    #返回数据集的条数
    def __len__(self):
        pass
#用自定义的类把它实例化一个数据对象dataset,
#这个dataset最重要的功能是getitem()和len()
dataset = DiabetesDataset() #实例化类
train_loader = DataLoader(dataset=dataset,batch_size=32,shuffle=True,num_workers=2)
# num_workers:要几个多线程并行读取数据

应用实例:

class DiabetesDataset(Dataset):
    def __init__(self,filepath):
        xy = np.loadtxt(filepath,delimiter=',',dtype=np.float32)
        self.len = xy.shape[0]
        self.x_data = torch.from_numpy(xy[:,:-1])
        self.y_data = torch.from_numpy(xy[:,[-1]])

    #定义一个方法支持下标操作,我们称为magic function
    def __getitem__(self, index):
        return self.x_data[index],self.y_data[index]

    #返回数据集的条数
    def __len__(self):
        return self.len

dataset = DiabetesDataset('diabetes.csv.gz') #实例化类
train_loader = DataLoader(dataset=dataset,batch_size=32,shuffle=True,num_workers=2)

4. 构建模型

4.1 基本模块

image-20230311193740512

创建模型有 2 个要素:构建子模块拼接子模块。如 LeNet 里包含很多卷积层、池化层、全连接层,当我们构建好所有的子模块之后,按照一定的顺序拼接起来。

继承nn.Module,必须实现__init__() 方法和forward()方法。其中__init__() 方法里创建子模块,在forward()方法里拼接子模块。

class LeNet(nn.Module):
    # 子模块创建
    def __init__(self, classes):
        super(LeNet, self).__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.fc1 = nn.Linear(16*5*5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, classes)
    # 子模块拼接
    def forward(self, x):
        out = F.relu(self.conv1(x))
        out = F.max_pool2d(out, 2)
        out = F.relu(self.conv2(out))
        out = F.max_pool2d(out, 2)
        out = out.view(out.size(0), -1)
        out = F.relu(self.fc1(out))
        out = F.relu(self.fc2(out))
        out = self.fc3(out)
        return outs

当我们调用net = LeNet(classes=2)创建模型时,会调用__init__()方法创建模型的子模块。

当我们在训练时调用outputs = net(inputs)时,会进入module.pycall()函数中:

    def __call__(self, *input, **kwargs):
        for hook in self._forward_pre_hooks.values():
            result = hook(self, input)
            if result is not None:
                if not isinstance(result, tuple):
                    result = (result,)
                input = result
        if torch._C._get_tracing_state():
            result = self._slow_forward(*input, **kwargs)
        else:
            result = self.forward(*input, **kwargs)
        ...
        ...
        ...

最终会调用result = self.forward(*input, **kwargs)函数,该函数会进入模型的forward()函数中,进行前向传播。

torch.nn中包含 4 个模块,如下图所示。

img

其中所有网络模型都是继承于nn.Module

nn.Module 有 8 个属性,都是OrderDict(有序字典)。在 LeNet 的__init__()方法中会调用父类nn.Module__init__()方法,创建这 8 个属性。

    def __init__(self):
        """
        Initializes internal Module state, shared by both nn.Module and ScriptModule.
        """
        torch._C._log_api_usage_once("python.nn_module")

        self.training = True
        self._parameters = OrderedDict()
        self._buffers = OrderedDict()
        self._backward_hooks = OrderedDict()
        self._forward_hooks = OrderedDict()
        self._forward_pre_hooks = OrderedDict()
        self._state_dict_hooks = OrderedDict()
        self._load_state_dict_pre_hooks = OrderedDict()
        self._modules = OrderedDict()
  • _parameters 属性:存储管理 nn.Parameter 类型的参数
  • _modules 属性:存储管理 nn.Module 类型的参数
  • _buffers 属性:存储管理缓冲属性,如 BN 层中的 running_mean
  • 5 个 *_hooks 属性:存储管理钩子函数
4.2 神经网络常见的层
  • 不含模型参数的层
# 构造一个输入减去均值后输出的层
class MyLayer(nn.Module):
    def __init__(self, **kwargs):
        super(MyLayer, self).__init__(**kwargs)
    
    def forward(self, x):
        return x - x.mean()
x = torch.tensor([0, 5, 10, 15, 20], dtype=torch.float)
layer = MyLayer()
layer(x)

输出:

tensor([-10.,  -5.,   0.,   5.,  10.])
  • 含模型参数的层:如果一个Tensor是Parameter,那么它会自动被添加到模型的参数列表里
# 使用ParameterList定义参数的列表
class MyListDense(nn.Module):
    def __init__(self):
        super(MyListDense, self).__init__()
        self.params = nn.ParameterList(
            [nn.Parameter(torch.randn(4, 4)) for i in range(3)])
        self.params.append(nn.Parameter(torch.randn(4, 1)))
 
    def forward(self, x):
        for i in range(len(self.params)):
            x = torch.mm(x, self.params[i])
        return x
net = MyListDense()
print(net)

输出:
image-20230311201754677

# 使用ParameterDict定义参数的字典
class MyDictDense(nn.Module):
    def __init__(self):
        super(MyDictDense, self).__init__()
        self.params = nn.ParameterDict({
                'linear1': nn.Parameter(torch.randn(4, 4)),
                'linear2': nn.Parameter(torch.randn(4, 1))
        })
        # 新增参数linear3
        self.params.update({'linear3': nn.Parameter(torch.randn(4, 2))}) 
 
    def forward(self, x, choice='linear1'):
        return torch.mm(x, self.params[choice])
 
net = MyDictDense()
print(net)

输出:

4.3 卷积层

二维卷积层:使用nn.Conv2d类构造,模型参数包括卷积核和标量偏差,在训练模式时,通常先对卷积核随机初始化,再不断迭代卷积核和偏差

nn.Conv2d(self, in_channels, out_channels, kernel_size, stride=1,
padding=0, dilation=1, groups=1,
bias=True, padding_mode=‘zeros’)

功能是对多个二维信号进行二维卷积

  • in_channels:输入通道数
  • out_channels:输出通道数,等价于卷积核个数
  • kernel_size:卷积核尺寸
  • stride:步长
  • padding:填充宽度,主要是为了调整输出的特征图大小,一般把 padding 设置合适的值后,保持输入和输出的图像尺寸不变。
  • dilation:空洞卷积大小,默认为1,这时是标准卷积,常用于图像分割任务中,主要是为了提升感受野
  • groups:分组卷积设置,主要是为了模型的轻量化,如在 ShuffleNet、MobileNet、SqueezeNet中用到
  • bias:偏置
# 计算卷积层,对输入和输出做相应的升维和降维
def comp_conv2d(conv2d, X):
    # (1, 1)代表批量大小和通道数
    X = X.view((1, 1) + X.shape)
    Y = conv2d(X)
    # 排除不关心的前两维:批量和通道
    return Y.view(Y.shape[2:]) 
# 注意这里是两侧分别填充1⾏或列,所以在两侧一共填充2⾏或列
conv2d = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3,padding=1)
 
X = torch.rand(8, 8)
comp_conv2d(conv2d, X).shape

输出:

image-20230311202938528

4.4 池化层

直接计算池化窗口内元素的最大值或者平均值,分别叫做最大池化或平均池化

# 二维池化层
def pool2d(X, pool_size, mode='max'):
    p_h, p_w = pool_size
    Y = torch.zeros((X.shape[0] - p_h + 1, X.shape[1] - p_w + 1))
    for i in range(Y.shape[0]):
        for j in range(Y.shape[1]):
            if mode == 'max':
                Y[i, j] = X[i: i + p_h, j: j + p_w].max()
            elif mode == 'avg':
                Y[i, j] = X[i: i + p_h, j: j + p_w].mean()
    return Y
X = torch.tensor([[0, 1, 2], [3, 4, 5], [6, 7, 8]], dtype=torch.float)
pool2d(X, (2, 2), 'max')

输出:

image-20230311203106436

pool2d(X, (2, 2), 'avg')

输出:

image-20230311203200505

具体实例:

神经网络训练过程:

  • 定义可学习参数的神经网络
  • 在输入数据集上进行迭代训练
  • 通过神经网络处理输入数据
  • 计算损失值
  • 将梯度反向传播给神经网络参数
  • 更新网络参数,使用梯度下降
import torch.nn.functional as F
 
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        # 输入图像channel是1;输出channel是6;5x5卷积核
        self.conv1 = nn.Conv2d(1, 6, 5)
        self.conv2 = nn.Conv2d(6, 16, 5)
        # an affine operation: y = Wx + b
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)
 
    def forward(self, x):
        # 2x2 Max pooling
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        # 如果是方阵,则可以只使用一个数字进行定义
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)
        x = x.view(-1, self.num_flat_features(x))
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x
 
    def num_flat_features(self, x):
        # 除去批处理维度的其他所有维度
        size = x.size()[1:]  
        num_features = 1
        for s in size:
            num_features *= s
        return num_features
 
net = Net()
net

输出:

image-20230311205401068

5. 损失函数

  • 二分类交叉熵损失函数:torch.nn.BCELoss,用于计算二分类任务时的交叉熵
m = nn.Sigmoid()
loss = nn.BCELoss()
input = torch.randn(3, requires_grad=True)
target = torch.empty(3).random_(2)
 
output = loss(m(input), target)
output.backward()
print('BCE损失函数的计算结果:',output)
  • 交叉熵损失函数:torch.nn.CrossEntropyLoss,用于计算交叉熵
loss = nn.CrossEntropyLoss()
input = torch.randn(3, 5, requires_grad=True)
target = torch.empty(3, dtype=torch.long).random_(5)
 
output = loss(input, target)
output.backward()
print('CrossEntropy损失函数的计算结果:',output)
  • L1损失函数:torch.nn.L1Loss,用于计算输出y和真实值target之差的绝对值
loss = nn.L1Loss()
input = torch.randn(3, 5, requires_grad=True)
target = torch.randn(3, 5)
 
output = loss(input, target)
output.backward()
print('L1损失函数的计算结果:',output)
  • MSE损失函数:torch.nn.MSELoss,用于计算输出y和真实值target之差的平方
loss = nn.MSELoss()
input = torch.randn(3, 5, requires_grad=True)
target = torch.randn(3, 5)
 
output = loss(input, target)
output.backward()
print('MSE损失函数的计算结果:',output)
  • 平滑L1(Smooth L1)损失函数:torch.nn.SmoothL1Loss,用于计算L1的平滑输出,减轻离群点带来的影响,通过与L1损失的比较,在0点的尖端处,过渡更为平滑
loss = nn.SmoothL1Loss()
input = torch.randn(3, 5, requires_grad=True)
target = torch.randn(3, 5)
 
output = loss(input, target)
output.backward()
print('Smooth L1损失函数的计算结果:',output)
  • 目标泊松分布的负对数似然损失:torch.nn.PoissonNLLLoss
loss = nn.PoissonNLLLoss()
log_input = torch.randn(5, 2, requires_grad=True)
target = torch.randn(5, 2)
 
output = loss(log_input, target)
output.backward()
print('PoissonNL损失函数的计算结果:',output)
  • KL散度:torch.nn.KLDivLoss,用于连续分布的距离度量,可用在对离散采用的连续输出空间分布的回归场景
inputs = torch.tensor([[0.5, 0.3, 0.2], [0.2, 0.3, 0.5]])
target = torch.tensor([[0.9, 0.05, 0.05], [0.1, 0.7, 0.2]], dtype=torch.float)
loss = nn.KLDivLoss(reduction='batchmean')
 
output = loss(inputs,target)
print('KLDiv损失函数的计算结果:',output)
  • MarginRankingLoss:torch.nn.MarginRankingLoss,用于计算两组数据之间的差异(相似度),可使用在排序任务的场景
loss = nn.MarginRankingLoss()
input1 = torch.randn(3, requires_grad=True)
input2 = torch.randn(3, requires_grad=True)
target = torch.randn(3).sign()
 
output = loss(input1, input2, target)
output.backward()
print('MarginRanking损失函数的计算结果:',output)
  • 多标签边界损失函数:torch.nn.MultiLabelMarginLoss,用于计算多标签分类问题的损失
loss = nn.MultiLabelMarginLoss()
x = torch.FloatTensor([[0.9, 0.2, 0.4, 0.8]])
# 真实的分类是,第3类和第0类
y = torch.LongTensor([[3, 0, -1, 1]])
 
output = loss(x, y)
print('MultiLabelMargin损失函数的计算结果:',output)
  • 二分类损失函数:torch.nn.SoftMarginLoss,用于计算二分类的logistic损失
# 定义两个样本,两个神经元
inputs = torch.tensor([[0.3, 0.7], [0.5, 0.5]])  
target = torch.tensor([[-1, 1], [1, -1]], dtype=torch.float)  
 
# 该loss对每个神经元计算,需要为每个神经元单独设置标签
loss_f = nn.SoftMarginLoss()
output = loss_f(inputs, target)
print('SoftMargin损失函数的计算结果:',output)
  • 多分类的折页损失函数:torch.nn.MultiMarginLoss,用于计算多分类问题的折页损失
inputs = torch.tensor([[0.3, 0.7], [0.5, 0.5]]) 
target = torch.tensor([0, 1], dtype=torch.long) 
 
loss_f = nn.MultiMarginLoss()
output = loss_f(inputs, target)
print('MultiMargin损失函数的计算结果:',output)
  • 三元组损失函数:torch.nn.TripletMarginLoss,用于处理<实体1,关系,实体2>类型的数据,计算该类型数据的损失
triplet_loss = nn.TripletMarginLoss(margin=1.0, p=2)
anchor = torch.randn(100, 128, requires_grad=True)
positive = torch.randn(100, 128, requires_grad=True)
negative = torch.randn(100, 128, requires_grad=True)
 
output = triplet_loss(anchor, positive, negative)
output.backward()
print('TripletMargin损失函数的计算结果:',output)
  • HingEmbeddingLoss:torch.nn.HingeEmbeddingLoss,用于计算输出的embedding结果的Hing损失
loss_f = nn.HingeEmbeddingLoss()
inputs = torch.tensor([[1., 0.8, 0.5]])
target = torch.tensor([[1, 1, -1]])
 
output = loss_f(inputs,target)
print('HingEmbedding损失函数的计算结果:',output)
  • 余弦相似度:torch.nn.CosineEmbeddingLoss,用于计算两个向量的余弦相似度,如果两个向量距离近,则损失函数值小,反之亦然
loss_f = nn.CosineEmbeddingLoss()
inputs_1 = torch.tensor([[0.3, 0.5, 0.7], [0.3, 0.5, 0.7]])
inputs_2 = torch.tensor([[0.1, 0.3, 0.5], [0.1, 0.3, 0.5]])
target = torch.tensor([1, -1], dtype=torch.float)
 
output = loss_f(inputs_1,inputs_2,target)
print('CosineEmbedding损失函数的计算结果:',output)
  • CTC损失函数:torch.nn.CTCLoss,用于处理时序数据的分类问题,计算连续时间序列和目标序列之间的损失
# Target are to be padded
# 序列长度
T = 50      
# 类别数(包括空类)
C = 20      
# batch size
N = 16
# Target sequence length of longest target in batch (padding length)
S = 30      
# Minimum target length, for demonstration purposes
S_min = 10  
 
input = torch.randn(T, N, C).log_softmax(2).detach().requires_grad_()
# 初始化target(0 = blank, 1:C = classes)
target = torch.randint(low=1, high=C, size=(N, S), dtype=torch.long)
 
input_lengths = torch.full(size=(N,), fill_value=T, dtype=torch.long)
target_lengths = torch.randint(low=S_min, high=S, size=(N,), dtype=torch.long)
 
ctc_loss = nn.CTCLoss()
loss = ctc_loss(input, target, input_lengths, target_lengths)
loss.backward()
print('CTC损失函数的计算结果:',loss)

6. 优化器

PyTorch 中的优化器是用于管理并更新模型中可学习参数的值,使得模型输出更加接近真实标签。

PyTorch 中提供了 Optimizer 类,定义如下:

class Optimizer(object):
    def __init__(self, params, defaults):        
        self.defaults = defaults
        self.state = defaultdict(dict)
        self.param_groups = []

主要有 3 个属性:

  • defaults:优化器的超参数,如 weight_decay,momentum
  • state:参数的缓存,如 momentum 中需要用到前几次的梯度,就缓存在这个变量中
  • param_groups:管理的参数组,是一个 list,其中每个元素是字典,包括 momentum、lr、weight_decay、params 等。
  • _step_count:记录更新 次数,在学习率调整中使用

方法:

  • zero_grad():清空所管理参数的梯度。由于 PyTorch 的特性是张量的梯度不自动清零,因此每次反向传播之后都需要清空梯度。
  • step():执行一步梯度更新
  • add_param_group():添加参数组
  • state_dict():获取优化器当前状态信息字典
  • load_state_dict():加载状态信息字典,包括 state 、momentum_buffer 和 param_groups。主要用于模型的断点续训练。我们可以在每隔 50 个 epoch 就保存模型的 state_dict 到硬盘,在意外终止训练时,可以继续加载上次保存的状态,继续训练

基本操作:

# 设置权重,服从正态分布  --> 2 x 2
weight = torch.randn((2, 2), requires_grad=True)
 
# 设置梯度为全1矩阵  --> 2 x 2
weight.grad = torch.ones((2, 2))
 
# 输出现有的weight和data
print("The data of weight before step:\n{}".format(weight.data))
print("The grad of weight before step:\n{}".format(weight.grad))

# 实例化优化器
optimizer = torch.optim.SGD([weight], lr=0.1, momentum=0.9)
 
# 进行一步操作
optimizer.step()
 
# 查看进行一步后的值,梯度
print("The data of weight after step:\n{}".format(weight.data))
print("The grad of weight after step:\n{}".format(weight.grad))

image-20230311204658484

# 权重清零
optimizer.zero_grad()
 
# 检验权重是否为0
print("The grad of weight after optimizer.zero_grad():\n{}".format(weight.grad))
# 添加参数:weight2
weight2 = torch.randn((3, 3), requires_grad=True)
optimizer.add_param_group({"params": weight2, 'lr': 0.0001, 'nesterov': True})
 
# 查看现有的参数
print("optimizer.param_groups is\n{}".format(optimizer.param_groups))
 
# 查看当前状态信息
opt_state_dict = optimizer.state_dict()
print("state_dict before step:\n", opt_state_dict)

image-20230311204927181

# 进行5次step操作
for _ in range(50):
    optimizer.step()
# 输出现有状态信息
print("state_dict after step:\n", optimizer.state_dict())

image-20230311205031525

7. 训练与测试

  • 训练
def train(epoch):
    # 设置训练状态
    model.train()
    train_loss = 0
    # 循环读取DataLoader中的全部数据
    for data, label in train_loader:
        # 将数据放到GPU用于后续计算
        data, label = data.cuda(), label.cuda()
        # 将优化器的梯度清0
        optimizer.zero_grad()
        # 将数据输入给模型
        output = model(data)
        # 设置损失函数
        loss = criterion(label, output)
        # 将loss反向传播给网络
        loss.backward()
        # 使用优化器更新模型参数
        optimizer.step()
        # 累加训练损失
        train_loss += loss.item()*data.size(0)
    train_loss = train_loss/len(train_loader.dataset)
    print('Epoch: {} \tTraining Loss: {:.6f}'.format(epoch, train_loss))
  • 测试
def val(epoch):  
    # 设置验证状态
    model.eval()
    val_loss = 0
    # 不设置梯度
    with torch.no_grad():
        for data, label in val_loader:
            data, label = data.cuda(), label.cuda()
            output = model(data)
            preds = torch.argmax(output, 1)
            loss = criterion(output, label)
            val_loss += loss.item()*data.size(0)
            # 计算准确率
            running_accu += torch.sum(preds == label.data)
    val_loss = val_loss/len(val_loader.dataset)
    print('Epoch: {} \tTraining Loss: {:.6f}'.format(epoch, val_loss))

学习资料

https://blog.csdn.net/qq_36816848/article/details/123562429

https://pytorch.zhangxiann.com/

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值