Github链接:《第3章》
无监督学习的类型与预处理
训练算法时只有输入数据,没有已知输出。
无监督学习类型
无监督变换
数据集的无监督变换,变换后的特征可能更容易被算法理解。
也常用于把高维特征的数据降维为较少特征的数据,带来一个好处就是降维后就能够可视化分析。
聚类算法
该类算法能够把输入数据划分成不同的组,每组包含类似的物项。
无监督学习的挑战
因为训练数据没有分类标签,所以很难用精度去评估模型有效。
因此,无监督算法通常用于探索数据的规律,而不是自动化系统。
无监督算法的另外一个用途是作为监督算法的预处理步骤,之前提到过可以进行特征变换,有时可以提高监督算法的精度、或者减少内存以及计算开销。
预处理与缩放
之前用过的SVM、神经网络对特征缩放非常敏感,需要对原始数据进行预处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
from sklearn.datasets import load_breast_cancer from sklearn.model_selection import train_test_split from sklearn.preprocessing import MinMaxScaler # 数据集 cancer = load_breast_cancer() # 切分 X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, random_state=1) # 先仅仅训练特征缩放器,只需要数据特征, 不需要标签 scaler = MinMaxScaler() scaler.fit(X_train) # 然后对训练集的特征完成实际缩放 X_train_scaled = scaler.transform(X_train) # 打印缩放前和缩放后,训练集中每种特征的最小值和最大值 print("transformed shape: {}".format(X_train_scaled.shape)) print("per-feature minimum before scaling:\n {}".format(X_train.min(axis=0))) print("per-feature maximum before scaling:\n {}".format(X_train.max(axis=0))) print("per-feature minimum after scaling:\n {}".format(X_train_scaled.min(axis=0))) print("per-feature maximum after scaling:\n {}".format(X_train_scaled.max(axis=0))) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
transformed shape: (426, 30) per-feature minimum before scaling: [6.981e+00 9.710e+00 4.379e+01 1.435e+02 5.263e-02 1.938e-02 0.000e+00 0.000e+00 1.060e-01 5.024e-02 1.153e-01 3.602e-01 7.570e-01 6.802e+00 1.713e-03 2.252e-03 0.000e+00 0.000e+00 9.539e-03 8.948e-04 7.930e+00 1.202e+01 5.041e+01 1.852e+02 7.117e-02 2.729e-02 0.000e+00 0.000e+00 1.566e-01 5.521e-02] per-feature maximum before scaling: [2.811e+01 3.928e+01 1.885e+02 2.501e+03 1.634e-01 2.867e-01 4.268e-01 2.012e-01 3.040e-01 9.575e-02 2.873e+00 4.885e+00 2.198e+01 5.422e+02 3.113e-02 1.354e-01 3.960e-01 5.279e-02 6.146e-02 2.984e-02 3.604e+01 4.954e+01 2.512e+02 4.254e+03 2.226e-01 9.379e-01 1.170e+00 2.910e-01 5.774e-01 1.486e-01] per-feature minimum after scaling: [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.] per-feature maximum after scaling: [1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.] |
即经过MinMaxScaler处理后,所有特征的值都缩放到0~1之间。
变换后的数据形状(特征之间的比例)与原始数据相同,特征只是发生了移动与缩放。
对测试集进行相同的缩放,也就是用训练集fit好的scaler在测试集执行,不必担心测试集的特征值范围和训练集不同导致什么问题,保持一致的缩放比例才是正确的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
from sklearn.datasets import load_breast_cancer from sklearn.model_selection import train_test_split from sklearn.preprocessing import MinMaxScaler # 数据集 cancer = load_breast_cancer() # 切分 X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, random_state=1) # 先仅仅训练特征缩放器,只需要数据特征, 不需要标签 scaler = MinMaxScaler() scaler.fit(X_train) # 然后对训练集的特征完成实际缩放 X_train_scaled = scaler.transform(X_train) # 对测试数据进行变换 X_test_scaled = scaler.transform(X_test) # 在缩放之后打印测试数据的属性 print("per-feature minimum after scaling:\n{}".format(X_test_scaled.min(axis=0))) print("per-feature maximum after scaling:\n{}".format(X_test_scaled.max(axis=0))) |
1 2 3 4 5 6 7 8 9 10 11 12 |
per-feature minimum after scaling: [ 0.0336031 0.0226581 0.03144219 0.01141039 0.14128374 0.04406704 0. 0. 0.1540404 -0.00615249 -0.00137796 0.00594501 0.00430665 0.00079567 0.03919502 0.0112206 0. 0. -0.03191387 0.00664013 0.02660975 0.05810235 0.02031974 0.00943767 0.1094235 0.02637792 0. 0. -0.00023764 -0.00182032] per-feature maximum after scaling: [0.9578778 0.81501522 0.95577362 0.89353128 0.81132075 1.21958701 0.87956888 0.9333996 0.93232323 1.0371347 0.42669616 0.49765736 0.44117231 0.28371044 0.48703131 0.73863671 0.76717172 0.62928585 1.33685792 0.39057253 0.89612238 0.79317697 0.84859804 0.74488793 0.9154725 1.13188961 1.07008547 0.92371134 1.20532319 1.63068851] |
先在原始数据上训练SVC模型,看一下精度:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
from sklearn.datasets import load_breast_cancer from sklearn.model_selection import train_test_split from sklearn.svm import SVC # 数据集 cancer = load_breast_cancer() # 切分 X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, random_state=1) # 训练SVC分类器 svm = SVC(C=100) svm.fit(X_train, y_train) print("Test set accuracy: {:.2f}".format(svm.score(X_test, y_test))) |
1 |
Test set accuracy: 0.62 |
测试集精度只有0.62。
再试试缩放后的特征作为输入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
from sklearn.datasets import load_breast_cancer from sklearn.model_selection import train_test_split from sklearn.preprocessing import MinMaxScaler # 数据集 cancer = load_breast_cancer() # 切分 X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, random_state=1) # 先仅仅训练特征缩放器,只需要数据特征, 不需要标签 scaler = MinMaxScaler() scaler.fit(X_train) # 然后对训练集的特征完成实际缩放 X_train_scaled = scaler.transform(X_train) # 对测试数据进行变换 X_test_scaled = scaler.transform(X_test) # 训练SVC svm = SVC(C=100) svm.fit(X_train_scaled, y_train) print("Test set accuracy: {:.2f}".format(svm.score(X_test_scaled, y_test))) |
1 |
Test set accuracy: 0.97 |
精度高达0.97。
也可以尝试其他的预处理缩放算法替换MinMaxScaler,比如:StandardScaler。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
from sklearn.datasets import load_breast_cancer from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler # 数据集 cancer = load_breast_cancer() # 切分 X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, random_state=1) # 先仅仅训练特征缩放器,只需要数据特征, 不需要标签 scaler = StandardScaler() scaler.fit(X_train) # 然后对训练集的特征完成实际缩放 X_train_scaled = scaler.transform(X_train) # 对测试数据进行变换 X_test_scaled = scaler.transform(X_test) # 训练SVC svm = SVC(C=100) svm.fit(X_train_scaled, y_train) print("Test set accuracy: {:.2f}".format(svm.score(X_test_scaled, y_test))) |
1 |
Test set accuracy: 0.97 |
精度也很棒。
降维、特征提取与流行学习
主成分分析
PCA可以从已有特征产生出新特征,新特征对解释数据更好用。
PCA可以指定提取的特征个数,实现降维的目的。
可视化用途
下面对cancer数据集利用PCA从30个维降到2个维度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
from sklearn.decomposition import PCA from sklearn.datasets import load_breast_cancer from sklearn.preprocessing import StandardScaler # 数据集 cancer = load_breast_cancer() # 标准化 scaler = StandardScaler() scaler.fit(cancer.data) X_scaled = scaler.transform(cancer.data) # 降维到2的PCA pca = PCA(n_components=2) # 训练PCA pca.fit(X_scaled) # 进行特征提取变换 X_pca = pca.transform(X_scaled) # 降维前的特征个数 print("Original shape: {}".format(str(X_scaled.shape))) # 降维后的特征个数 print("Reduced shape: {}".format(str(X_pca.shape))) |
1 2 |
Original shape: (569, 30) Reduced shape: (569, 2) |
因为PCA后只有2个特征维度,所以可以绘图做可视化分析。
主成分提取用途
图像分类场景,因为色值的范围是0~255,为了应用模型我们手动对特征/255缩放到0~1之间,看一下模型效果:
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 31 32 33 34 35 36 37 38 |
from sklearn.datasets import fetch_lfw_people import numpy as np from sklearn.neighbors import KNeighborsClassifier from sklearn.model_selection import train_test_split # 加载数据 people = fetch_lfw_people(min_faces_per_person=20, resize=0.7) # 3023张人脸照片作为样本输入 print("people.images.shape: {}".format(people.images.shape)) # 3023张图片对应的人名 print("Number of classes: {}".format(len(people.target_names))) # 3023列的false向量 mask = np.zeros(people.target.shape, dtype=np.bool) # 遍历所有的分类(也就是人名,target取值是0~61) # 每个分类保留50个样本 for target in np.unique(people.target): mask[np.where(people.target == target)[0][:50]] = True X_people = people.data[mask] # data是已经把每张图片n*m的2维压成1维的数据格式 y_people = people.target[mask] # 现在留下的样本,每个人不会超过50张 # 特征缩放到0~1之间, 因为是RGB颜色 X_people = X_people / 255 # 切分数据 #print(X_people, y_people) X_train, X_test, y_train, y_test = train_test_split( X_people, y_people, stratify=y_people, random_state=0) # KNN分类, 最近邻分类(只考虑最近的节点) knn = KNeighborsClassifier(n_neighbors=1) knn.fit(X_train, y_train) print(knn.score(X_test, y_test)) |
1 |
0.23255813953488372 |
精度不高。
KNN主要是依据点之间的距离进行计算的,而2张人脸图片对应某个位置像素之间求距离是没有什么明显意义的。
此时我们可以考虑利用PCA提取主成分,作为100个新特征输入到模型。
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
from sklearn.datasets import fetch_lfw_people import numpy as np from sklearn.neighbors import KNeighborsClassifier from sklearn.model_selection import train_test_split from sklearn.decomposition import PCA # 加载数据 people = fetch_lfw_people(min_faces_per_person=20, resize=0.7) # 3023张人脸照片作为样本输入 print("people.images.shape: {}".format(people.images.shape)) # 3023张图片对应的人名 print("Number of classes: {}".format(len(people.target_names))) # 3023列的false向量 mask = np.zeros(people.target.shape, dtype=np.bool) # 遍历所有的分类(也就是人名,target取值是0~61) # 每个分类保留50个样本 for target in np.unique(people.target): mask[np.where(people.target == target)[0][:50]] = True X_people = people.data[mask] # data是已经把每张图片n*m的2维压成1维的数据格式 y_people = people.target[mask] # 现在留下的样本,每个人不会超过50张 # 特征缩放到0~1之间, 因为是RGB颜色 X_people = X_people / 255 # 切分数据 #print(X_people, y_people) X_train, X_test, y_train, y_test = train_test_split( X_people, y_people, stratify=y_people, random_state=0) # PCA提取100个主成分作为新的特征, 基于训练集fit, 应用到训练集和测试集 pca = PCA(n_components=100, whiten=True, random_state=0).fit(X_train) X_train_pca = pca.transform(X_train) X_test_pca = pca.transform(X_test) print("X_train_pca.shape: {}".format(X_train_pca.shape)) # KNN分类, 最近邻分类(只考虑最近的节点) knn = KNeighborsClassifier(n_neighbors=1) knn.fit(X_train_pca, y_train) # 精度 print(knn.score(X_test_pca, y_test)) |
1 |
0.312015503875969 |
PCA通过,带来了精度提升。
非负矩阵分解
NFM和PCA类似,用于提取有用特征、也用于降维,但是要求所有特征非负。
具体例子如下,但效果并没有更好:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
from sklearn.datasets import fetch_lfw_people import numpy as np from sklearn.neighbors import KNeighborsClassifier from sklearn.model_selection import train_test_split from sklearn.decomposition import PCA from sklearn.decomposition import NMF # 加载数据 people = fetch_lfw_people(min_faces_per_person=20, resize=0.7) # 3023张人脸照片作为样本输入 print("people.images.shape: {}".format(people.images.shape)) # 3023张图片对应的人名 print("Number of classes: {}".format(len(people.target_names))) # 3023列的false向量 mask = np.zeros(people.target.shape, dtype=np.bool) # 遍历所有的分类(也就是人名,target取值是0~61) # 每个分类保留50个样本 for target in np.unique(people.target): mask[np.where(people.target == target)[0][:50]] = True X_people = people.data[mask] # data是已经把每张图片n*m的2维压成1维的数据格式 y_people = people.target[mask] # 现在留下的样本,每个人不会超过50张 # 特征缩放到0~1之间, 因为是RGB颜色 X_people = X_people / 255 # 切分数据 #print(X_people, y_people) X_train, X_test, y_train, y_test = train_test_split( X_people, y_people, stratify=y_people, random_state=0) # PCA提取100个特征, 作为新的特征, 基于训练集fit, 应用到训练集和测试集 nmf = NMF(n_components=15, random_state=0) nmf.fit(X_train) X_train_nmf = nmf.transform(X_train) X_test_nmf = nmf.transform(X_test) print("X_train_nmf.shape: {}".format(X_train_pca.shape)) # KNN分类, 最近邻分类(只考虑最近的节点) knn = KNeighborsClassifier(n_neighbors=1) knn.fit(X_train_nmf, y_train) # 精度 print('精度:', knn.score(X_test_nmf, y_test)) |
1 2 3 4 |
people.images.shape: (3023, 87, 65) Number of classes: 62 X_train_nmf.shape: (1547, 100) 精度: 0.1686046511627907 |
PCA通常是数据变换的首选方法。
t-SNE流行学习
用于将高维数据降到2维,新特征能够根据原始数据中数据点之间的远近程度将不同类比明确分开。
在可视化用途比PCA更有效,但只能用于做可视化,无法像PCA一样应用到测试集上。
举个栗子即可。
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 |
from sklearn.datasets import load_digits from sklearn.manifold import TSNE import matplotlib.pyplot as plt # 数据集 digits = load_digits() # 降维 tsne = TSNE(random_state=42) # 使用fit_transform而不是fit,因为TSNE没有transform方法 # 处理后的只剩余2个特征 digits_tsne = tsne.fit_transform(digits.data) print(digits_tsne.shape) # 图尺寸 plt.figure(figsize=(10, 10)) # x坐标的范围是第0个特征的最小和最大值 plt.xlim(digits_tsne[:, 0].min(), digits_tsne[:, 0].max() + 1) # y坐标的范围是第1个特征的最小和最大值 plt.ylim(digits_tsne[:, 1].min(), digits_tsne[:, 1].max() + 1) # 绘制所有TSNE转换后的数据点 for i in range(len(digits.data)): # 将数据实际绘制成数字的文本 plt.text(digits_tsne[i, 0], digits_tsne[i, 1], str(digits.target[i]), color = colors[digits.target[i]], fontdict={'weight': 'bold', 'size': 9}) plt.xlabel("t-SNE feature 0") plt.xlabel("t-SNE feature 1") |
1 |
(1797, 2) |
聚类
聚类(clustering)是将数据集划分成组的任务,这些组叫做簇。
聚类算法给每个数据点分配一个数字,表示数据点属于哪个簇。
k均值聚类
算法通过不断迭代找到几个簇中心,并将每个数据点分配给最近的簇中心。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
from sklearn.datasets import make_blobs from sklearn.cluster import KMeans # 生成模拟的二维数据 X, y = make_blobs(random_state=1) # 构建聚类模型, 指定3个簇中心 kmeans = KMeans(n_clusters=3) kmeans.fit(X) # 打印每个数据所属的簇标签, 因为n_clusters=3所以就是0~2 print(kmeans.labels_) # 也支持predict方法来计算一个新数据点属于哪个簇标签 print(kmeans.predict(X)) |
1 2 3 4 5 6 |
[0 1 1 1 2 2 2 1 0 0 1 1 2 0 2 2 2 0 1 1 2 1 2 0 1 2 2 0 0 2 0 0 2 0 1 2 1 1 1 2 2 1 0 1 1 2 0 0 0 0 1 2 2 2 0 2 1 1 0 0 1 2 2 1 1 2 0 2 0 1 1 1 2 0 0 1 2 2 0 1 0 1 1 2 0 0 0 0 1 0 2 0 0 1 1 2 2 0 2 0] [0 1 1 1 2 2 2 1 0 0 1 1 2 0 2 2 2 0 1 1 2 1 2 0 1 2 2 0 0 2 0 0 2 0 1 2 1 1 1 2 2 1 0 1 1 2 0 0 0 0 1 2 2 2 0 2 1 1 0 0 1 2 2 1 1 2 0 2 0 1 1 1 2 0 0 1 2 2 0 1 0 1 1 2 0 0 0 0 1 0 2 0 0 1 1 2 2 0 2 0] |
簇标签没有先验意义,我们并不知道每一个簇代表什么,只能人为观察。
kmeans类似于PCA可以进行特征变换,首先kmeans进行fit找到所有簇中心后,进而对训练集/测试集特征进行transform,从而将原始数据点变换为到各个簇中心的距离,作为新的特征。
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 |
from sklearn.datasets import make_blobs from sklearn.cluster import KMeans from sklearn.model_selection import train_test_split from sklearn.datasets import make_moons from sklearn.ensemble import GradientBoostingClassifier # 数据集 X, y = make_moons(n_samples=200, noise=0.05, random_state=0) # 切分 X_train, X_test, y_train, y_test = train_test_split(X, y) # 分成10簇 kmeans = KMeans(n_clusters=10, random_state=0) kmeans.fit(X_train) # 转换原有特征为到簇中心的距离 train_distance_features = kmeans.transform(X_train) test_distance_features = kmeans.transform(X_test) # 跑模型 gbdt = GradientBoostingClassifier() gbdt.fit(train_distance_features, y_train) # 精度 print(gbdt.score(test_distance_features, y_test)) |
1 |
0.98 |
Kmeans最初的簇中心是随机产生的,算法输出依赖于随机种子,sklearn会默认跑10次选最好的1次。另外,kmeans对簇形状有假设,人工确定簇个数是很难琢磨的。
凝聚聚类
算法首先声明每个点是自己的簇,然后不断合并相似的簇,直到簇数量达到目标。
度量应该合并哪2个簇有3种算法:
- ward
- average
- complete
ward适合大多数数据集,它适合每个簇数据点个数相当的情况,否则其他2个效果更好。
因为算法是不断合并簇的逻辑,所以它对新数据无法做出所属簇的预测。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
from sklearn.datasets import make_blobs from sklearn.cluster import AgglomerativeClustering from sklearn.model_selection import train_test_split from sklearn.datasets import make_moons import mglearn # 数据集 X, y = make_blobs(random_state=1) # 凝聚聚类为3个簇 agg = AgglomerativeClustering(n_clusters=3) assignment = agg.fit_predict(X) # 输出数据点分布以及所属簇 mglearn.discrete_scatter(X[:, 0], X[:, 1], assignment) plt.xlabel("Feature 0") plt.ylabel("Feature 1") |
DBSCAN聚类
DBSCAN 的主要优点是它不需要用户先验地设置簇的个数,可以划分具有复杂形状的簇,还可以找出不属于任何簇的点,DBSCAN 也不允许对新的测试数据进行预测。
聚类算法的对比与评估
这一段太晦涩了,不看了。
小结
无监督学习可以用于探索性的数据分析与预处理。
预处理以及分解方法在数据准备中具有重要作用。
通常来说,很难量化无监督算法的有用性,但这不应该妨碍你使用它们来深入理解数据。学完这些方法,你就已经掌握了机器学习从业者每天使用的所有必要的学习算法。
如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~
