Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >scikit-learn工具包中分类模型predict_proba、predict、decision_function用法详解「建议收藏」

scikit-learn工具包中分类模型predict_proba、predict、decision_function用法详解「建议收藏」

作者头像
全栈程序员站长
发布于 2022-11-08 07:29:16
发布于 2022-11-08 07:29:16
2.6K02
代码可运行
举报
运行总次数:2
代码可运行

在使用sklearn训练完分类模型后,下一步就是要验证一下模型的预测结果,对于分类模型,sklearn中通常提供了predict_proba、predict、decision_function三种方法来展示模型对于输入样本的评判结果。

说明一下,在sklearn中,对于训练好的分类模型,模型都有一个classes_属性,classes_属性中按顺序保存着训练样本的类别标记。下面是使用Logistic Regression分类器在为例,展示一下分类器的classes_属性。

1、先看一下样本标签从0开始的场景下训练分类模型

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import numpy as np
from sklearn.linear_model import LogisticRegression

x = np.array(
    [
        [-1, -1],
        [-2, -1],
        [1, 1],
        [2, 1],
        [-1, 1],
        [-1, 2],
        [1, -1],
        [1, -2]
    ]
)
y = np.array([2, 2, 3, 3, 0, 0, 1, 1])

clf = LogisticRegression()
clf.fit(x, y)
print(clf.classes_)

"""
    输出结果:[0 1 2 3]
"""

2、下面看一下样本标签不是从0开始的场景下训练分类模型

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import numpy as np
from sklearn.linear_model import LogisticRegression

x = np.array(
    [
        [-1, -1],
        [-2, -1],
        [1, 1],
        [2, 1],
        [-1, 1],
        [-1, 2],
        [1, -1],
        [1, -2]
    ]
)
y = np.array([6, 6, 2, 2, 4, 4, 8, 8])

clf = LogisticRegression()
clf.fit(x, y)
print(clf.classes_)

"""
    输出结果:[2 4 6 8]
"""

注意观察上述两种情况下classes_属性的输出结果,该输出结果的顺序就对应后续要说predict_proba、predict、decision_function输出结果的顺序或顺序组合。


在了解了分类模型classes_的标签顺序之后,下面看一下分类模型predict_proba、predict、decision_function三种函数输出结果的含义,以及他们之间的相关性。

1、predict_proba: 模型预测输入样本属于每种类别的概率,概率和为1,每个位置的概率分别对应classes_中对应位置的类别标签。以上述类别标签为[2 4 6 8]的那个分类器为例,查看一下分类模型预测的概率。

输入的[-1, -1]刚好是训练分类器时使用的数据,训练数据中[-1, -1]属于类别6,在predict_proba输出概率中,最大概率值出现在第三个位置上,第三个位置对应的classes_类别刚好也是类别6。这也就是说,predict_proba输出概率最大值索引位置对应的classes_元素就是样本所属的类别。下面就来看一下predict的预测结果与predict_proba的预测结果是否一致。

2、predict: 模型预测输入样本所属的类别,是则输出1,不是则输出0。

在上一步中知道了predict_proba是输出样本属于各个类别的概率,且取概率最大的类别作为样本的预测结果,下面看一下predict的预测结果与predict_proba的最大值是否一致。

predict的预测结果为类别6,对应于classes_中的第三个元素,也同时对应于predict_proba中的第三个元素,且是概率值最大的元素。

对于分类模型来说,通常知道模型的预测结果predict和预测概率predict_proba就可以了,那分类模型中的decision_function是干什么的呢?

3、decision_function: 帮助文档中给出的解释是“The confidence score for a sample is the signed distance of that sample to the hyperplane.”。意思就是使用样本到分隔超平面的有符号距离来度量预测结果的置信度,反正我是有点懵逼。放大招,灵魂三问。他是谁?他从哪里来?他到哪里去?

他是谁?

看一下支持向量机SVM中关于decision_function的解释是怎样的?

说了两件事情,其一是说评估样本X的的decision_function(等于没说,哈哈哈),其二是说,如果decision_dunction_shape=’ovr’,则输出的decison_function形状是(n_samples, n_classes), n_samples是输入样本的数量,n_classes是训练样本的类别数。这里再补充一点,如果decision_dunction_shape=’ovo,则输出的decison_function形状是(n_samples, n_classes * (n_classes – 1) / 2)。‘ovr’和‘ovo’又是啥?莫急,莫急。暂且知道是用于训练多分类的就行。

大致解释下decison_function就是用来衡量待预测样本到分类模型各个分隔超平面的距离(没找到太直观的解释方法)。

他从哪里来?

据说这家伙来自遥远的SVM星球。上面说这哥们能和分隔超平面扯上关系,熟悉SVM的会知道,SVM中通过支持向量来选择分隔超平面,分隔超平面将训练样本分为正反两派,支持向量的作用就是使得选择的分隔超平面离两边的类别都比较远,这样模型具有更强的健壮性。

他到哪里去?

说了半天,decison_function这玩意到底有啥用?莫急,莫急。下面先说一下上面提到的’ovr’和’ovo’分别是什么东东?

我们常见的分类器,比如LR和SVM都是只能支持二分类的,回想一下LR分类器,通过判断线性模型的预测结果是否大于0,进而判断sigmoid的输出结果是否大于0.5来判断模型属于正类还是负类。SVM也一样,前面讲了,SVM通过分隔超平面将样本分到两边去,也就是进行二分类。那么怎么能将二分类的分类算法应用到多分类任务上去呢?这就是‘ovr’和‘ovo’要解决的问题。

‘ovr’:全称是One-vs-Rest。就是一个人和对面一群人干一次架(群殴)。假如我们训练数据中包含[0, 1, 2, 3]四个分类,那么分别将0, 1, 2, 3作为正样本,其余的123, 023, 013, 012作为负样本,训练4个分类器,每个分类器预测的结果表示属于对应正类也就是0, 1, 2, 3 的概率。这样对于一个输入样本就相当于要进行4个二分类,然后取输出结果最大的数值对应的classes_类别。

‘ovo’:全称是One-vs-One。就是一个人分别和对面的每个人干一次架(单挑,车轮战术)。同样,假如我们训练数据中包含[0, 1, 2, 3]四个分类,先将类别0作为正样本,类别1,类别2,类别3依次作为负样本训练3个分类器,然后以类别1为正样本,类别0,类别2, 类别3作为负样本训练3个分类器,以此类推。由于类别0为正样本,类别1为负样本和类别1为正样本、类别0为负样本实质上是一样的,所以不需要重复训练。

通过上面的描述可知,假如训练样本有n_classes个类别,则’ovr’模式需要训练n_classes个分类器,‘ovo’模式需要训练n_classes * (n_classes – 1) / 2 个分类器。那么问题来了,有多少个分类器是不是就得有多少个分隔超平面,有多少个分隔超平面是不是就得有多少个decision_function值。这也就对应了“他是谁?”那部分所说的decison_function输出形状的描述。

下面进入正题,来看一下decision_function的真面目。

1、二分类的decison_function

二分类模型中,decision_function返回的数组形状等于样本个数,也就是一个样本返回一个decision_function值。并且,此时的decision_function_shape参数失效 ,因为只需要训练一个分类器就行了,就不存在是单挑还是群殴的问题了。下面以SVM二分类的实例来看一下结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import numpy as np
from sklearn.svm import SVC

x = np.array([[1,2,3],
                    [1,3,4],
                    [2,1,2],
                    [4,5,6],
                    [3,5,3],
                    [1,7,2]])
 
y = np.array([3, 3, 3, 2, 2, 2])

clf = SVC(probability=True)
clf.fit(x, y)
print(clf.decision_function(x))

# 返回array([2, 3]),其中2为negetive,3为positive
print(clf.classes_)

在二分类的情况下,分类模型的decision_function返回结果的形状与样本数量相同,且返回结果的数值表示模型预测样本属于positive正样本的可信度。并且,二分类情况下classes_中的第一个标签代表是负样本,第二个标签代表正样本。

模型在训练集上的decision_function以及predict_procaba、predict结果如下:

还记得前面讲过的decision_function是有符号的吧,大于0表示正样本的可信度大于负样本,否则可信度小于负样本。所以对于前3个样本,decison_function都认为是正样本的可信度高,后3个样本是负样本的可信度高。那么再看一下predict的结果,前3个预测为正样本3(ps:二分类情况下正样本对应的是classes_中的第二个类别),后3个样本预测为负样本2。再看一下predict_proba预测的样本所属的类别概率,可以看到前3个样本属于类别3的概率更大,后3个样本属于类别2的概率更大。

2、多分类的decision_function

多分类模型中,decision_function返回的数组形状依据使用的模式是‘ovr’还是‘ovo’而分别返回n_classes个和n_classes * (n_classes – 1) / 2个数值。下面以SVM多分类的实例来看一下结果:

One-vs-Rest多分类实例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import matplotlib.pyplot as plt
import numpy as np
from sklearn.svm import SVC
X = np.array(
    [
        [-1, -1],
        [-2, -1],
        [1, 1],
        [2, 1],
        [-1, 1],
        [-1, 2],
        [1, -1],
        [1, -2]
    ]
)
y = np.array([2, 2, 3, 3, 0, 0, 1, 1])
# SVC多分类模型默认采用ovr模式
clf = SVC(probability=True, decision_function_shape="ovr")
clf.fit(X, y)

# 计算样本距离每个分类边界的距离
# One-vs-One 按照decision_function的得分[01, 02, 03, 12, 13, 23]判断每个分类器的分类结果,然后进行投票
# One-vs-Rest 选择decision_function的得分[0-Rest, 1-Rest, 2-Rest, 3-Rest]最大的作为分类结果
print("decision_function:\n", clf.decision_function(X))
# precidt预测样本对应的标签类别
print("predict:\n", clf.predict(X))
# predict_proba 预测样本对应各个类别的概率
print("predict_proba:\n", clf.predict_proba(X)) #这个是得分,每个分类器的得分,取最大得分对应的类。
print("classes_:", clf.classes_)

模型在训练集上的decision_function以及predict_procaba、predict结果如下:

在ovr场景下,decision_function输出的最大值对应的正样本类别就是decision_function认为置信度最高的预测类别。下面看一下One-vs-One场景下的多分类。

One-vs-One多分类实例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import matplotlib.pyplot as plt
import numpy as np
from sklearn.svm import SVC
X = np.array(
    [
        [-1, -1],
        [-2, -1],
        [1, 1],
        [2, 1],
        [-1, 1],
        [-1, 2],
        [1, -1],
        [1, -2]
    ]
)
y = np.array([2, 2, 3, 3, 0, 0, 1, 1])
# SVC多分类模型默认采用ovr模式
clf = SVC(probability=True, decision_function_shape="ovo")
clf.fit(X, y)

# 计算样本距离每个分类边界的距离
# One-vs-One 按照decision_function的得分[01, 02, 03, 12, 13, 23]判断每个分类器的分类结果,然后进行投票
# One-vs-Rest 选择decision_function的得分[0-Rest, 1-Rest, 2-Rest, 3-Rest]最大的作为分类结果
print("decision_function:\n", clf.decision_function(X))
# precidt预测样本对应的标签类别
print("predict:\n", clf.predict(X))
# predict_proba 预测样本对应各个类别的概率
print("predict_proba:\n", clf.predict_proba(X)) #这个是得分,每个分类器的得分,取最大得分对应的类。
print("classes_:", clf.classes_)

模型在训练集上的decision_function以及predict_procaba、predict结果如下:

ovo模式下,4个类别的训练数据,需要训练6个二分类器,得到6个decition_function值,依照classes_的类别顺序,6个二分类器分别是[01, 02, 03, 12, 13, 23],前面的数字表示正类,后面的表示负类。以decision_function的第一行输出结果为例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
-0.07609727 对应 01分类器,且数值小于0,则分类结果为后者,即类别1
-1.00023294  对应 02分类器,且数值小于0,则分类结果为后者,即类别2
0.27849207  对应 03分类器,且数值大于0,则分类结果为前者,即类别0
-0.834258626  对应 12分类器,且数值小于0,则分类结果为后者,即类别2
0.24756982  对应 13分类器,且数值大于0,则分类结果为前者,即类别1
1.00006256 对应 23分类器,且数值大于0,则分类结果为前者,即类别2

最终得票数:{类别0: 1, 类别1: 2, 类别2: 3, 类别3: 0}
对以上分类结果voting投票,多数获胜,即最终分类结果为类别2

通过上面讲的这些大概也能得出decision_function、predict_procaba、predict之间的联系了:

decision_function:输出样本距离各个分类器的分隔超平面的置信度,并由此可以推算出predict的预测结果

predict_procaba:输出样本属于各个类别的概率值,并由此可以推算出predict的预测结果

predict:输出样本属于具体类别的预测结果

怎么用?

说了这么多,也知道decision_function的具体含义了,那么使用decison_function可以干什么呢?(没用说个毛线)

还是以SVM分类器为例,SVM分类器有个参数用来控制是否输出预测样本的概率值,probability=True时SVM分类器具有predict_proba函数,可以输出样本的预测概率,但是当probability=False,SVM分类器没有predict_proba函数,也就没办法得到样本预测结果的置信度(简单理解为概率)。但是我们又知道,当我们想要计算分类器的性能时,常常需要用到ROC和AUC,ROC曲线表示分类器预测结果FPR和TPR的变化趋势,AUC表示ROC曲线以下的面积。也就是说,要想得到ROC和AUC,就需要得到一组FPR和TPR,FPR和TPR的计算通常是基于一组样本的预测置信度,分别选择不同的置信度阈值,得到一组FPR和TPR值,然后得到ROC曲线的。现在没有predict_proba就得不到样本预测的置信度。But,还记得我们前面解释decison_function时说过的,decision_function表示通过度量样本距离分隔超平面距离的来表示置信度。那么我们是不是可以使用decision_function的置信度来计算ROC呢?答案当然是可以的啦。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
from sklearn.svm import SVC
from sklearn.metrics import roc_curve, roc_auc_score, auc, plot_roc_curve
from sklearn.multiclass import OneVsOneClassifier, OneVsRestClassifier
from sklearn.preprocessing import label_binarize
from sklearn import datasets
from sklearn.model_selection import train_test_split
np.random.seed(100)

# 加载iris数据集
iris = datasets.load_iris()
X = iris.data
y = iris.target
print(X.shape, y.shape)
n_samples, n_features = X.shape

# iris数据集加入噪声,使得ROC不是那么完美
X = np.c_[X, np.random.randn(n_samples, 50 * n_features)]
# y = label_binarize(y, classes=[0, 1, 2])
# n_classes = y.shape[1]
# 训练样本的类别数量
n_classes = 3

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.5, random_state=0)
print(X_train.shape, y_train.shape)
print(X_test.shape, y_test.shape)

# 使用One-vs-Rest模式训练SVM分类器
clf = OneVsRestClassifier(SVC(kernel="linear"))
clf.fit(X_train, y_train)

# 计算分类器在测试集上的决策值
y_scores = clf.decision_function(X_test)
print(y_scores.shape)

# 绘制每个类别的ROC曲线
fig, axes = plt.subplots(2, 2, figsize=(8, 8))
colors = ["r", "g", "b", "k"]
markers = ["o", "^", "v", "+"]

y_test = label_binarize(y_test, classes=clf.classes_)
for i in range(n_classes):
    # 计算每个类别的FPR, TPR 
    fpr, tpr, thr = roc_curve(y_test[:, i], y_scores[:, i])
#     print("classes_{}, fpr: {}, tpr: {}, threshold: {}".format(i, fpr, tpr, thr))
    # 绘制ROC曲线,并计算AUC值
    axes[int(i / 2), i % 2].plot(fpr, tpr, color=colors[i], marker=markers[i], label="AUC: {:.2f}".format(auc(fpr, tpr)))
    axes[int(i / 2), i % 2].set_xlabel("FPR")
    axes[int(i / 2), i % 2].set_ylabel("TPR")
    axes[int(i / 2), i % 2].set_title("Class_{}".format(clf.classes_[i]))
    axes[int(i / 2), i % 2].legend(loc="lower right")

print("AUC:", roc_auc_score(y_test, clf.decision_function(X_test), multi_class="ovr", average=None))

输出结果如下:AUC: [0.99470899 0.5962963 0.8619281 ]

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/185418.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年10月6日 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
《Scikit-Learn与TensorFlow机器学习实用指南》 第3章 分类
第3章 分类 来源:ApacheCN《Sklearn 与 TensorFlow 机器学习实用指南》翻译项目 译者:@时间魔术师 校对:@Lisanaaa @飞龙 在第一章我们提到过最常用的监督学习任务是回归(用于预测某个值)和分类(预测某个类别)。在第二章我们探索了一个回归任务:预测房价。我们使用了多种算法,诸如线性回归,决策树,和随机森林(这个将会在后面的章节更详细地讨论)。现在我们将我们的注意力转到分类任务上。 MNIST 在本章当中,我们将会使用 MNIST 这个数据集,它有着 70000
ApacheCN_飞龙
2018/05/16
1.8K0
python实现多分类评价指标
参考:https://www.jianshu.com/p/9332fcfbd197
西西嘛呦
2020/09/22
4K0
python实现多分类评价指标
使用Scikit-learn实现分类(MNIST)
这是我学习hands on ml with sklearn and tf 这本书做的笔记,这是第三章
用户7886150
2020/12/27
1.6K0
[Hands On ML] 3. 分类(MNIST手写数字预测)
MNIST 数据集已经事先被分成了一个训练集(前 60000 张图片)和一个测试集(最后 10000 张图片)
Michael阿明
2020/07/13
1.4K0
强大的 Scikit-learn 可视化让模型说话
使用 utils.discovery.all_displays 查找可用的 API。
数据STUDIO
2024/05/10
2390
强大的 Scikit-learn 可视化让模型说话
一文掌握sklearn中的支持向量机
前面两节已经介绍了线性SVC与非线性SVC的分类原理。本节将在理论的基础上,简单介绍下sklearn中的支持向量机是如何实现数据分类的。并参照理论中的概念对应介绍重要参数的含义,以及如何调节参数,使得模型在数据集中得到更高的分数。
数据STUDIO
2021/06/24
2K0
机器学习集成学习与模型融合!
对比过kaggle比赛上面的top10的模型,除了深度学习以外的模型基本上都是集成学习的产物。集成学习可谓是上分大杀器,今天就跟大家分享在Kaggle或者阿里天池上面大杀四方的数据科学比赛利器---集成学习。
Datawhale
2020/08/20
1.1K0
机器学习集成学习与模型融合!
牛逼了!Scikit-learn 0.22新版本发布,新功能更加方便
作者:xiaoyu,数据爱好者 Scikit-learn此次发布的版本为0.22。我浏览了一下,此次版本除了修复之前出现的一些bug,还更新了很多新功能,不得不说更加好用了。下面我把我了解到主要的几个最新功能和大家分享一下。
Python数据科学
2019/12/18
1.4K0
概率类模型评估指标,你知道几个?
混淆矩阵和精确性可以帮助我们了解概率类模型的分类结果。然而,我们选择概率类模型进行分类,大多数时候都不是为了单单追求效果,而是希望看到预测的相关概率。这种概率给出预测的可信度,所以对于概率类模型,我们希望能够由其他的模型评估指标来帮助我们判断,模型在"概率预测"这项工作上,完成得如何。本文介绍概率类模型独有的评估指标。本文字数8216,建议收藏。
数据STUDIO
2021/06/24
2.6K0
scikit-learn代码实现SVM分类与SVR回归以及调参
下面部分引用自https://blog.csdn.net/HHTNAN/article/details/79500003
全栈程序员站长
2022/11/17
2.1K0
scikit-learn代码实现SVM分类与SVR回归以及调参
机器学习13:多分类学习
有些情况下,二分类学习方法可以推广到多分类问题中;但是多数情况下需要基于一定的策略,利用二分类学习器解决多分类问题。
用户5473628
2019/08/08
6.3K0
深度学习实战-MNIST数据集的二分类
MNIST数据集是一组由美国高中生和人口调查局员工手写的70,000个数字的图片,每张图片上面有代表的数字标记。
皮大大
2023/08/25
8500
15分钟带你入门sklearn与机器学习——分类算法篇
【导读】众所周知,Scikit-learn(以前称为scikits.learn)是一个用于Python编程语言的免费软件机器学习库。它具有各种分类,回归和聚类算法,包括支持向量机,随机森林,梯度增强,k-means和DBSCAN,旨在与Python数值和科学库NumPy和SciPy互操作。本文将带你入门常见的机器学习分类算法——逻辑回归、朴素贝叶斯、KNN、SVM、决策树。
Python数据科学
2019/03/18
1.4K0
Scikit-learn新版本发布,一行代码秒升级
对于创建可视化任务,scikit-learn 推出了一个全新 plotting API。
量子位
2019/12/10
7250
Sklearn、TensorFlow 与 Keras 机器学习实用指南第三版(二)
在第一章中,我提到最常见的监督学习任务是回归(预测值)和分类(预测类)。在第二章中,我们探讨了一个回归任务,使用各种算法(如线性回归、决策树和随机森林)来预测房屋价值(这将在后面的章节中进一步详细解释)。现在我们将把注意力转向分类系统。
ApacheCN_飞龙
2024/05/24
3430
Sklearn、TensorFlow 与 Keras 机器学习实用指南第三版(二)
Python数据处理从零开始----第四章(可视化)(11)多分类ROC曲线目录
===============================================
用户1359560
2018/12/18
1.8K0
scikit-plot可视化模型
scikit-learn (sklearn)是Python环境下常见的机器学习库,包含了常见的分类、回归和聚类算法。在训练模型之后,常见的操作是对模型进行可视化,则需要使用Matplotlib进行展示。
用户8949263
2023/08/22
2670
scikit-plot可视化模型
快速入门Python机器学习(36)
GridSearchCV实现了"fit"和" score"方法。它还实现了"得分样本" "预测" "预测概率" "决策函数" "变换"和"逆变换" ,如果它们在所使用的估计器中实现的话。应用这些方法的估计器的参数通过参数网格上的交叉验证网格搜索进行优化。
顾翔
2022/09/23
6000
快速入门Python机器学习(36)
【机器学习】--模型评估指标之混淆矩阵,ROC曲线和AUC面积
实际上非常简单,精确率是针对我们预测结果而言的,它表示的是预测为正的样本中有多少是真正的正样本。那么预测为正就有两种可能了,一种就是把正类预测为正类(TP),另一种就是把负类预测为正类(FP),也就是
LhWorld哥陪你聊算法
2018/09/13
2.1K0
【机器学习】--模型评估指标之混淆矩阵,ROC曲线和AUC面积
机器学习-AUC-ROC-python
ROC(receiver operating characteristic curve):简称接收者操作特征曲线,是由二战中的电子工程师和雷达工程师发明的,主要用于检测此种方法的准确率有多高。图示:
DrugScience
2021/02/04
7050
机器学习-AUC-ROC-python
相关推荐
《Scikit-Learn与TensorFlow机器学习实用指南》 第3章 分类
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验