1. 程式人生 > >【SciKit-Learn學習筆記】7:PCA結合SVM做AT&T資料集人物影象分類

【SciKit-Learn學習筆記】7:PCA結合SVM做AT&T資料集人物影象分類

學習《scikit-learn機器學習》時的一些實踐。


原理見PCA及繪製降維與恢復示意圖

sklearn的PCA

sklearn中包裝的PCA也是不帶有歸一化和縮放等預處理操作的,可以用MinMaxScaler()實現並裝在Pipeline裡封裝起來。

from sklearn.decomposition import PCA
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import MinMaxScaler
import numpy as np


def init()
: global A # 原始矩陣 A = np.array([ [3, 2000], [2, 3000], [4, 5000], [5, 8000], [1, 2000] ]) # 這裡修改其中元素的型別為float64,否則將給MinMaxScaler()內部做轉換會引起警告 # By default(copy=True), astype always returns a newly allocated array. A = A.astype(np.float64, copy=False
) # 預處理->PCA管道 def std_PCA(**kwargs): scalar = MinMaxScaler() # 用於資料預處理(歸一化和縮放) pca = PCA(**kwargs) # PCA本身不包含預處理 pipline = Pipeline([('scalar', scalar), ('pca', pca)]) return pipline if __name__ == '__main__': init() # 使用(這裡n_components指定降維後的維數k) pca = std_PCA(n_components=
1) Z = pca.fit_transform(A) print(Z) # 資料還原 A_approx = pca.inverse_transform(Z) print(A_approx)

[[-0.2452941 ]
[-0.29192442]
[ 0.29192442]
[ 0.82914294]
[-0.58384884]]

[[2.33563616e+00 2.91695452e+03]
[2.20934082e+00 2.71106794e+03]
[3.79065918e+00 5.28893206e+03]
[5.24568220e+00 7.66090960e+03]
[1.41868164e+00 1.42213588e+03]]

結合SVM做AT&T資料集人物影象分類

後面的函式都新增到同一個檔案裡,修改主函式以執行不同的功能。

資料初始化和檢視樣本影象

後面的功能不再貼出"資料初始化"部分的輸出。

import logging
from sklearn.datasets import fetch_olivetti_faces
import numpy as np
from matplotlib import pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.metrics import confusion_matrix
from sklearn.metrics import classification_report
from z10.pca_skl import std_PCA
from sklearn.model_selection import GridSearchCV


def init():
    global X, y, targets, target_names, n_targets, n_samples, h, w, X_train, X_test, y_train, y_test
    # 更改logging日誌模組的預設行為
    logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s')

    # 讀取資料
    data_home = 'E:\Data\code\datasets'
    logging.info("開始讀取資料集")
    # Load the Olivetti faces data-set from AT&T (classification).
    faces = fetch_olivetti_faces(data_home=data_home)
    logging.info("讀取完成")

    # 統計
    X = faces.data  # 資料
    y = faces.target  # 標籤.這裡給出的實際是索引號
    targets = np.unique(y)  # 標籤只留一個
    target_names = np.array(["c%d" % t for t in targets])  # 用索引號給人物命名為"c索引號"
    n_targets = target_names.shape[0]  # 類別數
    n_samples, h, w = faces.images.shape  # 樣本數,影象高,影象寬
    print("樣本數:{}\n標籤種類數:{}".format(n_samples, n_targets))
    print("影象尺寸:寬{}高{}\n資料集X的shape:{}".format(w, h, X.shape))
    # 劃分訓練集和測試集
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=2)


def plot_gallery(images, titles, h, w, n_row=2, n_col=5):
    """繪製圖片陣列"""
    plt.figure(figsize=(2 * n_col, 2.2 * n_row), dpi=100)
    # 跳幀子圖佈局,這裡hspace指子圖之間高度h上的間隔
    plt.subplots_adjust(bottom=0, left=0.01, right=0.99, top=0.90, hspace=0.01)
    for i in range(n_row * n_col):
        plt.subplot(n_row, n_col, i + 1)
        plt.imshow(images[i].reshape((h, w)), cmap='gray')
        plt.title(titles[i])
        plt.axis('off')  # 不提供座標軸


def look_face(only_get_sample=False):
    """檢視人臉圖片陣列,引數為True時不做繪圖,僅僅返回樣本"""
    n_row = 2
    n_col = 6
    sample_images = None
    sample_titles = []
    # 對於每個可能的標籤
    for i in range(n_targets):
        # 選取出該標籤的所有樣本
        people_images = X[y == targets[i]]
        # 隨機從中選取出一個樣本,即對這個人隨機選它的一張照片
        people_sample_index = np.random.randint(0, people_images.shape[0], 1)
        people_sample_image = people_images[people_sample_index, :]
        # (不是第一個放進來的人)
        if sample_images is not None:
            # 這時要用np.concatenate()做陣列拼接,即將這個人(的特徵陣列)放在下一行
            sample_images = np.concatenate((sample_images, people_sample_image), axis=0)
        # (是第一個放進來的人)
        else:
            sample_images = people_sample_image
        # 將標籤放入標籤列表中
        sample_titles.append(target_names[i])
    # 如果僅僅是要得到樣本,這裡就不繪圖
    if only_get_sample:
        return sample_images, sample_titles
    # 呼叫繪製圖片陣列的函式來繪製這些人臉影象的圖片陣列
    plot_gallery(sample_images, sample_titles, h, w, n_row, n_col)
    plt.show()
if __name__ == '__main__':
    init()
    look_face()

樣本數:400
標籤種類數:40
影象尺寸:寬64高64
資料集X的shape:(400, 4096)
在這裡插入圖片描述

嘗試直接使用SVM做分類

特徵太多(4096),直接使用SVM分類效果很差。

def just_svm():
    """僅僅使用svm分類"""
    # class_weight用於解決資料不均衡,s設定為'balanced'由類庫自己計算權重
    clf = SVC(class_weight='balanced')
    clf.fit(X_train, y_train)
    y_pred = clf.predict(X_test)
    cm = confusion_matrix(y_test, y_pred, labels=range(n_targets))
    print("混淆矩陣:")
    # 設定列印時顯示方式,threshold=np.nan意思是輸出陣列的時候完全輸出,不需要省略號將中間資料省略
    np.set_printoptions(threshold=np.nan)
    print(cm)
    print("分類報告:")
    # 這裡因為除以0的問題會報warning,見:https://stackoverflow.com/questions/34757653
    print(classification_report(y_test, y_pred, target_names=[d for d in target_names]))
if __name__ == '__main__':
    init()
    just_svm()

混淆矩陣上並未出現"對角線"上大多非零值這樣的正常分類現象。
在這裡插入圖片描述
出現warning是生成分類報告中某些指標除以0產生的,另外測試樣本未必能選取到所有的類別(人),也會導致直接打入類別標籤時,出現類別找不到的情況。
在這裡插入圖片描述

繪製資料還原率隨k值變化的曲線

這裡k是降維到的維度,資料還原率這裡由降維後的各主成分的方差值佔總方差值的比例表徵。

def draw_explained_variance_ratio():
    """
    explained_variance_ratio_
    代表降維後的各主成分的方差值佔總方差值的比例
    直接影響資料還原率,該函式即用於繪製k值對資料還原率的影響
    """
    # 讓k值取值0~300之間每隔30次取樣一次,計算PCA處理後的資料還原率
    candidate_components = range(10, 300, 30)
    # 存資料還原率的列表
    explained_ratios = []
    # 對每次選取的k值
    for k in candidate_components:
        # 做歸一化和PCA得到降維後的Z
        model = std_PCA(n_components=k)
        Z = model.fit_transform(X)
        # 拿出其中的PCA模型
        pca = model.named_steps['pca']
        # pca.explained_variance_ratio_得到的是各維度的方差值佔總方差值的比例,對其求和得到的就是資料還原率
        explained_ratios.append(np.sum(pca.explained_variance_ratio_))
    plt.figure(figsize=(10, 6), dpi=100)
    plt.grid()
    # 繪製資料還原率隨著k值變化的曲線
    plt.plot(candidate_components, explained_ratios)
    plt.xlabel("PCA降維的k值(降到k維)")
    plt.ylabel("資料還原率")
    plt.title("資料還原率隨著k值變化的曲線")
    plt.yticks(np.arange(0.5, 1.05, 0.05))
    plt.xticks(np.arange(0, 300, 20))
    plt.show()
if __name__ == '__main__':
    init()
    draw_explained_variance_ratio()

在這裡插入圖片描述

繪製原圖和在不同還原率下的影象,進行對比

這裡只取前5個人 c 0 c 4 c_0 \to c_4 ,使用到了最開始定義的繪製圖片陣列的函式。

def draw_orign_to_restore():
    """繪製原圖和在不同還原率下的影象,進行對比"""
    # 這裡只固定列數,行數取決於後面用了多少k值
    n_col = 5
    # 取樣取得每個人的資料和標籤,每個人只取一條
    sample_images, sample_titles = look_face(only_get_sample=True)
    # 只取前5個人
    sample_images = sample_images[0:5]
    sample_titles = sample_titles[0:5]
    # 要繪製的圖,先把這5張原始圖放進來
    plotting_images = sample_images
    # 該函式用於組合成title
    title_prefix = lambda a, b: "{}:{}".format(a, b)
    # 對應的標題,也是先放入這五個原始圖的標題
    plotting_titles = [title_prefix('orig', t) for t in sample_titles]
    # k值
    candidate_components = [140, 75, 37, 19, 8]
    # 對每一種k值
    for k in candidate_components:
        # 用總樣本集X訓練PCA模型
        model = std_PCA(n_components=k)
        model.fit(X)
        # 僅對這5張圖資料進行降維
        Zs = model.transform(sample_images)
        # 還原回來
        Xs_inv = model.inverse_transform(Zs)
        # 將新得到的5張還原回來的圖接在要繪製的圖的數組裡
        plotting_images = np.concatenate((plotting_images, Xs_inv), axis=0)
        # 標題也是,相應的標題也生成然後加進來
        titles_pca = [title_prefix(k, t) for t in sample_titles]
        plotting_titles = np.concatenate((plotting_titles, titles_pca), axis=0)
    # 繪圖
    plot_gallery(plotting_images, plotting_titles, h, w, 1 + len(candidate_components), n_col)
    plt.show()

在這裡插入圖片描述

PCA降維+SVM分類

這裡用到了前面裝在Pipeline裡的帶資料與處理的PCA模型,使用GridSearchCV()在降維後的資料集上找到了給出的引數表中最佳的引數,然後使用最佳引數對應的分類器clf.best_estimator_進行分類,最終輸出分類報告。

def pca_svm():
    """PCA降維+SVM分類"""
    k = 140
    # svd_solver='randomized'指定隨機SVD,whiten=True做白化變換,讓降維後的資料方差都為1
    model = std_PCA(n_components=k, svd_solver='randomized', whiten=True)
    # 注意,PCA的fit(即獲得主成分特徵矩陣U_reduce的過程)僅使用訓練集!
    model.fit(X_train)
    # 訓練集和測試集分別降維
    X_train_pca = model.transform(X_train)
    X_test_pca = model.transform(X_test)
    # 使用GridSerachCV選擇最佳的svm.SVC模型引數
    param_grid = {'C': [1, 5, 10, 50, 100], 'gamma': [0.0001, 0.0005, 0.001, 0.005, 0.01]}
    # verbose控制verbosity,決定輸出的過程資訊的複雜度,=2詳細
    clf = GridSearchCV(SVC(kernel='rbf', class_weight='balanced'), param_grid, verbose=2, n_jobs=4)
    # 這裡用降維後的訓練集來找最佳引數,則找到的最佳引數也會適合降維後的訓練集
    clf = clf.fit(X_train_pca, y_train)
    print("尋找到的最佳引數:", clf.best_params_)
    # 用找到的最佳引數對降維後的測試集做預測,可以直接用clf.best_estimator_獲得訓練好的最佳引數模型
    y_pred = clf.best_estimator_.predict(X_test_pca)
    # 輸出分類報告看一下
    print(classification_report(y_test, y_pred))
if __name__ == '__main__':
    init()
    pca_svm()

指定verbose=2能在尋找引數時輸出更多的中間資訊。
在這裡插入圖片描述
使用降維後的高斯核SVM分類器做分類,分類效果大大提升。
在這裡插入圖片描述