本文是吴恩达老师的机器学习课程[1]的笔记和代码复现部分(聚类、降维)。

作者:黄海广[2]

备注:笔记和作业(含数据、原始作业文件)、视频都在github[3]中下载。

我将陆续将课程笔记和课程代码发布在公众号“机器学习初学者”,敬请关注。这个是第四部分:无监督学习,是原教程的第8周,包含了笔记和作业代码(原课程作业是 OCTAVE的,这里是复现 python 代码)

第一部分:回归

第二部分:逻辑回归

第三部分:支持向量机

本文作业代码[4]可以下载完整版

笔记的markdown 文件[5]

笔记的pdf 文件[6]

笔记部分目录

十三、聚类(Clustering)

13.1 无监督学习:简介

参考视频: 13 - 1 - Unsupervised Learning_ Introduction (3 min).mkv

在这个视频中,我将开始介绍聚类算法。这将是一个激动人心的时刻,因为这是我们学习的第一个非监督学习算法。我们将要让计算机学习无标签数据,而不是此前的标签数据。

那么,什么是非监督学习呢?在课程的一开始,我曾简单的介绍过非监督学习,然而,我们还是有必要将其与监督学习做一下比较。

在一个典型的监督学习中,我们有一个有标签的训练集,我们的目标是找到能够区分正样本和负样本的决策边界,在这里的监督学习中,我们有一系列标签,我们需要据此拟合一个假设函数。与此不同的是,在非监督学习中,我们的数据没有附带任何标签,我们拿到的数据就是这样的:

在这里我们有一系列点,却没有标签。因此,我们的训练集可以写成只有











,











…..一直到











。我们没有任何标签




。因此,图上画的这些点没有标签信息。也就是说,在非监督学习中,我们需要将一系列无标签的训练数据,输入到一个算法中,然后我们告诉这个算法,快去为我们找找这个数据的内在结构给定数据。我们可能需要某种算法帮助我们寻找一种结构。图上的数据看起来可以分成两个分开的点集(称为簇),一个能够找到我圈出的这些点集的算法,就被称为聚类算法。

这将是我们介绍的第一个非监督学习算法。当然,此后我们还将提到其他类型的非监督学习算法,它们可以为我们找到其他类型的结构或者其他的一些模式,而不只是簇。

我们将先介绍聚类算法。此后,我们将陆续介绍其他算法。那么聚类算法一般用来做什么呢?

在这门课程的早些时候,我曾经列举过一些应用:比如市场分割。也许你在数据库中存储了许多客户的信息,而你希望将他们分成不同的客户群,这样你可以对不同类型的客户分别销售产品或者分别提供更适合的服务。社交网络分析:事实上有许多研究人员正在研究这样一些内容,他们关注一群人,关注社交网络,例如FacebookGoogle+,或者是其他的一些信息,比如说:你经常跟哪些人联系,而这些人又经常给哪些人发邮件,由此找到关系密切的人群。因此,这可能需要另一个聚类算法,你希望用它发现社交网络中关系密切的朋友。我有一个朋友正在研究这个问题,他希望使用聚类算法来更好的组织计算机集群,或者更好的管理数据中心。因为如果你知道数据中心中,那些计算机经常协作工作。那么,你可以重新分配资源,重新布局网络。由此优化数据中心,优化数据通信。

最后,我实际上还在研究如何利用聚类算法了解星系的形成。然后用这个知识,了解一些天文学上的细节问题。好的,这就是聚类算法。这将是我们介绍的第一个非监督学习算法。在下一个视频中,我们将开始介绍一个具体的聚类算法。

13.2 K-均值算法

参考视频: 13 - 2 - K-Means Algorithm (13 min).mkv

K-均值是最普及的聚类算法,算法接受一个未标记的数据集,然后将数据聚类成不同的组。

K-均值是一个迭代算法,假设我们想要将数据聚类成 n 个组,其方法为:

首先选择




个随机的点,称为聚类中心cluster centroids);

对于数据集中的每一个数据,按照距离




个中心点的距离,将其与距离最近的中心点关联起来,与同一个中心点关联的所有点聚成一类。

计算每一个组的平均值,将该组所关联的中心点移动到平均值的位置。

重复步骤 2-4 直至中心点不再变化。

下面是一个聚类示例:

迭代 1 次

迭代 3 次

迭代 10 次







μ





,





μ





,...,





μ





 来表示聚类中心,用











,











,...,











来存储与第




个实例数据最近的聚类中心的索引,K-均值算法的伪代码如下:

Repeat {
for i = 1 to mc(i) := index (form 1 to K) of cluster centroid closest to x(i)for k = 1 to Kμk := average (mean) of points assigned to cluster k
}

算法分为两个步骤,第一个for循环是赋值步骤,即:对于每一个样例




,计算其应该属于的类。第二个for循环是聚类中心的移动,即:对于每一个类




,重新计算该类的质心。

K-均值算法也可以很便利地用于将数据分为许多不同组,即使在没有非常明显区分的组群的情况下也可以。下图所示的数据集包含身高和体重两项特征构成的,利用K-均值算法将数据分为三类,用于帮助确定将要生产的 T-恤衫的三种尺寸。

13.3 优化目标

参考视频: 13 - 3 - Optimization Objective (7 min).mkv

K-均值最小化问题,是要最小化所有的数据点与其所关联的聚类中心点之间的距离之和,因此 K-均值的代价函数(又称畸变函数 Distortion function)为:

其中
























代表与















最近的聚类中心点。我们的的优化目标便是找出使得代价函数最小的 











,











,...,



















,







,...,







回顾刚才给出的:K-均值迭代算法,我们知道,第一个循环是用于减小











引起的代价,而第二个循环则是用于减小













引起的代价。迭代的过程一定会是每一次迭代都在减小代价函数,不然便是出现了错误。

13.4 随机初始化

参考视频: 13 - 4 - Random Initialization (8 min).mkv

在运行 K-均值算法的之前,我们首先要随机初始化所有的聚类中心点,下面介绍怎样做:

  1. 我们应该选择$K

  2. 随机选择




    个训练实例,然后令




    个聚类中心分别与这




    个训练实例相等

K-均值的一个问题在于,它有可能会停留在一个局部最小值处,而这取决于初始化的情况。

为了解决这个问题,我们通常需要多次运行K-均值算法,每一次都重新进行随机初始化,最后再比较多次运行K-均值的结果,选择代价函数最小的结果。这种方法在




较小的时候(2--10)还是可行的,但是如果




较大,这么做也可能不会有明显地改善。

13.5 选择聚类数

参考视频: 13 - 5 - Choosing the Number of Clusters (8 min).mkv

没有所谓最好的选择聚类数的方法,通常是需要根据不同的问题,人工进行选择的。选择的时候思考我们运用K-均值算法聚类的动机是什么,然后选择能最好服务于该目的标聚类数。

当人们在讨论,选择聚类数目的方法时,有一个可能会谈及的方法叫作“肘部法则”。关于“肘部法则”,我们所需要做的是改变




值,也就是聚类类别数目的总数。我们用一个聚类来运行K 均值聚类方法。这就意味着,所有的数据都会分到一个聚类里,然后计算成本函数或者计算畸变函数









代表聚类数字。

我们可能会得到一条类似于这样的曲线。像一个人的肘部。这就是“肘部法则”所做的,让我们来看这样一个图,看起来就好像有一个很清楚的肘在那儿。好像人的手臂,如果你伸出你的胳膊,那么这就是你的肩关节、肘关节、手。这就是“肘部法则”。你会发现这种模式,它的畸变值会迅速下降,从 1 到 2,从 2 到 3 之后,你会在 3 的时候达到一个肘点。在此之后,畸变值就下降的非常慢,看起来就像使用 3 个聚类来进行聚类是正确的,这是因为那个点是曲线的肘点,畸变值下降得很快,






之后就下降得很慢,那么我们就选






。当你应用“肘部法则”的时候,如果你得到了一个像上面这样的图,那么这将是一种用来选择聚类个数的合理方法。

例如,我们的 T-恤制造例子中,我们要将用户按照身材聚类,我们可以分成 3 个尺寸:








,也可以分成 5 个尺寸














,这样的选择是建立在回答“聚类后我们制造的 T-恤是否能较好地适合我们的客户”这个问题的基础上作出的。

聚类参考资料:

1.相似度/距离计算方法总结

(1). 闵可夫斯基距离Minkowski/(其中欧式距离:






)

(2). 杰卡德相似系数(Jaccard):




























(3). 余弦相似度(cosine similarity):






维向量









的夹角记做




,根据余弦定理,其余弦值为:

(4). Pearson 皮尔逊相关系数: 

Pearson 相关系数即将









坐标向量各自平移到原点后的夹角余弦。

2.聚类的衡量指标

(1). 均一性:




类似于精确率,一个簇中只包含一个类别的样本,则满足均一性。其实也可以认为就是正确率(每个 聚簇中正确分类的样本数占该聚簇总样本数的比例和)

(2). 完整性:




类似于召回率,同类别样本被归类到相同簇中,则满足完整性;每个聚簇中正确分类的样本数占该 类型的总样本数比例的和

(3). V-measure:

均一性和完整性的加权平均
































(4). 轮廓系数

样本




的轮廓系数:







簇内不相似度:计算样本




到同簇其它样本的平均距离为







,应尽可能小。

簇间不相似度:计算样本




到其它簇







的所有样本的平均距离










,应尽可能大。

轮廓系数:







值越接近 1 表示样本




聚类越合理,越接近-1,表示样本




应该分类到 另外的簇中,近似为 0,表示样本




应该在边界上;所有样本的







的均值被成为聚类结果的轮廓系数。

(5). ARI

数据集




共有




个元素, 两个聚类结果分别是:











的元素个数为:


记:








































十四、降维(Dimensionality Reduction)

14.1 动机一:数据压缩

参考视频: 14 - 1 - Motivation I_ Data Compression (10 min).mkv

这个视频,我想开始谈论第二种类型的无监督学习问题,称为降维。有几个不同的的原因使你可能想要做降维。一是数据压缩,后面我们会看了一些视频后,数据压缩不仅允许我们压缩数据,因而使用较少的计算机内存或磁盘空间,但它也让我们加快我们的学习算法。

但首先,让我们谈论降维是什么。作为一种生动的例子,我们收集的数据集,有许多,许多特征,我绘制两个在这里。

假设我们未知两个的特征:







:长度:用厘米表示;







:是用英寸表示同一物体的长度。

所以,这给了我们高度冗余表示,也许不是两个分开的特征















,这两个基本的长度度量,也许我们想要做的是减少数据到一维,只有一个数测量这个长度。这个例子似乎有点做作,这里厘米英寸的例子实际上不是那么不切实际的,两者并没有什么不同。

将数据从二维降至一维:假使我们要采用两种不同的仪器来测量一些东西的尺寸,其中一个仪器测量结果的单位是英寸,另一个仪器测量的结果是厘米,我们希望将测量的结果作为我们机器学习的特征。现在的问题的是,两种仪器对同一个东西测量的结果不完全相等(由于误差、精度等),而将两者都作为特征有些重复,因而,我们希望将这个二维的数据降至一维。

从这件事情我看到的东西发生在工业上的事。如果你有几百个或成千上万的特征,它是它这往往容易失去你需要的特征。有时可能有几个不同的工程团队,也许一个工程队给你二百个特征,第二工程队给你另外三百个的特征,第三工程队给你五百个特征,一千多个特征都在一起,它实际上会变得非常困难,去跟踪你知道的那些特征,你从那些工程队得到的。其实不想有高度冗余的特征一样。

多年我一直在研究直升飞机自动驾驶。诸如此类。如果你想测量——如果你想做,你知道,做一个调查或做这些不同飞行员的测试——你可能有一个特征:







,这也许是他们的技能(直升机飞行员),也许







可能是飞行员的爱好。这是表示他们是否喜欢飞行,也许这两个特征将高度相关。你真正关心的可能是这条红线的方向,不同的特征,决定飞行员的能力。

将数据从三维降至二维:这个例子中我们要将一个三维的特征向量降至一个二维的特征向量。过程是与上面类似的,我们将三维向量投射到一个二维的平面上,强迫使得所有的数据都在同一个平面上,降至二维的特征向量。

这样的处理过程可以被用于把任何维度的数据降到任何想要的维度,例如将 1000 维的特征降至 100 维。

正如我们所看到的,最后,这将使我们能够使我们的一些学习算法运行也较晚,但我们会在以后的视频提到它。

14.2 动机二:数据可视化

参考视频: 14 - 2 - Motivation II_ Visualization (6 min).mkv

在许多及其学习问题中,如果我们能将数据可视化,我们便能寻找到一个更好的解决方案,降维可以帮助我们。

假使我们有有关于许多不同国家的数据,每一个特征向量都有 50 个特征(如GDP,人均GDP,平均寿命等)。如果要将这个 50 维的数据可视化是不可能的。使用降维的方法将其降至 2 维,我们便可以将其可视化了。

这样做的问题在于,降维的算法只负责减少维数,新产生的特征的意义就必须由我们自己去发现了。

14.3 主成分分析问题

参考视频: 14 - 3 - Principal Component Analysis Problem Formulation (9 min). mkv

主成分分析(PCA)是最常见的降维算法。

PCA中,我们要做的是找到一个方向向量(Vector direction),当我们把所有的数据都投射到该向量上时,我们希望投射平均均方误差能尽可能地小。方向向量是一个经过原点的向量,而投射误差是从特征向量向该方向向量作垂线的长度。

下面给出主成分分析问题的描述:

问题是要将




维数据降至




维,目标是找到向量











,











,...,











使得总的投射误差最小。主成分分析与线性回顾的比较:

主成分分析与线性回归是两种不同的算法。主成分分析最小化的是投射误差(Projected Error),而线性回归尝试的是最小化预测误差。线性回归的目的是预测结果,而主成分分析不作任何预测。

上图中,左边的是线性回归的误差(垂直于横轴投影),右边则是主要成分分析的误差(垂直于红线投影)。

PCA




个特征降维到




个,可以用来进行数据压缩,如果 100 维的向量最后可以用 10 维来表示,那么压缩率为 90%。同样图像处理领域的KL 变换使用PCA做图像压缩。但PCA 要保证降维后,还要保证数据的特性损失最小。

PCA技术的一大好处是对数据进行降维的处理。我们可以对新求出的“主元”向量的重要性进行排序,根据需要取前面最重要的部分,将后面的维数省去,可以达到降维从而简化模型或是对数据进行压缩的效果。同时最大程度的保持了原有数据的信息。

PCA技术的一个很大的优点是,它是完全无参数限制的。在PCA的计算过程中完全不需要人为的设定参数或是根据任何经验模型对计算进行干预,最后的结果只与数据相关,与用户是独立的。

但是,这一点同时也可以看作是缺点。如果用户对观测对象有一定的先验知识,掌握了数据的一些特征,却无法通过参数化等方法对处理过程进行干预,可能会得不到预期的效果,效率也不高。

14.4 主成分分析算法

参考视频: 14 - 4 - Principal Component Analysis Algorithm (15 min).mkv

PCA 减少




维到




维:

第一步是均值归一化。我们需要计算出所有特征的均值,然后令 















μ





。如果特征是在不同的数量级上,我们还需要将其除以标准差 







第二步是计算协方差矩阵covariance matrix






: 

第三步是计算协方差矩阵






特征向量eigenvectors):

Octave 里我们可以利用奇异值分解singular value decomposition)来求解,[U, S, V]= svd(sigma)

对于一个 






维度的矩阵,上式中的




是一个具有与数据之间最小投射误差的方向向量构成的矩阵。如果我们希望将数据从




维降至




维,我们只需要从




中选取前




个向量,获得一个






维度的矩阵,我们用














表示,然后通过如下计算获得要求的新特征向量











:





































其中











维的,因此结果为






维度。注,我们不对方差特征进行处理。

14.5 选择主成分的数量

参考视频: 14 - 5 - Choosing The Number Of Principal Components (13 min).mkv

主要成分分析是减少投射的平均均方误差:

训练集的方差为:











































我们希望在平均均方误差与训练集方差的比例尽可能小的情况下选择尽可能小的




值。

如果我们希望这个比例小于 1%,就意味着原本数据的偏差有 99%都保留下来了,如果我们选择保留 95%的偏差,便能非常显著地降低模型中特征的维度了。

我们可以先令






,然后进行主要成分分析,获得



















,然后计算比例是否小于 1%。如果不是的话再令






,如此类推,直到找到可以使得比例小于 1%的最小




 值(原因是各个特征之间通常情况存在某种相关性)。

还有一些更好的方式来选择




,当我们在Octave中调用“svd”函数的时候,我们获得三个参数:[U, S, V] = svd(sigma)

其中的




是一个






的矩阵,只有对角线上有值,而其它单元都是 0,我们可以使用这个矩阵来计算平均均方误差与训练集方差的比例:

也就是:

在压缩过数据后,我们可以采用如下方法来近似地获得原有的特征:

14.6 重建的压缩表示

参考视频: 14 - 6 - Reconstruction from Compressed Representation (4 min).mkv

在以前的视频中,我谈论PCA作为压缩算法。在那里你可能需要把 1000 维的数据压缩 100 维特征,或具有三维数据压缩到一二维表示。所以,如果这是一个压缩算法,应该能回到这个压缩表示,回到你原有的高维数据的一种近似。

所以,给定的











,这可能 100 维,怎么回到你原来的表示











,这可能是 1000 维的数组?

PCA算法,我们可能有一个这样的样本。如图中样本











,











。我们做的是,我们把这些样本投射到图中这个一维平面。然后现在我们需要只使用一个实数,比如











,指定这些点的位置后他们被投射到这一个三维曲面。给定一个点











,我们怎么能回去这个原始的二维空间呢?




为 2 维,




为 1 维,




















,相反的方程为:



























,















。如图:

如你所知,这是一个漂亮的与原始数据相当相似。所以,这就是你从低维表示




回到未压缩的表示。我们得到的数据的一个之间你的原始数据 




,我们也把这个过程称为重建原始数据。

当我们认为试图重建从压缩表示 




 的初始值。所以,给定未标记的数据集,您现在知道如何应用PCA,你的带高维特征




和映射到这的低维表示




。这个视频,希望你现在也知道如何采取这些低维表示




,映射到备份到一个近似你原有的高维数据。

现在你知道如何实施应用PCA,我们将要做的事是谈论一些技术在实际使用PCA很好,特别是,在接下来的视频中,我想谈一谈关于如何选择




14.7 主成分分析法的应用建议

参考视频: 14 - 7 - Advice for Applying PCA (13 min).mkv

假使我们正在针对一张 100×100 像素的图片进行某个计算机视觉的机器学习,即总共有 10000 个特征。

  1. 第一步是运用主要成分分析将数据压缩至 1000 个特征

  2. 然后对训练集运行学习算法

  3. 在预测时,采用之前学习而来的














    将输入的特征




    转换成特征向量




    ,然后再进行预测

注:如果我们有交叉验证集合测试集,也采用对训练集学习而来的














错误的主要成分分析情况:一个常见错误使用主要成分分析的情况是,将其用于减少过拟合(减少了特征的数量)。这样做非常不好,不如尝试正则化处理。原因在于主要成分分析只是近似地丢弃掉一些特征,它并不考虑任何与结果变量有关的信息,因此可能会丢失非常重要的特征。然而当我们进行正则化处理时,会考虑到结果变量,不会丢掉重要的数据。

另一个常见的错误是,默认地将主要成分分析作为学习过程中的一部分,这虽然很多时候有效果,最好还是从所有原始特征开始,只在有必要的时候(算法运行太慢或者占用太多内存)才考虑采用主要成分分析。

代码部分:

机器学习练习 4 - K-means 和 PCA(主成分分析)

代码修改并注释:黄海广,haiguang2000@qq.com

在本练习中,我们将实现 K-means 聚类,并使用它来压缩图像。 我们将从一个简单的 2D 数据集开始,以了解 K-means 是如何工作的,然后我们将其应用于图像压缩。 我们还将对主成分分析进行实验,并了解如何使用它来找到面部图像的低维表示。

K-means 聚类

我们将实施和应用 K-means 到一个简单的二维数据集,以获得一些直观的工作原理。 K-means 是一个迭代的,无监督的聚类算法,将类似的实例组合成簇。 该算法通过猜测每个簇的初始聚类中心开始,然后重复将实例分配给最近的簇,并重新计算该簇的聚类中心。 我们要实现的第一部分是找到数据中每个实例最接近的聚类中心的函数。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sb
from scipy.io import loadmat
def find_closest_centroids(X, centroids):m = X.shape[0]k = centroids.shape[0]idx = np.zeros(m)for i in range(m):min_dist = 1000000for j in range(k):dist = np.sum((X[i,:] - centroids[j,:]) ** 2)if dist < min_dist:min_dist = distidx[i] = jreturn idx

让我们来测试这个函数,以确保它的工作正常。 我们将使用练习中提供的测试用例。

data = loadmat('data/ex7data2.mat')
X = data['X']
initial_centroids = initial_centroids = np.array([[3, 3], [6, 2], [8, 5]])idx = find_closest_centroids(X, initial_centroids)
idx[0:3]
array([0., 2., 1.])

输出与文本中的预期值匹配(记住我们的数组是从零开始索引的,而不是从一开始索引的,所以值比练习中的值低一个)。 接下来,我们需要一个函数来计算簇的聚类中心。 聚类中心只是当前分配给簇的所有样本的平均值。

data2 = pd.DataFrame(data.get('X'), columns=['X1', 'X2'])
data2.head()
X1 X2
0 1.842080 4.607572
1 5.658583 4.799964
2 6.352579 3.290854
3 2.904017 4.612204
4 3.231979 4.939894
sb.set(context="notebook", style="white")
sb.lmplot('X1', 'X2', data=data2, fit_reg=False)
plt.show()

def compute_centroids(X, idx, k):m, n = X.shapecentroids = np.zeros((k, n))for i in range(k):indices = np.where(idx == i)centroids[i,:] = (np.sum(X[indices,:], axis=1) / len(indices[0])).ravel()return centroids
compute_centroids(X, idx, 3)
array([[2.42830111, 3.15792418],[5.81350331, 2.63365645],[7.11938687, 3.6166844 ]])

此输出也符合练习中的预期值。 下一部分涉及实际运行该算法的一些迭代次数和可视化结果。 这个步骤是由于并不复杂,我将从头开始构建它。 为了运行算法,我们只需要在将样本分配给最近的簇并重新计算簇的聚类中心。

def run_k_means(X, initial_centroids, max_iters):m, n = X.shapek = initial_centroids.shape[0]idx = np.zeros(m)centroids = initial_centroidsfor i in range(max_iters):idx = find_closest_centroids(X, centroids)centroids = compute_centroids(X, idx, k)return idx, centroids
idx, centroids = run_k_means(X, initial_centroids, 10)
cluster1 = X[np.where(idx == 0)[0],:]
cluster2 = X[np.where(idx == 1)[0],:]
cluster3 = X[np.where(idx == 2)[0],:]fig, ax = plt.subplots(figsize=(12,8))
ax.scatter(cluster1[:,0], cluster1[:,1], s=30, color='r', label='Cluster 1')
ax.scatter(cluster2[:,0], cluster2[:,1], s=30, color='g', label='Cluster 2')
ax.scatter(cluster3[:,0], cluster3[:,1], s=30, color='b', label='Cluster 3')
ax.legend()
plt.show()

我们跳过的一个步骤是初始化聚类中心的过程。 这可以影响算法的收敛。 我们的任务是创建一个选择随机样本并将其用作初始聚类中心的函数。

def init_centroids(X, k):m, n = X.shapecentroids = np.zeros((k, n))idx = np.random.randint(0, m, k)for i in range(k):centroids[i,:] = X[idx[i],:]return centroids
init_centroids(X, 3)
array([[0.99253246, 5.01567424],[6.63060699, 3.01502301],[4.88804332, 5.50670795]])
from sklearn.cluster import KMeans#导入kmeans库model = KMeans(n_clusters=3, n_init=100, n_jobs=-1)
model.fit(data2)
KMeans(algorithm='auto', copy_x=True, init='k-means++', max_iter=300,n_clusters=3, n_init=100, n_jobs=-1, precompute_distances='auto',random_state=None, tol=0.0001, verbose=0)
centroids = model.cluster_centers_
print(centroids.shape)C = model.predict(data2)
print(C.shape)
(3, 2)
(300,)
centroids[C].shape
(300, 2)

肘部法则

当人们在讨论,选择聚类数目的方法时,有一个可能会谈及的方法叫作“肘部法则”。关于“肘部法则”,我们所需要做的是改变 K 值,也就是聚类类别数目的总数。我们用一个聚类来运行 K 均值聚类方法。这就意味着,所有的数据都会分到一个聚类里,然后计算成本函数或者计算畸变函数 J。K 代表聚类数字。

我们可能会得到一条类似于这样的曲线。像一个人的肘部。这就是“肘部法则”所做的,让我们来看这样一个图,看起来就好像有一个很清楚的肘在那儿。好像人的手臂,如果你伸出你的胳膊,那么这就是你的肩关节、肘关节、手。这就是“肘部法则”。你会发现这种模式,它的畸变值会迅速下降,从 1 到 2,从 2 到 3 之后,你会在 2 的时候达到一个肘点。在此之后,畸变值就下降的非常慢,看起来就像使用 3 个聚类来进行聚类是正确的,这是因为那个点是曲线的肘点,畸变值下降得很快,K=2 之后就下降得很慢,那么我们就选 K=2。当你应用“肘部法则”的时候,如果你得到了一个像下面这样的图,那么这将是一种用来选择聚类个数的合理方法。

import numpy as np
from sklearn.cluster import KMeans
from scipy.spatial.distance import cdist
import matplotlib.pyplot as pltc1x = np.random.uniform(0.5, 1.5, (1, 10))
c1y = np.random.uniform(0.5, 1.5, (1, 10))
c2x = np.random.uniform(3.5, 4.5, (1, 10))
c2y = np.random.uniform(3.5, 4.5, (1, 10))
x = np.hstack((c1x, c2x))
y = np.hstack((c1y, c2y))
X = np.vstack((x, y)).TK = range(1, 10)
meanDispersions = []
for k in K:kmeans = KMeans(n_clusters=k)kmeans.fit(X)meanDispersions.append(sum(np.min(cdist(X, kmeans.cluster_centers_, 'euclidean'), axis=1)) / X.shape[0])plt.plot(K, meanDispersions, 'bx-')
plt.xlabel('k')
plt.ylabel('Average Dispersion')
plt.title('Selecting k with the Elbow Method')
plt.show()

#导入相应的包
import scipy
import scipy.cluster.hierarchy as sch
from scipy.cluster.vq import vq,kmeans,whiten
import numpy as np
import matplotlib.pylab as plt
import pandas as pd
from sklearn import preprocessing
from sklearn.decomposition import PCA
newData=np.load("data/filename.npy")
newData
array([[-1.18945132, -0.31092235],[ 2.06415695, -0.74854414],[-1.43769023, -0.80669682],[-3.23039706,  0.84519783],[ 2.36892693, -0.44480961],[ 0.28997221,  2.79266758],[ 1.2099519 , -0.00638496],[-2.09689459, -0.22796377],[ 5.50091159, -0.14275827],[-3.47948639, -0.94978548]])
#1. 层次聚类
#生成点与点之间的距离矩阵,这里用的欧氏距离:
disMat = sch.distance.pdist(newData,'euclidean')
#进行层次聚类:
Z=sch.linkage(disMat,method='average')
#将层级聚类结果以树状图表示出来并保存为plot_dendrogram.png
P=sch.dendrogram(Z)

dbscan 密度聚类

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets.samples_generator import make_blobs
# from  .agglomerative_clustering import test_AgglomerativeClustering,test_AgglomerativeClustering_nclusters,test_AgglomerativeClustering_linkage
# from .dbscan import test_DBSCAN,test_DBSCAN_epsilon,test_DBSCAN_min_samples
# from chapters.Cluster_EM.gmm import test_GMM,test_GMM_cov_type,test_GMM_n_components
# from .kmeans import test_Kmeans,test_Kmeans_n_init,test_Kmeans_nclustersdef create_data(centers,num=100,std=0.7):'''生成用于聚类的数据集:param centers: 聚类的中心点组成的数组。如果中心点是二维的,则产生的每个样本都是二维的。:param num: 样本数:param std: 每个簇中样本的标准差:return: 用于聚类的数据集。是一个元组,第一个元素为样本集,第二个元素为样本集的真实簇分类标记'''X, labels_true = make_blobs(n_samples=num, centers=centers, cluster_std=std)return  X,labels_true
def plot_data(*data):'''绘制用于聚类的数据集:param data: 可变参数。它是一个元组。元组元素依次为:第一个元素为样本集,第二个元素为样本集的真实簇分类标记:return: None'''X,labels_true=datalabels=np.unique(labels_true)fig=plt.figure()ax=fig.add_subplot(1,1,1)colors='rgbyckm' # 每个簇的样本标记不同的颜色for i,label in enumerate(labels):position=labels_true==labelax.scatter(X[position,0],X[position,1],label="cluster %d"%label,color=colors[i%len(colors)])ax.legend(loc="best",framealpha=0.5)ax.set_xlabel("X[0]")ax.set_ylabel("Y[1]")ax.set_title("data")plt.show()
from sklearn import  cluster
from sklearn.metrics import adjusted_rand_score
import matplotlib.pyplot as plt
plt.figure(figsize=(12,12))
def test_DBSCAN(*data):'''测试 DBSCAN 的用法:param data:  可变参数。它是一个元组。元组元素依次为:第一个元素为样本集,第二个元素为样本集的真实簇分类标记:return: None'''X,labels_true=dataclst=cluster.DBSCAN()predicted_labels=clst.fit_predict(X)print("ARI:%s"% adjusted_rand_score(labels_true,predicted_labels))print("Core sample num:%d"%len(clst.core_sample_indices_))
def test_DBSCAN_epsilon(*data):'''测试 DBSCAN 的聚类结果随  eps 参数的影响:param data:  可变参数。它是一个元组。元组元素依次为:第一个元素为样本集,第二个元素为样本集的真实簇分类标记:return: None'''X,labels_true=dataepsilons=np.logspace(-1,1.5)ARIs=[]Core_nums=[]for epsilon in epsilons:clst=cluster.DBSCAN(eps=epsilon)predicted_labels=clst.fit_predict(X)ARIs.append( adjusted_rand_score(labels_true,predicted_labels))Core_nums.append(len(clst.core_sample_indices_))## 绘图fig=plt.figure()ax=fig.add_subplot(1,2,1)ax.plot(epsilons,ARIs,marker='+')ax.set_xscale('log')ax.set_xlabel(r"$\epsilon$")ax.set_ylim(0,1)ax.set_ylabel('ARI')ax=fig.add_subplot(1,2,2)ax.plot(epsilons,Core_nums,marker='o')ax.set_xscale('log')ax.set_xlabel(r"$\epsilon$")ax.set_ylabel('Core_Nums')fig.suptitle("DBSCAN")plt.show()
def test_DBSCAN_min_samples(*data):'''测试 DBSCAN 的聚类结果随  min_samples 参数的影响:param data:  可变参数。它是一个元组。元组元素依次为:第一个元素为样本集,第二个元素为样本集的真实簇分类标记:return:  None'''X,labels_true=datamin_samples=range(1,100)ARIs=[]Core_nums=[]for num in min_samples:clst=cluster.DBSCAN(min_samples=num)predicted_labels=clst.fit_predict(X)ARIs.append( adjusted_rand_score(labels_true,predicted_labels))Core_nums.append(len(clst.core_sample_indices_))## 绘图fig=plt.figure()ax=fig.add_subplot(1,2,1)ax.plot(min_samples,ARIs,marker='+')ax.set_xlabel( "min_samples")ax.set_ylim(0,1)ax.set_ylabel('ARI')ax=fig.add_subplot(1,2,2)ax.plot(min_samples,Core_nums,marker='o')ax.set_xlabel( "min_samples")ax.set_ylabel('Core_Nums')fig.suptitle("DBSCAN")plt.show()
centers=[[1,1],[2,2],[1,2],[10,20]] # 用于产生聚类的中心点
X,labels_true=create_data(centers,1000,0.5) # 产生用于聚类的数据集
X
array([[0.81394428, 0.97283296],[2.39126942, 1.39691895],[0.52294212, 2.28195147],...,[0.07392303, 0.23407894],[0.92014917, 1.63693107],[0.95976852, 1.00443603]])
labels_true
array([0, 1, 2, 1, 2, 2, 3, 3, 3, 3, 3, 2, 2, 1, 1, 0, 0, 0, 2, 1, 3, 1,0, 1, 1, 1, 1, 0, 1, 2, 3, 2, 3, 1, 0, 1, 3, 2, 3, 2, 2, 2, 2, 3,2, 2, 2, 0, 1, 3, 0, 2, 1, 0, 2, 2, 3, 2, 2, 1, 3, 3, 1, 2, 2, 0,1, 3, 1, 0, 2, 0, 0, 0, 0, 1, 0, 1, 0, 3, 0, 3, 0, 1, 2, 2, 0, 1,1, 3, 0, 1, 0, 3, 0, 0, 1, 3, 3, 3, 0, 1, 0, 1, 0, 1, 1, 2, 2, 2,1, 1, 3, 2, 2, 2, 2, 1, 2, 1, 1, 0, 2, 0, 1, 1, 1, 1, 1, 3, 2, 0,0, 1, 3, 1, 0, 3, 3, 2, 3, 1, 1, 2, 1, 0, 2, 3, 0, 1, 3, 2, 2, 2,0, 2, 2, 0, 3, 0, 1, 0, 3, 3, 0, 0, 3, 2, 3, 1, 3, 0, 0, 1, 0, 1,3, 3, 3, 1, 3, 2, 3, 2, 0, 0, 2, 1, 1, 2, 3, 1, 2, 3, 0, 3, 1, 0,0, 1, 3, 2, 1, 1, 0, 3, 1, 2, 2, 3, 0, 0, 2, 2, 1, 3, 3, 0, 2, 0,0, 2, 1, 0, 3, 0, 1, 3, 2, 2, 0, 0, 3, 3, 2, 2, 1, 3, 0, 1, 3, 1,2, 2, 0, 3, 0, 2, 2, 2, 2, 2, 1, 2, 0, 2, 0, 0, 2, 3, 1, 3, 3, 3,3, 3, 3, 0, 1, 3, 2, 0, 1, 2, 2, 3, 0, 1, 1, 0, 3, 0, 3, 1, 2, 2,3, 0, 0, 3, 1, 0, 3, 2, 2, 0, 3, 2, 1, 0, 3, 3, 2, 1, 0, 1, 3, 3,0, 1, 2, 0, 3, 3, 0, 2, 0, 0, 0, 2, 3, 0, 2, 3, 1, 2, 1, 1, 3, 3,1, 2, 2, 0, 0, 2, 0, 3, 1, 3, 1, 2, 2, 1, 2, 0, 0, 1, 2, 1, 1, 1,0, 0, 1, 3, 2, 3, 3, 0, 3, 1, 1, 1, 0, 3, 1, 1, 3, 1, 3, 0, 3, 3,1, 0, 2, 0, 0, 2, 2, 1, 2, 3, 2, 2, 0, 2, 2, 1, 3, 1, 1, 2, 3, 1,3, 2, 3, 2, 1, 3, 3, 0, 1, 1, 3, 2, 1, 1, 2, 3, 1, 3, 0, 3, 0, 2,3, 0, 0, 2, 3, 2, 3, 0, 0, 3, 3, 3, 0, 0, 2, 3, 2, 3, 3, 0, 2, 3,2, 3, 0, 1, 1, 1, 3, 0, 3, 0, 1, 0, 3, 0, 3, 1, 2, 2, 2, 1, 2, 0,3, 0, 0, 1, 0, 0, 3, 0, 2, 3, 3, 1, 0, 2, 1, 0, 2, 3, 0, 1, 2, 1,3, 0, 1, 2, 2, 3, 2, 1, 0, 1, 2, 3, 2, 3, 1, 2, 3, 0, 1, 2, 2, 2,3, 0, 3, 1, 1, 1, 2, 1, 1, 0, 0, 1, 1, 0, 3, 3, 0, 1, 1, 3, 1, 3,2, 2, 0, 3, 1, 0, 2, 1, 0, 2, 3, 1, 0, 1, 2, 3, 2, 2, 0, 0, 0, 1,0, 0, 0, 3, 3, 2, 0, 0, 2, 2, 1, 2, 1, 0, 0, 1, 0, 2, 3, 1, 1, 3,3, 1, 3, 3, 3, 1, 2, 0, 2, 1, 3, 3, 1, 1, 1, 0, 1, 3, 3, 3, 1, 1,2, 1, 2, 1, 0, 3, 1, 0, 1, 1, 2, 3, 1, 0, 1, 0, 1, 0, 3, 0, 0, 3,3, 0, 1, 3, 2, 3, 3, 3, 3, 2, 3, 2, 3, 2, 0, 3, 2, 0, 0, 2, 2, 0,1, 0, 2, 1, 0, 2, 2, 0, 0, 1, 0, 1, 0, 3, 3, 3, 1, 2, 3, 1, 0, 1,3, 3, 0, 1, 3, 2, 3, 3, 0, 0, 0, 3, 2, 1, 0, 0, 2, 3, 0, 0, 2, 0,3, 1, 3, 3, 3, 2, 1, 2, 3, 0, 1, 3, 3, 2, 3, 2, 2, 2, 1, 3, 0, 2,3, 2, 1, 2, 3, 3, 1, 1, 0, 0, 1, 2, 1, 1, 2, 3, 2, 0, 1, 0, 0, 3,2, 3, 1, 1, 2, 3, 1, 1, 0, 3, 2, 0, 1, 3, 2, 2, 0, 0, 1, 0, 3, 2,1, 0, 1, 3, 0, 3, 3, 3, 1, 2, 2, 0, 2, 2, 2, 0, 0, 0, 3, 2, 0, 0,1, 0, 1, 1, 2, 1, 0, 3, 0, 0, 3, 3, 3, 3, 2, 1, 0, 1, 2, 3, 1, 0,0, 0, 1, 1, 1, 3, 1, 0, 3, 2, 2, 2, 2, 2, 1, 2, 3, 1, 3, 0, 1, 2,3, 1, 0, 0, 0, 1, 2, 3, 2, 2, 3, 3, 2, 1, 0, 2, 2, 2, 2, 3, 1, 0,1, 2, 0, 0, 2, 1, 2, 0, 0, 2, 1, 0, 0, 2, 3, 0, 1, 2, 0, 3, 1, 2,1, 0, 3, 2, 0, 1, 0, 0, 3, 1, 2, 0, 0, 2, 2, 1, 1, 2, 3, 2, 3, 0,0, 2, 3, 2, 1, 0, 0, 3, 0, 0, 2, 3, 3, 3, 1, 3, 0, 2, 2, 3, 2, 1,0, 3, 3, 0, 1, 3, 3, 2, 2, 2, 3, 3, 2, 3, 2, 1, 3, 3, 3, 2, 0, 1,2, 1, 3, 1, 1, 1, 0, 1, 1, 0, 2, 1, 2, 2, 1, 3, 0, 2, 0, 1, 2, 1,1, 0, 3, 0, 2, 1, 1, 0, 1, 1, 0, 1, 0, 1, 2, 3, 1, 3, 3, 2, 1, 2,2, 1, 2, 3, 2, 2, 0, 3, 0, 1, 0, 0, 3, 1, 3, 1, 0, 2, 1, 2, 2, 2,3, 0, 3, 2, 1, 3, 1, 0, 2, 0])
test_DBSCAN(X,labels_true) #  调用 test_DBSCAN 函数
ARI:0.3326689255565291
Core sample num:990
test_DBSCAN_epsilon(X,labels_true) #  调用 test_DBSCAN_epsilon 函数

test_DBSCAN_min_samples(X,labels_true) #  调用 test_DBSCAN_min_samples 函数

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
%matplotlib inline
X1, y1=datasets.make_circles(n_samples=5000, factor=.6,noise=.05)
X2, y2 = datasets.make_blobs(n_samples=1000, n_features=2, centers=[[1.2,1.2]], cluster_std=[[.1]],random_state=9)X = np.concatenate((X1, X2))
plt.scatter(X[:, 0], X[:, 1], marker='o')
plt.show()

from sklearn.cluster import KMeans
y_pred = KMeans(n_clusters=3, random_state=9).fit_predict(X)
plt.scatter(X[:, 0], X[:, 1], c=y_pred)
plt.show()

from sklearn.cluster import DBSCAN
y_pred = DBSCAN().fit_predict(X)
plt.scatter(X[:, 0], X[:, 1], c=y_pred)
plt.show()

y_pred = DBSCAN(eps = 0.1).fit_predict(X)
plt.scatter(X[:, 0], X[:, 1], c=y_pred)
plt.show()

y_pred = DBSCAN(eps = 0.1, min_samples = 4).fit_predict(X)
plt.scatter(X[:, 0], X[:, 1], c=y_pred)
plt.show()

Principal component analysis(主成分分析)

PCA 是在数据集中找到“主成分”或最大方差方向的线性变换。 它可以用于降维。 在本练习中,我们首先负责实现 PCA 并将其应用于一个简单的二维数据集,以了解它是如何工作的。 我们从加载和可视化数据集开始。

data = loadmat('data/ex7data1.mat')
X = data['X']fig, ax = plt.subplots(figsize=(12,8))
ax.scatter(X[:, 0], X[:, 1])
plt.show()

PCA 的算法相当简单。 在确保数据被归一化之后,输出仅仅是原始数据的协方差矩阵的奇异值分解。

def pca(X):# normalize the featuresX = (X - X.mean()) / X.std()# compute the covariance matrixX = np.matrix(X)cov = (X.T * X) / X.shape[0]# perform SVDU, S, V = np.linalg.svd(cov)return U, S, V
U, S, V = pca(X)
U, S, V
(matrix([[-0.79241747, -0.60997914],[-0.60997914,  0.79241747]]),array([1.43584536, 0.56415464]),matrix([[-0.79241747, -0.60997914],[-0.60997914,  0.79241747]]))
print(X.shape)
print(U.shape)
print(S.shape)
print(V.shape)
(50, 2)
(2, 2)
(2,)
(2, 2)

现在我们有主成分(矩阵 U),我们可以用这些来将原始数据投影到一个较低维的空间中。 对于这个任务,我们将实现一个计算投影并且仅选择顶部 K 个分量的函数,有效地减少了维数。

def project_data(X, U, k):U_reduced = U[:,:k]return np.dot(X, U_reduced)
Z = project_data(X, U, 1)

我们也可以通过反向转换步骤来恢复原始数据。

def recover_data(Z, U, k):U_reduced = U[:,:k]return np.dot(Z, U_reduced.T)
X_recovered = recover_data(Z, U, 1)

fig, ax = plt.subplots(figsize=(12,8))
ax.scatter(list(X_recovered[:, 0]), list(X_recovered[:, 1]))
plt.show()

请注意,第一主成分的投影轴基本上是数据集中的对角线。 当我们将数据减少到一个维度时,我们失去了该对角线周围的变化,所以在我们的再现中,一切都沿着该对角线。

参考资料

[1] 机器学习课程: https://www.coursera.org/course/ml
[2] 黄海广: https://github.com/fengdu78
[3] github: https://github.com/fengdu78/Coursera-ML-AndrewNg-Notes
[4] 作业代码: https://github.com/fengdu78/Coursera-ML-AndrewNg-Notes/blob/master/codeex7-kmeans%20and%20PCA/ML-Exercise7.ipynb
[5] markdown 文件: https://github.com/fengdu78/Coursera-ML-AndrewNg-Notes/blob/master/markdown/week8.md
[6] pdf 文件: https://github.com/fengdu78/Coursera-ML-AndrewNg-Notes/blob/master/机器学习个人笔记完整版v5.4-A4打印版.pdf

关于本站

“机器学习初学者”公众号由是黄海广博士创建,黄博个人知乎粉丝23000+,github排名全球前100名(33000+)。本公众号致力于人工智能方向的科普性文章,为初学者提供学习路线和基础资料。原创作品有:吴恩达机器学习个人笔记、吴恩达深度学习笔记等。

往期精彩回顾

  • 那些年做的学术公益-你不是一个人在战斗

  • 适合初学者入门人工智能的路线及资料下载

  • 吴恩达机器学习课程笔记及资源(github标星12000+,提供百度云镜像)

  • 吴恩达深度学习笔记及视频等资源(github标星8500+,提供百度云镜像)

  • 《统计学习方法》的python代码实现(github标星7200+)

  • 机器学习的数学精华(在线阅读版)

备注:加入本站微信群或者qq群,请回复“加群

加入知识星球(4300+用户,ID:92416895),请回复“知识星球

经典算法笔记:无监督算法(聚类、降维)相关推荐

  1. 聚类算法(无监督算法)

    一.聚类算法简介 聚类的基本思想:对于给定的M个样本的数据集,给定聚类(簇)的个数K(K<M),初始化每个样本所属的类别,再根据一定的规则不断地迭代并重新划分数据集的类别(改变样本与簇的类别关系 ...

  2. 有监督算法和无监督算法都有哪些

    有监督分类的算法: 在已经有标签的数据上去分类 在已经有标签的数据上去预测上传笔记本电脑的品相 品相好的给1 不好的给0 主要有下面一些分类算法 k临近算法分类 朴素贝叶斯分类 决策树分类 随机森林分 ...

  3. 有监督算法和无监督算法的理解

    无监督和有监督的理解方法有很多,主要可以从以下几方面来理解: 1.无监督与监督学习的区别在于一个无教学值,一个有教学值.但是,个人认为他们的区别在于无监督学习一般是采用聚簇等算法来分类不同样本.而监督 ...

  4. 【采用】无监督核心聚类算法

    第一种 K-means 现在,我们暂时不去考虑原始数据的形式,假设我们已经讲其映射到了一个欧式空间上了,为了方便,我们用二维空间来展示,如下图所示: 图1 散点图 单纯用肉眼看,我们的大脑很快就能判断 ...

  5. 无监督算法与异常检测

    一.整体概览 反欺诈往往看做是二分类问题,但是仔细想想是多分类问题,因为每种不同类型的欺诈都当做是一种单独的类型.欺诈除了多样并且不断变化,欺诈检测还面临一下问题: 1). 由于大部分情况数据是没有标 ...

  6. 论标签对无监督算法的使用

    说到无监督机器学习(unsupervised machinelearning),头脑中第一个蹦出来的词一定是三个字--"无标签"!那么,是不是说,无监督就一定不需要标签?用了标签就 ...

  7. 无监督算法——自组织映射算法(Self-Organizing Map , SOM)

    Kohonen提出了一种神经网络模型.   自组织映射神经网络, 即Self Organizing Maps (SOM), 可以对数据进行无监督学习聚类. SOM是一种比较简单的神经网络,他不像BP网 ...

  8. AIOps指标异常检测之无监督算法

    随着系统规模的变大.复杂度的提高.监控覆盖的完善,监控数据量越来越大,运维人员无法从海量监控数据中发现质量问题.智能化的异常检测就是要通过AI算法,自动.实时.准确地从监控数据中发现异常,为后续的诊断 ...

  9. 文本相似度之五种无监督算法实现代码

    短文本相似度,即求解两个短文本之间的相似程度:它是文本匹配任务或文本蕴含任务的一种特殊形式,返回文本之间相似程度的具体数值.然而在工业界中,短文本相似度计算占有举足轻重的地位. 例如:在问答系统任务( ...

最新文章

  1. AI一分钟 | 南大用算法匹配室友,降低矛盾概率;丰田向Uber投5亿美元
  2. 阿里云面向企业效率的云上产品全解析——云呼叫中心
  3. ffmpeg for android windows,windows10下编译ffmpeg for android
  4. 【机器学习基础】使用python实现BP算法
  5. (*长期更新)软考网络工程师学习笔记——Section 15 无线网络技术
  6. 一张图解释什么是遗传算法_遗传算法简介及代码详解
  7. django批量修改table_django-formset实现数据表的批量操作
  8. 2018-1-27:内存的划分
  9. backup1:开始数据库备份
  10. [极客大挑战 2019]BuyFlag
  11. 从对集合数据去重到Distinct源码分析
  12. 【SpringBoot】开启热部署(jsp 页面热部署、devtools 全局热部署)
  13. 海量中文语料上预训练ALBERT模型:参数更少,效果更好
  14. ARM-linux的启动流程
  15. pymongo的常用操作
  16. 农业农村部:非洲猪瘟对猪肉市场价格影响有限
  17. w ndows7旗舰版怎么重装系统,windows7旗舰版iso怎么安装
  18. linux系统怎么关闭屏保,Linux关闭屏保
  19. 学习python记录——第五天
  20. Java黑皮书课后题第2章:2.16(几何:六边形面积)编写程序,提示用户输入六边形边长,然后显示它的面积

热门文章

  1. Redis 如何保持和MySQL数据一致【一】
  2. ionic入门教程第一课--环境搭建和新建ionic项目
  3. Oracle存储过程小解
  4. windows 编程 —— 消息与参数(定时器、初始化消息、改变大小)
  5. 【转】自旋锁及其衍生锁
  6. linux python pip卸载,Python pip的安装及卸载
  7. datatable 能不能有vlookup_Vlookup、Hlookup、Lookup有什么区别?
  8. 【OpenCV入门学习笔记2】:Mat对象
  9. 鸿蒙 电视 安卓,华为鸿蒙2.0、EMUI 11齐发 打通手机、电视、PC全平台
  10. 抖音是用python写的吗_用 Python 实现抖音尬舞机