【Intel校企合作课程】基于VGG-19的猫狗大战之图像识别

目录

项目背景及意义

1 作业简介:猫狗大战之图像识别

1.1 问题描述

1.2 图像展示

2 数据预处理

2.1 数据集结构

2.2 数据探索性分析

2.3 拆分数据集

2.4 提取数据集

2.5 数据增强

3 使用卷积神经网络识别猫狗图像

3.1 卷积神经网络

3.2 VGG-19架构

3.3 VGG-19与VGG-16架构对比

3.4 优化函数

4 在GPU上进行训练

4.1 训练步骤

4.2 查看test数据集F1分数及时间

4.3 使用验证集和测试集进行推理测试

5 在CPU上进行训练

5.1 创建VGG19模型

5.2 直接在CPU上进行训练

6 使用oneAPI组件

6.1 Transfer Learning with oneAPI AI Analytics Toolkit进行迁移学习

6.2 使用Intel Extension for PyTorch进行优化

6.3 使用 Intel® Neural Compressor 量化模型 

6.4 使用量化后的模型在 CPU上进行推理

7 总结


项目背景及意义

        猫狗大战是一个经典的深度学习分类挑战,旨在通过构建一个准确的分类模型,区分图像中的猫和狗。随着互联网的普及和社交媒体的盛行,人们在网络上分享各种各样的猫狗图片。然而,对于一些人来说,区分猫和狗并不容易,特别是在图像复杂或者特征不明显的情况下。
        通过猫狗大战项目,我们可以利用深度学习算法和图像分类技术,自动化地识别和区分猫和狗。这样的模型可以在很多场景中应用,例如社交媒体平台上的自动标注、宠物识别应用、安防监控系统等。

1 作业简介:猫狗大战之图像识别

1.1 问题描述

        在本项目中需要收集和准备适用于VGG19模型的图像数据集,包括分类标签和数据预处理。随后根据VGG19的网络结构,在深度学习框架中构建VGG19模型,包括卷积层、池化层和全连接层的设置。使用准备好的训练数据集对VGG19模型进行训练,调整模型的参数和超参数。训练完成后,使用验证集对训练好的VGG19模型进行评估,通过计算准确率、损失值等指标来评估模型的性能。

1.2 图像展示

2 数据预处理

2.1 数据集结构

        本项目数据集共由测试集和训练集两部分组成,分别包含为test1,train文件夹。

2.2 数据探索性分析

        首先取训练集中的前九张图片进行展示

# 获取训练集中的九张图片
image_dir = './train'
image_files = os.listdir(image_dir)[:9]

# 创建一个3行3列的子图布局
fig, axes = plt.subplots(3, 3, figsize=(10, 10))

# 设置图片标题的字体大小
plt.rcParams['axes.titlesize'] = 10

# 遍历每个子图,并显示图片
for i, ax in enumerate(axes.flat):
    # 加载图片
    image_path = os.path.join(image_dir, image_files[i])
    sample_image = plt.imread(image_path)

    # 显示图片
    ax.imshow(sample_image)
    ax.axis('off')
    ax.set_title(f'Image {i+1}')

# 调整子图之间的间距
plt.tight_layout()

# 展示图片
plt.show()

2.3 拆分数据集

对训练集进行拆分,按照8:2的比例分为训练集和验证集,并分别对训练集和验证集分为cats和dogs文件夹,利于后续训练。

import os
import random
import shutil
import numpy as np  # 导入NumPy库
from numpy.random import seed
#此代码划分训练集和数据集
def spiltTrainTest(image_folder,tarin_ratio=0.8,seed=42):
    image_file=[file for file in os.listdir(image_folder) if file.lower().endswith('.jpg')]#访问jpg
    random.seed(seed)
    random.shuffle(image_file)#打乱列表
    split=int(len(image_file)*tarin_ratio)#计算切分点
    train_set=image_file[:split]
    test_set=image_file[split:]
    return train_set,test_set

def moveImage_tofolders(file_list,source_folder,target_folder):
    for file_name in file_list:
        source_path=os.path.join(source_folder,file_name)#image/cat.ssss原来的路径
        target_path=os.path.join(target_folder,file_name)#train/cat.sss
        shutil.move(source_path,target_path)

if __name__=='__main__':
    image_folder="train"#图片文件夹
    train_set, test_set=spiltTrainTest(image_folder)

    train_folder="train"
    test_folder="test"
    os.makedirs(train_folder, exist_ok=True)
    os.makedirs(test_folder, exist_ok=True)
    moveImage_tofolders(train_set,image_folder,train_folder)
    moveImage_tofolders(test_set,image_folder,test_folder)

2.4 提取数据集

# 创建自定义数据集
class SelfDataset(Dataset):
    def __init__(self, root_dir, transform=None):
        self.root_dir = root_dir
        self.transform = transform
        self.classes = ['cats', 'dogs']
        self.data = self.load_data()
 
    def load_data(self):
        data = []
        for class_idx, class_name in enumerate(self.classes):
            class_path = os.path.join(self.root_dir, class_name)
            for file_name in os.listdir(class_path):
                file_path = os.path.join(class_path, file_name)
                if os.path.isfile(file_path) and file_name.lower().endswith('.jpeg'):
                    data.append((file_path, class_idx))
        return data
 
    def __len__(self):
        return len(self.data)
 
    def __getitem__(self, idx):
        img_path, label = self.data[idx]
        img = Image.open(img_path).convert('RGB')
        if self.transform:
            img = self.transform(img)
        return img, label

2.5 数据增强

        采用transforms.RandomResizedCrop(64)首先对图像进行随机裁剪,并随机调整裁剪后的图像大小为 64x64 像素。这样的操作有助于模型学习对不同尺寸和位置的物体具有更好的鲁棒性,从而提高泛化能力。同时使用了transforms.RandomHorizontalFlip进行随机水平翻转图像。这个操作通过一定的概率水平翻转图像,以扩充训练数据。这可以帮助模型学到物体在水平方向上的不变性。其次,还使用了transforms.ToTensor将图像转换为张量形式。这个操作将图像的像素值从0到255缩放到0到1之间,并将图像的维度顺序从H x W x C变为C x H x W,以满足模型的输入要求。

# 数据增强
transform = transforms.Compose([
    transforms.RandomResizedCrop(64),

    transforms.RandomHorizontalFlip(),

    transforms.ToTensor(),
])

使用卷积神经网络识别猫狗图像

3.1 卷积神经网络

        卷积神经网络(Convolutional Neural Network,CNN)是一种深度学习模型,广泛应用于图像和视频处理任务。与传统神经网络相比,卷积神经网络利用了卷积操作和池化操作,能够有效地处理具有空间结构的数据,如图像。

        卷积神经网络通常由多个卷积层、激活函数、池化层和全连接层组成。每个卷积层由多个卷积核组成,每个卷积核可以提取输入数据中的不同特征。卷积操作通过滑动窗口的方式在输入数据上进行计算,生成特征图。激活函数通常被应用于特征图上,以引入非线性。池化层通过降采样的方式减小特征图的尺寸,同时保留重要的特征。全连接层将池化层输出的特征图映射到模型的最终输出。

3.2 VGG-19架构

        VGG19 是一个深度卷积神经网络,由牛津大学的 Visual Geometry Group 开发并命名。这个名字中的 "19" 代表着它的网络深度——总共有 19 层,这包括卷积层和全连接层。

        这个模型在一系列的图像处理任务中表现得非常出色,包括图像分类、物体检测和语义分割等。VGG19 的一个显著特点是其结构的简洁和统一。它主要由一系列的卷积层和池化层堆叠而成,其中每个卷积层都使用相同大小的滤波器,并且每两个卷积层之间都插入了一个池化层。这样的设计使得 VGG19 既可以有效地提取图像特征,又保持了结构的简洁性。

        在本项目中,使用VGG19模型来实现猫狗识别,下图为模型结构图。

3.3 VGG-19与VGG-16架构对比

        VGG16和VGG19模型最大的区别就是它们的深度不同。VGG16模型是由16个卷积层和3个全连接层组成,而VGG19模型则是由19个卷积层和3个全连接层组成,因此VGG19模型比VGG16模型更加深层。

        另外,在VGG19模型中增加了更多的卷积层,这些层会增加更多的特征提取能力。然而,由于深度的增加,VGG19模型的参数量也跟着增加,这会造成训练的时间和计算资源的费用增加。

3.4 优化函数

在本项目中使用以下几个函数来提高训练的精度:

(1)交叉熵损失函数 (nn.CrossEntropyLoss())。交叉熵损失对于分类任务是一种常见的损失函数,它在训练期间衡量模型的预测和真实标签之间的差异。

(2)Adam 优化器 (optim.Adam)。是一种基于梯度的优化算法,通常在深度学习中表现较好。

(3)ReduceLROnPlateau 学习率调度器(optim.lr_scheduler.ReduceLROnPlateau)。该调度器在验证集上监测模型性能,并在性能停滞时降低学习率。

criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(vgg16_model.parameters(), lr=0.001, weight_decay=1e-4)
 
 
# 添加学习率调度器
# 使用 ReduceLROnPlateau 调度器
scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='max', factor=0.1, patience=3, verbose=True)
 
 
# 训练参数
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
vgg16_model.to(device)

在GPU上进行训练

4.1 训练步骤

通过循环训练、验证和测试的步骤,逐渐优化模型的参数,提高模型在训练数据和测试数据上的性能,详细步骤如下:

(1)设置迭代次数上限为50,初始化 F1分数计数为0。

(2)设置模型为训练模式:使用vgg19_model.train()将模型设置为训练模式,以启用模型的训练过程中的特定行为。

(3)计算训练损失:遍历训练数据集train_loader中的数据批次,将数据传递给模型进行前向计算,计算预测值outputs,并计算损失值loss。然后进行反向传播和优化器的更新,以使模型学习适应训练数据。

(4)计算验证损失:在每个epoch结束时,使用验证数据集val_loader进行验证。遍历验证数据集中的数据批次,将数据传递给模型进行前向计算,计算预测值val_outputs,并计算验证损失val_loss。

(5)计算平均训练损失和平均验证损失:将训练损失和验证损失分别除以对应数据集的批次数,得到平均训练损失avg_train_loss和平均验证损失avg_val_loss。

(6)打印训练过程中的损失和验证损失:使用print语句打印当前epoch的训练集损失和验证集损失。

(7)模型评估:将模型设置为评估模式,使用测试数据集test_loader进行模型的最终评估。遍历测试数据集中的数据批次,将数据传递给模型进行前向计算,得到预测值outputs。通过计算预测值outputs和真实标签labels之间的F1分数,评估模型在测试集上的性能。

(8)调整学习率:使用scheduler.step(f1)调整优化器的学习率。

# 训练循环
num_epochs = 0
consecutive_f1_count = 0

while num_epochs < 10:
    print(f'第{num_epochs+1}次训练开始了')
    vgg19_model.train()  # 设置模型为训练模式
    train_loss = 0.0
    for inputs, labels in train_loader:
       
        inputs, labels = inputs.to(device), labels.to(device)

        # 将数据传递给模型
        outputs = vgg19_model(inputs)

        # 计算损失
        loss = criterion(outputs, labels)

        # 反向传播和优化
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        train_loss += loss.item()

    # 计算平均训练损失
    avg_train_loss = train_loss / len(train_loader)

    # 计算平均验证损失


    # 打印训练过程中的损失和验证损失
    print(f'Epoch [{num_epochs+1}], 第{num_epochs+1}轮:训练集损失: {avg_train_loss:.4f}')

    # 在模型训练完后,使用测试集进行最终评估
    vgg19_model.eval()
    all_predictions = []
    all_labels = []
    start_time = time.time()  # 记录开始时间
    with torch.no_grad():
        for inputs, labels in test_loader:
            inputs, labels = inputs.to(device), labels.to(device)

            # 在测试集上进行推理
            outputs = vgg19_model(inputs)

            # 将预测结果和真实标签保存
            _, predicted = torch.max(outputs, 1)
            all_predictions.extend(predicted.cpu().numpy())
            all_labels.extend(labels.cpu().numpy())

    end_time = time.time()  # 记录结束时间
    elapsed_time = end_time - start_time
    print(f'测试集用的时间为: {elapsed_time:.2f} seconds')

    # 计算F1分数
    f1 = f1_score(all_labels, all_predictions, average='binary')  # 适用于二分类问题

    # 打印每轮的测试F1分数
    print(f'第{num_epochs+1}轮的测试F1分数: {f1:.4f}')


    # 调整学习率
    scheduler.step(f1)

    # 增加训练次数
    num_epochs += 1

4.2 查看test数据集F1分数及时间

在GPU上查看F1分数和时间时,大概F1分数达到0.9以上,时间为70s左右。

4.3 使用验证集和测试集进行推理测试

        从验证数据集中选择一张图片,并使用训练好的模型进行预测。然后,将原始图片、真实标签和预测标签可视化展示出来。

# 选择一张 test_loader 中的图片
sample_image, true_label = next(iter(test_loader))

# 将图片传递给模型进行预测
sample_image = sample_image.to(device)
with torch.no_grad():
    model_output = vgg19_model(sample_image)

# 获取预测结果
_, predicted_label = torch.max(model_output, 1)

# 转换为 NumPy 数组
sample_image = sample_image.cpu().numpy()[1]  # 将数据从 GPU 移回 CPU 并取出第一张图片
predicted_label = predicted_label[1].item()
print(predicted_label)
true_label = true_label[0].item()  # 直接获取标量值
print(true_label)
# 获取类别标签
class_labels = ['cat', 'dog']

# 显示图像
plt.imshow(np.transpose(sample_image, (1, 2, 0)))  # 转置图片的维度顺序
plt.title(f'TRUE LABEL IS: {class_labels[true_label]}, PREDICT LABEL IS: {class_labels[predicted_label]}')
plt.axis('off')
plt.show()

import matplotlib.pyplot as plt
import numpy as np
from PIL import Image  # Ensure you have the PIL library installed

# Assuming you have defined data_transform earlier
data_transform = transforms.Compose([
    transforms.Resize(224),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
])

# 选择一张指定图像进行训练
image_path = './test1/10011.jpg'  # 替换为你的图像文件路径
sample_image = Image.open(image_path).convert('RGB')
sample_image = data_transform(sample_image)
sample_image = sample_image.unsqueeze(0)  # 添加批次维度

# 将图片传递给模型进行预测
sample_image = sample_image.to(device)
with torch.no_grad():
    model_output = vgg19_model(sample_image)

# 获取预测结果
_, predicted_label = torch.max(model_output, 1)

# 转换为 NumPy 数组
sample_image = sample_image.cpu().numpy()[0]  # 将数据从 GPU 移回 CPU 并取出第一张图片
predicted_label = predicted_label[0].item()
print(predicted_label)

# 获取类别标签
class_labels = ['cat', 'dog']

# 显示图像
plt.imshow(np.transpose(sample_image, (1, 2, 0)))  # 转置图片的维度顺序
plt.title(f' PREDICT LABEL IS: {class_labels[predicted_label]}')
plt.axis('off')
plt.show()

5 在CPU上进行训练

5.1 创建VGG19模型

class CustomVGG19(nn.Module):
    def __init__(self):
        super(CustomVGG19, self).__init__()
        self.vgg19_model = models.vgg19(pretrained=True)
        for param in self.vgg19_model.features.parameters():
            param.requires_grad = False
        num_features = self.vgg19_model.classifier[6].in_features
        self.vgg19_model.classifier[6] = nn.Sequential(
            nn.Linear(num_features, 512),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(512, 2)
        )

    def forward(self, x):
        return self.vgg19_model(x)

# 创建 CustomVGG19 模型实例
vgg19_model = CustomVGG19()

# 加载权重
vgg19_model.vgg19_model.load_state_dict(torch.load('vgg19_model.pth', map_location=torch.device('cpu')))

# 将模型移动到CPU
device = torch.device('cpu')
vgg19_model.to(device)

# 重新构建优化器
optimizer = optim.Adam(vgg19_model.parameters(), lr=0.001, weight_decay=1e-4)

# 使用Intel Extension for PyTorch进行优化
vgg19_model, optimizer = ipex.optimize(model=vgg19_model, optimizer=optimizer, dtype=torch.float32)

5.2 直接在CPU上进行训练

vgg19_model.eval()

# Assuming you have a DataLoader for the test dataset (test_loader)
all_predictions = []
all_labels = []
start_time = time.time()

with torch.no_grad():
    for inputs, labels in test_loader:
        inputs, labels = inputs.to(device), labels.to(device)
        outputs = vgg19_model(inputs)
        _, predicted = torch.max(outputs, 1)
        all_predictions.extend(predicted.cpu().numpy())
        all_labels.extend(labels.cpu().numpy())

end_time = time.time()  # 记录结束时间
elapsed_time = end_time - start_time
print(f'测试集用的时间为: {elapsed_time:.2f} seconds')
f1 = f1_score(all_labels, all_predictions, average='binary')  # 适用于二分类问题
print(f'F1分数为: {f1:.4f}')

在CPU上直接进行推理的话,时间会非常慢。大概为120s左右


6 使用oneAPI组件

6.1 Transfer Learning with oneAPI AI Analytics Toolkit进行迁移学习

class CustomVGG19(nn.Module):
    def __init__(self):
        super(CustomVGG19, self).__init__()
        self.vgg19_model = models.vgg19(pretrained=True)
        for param in self.vgg19_model.features.parameters():
            param.requires_grad = False
        num_features = self.vgg19_model.classifier[6].in_features
        self.vgg19_model.classifier[6] = nn.Sequential(
            nn.Linear(num_features, 512),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(512, 2)
        )

    def forward(self, x):
        return self.vgg19_model(x)

6.2 使用Intel Extension for PyTorch进行优化

vgg19_model = CustomVGG19()

# 加载权重
vgg19_model.vgg19_model.load_state_dict(torch.load('vgg19_model.pth', map_location=torch.device('cpu')))

# 将模型移动到CPU
device = torch.device('cpu')
vgg19_model.to(device)

# 重新构建优化器
optimizer = optim.Adam(vgg19_model.parameters(), lr=0.001, weight_decay=1e-4)

# 使用Intel Extension for PyTorch进行优化
vgg19_model, optimizer = ipex.optimize(model=vgg19_model, optimizer=optimizer, dtype=torch.float32)

6.3 使用 Intel® Neural Compressor 量化模型 

# 检查文件是否存在
assert os.path.exists("./vgg19_optimized.pth"), "文件不存在"
 
# 尝试加载模型
model = torch.load("./vgg19_optimized.pth")
print("模型加载成功")
from neural_compressor.config import PostTrainingQuantConfig, AccuracyCriterion
from neural_compressor import quantization
import os
 
# 加载模型
model = CustomVGG19()
model.load_state_dict(torch.load('vgg19_optimized.pth'))
model.to('cpu')  # 将模型移动到 CPU
model.eval()
 
# 定义评估函数
def eval_func(model):
    with torch.no_grad():
        y_true = []
        y_pred = []
 
        for inputs, labels in train_loader:
            inputs = inputs.to('cpu')
            labels = labels.to('cpu')
            preds_probs = model(inputs)
            preds_class = torch.argmax(preds_probs, dim=-1)
            y_true.extend(labels.numpy())
            y_pred.extend(preds_class.numpy())
 
        return accuracy_score(y_true, y_pred)
 
# 配置量化参数
conf = PostTrainingQuantConfig(backend='ipex',  # 使用 Intel PyTorch Extension
                               accuracy_criterion=AccuracyCriterion(higher_is_better=True, 
                                                                   criterion='relative',  
                                                                   tolerable_loss=0.01))
 
# 执行量化
q_model = quantization.fit(model,
                           conf,
                           calib_dataloader=train_loader,
                           eval_func=eval_func)
 
# 保存量化模型
quantized_model_path = './quantized_models'
if not os.path.exists(quantized_model_path):
    os.makedirs(quantized_model_path)
 
q_model.save(quantized_model_path)

量化成功后,结果如下:

6.4 使用量化后的模型在 CPU上进行推理

import json
from neural_compressor import quantization
 
# 指定量化模型的路径
quantized_model_path = './quantized_models'
 
# 加载 Qt 模型和 JSON 配置
vgg19_model_path = f'{quantized_model_path}/best_model.pt'
json_config_path = f'{quantized_model_path}/best_configure.json'
 
# 加载 Qt 模型
vgg19_model = torch.jit.load(vgg19_model_path, map_location='cpu')
 
# 加载 JSON 配置
with open(json_config_path, 'r') as json_file:
    json_config = json.load(json_file)
 
# 打印 JSON 配置(可选)
# print(json_config)
import torch
from sklearn.metrics import f1_score
import time
 
# 假设 test_loader 是你的测试数据加载器
# 请确保它返回 (inputs, labels) 的形式
 
 
# 将模型设置为评估模式
vgg19_model.eval()
 
# 初始化变量用于存储真实标签和预测标签
y_true = []
y_pred = []
 
# 开始推理
start_time = time.time()
 
# 设置 batch_size
batch_size = 64
 
# 使用 DataLoader 时设置 batch_size
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
 
# 在推理时处理每个批次
 
 
with torch.no_grad():
    for inputs, labels in test_loader:
        # 将输入数据移动到 CPU(如果尚未在 CPU 上)
        inputs = inputs.to('cpu')
        labels = labels.to('cpu')
 
        # 获取模型预测
        preds_probs = vgg19_model(inputs)
        preds_class = torch.argmax(preds_probs, dim=-1)
 
        # 扩展真实标签和预测标签列表
        y_true.extend(labels.numpy())
        y_pred.extend(preds_class.numpy())
 
# 计算 F1 分数
f1 = f1_score(y_true, y_pred, average='weighted')
 
# 计算推理时间
inference_time = time.time() - start_time
 
# 打印结果
print(f"测试集用的时间为: {inference_time} seconds")
print(f"F1分数: {f1}")
 

7 总结

        通过深度学习的卷积神经网络以及OneAPI加速来实现猫狗图像识别,对这两方面的内容有了更深的理解,实现能力大幅提高。

        在使用oneAPI的优化组件以后,推理的时间大幅度下降并且在整个过程中F1分数的值始终在0.9左右。证明了oneAPI优秀的模型压缩能力,在加速的同时保障了正确性。

  • 18
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
深度学习是一种机器学习的分支,其目标是通过模仿人脑神经网络的工作方式来模拟和理解人类的智能行为。TensorFlow是一个强大的深度学习框架,它提供了丰富的工具和函数来构建和训练神经网络模型。 在图像领域中,一项有趣的实验是图像风格迁移。这是一种技术,将一张图像的风格应用于另一张图像,创造出一幅以第一幅图像风格为基础的新图像。其中VGG-19是一种深度卷积神经网络模型,被广泛用于图像识别图像风格迁移任务。 图像风格迁移实验基于VGG-19模型的步骤如下:首先,我们需要将待迁移的风格图像和内容图像加载到模型中。然后,通过计算内容图像和目标图像之间的差异来定义一个损失函数。该损失函数包括内容损失和风格损失两部分,内容损失用于保留内容图像的特征,风格损失用于学习风格图像的特征。 接下来,我们使用梯度下降的优化算法来最小化损失函数,从而生成目标图像。在每次迭代中,我们根据当前生成的图像的梯度来更新输入图像。 在实验过程中,我们可以观察到生成图像逐渐采用了风格图像的特征,并保留了内容图像的主要结构。通过调整不同的参数和迭代次数,我们可以获得不同风格和质量的图像。 综上所述,VGG-19模型的图像风格迁移实验利用了深度学习和TensorFlow的强大功能。它为我们提供了一种有趣的方式来创造具有不同风格的图像,并有助于我们更深入地理解和应用深度学习的原理和技术。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值