降维
PCA
主成分分析,识别最靠近数据的超平面,将数据投影到其上。需要选择正确的投影超平面,保留最大的差异性(可以是投影后和原始数据之间的均方差)。让丢失的信息更少。
主成分分析就可以再训练集中识别出那条轴对差异性的贡献度最高(通过SVD 分解)。确定了主成分后就可以将数据集进行投影到前d 个轴上,从而将数据集降到d 维。
from sklearn.decomposition import PCA
pca = PCA(n_components = 2) # 将数据集降到2维,会自动处理数据居中的问题。
X2D = pca.fit_transform(X)
pca.explained_variance_ratio_ # 可解释方差比
array([0.84248607, 0.14631839]) # 表示数据集方差的 84%位于第一个PC(轴) 上,14% 位于第二个PC 上
pca = PCA() # 不进行降维,只是选择足够大的方差部分的维度
pca.fit(X_train)
cumsum = np.cumsum(pca.explained_variance_ratio_) # 累加
d = np.argmax(cumsum >= 0.95) + 1 # 保留95%训练集方差所需的最小维度
# 然后可以 pca = PCA(n_components = d ) 在进行一次
pca = PCA(n_components=0.95) # 也可以直接设置0-1 的浮点数表示要保留的方差率,而不是指定要保留的PC
X_reduced = pca.fit_transform(X_train)
降维后,训练集占用的空间就会少很多。比如MNIST 数据集,本来784 个特征,经过降维保留95的方差就只剩 154 个特征了。这样可以极大的加速分类算法。
通过应用逆变换还可以将数据集解压回原来的 784维,虽然已经丢失 了一些信息,但会尽可能的接近原始数据。原始数据和重构数据之间的 均方距离称为重构误差。
pca = PCA(n_components = 154) # 压缩
X_reduced = pca.fit_transform(X_train)
X_recovered = pca.inverse_transform(X_reduced) # 解压,数字的形状仍是保存完好。
# 随机PCA
# svd_solver = 'randomized' 就采用了随机PCA 的算法,比较SVD 的方法,当目标维度远比原维度高时,这个更快
rnd_pca = PCA(n_components=154, svd_solver="randomized", random_state=42)
X_reduced = rnd_pca.fit_transform(X_train) # 默认 auto 自动选择,full 强制使用SVD
# 增量PCA
# 让训练集划分维多个小批量,逐步送人算法,这对于大型数据集和在线应用很有用
from sklearn.decomposition import IncrementalPCA
n_batches = 100 # 100 个批次
inc_pca = IncrementalPCA(n_components=154) # 降维到154
for X_batch in np.array_split(X_train, n_batches): # 切分数据集
print(".", end="") #
inc_pca.partial_fit(X_batch) # 每个小批量调用 partial_fit() not fit()
X_reduced = inc_pca.transform(X_train)
# 还可以使用np。memmap()
# 将存储再磁盘上的二进制文件中的大型数组当作完全时再内存中一样来操作
filename = "my_mnist.data"
m, n = X_train.shape
X_mm = np.memmap(filename, dtype='float32', mode='write', shape=(m, n))
batch_size = m // n_batches
inc_pca = IncrementalPCA(n_components=154, batch_size=batch_size)
inc_pca.fit(X_mm) # IPCA 再时间内仅使用数组的一小部分,内存是受控的,这就可以使用fit() 了,。。
内核技术是将实例映射到高维特征空间,从而可以使用SVM 来进行分线性分类会回归。高维特种空间的线性决策边界再低维中就是复杂的非线性决策边界。
应用到PCA 中,执行复杂的非线性投影来降低维度,就是内核PCA(KPCA) 了。
from sklearn.decomposition import KernelPCA
rbf_pca = KernelPCA(n_components = 2, kernel="rbf", gamma=0.04) # 使用rbf 内核
X_reduced = rbf_pca.fit_transform(X)
kPCA 是一种无监督学习,没有明显的性能指标来选择超参数和内核。也就是说降维通常是有监督学习的数据准备步骤,可以使用网格搜索的方式选择性能最佳的内核和超参数。
from sklearn.model_selection import GridSearchCV
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
clf = Pipeline([
("kpca", KernelPCA(n_components=2)), # 降低到二维
("log_reg", LogisticRegression(solver="lbfgs")) # 逻辑回归分类
])
param_grid = [{
"kpca__gamma": np.linspace(0.03, 0.05, 10),
"kpca__kernel": ["rbf", "sigmoid"]
}]
grid_search = GridSearchCV(clf, param_grid, cv=3) # 网格搜索最佳内核和gamma
grid_search.fit(X, y)
print(grid_search.best_params_)
LLE
局部线性嵌入是另一种非线性的降维技术。工作原理:首先测量每个训练实例如何与最近的邻居线性相关,然后寻找可以最好的保留这些局部关系的训练集的局部表示形式。适合与展开扭曲的流形。
from sklearn.manifold import LocallyLinearEmbedding
# 每个实例的 k 个最近的邻居
lle = LocallyLinearEmbedding(n_components=2, n_neighbors=10, random_state=42)
X_reduced = lle.fit_transform(X)
无监督学习技术
聚类
识别相似实例并将其分配给相似实例的集群或组。与分类不同的就是没有标签。可以用于客户细分,数据分析,降维,异常检测,半监督学习,搜索引擎,分割图像,
K-Means
from sklearn.cluster import KMeans # 老经典了。。
k = 5
kmeans = KMeans(n_clusters=k, random_state=42) # 分配5个集群
y_pred = kmeans.fit_predict(X)
y_pred
>>>v array([4, 0, 1, ..., 2, 1, 0], dtype=int32) # 是聚类的索引,不是标签
y_pred is kmeans.labels_
>>> True
>
kmeans.cluster_centers_ # 看一下算法发现的5个中心点
X_new = np.array([[0, 2], [3, 2], [-3, 3], [-3, 2.5]])
kmeans.predict(X_new) # 对新数据的分类
array([1, 1, 2, 2], dtype=int32)
# 这种强行分配一个聚类的称为硬聚类,不如给每个实例赋予每个集群一个分数(软聚类)。分数可以是距离或相似性
kmeans.transform(X_new) # transform 方法计算每个实例到每个中心的距离。
# 但中心点的随机初始化有可能不会聚类出正确的结果
good_init = np.array([[-3, 3], [-3, 2], [-3, 1], [-1, 2], [0, 2]])
kmeans = KMeans(n_clusters=5, init=good_init, n_init=1, random_state=42)
kmeans.fit(X) # 这里假设已经知道了中心点的位置(通过另一种聚类方法)
kmeans.inertia_ # 使用这个属性查看模型惯性。
# 另一种方案就是使用不同的随机初始化多次运行,保留最优解。随机初始化的数量 n_init 控制
# 默认等于10 ,调用fit() 是会运行十次。使用模型的惯性来判断最优解(每个实例和最接近中心点的均方距离)
# 但实际上默认sklearn 采用了KMeans++ 的初始化方法,基本不会出现次优解的情况。。。。。
# Mini-Batch K-Means
# 一种变体,每次迭代使用小批量K-Means稍微移动中心点,算法速度提高还可以容纳大内存的数据。
from sklearn.cluster import MiniBatchKMeans
minibatch_kmeans = MiniBatchKMeans(n_clusters=5, random_state=42)
minibatch_kmeans.fit(X)
# 寻找最佳聚类,可以比较不同集群数量的轮廓分数
from sklearn.metrics import silhouette_score
silhouette_score(X, kmeans.labels_) # 传入训练好的标签和数据
0.655517642572828 # 分数
kmeans_per_k = [KMeans(n_clusters=k, random_state=42).fit(X)
for k in range(1, 10)]
silhouette_scores = [silhouette_score(X, model.labels_)
for model in kmeans_per_k[1:]]
# 可以看出 K=4 就很合适了
局限性:要多次运行才能避免次优解,需要指定集群数,当集群有不同大小,不同密度,非球型时效果都不好(使用之前一定要缩放输入特征的)。
使用聚类进行图像分割
属于同一单个对象的所有像素分配给一个类,复杂的还要看CNN 啥的,这里只是颜色分割。。
from matplotlib.image import imread
image = imread(name)
# image.shape (533, 800, 3)
X = image.reshape(-1, 3)
kmeans = KMeans(n_clusters=8, random_state=42).fit(X)
segmented_img = kmeans.cluster_centers_[kmeans.labels_]
segmented_img = segmented_img.reshape(image.shape)
segmented_imgs = []
n_colors = (10, 8, 6, 4, 2)
for n_clusters in n_colors:
kmeans = KMeans(n_clusters=n_clusters, random_state=42).fit(X)
segmented_img = kmeans.cluster_centers_[kmeans.labels_]
segmented_imgs.append(segmented_img.reshape(image.shape))
plt.figure(figsize=(10,5))
plt.subplots_adjust(wspace=0.05, hspace=0.1)
plt.subplot(231)
plt.imshow(image)
plt.title("Original image")
plt.axis('off')
for idx, n_clusters in enumerate(n_colors):
plt.subplot(232 + idx)
plt.imshow(segmented_imgs[idx])
plt.title("{} colors".format(n_clusters)) # 聚类的颜色都是采用的平均值代替的
plt.axis('off') # 聚类不是看颜色鲜明不鲜明,只看范围大小。
plt.show()
使用聚类进行预处理
聚类是一种有效的降维方法,常用于数据预处理
from sklearn.model_selection import GridSearchCV
param_grid = dict(kmeans__n_clusters=range(2, 100)) # 由于只是数据的预处理没有必要采用轮廓分析
pipeline = Pipeline([ # 直接网格搜索最佳的K 值就可。
("kmeans", KMeans(random_state=42)),
("log_reg", LogisticRegression(multi_class="ovr", solver="lbfgs", max_iter=5000, random_state=42)),
])
pipeline.fit(X_train, y_train)
grid_clf = GridSearchCV(pipeline, param_grid, cv=3, verbose=2)
grid_clf.fit(X_train, y_train)
grid_clf.best_params_
{'kmeans__n_clusters': 99
DBSCAN,另一种聚类算法,将聚类定义为高密度的连续区域,就是实例周围距离 ϵ 内至少包含 min_samples 个实例,就认为是一个核心,核心的周围就是一个集群(周围有另外一个核心就合并为一个集群),其他实例就认为是异常了。
from sklearn.cluster import DBSCAN
dbscan = DBSCAN(eps=0.05, min_samples=5) # 简单但功能强大,只有两个超参数,不适应与密度变换太大的
dbscan.fit(X)
dbscan.labels_[:10] # 查看标签
array([ 0, 2, -1, -1, 1, 0, 0, 0, 2, 5]) # 这些-1 就是异常的实例
dbscan.core_sample_indices_[:10] # 核心的索引
dbscan.components_[:3] # 核心实例本身
适当的增大 eps 扩大每个实例的领域,效果更好。但这个类有 fit_predict() 方法,但没有predict() 方法,也就是说无法进行预测新的实例,这就需要使用另外的算法,实现起来也不难。
from sklearn.neighbors import KNeighborsClassifier # 比如使用一个KNC
knn = KNeighborsClassifier(n_neighbors=50)
knn.fit(dbscan.components_, dbscan.labels_[dbscan.core_sample_indices_]) # 再核心实例上训练分类器
X_new = np.array([[-0.5, 0], [0, 0.5], [1, -0.1], [2, 1]])
knn.predict(X_new)
使用keras的顺序API构建图像分类器
import tensorflow as tf
import tensorflow.keras as keras
fashion_mnist = keras.datasets.fashion_mnist # 时尚服饰数据集,这个更有多样性
(X_train_full,y_train_full),(X_test,y_test) = fashion_mnist.load_data()
X_train_full.shape # 这个加载的数据形式补像sklearn 不是一维的了
>>> (60000, 28, 28)
X_train_full.dtype
>>>dtype('uint8') # 而且像素强度是整数了
# 分出了测试集,验证集,训练集,这里使用梯度下降来训练网络,所有要缩放输入特征到0-1
X_valid, X_train = X_train_full[:5000] / 255., X_train_full[5000:] / 255.
y_valid, y_train = y_train_full[:5000], y_train_full[5000:]
X_test = X_test / 255.
class_names = ["T-shirt/top", "Trouser", "Pullover", "Dress", "Coat",
"Sandal", "Shirt", "Sneaker", "Bag", "Ankle boot"]
plt.imshow(X_train[233], cmap="binary")
print(class_names[y_train[233]]) # 标签都是数字,用名字对应一下
plt.axis('off')
plt.show()
>>> Bag
model = keras.models.Sequential() # 按顺序构建模型
model.add(keras.layers.Flatten(input_shape=[28, 28])) # 展平为一维,要指定大小
model.add(keras.layers.Dense(300, activation="relu"))
model.add(keras.layers.Dense(100, activation="relu")) # 三层全连接
model.add(keras.layers.Dense(10, activation="softmax"))
model = keras.models.Sequential([
keras.layers.Flatten(input_shape=[28, 28]),
keras.layers.Dense(300, activation="relu"),
keras.layers.Dense(100, activation="relu"),
keras.layers.Dense(10, activation="softmax")
])
model.summary() # 显示模型的所有层,None表示批处理大小任意。
# 第一成权重有 784 * 300 外加300 个偏置项一个 235500 个参数
# 这过拟合的风险就很大
>>>
Model: "sequential_2"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
flatten_1 (Flatten) (None, 784) 0
_________________________________________________________________
dense_3 (Dense) (None, 300) 235500
_________________________________________________________________
dense_4 (Dense) (None, 100) 30100
_________________________________________________________________
dense_5 (Dense) (None, 10) 1010
=================================================================
Total params: 266,610
Trainable params: 266,610
Non-trainable params: 0
model.layers
>>>
[<tensorflow.python.keras.layers.core.Flatten at 0x1a966194520>,
<tensorflow.python.keras.layers.core.Dense at 0x1a9661afb80>,
<tensorflow.python.keras.layers.core.Dense at 0x1a9661afdf0>,
<tensorflow.python.keras.layers.core.Dense at 0x1a9661af520>]
hidden1 = model.layers[1]
hidden1.name
>>>'dense_3'
model.get_layer('dense_3') is hidden1 # True
weights , biases = hidden1.get_weights() # biases 初始化为0,
weights.shape # 这个经过了随机初始化的
>>> (784, 300)
model.compile(loss="sparse_categorical_crossentropy", # 创建模型后,需要compile 编译模型
optimizer="sgd", # 指定损失函数和优化器,还可以选择再训练和评估期间要计算的其他指标
metrics=["accuracy"]) # 指标
'''
model.compile(loss=keras.losses.sparse_categorical_crossentropy,
optimizer=keras.optimizers.SGD(lr=xxx), # 这样可以指定学习率
metrics=[keras.metrics.sparse_categorical_accuracy])
sparse_categorical_crossentropy 适用于稀疏标签就是0,1,2之类的
categorical_crossentropy 适用于独热类型的标签
binary_crossentropy 适用于二进制分类,带有一个或多个二进制标签的
稀疏标签转独热标签使用 keras.utils.to_categorical()
反之 np.argmax(axis=1)
'''
history = model.fit(X_train, y_train, epochs=30, # 训练只需要执行fit 方法
validation_data=(X_valid, y_valid))
# 传入输入特征和标签,训练的世代,验证集是可选的,每个世代结束时就用验证集测量损失和其他指标
# 还可以指定 validation_split=0.1 数据的最后10% 用于验证,而不用穿验证集
# fit 返回一个iHistory对象,包含训练参数,历经的世代列表 history.epoch
# 包含在训练集和验证集上的每个轮次结束时测得的损失和额外指标的字典 history.history
>>> 1719/1719 [==============================] - 3s 2ms/step - loss: 0.2259 - accuracy: 0.9195 - val_loss: 0.3086 - val_accuracy: 0.8924 # 这个进度条就很人性化啊。。
history.history.keys() #
>>> dict_keys(['loss', 'accuracy', 'val_loss', 'val_accuracy'])
import pandas as pd
# 使用pandas 的plot 方法就可以绘制(字典)这个学习曲线
pd.DataFrame(history.history).plot(figsize=(8, 5))
plt.grid(True)
plt.gca().set_ylim(0, 1)
plt.show()
# 验证集和训练集的曲线都很接近,说明没有太多的拟合。但最终训练集的性能一定时超过验证集的,这里训练的时间还是短
# 验证损失仍然是下降的趋势,说明模型尚未收敛,可以继续训练
history = model.fit(X_train, y_train, epochs=30, # 第二轮
validation_data=(X_valid, y_valid))
pd.DataFrame(history.history).plot(figsize=(8, 5))
plt.grid(True)
plt.gca().set_ylim(0, 1)
plt.show()
'''
如果性能不佳就要回头调整超参数。学习率,更换优化器,调整超参数(层数,神经元数,计划函数)
batch_size,fit() 中默认32.
然后就可以使用测试集进行评估
'''
model.evaluate(X_test, y_test) # 比较验证集性能略低正常,因为超参数是验证集而不是测试集上调整的(虽然这里没有调整)
>>> 313/313 [==============================] - 0s 1ms/step - loss: 0.3350 - accuracy: 0.8894
X_new = X_test[:3]
y_proba = model.predict(X_new) # 对新的实例进行预测
print(y_proba.round(2)) # 展示每个实例每个类的概率
# y_pred = model.predict_classes(X_new)
y_pred = np.argmax(y_proba, axis=-1)
np.array(class_names)[y_pred]
>>>
[[0. 0. 0. 0. 0. 0. 0. 0.01 0. 0.99]
[0. 0. 1. 0. 0. 0. 0. 0. 0. 0. ]
[0. 1. 0. 0. 0. 0. 0. 0. 0. 0. ]]
array(['Ankle boot', 'Pullover', 'Trouser'], dtype='<U11') # 高帮靴,套衫,裤子吧。。。
plt.figure(figsize=(7.2, 2.4))
for index, image in enumerate(X_new):
plt.subplot(1, 3, index + 1)
plt.imshow(image, cmap="binary", interpolation="nearest")
plt.axis('off')
plt.title(class_names[y_test[index]], fontsize=12)
plt.subplots_adjust(wspace=0.2, hspace=0.5)
plt.show()