Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >使用轮廓分数提升时间序列聚类的表现

使用轮廓分数提升时间序列聚类的表现

作者头像
deephub
发布于 2023-10-23 11:37:11
发布于 2023-10-23 11:37:11
48900
代码可运行
举报
文章被收录于专栏:DeepHub IMBADeepHub IMBA
运行总次数:0
代码可运行

我们将使用轮廓分数和一些距离指标来执行时间序列聚类实验,并且进行可视化

让我们看看下面的时间序列:

如果沿着y轴移动序列添加随机噪声,并随机化这些序列,那么它们几乎无法分辨,如下图所示-现在很难将时间序列列分组为簇:

上面的图表是使用以下脚本创建的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 # Import necessary libraries
 import os
 import pandas as pd
 import numpy as np
 
 # Import random module with an alias 'rand'
 import random as rand
 from scipy import signal
 
 # Import the matplotlib library for plotting
 import matplotlib.pyplot as plt
 
 # Generate an array 'x' ranging from 0 to 5*pi with a step of 0.1
 x = np.arange(0, 5*np.pi, 0.1)
 
 # Generate square, sawtooth, sin, and cos waves based on 'x'
 y_square = signal.square(np.pi * x)
 y_sawtooth = signal.sawtooth(np.pi * x)
 y_sin = np.sin(x)
 y_cos = np.cos(x)
 
 # Create a DataFrame 'df_waves' to store the waveforms
 df_waves = pd.DataFrame([x, y_sawtooth, y_square, y_sin, y_cos]).transpose()
 
 # Rename the columns of the DataFrame for clarity
 df_waves = df_waves.rename(columns={0: 'time',
                                     1: 'sawtooth',
                                     2: 'square',
                                     3: 'sin',
                                     4: 'cos'})
 
 # Plot the original waveforms against time
 df_waves.plot(x='time', legend=False)
 plt.show()
 
 # Add noise to the waveforms and plot them again
 for col in df_waves.columns:
     if col != 'time':
         for i in range(1, 10):
             # Add noise to each waveform based on 'i' and a random value
             df_waves['{}_{}'.format(col, i)] = df_waves[col].apply(lambda x: x + i + rand.random() * 0.25 * i)
 
 # Plot the waveforms with added noise against time
 df_waves.plot(x='time', legend=False)
 plt.show()

现在我们需要确定聚类的基础。这里有两种方法:

把接近于一组的波形分组——较低欧几里得距离的波形将聚在一起。

把看起来相似的波形分组——它们有相似的形状,但欧几里得距离可能不低

距离度量

一般来说,我们希望根据形状对时间序列进行分组,对于这样的聚类-可能希望使用距离度量,如相关性,这些度量或多或少与波形的线性移位无关。

让我们看看上面定义的带有噪声的波形对之间的欧几里得距离和相关性的热图:

可以看到欧几里得距离对波形进行分组是很困难的,因为任何一组波形对的模式都是相似的。例如,除了对角线元素外,square & cos之间的相关形状与square和square之间的相关形状非常相似

所有的形状都可以很容易地使用相关热图组合在一起——因为类似的波形具有非常高的相关性(sin-sin对),而像sin和cos这样的波形几乎没有相关性。

轮廓分数

通过上面热图和分析,根据高相关性分配组看起来是一个好主意,但是我们如何定义相关阈值呢?看起来像一个迭代过程,容易出现不准确和大量的人工工作。

在这种情况下,我们可以使用轮廓分数(Silhouette score),它为执行的聚类分配一个分数。我们的目标是使轮廓分数最大化。

轮廓分数(Silhouette Score)是一种用于评估聚类质量的指标,它可以帮助你确定数据点是否被正确地分配到它们的簇中。较高的轮廓分数表示簇内数据点相互之间更加相似,而不同簇之间的数据点差异更大,这通常是良好的聚类结果。

轮廓分数的计算方法如下:

  1. 对于每个数据点 i,计算以下两个值:
    • a(i):数据点 i 到同一簇中所有其他点的平均距离(簇内平均距离)。
    • b(i):数据点 i 到与其不同簇中的所有簇的平均距离,取最小值(最近簇的平均距离)。
  2. 然后,计算每个数据点的轮廓系数 s(i),它定义为:s(i) = \frac{b(i) - a(i)}{\max\{a(i), b(i)\}}
  3. 最后,计算整个数据集的轮廓分数,它是所有数据点的轮廓系数的平均值:\text{轮廓分数} = \frac{1}{N} \sum_{i=1}^{N} s(i)

其中,N 是数据点的总数。

轮廓分数的取值范围在 -1 到 1 之间,具体含义如下:

  • 轮廓分数接近1:表示簇内数据点相似度高,不同簇之间的差异很大,是一个好的聚类结果。
  • 轮廓分数接近0:表示数据点在簇内的相似度与簇间的差异相当,可能是重叠的聚类或者不明显的聚类。
  • 轮廓分数接近-1:表示数据点更适合分配到其他簇,不同簇之间的差异相比簇内差异更小,通常是一个糟糕的聚类结果。

一些重要的知识点:

在所有点上的高平均轮廓分数(接近1)表明簇的定义良好且明显。

低或负的平均轮廓分数(接近-1)表明重叠或形成不良的集群。

0左右的分数表示该点位于两个簇的边界上。

聚类

现在让我们尝试对时间序列进行分组。我们已经知道存在四种不同的波形,因此理想情况下应该有四个簇。

欧氏距离

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 pca = decomposition.PCA(n_components=2)
 pca.fit(df_man_dist_euc)
 df_fc_cleaned_reduced_euc = pd.DataFrame(pca.transform(df_man_dist_euc).transpose(), 
                                               index = ['PC_1','PC_2'],
                                               columns = df_man_dist_euc.transpose().columns)
 
 index = 0
 range_n_clusters = [2, 3, 4, 5, 6, 7, 8]
 
 # Iterate over different cluster numbers
 for n_clusters in range_n_clusters:
     # Create a subplot with silhouette plot and cluster visualization
     fig, (ax1, ax2) = plt.subplots(1, 2)
     fig.set_size_inches(15, 7)
 
     # Set the x and y axis limits for the silhouette plot
     ax1.set_xlim([-0.1, 1])
     ax1.set_ylim([0, len(df_man_dist_euc) + (n_clusters + 1) * 10])
 
     # Initialize the KMeans clusterer with n_clusters and random seed
     clusterer = KMeans(n_clusters=n_clusters, n_init="auto", random_state=10)
     cluster_labels = clusterer.fit_predict(df_man_dist_euc)
 
     # Calculate silhouette score for the current cluster configuration
     silhouette_avg = silhouette_score(df_man_dist_euc, cluster_labels)
     print("For n_clusters =", n_clusters, "The average silhouette_score is :", silhouette_avg)
     sil_score_results.loc[index, ['number_of_clusters', 'Euclidean']] = [n_clusters, silhouette_avg]
     index += 1
     
     # Calculate silhouette values for each sample
     sample_silhouette_values = silhouette_samples(df_man_dist_euc, cluster_labels)
     
     y_lower = 10
 
     # Plot the silhouette plot
     for i in range(n_clusters):
         # Aggregate silhouette scores for samples in the cluster and sort them
         ith_cluster_silhouette_values = sample_silhouette_values[cluster_labels == i]
         ith_cluster_silhouette_values.sort()
 
         # Set the y_upper value for the silhouette plot
         size_cluster_i = ith_cluster_silhouette_values.shape[0]
         y_upper = y_lower + size_cluster_i
 
         color = cm.nipy_spectral(float(i) / n_clusters)
 
         # Fill silhouette plot for the current cluster
         ax1.fill_betweenx(np.arange(y_lower, y_upper), 0, ith_cluster_silhouette_values, facecolor=color, edgecolor=color, alpha=0.7)
 
         # Label the silhouette plot with cluster numbers
         ax1.text(-0.05, y_lower + 0.5 * size_cluster_i, str(i))
         y_lower = y_upper + 10  # Update y_lower for the next plot
 
     # Set labels and title for the silhouette plot
     ax1.set_title("The silhouette plot for the various clusters.")
     ax1.set_xlabel("The silhouette coefficient values")
     ax1.set_ylabel("Cluster label")
 
     # Add vertical line for the average silhouette score
     ax1.axvline(x=silhouette_avg, color="red", linestyle="--")
     ax1.set_yticks([])  # Clear the yaxis labels / ticks
     ax1.set_xticks([-0.1, 0, 0.2, 0.4, 0.6, 0.8, 1])
 
     # Plot the actual clusters
     colors = cm.nipy_spectral(cluster_labels.astype(float) / n_clusters)
     ax2.scatter(df_fc_cleaned_reduced_euc.transpose().iloc[:, 0], df_fc_cleaned_reduced_euc.transpose().iloc[:, 1],
                 marker=".", s=30, lw=0, alpha=0.7, c=colors, edgecolor="k")
 
     # Label the clusters and cluster centers
     centers = clusterer.cluster_centers_
     ax2.scatter(centers[:, 0], centers[:, 1], marker="o", c="white", alpha=1, s=200, edgecolor="k")
 
     for i, c in enumerate(centers):
         ax2.scatter(c[0], c[1], marker="$%d$" % i, alpha=1, s=50, edgecolor="k")
 
     # Set labels and title for the cluster visualization
     ax2.set_title("The visualization of the clustered data.")
     ax2.set_xlabel("Feature space for the 1st feature")
     ax2.set_ylabel("Feature space for the 2nd feature")
 
     # Set the super title for the whole plot
     plt.suptitle("Silhouette analysis for KMeans clustering on sample data with n_clusters = %d" % n_clusters,
                  fontsize=14, fontweight="bold")
 
 plt.savefig('sil_score_eucl.png')
 plt.show()

可以看到无论分成多少簇,数据都是混合的,并不能为任何数量的簇提供良好的轮廓分数。这与我们基于欧几里得距离热图的初步评估的预期一致

相关性

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 pca = decomposition.PCA(n_components=2)
 pca.fit(df_man_dist_corr)
 df_fc_cleaned_reduced_corr = pd.DataFrame(pca.transform(df_man_dist_corr).transpose(), 
                                               index = ['PC_1','PC_2'],
                                               columns = df_man_dist_corr.transpose().columns)
 
 index=0
 range_n_clusters = [2,3,4,5,6,7,8]
 for n_clusters in range_n_clusters:
     # Create a subplot with 1 row and 2 columns
     fig, (ax1, ax2) = plt.subplots(1, 2)
     fig.set_size_inches(15, 7)
 
     # The 1st subplot is the silhouette plot
     # The silhouette coefficient can range from -1, 1 but in this example all
     # lie within [-0.1, 1]
     ax1.set_xlim([-0.1, 1])
     # The (n_clusters+1)*10 is for inserting blank space between silhouette
     # plots of individual clusters, to demarcate them clearly.
     ax1.set_ylim([0, len(df_man_dist_corr) + (n_clusters + 1) * 10])
 
     # Initialize the clusterer with n_clusters value and a random generator
     # seed of 10 for reproducibility.
     clusterer = KMeans(n_clusters=n_clusters, n_init="auto", random_state=10)
     cluster_labels = clusterer.fit_predict(df_man_dist_corr)
 
     # The silhouette_score gives the average value for all the samples.
     # This gives a perspective into the density and separation of the formed
     # clusters
     silhouette_avg = silhouette_score(df_man_dist_corr, cluster_labels)
     print(
         "For n_clusters =",
         n_clusters,
         "The average silhouette_score is :",
         silhouette_avg,
     )
     sil_score_results.loc[index,['number_of_clusters','corrlidean']] = [n_clusters,silhouette_avg]
     index=index+1
     
     sample_silhouette_values = silhouette_samples(df_man_dist_corr, cluster_labels)
     
     y_lower = 10
     for i in range(n_clusters):
         # Aggregate the silhouette scores for samples belonging to
         # cluster i, and sort them
         ith_cluster_silhouette_values = sample_silhouette_values[cluster_labels == i]
 
         ith_cluster_silhouette_values.sort()
 
         size_cluster_i = ith_cluster_silhouette_values.shape[0]
         y_upper = y_lower + size_cluster_i
 
         color = cm.nipy_spectral(float(i) / n_clusters)
         ax1.fill_betweenx(
             np.arange(y_lower, y_upper),
             0,
             ith_cluster_silhouette_values,
             facecolor=color,
             edgecolor=color,
             alpha=0.7,
         )
 
         # Label the silhouette plots with their cluster numbers at the middle
         ax1.text(-0.05, y_lower + 0.5 * size_cluster_i, str(i))
 
         # Compute the new y_lower for next plot
         y_lower = y_upper + 10  # 10 for the 0 samples
 
     ax1.set_title("The silhouette plot for the various clusters.")
     ax1.set_xlabel("The silhouette coefficient values")
     ax1.set_ylabel("Cluster label")
 
     # The vertical line for average silhouette score of all the values
     ax1.axvline(x=silhouette_avg, color="red", linestyle="--")
 
     ax1.set_yticks([])  # Clear the yaxis labels / ticks
     ax1.set_xticks([-0.1, 0, 0.2, 0.4, 0.6, 0.8, 1])
 
     # 2nd Plot showing the actual clusters formed
     colors = cm.nipy_spectral(cluster_labels.astype(float) / n_clusters)
     
     ax2.scatter(
         df_fc_cleaned_reduced_corr.transpose().iloc[:, 0], 
         df_fc_cleaned_reduced_corr.transpose().iloc[:, 1], marker=".", s=30, lw=0, alpha=0.7, c=colors, edgecolor="k"
     )
     
 #     for i in range(len(df_fc_cleaned_cleaned_reduced.transpose().iloc[:, 0])):
 #                         ax2.annotate(list(df_fc_cleaned_cleaned_reduced.transpose().index)[i], 
 #                                      (df_fc_cleaned_cleaned_reduced.transpose().iloc[:, 0][i], 
 #                                       df_fc_cleaned_cleaned_reduced.transpose().iloc[:, 1][i] + 0.2))
         
     # Labeling the clusters
     centers = clusterer.cluster_centers_
     # Draw white circles at cluster centers
     ax2.scatter(
         centers[:, 0],
         centers[:, 1],
         marker="o",
         c="white",
         alpha=1,
         s=200,
         edgecolor="k",
     )
 
     for i, c in enumerate(centers):
         ax2.scatter(c[0], c[1], marker="$%d$" % i, alpha=1, s=50, edgecolor="k")
 
     ax2.set_title("The visualization of the clustered data.")
     ax2.set_xlabel("Feature space for the 1st feature")
     ax2.set_ylabel("Feature space for the 2nd feature")
 
     plt.suptitle(
         "Silhouette analysis for KMeans clustering on sample data with n_clusters = %d"
         % n_clusters,
         fontsize=14,
         fontweight="bold",
     )
 
 plt.show()

当选择的簇数为4时,我们可以清楚地看到分离的簇,其他结果通常比欧氏距离要好得多。

欧几里得距离与相关廓形评分的比较

轮廓分数表明基于相关性的距离矩阵在簇数为4时效果最好,而在欧氏距离的情况下效果就不那么明显了结论

总结

在本文中,我们研究了如何使用欧几里得距离和相关度量执行时间序列聚类,并观察了这两种情况下的结果如何变化。如果我们在评估聚类时结合Silhouette,我们可以使聚类步骤更加客观,因为它提供了一种很好的直观方式来查看聚类的分离情况。

作者:Girish Dev Kumar Chaurasiya

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-10-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DeepHub IMBA 微信公众号,前往查看

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

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
独家 | 用LLM实现客户细分(上篇)
实践中可以采用多种方式处理客户细分项目,在本文中,将教会您诸多高端技术,不仅可以定义聚类,还可以分析结果。本文针对那些想要利用多种工具来解决聚类问题,以便更快成为高级数据科学家(DS)的读者。
数据派THU
2023/10/31
8360
独家 | 用LLM实现客户细分(上篇)
6个常用的聚类评价指标
评估聚类结果的有效性,即聚类评估或验证,对于聚类应用程序的成功至关重要。它可以确保聚类算法在数据中识别出有意义的聚类,还可以用来确定哪种聚类算法最适合特定的数据集和任务,并调优这些算法的超参数(例如k-means中的聚类数量,或DBSCAN中的密度参数)。
deephub
2023/11/24
1.8K0
6个常用的聚类评价指标
聚类-KMeans算法(图解算法原理)
k均值聚类算法(k-means clustering algorithm)是一种迭代求解的聚类分析算法,也就是将数据分成K个簇的算法,其中K是用户指定的。
唔仄lo咚锵
2022/10/04
3.4K0
聚类-KMeans算法(图解算法原理)
知识篇——聚类算法应用
时隔两月开始继续储备机器学习的知识,监督学习已经告一段落,非监督学习从聚类开始。 非监督学习与监督学习最大的区别在于目标变量事先不存在,也就是说 监督学习可以做到“对于输入数据X能预测变量Y”,而非监督学习能做到的是“从数据X中能发现什么?”,比如“构成X的最佳6个数据簇都是哪些?”或者“X中哪三个特征最频繁共现?” 这就很好玩了,比如我在Udacity的第三个项目,一家批发经销商想将发货方式从每周五次减少到每周三次,简称成本,但是造成一些客户的不满意,取消了提货,带来更大亏损,项目要求是通过分析
刘开心_1266679
2018/04/17
2K0
知识篇——聚类算法应用
一个企业级数据挖掘实战项目|客户细分模型(上)
客户细分模型是将整体会员划分为不同的细分群体或类别,然后基于细分群体做管理、营销和关怀。客户细分模型常用于整体会员的宏观性分析以及探索性分析,通过细分建立初步认知,为下一步的分析和应用提供基本认知。
数据STUDIO
2021/09/10
2.8K1
一个企业级数据挖掘实战项目|客户细分模型(上)
机器学习 | KMeans聚类分析详解
大量数据中具有"相似"特征的数据点或样本划分为一个类别。聚类分析提供了样本集在非监督模式下的类别划分。聚类的基本思想是"物以类聚、人以群分",将大量数据集中相似的数据样本区分出来,并发现不同类的特征。
数据STUDIO
2021/06/24
4.4K0
KMeans+降维,实现用户聚类!
基于Python Outlier Detection库进行异常值处理(Kmeans对异常值敏感)。
皮大大
2023/11/09
8880
基于k-means++和brich算法的文本聚类
分词和过滤停用词,这里分词有两步,第一步是对停用词进行分词,第二步是切分训练数据。
opprash
2019/09/09
2.5K0
4种聚类算法及可视化(Python)
在这篇文章中,基于20家公司的股票价格时间序列数据。根据股票价格之间的相关性,看一下对这些公司进行聚类的四种不同方式。
算法进阶
2023/08/28
1.2K0
4种聚类算法及可视化(Python)
机器学习(7)——聚类算法聚类算法
聚类算法 前面介绍的集中算法都是属于有监督机器学习方法,这章和前面不同,介绍无监督学习算法,也就是聚类算法。在无监督学习中,目标属性是不存在的,也就是所说的不存在“y”值,我们是根据内部存在的数据特征,划分不同的类别,使得类别内的数据比较相似。 我们对数据进行聚类的思想不同可以设计不同的聚类算法,本章主要谈论三种聚类思想以及该聚类思想下的三种聚类算法。666 本章主要涉及到的知识点有: “距离” K-Means算法 几种优化K-Means算法 密度聚类 算法思想:“物以类聚,人以群分” 本节首先通过聚类算法
DC童生
2018/04/27
3.8K0
机器学习(7)——聚类算法聚类算法
实战干货|Python数据分析消费者用户画像
基于Python Outlier Detection库进行异常值处理(Kmeans对异常值敏感)。
用户6888863
2023/11/30
1.3K0
实战干货|Python数据分析消费者用户画像
K_means算法案例分析
得出当聚类中心数量为3的时候,轮廓系数最大;此时,也可以观察到聚类中心数量为3也符合数据的分布特点,的确是相对较为合理的类簇数量。
用户3577892
2020/06/11
8950
分群思维(四)基于KMeans聚类的广告效果分析
从雷达图我们发现四个各类别在6个数字特征上具有较大差异,0类更偏向访问深度,3类更偏向日均uv,2和3类在多个指标表现都较好,但也存在差异。
HsuHeinrich
2023/03/29
7370
分群思维(四)基于KMeans聚类的广告效果分析
[Python从零到壹] 十三.机器学习之聚类算法四万字总结(K-Means、BIRCH、树状聚类、MeanShift)
在过去,科学家会根据物种的形状习性规律等特征将其划分为不同类型的门类,比如将人种划分为黄种人、白种人和黑种人,这就是简单的人工聚类方法。聚类是将数据集中某些方面相似的数据成员划分在一起,给定简单的规则,对数据集进行分堆,是一种无监督学习。聚类集合中,处于相同聚类中的数据彼此是相似的,处于不同聚类中的元素彼此是不同的。本章主要介绍聚类概念和常用聚类算法,然后详细讲述Scikit-Learn机器学习包中聚类算法的用法,并通过K-Means聚类、Birch层次聚类及PAC降维三个实例加深读者印象。
Eastmount
2021/12/02
2.2K0
[Python从零到壹] 十三.机器学习之聚类算法四万字总结(K-Means、BIRCH、树状聚类、MeanShift)
机器学习之鸢尾花-聚类
将物理或抽象对象的集合分成由类似的对象组成的多个类的过程被称为聚类。由聚类所生成的簇是一组数据对象的集合,这些对象与同一个簇中的对象彼此相似,与其他簇中的对象相异。“物以类聚,人以群分”,在自然科学和社会科学中,存在着大量的分类问题。聚类分析又称群分析,它是研究(样品或指标)分类问题的一种统计分析方法。聚类分析起源于分类学,但是聚类不等于分类。聚类与分类的不同在于,聚类所要求划分的类是未知的。聚类分析内容非常丰富,有系统聚类法、有序样品聚类法、动态聚类法、模糊聚类法、图论聚类法、聚类预报法等。
python与大数据分析
2022/03/11
7370
机器学习之鸢尾花-聚类
Optimizing the number of centroids最优化形心数量
Centroids are difficult to interpret, and it can also be very difficult to determine whether we have the correct number of centroids. It's important to understand whether your data is unlabeled or not as this will directly influence the evaluation measures we can use.
到不了的都叫做远方
2019/11/21
5260
k均值聚类
n_digits: 10, n_samples 1797, n_features 64 _______________________________________________________________________________ init time inertiahomo compl v-meas ARI AMI silhouette k-means++ 0.43s 69684 0.683 0.722 0.702 0.573 0.699 0.154 random 0.30s 69656 0.673 0.713 0.692 0.558 0.689 0.120 PCA-based 0.05s 70793 0.667 0.695 0.681 0.553 0.677 0.156 _______________________________________________________________________________
裴来凡
2022/05/29
5340
k均值聚类
RDKit | 基于PCA探索化学空间
该库包含超过10 000 000个SMILES。可以将.smiles文件作为文本文件读取,将10000个分子保存在pandas中。
DrugAI
2021/01/29
1.3K0
RDKit | 基于PCA探索化学空间
特征工程系列之非线性特征提取和模型堆叠
当在数据一个线性子空间像扁平饼时 PCA 是非常有用的。但是如果数据形成更复杂的形状呢?一个平面(线性子空间)可以推广到一个 流形 (非线性子空间),它可以被认为是一个被各种拉伸和滚动的表面。
墨明棋妙27
2022/09/23
1.4K0
【机器学习】第四部分:聚类问题
聚类(cluster)与分类(class)问题不同,聚类是属于无监督学习模型,而分类属于有监督学习。聚类使用一些算法把样本分为N个群落,群落内部相似度较高,群落之间相似度较低。在机器学习中,通常采用“距离”来度量样本间的相似度,距离越小,相似度越高;距离越大,相似度越低.
杨丝儿
2022/02/18
1.5K0
【机器学习】第四部分:聚类问题
相关推荐
独家 | 用LLM实现客户细分(上篇)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验