機器學習——KNN算法(k近鄰算法)


一 KNN算法

1. KNN算法簡介

  KNN(K-Nearest Neighbor)工作原理:存在一個樣本數據集合,也稱為訓練樣本集,並且樣本集中每個數據都存在標簽,即我們知道樣本集中每一數據與所屬分類對應的關系。輸入沒有標簽的數據后,將新數據中的每個特征與樣本集中數據對應的特征進行比較,提取出樣本集中特征最相似數據(最近鄰)的分類標簽。一般來說,我們只選擇樣本數據集中前k個最相似的數據,這就是k近鄰算法中k的出處,通常k是不大於20的整數。最后選擇k個最相似數據中出現次數最多的分類作為新數據的分類。

  說明:KNN沒有顯示的訓練過程,它是“懶惰學習”的代表,它在訓練階段只是把數據保存下來,訓練時間開銷為0,等收到測試樣本后進行處理。

  舉個栗子:以電影分類作為例子,電影題材可分為愛情片,動作片等,那么愛情片有哪些特征?動作片有哪些特征呢?也就是說給定一部電影,怎么進行分類?這里假定將電影分為愛情片和動作片兩類,如果一部電影中接吻鏡頭很多,打斗鏡頭較少,顯然是屬於愛情片,反之為動作片。有人曾根據電影中打斗動作和接吻動作數量進行評估,數據如下:

電影名稱

打斗鏡頭

接吻鏡頭

電影類別

Califoria Man

3

104

愛情片

Beautigul Woman

1

81

愛情片

Kevin Longblade

101

10

動作片

Amped II

98

2

動作片

  給定一部電影數據(18,90)打斗鏡頭18個,接吻鏡頭90個,如何知道它是什么類型的呢?KNN是這樣做的,首先計算未知電影與樣本集中其他電影的距離(這里使用歐式距離),數據如下:

電影名稱

與未知分類電影的距離

Califoria Man

20.5

Beautigul Woman

19.2

Kevin Longblade

115.3

Amped II

118.9

  現在我們按照距離的遞增順序排序,可以找到k個距離最近的電影,加入k=3,那么來看排序的前3個電影的類別,愛情片,愛情片,動作片,下面來進行投票,這部未知的電影愛情片2票,動作片1票,那么我們就認為這部電影屬於愛情片。 

2. KNN算法優缺點

  優點:精度高,對異常值不敏感、無數據輸入假定

  缺點:計算復雜度高、空間復雜度高

3. KNN算法python代碼實現

  實現步驟:

    (1)計算距離

    (2)選擇距離最小的k個點

    (3)排序

  代碼實現:

import numpy as np
import operator


def classfy(int_x, data_set, labels, k=3):
    """
    kNN(k=3) 分類器
    :param int_x: 目標特征向量
    :param data_set: 數據集
    :param labels: 分類向量
    :param k: k 值
    :return: 距離
    """
    data = np.array(data_set)
    doint = np.array(int_x)
    # 計算距離(歐氏距離公式)
    distance = np.sum((data - doint) ** 2, axis=1) ** 0.5
    # 距離排序
    # distance = np.sort(distance)
    distances = distance.argsort()  # 排序后顯示在原列表的下標
    class_count = {}
    # 選擇距離最小的N個點
    for i in range(k):
        # 獲取labels里前K個元素([1,0,3,2],取前三個)
        vote_i_label = labels[distances[i]]
        # 統計各元素個數
        class_count[vote_i_label] = class_count.get(vote_i_label, 0) + 1
        # dict.get(key,default=None),字典的get()方法,返回指定鍵的值,如果值不在字典中返回默認值。

    # 按個數排序
    sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True)
    # key=operator.itemgetter(1)根據字典的值進行排序
    # key=operator.itemgetter(0)根據字典的鍵進行排序
    # reverse降序排序字典
    return sorted_class_count[0][0]

二 KNN算法實例

1. 鳶尾花品種預測

 普通實現:

 

# 導入相應的包
import random
import operator
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt


def classify(int_x, data_set, labels, k=3):
    """
    kNN(k=3) 分類器
    :param int_x: 目標特征向量
    :param data_set: 數據集
    :param labels: 分類向量
    :param k: k 值
    :return: 距離
    """
    data = np.array(data_set)
    doint = np.array(int_x)
    # 計算距離(歐氏距離公式)
    distance = np.sum((data - doint) ** 2, axis=1) ** 0.5
    # 距離排序
    # distance = np.sort(distance)
    distances = distance.argsort()  # 排序后顯示在原列表的下標
    class_count = {}
    # 選擇距離最小的N個點
    for i in range(k):
        # 獲取labels里前K個元素([1,0,3,2],取前三個)
        vote_i_label = labels[distances[i]]
        # 統計各元素個數
        class_count[vote_i_label] = class_count.get(vote_i_label, 0) + 1
        # dict.get(key,default=None),字典的get()方法,返回指定鍵的值,如果值不在字典中返回默認值。

    # 按個數排序
    sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True)
    # key=operator.itemgetter(1)根據字典的值進行排序
    # key=operator.itemgetter(0)根據字典的鍵進行排序
    # reverse降序排序字典
    return sorted_class_count[0][0]


def get_data_set(file):
    """
    獲得訓練集數據
    :param filename:
    :return:
    """
    df = pd.read_csv(file, sep=',', names=['sepal_length', 'sepal_wide', 'petal_length', 'petal_wide','species'])
    data_set = df.ix[:, :-1]
    labels = df.ix[:, -1]
    return data_set, labels


def draw_image(file):
    """
    畫圖
    :param file:
    :return:
    """
    df = pd.read_csv(file, delimiter=',', names=['sepal_length', 'sepal_wide', 'petal_length', 'petal_wide','species'])
    # setosa花瓣花萼長寬
    setosa_sepal_length, setosa_sepal_width, setosa_petal_length, setosa_petal_width = [], [], [], []
    # versicolor花瓣花萼長寬
    versicolor_sepal_length, versicolor_sepal_width, versicolor_petal_length, versicolor_petal_width = [], [], [], []
    # virginica花瓣花萼長寬
    virginica_sepal_length, virginica_sepal_width, virginica_petal_length, virginica_petal_width = [], [], [], []
    # 分組
    for i in range(len(df)):
        if df.ix[i, -1] == 'Iris-setosa':
            setosa_sepal_length.append(df.ix[i][0])
            setosa_sepal_width.append(df.ix[i][1])
            setosa_petal_length.append(df.ix[i][2])
            setosa_petal_width.append(df.ix[i][3])
        elif df.ix[i, -1] == 'Iris-versicolor':
            versicolor_sepal_length.append(df.ix[i][0])
            versicolor_sepal_width.append(df.ix[i][1])
            versicolor_petal_length.append(df.ix[i][2])
            versicolor_petal_width.append(df.ix[i][3])
        else:
            virginica_sepal_length.append(df.ix[i][0])
            virginica_sepal_width.append(df.ix[i][1])
            virginica_petal_length.append(df.ix[i][2])
            virginica_petal_width.append(df.ix[i][3])
    plt.figure(figsize=(9, 4))
    # petal 長寬散點圖
    plt.subplot(1, 2, 1)
    plt.scatter(setosa_petal_width, setosa_petal_length, marker='.', alpha=0.5, label='setosa')
    plt.scatter(versicolor_petal_width, versicolor_petal_length, marker='+', alpha=0.5, label='versicolor')
    plt.scatter(virginica_petal_width, virginica_petal_length, marker='<', alpha=0.5, label='virginica')
    plt.xlabel('花瓣寬度')
    plt.ylabel('花瓣長度')
    plt.title('petal of iris')
    plt.legend()
    # sepal 長寬散點圖
    plt.subplot(1, 2, 2)
    plt.scatter(setosa_sepal_width, setosa_sepal_length, marker='.', alpha=0.5, label='setosa')
    plt.scatter(versicolor_sepal_width, versicolor_sepal_length, marker='+', alpha=0.5, label='versicolor')
    plt.scatter(virginica_sepal_width, virginica_sepal_length, marker='<', alpha=0.5, label='virginica')
    plt.xlabel('花萼寬度')
    plt.ylabel('花萼長度')
    plt.title('sepal of iris')
    plt.legend()
    plt.show()


def data_class_test(data_set, labels):
    """
    測試算法
    :param data_set: 測試集
    :param labels: 目標變量
    :return:
    """
    test_data_length = int(len(data_set) * 0.1)  # 拿出作為測試集的數據大小
    index_list = random.sample(range(len(data_set)), test_data_length)
    test_group = pd.DataFrame(np.zeros((test_data_length, 4)),
                              columns=['sepal_length', 'sepal_wide', 'petal_length', 'petal_wide'])
    test_label = []
    error_count = 0.0  # 錯誤統計
    # 獲取測試集
    for i in range(test_data_length):
        index = index_list[i]
        test_group.ix[i] = data_set.ix[index]
        test_label.append(labels[index])
    # 去除測試集的訓練集和目標變量
    train_data_set = data_set.drop(index_list)
    train_label = labels.drop(index_list)
    train_data_set = train_data_set.reset_index(drop=True)
    train_label = train_label.reset_index(drop=True)
    for i in range(test_data_length):
        iris_type = classify(test_group.ix[i], train_data_set, train_label)
        if iris_type != test_label[i]:
            print('錯誤分類結果:%s, 實際答案:%s' % (iris_type, test_label[i]))
            error_count += 1.0
    print('錯誤總數:%d' % error_count)
    print('分類器錯誤率為: %0.2f%%' % (error_count / float(test_data_length) * 100))


def classify_flower(data_set, labels):
    """
    對給定的數據進行花品種預測
    :param data_set:
    :param labels:
    :return:
    """
    # 輸入判斷數據
    sepal_length= float(input('請輸入花萼長度:'))
    sepal_wide = float(input('請輸入花萼寬度:'))
    petal_length = float(input('請輸入花瓣長度:'))
    petal_wide = float(input('請輸入花瓣寬度:'))
    x = np.array([sepal_length, sepal_wide, petal_length, petal_wide])
    iris_type = classify(x, data_set, labels)
    print('該花的品種為:%s' % iris_type)


if __name__ == '__main__':
    file = 'data\iris.csv'
    data_set, labels = get_data_set(file)
    # 畫圖
    #draw_image(file)
    # 預測品種
    #classify_flower(data_set, labels)
    # 算法測試
    data_class_test(data_set, labels)

  

sklearn實現:

from sklearn.datasets import load_iris  # 導入IRIS數據集
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import random
from sklearn import neighbors, preprocessing
import sklearn


def classify(data_set, labels, k=3):
    """
    分類器
    :param data_set: 數據集
    :param labels: 分類向量
    :param k: k值
    :return:
    """
    # 生成sk-learn的最近k鄰分類功能
    clf = neighbors.KNeighborsClassifier(algorithm='kd_tree', n_neighbors=k)
    # 擬合(訓練)數據
    clf.fit(data_set, labels)
    return clf


def auto_norm(data_set):
    """
    歸一化數據:將任意取值范圍內的特征轉化為0-1區間的值
    :param data_set:
    :return:
    """
    min_max_scaler = preprocessing.MinMaxScaler()
    data_set = min_max_scaler.fit_transform(data_set)  # 將數據特征縮放至0-1范圍
    return data_set


def get_data_set():
    """
    獲得數據
    :return:
    """
    iris = load_iris()  # 特征矩陣
    data_set, labels = iris.data, iris.target
    print(type(data_set))
    return data_set, labels


def draw_image(data_set, labels):
    # setosa花瓣花萼長寬
    setosa_sepal_length, setosa_sepal_width, setosa_petal_length, setosa_petal_width = [], [], [], []
    # versicolor花瓣花萼長寬
    versicolor_sepal_length, versicolor_sepal_width, versicolor_petal_length, versicolor_petal_width = [], [], [], []
    # virginica花瓣花萼長寬
    virginica_sepal_length, virginica_sepal_width, virginica_petal_length, virginica_petal_width = [], [], [], []
    # 分組
    for i in range(len(data_set)):
        if labels[i] == 0:
            setosa_sepal_length.append(data_set[i][0])
            setosa_sepal_width.append(data_set[i][1])
            setosa_petal_length.append(data_set[i][2])
            setosa_petal_width.append(data_set[i][3])
        elif labels[i] == 1:
            versicolor_sepal_length.append(data_set[i][0])
            versicolor_sepal_width.append(data_set[i][1])
            versicolor_petal_length.append(data_set[i][2])
            versicolor_petal_width.append(data_set[i][3])
        else:
            virginica_sepal_length.append(data_set[i][0])
            virginica_sepal_width.append(data_set[i][1])
            virginica_petal_length.append(data_set[i][2])
            virginica_petal_width.append(data_set[i][3])
    plt.figure(figsize=(9, 4))
    # petal 長寬散點圖
    plt.subplot(1, 2, 1)
    plt.scatter(setosa_petal_width, setosa_petal_length, marker='.', alpha=0.5, label='setosa')
    plt.scatter(versicolor_petal_width, versicolor_petal_length, marker='+', alpha=0.5, label='versicolor')
    plt.scatter(virginica_petal_width, virginica_petal_length, marker='<', alpha=0.5, label='virginica')
    plt.xlabel('花瓣寬度')
    plt.ylabel('花瓣長度')
    plt.title('petal of iris')
    plt.legend()
    # sepal 長寬散點圖
    plt.subplot(1, 2, 2)
    plt.scatter(setosa_sepal_width, setosa_sepal_length, marker='.', alpha=0.5, label='setosa')
    plt.scatter(versicolor_sepal_width, versicolor_sepal_length, marker='+', alpha=0.5, label='versicolor')
    plt.scatter(virginica_sepal_width, virginica_sepal_length, marker='<', alpha=0.5, label='virginica')
    plt.xlabel('花萼寬度')
    plt.ylabel('花萼長度')
    plt.title('sepal of iris')
    plt.legend()
    plt.show()


def data_class_test(data_set, labels):
    """
    測試算法
    :param data_set: 測試集
    :param labels: 目標變量
    :return:
    """
    test_data_length = int(len(data_set) * 0.1)  # 拿出作為測試集的數據大小
    index_list = random.sample(range(len(data_set)), test_data_length)
    test_data = np.array(range(test_data_length * 4)).reshape(test_data_length, 4)
    test_label = []
    error_count = 0.0  # 錯誤統計
    # 獲取測試集
    for i in range(test_data_length):
        index = index_list[i]
        test_data[i, :] = data_set[index,:]
        test_label.append(labels[index])
    # 去除測試集的訓練集和目標變量
    train_data_set = np.delete(data_set, index_list, 0)
    train_label = np.delete(labels, index_list)
    clf = classify(train_data_set, train_label)
    test_data_norm = auto_norm(test_data)
    score = clf.score(test_data_norm, test_label)
    print('正確率:%0.2f%%' % (float(score) * 100))


def classify_flower(data_set, labels):
    """
    對給定的數據進行花分類判斷
    :param data_set:
    :param labels:
    :return:
    """
    # 花的類別
    flower_stype = ['Iris-setosa', 'Iris-versicolor', 'Iris-virginica']
    # 輸入判斷數據
    sepal_length= float(input('請輸入花萼長度:'))
    sepal_wide = float(input('請輸入花萼寬度:'))
    petal_length = float(input('請輸入花瓣長度:'))
    petal_wide = float(input('請輸入花瓣寬度:'))
    x = np.array([sepal_length, sepal_wide, petal_length, petal_wide])
    iris_type = flower_stype[int(classify(data_set, labels).predict([x]))]
    print('該花的品種為:%s' % iris_type)


if __name__ == '__main__':
    file = 'data\iris.csv'
    data_set,labels = get_data_set()
    # 畫圖
    #draw_image(data_set, labels)
    # 預測品種
    classify_flower(data_set, labels)
    # 測試算法
    data_class_test(data_set,labels)

  

 

2. 改進約會網站匹配

  這個例子簡單說就是通過KNN找到你喜歡的人,首先數據樣本包含三個特征,(a)每年獲得的飛行常客里程數(b)玩游戲消耗的時間(c)每周消耗的冰激淋公升數,樣本數據放在txt中,如下,前三列為三個特征值,最后一列為標簽

普通實現:

# 導入相關工具包
import numpy as np
import operator
import matplotlib.pyplot as plt


def classify(int_x, data_set, labels, k=3):
    """
    kNN(k=3) 分類器
    :param int_x: 目標特征向量
    :param data_set: 數據集
    :param labels: 分類向量
    :param k: k 值
    :return: 距離
    """
    data = np.array(data_set)
    doint = np.array(int_x)
    # 計算距離(歐氏距離公式)
    distance = np.sum((data - doint) ** 2, axis=1) ** 0.5
    # 距離排序
    #distance = np.sort(distance)
    distances = distance.argsort()  # 排序后顯示在原列表的下標
    class_count = {}
    # 選擇距離最小的N個點
    for i in range(k):
        # 獲取labels里前K個元素([1,0,3,2],取前三個)
        vote_i_label = labels[distances[i]]
        # 統計各元素個數
        class_count[vote_i_label] = class_count.get(vote_i_label, 0) + 1
    # 按個數排序
    sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True)
    return sorted_class_count[0][0]


def auto_norm(data_set):
    """
    歸一化數據:將任意取值范圍內的特征轉化為0-1區間的值
    公式:new_values = (current_val - min) / (max - min)
    :param data_set:
    :return:
    """
    min_val = data_set.min(0) # 獲取數組內最小值
    max_val = data_set.max(0) # 獲取數組內最大值
    ranges = max_val - min_val

    # data_set的行數
    m = data_set.shape[0]
    norm_data_set = data_set - np.tile(min_val, (m,1))
    norm_data_set = norm_data_set/np.tile(ranges, (m, 1))
    return norm_data_set, ranges, min_val


def file_to_martrix(filename):
    """
    將文件記錄轉換到numpy-數組的解析程序
    :param filename: 數據集文件名
    :return return_mat:特征值矩陣
    :return class_label_vector: 目標變量向量
    """
    # 打開並加載文件
    with open(filename) as df:
        # 讀取所有行
        array_lines = df.readlines()
        # 得到文件行數
        number_of_lines = len(array_lines)
        # 創建空矩陣
        return_mat = np.zeros([number_of_lines, 3])  # 得到一個填充為0的矩陣
        # 返回的分類標簽
        class_label_vector = []
        # 行的索引
        index = 0
        for line in array_lines:
            line = line.strip()
            list_from_line = line.split('\t')
            return_mat[index, :] = list_from_line[0:3]
            class_label_vector.append(int(list_from_line[-1]))
            index += 1
    return return_mat, class_label_vector


def create_matplotlab_img(data_set, labels):
    """
    創建散點圖展示數據分析
    :param data_set: 特征數據集
    :param labels: 分類向量
    :return: None
    """
    # 初始化數據
    type_1_x = []
    type_1_y = []
    type_2_x = []
    type_2_y = []
    type_3_x = []
    type_3_y = []

    for i in range(len(labels)):
        if labels[i] == 1:
            type_1_x.append(data_set[i][0])
            type_1_y.append(data_set[i][1])
        if labels[i] == 2:
            type_2_x.append(data_set[i][0])
            type_2_y.append(data_set[i][1])
        if labels[i] == 3:
            type_3_x.append(data_set[i][0])
            type_3_y.append(data_set[i][1])
    fig = plt.figure()
    ax = fig.add_subplot(111)
    # 設置數據屬性
    type_1 = ax.scatter(type_1_x, type_1_y, s=20, c='g', alpha=0.8)
    type_2 = ax.scatter(type_2_x, type_2_y, s=20, c='r', alpha=0.8)
    type_3 = ax.scatter(type_3_x, type_3_y, s=20, c='m', alpha=0.8)

    plt.title('約會對象分析')
    plt.xlabel('每周消耗的冰激凌公升數')
    plt.ylabel('玩游戲所消耗時間百分比')
    ax.legend((type_1,type_2,type_3), ('不喜歡', '魅力一般', '極具魅力'))
    plt.show()


def dating_class_test():
    """
    測試算法
    :return:
    """
    hold_out_ratio = 0.10  # 拿出作為測試集的數據比例
    data_set, labels = file_to_martrix('data\dating_test_set_2.txt')
    norm_data_set, ranges, min_vals = auto_norm(data_set)  # 歸一化數據
    size = norm_data_set.shape[0]  # 獲取數據集行數
    num_test_size = int(size * hold_out_ratio)  # 保留行數

    error_count = 0.0  # 錯誤統計
    for i in range(num_test_size):
        classifier_result = classify(norm_data_set[i, :],
                                    norm_data_set[num_test_size:size],
                                    labels[num_test_size:size], 5)
        print('分類器返回:%d, 真是答案為:%d'% (classifier_result, labels[i]))
        if classifier_result != labels[i]:
            error_count += 1.0
    print('分類器錯誤率為: %0.2f%%'% (error_count / float(num_test_size) * 100))


def classify_person():
    """
    對給定的數據進行人群分類判斷
    :param data_set:
    :param labels:
    :return:
    """
    class_list = ['沒興趣,不去約會', '有點意思,工作日約會', '極具魅力,周末約會']
    print("請輸入:\n")
    fly_miles = float(input("每年獲得的飛行常客里程數:"))
    ice_cream = float(input('每周消耗的冰激凌公升數:'))
    game_time = float(input('玩視頻游戲所消耗時間百分比:'))

    data_set, labels = file_to_martrix('data\dating_test_set_2.txt')
    norm_data_set, ranges, min_vals = auto_norm(data_set)
    int_x = [fly_miles, ice_cream, game_time]  # 待驗證的特征向量
    norm_int_x = (int_x - min_vals) / ranges
    res = classify(norm_int_x, norm_data_set, labels, k=5)
    print(res)
    print(class_list[res - 1])


if __name__ == '__main__':
    filename = 'data\dating_test_set_2.txt'
    # 算法測試
    #dating_class_test()
    #data_set, labels = file_to_martrix(filename)
    # 畫圖
    #create_matplotlab_img(data_set, labels)
    classify_person()

 sklearn實現:

 

# 導入相應的包
import numpy as np
import matplotlib.pyplot as plt
from sklearn import neighbors, preprocessing


def file_to_martrix(filename):
    """
    將文件記錄轉換到numpy-數組的解析程序
    :param filename: 數據集文件名
    :return return_mat:特征值矩陣
    :return class_label_vector: 目標變量向量
    """
    # 打開並加載文件
    with open(filename) as df:
        # 讀取所有行
        array_lines = df.readlines()
        # 得到文件行數
        number_of_lines = len(array_lines)
        # 創建空矩陣
        return_mat = np.zeros((number_of_lines, 3))  # 得到一個填充為0的矩陣
        class_label_vector = []
        index = 0
        for line in array_lines:
            line = line.strip()
            list_from_line = line.split('\t')
            return_mat[index, :] = list_from_line[0:3]
            class_label_vector.append(int(list_from_line[-1]))
            index += 1
    return return_mat, class_label_vector


def create_matplotlab_img(data_set, labels):
    """
    創建散點圖展示數據分析
    :param data_set: 特征數據集
    :param labels: 分類向量
    :return: None
    """
    # 初始化數據
    type_1_x = []
    type_1_y = []
    type_2_x = []
    type_2_y = []
    type_3_x = []
    type_3_y = []

    for i in range(len(labels)):
        if labels[i] == 1:
            type_1_x.append(data_set[i][0])
            type_1_y.append(data_set[i][1])
        if labels[i] == 2:
            type_2_x.append(data_set[i][0])
            type_2_y.append(data_set[i][1])
        if labels[i] == 3:
            type_3_x.append(data_set[i][0])
            type_3_y.append(data_set[i][1])
    fig = plt.figure()
    ax = fig.add_subplot(111)
    # 設置數據屬性
    type_1 = ax.scatter(type_1_x, type_1_y, s=20, c='g', alpha=0.8)
    type_2 = ax.scatter(type_2_x, type_2_y, s=20, c='r', alpha=0.8)
    type_3 = ax.scatter(type_3_x, type_3_y, s=20, c='m', alpha=0.8)

    plt.title('約會對象分析')
    plt.xlabel('每周消耗的冰激凌公升數')
    plt.ylabel('玩游戲所消耗時間百分比')
    ax.legend((type_1,type_2,type_3), ('不喜歡', '魅力一般', '極具魅力'))
    plt.show()


def classify(data_set, labels, k=3):
    """
    分類器
    :param data_set: 數據集
    :param labels: 分類向量
    :param k: k值
    :return:
    """
    # 生成sk-learn的最近k鄰分類功能
    clf = neighbors.KNeighborsClassifier(algorithm='kd_tree', n_neighbors=k)
    # 擬合(訓練)數據
    clf.fit(data_set, labels)
    return clf


def auto_norm(data_set):
    """
    歸一化數據:將任意取值范圍內的特征轉化為0-1區間的值
    :param data_set:
    :return:
    """
    min_max_scaler = preprocessing.MinMaxScaler()
    data_set = min_max_scaler.fit_transform(data_set)  # 將數據特征縮放至0-1范圍
    return data_set


def classify_person(clf_model):
    """
    對給定的數據進行人群分類判斷
    :param clf_model:
    :return:
    """
    class_list = ['沒興趣,不去約會', '有點意思,工作日約會', '極具魅力,周末約會']
    print("請輸入:\n")
    fly_miles = float(input("每年獲得的飛行常客里程數:"))
    ice_cream = float(input('每周消耗的冰激凌公升數:'))
    game_time = float(input('玩視頻游戲所消耗時間百分比:'))

    train_set = np.array([fly_miles, ice_cream, game_time]).reshape(1, 3)
    # 歸一化數據
    train_set = auto_norm(train_set)
    predic = int(clf_model.predict(train_set))
    print(class_list[predic - 1])


if __name__ == '__main__':
    filename = 'data\dating_test_set_2.txt'
    data_set, labels = file_to_martrix(filename)
    clf = classify(data_set, labels)
    classify_person(clf)
    create_matplotlab_img(data_set, labels)

 

運行:

 

3. 手寫數字識別

訓練集、測試集形式:

 

 

首先我們要將圖像數據處理為一個向量,將32*32的二進制圖像信息轉化為1*1024的向量,再使用分類器,代碼如下:

普通實現:

 

# kNN算法: 手寫圖像識別
import numpy as np
import operator
import random
from os import listdir


def image_to_vector(filename):
    """
    將圖像轉換成向量
    :param filename:目標圖像文件名
    :return: 數據向量
    """
    vect = np.zeros((1, 1024)) # 創建1 * (32 * 32)數據向量
    with open(filename) as f:
        for i in range(32):
            line_str = f.readline() # 第 i 行
            for j in range(32):
                vect[0, 32 * i + j] = line_str[j]
    return vect


def classify(int_x, data_set, labels, k=3):
    """
    kNN(k=3) 分類器
    :param int_x: 目標特征向量
    :param data_set: 數據集
    :param labels: 分類向量
    :param k: k 值
    :return: 距離
    """
    data = np.array(data_set)
    doint = np.array(int_x)
    # 計算距離(歐氏距離公式)
    distance = np.sum((data - doint) ** 2, axis=1) ** 0.5
    # 距離排序
    # distance = np.sort(distance)
    distances = distance.argsort()  # 排序后顯示在原列表的下標
    class_count = {}
    # 選擇距離最小的N個點
    for i in range(k):
        # 獲取labels里前K個元素([1,0,3,2],取前三個)
        vote_i_label = labels[distances[i]]
        # 統計各元素個數
        class_count[vote_i_label] = class_count.get(vote_i_label, 0) + 1
        # dict.get(key,default=None),字典的get()方法,返回指定鍵的值,如果值不在字典中返回默認值。

    # 按個數排序
    sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True)
    # key=operator.itemgetter(1)根據字典的值進行排序
    # key=operator.itemgetter(0)根據字典的鍵進行排序
    # reverse降序排序字典
    return sorted_class_count[0][0]


def load_training_file():
    """
    獲取訓練集數據
    :return:
    """
    training_digit = listdir(r'training_digits')  # 獲取目錄內容
    file_count = int(len(training_digit) * 0.8)
    training_digit = random.sample(training_digit, file_count)
    # 第一步: 創建訓練集數據
    training_file_data = np.zeros((file_count, 1024))  # 利用行數創建訓練集數據
    labels = []
    for i in range(file_count):
        # 從文件名解析分類數字
        filename_str = training_digit[i]
        class_num_str = int(filename_str[0])
        labels.append(class_num_str)
        img_vector = image_to_vector('training_digits/%s'% filename_str)
        training_file_data[i:] = img_vector
    return training_file_data, labels


def hand_writing_class_test():
    """
    測試識別手寫數字分類正確率
    :return:
    """
    error_count = 0.0
    # 第一步: 獲取訓練集數據
    training_file_data, labels = load_training_file()

    # 第二步:獲取測試集數據
    test_file_list = listdir(r'test_digits')
    m = len(test_file_list)
    for i in range(m):
        filename_str = test_file_list[i]
        class_num_str = int(filename_str[0])  # 獲取分類
        test_img_vector = image_to_vector('test_digits/%s' % filename_str)

        # 創建一個5NN分類模型
        classifier_result = classify(test_img_vector, training_file_data, labels, 5)
        if classifier_result != class_num_str:
            error_count += 1.0
            print('第{}條出錯了 預測為:{},實際為:{}'.format(i, classifier_result, class_num_str))
    print('出錯總數:%s'% error_count)
    print('出錯率:%0.2f%%' % (error_count/float(m)*100))


if __name__ == '__main__':
    hand_writing_class_test()

 

sklearn實現:

 

# kNN算法: 手寫圖像識別
import random
import numpy as np
from os import listdir
from sklearn import neighbors


def image_to_vector(filename):
    """
    將圖像轉換成向量
    :param filename:目標圖像文件名
    :return: vect - 返回的二進制圖像的1*1024向量
    """
    vect = np.zeros((1, 1024))  # 創建1 * (32 * 32)數據向量
    with open(filename) as f:
        for i in range(32):
            line_str = f.readline()  # 第 i 行
            for j in range(32):
                # 每一行的前32個數據依次添加到vect
                vect[0, 32 * i + j] = line_str[j]
    return vect


def load_training_file():
    """
    獲取訓練集數據
    :return:
    """
    training_digit = listdir(r'training_digits')  # 獲取目錄內容
    file_count = int(len(training_digit) * 0.8)
    training_digit = random.sample(training_digit, file_count)   # 隨機獲取部分目錄內容
    training_file_data = np.zeros((file_count, 1024))  # 利用行數創建空的訓練集數據
    labels = []
    for i in range(file_count):
        # 從文件名解析分類數字
        filename_str = training_digit[i]
        class_num_str = int(filename_str[0])  # 獲取分類
        labels.append(class_num_str)
        img_vector = image_to_vector('training_digits/%s' % filename_str)
        training_file_data[i:] = img_vector
    return training_file_data, labels


def classify(data_set, labels, k=3):
    """
    分類器
    :param data_set: 數據集
    :param labels: 分類向量
    :param k: k值
    :return:
    """
    # 生成sk-learn的最近k鄰分類功能
    clf = neighbors.KNeighborsClassifier(algorithm='kd_tree', n_neighbors=k)
    # 擬合(訓練)數據
    clf.fit(data_set, labels)
    return clf


def hand_writing_class_test():
    """
    測試識別手寫數字分類正確率
    :return:
    """
    error_count = 0.0
    # 第一步: 創建訓練集數據
    training_file_data, labels = load_training_file()
    # 第二步: 創建測試集數據
    test_digit = listdir(r'test_digits')
    test_file_count = len(test_digit)
    for i in range(test_file_count):
        # 文件名的第一個字符為真實數值
        test_value = int(test_digit[i][0])
        test_vect = image_to_vector('test_digits/%s' % test_digit[i])

        # 創建一個5NN分類模型
        clf = classify(training_file_data, labels, 5)
        result = clf.predict(test_vect)
        if result != test_value:
            print('第{}條出錯了 預測為:{},實際為:{}'.format(i, result, test_value))
            error_count += 1.0
    print('出錯總數:%s' % error_count)
    print('出錯率:%0.2f%%' % (error_count/float(test_file_count) * 100))


if __name__ == '__main__':
    hand_writing_class_test()

  輸出:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM