概念
无监督学习算法包括没有已知的输出,在学习的过程中没有老师指导学习算法,只有依靠输入数据,并需要从这些数据中提取知识。
无监督学习包括了数据集变换与聚类。
什么是数据集变换?
数据集的无监督变换是一种算法,用于创建数据新的表示,这种表示更容易被人或其它机器学习算法所理解。无监督变换常用手段是降维,输入是具有许多特征的高维数据表示,通过算法用较少的特征就可以概括其重要性。常见应用将数据降为可视化的二维表示。
聚类算法是将数据划分为不同的组,每组包含相似的物项。
无监督学习的主要挑战是评估算法是否学习到了有用的东西。无监督学习算法所使用的数据一般不包含任何标签信息。因此不知道正确的输出应该是什么。那么判断一个模型是否 表现很好就很困难。通常来说,评估无监督算法结果的唯一方法是人工检查。
常见应用预处理
无监督算法的另一个常见应用是作为监督算法的预处理步骤,通过学习新的数据标识,有时可以提高监督算法的精度,或者减少内存的占用和时间开销。常见4种预处理:
1 StandardScaler 确保每个特征的平均值为0,方差为1, 使所有特征都位于同一量级。缺点是这种缩放不能保证特征任何特定的值的最大值与最小值。
2 RobustScaler 确保每个特征的统计属性都位于同一范围,与StandardScaler类似,但是使用的是中位数与四分位数,而不是平均值与方差。这种处理可以忽略一些异常值,可能会给其它缩放方法造成麻烦。
3 MinMaxScaler,通过移动数据,使所有特征都刚好位于0到1之间。对于二维数据集来说,所有的数据都包含在x轴0到1与y轴0到1组成的矩形中。
4 Normalizer 用到一种完全不同的缩放方法。通过对每个数据点进行缩放,使得特征向量的欧式长度等于1.也就是将数据点投射到半径为1的圆上(更高维度就是球面)这种处理在只有数据的方向是重要的,而特征向量的长度无关紧要时,就很适合这种归一化处理。
下面我们来看一下MinMaxScaler的预处理的效果,以及处理后的数据在svm算法应用上的前后对比效果:
scss
def viewMinMaxScaleProcessDataChange():
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("train feature minimum before scaling:\b{}".format(X_train.min(axis=0)))
print("train feature maximum before scaling:\b{}".format(X_train.max(axis=0)))
print("train feature minimum after scaling:\b{}".format(X_train_scaled.min(axis=0)))
print("train feature maximum after scaling:\b{}".format(X_train_scaled.max(axis=0)))
# MinMaxScaler(以及其他所有缩放器)总是对训练集和测试集应用完全相同的变换。也就是说,transform方法总是减去训练集的最小值,然后除以训练集的范围,
# 而这两个值可能与测试集的最小值和范围并不相同。
X_test_scaled = scaler.transform(X_test)
# 在缩放之后打印测试数据的属性
print("test feature minimum before scaling:\b{}".format(X_test.min(axis=0)))
print("test feature maximum before scaling:\b{}".format(X_test.max(axis=0)))
print("test feature minimum after scaling:\b{}".format(X_test_scaled.min(axis=0)))
print("test feature maximum after scaling:\b{}".format(X_test_scaled.max(axis=0)))
def MinMaxScaleOnSvm():
cancer = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, random_state=0)
svm = SVC(C=100, gamma='auto')
svm.fit(X_train, y_train)
print("Test accuracy:{:.2f}".format(svm.score(X_test, y_test)))
scaler = MinMaxScaler()
scaler.fit(X_train)
X_train_scaled = scaler.transform(X_train)
X_test_scaled = scaler.transform(X_test)
svm.fit(X_train_scaled, y_train)
print("Scaled test set accuracy:{:.2f}".format(svm.score(X_test_scaled, y_test)))
if __name__ == '__main__':
viewMinMaxScaleProcessDataChange()
MinMaxScaleOnSvm()
下面是执行后的结果,可以看到原来很大的数据在经过处理后,值的范围在0到1之间. 此外,svm算法对数据的释放是敏感的,经过缩放后的数据,在svm的学习效果上从0.63提升到了0.97,效果非常显著。
css
train 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]
train 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]
train 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.]
train 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.]
test feature minimum before scaling[7.691e+00 1.038e+01 4.834e+01 1.704e+02 6.828e-02 3.116e-02 0.000e+00 0.000e+00 1.365e-01 4.996e-02 1.115e-01 3.871e-01 8.484e-01 7.228e+00 2.866e-03 3.746e-03 0.000e+00 0.000e+00 7.882e-03 1.087e-03 8.678e+00 1.420e+01 5.449e+01 2.236e+02 8.774e-02 5.131e-02 0.000e+00 0.000e+00 1.565e-01 5.504e-02]
test feature maximum before scaling[2.722e+01 3.381e+01 1.821e+02 2.250e+03 1.425e-01 3.454e-01 3.754e-01 1.878e-01 2.906e-01 9.744e-02 1.292e+00 2.612e+00 1.012e+01 1.587e+02 1.604e-02 1.006e-01 3.038e-01 3.322e-02 7.895e-02 1.220e-02 3.312e+01 4.178e+01 2.208e+02 3.216e+03 2.098e-01 1.058e+00 1.252e+00 2.688e-01 6.638e-01 2.075e-01]
test 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]
test 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]
Test accuracy:0.63
Scaled test set accuracy:0.97
PCA、NMF、t-SNE算法
利用无监督学习进行数据变换的最常见目的是可视化,压缩数据,以及寻找信息量更大的数据表示以用于进一步的处理。为了实现上述目的,最常用的算法是主成分分析(principal component analysis, PCA)。
PCA是一种旋转数据集的方法,旋转后的特征在统计上不相关,做完旋转之后,通常根据新特征对解释数据的重要性来选择一个子集。
算法首先找到方差最大的方向,将其标记为"成分1"(Component 1)。这是数据中包含最多信息的方向(或向量),换句话说,沿着这个方向的特征之间最为相关。然后,算法找到与第一个方向正交(成直角)且包含最多信息的方向. 利用这一过程找到的方向被称为主成分(principal component),因为它们是数据方差的主要方向。一般来说,主成分的个数与原始特征相同。
PCA的优缺点: 通过仅保留一部分主成分来进行降维。 也可以找到一种数据表示,比给定的原始表示更适合于分析,可以用于特征提取。缺点是通常不容易对图中的两个轴做出解释。主成分对应于原始数据中的方向,所以它们是原始特征的组合。但这些组合往往非常复杂。
非负矩阵分解:
非负矩阵分解(non-negative matrix factorization, NMF)是一种无监督学习算法. 其目的在于提取有用的特征,可用于降维。 原理是通过试图将每个数据点写成分量的加权求和,但与PCA不同的是,PCA想要的是正交分量,并且能够解释尽可能多的数据方差。但是NMF希望分量和系数都为非负。
为了说明非负矩阵分解算法的应用效果,以比较常见的人脸识别为例子进行说明,首先我们从人脸库中打印一些基本的信息:
python
import matplotlib.pyplot as plt
import numpy as np
from sklearn.datasets import fetch_lfw_people
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.decomposition import PCA
from sklearn.decomposition import NMF
import warnings
warnings.filterwarnings("ignore")
def viewOriginalImage():
people = fetch_lfw_people(min_faces_per_person=20, resize=0.7)
fix, axes = plt.subplots(2, 5, figsize=(15, 8), subplot_kw={'xticks': (), 'yticks': ()})
for target, image, ax in zip(people.target, people.images, axes.ravel()):
ax.imshow(image)
ax.set_title(people.target_names[target])
# plt.show()
print("people.images.shape:{}".format(people.images.shape))
print("Number of classes:{}".format(len(people.target_names)))
# 计算每个目标出现的次数
counts = np.bincount(people.target)
names = people.target_names
for i, (count, name) in enumerate(zip(counts, names)):
print("{0:25} {1:3}".format(name, count), end=' ')
if (i + 1) % 3 == 0:
print()
下面的打印信息显示了一共有3023张人脸特征图片,每张图片有87*65个特征点,这么多图片实际是62个人的照片集。 此外我们打印了每个人具体有多少张特征图片,例如George W Bush这个人有530张对应的照片,估计是个比较牛的人,采集了这么多张照片。
javascript
people.images.shape:(3023, 87, 65)
Number of classes:62
Alejandro Toledo 39 Alvaro Uribe 35 Amelie Mauresmo 21
Andre Agassi 36 Angelina Jolie 20 Ariel Sharon 77
Arnold Schwarzenegger 42 Atal Bihari Vajpayee 24 Bill Clinton 29
Carlos Menem 21 Colin Powell 236 David Beckham 31
Donald Rumsfeld 121 George Robertson 22 George W Bush 530
Gerhard Schroeder 109 Gloria Macapagal Arroyo 44 Gray Davis 26
Guillermo Coria 30 Hamid Karzai 22 Hans Blix 39
Hugo Chavez 71 Igor Ivanov 20 Jack Straw 28
Jacques Chirac 52 Jean Chretien 55 Jennifer Aniston 21
Jennifer Capriati 42 Jennifer Lopez 21 Jeremy Greenstock 24
Jiang Zemin 20 John Ashcroft 53 John Negroponte 31
Jose Maria Aznar 23 Juan Carlos Ferrero 28 Junichiro Koizumi 60
Kofi Annan 32 Laura Bush 41 Lindsay Davenport 22
Lleyton Hewitt 41 Luiz Inacio Lula da Silva 48 Mahmoud Abbas 29
Megawati Sukarnoputri 33 Michael Bloomberg 20 Naomi Watts 22
Nestor Kirchner 37 Paul Bremer 20 Pete Sampras 22
Recep Tayyip Erdogan 30 Ricardo Lagos 27 Roh Moo-hyun 32
Rudolph Giuliani 26 Saddam Hussein 23 Serena Williams 52
Silvio Berlusconi 33 Tiger Woods 23 Tom Daschle 25
Tom Ridge 33 Tony Blair 144 Vicente Fox 32
Vladimir Putin 49 Winona Ryder 24
流形学习算法(manifold learning algorithm): 主要用于可视化很少用来生成两个以上的新特征. 其中特别有用的一个就是t-SNE算法, 该算法只能变换用于训练的数据, 不允许变换新数据,这意味着这些算法不能用于测试集, 因此流形学习对探索性数据分析是很有用的,但如果最终目标是监督学习的话,则很少使用。
t-SNE算法思想: 首先给出每个数据点的随机二维表示,然后尝试让在原始特征空间中距离较近的点更加靠近,原始特征空间中相距较远的点更加远离。 以此找到数据的一个二维表示,尽可能地保持数据点之间的距离。t-SNE重点关注距离较近的点,换句话说,它试图保存那些表示比较靠近的点的信息。
下main我们以数字分类为例,看一下pca与流行学习算法两种无监督学习的预处理算法,在数字分类上的一些效果:
ini
def pcaProcessAndViewer():
digits = load_digits()
# 将64个成分降维到2个
pca = PCA(n_components=2)
pca.fit(digits.data)
digits_pca = pca.transform(digits.data)
# rgb 颜色对应的code
colors = ["#476A2A", "#7851B8", "#BD3430", "#4A2D4E", "#875525", "#A83683", "#4E655E", "#853541", "#3A3120",
"#535D8E"]
plt.figure(figsize=(10, 10))
# 分别设置x轴与y轴的取值范围
plt.xlim(digits_pca[:, 0].min(), digits_pca[:, 0].max())
plt.ylim(digits_pca[:, 1].min(), digits_pca[:, 1].max())
for i in range(len(digits.data)):
# digits_pca[i, 0]作为x轴坐标,digits_pca[i, 1]作为y轴坐标,定位到的这个点的值是digits.target[i],字体是粗体字号为9
plt.text(digits_pca[i, 0], digits_pca[i, 1], str(digits.target[i]), color=colors[digits.target[i]],
fontdict={'weight': 'bold', 'size': 9})
plt.xlabel("First principal component")
plt.xlabel("Second principal component")
plt.show()
从下图可以看出,pca处理后,将 0,6,4三个数字比较好的分类了。但是这三个数字之间相互还是有参杂。
下面将t-SNE应用于同一数据集,并对结果进行比较,可以发现t-SNE的结果非常棒。所有类别都被明确分开。数字1和9被分成几块,但大多数类别都形成一个密集的组。实际上这种方法并不知道类别标签:它完全是无监督的。但它能够找到数据的一种二维表示,仅根据原始空间中数据点之间的靠近程度就能够将各个类别明确分开。
ini
def tSneProcessAndViewer():
digits = load_digits()
tsne = TSNE(random_state=42)
digits_stne = tsne.fit_transform(digits.data)
plt.figure(figsize=(10, 10))
# 分别设置x轴与y轴的取值范围
plt.xlim(digits_stne[:, 0].min(), digits_stne[:, 0].max())
plt.ylim(digits_stne[:, 1].min(), digits_stne[:, 1].max())
colors = ["#476A2A", "#7851B8", "#BD3430", "#4A2D4E", "#875525", "#A83683", "#4E655E", "#853541", "#3A3120",
"#535D8E"]
for i in range(len(digits.data)):
# digits_pca[i, 0]作为x轴坐标,digits_pca[i, 1]作为y轴坐标,定位到的这个点的值是digits.target[i],字体是粗体字号为9
plt.text(digits_stne[i, 0], digits_stne[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")
plt.show()