Scott's Blog

学则不固, 知则不惑

0%

非监督学习:Clustering and Visualization

非监督学习:即根据现有的数据去发掘存在数据中的一些模式,比如根据用户的购买记录,(clustering) 定义用户画像, 或根据数据的模式来压缩数据(dimension reduction.

非监督学习

非监督学习介绍

相比较监督学习,如对癌症良性(benign)与恶性(cancerous)的分类,在这个例子中,我们发现的模式是: guided,或者说是: supervised,这一过程依赖我们标记过的数据,而非监督学习不需要我们使用标记过的数据去发现这些模式,我们不需要去guide the machine。

聚类与数据探索

iris 数据集

iris数据集包括了三种植物,它们有四种衡量方式:

  • petal width
  • petal length
  • sepal width
  • sepql length

这些叫做数据的 features ,在这门课程中,像这样的课程将会将数据集定义为2维数组,列定义衡量标准( the features ),行代表一个样例( a sample ).

我们的数据集有四个衡量方式,对应一个四维数组,事实上你可以推算出来:

\[Dimension=number\ of\ features\]

我们无法直接对四维数据可视化,但我们可以使用非监督学习技术来从其中获取想要的信息。

这一章,我们会使用 k-means 聚类算法将所有的样例数据分类。

k-means算法会从数据集中发现特定的分类,即把n个点(可以是样本的一次观察或一个实例)划分到k个聚类中,使得每个点都属于离它最近的均值(此即聚类中心)对应的聚类,以之作为聚类的标准。

k-means算法在sklearn中有实现,我们来看一个示例:

1
2
3
4
5
6
from sklearn.cluster import KMeans
modal = KMeans(n_clusters=3)

modal.fit(samples)

labels = model.predit(samples)
  1. 导入kmeans包
  2. 实例化,并指定你想要从数据中发现的聚类数量(n_clusters)
  3. fit sample数据
  4. 用同样的数据做预测(演示需要),这会返回所有的sample,同时其中包含它所属的聚类。

这里有k-means算法的动画演示,看一下你就明白啦,另外一个油管的视频版

一个简单的例子

我们的数据:

我们的数据 points 是一个300行,2列的数据集。

1
2
3
4
5
6
array([[ 0.06544649, -0.76866376],
[-1.52901547, -0.42953079],
[ 1.70993371, 0.69885253],
[ 1.16779145, 1.01262638],
[-1.80110088, -0.31861296],
[-1.63567888, -0.02859535])

数据集的分布:

我们会把数据集中的第一列作为 x,第二列作为 y,使用数组的切片分别将 x,y 部分的值赋给xs,ys.

1
2
xs = points[:,0]
ys = points[:1]

有了数据之后,我们就可以对模型进行初始化与训练了,我们初始化Kmeans实例,并将数据整个传给它进行训练,训练之后我们用新准备的数据new_points来预测它的性能。

model预测后的结果保存在labels中,这是对new_points数据的预测,labels的数据只有一列,保存着分类的情况,它跟我们的数组new_points对应的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Import KMeans
from sklearn.cluster import KMeans

# Create a KMeans instance with 3 clusters: model
model = KMeans(n_clusters=3)

# Fit model to points
model.fit(points)

# Determine the cluster labels of new_points: labels
labels = model.predict(new_points)

# Print cluster labels of new_points
print(labels)

因为预测的数据跟原始数据是对应的,所以我们以数据中的第一列作为x,第二列作为y,绘制scatter图,然后指定scatter的颜色参数c为labels,这样就可以在数据可视化的时候将不同聚类的数据分开表示。

聚类的中心:centroids

不同的聚类,它们都有自己的中心点,这个中心点可以直接通过模型的属性 model.cluster_centers_ 找到。我们的模型找到了几个聚类,cluster_centers_就有几组数据,下面是3个n_clusters的例子:

1
2
3
4
array([[-1.57568905, -0.22531944],
[ 0.18034887, -0.81701955],
[ 1.01378685, 0.98288627]])

我们将聚类后的数据与centroids都绘出来的效果:

选择 n_clusters(inertia)

如何选择n_clusters,一个好的聚合器,需要尽可能的使不同聚类的数量少,同时聚类中的数据集与centroids的距离最近。

聚类器有一个参数专门来描述这种关系,即 ‌inertia_, 我们可以对同一个数据变化不同的n_clusters来查看它的inertia_的变化:

在这里例子中,可以看到k从1-3一直是急剧下降的,随后开始变得平缓,我们一般选择图像从急剧下降到变的平缓之间的这个值,这个例子中,3是不错的选择。

缩放数据与pipline

我们有一组这样的数据:

1
[242.0, 23.2, 25.4, 30.0, 38.4, 13.4]

每一列是features,对数据集进行聚类,但是发现效果并不理想,原因是这些列之间的方差很大,我们需要将其标准化到同一个维度,如0,1或者-1,1,这个步骤叫 ‌Scaling

我们使用 ‌StandardScaler 来缩放数据,另外这里我们使用管道来将缩放与kmeans的步骤结合到一起,关于pipline的介绍,请参考之前的文章。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 导入数据
import pandas as pd
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans

# 创建scaler
scaler = StandardScaler()

# 创建 kmeans 实例
kmeans = KMeans(n_clusters=4)

# 创建管道 pipeline
pipeline = make_pipeline(scaler,kmeans)


# 对管道fit数据
pipeline.fit(samples)

# 计算 labels
labels = pipeline.predict(samples)

# 根据 labels 和 species 组合成dataframe
df = pd.DataFrame({'labels':labels,'species':species})

# 计算 crosstab
ct = pd.crosstab(df['labels'],df['species'])

# 打印 ct
print(ct)

如果你对 crosstab 函数不是很了解,这里pandas 中关于crosstab 函数与 pivot_table 函数的对比。

根据股价变动聚类股票

我们这里有一份股价数据,它的shape为:

1
2
In [2]: movements.shape
Out[2]: (60, 963)

其中每一行是一个公司的数据,对于 movements 其中的每一行数据,长度为963:

1
2
In [5]: len(movements[0])
Out[5]: 963

其记录的是这个公司每天的股价波动情况。

我们前面处理的数据,在缩放的时候,我们使用的是 ‌StandardScaler, standardscaler缩放的方式是移除所有数据的平均值然后缩放到一个维度,但对于股价来说,我们使用 ‌Normalizer,它会针对每个公司的股价分别缩放,彼此独立。

缩放数据并创建管道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Import Normalizer
from sklearn.preprocessing import Normalizer

# Create a normalizer: normalizer
normalizer = Normalizer()

# Create a KMeans model with 10 clusters: kmeans
kmeans = KMeans(n_clusters=10)

# Make a pipeline chaining normalizer and kmeans: pipeline
pipeline = make_pipeline(normalizer,kmeans)

# Fit pipeline to the daily price movements
pipeline.fit(movements)

聚类股票

1
2
3
4
5
6
7
8
9
10
11
# Import pandas
import pandas as pd

# Predict the cluster labels: labels
labels = pipeline.predict(movements)

# Create a DataFrame aligning labels and companies: df
df = pd.DataFrame({'labels': labels, 'companies': companies})

# Display df sorted by cluster label
print(df.sort_values(by='labels'))

这里的companies数据,是在导入数据之前就剔出来了的。

可视化层次结构

对于那些没有技术背景的人来说,最好使用图表表达你的发现。对于非监督学习这部分,我们学习两种可视化技术:

  • t-SNE
  • Hierarchical clustring

我们先介绍 Hierarchical clustring

Hierarchical clustring

Hierarchical clustring 的原理是,我们处理数据时,第一次使用比较大的 ‌n_clusters,随后慢慢缩小,到最后只剩一个 ‌n_clusters

为了绘制Hierarchical clustring,我们需要的包主要是 linkage,dendrogram.

  • linkage: 负责计算层次聚类信息
  • dendrogram: 负责可视化

下面我们以一份谷物数据举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Perform the necessary imports
from scipy.cluster.hierarchy import linkage, dendrogram
import matplotlib.pyplot as plt

# Calculate the linkage: mergings
mergings = linkage(samples,method='complete')

# Plot the dendrogram, using varieties as labels
dendrogram(mergings,
labels=varieties,
leaf_rotation=90,
leaf_font_size=6,
)
plt.show()

股票的层次化示例

1
2
3
4
5
6
7
8
9
10
11
12
# Import normalize
from sklearn.preprocessing import normalize

# Normalize the movements: normalized_movements
normalized_movements = normalize(movements)

# Calculate the linkage: mergings
mergings = linkage(normalized_movements,method='complete')

# Plot the dendrogram
dendrogram(mergings,labels=companies,leaf_rotation=90,leaf_font_size=6)
plt.show()

这里我们来对比下标准化与无标准化的结果,即linkage(normalized_movements,method='complete')linkage(movements,method='complete')的区别:

not normalized
normalized

intermediate clustering

dendrogram的图是有高度的,它的高度值描述的是两个features之间的距离,chevron与exxon的距离是它们两者merge的时候那个柱子的高度,也就是这两者之间的距离,在这个层面,它们之间的距离是最短的。

而看整个图像的话,图像最高的部分发生的合并,则说明它们之间的距离是最远的。

当我们在计算层次聚类信息的时候,linkage方法中有一个method,我们把它指定为 complete,它的意思是我们不会限定 features之间的距离,我需要计算所有features之间的距离,不管他们多远;如果我在这里作出限制,比如15,那么两个features之间距离超过15的部分我就忽略了。

怎么从一堆数据集中,根据 features之间的距离 来筛选数据呢?我们需要 fcluster 方法。

我们看看设定高度为 15 的情况:

1
2
3
4
5
6
7
8
9
10
11
# Perform the necessary imports
import matplotlib.pyplot as plt
from scipy.cluster.hierarchy import linkage, dendrogram

# Calculate the linkage: mergings
mergings = linkage(samples,method='single')

# Plot the dendrogram
dendrogram(mergings,labels=country_names,leaf_rotation=90,leaf_font_size=6)
plt.show()

下面是fcluster方法使用示例,筛选高度为6以内的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Perform the necessary imports
import pandas as pd
from scipy.cluster.hierarchy import fcluster

# Use fcluster to extract labels: labels
labels = fcluster(mergings,6,criterion='distance')

# Create a DataFrame with labels and varieties as columns: df
df = pd.DataFrame({'labels': labels, 'varieties': varieties})

# Create crosstab: ct
ct = pd.crosstab(df['labels'],df['varieties'])

# Display ct
print(ct)

t-SNE

t-sne算法的原理就是把高维度的数据转化成2维或者3维的,所以它是一种用于降维的机器学习方法。当我们想要对高维数据进行分类,又不清楚这个数据集有没有很好的可分性(即同类之间间隔小,异类之间间隔大),可以通过t-SNE投影到2维或者3维的空间中观察一下。

  • SNE构建一个高维对象之间的概率分布,使得相似的对象有更高的概率被选择,而不相似的对象有较低的概率被选择。
  • SNE在低维空间里在构建这些点的概率分布,使得这两个概率分布之间尽可能的相似。

我们看到t-SNE模型是非监督的降维,他跟kmeans等不同,他不能通过训练得到一些东西之后再用于其它数据(比如kmeans可以通过训练得到k个点,再用于其它数据集,而t-SNE只能单独的对数据做操作,也就是说他只有fit_transform,而没有fit操作)

参考 数据降维与可视化——t-SNE 以及 t-SNE完整笔记

t-sne中有两个地方值得注意的,一个是fit_transform方法,它的作用是将X投影到一个嵌入空间并返回转换结果,另一个是learning_rate,它是学习率,建议取值为10.0-1000.0。

t-sne生成的数据是不一样的,尽管你绘图的代码是一样的。

TSNE对于谷物数据的可视化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Import TSNE
from sklearn.manifold import TSNE

# Create a TSNE instance: model
model = TSNE(learning_rate=200)

# Apply fit_transform to samples: tsne_features
tsne_features = model.fit_transform(samples)

# Select the 0th feature: xs
xs = tsne_features[:,0]

# Select the 1st feature: ys
ys = tsne_features[:,1]

# Scatter plot, coloring by variety_numbers
plt.scatter(xs,ys,c=variety_numbers)
plt.show()

TSNE对于股票数据的可视化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Import TSNE
from sklearn.manifold import TSNE

# Create a TSNE instance: model
model = TSNE(learning_rate=50)

# Apply fit_transform to normalized_movements: tsne_features
tsne_features = model.fit_transform(normalized_movements)

# Select the 0th feature: xs
xs = tsne_features[:,0]

# Select the 1th feature: ys
ys = tsne_features[:,1]

# Scatter plot
plt.scatter(xs,ys,alpha=0.5)

# Annotate the points
for x, y, company in zip(xs, ys, companies):
plt.annotate(company, (x, y), fontsize=5, alpha=0.75)
plt.show()