监督学习--分类之决策树

监督学习-分类-决策树


决策树使用树形分支结构分类事物

例:

  • 小丽找对象,要求:高、帅、富
  • 小明找对象,要求:美美美
if height >= 172:
    if hansom = '帅':
        if rich >= 5000000:
            print('小哥哥我晚上有空!')
        else:
            print('加个微信吧!')
    else:
        print('孩子放学了,我该做饭去了!')
else:
    print('呵呵,洗澡去了!')

构造二叉树结构的两个指标:

  • 在什么地方分支(特征的先后次序,执行效率不同)
  • 分支的阈值定多数合适?

所谓的决策树算法,就是通过数据自动学习出二叉树的分支维度顺序和每个分支的阈值

与一般的分支结构不同,决策树的分支条件(特征维度、阈值)通过训练得到,而非手动构造

import numpy as np
import matplotlib.pyplot as plt

from sklearn.datasets import load_iris

决策树skicit-learn实现

1:获取数据,特征工程,样本数据(特征和标签)

iris = load_iris()
# iris
X = iris['data'][:, 2:]
y = iris['target']
# X

2:将样本数据分为训练集和测试集

为方便可视化理解,只取第2和第3列两维特征

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.3, random_state=3)

3:算法:创建分类器

  • 调包,调用不同分类器
  • 调参,调节超参数
from sklearn.tree import DecisionTreeClassifier
# 创建空分类器
# my_classifier = DecisionTreeClassifier()

my_classifier = DecisionTreeClassifier(max_depth=2, criterion='entropy', random_state=42)
# # max_depth最大深度,entropy信息熵算法,随机种子
my_classifier
DecisionTreeClassifier(class_weight=None, criterion='entropy', max_depth=2,
                       max_features=None, max_leaf_nodes=None,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=2,
                       min_weight_fraction_leaf=0.0, presort=False,
                       random_state=42, splitter='best')

4:训练:用训练集(特征和标签)训练分类器

my_classifier.fit(X_train, y_train)
DecisionTreeClassifier(class_weight=None, criterion='entropy', max_depth=2,
                       max_features=None, max_leaf_nodes=None,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=2,
                       min_weight_fraction_leaf=0.0, presort=False,
                       random_state=42, splitter='best')

5:预测:使用测试集(特征)预测标签

predictions = my_classifier.predict(X_test)
predictions
array([0, 0, 0, 0, 0, 2, 1, 0, 2, 1, 1, 0, 1, 1, 2, 0, 1, 2, 2, 0, 2, 2,
       2, 1, 0, 2, 1, 1, 1, 1, 0, 0, 2, 1, 0, 0, 2, 0, 2, 1, 2, 1, 0, 0,
       2])
predictions.shape
(45,)

6:用测试集自带的标签和预测的标签比较,评价分类器的准确率

from sklearn.metrics import accuracy_score
accuracy_score(predictions, y_test)  # 正确率
0.9555555555555556

决策树原理可视化

# 绘图函数,不用写,直接调用
def plot_decision_boundary(model, axis):
   
    x0, x1 = np.meshgrid(
        np.linspace(axis[0], axis[1], int((axis[1]-axis[0])*100)).reshape(-1, 1),
        np.linspace(axis[2], axis[3], int((axis[3]-axis[2])*100)).reshape(-1, 1),
    )
    X_new = np.c_[x0.ravel(), x1.ravel()]

    y_predict = model.predict(X_new)
    zz = y_predict.reshape(x0.shape)

    from matplotlib.colors import ListedColormap
    custom_cmap = ListedColormap(['#EF9A9A','#FFF59D','#90CAF9'])
   
    plt.contourf(x0, x1, zz, cmap=custom_cmap)
plot_decision_boundary(my_classifier, axis=[0.5, 7.5, 0, 3])

plt.scatter(X[y==0, 0], X[y==0, 1])
plt.scatter(X[y==1, 0], X[y==1, 1])
plt.scatter(X[y==2, 0], X[y==2, 1])
<matplotlib.collections.PathCollection at 0x134be1d0>

[外链图片转存失败(img-RkKTIih7-1566998078176)(output_21_1.png)]


下面代码,生成可视化文件的库:graphviz需要安装,不同环境安装方式不同

  • 可以使用 export_graphviz 导出器以 Graphviz 格式导出决策树
  • 如果你是用 conda 来管理包,那么安装 graphviz 二进制文件和 python 包可以用以下指令安装:
# Conda安装库命令
conda install python-graphviz

注:网络问题可能需要翻墙

pdf文件是在整个 iris 数据集上训练的决策树树的 graphviz 导出示例,其结果被保存在 iris_xxx.pdf 中:

  • iris_entropy.pdf
  • iris_gini.pdf

生成文档中:

  • gini值为当前节点系统基尼系数
  • entropy值为当前节点系统熵值
  • samples值为此分支节点下还有多少值待拆分
  • values值为待拆分值的分类
import graphviz
from sklearn.tree import export_graphviz
# # 基尼系数算法
# dot_data = export_graphviz(my_classifier, out_file=None) 
# graph = graphviz.Source(dot_data)
# graph.render("iris_gini")
# # 信息熵算法
# dot_data = export_graphviz(my_classifier, out_file=None) 
# graph = graphviz.Source(dot_data)
# graph.render("iris_entropy")

用下面测试数据人工模拟决策树分类过程

模拟时,data的下标是0,1,2,3,target的结果是0,1,2

X[0], X[51], X[102]
(array([1.4, 0.2]), array([4.5, 1.5]), array([5.9, 2.1]))
y[0], y[51], y[102]
(0, 1, 2)

决策树原理

  • 每个分支节点在哪个特征维度划分?
  • 某个特征维度在什么值划分?

根据某一特征维度下的某一阈值进行二分,划分后使得整个系统信息熵降低


信息熵

信息熵:不确定性的度量

  • 封闭系统,不确定性越高,熵值越大,越混乱
  • 封闭系统,不确定性越低,熵值越小,越规律

信息熵公式:$ H=-\sum ^{k}{i=1}p{i}\log \left( p_{i}\right) $

  • 解释:一个封闭系统中,有k类信息,每类信息的比例为p

信息、能量、物质三者是可以相互转换的


例:鸢尾花数据集,共有3类鸢尾花,每类鸢尾花所占比例为1/3,p1/p2/p3都等于1/3,本系统熵值为:

$ H= -\dfrac {1}{3}\cdot \log \left( \dfrac {1}{3}\right) -\dfrac {1}{3}\cdot \log \left( \dfrac {1}{3}\right) -\dfrac {1}{3}\cdot \log \left( \dfrac {1}{3}\right) =1.0986 $

-(1/3) * np.log2(1/3) - (1/3) * np.log2(1/3) - (1/3) * np.log2(1/3)  # byte 比特
1.584962500721156

例:一个封闭系统,三种花所占比例为:1/10、2/10、7/10,本系统熵值为:

-(1/10) * np.log2(1/10) - (2/10) * np.log2(2/10) - (7/10) * np.log2(7/10)
1.1567796494470395

例:一个封闭系统,三种花所占比例为:1, 0, 0,本系统熵值为:

-1/1 * np.log2(1/1) - 0 - 0  # 完全确定的系统,没有不确定性
-0.0
1.58 - 1.15  # 信息增益,传入一条信息到封闭空间,导致空间熵值下降,下降数就是信息量
# 原封闭系统,熵值最大就是1.58,最小是0
0.43000000000000016

信息熵图像

  • 设系统分两类,一类比例为x,则另一类比例为1-x
  • 代入信息熵公式:H = -x*log(x)-(1-x)*log(1-x)
def en(x):
    return -x * np.log2(x) - (1-x) * np.log2(1-x)

x = np.linspace(0.01, 0.99, 200)  # 对数的底大于0且不等于1
x

plt.plot(x, en(x))  # x轴为分类的比例,y轴为熵值
plt.xticks(np.arange(10)*0.1)
plt.grid(linewidth=0.2)
plt.show()

[外链图片转存失败(img-112FJKKi-1566998078184)(output_38_0.png)]

结论:2分类系统中,类别比例0.5的,熵值最大,不确定性最高

使用信息熵寻找最优划分

  • 遍历特征数据下所有可划分特征维度和所有取值
    • 当划分一种特征一个值后,系统熵值降到最低,此特征下的此值就是本分支节点
  • 继续用分割后熵值不为0的特征数据划分下一分支节点,循环往复
  • 直到整个系统熵降到最低

手写决策树算法

使用信息熵作为指标划分数据为不同类别

from collections import Counter
# 计算系统总信息熵的流程
y
Counter(y)
res = 0
for i in Counter(y).values():
    print(i)
    p = i / len(y)  # 每类值出现的概率
    res += -p * np.log2(p)
res
50
50
50

1.584962500721156

# 划分类别函数

def split(X, y, d, value):  # 特征,标签,维度,阈值
    index_a = X[:, d] <= value  # 布尔索引
    index_b = X[:, d] > value

    # 返回被划分后的两类数据(特征和标签)
    return X[index_a], X[index_b], y[index_a], y[index_b]


# 计算信息熵的函数
def entropy(y):
    counter = Counter(y)
    res = 0.0
    for i in counter.values():
        p = i / len(y)  # 每类值出现的概率
        res += -p * np.log2(p)
    return res


# 寻找最佳维度和阈值,让每次划分后系统信息熵下降最大(信息熵最低)
def try_split(X, y):
    best_entropy = float('inf')  # 系统拆分后的总信息熵,设为正无穷大
    best_d, best_v = -1, -1  # 划分维度和本维度下的划分阈值
    
    # 遍历特征列
    # d的取值为0, 1,总共只有2两列,0代表花瓣长1代表花瓣宽
    for d in range(X.shape[1]):  # 遍历特征数据X最外维的值(遍历所有特征列)
        # 遍历特征行(取每两行 特征值的中间值)
        sorted_index = np.argsort(X[:, d])  # 排序特征行(d列的所有行)
        for i in range(1, len(X)):  # 
            x1 = X[sorted_index[i - 1], d]  # 本列特征值i- 1
            x2 = X[sorted_index[i], d]
            
            if x1 != x2:  # 如果相等则没有划分价值,跳过代码
                v = (x1 + x2) / 2  # 候选阈值
                # 数据用本维度和阈值划分为left和right两部分
                X_l, X_r, y_l, y_r = split(X, y, d, v)

                # 本阈值划分后的系统熵值
                e = entropy(y_l) + entropy(y_r)
                if e < best_entropy:  # 如果切分后的新系统熵值更小,更新参数
                    best_entropy, best_d, best_v = e, d, v
        return best_entropy, best_d, best_v 
    
try_split(X, y)
(1.0, 0, 2.45)
best_entropy, best_d, best_v = try_split(X_train, y_train)
print('最低系统信息熵为:', best_entropy)
print('最佳划分维度为:', best_d)
print('本维度最佳划分阈值为:', best_v)
最低系统信息熵为: 1.0
最佳划分维度为: 0
本维度最佳划分阈值为: 2.45
# 第一次划分后的结果
X1_l, X1_r, y1_l, y1_r = split(X_train, y_train, best_d, best_v)
X1_l.shape, X1_r.shape, y1_l.shape, y1_r.shape
((33, 2), (72, 2), (33,), (72,))
# 查看划分后的左侧标签,信息熵为0,说明完全划分完毕
entropy(y1_l), entropy(y1_r)  # 右侧熵不为0,有待继续划分
(0.0, 1.0)
# 继续划分右侧数据
best_entropy2, best_d2, best_v2 = try_split(X1_r, y1_r)
print('最低系统信息熵为:', best_entropy2)
print('最佳划分维度为:', best_d2)
print('本维度最佳划分阈值为:', best_v2)
最低系统信息熵为: 0.5898925289094291
最佳划分维度为: 0
本维度最佳划分阈值为: 4.75
# 第二次划分
X2_l, X2_r, y2_l, y2_r = split(X1_r, y1_r, best_d2, best_v2)
X2_l.shape, X2_r.shape, y2_l.shape, y2_r.shape
((34, 2), (38, 2), (34,), (38,))
entropy(y2_l), entropy(y2_r)
(0.19143325481419343, 0.3984592740952357)

系统分支熵值仍不为0,还可继续划分,略

刚才的运算构造的决策树为:

if X[0] <= 2.45:
    X_left_en = 0.0
    print('第一种鸢尾花,分类完毕')
else:
    if X[0] <= 4.75:
        left_en = 0.191
        print('第二种鸢尾花')
    else:
        right_en = 0.398
        print('第三种鸢尾花')

基尼系数

基尼系数公式: G = 1 − ∑ i = 1 k p i 2 G=1-\sum_{i=1}^{k} p_{i} 2 G=1i=1kpi2

例:三种鸢尾花,比例都为1/3,基尼系数为: G = 1 − ( 1 3 ) 2 − ( 1 3 ) 2 − ( 1 3 ) 2 = 0.6666 G=1-\left(\frac{1}{3}\right)^{2}-\left(\frac{1}{3}\right)^{2}-\left(\frac{1}{3}\right)^{2}=0.6666 G=1(31)2(31)2(31)2=0.6666

1 - (1/3) ** 2  - (1/3) ** 2  - (1/3) ** 2 
0.6666666666666665

例:三种花,比例为:1/10,2/10,7/10,基尼系数为:

1 - ((1/10)**2 + (2/10)**2 + (7/10)**2)
0.4600000000000001

例:三种花,比例为:1,0,0,基尼系数为:

1 - (1**2 + 0 + 0)
0

基尼系数图像

  • 设系统分两类,一类比例为x,则另一类比例为1-x
  • 代入信息熵公式: G = 1 − ( x 2 + ( 1 − x ) ∗ 2 ) = − 2 x 2 + 2 x G=1-\left(x^2+(1-x)* 2\right)=-2x^2+2x G=1(x2+(1x)2)=2x2+2x# 抛物线
def gini(x):
    return 1 - (x ** 2 + (1-x) ** 2)

x = np.linspace(0.01, 0.99, 200)  # 对数的底大于0且不等于1
x

plt.plot(x, gini(x))  # x轴为分类的比例,y轴为熵值
plt.xticks(np.arange(10)*0.1)
plt.grid(linewidth=0.2)
plt.show()

[外链图片转存失败(img-lOdBZ28k-1566998078187)(output_61_0.png)]

def en(x):
    return -x * np.log2(x) - (1-x) * np.log2(1-x)

def gini(x):
    return 1 - (x ** 2 + (1-x) ** 2)

x = np.linspace(0.01, 0.99, 200)  # 对数的底大于0且不等于1

plt.plot(x, en(x), color='r', alpha=0.8, label='en')  # x轴为分类的比例,y轴为熵值
plt.plot(x, gini(x)+0.5, color='g', alpha=0.8, label='gini')
plt.xticks(np.arange(10)*0.1)
plt.grid(linewidth=0.2)
plt.legend()
<matplotlib.legend.Legend at 0x531c748>

[外链图片转存失败(img-Yh21dqip-1566998078191)(output_62_1.png)]

基尼系数和信息熵比较

  • 信息熵计算比基尼系数更慢(对数运算)
  • sklearn默认基尼系数
  • 二者差异较小,大部分数据下优劣相当

决策树作为一个算法类别,有很多不同实现

  • ID3
    • 只能做分类,分支为二分或多分
    • 使用信息增益做分隔
  • C4.5
    • 只能做分类,分支为二分或多分
    • 使用信息增益比来分特征(惩罚了值类别特别多的特征)
  • C5.0
    • C4.5的性能提升版
  • CART
    • Classification And Regression Tree,分类和回归树
    • 能做分类也能做回归(可以用分类后值的概率作为回归结果)
    • 分支为二叉树
    • 使用基尼系数来分特征

sklearn内的决策树算法实现为CART算法的最优版本



决策树剪枝:对一些参数进行平衡

剪去枝干,让树变小,目的是:

  • 降低复杂度
  • 解决过拟合

过拟合和模型精确度是一对互相矛盾的指标:

  • 拟合程度越低,模型预测越不精确
  • 精确度越高,越容易过拟合
  • 二者需要取得平衡,在适度拟合的前提下,模型预测达到最精确

选择模型的标准:如果两个模型精度一样,效率也一样,选择更简单那个

  • 奥卡姆剃刀原理
  • 如无必要,勿增实体

科学思维:车库里的龙


import numpy as np
import matplotlib.pyplot as plt

# 生成非线性测试数据
from sklearn import datasets
X, y = datasets.make_moons(noise=0.25, random_state=666)  # 噪点参数0.25
X[:5]
array([[ 1.09760106,  0.85766164],
       [-0.01322635,  0.07455881],
       [-0.48210848,  0.86080586],
       [-0.91727991,  0.82195148],
       [-0.84571072,  0.61547083]])
y
array([1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
       1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1,
       1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0,
       0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0,
       0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0], dtype=int64)
plt.scatter(X[y==0, 0], X[y==0, 1])
plt.scatter(X[y==1, 0], X[y==1, 1])
<matplotlib.collections.PathCollection at 0x53864e0>

[外链图片转存失败(img-Lp7ACHtZ-1566998078196)(output_67_1.png)]

默认参数训练模型

from sklearn.tree import DecisionTreeClassifier

dt_clf = DecisionTreeClassifier()
dt_clf.fit(X, y)
DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=None,
                       max_features=None, max_leaf_nodes=None,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=2,
                       min_weight_fraction_leaf=0.0, presort=False,
                       random_state=None, splitter='best')
# 默认参数训练模型可视化
plot_decision_boundary(dt_clf, axis=[-1.5, 2.5, -1.0, 1.5])
# axis为横轴和众轴的范围

plt.scatter(X[y==0,0], X[y==0,1])
plt.scatter(X[y==1,0], X[y==1,1])

# 分支规则太细,过拟合明显
<matplotlib.collections.PathCollection at 0x5468128>

[外链图片转存失败(img-8LB320kK-1566998078208)(output_70_1.png)]

# 设分支最大深度为2(值越小越不容易过拟合)
# 分割两次,每次都限制最大和最小范围

dt_clf2 = DecisionTreeClassifier(max_depth=2)
dt_clf2.fit(X, y)

plot_decision_boundary(dt_clf2, axis=[-1.5, 2.5, -1.0, 1.5])
plt.scatter(X[y==0,0], X[y==0,1])
plt.scatter(X[y==1,0], X[y==1,1])
<matplotlib.collections.PathCollection at 0xfd38be0>

[外链图片转存失败(img-7iaWO4DJ-1566998078215)(output_71_1.png)]

# 节点至少有x个样本数据才会继续分支(值越高越不容易过拟合)

dt_clf3 = DecisionTreeClassifier(min_samples_split=10)
dt_clf3.fit(X, y)

plot_decision_boundary(dt_clf3, axis=[-1.5, 2.5, -1.0, 1.5])
plt.scatter(X[y==0,0], X[y==0,1])
plt.scatter(X[y==1,0], X[y==1,1])
<matplotlib.collections.PathCollection at 0xfbe3198>

[外链图片转存失败(img-wBIJ2scR-1566998078218)(output_72_1.png)]

# 叶子节点至少应该有x个样本数据(最终节点内最少样本数,越大越不容易过拟合)

dt_clf4 = DecisionTreeClassifier(min_samples_leaf=6)
dt_clf4.fit(X, y)

plot_decision_boundary(dt_clf4, axis=[-1.5, 2.5, -1.0, 1.5])
plt.scatter(X[y==0,0], X[y==0,1])
plt.scatter(X[y==1,0], X[y==1,1])
<matplotlib.collections.PathCollection at 0xfc56710>

[外链图片转存失败(img-KcplM0NA-1566998078223)(output_73_1.png)]

# 最多有多少个叶子节点(越少越不容易过拟合)

dt_clf5 = DecisionTreeClassifier(max_leaf_nodes=4)
dt_clf5.fit(X, y)

plot_decision_boundary(dt_clf5, axis=[-1.5, 2.5, -1.0, 1.5])
plt.scatter(X[y==0,0], X[y==0,1])
plt.scatter(X[y==1,0], X[y==1,1])
<matplotlib.collections.PathCollection at 0xfcc6c88>

[外链图片转存失败(img-B1ps3gwm-1566998078228)(output_74_1.png)]

# # 更多参数见文档
# help(DecisionTreeClassifier)

决策树解决回归问题

from sklearn import datasets
boston = datasets.load_boston()
X = boston.data
y = boston.target
# X
X.shape
(506, 13)
y[:10]
array([24. , 21.6, 34.7, 33.4, 36.2, 28.7, 22.9, 27.1, 16.5, 18.9])
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)
from sklearn.tree import DecisionTreeRegressor

dt_reg = DecisionTreeRegressor()
dt_reg.fit(X_train, y_train)  # 训练
DecisionTreeRegressor(criterion='mse', max_depth=None, max_features=None,
                      max_leaf_nodes=None, min_impurity_decrease=0.0,
                      min_impurity_split=None, min_samples_leaf=1,
                      min_samples_split=2, min_weight_fraction_leaf=0.0,
                      presort=False, random_state=None, splitter='best')
dt_reg.predict(X_test)  # 预测
array([27.1, 50. , 20.4, 34.9, 21.6, 21.1, 17.8, 20.5, 28.5, 16.7,  8.8,
       23.9, 22.9, 16.7, 22.9, 26.4, 32.5, 18.4, 14.1, 30.1, 23.3, 10.4,
       10.2, 48.3, 27.5, 15. , 21.2, 13.4, 36.1, 32.2, 43.1,  9.5, 31.5,
       17.8, 22.6, 22.4, 36. , 32.5, 23.1, 13.3, 24. , 20.6, 26.6, 16.8,
       24.1, 10.2, 30.1, 34.9, 24.4, 22.7, 24.6, 23.3, 50. , 16.8, 21.4,
       18.8, 21.7, 15.2, 14.1, 15.6, 20.9, 20.4, 28. , 14.9, 14.3, 10.4,
       22.4, 24. , 23.8, 20.3,  5.6, 31.5, 18.2, 14.9, 46. , 22.4,  5.6,
       23.8, 19. , 32.7, 28.4, 22. , 10.2, 30.1, 36.1, 19.4, 23.3, 24.3,
       19.7, 50. , 15.6, 34.6, 24.6, 32.5, 19.5, 13.3, 29.8, 23.3, 13.3,
       16.3, 17.8, 16.1, 24.8, 14.1, 17.7, 23.1, 16.1, 21.7, 22.6, 23.1,
       48.3, 20.5, 22.4,  5.6, 22.2, 24.4, 17.8, 13.3, 33.2, 16.8, 21.9,
       20.3, 25.1, 12.3, 10.4, 33.4, 12.7])
dt_reg.score(X_train, y_train)  # 训练集特征预测效果100%
1.0
dt_reg.score(X_test, y_test)  # 测试集特征预测过小(58%),说明模型过拟合比较严重,需要调参
0.5883659617972887
# help(DecisionTreeRegressor)


决策树的局限

  • 分类边界横平竖直,不能如实反映数据真实分隔情况
  • 对某些边界值很敏感,数据一点小改变会导致结果发生很大变化

决策树的优势:

  • 结果具有可解释性
  • 决策树模型之前的差异性大,模型多样化,最适合做集成学习

实际工作中,决策树一般不单独使用,而是和转为集成算法中的随机森林,更好的应用





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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值