sklearn-2无监督学习

所有代码都上传到gitee仓库,可自行下载machine_learning: 机器学习基础教程 作者andreas 读书笔记代码 (gitee.com)

1 分类

数据集变换(无监督变换,unsupervised transformation)和聚类

1.1 数据集变换

创建数据的新的表示方法,因为新数据可以让机器学习模型更容易理解

一个常见应用是数据降维,即将多特征数据转化成特征数量少的数据,降维还可以将数据可视化

另一个常见应用是找到数据的各个组成部分,比如从文本中提取主题

1.2 聚类算法

原理 将数据划分成不同的组,每组包含相似的内容

例子 从一堆照片识别出某人的所有照片,然后将这人照片分成一个组

2 难点

因为无监督,所以不知道学习的结果是否正确与准确

3 作用

作为监督学习的预处理步骤,提高监督学习准确率

特征提取,数据缩放预处理

4 预处理和缩放

4.1 预处理

本节预处理主要讲了放缩

4.1.1 不同类别预处理

StandardScaler 将数据所有特征处理为0-1范围内的值(减平均值除标准差),但不能保证特征值有特定的最值

RobustScaler 和StandardSccaler类似,只是用的是中位数和四分位数,而不是均值和标准差

MinMaxScaler 移动数据,使范围在0-1之间

Normalizer 使每个点特征向量的欧式长度为1(模长度?),也就是说每个特征向量放缩程度不一样

4.1.2 应用数据变换

看个例子,对多特征数据和两个特征数据缩放,看是否缩放成功

训练集和测试集使用单独的scaler会导致放缩不一致,无法将测试集特征范围放缩到0-1范围内,如要保证train和test集都在0-1需使用同一个scaler

    def test_scale_cancer_minmax(self):
        xtr, xte, ytr, yte = train_test_split(self.cancer.data, self.cancer.target, random_state=1)
        print(f'origin cancer data and target shape: {self.cancer.data.shape}, {self.cancer.target.shape}')
        scaler = MinMaxScaler().fit(xtr)
        xtr_scaled, xte_scaled = scaler.transform(xtr), scaler.transform(xte)
        print(f'transformed shape: {xtr_scaled.shape}')
        print(f'feature min:{xtr.min(axis=0)}\nmax:{xtr.max(axis=0)},\nfeature scaled min:{xtr_scaled.min(axis=0)}\nscaled max:{xtr_scaled.max(axis=0)}')

4.1.3 对测试集和训练集进行相同缩放

看个例子,对比缩放前后特征值最大最小值,看测试集和训练集缩放结果,是否缩放比例一致。实际发现缩放后特征值已在范围0-1内

    def test_scale_blob_minmax(self):
        xtr, xte, _, _ = train_test_split(*self.blob, random_state=5, test_size=.1)
        fig, axes = plot.subplots(1, 3, figsize=(13, 4))
        axes[0].scatter(xtr[:, 0], xtr[:, 1], c=mglearn.cm2(0), label='train set', s=60)
        axes[0].scatter(xte[:, 0], xte[:, 1], c=mglearn.cm2(0), marker='^', label='test set', s=60)
        axes[0].legend(loc='upper left')
        axes[0].set_title('origin data')

        scaler = MinMaxScaler().fit(xtr)    # data = Scaler().fit_transform(input)
        xtr_scaled, xte_scaled = scaler.transform(xtr), scaler.transform(xte)
        axes[1].scatter(xtr_scaled[:, 0], xtr_scaled[:, 1], c=mglearn.cm2(0), label='train set', s=60)
        axes[1].scatter(xte_scaled[:, 0], xte_scaled[:, 1], c=mglearn.cm2(0), label='test set', s=60, marker='^')
        axes[1].set_title('scaled data')

        te_scaler = MinMaxScaler().fit(xte)
        xte_scaled_bad = te_scaler.transform(xte)
        axes[2].scatter(xtr_scaled[:, 0], xtr_scaled[:, 1], c=mglearn.cm2(0), label='train set', s=60)
        axes[2].scatter(xte_scaled_bad[:, 0], xte_scaled_bad[:, 1], c=mglearn.cm2(0), label='test set', s=60, marker='^')
        axes[2].set_title('improperly scaled test data')

        for ax in axes:
            ax.set_xlabel('feature 0')
            ax.set_ylabel('feature 1')
        plot.show()

这几个图是两个特征数据集缩放情况,发现测试集和训练集单独使用scaler无法保证缩放两者缩放比例一致

4.1.4 预处理对监督学习作用

对比下对特征值范围敏感的模型SVC放缩前后准确度

    def test_predict_cancer_scaled(self):
        xtr, xte, ytr, yte = train_test_split(self.cancer.data, self.cancer.target, random_state=0)
        svm = SVC(C=100).fit(xtr, ytr)
        print(f'SVC accuracy: {svm.score(xte, yte)}')

        scaler = MinMaxScaler().fit(xtr)
        xtr_scaled, xte_scaled = scaler.transform(xtr), scaler.transform(xte)
        svm.fit(xtr_scaled, ytr)
        print(f'SVC scaled accuracy: {svm.score(xte_scaled, yte)}')

4.2 降维,特征提取,流形学习

4.2.1 主成分分析(PCA,principal component analysis)

概念 是一种旋转数据集的方法,旋转后的特征在统计上不相关

主成分 在数据集样本中找到方差最大的方向,这个方向叫主成分

看个例子辅助理解

4.2.1.1 将PCA应用于cancer数据集

一个作用 将高维特征数据集可视化,比如cancer数据集有30个特征,可视化的话如果散点图矩阵需要30*30/2-30=420个图,太多了

对cancer数据集每个特征计算两个类别的直方图:

    def test_(self):
        fig, axes = plot.subplots(6, 5, figsize=(20, 10))
        malignant, benign = self.cancer.data[self.cancer.target==0], self.cancer.data[self.cancer.target==1]
        ax = axes.ravel()
        for i in range(30):
            _, bins = np.histogram(self.cancer.data[:, i], bins=50)
            ax[i].hist(malignant[:, i], bins=bins, color=mglearn.cm3(0), alpha=.5)
            ax[i].hist(benign[:, i], bins=bins, color=mglearn.cm3(2), alpha=.5)
            ax[i].set_title(self.cancer.feature_names[i])
            ax[i].set_yticks(())
        #ax[0].set_xlabel('feature magnitude')
        #ax[0].set_ylabel('frequency')
        ax[0].legend(['magnitude', 'benign'], loc='best')
        fig.tight_layout()
        plot.show()

这些直方图可以看出良性和恶行肿瘤中哪些特征是易于区分的,但看不出特征之间相互作用关系,PCA可以做到并可视化

先放缩cancer数据,然后用PCA寻找主成分,转化为主成分后看下shape

看到主成分是有两个特征的样本,通过散点图画下主成分

代码如下,可以看出用这两个主特征可以很好的区别良性恶性肿瘤,但问题是这两个主特征是怎么从30个特征里算出来的?这也是pca的一个缺点

    def test_pca_cancer(self):
        scaler = StandardScaler().fit(self.cancer.data)
        x_scaled = scaler.transform(self.cancer.data)
        pca = PCA(n_components=2).fit(x_scaled)   # keep 2 pca in 30 features
        x_pca = pca.transform(x_scaled)
        print(f'scaled x shape:{x_scaled.shape}, pca x shape:{x_pca.shape}')
        plot.figure(figsize=(8, 8))
        mglearn.discrete_scatter(x_pca[:, 0], x_pca[:, 1], self.cancer.target)
        plot.legend(self.cancer.target_names, loc='best')
        plot.gca().set_aspect('equal')
        plot.xlabel('first principal component')
        plot.ylabel('second principal component')
        plot.show()
4.2.1.2 特征提取人脸

先看下长啥样

    def test_show_faces(self):
        fix, axes = plot.subplots(2, 5, figsize=(15, 8), subplot_kw={'xticks': (), 'yticks': ()})
        for target, image, ax in zip(self.faces.target, self.faces.images, axes.ravel()):
            ax.imshow(image)
            ax.set_title(self.faces.target_names[target])
        print(f'faces shape: {self.faces.images.shape}, number of classes: {len(self.faces.target_names)}')
        plot.show()

结果表明,有3023张图片,总共属于62个人的脸,每张照片87x65像素

但数据有些倾斜,即照片里某几个人的照片多些。为了降低数据倾斜的影响,可以每个人取数量相同的照片

然后对数据放缩,灰度值范围为0-255,直接除个255放缩到0-1内

人脸识别难点 1图片库图片很多但每个单独的人图片不多 2有时需要加别人的人脸同时不希望重新训练模型

方法 单一最近邻分类器,看下有多准

    def test_predict_faces(self):
        mask = np.zeros(self.faces.target.shape, dtype=np.bool_)
        for target in np.unique(self.faces.target):
            mask[np.where(self.faces.target == target)[0][:50]] = 1
        x_faces, y_faces = self.faces.data[mask], self.faces.target[mask]
        x_faces /= 255
        xtr, xte, ytr, yte = train_test_split(x_faces, y_faces, stratify=y_faces, random_state=0)
        knn = KNC(n_neighbors=1).fit(xtr, ytr)
        print(f'test 1-nn score: {knn.score(xte, yte)}')

发现准度很低,约为21%,其实knn原理是最近邻,这种方式识别人脸其实不太行,具体来说是比较每个像素的灰度值,这其实捕捉不到人脸的特征,人脸识别需要通过人脸特征,而不是灰度值识别。那咋办?

此时可以使用PCA的白化方法。该方法可将主成分放缩到相同尺度,看个例子理解下

然后创pca对象,提取前100个主成分(即特征数量,人脸识别中特征数量为像素总大小:87x65=5655个),然后用pca学习的数据预测下人脸识别准度。这100个主成分好像是从5655得出来的,怎么得出来的还不知道

        pca = PCA(n_components=100, whiten=True, random_state=0).fit(xtr)
        xtr_pca, xte_pca = pca.transform(xtr), pca.transform(xte)
        knn_pca = KNC(n_neighbors=1).fit(xtr_pca, ytr)
        print(f'test 1-nn pca score: {knn_pca.score(xte_pca, yte)}')

发现更准了,提高了8个百分点

可视化看几个主成分长啥样

    def test_plot_pca_component(self):
        x_faces, y_faces = self.get_train_test_50_faces()
        xtr, xte, ytr, yte = train_test_split(x_faces, y_faces, stratify=y_faces, random_state=0)
        pca = PCA(n_components=100, whiten=True, random_state=0).fit(xtr)
        fix, axes = plot.subplots(3, 5, figsize=(15, 12), subplot_kw={'xticks': (), 'yticks': ()})
        for i, (component, ax) in enumerate(zip(pca.components_, axes.ravel())):
            ax.imshow(component.reshape(self.faces.images[0].shape), cmap='viridis')    # viridis:翠绿色
            ax.set_title(f'{i + 1} component')
        plot.show()

4.2.1.3 PCA应用于人脸识别总结

pca对人脸识别来说并不能捕获主要特征,4.2.1用pca分析人脸主要为了加强对pca理解

4.2.2 非负矩阵分解(non-negative matrix factorization,NMF)

主要目的 提取有用特征(思考相比于pca区别,pca提取主成分(正交),NMF提取有用特征,可以不正交)

用途 1也可用来降维 2可以识别叠加分量中的独立分量(如嘈杂环境识别出都有哪些音源)

特点 1提取有用特征不是原封不动提取,也是类似pca,区别是希望分量和分量系数均大于0,所以只适用于值大于0的数据集 2对多个源叠加出的数据很有用,可以识别出独立分量 3提取的特征相比于pca更容易理解

4.2.2.1 NMF应用于模拟数据
4.2.2.2 NMF应用于人脸识别

重点看下怎么使用

用nmf提取15个向量看下

    def test_plot_extract_15_components(self):
        x_faces, y_faces = self.get_train_test_50_faces()
        xtr, xte, ytr, yte = train_test_split(x_faces, y_faces, stratify=y_faces, random_state=0)
        nmf = NMF(n_components=15, random_state=0).fit(xtr)
        xtr_nmf, xte_nmf = nmf.transform(xtr), nmf.transform(xte)
        fix, axes = plot.subplots(3, 5, figsize=(15, 12), subplot_kw={'xticks':(), 'yticks':()})
        for i, (component, ax) in enumerate(zip(nmf.components_, axes.ravel())):
            ax.imshow(component.reshape(self.faces.images[0].shape))
            ax.set_title(f'{i} component')
        plot.show()

nmf的分量都是非负,因此比pca的更像人脸,比如成分4和7可以看出是分别向右和左看的人脸

看下成分是4和7的主要素材长啥样

4.2.2.3 nmf用于识别独立信号

nmf还可从混合信号中分离出单独信号分量

    def test_plot_signal(self):
        fig, axes = plot.subplots(5, figsize=(8, 4), gridspec_kw={'hspace': .5}, subplot_kw={'xticks':(), 'yticks':()})
        axes[0].plot(self.signals, '-')
        axes[0].set_title('origin signal data')
        A = np.random.RandomState(0).uniform(size=(100, 3))
        X = np.dot(self.signals, A.T)   # matrix multiply 2000-3 x 3-100 = 2000-100
        print(f'shape of measurements: {X.shape}')
        nmf = NMF(n_components=3, random_state=42)
        S_ = nmf.fit_transform(X)
        print(f'recovered signal shape: {S_.shape}')
        H = PCA(n_components=3).fit_transform(X)
        models = [X, self.signals, S_, H]
        names = ["Observations(first 3 measurements)","True sources", "NMF recovered", "PCA recovered"]
        for model, name, ax in zip(models, names, axes[1:]):
            ax.set_title(name)
            ax.plot(model[:, :3], '-')
        plot.show()

4.2.3 t-SNE流形学习

问题 PCA对于人脸主成分提取,实际限制了人脸可视化的有效性

方法 t-SNE是专门针对可视化的一类算法,可以给出更好的可视化,对特征映射或拟合也更复杂

用途 主要用于可视化

看下pca对手写数字的主成分分类,不太能区分出各数字间的区别

看下t-SNE对数据的分类,发现可以很明显把各数字分成独自的一堆

    def test_plot_tSNE_and_pca(self):
        fig, axes = plot.subplots(2, 5, figsize=(10, 5), subplot_kw={'xticks':(),'yticks':()})
        for ax, img in zip(axes.ravel(), self.digits.images):
            ax.imshow(img)
        print(f'digits data shape: {self.digits.data.shape}')
        pca = PCA(n_components=2).fit(self.digits.data)
        digits_pca = pca.transform(self.digits.data)
        colors = ["#476A2A", "#7851B8", "#BD3430", "#4A2D4E", "#875525",
                  "#A83683", "#4E655E", "#853541", "#3A3120", "#535D8E"]
        plot.figure(figsize=(10, 10))
        plot.xlim(digits_pca[:, 0].min(), digits_pca[:, 0].max())
        plot.ylim(digits_pca[:, 1].min(), digits_pca[:, 1].max())
        for i in range(len(self.digits.data)):
            plot.text(digits_pca[i, 0], digits_pca[i, 1], str(self.digits.target[i]), color=colors[self.digits.target[i]], fontdict={'weight': 'bold', 'size': 9})
        plot.xlabel('first principal component')
        plot.ylabel('second principal component')

        digits_sne = TSNE(random_state=42).fit_transform(self.digits.data)
        plot.figure(figsize=(10, 10))
        plot.xlim(digits_sne[:, 0].min(), digits_sne[:, 0].max())
        plot.ylim(digits_sne[:, 1].min(), digits_sne[:, 1].max())
        for i in range(len(self.digits.data)):
            plot.text(digits_sne[i, 0], digits_sne[i, 1], str(self.digits.target[i]), color=colors[self.digits.target[i]], fontdict={'weight': 'bold', 'size': 9})
        plot.xlabel('t-SNE feature 0')
        plot.ylabel('t-SNE feature 1')
        plot.show()

4.2.4 总结

提取特征特点解释性分量地位
pca正交困难主次
NMF非负容易平等

5 聚类

聚类与分类相反,将单个点放到一个类里,类特征高度独立

5.1 k均值聚类(KMeans)

思想 尝试找到数据特定区域的簇中心。算法交替执行两个步骤:将每个点分配给里的最近的簇中心,将簇中心设为所有分配点的平均值。重复执行这两个步骤,直到簇位置不再变化为止

使用 创k均值对象需指定簇数量即可

看下正常预期的簇数量和异常簇数量

5.1.1 k均值不适合场景与案例

缺陷 簇是一个凸的形状,也意味着簇只能找一些简单的簇形状

缺陷1 无法判断个别簇边界点属于的簇,可能误判,因为是取平均值,每个样本点权重相同

缺陷2 对每个方向的权重相同,导致无法判断有某些特点的数据集,比如半月数据,比如棒状数据集(簇中心随机)

5.1.2 矢量量化,或将k均值分解

对比pca, nmf, kmean对人脸识别的结果和分量如下图

    def test_plot_faces_pca_nmf_kmean(self):
        x_faces, y_faces = self.get_train_test_50_faces()
        xtr, xte, ytr, yte = train_test_split(x_faces, y_faces, stratify=y_faces, random_state=0)
        nmf, pca, kmean = NMF(n_components=100, random_state=0).fit(xtr), PCA(n_components=100, random_state=0).fit(xtr), KMeans(n_clusters=100, random_state=0).fit(xtr)
        x_nmf = np.dot(nmf.transform(xte), nmf.components_)
        x_pca = pca.inverse_transform(pca.transform(xte))
        x_kmean = kmean.cluster_centers_[kmean.predict(xte)]

        image_shape = self.faces.images[0].shape
        fig, axes = plot.subplots(3, 5, figsize=(8, 8), subplot_kw={'xticks':(), 'yticks':()})
        fig.suptitle('extracted components')
        for ax, comp_kmean, comp_pca, comp_nmf in zip(axes.T, kmean.cluster_centers_, pca.components_, nmf.components_):
            ax[0].imshow(comp_kmean.reshape(image_shape))
            ax[1].imshow(comp_pca.reshape(image_shape), cmap='viridis')
            ax[2].imshow(comp_nmf.reshape(image_shape))
        axes[0, 0].set_ylabel('kmean')
        axes[1, 0].set_ylabel('pca')
        axes[2, 0].set_ylabel('nmf')
        fig, axes = plot.subplots(4, 5, subplot_kw={'xticks':(), 'yticks':()}, figsize=(8, 8))
        fig.suptitle('reconstructions')
        for ax, orig, rec_kmean, rec_pca, rec_nmf in zip(axes.T, xte, x_kmean, x_pca, x_nmf):
            ax[0].imshow(orig.reshape(image_shape))
            ax[1].imshow(rec_kmean.reshape(image_shape))
            ax[2].imshow(rec_pca.reshape(image_shape))
            ax[3].imshow(rec_nmf.reshape(image_shape))
        axes[0, 0].set_ylabel('origin')
        axes[1, 0].set_ylabel('kmean')
        axes[2, 0].set_ylabel('pca')
        axes[3, 0].set_ylabel('nmf')
        plot.show()

发现kmean反向还原的某几个图像不如pca和nmf准,比如第四列明显误判,原因可能是簇的数量,感觉也是模型局限性

5.1.3 优缺点

优点 速度较快,较容易理解,容易扩展到大数据集

缺点 对簇数量约束强,算法输出(初始簇)依赖随机种子(改进的聚类算法有凝聚聚类)

5.2 凝聚聚类算法(agglomerative clustering)

思想 将每个点都看作一个簇,然后合并两个最相似簇,停止条件是簇的数量。可调的参数有簇数量,簇合并的规则

簇合并方法

1 ward,默认,使两个簇方差最小

2 average 将簇中点之间平均距离最小的两个簇合并

3 complete 将簇中两点最大距离最屌的两个簇合并

5.2.1 层次聚类与树状图

凝聚聚类过程可以堪称层次聚类,观察层次聚类过程有助于理解分簇具体过程与特点

层次图只能看两个特征的数据集,对于多个特征的数据集,可以用树状图看,但sklearn没提供树状图功能,可以用scipy查看,scipy通过x生成一个连接数组,转化为二维图查看聚类过程

树状图的y还可以看出点与簇之间的距离

缺陷是凝聚聚类仍无法解决two_moon这类数据集

5.2.2 优缺点

优点 解决k均值聚类无法处理某些图像形状的簇聚类

缺点 仍无法看出two_moon这类数据聚类

5.3 DBSCAN

思想 1识别特征空间中拥挤的点 2拥挤点形成的簇用相对较空的趋于分割

核心样本 簇中密集的那些点,点一个eps距离内有min_sample各样本,则该点称为核心样本点

过程 先随机选点,然后判断该点是否为核心样本点,如果是则给该点分一个簇标签,然后递归判断该店所有邻居是否为核心样本点,以此类推。如果随机选点不是核心样本点,则给该点一个噪点的标签

所有点有哪几类 核心点 边界点 噪点

特点 1虽然初始选点随机,但多次运行DBSCAN的簇结果相同 2边界点属于的簇是他的邻居核心点所在簇(如果和多个簇相邻则取决于数据点的访问顺序)

解决了k均值和凝聚聚类无法解决的two_moon聚类,但需要调参

5.3.1 优缺点

优点 不需要用户事先知道数据大概可分为多少个簇,相比于凝聚聚类,可以处理更复杂形状,可以找出不属于任何簇的点

缺点 无法通过DBSCAN创建多于一个的较大簇

5.4 聚类算法对比与评估

5.4.1 用真实值评估

有指标可以评估聚类算法准确度,看两个用的多的:调整rand指数(adjusted rand index,ARI)和归一化互信息(normalized mutual information,NMI),1表示最好,0表示不相关

看下DBSCAN, k均值,凝聚算法对two_moon数据集分类的ARI得分

    def test_predict_accuracy(self):
        scaler = StandardScaler().fit(self.moons[0])
        x_scale = scaler.transform(self.moons[0])
        fig, axes = plot.subplots(1, 4, figsize=(15, 3), subplot_kw={'xticks':(),'yticks':()})
        algorithms = [KMeans(n_clusters=2), Agg(n_clusters=2), DBSCAN()]
        random_clusters = np.random.RandomState(seed=0)
        random_clusters = random_clusters.randint(low=0, high=2, size=len(self.moons[0]))
        axes[0].scatter(x_scale[:, 0], x_scale[:, 1], c=random_clusters, cmap=mglearn.cm3, s=60)
        axes[0].set_title(f'random assignment ARI: {adjusted_rand_score(self.moons[1], random_clusters):.2f}')
        for ax, algorithm in zip(axes[1:], algorithms):
            clusters = algorithm.fit_predict(x_scale)
            ax.scatter(x_scale[:, 0], x_scale[:, 1], c=clusters, cmap=mglearn.cm3, s=60)
            ax.set_title(f'{algorithm.__class__.__name__} - ARI: {adjusted_rand_score(self.moons[1], clusters):.2f}')
        plot.tight_layout()
        plot.show()

注意,ARI和NMI一般适用于有已知簇标签情况的聚类,是监督学习,无监督用不上,可以使用无监督的评估指标

5.4.2 没有真实值情况评估

有些指标可以无监督评估准确度,比如轮廓系数(silhouette coefficient),它计算一个簇的紧凑度,1表示最好,0表示最差,注意,轮廓系数不允许复杂簇形状

三个聚类算法对半月数据集查看轮廓系数比分评估

发现KMean评分最高,尽管DBSCAN聚类更符合直觉

所以,对评估指标的选择很关键。一般可以选择基于鲁棒性的聚类指标。这种指标先向数据添加一些噪声,然后调节参数进行评估

即使用鲁棒性号的指标评估模型得到了高分,也无法具体解释每个簇分类的实际含义,比如想把人脸里有胡子的分成簇,但实际不知道怎么分,唯一方法是对簇进行人工分析

5.4.3 在人脸数据集比较聚类算法

分别在人脸数据集上应用DBSCAN,KMeans, 凝聚聚类算法

DBSCAN

1 随着eps由小变大,簇数量先增加到10各左右再减小到1-2个

2 簇数量最高时,分析每个簇成分,发现都是高度相似的人脸,人脸表情,角度,明暗等高度相似,且基本是同一个人的脸

KMeans 

1 不想DBSCAN有噪点,KMeans将所有点都会归到簇地下底下

总结 

簇的均匀度, KMeans最均匀,凝聚聚类次之,DBSCAN最不均匀

5.6 总结与对比

1-5,1最差5最好

模型速度大数据集
kmean43
凝聚聚类3.53
DBSCAN33

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值