37、卷积神经网络实验:MNIST与CIFAR - 10数据集

卷积神经网络实验:MNIST与CIFAR - 10数据集

1. 实验背景

在图像识别领域,卷积神经网络(CNN)是一种强大的工具。我们将通过MNIST和CIFAR - 10两个数据集来深入探索CNN的性能和特点。MNIST数据集包含手写数字图像,而CIFAR - 10数据集则包含10个不同类别的真实图像,包括动物和交通工具。

2. MNIST数据集实验

2.1 部分数字的影响

最初的模型在处理部分数字时效果不佳,但当我们将部分数字纳入训练集后,模型对实际数字的响应变得稳健,而对其他数字的响应则非常微弱甚至不存在。这是因为第一个模型没有涵盖实际应用中可能遇到的输入空间,而第二个模型的训练数据集更好地代表了可能的输入分布,所以性能有了显著提升。

2.2 打乱MNIST数字实验

对于传统神经网络,打乱MNIST数字的像素顺序(只要对每个图像的像素重映射是确定性的)不会影响其训练和分类效果。但对于CNN,情况有所不同。我们将打乱的MNIST数字数据集替换标准MNIST数据集,在基线CNN模型中进行训练。实验结果表明,CNN在处理打乱的数字时测试误差更高。这是因为CNN通过卷积学习核来创建输入的新表示,卷积生成的响应具有空间依赖性,而打乱数字后这种空间依赖性大多被消除,导致CNN的底层可学习的内容减少。不过,最终CNN在处理打乱数字时的表现仍优于传统模型,误差约为2%,而传统模型为4.4%。

3. CIFAR - 10数据集实验

3.1 CIFAR - 10数据集介绍

CIFAR - 10是一个10类数据集,由加拿大高级研究所(CIFAR)提供。它包含32×32像素的RGB图像,分为6类动物和4类交通工具。训练集有50,000张图像,每类5,000张;测试集有10,000张图像,每类1,000张。与MNIST相比,CIFAR - 10是真实的图像数据集,背景和图像内容更加复杂,模型学习难度更大。

CIFAR - 10的类别如下表所示:
| Label | Class |
| — | — |
| 0 | airplane |
| 1 | automobile |
| 2 | bird |
| 3 | cat |
| 4 | deer |
| 5 | dog |
| 6 | frog |
| 7 | horse |
| 8 | ship |
| 9 | truck |

3.2 全数据集模型训练

我们使用两种不同的模型在整个CIFAR - 10数据集上进行训练:
- 浅层模型 :与之前用于MNIST数据集的模型类似,只有两个卷积层。
- 深层模型 :在池化和全连接层之前使用多个卷积层。

同时,我们尝试了随机梯度下降(SGD)和Adadelta两种优化算法。固定小批量大小为64,训练60个周期,共进行46,875次梯度下降步骤。对于SGD,学习率为0.01,动量为0.9;Adadelta是自适应的,会动态调整学习率。

3.2.1 模型构建

以下是构建浅层和深层模型的代码:

# 浅层模型
import keras
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras import backend as K
import numpy as np

batch_size = 64
num_classes = 10
epochs = 60
img_rows, img_cols = 32, 32

x_train = np.load("cifar10_train_images.npy")
y_train = np.load("cifar10_train_labels.npy")
x_test = np.load("cifar10_test_images.npy")
y_test = np.load("cifar10_test_labels.npy")

if K.image_data_format() == 'channels_first':
    x_train = x_train.reshape(x_train.shape[0], 3, img_rows, img_cols)
    x_test = x_test.reshape(x_test.shape[0], 3, img_rows, img_cols)
    input_shape = (3, img_rows, img_cols)
else:
    x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 3)
    x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 3)
    input_shape = (img_rows, img_cols, 3)

x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255

y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)

model = Sequential()
model.add(Conv2D(32, kernel_size=(3, 3),
                 activation='relu',
                 input_shape=input_shape))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes, activation='softmax'))

model.compile(loss=keras.losses.categorical_crossentropy,
              optimizer=keras.optimizers.Adadelta(),
              metrics=['accuracy'])

# 深层模型
model = Sequential()
model.add(Conv2D(32, kernel_size=(3, 3),
                 activation='relu',
                 input_shape=input_shape))
model.add(Conv2D(64, (3,3), activation='relu'))
model.add(Conv2D(64, (3,3), activation='relu'))
model.add(Conv2D(64, (3,3), activation='relu'))
model.add(Conv2D(64, (3,3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes, activation='softmax'))

model.compile(loss=keras.losses.categorical_crossentropy,
              optimizer=keras.optimizers.Adadelta(),
              metrics=['accuracy'])
3.2.2 模型训练和评估

我们还需要一个使用SGD的版本,只需在 compile 方法中替换优化器引用:

optimizer=keras.optimizers.SGD(lr=0.01, momentum=0.9)

以下是完整的训练和测试代码:

print("Model parameters = %d" % model.count_params())
print(model.summary())
history = model.fit(x_train, y_train,
                    batch_size=batch_size,
                    epochs=epochs,
                    verbose=1,
                    validation_data=(x_test[:1000], y_test[:1000]))
score = model.evaluate(x_test[1000:], y_test[1000:], verbose=0)
print('Test loss:', score[0])
print('Test accuracy:', score[1])
model.save("cifar10_cnn_model.h5")
3.2.3 实验结果

运行代码后,得到不同模型和优化器组合的测试集准确率如下表所示:
| | Shallow | Deep |
| — | — | — |
| Adadelta | 71.9% | 74.8% |
| SGD | 70.0% | 72.8% |

从表中可以看出,Adadelta在浅层和深层模型中都能得到更准确的模型,且深层模型的表现优于浅层模型。自适应优化器(如Adadelta和Adam)通常比普通SGD更受青睐,但也有观点认为,当学习率设置正确并在训练过程中逐渐降低时,SGD最终的效果可能相同甚至更好。我们可以先使用自适应优化器使模型接近损失函数的最小值,然后再切换到SGD进行微调。

3.3 模型分析

3.3.1 训练损失分析

通过观察训练过程中损失的变化,我们发现:
- 使用Adadelta时,损失没有SGD低,且浅层模型的损失随周期略有增加,这与传统观点中训练损失应单调递减相悖,可能是自适应算法的一种表现。但从测试准确率来看,Adadelta能使浅层和深层模型都获得更高的准确率。
- 使用SGD时,浅层模型的损失比深层模型小,这通常被认为是可能过拟合的迹象,即模型过度学习了训练集的细节。根据测试准确率,使用SGD的浅层模型表现最差,而深层模型在60个训练周期内没有出现极小的损失。

3.3.2 验证集准确率分析

验证集误差(1 - 准确率)的变化情况如下:
- 无论使用哪种优化器,深层模型在训练过程中的验证集误差都更低,表现更好。
- SGD的曲线符合我们的直觉,随着模型训练,误差逐渐减小,深层模型很快超过浅层模型,且曲线相对平滑。
- Adadelta的误差曲线在最初几个周期后明显下降,但之后验证集误差会有一些波动,这是由于Adadelta算法的自适应性质,它会动态调整学习率以寻找更好的最小值。从最终结果来看,Adadelta能找到性能更好的模型。

3.4 动物或交通工具分类实验

CIFAR - 10数据集中有4类交通工具和6类动物,我们构建一个模型来区分这两类。具体步骤如下:
1. 调整标签

import numpy as np
y_train = np.load("cifar10_train_labels.npy")
y_test = np.load("cifar10_test_labels.npy")
for i in range(len(y_train)):
    if (y_train[i] in [0,1,8,9]):
        y_train[i] = 0
    else:
        y_train[i] = 1
for i in range(len(y_test)):
    if (y_test[i] in [0,1,8,9]):
        y_test[i] = 0
    else:
        y_test[i] = 1
np.save("cifar10_train_animal_vehicle_labels.npy", y_train)
np.save("cifar10_test_animal_vehicle_labels.npy", y_test)
  1. 模型训练
    使用之前的浅层模型架构,类别数设置为2,小批量大小为128,训练12个周期。使用Adadelta作为优化器,前1,000个测试样本用于验证,其余9,000个用于最终测试。
3.4.1 性能指标计算

我们更新了 tally_predictions 函数来计算基本指标:

def tally_predictions(model, x, y):
    pp = model.predict(x)
    p = np.zeros(pp.shape[0], dtype="uint8")
    for i in range(pp.shape[0]):
        p[i] = 0 if (pp[i,0] > pp[i,1]) else 1
    tp = tn = fp = fn = 0
    for i in range(len(y)):
        if (p[i] == 0) and (y[i] == 0):
            tn += 1
        elif (p[i] == 0) and (y[i] == 1):
            fn += 1
        elif (p[i] == 1) and (y[i] == 0):
            fp += 1
        else:
            tp += 1
    score = float(tp+tn) / float(tp+tn+fp+fn)
    return [tp, tn, fp, fn, score]

将返回的列表传递给 basic_metrics advanced_metrics (代码未给出),得到的性能指标如下表所示:
| Metric | Result |
| — | — |
| TP | 5,841 |
| FP | 480 |
| TN | 3,520 |
| FN | 159 |
| TPR (sensitivity, recall) | 0.9735 |
| TNR (specificity) | 0.8800 |
| PPV (precision) | 0.9241 |
| NPV | 0.9568 |
| FPR | 0.1200 |
| FNR | 0.0265 |
| F1 | 0.9481 |
| MCC | 0.8671 |
| κ | 0.8651 |
| Informedness | 0.8535 |
| Markedness | 0.8808 |
| Accuracy | 0.9361 |

从这些指标可以看出,该模型表现良好,但特异性为88%略低。Matthews相关系数(MCC)为0.8671,表明这是一个不错的二元分类器。

3.4.2 ROC曲线计算
from sklearn.metrics import roc_auc_score, roc_curve
def roc_curve_area(model, x, y):
    pp = model.predict(x)
    p = np.zeros(pp.shape[0], dtype="uint8")
    for i in range(pp.shape[0]):
        p[i] = 0 if (pp[i,0] > pp[i,1]) else 1
    auc = roc_auc_score(y,p)
    roc = roc_curve(y,pp[:,1])
    return [auc, roc]

计算得到的AUC为0.9267,ROC曲线陡峭且接近图的左上角,这是模型性能良好的标志。

3.4.3 误分类分析

我们可以使用完整的类别标签来分析误分类的情况,找出哪些车辆类别容易被误分类为动物,哪些动物类别容易被误分类为车辆。代码如下:

import numpy as np
from keras.models import load_model
x_test = np.load("cifar10_test_images.npy")/255.0
y_label = np.load("cifar10_test_labels.npy")
y_test = np.load("cifar10_test_animal_vehicle_labels.npy")
model = load_model("cifar10_cnn_animal_vehicle_model.h5")
pp = model.predict(x_test)
p = np.zeros(pp.shape[0], dtype="uint8")
for i in range(pp.shape[0]):
    p[i] = 0 if (pp[i,0] > pp[i,1]) else 1
hp = []; hn = []
for i in range(len(y_test)):
    if (p[i] == 0) and (y_test[i] == 1):
        hn.append(y_label[i])
    elif (p[i] == 1) and (y_test[i] == 0):
        hp.append(y_label[i])
hp = np.array(hp)
hn = np.array(hn)
a = np.histogram(hp, bins=10, range=[0,9])[0]
b = np.histogram(hn, bins=10, range=[0,9])[0]
print("vehicles as animals: %s" % np.array2string(a))
print("animals as vehicles: %s" % np.array2string(b))

结果显示:

vehicles as animals: [189 69 0 0 0 0 0 0 105 117]
animals as vehicles: [ 0 0 64 34 23 11 12 15 0 0]

这意味着在被误分类为动物的车辆中,有189个是飞机(类别0);最不容易被误分类为动物的车辆是汽车(类别1)。在被误分类为车辆的动物中,鸟类(类别2)最容易被误分类,而狗(类别5)最不容易被误分类。这是因为飞机和飞行的鸟类在图像上看起来可能比较相似。

4. 总结

通过对MNIST和CIFAR - 10数据集的实验,我们可以得出以下结论:
- CNN在处理具有空间依赖性的数据时表现出色,但当空间关系较弱或不存在时,其优势可能会减弱。
- 自适应优化算法(如Adadelta)和适当深度的网络通常能得到性能更好的模型,但模型大小需要根据训练集进行调整,避免过拟合。
- 在构建分类模型时,我们可以通过分析误分类情况来进一步了解模型的局限性和数据的特点。

在实际应用中,我们可以根据具体的数据集和任务需求,选择合适的模型架构和优化算法,并通过不断调整和实验来提高模型的性能。

下面是一个简单的mermaid流程图,展示了CIFAR - 10数据集实验的主要步骤:

graph LR
    A[加载CIFAR - 10数据集] --> B[构建模型(浅层/深层)]
    B --> C[选择优化算法(Adadelta/SGD)]
    C --> D[训练模型]
    D --> E[评估模型]
    E --> F[分析结果(损失、准确率等)]
    F --> G[调整标签(动物/交通工具分类)]
    G --> H[重新训练模型]
    H --> I[计算性能指标]
    I --> J[分析误分类情况]

5. 实验结论与建议

5.1 实验结论总结

  • CNN特性 :CNN在处理具有空间依赖性的数据时具有显著优势,如在MNIST数据集上能有效学习数字特征。但当数据的空间关系被打乱或减弱时,其性能会受到一定影响,不过仍优于传统神经网络。
  • 优化算法 :自适应优化算法(如Adadelta)通常能使模型更快地收敛到较好的结果,在浅层和深层模型中都能得到更准确的模型。相比之下,普通SGD在学习率设置不当的情况下可能效果不佳,但当学习率设置正确并在训练过程中逐渐降低时,SGD最终的效果可能相同甚至更好。
  • 模型深度 :适当增加模型的深度可以提高模型的表达能力,从而在复杂数据集(如CIFAR - 10)上取得更好的性能。但模型过深可能会导致过拟合,需要根据训练集的大小和复杂度来调整模型的深度。
  • 误分类分析 :通过分析模型的误分类情况,我们可以了解模型的局限性和数据的特点,从而有针对性地改进模型或收集更多的数据。

5.2 实际应用建议

  • 模型选择 :在实际应用中,首先应根据数据集的特点选择合适的模型架构。如果数据具有明显的空间特征,CNN是一个不错的选择;如果数据的空间关系较弱,可以考虑使用传统神经网络或其他模型。
  • 优化算法选择 :建议优先使用自适应优化算法(如Adadelta或Adam),因为它们通常能在较少的训练步骤内得到较好的结果。如果有足够的时间和计算资源,可以尝试使用SGD,并通过调整学习率和动量来优化模型。
  • 模型调整 :在训练模型时,应从一个较小的模型开始,逐渐增加模型的复杂度,观察模型的性能变化。如果模型出现过拟合的迹象,可以尝试减少模型的参数数量、增加训练数据或使用正则化方法(如Dropout)。
  • 误分类处理 :在模型训练完成后,分析误分类的样本,找出模型容易混淆的类别。可以通过收集更多的这些类别的样本、调整模型架构或优化特征工程来提高模型的性能。

6. 未来研究方向

6.1 模型改进

  • 架构创新 :探索新的CNN架构,以更好地处理复杂数据集和弱空间关系的数据。例如,结合注意力机制、残差连接等技术,提高模型的表达能力和泛化能力。
  • 多模态融合 :将图像数据与其他模态的数据(如文本、音频)相结合,构建多模态模型,以获取更丰富的信息,提高模型的性能。

6.2 数据集扩展

  • 数据增强 :研究更有效的数据增强方法,以增加训练数据的多样性,提高模型的泛化能力。例如,使用生成对抗网络(GAN)生成更多的合成数据。
  • 新数据集探索 :寻找和使用更多具有挑战性的数据集,以推动CNN技术的发展。例如,处理高分辨率图像、视频数据等。

6.3 优化算法研究

  • 自适应优化算法改进 :进一步研究自适应优化算法的原理和性能,提出更高效、更稳定的优化算法。
  • 混合优化策略 :探索将不同的优化算法结合使用的策略,以充分发挥各种算法的优势。

7. 附录:代码汇总

7.1 CIFAR - 10数据集预处理代码

import keras
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras import backend as K
import numpy as np

batch_size = 64
num_classes = 10
epochs = 60
img_rows, img_cols = 32, 32

x_train = np.load("cifar10_train_images.npy")
y_train = np.load("cifar10_train_labels.npy")
x_test = np.load("cifar10_test_images.npy")
y_test = np.load("cifar10_test_labels.npy")

if K.image_data_format() == 'channels_first':
    x_train = x_train.reshape(x_train.shape[0], 3, img_rows, img_cols)
    x_test = x_test.reshape(x_test.shape[0], 3, img_rows, img_cols)
    input_shape = (3, img_rows, img_cols)
else:
    x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 3)
    x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 3)
    input_shape = (img_rows, img_cols, 3)

x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255

y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)

7.2 浅层模型构建代码

model = Sequential()
model.add(Conv2D(32, kernel_size=(3, 3),
                 activation='relu',
                 input_shape=input_shape))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes, activation='softmax'))

model.compile(loss=keras.losses.categorical_crossentropy,
              optimizer=keras.optimizers.Adadelta(),
              metrics=['accuracy'])

7.3 深层模型构建代码

model = Sequential()
model.add(Conv2D(32, kernel_size=(3, 3),
                 activation='relu',
                 input_shape=input_shape))
model.add(Conv2D(64, (3,3), activation='relu'))
model.add(Conv2D(64, (3,3), activation='relu'))
model.add(Conv2D(64, (3,3), activation='relu'))
model.add(Conv2D(64, (3,3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes, activation='softmax'))

model.compile(loss=keras.losses.categorical_crossentropy,
              optimizer=keras.optimizers.Adadelta(),
              metrics=['accuracy'])

7.4 使用SGD的模型编译代码

optimizer=keras.optimizers.SGD(lr=0.01, momentum=0.9)

7.5 模型训练和评估代码

print("Model parameters = %d" % model.count_params())
print(model.summary())
history = model.fit(x_train, y_train,
                    batch_size=batch_size,
                    epochs=epochs,
                    verbose=1,
                    validation_data=(x_test[:1000], y_test[:1000]))
score = model.evaluate(x_test[1000:], y_test[1000:], verbose=0)
print('Test loss:', score[0])
print('Test accuracy:', score[1])
model.save("cifar10_cnn_model.h5")

7.6 调整标签代码

import numpy as np
y_train = np.load("cifar10_train_labels.npy")
y_test = np.load("cifar10_test_labels.npy")
for i in range(len(y_train)):
    if (y_train[i] in [0,1,8,9]):
        y_train[i] = 0
    else:
        y_train[i] = 1
for i in range(len(y_test)):
    if (y_test[i] in [0,1,8,9]):
        y_test[i] = 0
    else:
        y_test[i] = 1
np.save("cifar10_train_animal_vehicle_labels.npy", y_train)
np.save("cifar10_test_animal_vehicle_labels.npy", y_test)

7.7 计算基本指标代码

def tally_predictions(model, x, y):
    pp = model.predict(x)
    p = np.zeros(pp.shape[0], dtype="uint8")
    for i in range(pp.shape[0]):
        p[i] = 0 if (pp[i,0] > pp[i,1]) else 1
    tp = tn = fp = fn = 0
    for i in range(len(y)):
        if (p[i] == 0) and (y[i] == 0):
            tn += 1
        elif (p[i] == 0) and (y[i] == 1):
            fn += 1
        elif (p[i] == 1) and (y[i] == 0):
            fp += 1
        else:
            tp += 1
    score = float(tp+tn) / float(tp+tn+fp+fn)
    return [tp, tn, fp, fn, score]

7.8 计算ROC曲线代码

from sklearn.metrics import roc_auc_score, roc_curve
def roc_curve_area(model, x, y):
    pp = model.predict(x)
    p = np.zeros(pp.shape[0], dtype="uint8")
    for i in range(pp.shape[0]):
        p[i] = 0 if (pp[i,0] > pp[i,1]) else 1
    auc = roc_auc_score(y,p)
    roc = roc_curve(y,pp[:,1])
    return [auc, roc]

7.9 误分类分析代码

import numpy as np
from keras.models import load_model
x_test = np.load("cifar10_test_images.npy")/255.0
y_label = np.load("cifar10_test_labels.npy")
y_test = np.load("cifar10_test_animal_vehicle_labels.npy")
model = load_model("cifar10_cnn_animal_vehicle_model.h5")
pp = model.predict(x_test)
p = np.zeros(pp.shape[0], dtype="uint8")
for i in range(pp.shape[0]):
    p[i] = 0 if (pp[i,0] > pp[i,1]) else 1
hp = []; hn = []
for i in range(len(y_test)):
    if (p[i] == 0) and (y_test[i] == 1):
        hn.append(y_label[i])
    elif (p[i] == 1) and (y_test[i] == 0):
        hp.append(y_label[i])
hp = np.array(hp)
hn = np.array(hn)
a = np.histogram(hp, bins=10, range=[0,9])[0]
b = np.histogram(hn, bins=10, range=[0,9])[0]
print("vehicles as animals: %s" % np.array2string(a))
print("animals as vehicles: %s" % np.array2string(b))

7. 参考文献

本博客中涉及的代码和实验基于Python和Keras库实现,相关库的详细文档可以参考以下链接:
- Keras官方文档:https://keras.io/
- NumPy官方文档:https://numpy.org/doc/
- Scikit - learn官方文档:https://scikit - learn.org/stable/documentation.html

以下是一个mermaid流程图,展示了整个实验的流程:

graph LR
    A[选择数据集(MNIST/CIFAR - 10)] --> B[数据预处理]
    B --> C[构建模型(CNN/传统神经网络)]
    C --> D[选择优化算法(Adadelta/SGD等)]
    D --> E[训练模型]
    E --> F[评估模型]
    F --> G{模型性能是否满足要求?}
    G -- 是 --> H[分析误分类情况]
    G -- 否 --> I[调整模型架构或优化算法]
    I --> E
    H --> J[总结实验结果]
    J --> K[提出改进建议和未来研究方向]

通过以上的实验和分析,我们对CNN在不同数据集上的性能有了更深入的了解。在实际应用中,我们可以根据具体的需求和数据特点,选择合适的模型和优化算法,以提高模型的性能和泛化能力。同时,不断探索新的模型架构和优化算法,将有助于推动深度学习技术的发展。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值