《機器學習實戰》-k近鄰算法

K-近鄰算法

  • k-近鄰分類算法概述
  • 使用 k-近鄰算法改進約會網站的配對效果
  • 手寫識別系統
  • 總結

不知道有沒有喜歡看電影的同窗,今天咱們先不講咱們的 k-近鄰算法,咱們來說講電影。git

可能有的同窗喜歡看恐怖片,可能男生比較喜歡看愛情片,也有可能咱們的女同窗喜歡看動做片。那同窗們大家有沒有想過,咱們所說的恐怖片、愛情片和動做片都是以什麼來劃分的呢?。。。對,有些同窗已經講到重點了,動做片中可能打鬥場景較多;愛情片會存在接吻的鏡頭。可是,可能有些同窗已經想到了。。。對,雖然動做片中會有較多的打鬥場景,那麼大家有沒有想過某些動做片中會有接吻的鏡頭,愛情片也是這樣。可是,有一點咱們是須要清楚的,假設電影只有兩個分類——動做片和愛情片二分類問題適合入門,動做片的打鬥場景相對於愛情片必定是較多的,而愛情片的接吻鏡頭相對於動做片是較多的,肯定這一點後,經過這一點咱們就能判斷一部電影的類型了。程序員

k-近鄰算法概述

k-近鄰算法:測量不一樣特徵值之間的距離方法進行分類算法

優勢:精度高、對異常值不敏感、無數據輸入假定。
缺點:計算複雜度高、空間複雜度高。
使用數據類型:數值型和標稱型。

k-近鄰算法(kNN)工做原理:小程序

  1. 存在一個樣本集,該樣本集中的每條數據都有標記。
  2. 輸入沒有標記的新數據,對新數據的每一個特徵都與樣本集中數據對應特徵比較。
  3. 經過算法提取樣本集中最類似(最近鄰)的分類標記。通常咱們只選擇樣本集中前 k 個最類似的數據,這就是 k-近鄰算法中 k 的出處,一般 k 是不大於20的整數。
  4. 選擇 k 個最類似數據中出現次數最多的分類,做爲新數據的分類。

相信你們對 k-近鄰算法有了一個大概的瞭解,對他須要作什麼有了必定的瞭解,可是因爲他的抽象,大家可能仍是似懂非懂,這個時候咱們來到咱們以前所敘述的電影分類的例子中,剛剛咱們得出了一個結論——動做片的打鬥場景多餘愛情片;愛情片的接吻場景大於動做片,那如今咱們有一部沒有看過的電影,咱們如何肯定它是愛情片仍是動做片呢?固然,有的同窗已經想到了。。。使用咱們的 kNN 來解決這個問題。windows

​ 圖2-1 使用打鬥和接吻鏡頭數分類數組

經過圖2-1咱們能很清晰的看到每一個電影純在多少個打鬥鏡頭和接吻鏡頭。app

​ 表2-1 每部電影的打頭鏡頭和接吻鏡頭次數和電影類型機器學習

序號 電影名稱 打鬥鏡頭 接吻鏡頭 電影類型
1 California Man 3 104 愛情片
2 He’s Not Really into Dudes 2 100 愛情片
3 Beautiful Woman 1 81 愛情片
4 Kevin Longblade 101 10 動做片
5 Robo Slayer 3000 99 5 動做片
6 Amped II 98 2 動做片
7 18 90 未知

很明顯經過表2-1咱們沒法得知’?’是什麼類型的電影。可是咱們能夠按照剛剛的思路計算未知電影與其餘電影的距離。如表2-2所示。暫時不要關心這個數據是怎麼算出來的,你目前只須要跟着個人思路走,等下一切自會揭曉。函數

​ 表2-2 已知電影與未知電影的距離

序號 電影名稱 與未知電影的距離
1 California Man 20.5
2 He’s Not Really into Dudes 18.7
3 Beautiful Woman 19.2
4 Kevin Longblade 115.3
5 Robo Slayer 3000 117.4
6 Amped II 118.9

咱們能夠從表2-2中找到 k 個距離’?’最近的電影。咱們假設 k=3,則這三個最靠近的電影依次是He’s Not Really into Dudes、Beautiful Woman和 California Man。經過 k-近鄰算法的結論,咱們發現這3部電影都是愛情片,所以咱們斷定未知電影是愛情片。

經過對電影類型的判斷,相信同窗們對 k-近鄰算法有了一個初步的認識。

下面我將帶你們簡單的瞭解下 k-近鄰算法的流程:

1. 收集數據:提供文本文件
2. 準備數據:對文本文件的數據作處理
3. 分析數據:檢查數據確保它符合要求
4. 訓練算法:此步驟不適用於 k-近鄰算法
5. 測試算法:使用測試樣本測試
6. 使用算法:構建一個完整的應用程序

解析和導入數據

使用 Python 導入數據

# kNN.py

from numpy import *
import operator


def create_data_set():
    """
    初始化數據,其中group 數組的函數應該和標記向量 labels 的元素數目相同。
    :return: 返回訓練樣本集和標記向量
    """
    group = array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]])  # 建立數據集
    labels = ['A', 'A', 'B', 'B']  # 建立標記

    return group, labels

因爲咱們大腦的限制,咱們一般只能處理可視化爲三維如下的事務,固然也爲了之後課程的易於理解,咱們對於每一個數據點一般只使用兩個特徵。主要使用多個特徵就須要常用線性代數的知識,只要你對一個特徵、兩個特徵把握準確了,特徵多了也不過是多加幾個參數而已。

數組中的每一組數據對應一個標記,即[1.0, 1.1]對應’A’、[0, 0.1]對應’B’,固然,例子中的數值是你能夠定製化設計的。咱們能夠經過四組數據畫出他們的圖像。

​ 圖2-2 k-近鄰算法_帶有四個數據點的簡單例子

數據準備好了,下面就是咱們的動手時間了。

實施 kNN 分類算法

# 僞代碼
1. 計算已知類別數據集中的點與當前點之間的距離。
2. 按照距離遞增次序排序。
3. 選取與當前點距離最小的 k 個點。
4. 肯定前 k 個點多在類別的出現頻率。
5. 返回前 k 個點出現頻率最高的類別做爲當前點的預測分類。

1️⃣在kNN.py中使用歐氏距離計算兩個向量\(x_A\)\(x_B\)之間的距離:

\[d=\sqrt{(x_{A_0}-x_{B_0})^2+(x_{A_1}-{x_{B_1}})^2}\]

例如,點\((0,0)\)\((1,2)\)之間的距離計算爲:

\(\sqrt{(1-0)^2+(2-0)^2}\)

若是數據集存在4個特徵值,則點\((1,0,0,1)\)\((7,6,9,4)\)之間的距離計算爲:

\(\sqrt{(7-1)^2+(6-0)^2+(9-0)^2+(4-1)^2}\)

2️⃣計算完全部點的距離後,對數據從小到大排序後肯定 k 個距離最小元素所在的主要分類。輸入的 k 是正整數。

3️⃣最後將 class_count 字典分解爲元祖列表,而後導入 operator.itemgetter 方法按照第二個元素的次序對元組進行從大到小的排序,以後返回發生頻率最高的元素標籤。

目前咱們已經成功構造了一個分類器,相信在接下來的旅途中,咱們構造使用分類算法將會更加容易。

# kNN.py

from numpy import *
import operator


def create_data_set():
    """
    初始化數據,其中group 數組的函數應該和標記向量 labels 的元素數目相同。
    :return: 返回訓練樣本集和標記向量
    """
    group = array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]])  # 建立數據集
    labels = ['A', 'A', 'B', 'B']  # 建立標記

    return group, labels


def classify0(in_x, data_set, labels, k):
    """
    對上述 create_data_set 的數據使用 k-近鄰算法分類。
    :param in_x: 用於分類的向量
    :param data_set: 訓練樣本集
    :param labels: 標記向量
    :param k: 選擇最近的數據的數目
    :return:
    """
    data_set_size = data_set.shape[0]  # 計算訓練集的大小
    # 4

    # 距離計算
    # tile(inX, (a, b)) tile函將 inX 重複 a 行,重複 b 列
    # … - data_set 每一個對應的元素相減,至關於歐式距離開平房內的減法運算
    diff_mat = tile(in_x, (data_set_size, 1)) - data_set
    '''
       [[-1.  -1.1]
        [-1.  -1. ]
        [ 0.   0. ]
        [ 0.  -0.1]]
    '''

    # 對 diff_mat 內部的每一個元素平方
    sq_diff_mat = diff_mat ** 2
    '''
        [[1.   1.21]
        [1.   1.  ]
        [0.   0.  ]
        [0.   0.01]]
    '''

    # sum(axis=0) 每列元素相加,sum(axis=1) 每行元素相加
    sq_distances = sq_diff_mat.sum(axis=1)
    # [2.21 2.   0.   0.01]

    # 每一個元素開平方求歐氏距離
    distances = sq_distances ** 0.5
    # [1.48660687 1.41421356 0.         0.1       ]

    # argsort函數返回的是數組值從小到大的索引值
    sorted_dist_indicies = distances.argsort()
    # [2 3 1 0]

    # 選擇距離最小的 k 個點
    class_count = {}  # type:dict
    for i in range(k):
        # 取出前 k 個對應的標籤
        vote_ilabel = labels[sorted_dist_indicies[i]]
        # 計算每一個類別的樣本數
        class_count[vote_ilabel] = class_count.get(vote_ilabel, 0) + 1

    # operator.itemgetter(0) 按照鍵 key 排序,operator.itemgetter(1) 按照值 value 排序
    # reverse 倒序取出頻率最高的分類
    sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True)
    # [('B', 2), ('A', 1)]

    # 取出頻率最高的分類結果
    return sorted_class_count[0][0]


if __name__ == '__main__':
    group, labels = create_data_set()
    print(classify0([0, 0], group, labels, 3))
 7

測試分類器

上文咱們已經使用 k-近鄰算法構造了一個分類器分類的概念是在已有數據的基礎上學會一個分類函數或構造出一個分類模型,即分類器。上一章節我已經講到,機器學習並非真正的預言家,k-近鄰算法也是機器學習算法中的一種,所以它的答案並不老是正確的,正如上章節所講,他會受到多種因素的影響,如訓練集的個數、訓練數據的特徵等。上述的 k-近鄰算法因爲訓練集的個數以及訓練數據的特徵遠遠不夠的,所以他並無太大的實際用處,下面將帶你們深刻 k-近鄰算法。

使用 k-近鄰算法改進約會網站的配對效果

工做一段時間後的你寂寞難耐,因此你準備去相親網站找男/女友。在某個在線約會網站上,通過幾個月總結,你發現你曾交往過三種類型的人:

1. 不喜歡的人
2. 魅力通常的人
3. 極具魅力的人

雖然你本身總結出了這三類人,可是約會網站沒法對你接觸的人經過這三種分類幫你作出確切的判斷。也許你週一至週五想約會那些魅力通常的人,而週末想約會那些極具魅力的人,因此作出確切的判斷頗有必要。所以你收集了一些約會網站不曾記錄的數據信息,想本身作個分類軟件給相親網站的產品經理,讓他幫你把你的分類軟件部署到他們網站上。下面就讓咱們來動手實現…

1. 收集數據:提供文本文件
2. 準備數據:使用 Python 解析文本文件
3. 分析數據:使用 Matplotlib 畫二維散點圖
4. 訓練算法:此步驟不適用於 k-近鄰算法
5. 測試算法:使用你提供的部分數據做爲測試樣本。
6. 使用算法:對心得約會對象進行預測。

測試樣本:測試樣本是已經完成分類的數據,既有標記,而非測試樣本沒有標記,所以使用你的測試算法去判斷你的測試數據,若是預測類別與實際類別不一樣,則標記爲一個錯誤。

收集數據

其實應該叫作採集數據更專業,不然你也能夠私底下稱爲爬蟲?

準備數據:使用 Python 解析文本文件

你能夠從個人 git 上第二章下載 datingTestSet.txt的文件,該文件每一個樣本數據佔據一行,總共有1000行。每一個樣本包含三個特徵:

1. 每一年的飛行里程數
2. 玩視頻遊戲所耗時間百分比
3. 每週消費的冰淇淋公升數。

在把上述特徵輸入到分類器以前,咱們須要新建file2matrix函數先處理輸入格式問題。

# kNN.py

def file2matrix(filename):
    with open(filename, 'r', encoding='utf-8') as fr:
        # 獲取文件的行數
        array_0_lines = fr.readlines()  # type:list
        number_of_lines = len(array_0_lines)

        # 建立以零填充的的 NumPy 矩陣,並將矩陣的另外一維度設置爲固定值3
        return_mat = zeros((number_of_lines, 3))  # 建立一個1000行3列的0零矩陣

        # 解析文件數據到列表
        class_label_vector = []  # 把結果存儲成列向量
        index = 0

        # 書本內容(錯誤)
        # for line in fr.readlines():
        #     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

        # 本身編寫
        for line in array_0_lines:
            line = line.strip()
            list_from_line = line.split("\t")
            # return_mat 存儲每一行數據的特徵值
            return_mat[index, :] = list_from_line[0:3]

            # 經過數據的標記作分類
            if list_from_line[-1] == "didntLike":
                class_label_vector.append(int(1))
            elif list_from_line[-1] == "smallDoses":
                class_label_vector.append(int(2))
            elif list_from_line[-1] == "largeDoses":
                class_label_vector.append(int(3))
            index += 1

    return return_mat, class_label_vector

分析數據:使用 Matplotlib 畫二維散點圖

話很少說,直接上代碼

# kNN.py

from numpy import *
import operator


def create_data_set():
    """
    初始化數據,其中group 數組的函數應該和標記向量 labels 的元素數目相同。
    :return: 返回訓練樣本集和標記向量
    """
    group = array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]])  # 建立數據集
    labels = ['A', 'A', 'B', 'B']  # 建立標記

    return group, labels


def classify0(in_x, data_set, labels, k):
    """
    對上述 create_data_set 的數據使用 k-近鄰算法分類。
    :param in_x: 用於分類的向量
    :param data_set: 訓練樣本集
    :param labels: 標記向量
    :param k: 選擇最近的數據的數目
    :return:
    """
    data_set_size = data_set.shape[0]  # 計算訓練集的大小
    # 4

    # 距離計算
    # tile(inX, (a, b)) tile函將 inX 重複 a 行,重複 b 列
    # … - data_set 每一個對應的元素相減,至關於歐式距離開平房內的減法運算
    diff_mat = tile(in_x, (data_set_size, 1)) - data_set
    '''
       [[-1.  -1.1]
        [-1.  -1. ]
        [ 0.   0. ]
        [ 0.  -0.1]]
    '''

    # 對 diff_mat 內部的每一個元素平方
    sq_diff_mat = diff_mat ** 2
    '''
        [[1.   1.21]
        [1.   1.  ]
        [0.   0.  ]
        [0.   0.01]]
    '''

    # sum(axis=0) 每列元素相加,sum(axis=1) 每行元素相加
    sq_distances = sq_diff_mat.sum(axis=1)
    # [2.21 2.   0.   0.01]

    # 每一個元素開平方求歐氏距離
    distances = sq_distances ** 0.5
    # [1.48660687 1.41421356 0.         0.1       ]

    # argsort函數返回的是數組值從小到大的索引值
    sorted_dist_indicies = distances.argsort()
    # [2 3 1 0]

    # 選擇距離最小的 k 個點
    class_count = {}  # type:dict
    for i in range(k):
        # 取出前 k 個對應的標籤
        vote_i_label = labels[sorted_dist_indicies[i]]
        # 計算每一個類別的樣本數
        class_count[vote_i_label] = class_count.get(vote_i_label, 0) + 1

    # operator.itemgetter(0) 按照鍵 key 排序,operator.itemgetter(1) 按照值 value 排序
    # reverse 倒序取出頻率最高的分類
    sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True)
    # [('B', 2), ('A', 1)]

    # 取出頻率最高的分類結果
    classify_result = sorted_class_count[0][0]

    return classify_result


def file2matrix(filename):
    with open(filename, 'r', encoding='utf-8') as fr:
        # 獲取文件的行數
        array_0_lines = fr.readlines()  # type:list
        number_of_lines = len(array_0_lines)

        # 建立以零填充的的 NumPy 矩陣,並將矩陣的另外一維度設置爲固定值3
        return_mat = zeros((number_of_lines, 3))  # 建立一個1000行3列的0零矩陣

        # 解析文件數據到列表
        class_label_vector = []  # 把結果存儲成列向量
        index = 0

        # 書本內容(報錯)
        # for line in fr.readlines():
        #     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

        # 本身編寫
        for line in array_0_lines:
            line = line.strip()
            list_from_line = line.split("\t")
            # return_mat 存儲每一行數據的特徵值
            return_mat[index, :] = list_from_line[0:3]

            # 經過數據的標記作分類
            if list_from_line[-1] == "didntLike":
                class_label_vector.append(int(1))
            elif list_from_line[-1] == "smallDoses":
                class_label_vector.append(int(2))
            elif list_from_line[-1] == "largeDoses":
                class_label_vector.append(int(3))
            index += 1

    return return_mat, class_label_vector


def scatter_diagram(dating_data_mat, dating_labels, diagram_type=1):
    import matplotlib.pyplot as plt
    from matplotlib.font_manager import FontProperties

    # windows下配置 font 爲中文字體
    # font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)

    # mac下配置 font 爲中文字體
    font = FontProperties(fname='/System/Library/Fonts/STHeiti Medium.ttc')

    # 經過 dating_labels 的索引獲取不一樣分類在矩陣內的行數
    index = 0
    index_1 = []
    index_2 = []
    index_3 = []
    for i in dating_labels:
        if i == 1:
            index_1.append(index)
        elif i == 2:
            index_2.append(index)
        elif i == 3:
            index_3.append(index)
        index += 1

    # 對不一樣分類在矩陣內不一樣的行數構造每一個分類的矩陣
    type_1 = dating_data_mat[index_1, :]
    type_2 = dating_data_mat[index_2, :]
    type_3 = dating_data_mat[index_3, :]

    fig = plt.figure()
    ax = fig.add_subplot(111)  # 就是1行一列一張畫布一張圖,

    if diagram_type == 1:
        # 經過對特徵0、1比較的散點圖
        type_1 = ax.scatter(type_1[:, 0], type_1[:, 1], c='red')
        type_2 = ax.scatter(type_2[:, 0], type_2[:, 1], c='blue')
        type_3 = ax.scatter(type_3[:, 0], type_3[:, 1], c='green')
        plt.xlabel('每一年的飛行里程數', fontproperties=font)
        plt.ylabel('玩視頻遊戲所耗時間百分比', fontproperties=font)

    elif diagram_type == 2:
        # 經過對特徵一、2比較的散點圖
        type_1 = ax.scatter(type_1[:, 1], type_1[:, 2], c='red')
        type_2 = ax.scatter(type_2[:, 1], type_2[:, 2], c='blue')
        type_3 = ax.scatter(type_3[:, 1], type_3[:, 2], c='green')
        plt.xlabel('玩視頻遊戲所耗時間百分比', fontproperties=font)
        plt.ylabel('每週所消費的冰淇淋公升數', fontproperties=font)

    elif diagram_type == 3:
        # 經過對特徵0、2比較的散點圖
        type_1 = ax.scatter(type_1[:, 0], type_1[:, 2], c='red')
        type_2 = ax.scatter(type_2[:, 0], type_2[:, 2], c='blue')
        type_3 = ax.scatter(type_3[:, 0], type_3[:, 2], c='green')
        plt.xlabel('每一年的飛行里程數', fontproperties=font)
        plt.ylabel('每週所消費的冰淇淋公升數', fontproperties=font)

    plt.legend((type_1, type_2, type_3), ('不喜歡的人', '魅力通常的人', '極具魅力的人'), loc=4, prop=font)
    plt.show()


if __name__ == '__main__':
    group, labels = create_data_set()
    classify0([0, 0], group, labels, 3)

    import os

    filename = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'dating_test_set.txt')
    dating_data_mat, dating_labels = file2matrix(filename)

    # 須要畫圖演示開啓
    '''
    diagram_type = 1, 比較特徵(0, 1);
    diagram_type = 2, 比較特徵(1, 2);
    diagram_type = 3, 比較特徵(0, 2)
    '''
    # scatter_diagram(dating_data_mat, dating_labels, diagram_type=1)

    norm_mat, ranges, min_vals = auto_norm(dating_data_mat)

​ 圖2-3 玩視頻遊戲和每一年飛行里程數特徵比較

準備數據:歸一化數值

​ 表2-3 四條約會網站原始數據

玩視頻遊戲所耗時間百分比 每一年飛行里程數 每週消費的冰淇淋公升數 樣本分類
1 0.8 400 0.5 1
2 12 134000 0.9 3
3 0 20000 1.1 2
4 67 32000 0.1 2

若是咱們要計算表2-3中樣本三和樣本4的距離,可使用下面的方法:

\(\sqrt{(0-67)^2+(20000-32000)^2+(1.1-0.1)^2}\)

可是上面方程彙總差值最大的屬性對計算結果的影響很大,而且是遠遠大於其餘兩個特徵的差值。可是你可能會認爲以上三種特徵是同等重要的,所以做爲三個等權重的特徵之一,第二個特徵不該該嚴重地影響到計算結果。

爲了處理這種不一樣取值範圍的特徵值時,咱們一般採用歸一化數值法,將特徵值的取值範圍處理爲\(0\)\(1\)或者\(-1\)\(1\)之間。咱們可使用下面的公式把特徵值的取值範圍轉化爲\(0\)\(1\)區間內的值:

\(new_value=(old_value-min)/(max-min)\)其中\(min\)\({max}\) 分別是數據集彙總的最小特徵值和最大特徵值。

所以咱們須要在 kNN.py 文件中增長一個新函數auto_norm(),該函數能夠自動將數字特徵值轉化爲\(0\)\(1\)的區間。

# kNN.py

def auto_norm(data_set):
    # min(0)使得函數從列中選取最小值,min(1)使得函數從行中選取最小值
    min_vals = data_set.min(0)
    max_vals = data_set.max(0)
    ranges = max_vals - min_vals

    # 獲取 data_set 的總行數
    m = data_set.shape[0]

    # 特徵值相除
    # 至關於公式裏的old_value-min
    # tile函數至關於將 min_vals 重複 m 行,重複1列
    norm_data_set = data_set - tile(min_vals, (m, 1))
    # 至關於公式裏的(old_value-min)/(max-min)
    norm_data_set = norm_data_set / tile(ranges, (m, 1))

    return norm_data_set, ranges, min_vals

測試算法:驗證分類器

上節咱們已經將數據按照需求作了歸一化數值處理,本節咱們將測試分類器的效果。以前講到過機器學習算法一般將已有數據的\(80\%\)做爲訓練樣本,其他的\(20\%\)做爲測試數據去測試分類測試數據應該是隨機選擇的,檢測分類器的正確率。

所以咱們須要在 kNN.py 文件中建立函數dating_class_test()

# kNN.py

from numpy import *
import operator


def create_data_set():
    """
    初始化數據,其中group 數組的函數應該和標記向量 labels 的元素數目相同。
    :return: 返回訓練樣本集和標記向量
    """
    group = array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]])  # 建立數據集
    labels = ['A', 'A', 'B', 'B']  # 建立標記

    return group, labels


def classify0(in_x, data_set, labels, k):
    """
    對上述 create_data_set 的數據使用 k-近鄰算法分類。
    :param in_x: 用於分類的向量
    :param data_set: 訓練樣本集
    :param labels: 標記向量
    :param k: 選擇最近的數據的數目
    :return:
    """
    data_set_size = data_set.shape[0]  # 計算訓練集的大小
    # 4

    # 距離計算
    # tile(inX, (a, b)) tile函將 inX 重複 a 行,重複 b 列
    # … - data_set 每一個對應的元素相減,至關於歐式距離開平房內的減法運算
    diff_mat = tile(in_x, (data_set_size, 1)) - data_set
    '''
       [[-1.  -1.1]
        [-1.  -1. ]
        [ 0.   0. ]
        [ 0.  -0.1]]
    '''

    # 對 diff_mat 內部的每一個元素平方
    sq_diff_mat = diff_mat ** 2
    '''
        [[1.   1.21]
        [1.   1.  ]
        [0.   0.  ]
        [0.   0.01]]
    '''

    # sum(axis=0) 每列元素相加,sum(axis=1) 每行元素相加
    sq_distances = sq_diff_mat.sum(axis=1)
    # [2.21 2.   0.   0.01]

    # 每一個元素開平方求歐氏距離
    distances = sq_distances ** 0.5
    # [1.48660687 1.41421356 0.         0.1       ]

    # argsort函數返回的是數組值從小到大的索引值
    sorted_dist_indicies = distances.argsort()
    # [2 3 1 0]

    # 選擇距離最小的 k 個點
    class_count = {}  # type:dict
    for i in range(k):
        # 取出前 k 個對應的標籤
        vote_i_label = labels[sorted_dist_indicies[i]]
        # 計算每一個類別的樣本數
        class_count[vote_i_label] = class_count.get(vote_i_label, 0) + 1

    # operator.itemgetter(0) 按照鍵 key 排序,operator.itemgetter(1) 按照值 value 排序
    # reverse 倒序取出頻率最高的分類
    sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True)
    # [('B', 2), ('A', 1)]

    # 取出頻率最高的分類結果
    classify_result = sorted_class_count[0][0]

    return classify_result


def file2matrix(filename):
    with open(filename, 'r', encoding='utf-8') as fr:
        # 獲取文件的行數
        array_0_lines = fr.readlines()  # type:list
        number_of_lines = len(array_0_lines)

        # 建立以零填充的的 NumPy 矩陣,並將矩陣的另外一維度設置爲固定值3
        return_mat = zeros((number_of_lines, 3))  # 建立一個1000行3列的0零矩陣

        # 解析文件數據到列表
        class_label_vector = []  # 把結果存儲成列向量
        index = 0

        # 書本內容(報錯)
        # for line in fr.readlines():
        #     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

        # 本身編寫
        for line in array_0_lines:
            line = line.strip()
            list_from_line = line.split("\t")
            # return_mat 存儲每一行數據的特徵值
            return_mat[index, :] = list_from_line[0:3]

            # 經過數據的標記作分類
            if list_from_line[-1] == "didntLike":
                class_label_vector.append(int(1))
            elif list_from_line[-1] == "smallDoses":
                class_label_vector.append(int(2))
            elif list_from_line[-1] == "largeDoses":
                class_label_vector.append(int(3))
            index += 1

    return return_mat, class_label_vector


def scatter_diagram(dating_data_mat, dating_labels, diagram_type=1):
    import matplotlib.pyplot as plt
    from matplotlib.font_manager import FontProperties

    # windows下配置 font 爲中文字體
    # font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)

    # mac下配置 font 爲中文字體
    font = FontProperties(fname='/System/Library/Fonts/STHeiti Medium.ttc')

    # 經過 dating_labels 的索引獲取不一樣分類在矩陣內的行數
    index = 0
    index_1 = []
    index_2 = []
    index_3 = []
    for i in dating_labels:
        if i == 1:
            index_1.append(index)
        elif i == 2:
            index_2.append(index)
        elif i == 3:
            index_3.append(index)
        index += 1

    # 對不一樣分類在矩陣內不一樣的行數構造每一個分類的矩陣
    type_1 = dating_data_mat[index_1, :]
    type_2 = dating_data_mat[index_2, :]
    type_3 = dating_data_mat[index_3, :]

    fig = plt.figure()
    ax = fig.add_subplot(111)  # 就是1行一列一張畫布一張圖,

    if diagram_type == 1:
        # 經過對特徵0、1比較的散點圖
        type_1 = ax.scatter(type_1[:, 0], type_1[:, 1], c='red')
        type_2 = ax.scatter(type_2[:, 0], type_2[:, 1], c='blue')
        type_3 = ax.scatter(type_3[:, 0], type_3[:, 1], c='green')
        plt.xlabel('每一年的飛行里程數', fontproperties=font)
        plt.ylabel('玩視頻遊戲所耗時間百分比', fontproperties=font)

    elif diagram_type == 2:
        # 經過對特徵一、2比較的散點圖
        type_1 = ax.scatter(type_1[:, 1], type_1[:, 2], c='red')
        type_2 = ax.scatter(type_2[:, 1], type_2[:, 2], c='blue')
        type_3 = ax.scatter(type_3[:, 1], type_3[:, 2], c='green')
        plt.xlabel('玩視頻遊戲所耗時間百分比', fontproperties=font)
        plt.ylabel('每週所消費的冰淇淋公升數', fontproperties=font)

    elif diagram_type == 3:
        # 經過對特徵0、2比較的散點圖
        type_1 = ax.scatter(type_1[:, 0], type_1[:, 2], c='red')
        type_2 = ax.scatter(type_2[:, 0], type_2[:, 2], c='blue')
        type_3 = ax.scatter(type_3[:, 0], type_3[:, 2], c='green')
        plt.xlabel('每一年的飛行里程數', fontproperties=font)
        plt.ylabel('每週所消費的冰淇淋公升數', fontproperties=font)

    plt.legend((type_1, type_2, type_3), ('不喜歡的人', '魅力通常的人', '極具魅力的人'), loc=4, prop=font)
    plt.show()


def auto_norm(data_set):
    # min(0)使得函數從列中選取最小值,min(1)使得函數從行中選取最小值
    min_vals = data_set.min(0)
    max_vals = data_set.max(0)
    ranges = max_vals - min_vals

    # 獲取 data_set 的總行數
    m = data_set.shape[0]

    # 特徵值相除
    # 至關於公式裏的old_value-min
    # tile函數至關於將 min_vals 重複 m 行,重複1列
    norm_data_set = data_set - tile(min_vals, (m, 1))
    # 至關於公式裏的(old_value-min)/(max-min)
    norm_data_set = norm_data_set / tile(ranges, (m, 1))

    return norm_data_set, ranges, min_vals


def dating_class_test():
    import os

    # 測試樣本比率
    ho_ratio = 0.20

    # 讀取文本數據
    filename = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'dating_test_set.txt')
    dating_data_mat, dating_labels = file2matrix(filename)

    # 對數據歸一化特徵值處理
    norm_mat, ranges, min_vals = auto_norm(dating_data_mat)

    m = norm_mat.shape[0]
    num_test_vecs = int(m * ho_ratio)
    error_count = 0

    for i in range(num_test_vecs):
        # 由於你的數據原本就是隨機的,因此直接選擇前20%的數據做爲測試數據
        classifier_result = classify0(norm_mat[i, :], norm_mat[num_test_vecs:m, :], dating_labels[num_test_vecs:m], 3)

        if classifier_result != dating_labels[i]: error_count += 1

    print("the total error rate is: {}".format(error_count / float(num_test_vecs)))
    # the total error rate is: 0.08


def main():
    import os

    group, labels = create_data_set()
    classify0([0, 0], group, labels, 3)

    filename = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'dating_test_set.txt')
    dating_data_mat, dating_labels = file2matrix(filename)

    # 須要畫圖演示開啓
    '''
    diagram_type = 1, 比較特徵(0, 1);
    diagram_type = 2, 比較特徵(1, 2);
    diagram_type = 3, 比較特徵(0, 2)
    '''
    # scatter_diagram(dating_data_mat, dating_labels, diagram_type=1)

    auto_norm(dating_data_mat)


if __name__ == '__main__':
    main()
    dating_class_test()

運行整個算法,最後得出分類器處理約會數據集的錯誤率是\(8\%\),這是一個至關不錯的結果。咱們也能夠改變測試集的比率即 ho_ratio 的值來檢測錯誤率的變化。

使用算法:構建完整可用系統

剛剛已經講到咱們的算法錯誤率只有\(8\%\),這是一個很不錯的算法了。如今咱們手動實現一個小程序讓咱們找到某我的並輸入他/她的信息,讓小程序給出咱們對對方喜歡程度的預測值。

# kNN.py

from numpy import *
import operator


def create_data_set():
    """
    初始化數據,其中group 數組的函數應該和標記向量 labels 的元素數目相同。
    :return: 返回訓練樣本集和標記向量
    """
    group = array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]])  # 建立數據集
    labels = ['A', 'A', 'B', 'B']  # 建立標記

    return group, labels


def classify0(in_x, data_set, labels, k):
    """
    對上述 create_data_set 的數據使用 k-近鄰算法分類。
    :param in_x: 用於分類的向量
    :param data_set: 訓練樣本集
    :param labels: 標記向量
    :param k: 選擇最近的數據的數目
    :return:
    """
    data_set_size = data_set.shape[0]  # 計算訓練集的大小
    # 4

    # 距離計算
    # tile(inX, (a, b)) tile函將 inX 重複 a 行,重複 b 列
    # … - data_set 每一個對應的元素相減,至關於歐式距離開平房內的減法運算
    diff_mat = tile(in_x, (data_set_size, 1)) - data_set
    '''
       [[-1.  -1.1]
        [-1.  -1. ]
        [ 0.   0. ]
        [ 0.  -0.1]]
    '''

    # 對 diff_mat 內部的每一個元素平方
    sq_diff_mat = diff_mat ** 2
    '''
        [[1.   1.21]
        [1.   1.  ]
        [0.   0.  ]
        [0.   0.01]]
    '''

    # sum(axis=0) 每列元素相加,sum(axis=1) 每行元素相加
    sq_distances = sq_diff_mat.sum(axis=1)
    # [2.21 2.   0.   0.01]

    # 每一個元素開平方求歐氏距離
    distances = sq_distances ** 0.5
    # [1.48660687 1.41421356 0.         0.1       ]

    # argsort函數返回的是數組值從小到大的索引值
    sorted_dist_indicies = distances.argsort()
    # [2 3 1 0]

    # 選擇距離最小的 k 個點
    class_count = {}  # type:dict
    for i in range(k):
        # 取出前 k 個對應的標籤
        vote_i_label = labels[sorted_dist_indicies[i]]
        # 計算每一個類別的樣本數
        class_count[vote_i_label] = class_count.get(vote_i_label, 0) + 1

    # operator.itemgetter(0) 按照鍵 key 排序,operator.itemgetter(1) 按照值 value 排序
    # reverse 倒序取出頻率最高的分類
    sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True)
    # [('B', 2), ('A', 1)]

    # 取出頻率最高的分類結果
    classify_result = sorted_class_count[0][0]

    return classify_result


def file2matrix(filename):
    with open(filename, 'r', encoding='utf-8') as fr:
        # 獲取文件的行數
        array_0_lines = fr.readlines()  # type:list
        number_of_lines = len(array_0_lines)

        # 建立以零填充的的 NumPy 矩陣,並將矩陣的另外一維度設置爲固定值3
        return_mat = zeros((number_of_lines, 3))  # 建立一個1000行3列的0零矩陣

        # 解析文件數據到列表
        class_label_vector = []  # 把結果存儲成列向量
        index = 0

        # 書本內容(報錯)
        # for line in fr.readlines():
        #     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

        # 本身編寫
        for line in array_0_lines:
            line = line.strip()
            list_from_line = line.split("\t")
            # return_mat 存儲每一行數據的特徵值
            return_mat[index, :] = list_from_line[0:3]

            # 經過數據的標記作分類
            if list_from_line[-1] == "didntLike":
                class_label_vector.append(int(1))
            elif list_from_line[-1] == "smallDoses":
                class_label_vector.append(int(2))
            elif list_from_line[-1] == "largeDoses":
                class_label_vector.append(int(3))
            index += 1

    return return_mat, class_label_vector


def scatter_diagram(dating_data_mat, dating_labels, diagram_type=1):
    import matplotlib.pyplot as plt
    from matplotlib.font_manager import FontProperties

    # windows下配置 font 爲中文字體
    # font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)

    # mac下配置 font 爲中文字體
    font = FontProperties(fname='/System/Library/Fonts/STHeiti Medium.ttc')

    # 經過 dating_labels 的索引獲取不一樣分類在矩陣內的行數
    index = 0
    index_1 = []
    index_2 = []
    index_3 = []
    for i in dating_labels:
        if i == 1:
            index_1.append(index)
        elif i == 2:
            index_2.append(index)
        elif i == 3:
            index_3.append(index)
        index += 1

    # 對不一樣分類在矩陣內不一樣的行數構造每一個分類的矩陣
    type_1 = dating_data_mat[index_1, :]
    type_2 = dating_data_mat[index_2, :]
    type_3 = dating_data_mat[index_3, :]

    fig = plt.figure()
    ax = fig.add_subplot(111)  # 就是1行一列一張畫布一張圖,

    if diagram_type == 1:
        # 經過對特徵0、1比較的散點圖
        type_1 = ax.scatter(type_1[:, 0], type_1[:, 1], c='red')
        type_2 = ax.scatter(type_2[:, 0], type_2[:, 1], c='blue')
        type_3 = ax.scatter(type_3[:, 0], type_3[:, 1], c='green')
        plt.xlabel('每一年的飛行里程數', fontproperties=font)
        plt.ylabel('玩視頻遊戲所耗時間百分比', fontproperties=font)

    elif diagram_type == 2:
        # 經過對特徵一、2比較的散點圖
        type_1 = ax.scatter(type_1[:, 1], type_1[:, 2], c='red')
        type_2 = ax.scatter(type_2[:, 1], type_2[:, 2], c='blue')
        type_3 = ax.scatter(type_3[:, 1], type_3[:, 2], c='green')
        plt.xlabel('玩視頻遊戲所耗時間百分比', fontproperties=font)
        plt.ylabel('每週所消費的冰淇淋公升數', fontproperties=font)

    elif diagram_type == 3:
        # 經過對特徵0、2比較的散點圖
        type_1 = ax.scatter(type_1[:, 0], type_1[:, 2], c='red')
        type_2 = ax.scatter(type_2[:, 0], type_2[:, 2], c='blue')
        type_3 = ax.scatter(type_3[:, 0], type_3[:, 2], c='green')
        plt.xlabel('每一年的飛行里程數', fontproperties=font)
        plt.ylabel('每週所消費的冰淇淋公升數', fontproperties=font)

    plt.legend((type_1, type_2, type_3), ('不喜歡的人', '魅力通常的人', '極具魅力的人'), loc=4, prop=font)
    plt.show()


def auto_norm(data_set):
    # min(0)使得函數從列中選取最小值,min(1)使得函數從行中選取最小值
    min_vals = data_set.min(0)
    max_vals = data_set.max(0)
    ranges = max_vals - min_vals

    # 獲取 data_set 的總行數
    m = data_set.shape[0]

    # 特徵值相除
    # 至關於公式裏的old_value-min
    # tile函數至關於將 min_vals 重複 m 行,重複1列
    norm_data_set = data_set - tile(min_vals, (m, 1))
    # 至關於公式裏的(old_value-min)/(max-min)
    norm_data_set = norm_data_set / tile(ranges, (m, 1))

    return norm_data_set, ranges, min_vals


def dating_class_test():
    import os

    # 測試樣本比率
    ho_ratio = 0.20

    # 讀取文本數據
    filename = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'dating_test_set.txt')
    dating_data_mat, dating_labels = file2matrix(filename)

    # 對數據歸一化特徵值處理
    norm_mat, ranges, min_vals = auto_norm(dating_data_mat)

    m = norm_mat.shape[0]
    num_test_vecs = int(m * ho_ratio)
    error_count = 0

    for i in range(num_test_vecs):
        # 由於你的數據原本就是隨機的,因此直接選擇前20%的數據做爲測試數據
        classifier_result = classify0(norm_mat[i, :], norm_mat[num_test_vecs:m, :], dating_labels[num_test_vecs:m], 3)

        if classifier_result != dating_labels[i]: error_count += 1

    # print("the total error rate is: {}".format(error_count / float(num_test_vecs)))
    # the total error rate is: 0.08


def main():
    import os

    group, labels = create_data_set()
    classify0([0, 0], group, labels, 3)

    filename = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'dating_test_set.txt')
    dating_data_mat, dating_labels = file2matrix(filename)

    # 須要畫圖演示開啓
    '''
    diagram_type = 1, 比較特徵(0, 1);
    diagram_type = 2, 比較特徵(1, 2);
    diagram_type = 3, 比較特徵(0, 2)
    '''
    # scatter_diagram(dating_data_mat, dating_labels, diagram_type=1)

    auto_norm(dating_data_mat)


def classify_person():
    import os

    result_list = ['討厭', '有點喜歡', '很是喜歡']

    ff_miles = float(input("每一年的出行千米數(km)?例如:1000\n"))
    percent_tats = float(input("每一年玩遊戲的時間佔比(.%)?例如:10\n"))
    ice_cream = float(input("每一年消費多少零食(kg)?例如:1\n"))

    filename = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'dating_test_set.txt')
    dating_data_mat, dating_labels = file2matrix(filename)

    norm_mat, ranges, min_vals = auto_norm(dating_data_mat)

    in_arr = array([ff_miles, percent_tats, ice_cream])

    classifier_result = classify0((in_arr - min_vals) / ranges, norm_mat, dating_labels, 3)

    print("你可能對他/她的印象:\n{}".format(result_list[classifier_result - 1]))


if __name__ == '__main__':
    main()
    dating_class_test()
    classify_person()

​ 圖2-4 約會-終

從圖2-4中能夠看出咱們經過輸入特徵值獲得了小程序給咱們預測的結果,算是一個小小的結束。咱們也實現了咱們的第一個算法,我能夠很自信的告訴你,你能夠把這個小程序讓約會網站的產品經理部署了。

聰明的同窗已經發現咱們這個約會小程序處理的數據都是較爲容易讓人理解的數據,那咱們如何對不容易讓人理解的數據構造一個分類器呢?接下來咱們就要實現咱們的第二個算法——手寫識別系統。

手寫識別系統

如今讓咱們手動構造一個簡單的手寫識別系統,該系統只能識別數字\(0-9\)

如下是咱們使用 k-近鄰算法實現手寫識別系統須要的步驟:

1. 收集數據:提供文本文件
2. 準備數據:編寫函數 calssify(),將圖像格式轉換爲分類器使用的 list 格式
3. 分析數據:檢查數據確保它符合要求
4. 訓練算法:此步驟不適用於 k-近鄰算法
5. 測試算法:使用測試樣本測試
6. 使用算法:構建一個完整的應用程序

準備數據

在 digits 文件夾內有兩個子目錄:目錄 traininigDigits 中大約有2000個例子,每一個例子的內容如圖2-5所示,麼個數字大約有200個樣本;目錄 testDigits 中包含了了大約900個測試數據,而且兩組數據沒有重疊。

​ 圖2-5 數字0的文本圖

爲了使用前面約會例子的分類器,咱們把圖像格式處理爲一個向量。圖像在計算機上是由一個一個像素點組成的。咱們能夠把本例中32*32的二進制圖像矩陣轉換爲1*1024的向量。

下面我就來實現一個 img2vector 函數,將圖像轉換爲向量。

# kNN.py

def img2vector(filename):
    # 構造一個一行有1024個元素的矩陣
    return_vect = zeros((1, 1024))

    with open(filename, 'r', encoding='utf-8') as fr:
        # 讀取文件的每一行的全部元素
        for i in range(32):
            line_str = fr.readline()
            # 把文件每一行的全部元素按照順序寫入構造的1*1024的零矩陣
            for j in range(32):
                return_vect[0, 32 * i + j] = int(line_str[j])
                
        return return_vect

測試算法

咱們已經能夠把單個圖像的文本文件格式轉化爲分類器能夠識別的格式了,咱們接下來的工做就是要把咱們現有的數據輸入到分類器,檢查分類器的執行效果了。所以咱們來構造一個 hand_writing_class_test 方法來實現該功能。

# kNN.py

def hand_writing_class_test():
    import os

    # 獲取訓練集和測試集數據的根路徑
    training_digits_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'digits/trainingDigits')
    test_digits_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'digits/testDigits')

    # 對訓練集數據作處理,構造一個 m*1024 的矩陣,m 是訓練集數據的個數
    hw_labels = []
    training_file_list = os.listdir(training_digits_path)  # type:list
    m = training_file_list.__len__()
    training_mat = zeros((m, 1024))

    # 對訓練集中的單個數據作處理
    for i in range(m):
        # 取出文件中包含的數字
        file_name_str = training_file_list[i]  # type:str
        file_str = file_name_str.split('.')[0]
        class_num_str = int(file_str.split('_')[0])
        # 添加標記
        hw_labels.append(class_num_str)
        # 把該文件中的全部元素構形成 1*1024 的矩陣後存入以前構造的 m*1024 的矩陣中對應的行
        training_mat[i, :] = img2vector(os.path.join(training_digits_path, file_name_str))

    # 對測試集數據作處理,構造一個 m*1024 的矩陣,m 是測試集數據的個數
    test_file_list = os.listdir(test_digits_path)
    error_count = 0
    m_test = test_file_list.__len__()

    # 對測試集中的單個數據作處理
    for i in range(m_test):
        # 取出文件中包含的數字
        file_name_str = test_file_list[i]
        file_str = file_name_str.split('.')[0]
        class_num_str = int(file_str.split('_')[0])

        # 把該文件中的全部元素構形成一個 1*1024 的矩陣
        vector_under_test = img2vector(os.path.join(test_digits_path, file_name_str))

        # 對剛剛構造的 1*1024 的矩陣進行分類處理判斷結果
        classifier_result = classify0(vector_under_test, training_mat, hw_labels, 3)

        # 對判斷錯誤的計數加 1
        if classifier_result != class_num_str: error_count += 1

    print("錯誤率: {}".format(error_count / float(m_test)))
    # 錯誤率: 0.010570824524312896

k-近鄰算法識別手寫數字數據集,錯誤率爲1%。如約會的例子,若是咱們改變 k 的值,修改訓練樣本或者測試樣本的數據,都會對 k-近鄰算法的準確率產生必定的影響,感興趣的能夠本身測試。

使用算法:構建完整可用系統

既然咱們剛剛實現的算法錯誤率僅有1%。那爲何咱們不手動實現一個系統經過輸入圖片而後識別圖片上的數字呢?那就讓咱們開動吧!僅作參考,涉及知識點過多,不感興趣的同窗能夠跳過。爲了實現該系統,首先咱們要手寫一個img_binaryzation 方法對圖片的大小修改爲咱們須要的 32*32px,而後對圖片進行二值化處理生成一個.txt文件,以後咱們把該 .txt文件傳入咱們的 hand_writing_test 方法中獲得結果。

# kNN.py

from numpy import *
import operator


def create_data_set():
    """
    初始化數據,其中group 數組的函數應該和標記向量 labels 的元素數目相同。
    :return: 返回訓練樣本集和標記向量
    """
    group = array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]])  # 建立數據集
    labels = ['A', 'A', 'B', 'B']  # 建立標記

    return group, labels


def classify0(in_x, data_set, labels, k):
    """
    對上述 create_data_set 的數據使用 k-近鄰算法分類。
    :param in_x: 用於分類的向量
    :param data_set: 訓練樣本集
    :param labels: 標記向量
    :param k: 選擇最近的數據的數目
    :return:
    """
    data_set_size = data_set.shape[0]  # 計算訓練集的大小
    # 4

    # 距離計算
    # tile(inX, (a, b)) tile函將 inX 重複 a 行,重複 b 列
    # … - data_set 每一個對應的元素相減,至關於歐式距離開平房內的減法運算
    diff_mat = tile(in_x, (data_set_size, 1)) - data_set
    '''
       [[-1.  -1.1]
        [-1.  -1. ]
        [ 0.   0. ]
        [ 0.  -0.1]]
    '''

    # 對 diff_mat 內部的每一個元素平方
    sq_diff_mat = diff_mat ** 2
    '''
        [[1.   1.21]
        [1.   1.  ]
        [0.   0.  ]
        [0.   0.01]]
    '''

    # sum(axis=0) 每列元素相加,sum(axis=1) 每行元素相加
    sq_distances = sq_diff_mat.sum(axis=1)
    # [2.21 2.   0.   0.01]

    # 每一個元素開平方求歐氏距離
    distances = sq_distances ** 0.5
    # [1.48660687 1.41421356 0.         0.1       ]

    # argsort函數返回的是數組值從小到大的索引值
    sorted_dist_indicies = distances.argsort()
    # [2 3 1 0]

    # 選擇距離最小的 k 個點
    class_count = {}  # type:dict
    for i in range(k):
        # 取出前 k 個對應的標籤
        vote_i_label = labels[sorted_dist_indicies[i]]
        # 計算每一個類別的樣本數
        class_count[vote_i_label] = class_count.get(vote_i_label, 0) + 1

    # operator.itemgetter(0) 按照鍵 key 排序,operator.itemgetter(1) 按照值 value 排序
    # reverse 倒序取出頻率最高的分類
    sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True)
    # [('B', 2), ('A', 1)]

    # 取出頻率最高的分類結果
    classify_result = sorted_class_count[0][0]

    return classify_result


def file2matrix(filename):
    with open(filename, 'r', encoding='utf-8') as fr:
        # 獲取文件的行數
        array_0_lines = fr.readlines()  # type:list
        number_of_lines = array_0_lines.__len__()

        # 建立以零填充的的 NumPy 矩陣,並將矩陣的另外一維度設置爲固定值3
        return_mat = zeros((number_of_lines, 3))  # 建立一個1000行3列的0零矩陣

        # 解析文件數據到列表
        class_label_vector = []  # 把結果存儲成列向量
        index = 0

        # 書本內容(報錯)
        # for line in fr.readlines():
        #     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

        # 本身編寫
        for line in array_0_lines:
            line = line.strip()
            list_from_line = line.split("\t")
            # return_mat 存儲每一行數據的特徵值
            return_mat[index, :] = list_from_line[0:3]

            # 經過數據的標記作分類
            if list_from_line[-1] == "didntLike":
                class_label_vector.append(int(1))
            elif list_from_line[-1] == "smallDoses":
                class_label_vector.append(int(2))
            elif list_from_line[-1] == "largeDoses":
                class_label_vector.append(int(3))
            index += 1

    return return_mat, class_label_vector


def scatter_diagram(dating_data_mat, dating_labels, diagram_type=1):
    import matplotlib.pyplot as plt
    from matplotlib.font_manager import FontProperties

    # windows下配置 font 爲中文字體
    # font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)

    # mac下配置 font 爲中文字體
    font = FontProperties(fname='/System/Library/Fonts/STHeiti Medium.ttc')

    # 經過 dating_labels 的索引獲取不一樣分類在矩陣內的行數
    index = 0
    index_1 = []
    index_2 = []
    index_3 = []
    for i in dating_labels:
        if i == 1:
            index_1.append(index)
        elif i == 2:
            index_2.append(index)
        elif i == 3:
            index_3.append(index)
        index += 1

    # 對不一樣分類在矩陣內不一樣的行數構造每一個分類的矩陣
    type_1 = dating_data_mat[index_1, :]
    type_2 = dating_data_mat[index_2, :]
    type_3 = dating_data_mat[index_3, :]

    fig = plt.figure()
    ax = fig.add_subplot(111)  # 就是1行一列一張畫布一張圖,

    if diagram_type == 1:
        # 經過對特徵0、1比較的散點圖
        type_1 = ax.scatter(type_1[:, 0], type_1[:, 1], c='red')
        type_2 = ax.scatter(type_2[:, 0], type_2[:, 1], c='blue')
        type_3 = ax.scatter(type_3[:, 0], type_3[:, 1], c='green')
        plt.xlabel('每一年的飛行里程數', fontproperties=font)
        plt.ylabel('玩視頻遊戲所耗時間百分比', fontproperties=font)

    elif diagram_type == 2:
        # 經過對特徵一、2比較的散點圖
        type_1 = ax.scatter(type_1[:, 1], type_1[:, 2], c='red')
        type_2 = ax.scatter(type_2[:, 1], type_2[:, 2], c='blue')
        type_3 = ax.scatter(type_3[:, 1], type_3[:, 2], c='green')
        plt.xlabel('玩視頻遊戲所耗時間百分比', fontproperties=font)
        plt.ylabel('每週所消費的冰淇淋公升數', fontproperties=font)

    elif diagram_type == 3:
        # 經過對特徵0、2比較的散點圖
        type_1 = ax.scatter(type_1[:, 0], type_1[:, 2], c='red')
        type_2 = ax.scatter(type_2[:, 0], type_2[:, 2], c='blue')
        type_3 = ax.scatter(type_3[:, 0], type_3[:, 2], c='green')
        plt.xlabel('每一年的飛行里程數', fontproperties=font)
        plt.ylabel('每週所消費的冰淇淋公升數', fontproperties=font)

    plt.legend((type_1, type_2, type_3), ('不喜歡的人', '魅力通常的人', '極具魅力的人'), loc=4, prop=font)
    plt.show()


def auto_norm(data_set):
    # min(0)使得函數從列中選取最小值,min(1)使得函數從行中選取最小值
    min_vals = data_set.min(0)
    max_vals = data_set.max(0)
    ranges = max_vals - min_vals

    # 獲取 data_set 的總行數
    m = data_set.shape[0]

    # 特徵值相除
    # 至關於公式裏的old_value-min
    # tile函數至關於將 min_vals 重複 m 行,重複1列
    norm_data_set = data_set - tile(min_vals, (m, 1))
    # 至關於公式裏的(old_value-min)/(max-min)
    norm_data_set = norm_data_set / tile(ranges, (m, 1))

    return norm_data_set, ranges, min_vals


def dating_class_test():
    import os

    # 測試樣本比率
    ho_ratio = 0.20

    # 讀取文本數據
    filename = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'dating_test_set.txt')
    dating_data_mat, dating_labels = file2matrix(filename)

    # 對數據歸一化特徵值處理
    norm_mat, ranges, min_vals = auto_norm(dating_data_mat)

    m = norm_mat.shape[0]
    num_test_vecs = int(m * ho_ratio)
    error_count = 0

    for i in range(num_test_vecs):
        # 由於你的數據原本就是隨機的,因此直接選擇前20%的數據做爲測試數據
        classifier_result = classify0(norm_mat[i, :], norm_mat[num_test_vecs:m, :], dating_labels[num_test_vecs:m], 3)

        if classifier_result != dating_labels[i]: error_count += 1

    # print("the total error rate is: {}".format(error_count / float(num_test_vecs)))
    # the total error rate is: 0.08


def matplotlib_run():
    import os

    group, labels = create_data_set()
    classify0([0, 0], group, labels, 3)

    filename = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'dating_test_set.txt')
    dating_data_mat, dating_labels = file2matrix(filename)

    # 須要畫圖演示開啓
    '''
    diagram_type = 1, 比較特徵(0, 1);
    diagram_type = 2, 比較特徵(1, 2);
    diagram_type = 3, 比較特徵(0, 2)
    '''
    scatter_diagram(dating_data_mat, dating_labels, diagram_type=2)

    auto_norm(dating_data_mat)


def classify_person():
    import os

    result_list = ['討厭', '有點喜歡', '很是喜歡']

    ff_miles = float(input("每一年的出行千米數(km)?例如:1000\n"))
    percent_tats = float(input("每日玩遊戲的時間佔比(.%)?例如:10\n"))
    ice_cream = float(input("每週消費多少零食(kg)?例如:1\n"))

    filename = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'dating_test_set.txt')
    dating_data_mat, dating_labels = file2matrix(filename)

    norm_mat, ranges, min_vals = auto_norm(dating_data_mat)

    in_arr = array([ff_miles, percent_tats, ice_cream])

    classifier_result = classify0((in_arr - min_vals) / ranges, norm_mat, dating_labels, 3)

    print("你可能對他/她的印象:\n{}".format(result_list[classifier_result - 1]))


def img2vector(filename):
    # 構造一個一行有1024個元素的即 1*1024 的矩陣
    return_vect = zeros((1, 1024))

    with open(filename, 'r', encoding='utf-8') as fr:
        # 讀取文件的每一行的全部元素
        for i in range(32):
            line_str = fr.readline()
            # 把文件每一行的全部元素按照順序寫入構造的 1*1024 的零矩陣
            for j in range(32):
                return_vect[0, 32 * i + j] = int(line_str[j])

        return return_vect


def hand_writing_class_test():
    import os

    # 獲取訓練集和測試集數據的根路徑
    training_digits_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'digits/trainingDigits')
    test_digits_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'digits/testDigits')

    # 對訓練集數據作處理,構造一個 m*1024 的矩陣,m 是訓練集數據的個數
    hw_labels = []
    training_file_list = os.listdir(training_digits_path)  # type:list
    m = training_file_list.__len__()
    training_mat = zeros((m, 1024))

    # 對訓練集中的單個數據作處理
    for i in range(m):
        # 取出文件中包含的數字
        file_name_str = training_file_list[i]  # type:str
        file_str = file_name_str.split('.')[0]
        class_num_str = int(file_str.split('_')[0])
        # 添加標記
        hw_labels.append(class_num_str)
        # 把該文件中的全部元素構形成 1*1024 的矩陣後存入以前構造的 m*1024 的矩陣中對應的行
        training_mat[i, :] = img2vector(os.path.join(training_digits_path, file_name_str))

    # 對測試集數據作處理,構造一個 m*1024 的矩陣,m 是測試集數據的個數
    test_file_list = os.listdir(test_digits_path)
    error_count = 0
    m_test = test_file_list.__len__()

    # 對測試集中的單個數據作處理
    for i in range(m_test):
        # 取出文件中包含的數字
        file_name_str = test_file_list[i]
        file_str = file_name_str.split('.')[0]
        class_num_str = int(file_str.split('_')[0])

        # 把該文件中的全部元素構形成一個 1*1024 的矩陣
        vector_under_test = img2vector(os.path.join(test_digits_path, file_name_str))

        # 對剛剛構造的 1*1024 的矩陣進行分類處理判斷結果
        classifier_result = classify0(vector_under_test, training_mat, hw_labels, 3)

        # 對判斷錯誤的計數加 1
        if classifier_result != class_num_str: error_count += 1

    print("錯誤率: {}".format(error_count / float(m_test)))


def hand_writing_run():
    import os

    test_digits_0_13_filename = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'digits/testDigits/0_13.txt')
    img2vector(test_digits_0_13_filename)
    hand_writing_class_test()


def img_binaryzation(img_filename):
    import os
    import numpy as np
    from PIL import Image
    import pylab

    # 修改圖片的路徑
    img_filename = os.path.join(os.path.dirname(os.path.dirname(__file__)), img_filename)

    # 調整圖片的大小爲 32*32px
    img = Image.open(img_filename)
    out = img.resize((32, 32), Image.ANTIALIAS)
    out.save(img_filename)

    # RGB 轉爲二值化圖
    img = Image.open(img_filename)
    lim = img.convert('1')
    lim.save(img_filename)

    img = Image.open(img_filename)

    # 將圖像轉化爲數組並將像素轉換到0-1之間
    img_ndarray = np.asarray(img, dtype='float64') / 256

    # 將圖像的矩陣形式轉化成一位數組保存到 data 中
    data = np.ndarray.flatten(img_ndarray)

    # 將一維數組轉化成矩陣
    a_matrix = np.array(data).reshape(32, 32)

    # 將矩陣保存到 txt 文件中轉化爲二進制0,1存儲
    img_filename_list = img_filename.split('.')  # type:list
    img_filename_list[-1] = 'jpg'
    txt_filename = '.'.join(img_filename_list)
    pylab.savetxt(txt_filename, a_matrix, fmt="%.0f", delimiter='')

    # 把 .txt 文件中的0和1調換
    with open(txt_filename, 'r') as fr:
        data = fr.read()
        data = data.replace('1', '2')
        data = data.replace('0', '1')
        data = data.replace('2', '0')

        with open(txt_filename, 'w') as fw:
            fw.write(data)

    return txt_filename


def hand_writing_test(img_filename):
    txt_filename = img_binaryzation(img_filename)
    import os

    # 獲取訓練集和測試集數據的根路徑
    training_digits_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'digits/trainingDigits')

    # 對訓練集數據作處理,構造一個 m*1024 的矩陣,m 是訓練集數據的個數
    hw_labels = []
    training_file_list = os.listdir(training_digits_path)  # type:list
    m = training_file_list.__len__()
    training_mat = zeros((m, 1024))

    # 對訓練集中的單個數據作處理
    for i in range(m):
        # 取出文件中包含的數字
        file_name_str = training_file_list[i]  # type:str
        file_str = file_name_str.split('.')[0]
        class_num_str = int(file_str.split('_')[0])
        # 添加標記
        hw_labels.append(class_num_str)
        # 把該文件中的全部元素構形成 1*1024 的矩陣後存入以前構造的 m*1024 的矩陣中對應的行
        training_mat[i, :] = img2vector(os.path.join(training_digits_path, file_name_str))

    # 把該文件中的全部元素構形成一個 1*1024 的矩陣
    vector_under_test = img2vector(txt_filename)

    # 對剛剛構造的 1*1024 的矩陣進行分類處理判斷結果
    classifier_result = classify0(vector_under_test, training_mat, hw_labels, 3)

    return classifier_result


if __name__ == '__main__':
    # matplotlib_run()
    # dating_class_test()
    # classify_person()
    # hand_writing_run()
    classifier_result = hand_writing_test(img_filename='2.jpg')
    print(classifier_result)

好了,咱們已經實現了咱們的手寫識別系統,恭喜你,完成了第一個算法的學習。

總結

k-近鄰算法是分類數據最簡單最有效的算法,沒有複雜的過程和數學公式,相信經過兩個例子同窗們對 k-近鄰算法有了較爲深刻的瞭解。可是細心的同窗運行這兩個算法的時候已經發現了運行該算法的是很是耗時間的。拿識別手寫系統舉例,由於該算法須要爲每一個測試向量作2000次距離計算,每一個距離包括了1024個維度浮點運算,總計要執行900次,此外,咱們還須要爲測試向量準備2MB的存儲空間。既然有了問題,做爲程序員的咱們是必定要去解決的,那麼是否存在一種算法減小存儲空間和計算時間的開銷呢?下一章揭曉答案——決策樹。

相關文章
相關標籤/搜索