機器學習實戰2.1. 超詳細的k-近鄰演算法KNN(附Python程式碼)

xiaoming3526發表於2019-03-27

機器學習實戰2.1. 超詳細的k-近鄰演算法KNN(附Python程式碼)

搜尋微信公眾號:‘AI-ming3526’或者’計算機視覺這件小事’ 獲取更多機器學習乾貨
csdn:https://blog.csdn.net/baidu_31657889/
github:https://github.com/aimi-cn/AILearners

本文參考地址:[apachecn/AiLearning]

本文出現的所有程式碼,均可在github上下載,不妨來個Star把謝謝~:Github程式碼地址

KNN 概述

k-近鄰(kNN, k-NearestNeighbor)演算法是一種基本分類與迴歸方法,我們這裡只討論分類問題中的 k-近鄰演算法。

一句話總結:近朱者赤近墨者黑!
工作原理:
存在一個樣本資料集合,也稱作為訓練樣本集,並且樣本集中每個資料都存在標籤,即我們知道樣本集中每一個資料與所屬分類的對應關係。輸入沒有標籤的新資料後,將新的資料的每個特徵與樣本集中資料對應的特徵進行比較,然後演算法提取樣本最相似資料(最近鄰)的分類標籤。一般來說,我們只選擇樣本資料集中前k個最相似的資料,這就是k-近鄰演算法中k的出處,通常k是不大於20的整數。最後,選擇k個最相似資料中出現次數最多的分類,作為新資料的分類。

k近鄰演算法的輸入為例項的特徵向量,對應於特徵空間的點;輸出為例項的類別,可以取多類。k 近鄰演算法假設給定一個訓練資料集,其中的例項類別已定。分類時,對新的例項,根據其 k 個最近鄰的訓練例項的類別,通過多數表決等方式進行預測。因此,k近鄰演算法不具有顯式的學習過程。
k近鄰演算法實際上利用訓練資料集對特徵向量空間進行劃分,並作為其分類的“模型”。 k值的選擇、距離度量以及分類決策規則是k近鄰演算法的三個基本要素。

KNN 場景

電影可以按照題材分類,那麼如何區分 動作片愛情片 呢?

  1. 動作片:打鬥次數更多
  2. 愛情片:親吻次數更多

基於電影中的親吻、打鬥出現的次數,使用 k-近鄰演算法構造程式,就可以自動劃分電影的題材型別。

在這裡插入圖片描述

現在根據上面我們得到的樣本集中所有電影與未知電影的距離,按照距離遞增排序,可以找到 k 個距離最近的電影。
假定 k=3,則三個最靠近的電影依次是, He’s Not Really into Dudes 、 Beautiful Woman 和 California Man。
knn 演算法按照距離最近的三部電影的型別,決定未知電影的型別,而這三部電影全是愛情片,因此我們判定未知電影是愛情片。
對於K近鄰演算法概念還沒搞太清楚的同學 可以往下面看 直到看完整個demo你就可以理解KNN演算法是幹啥的了

KNN 原理

KNN 工作原理

  1. 假設有一個帶有標籤的樣本資料集(訓練樣本集),其中包含每條資料與所屬分類的對應關係。
  2. 輸入沒有標籤的新資料後,將新資料的每個特徵與樣本集中資料對應的特徵進行比較。
    1. 計算新資料與樣本資料集中每條資料的距離。
    2. 對求得的所有距離進行排序(從小到大,越小表示越相似)。
    3. 取前 k (k 一般小於等於 20 )個樣本資料對應的分類標籤。
  3. 求 k 個資料中出現次數最多的分類標籤作為新資料的分類。

KNN 通俗理解

給定一個訓練資料集,對新的輸入例項,在訓練資料集中找到與該例項最鄰近的 k 個例項,這 k 個例項的多數屬於某個類,就把該輸入例項分為這個類。

KNN 開發流程

收集資料:任何方法
準備資料:距離計算所需要的數值,最好是結構化的資料格式
分析資料:任何方法
訓練演算法:此步驟不適用於 k-近鄰演算法
測試演算法:計算錯誤率
使用演算法:輸入樣本資料和結構化的輸出結果,然後執行 k-近鄰演算法判斷輸入資料分類屬於哪個分類,最後對計算出的分類執行後續處理

KNN 演算法特點

優點:精度高、對異常值不敏感、無資料輸入假定
缺點:計算複雜度高、空間複雜度高
適用資料範圍:數值型和標稱型

話不多說直接上程式碼:

程式碼地址:https://github.com/aimi-cn/AILearners/tree/master/src/py2.x/ml/jqxxsz/2.KNN/KNN.py

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@File    :   KNN.py
@Time    :   2019/03/27 11:07:01
@Author  :   xiao ming 
@Version :   1.0
@Contact :   xiaoming3526@gmail.com
@Desc    :   KNN近鄰演算法
@github  :   https://github.com/aimi-cn/AILearners
@reference:  https://github.com/apachecn/AiLearning
'''

# here put the import lib
from __future__ import print_function
from numpy import *
import numpy as np
import operator
# 匯入科學計算包numpy和運算子模組operator
from os import listdir
from collections import Counter

def createDataSet():
    """
    建立資料集和標籤
    呼叫方式
    import kNN
    group, labels = kNN.createDataSet()
    """
    group =  array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
    labels = ['A','A','B','B']
    return group, labels

def test1():
    group, labels = createDataSet()
    '''
    [[1.  1.1]  #標籤對應A
    [1.  1. ]   #標籤對應A
    [0.  0. ]   #標籤對應B
    [0.  0.1]]  #標籤對應B

    ['A', 'A', 'B', 'B']
    '''
    #print(str(group))
    #print(str(labels))
    #print(classify0([0.1, 0.1], group, labels, 3))
    print(classify1([0.1, 0.1], group, labels, 3))

def classify0(inX, dataSet, labels, K):
    '''
    inX: 用於分類的輸入向量 輸入的測試資料
    dataSet:輸入的訓練樣本
    lables:標籤向量
    k:選擇最近鄰居的數目 通常小於20
    注意:labels元素數目和dataSet行數相同;程式使用歐式距離公式.

    預測資料所在分類可在輸入下列命令
    kNN.classify0([0,0], group, labels, 3)
    '''
    # -----------實現 classify0() 方法的第一種方式----------------------------------------------------------------------------------------------------------------------------
    # 1. 距離計算
    #計算資料大小
    dataSetSize = dataSet.shape[0]
    '''
    tile使用: 列3表示複製的行數, 行1/2表示對inX的重複的次數
    In [2]: inx = [1,2,3]
    In [3]: tile(inx,(3,1))  #列3表示複製的行數 行1表示對inX的重複的次數
    Out[3]: 
    array([[1, 2, 3],
        [1, 2, 3],
        [1, 2, 3]])

    In [4]: tile(inx,(3,2)) #列3表示複製的行數 行2表示對inX的重複的次數
    Out[4]: 
    array([[1, 2, 3, 1, 2, 3],
        [1, 2, 3, 1, 2, 3],
        [1, 2, 3, 1, 2, 3]])
    '''
    # tile生成和訓練樣本對應的矩陣,並與訓練樣本求差
    diffMat = tile(inX, (dataSetSize, 1)) - dataSet
    '''
    歐氏距離: 點到點之間的距離
       第一行: 同一個點 到 dataSet的第一個點的距離。
       第二行: 同一個點 到 dataSet的第二個點的距離。
       ...
       第N行: 同一個點 到 dataSet的第N個點的距離。
    [[1,2,3],[1,2,3]]-[[1,2,3],[1,2,0]]
    (A1-A2)^2+(B1-B2)^2+(c1-c2)^2
    '''
    # 取平方
    sqDiffMat = diffMat ** 2
    # 講矩陣的每一行相加
    sqDistances = sqDiffMat.sum(axis=1)
    # 開方
    distances = sqDistances ** 0.5
    #print ('distances=', distances)
    #distances= [1.3453624  1.27279221 0.14142136 0.1]
    # 根據距離排序從小到大的排序,返回對應的索引位置
    sortedDistIndicies = distances.argsort()
    #print ('distances.argsort()=', sortedDistIndicies)
    #distances.argsort()= [3 2 1 0]

    # 2. 選擇距離最小的K個點
    classCount = {}
    for i in range(K):
        #找到該樣本的型別
        voteIlabel = labels[sortedDistIndicies[i]]
        # 在字典中將該型別加一
        # 字典的get方法
        # 如:list.get(k,d) 其中 get相當於一條if...else...語句,引數k在字典中,字典將返回list[k];如果引數k不在字典中則返回引數d,如果K在字典中則返回k對應的value值
        # l = {5:2,3:4}
        # print l.get(3,0)返回的值是4;
        # Print l.get(1,0)返回值是0;
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    #print(classCount)
    #{'A': 1, 'B': 2}

    # 3. 排序並且返回出現最多的那個資料型別
    # 字典的 items() 方法,以列表返回可遍歷的(鍵,值)元組陣列。
    # 例如:dict = {'Name': 'Zara', 'Age': 7}   print "Value : %s" %  dict.items()   Value : [('Age', 7), ('Name', 'Zara')]
    # sorted 中的第2個引數 key=operator.itemgetter(1) 這個引數的意思是先比較第幾個元素
    # 例如:a=[('b',2),('a',1),('c',0)]  b=sorted(a,key=operator.itemgetter(1)) >>>b=[('c',0),('a',1),('b',2)] 可以看到排序是按照後邊的0,1,2進行排序的,而不是a,b,c
    # b=sorted(a,key=operator.itemgetter(0)) >>>b=[('a',1),('b',2),('c',0)] 這次比較的是前邊的a,b,c而不是0,1,2
    # b=sorted(a,key=opertator.itemgetter(1,0)) >>>b=[('c',0),('a',1),('b',2)] 這個是先比較第2個元素,然後對第一個元素進行排序,形成多級排序。
    #我們現在需要出現次數最多的那個 所以使用reverse=True列表反向排序
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]

def classify1(inX, dataSet, labels, K):
    '''
    inX: 用於分類的輸入向量 輸入的測試資料
    dataSet:輸入的訓練樣本
    lables:標籤向量
    k:選擇最近鄰居的數目 通常小於20
    注意:labels元素數目和dataSet行數相同;程式使用歐式距離公式.

    預測資料所在分類可在輸入下列命令
    kNN.classify0([0,0], group, labels, 3)
    '''
    # -----------實現 classify0() 方法的第二種方式----------------------------------------------------------------------------------------------------------------------------
    # 歐氏距離: 點到點之間的距離
    #    第一行: 同一個點 到 dataSet的第一個點的距離。
    #    第二行: 同一個點 到 dataSet的第二個點的距離。
    #    ...
    #    第N行: 同一個點 到 dataSet的第N個點的距離。

    # [[1,2,3],[1,2,3]]-[[1,2,3],[1,2,0]]
    # (A1-A2)^2+(B1-B2)^2+(c1-c2)^2
    
    # inx - dataset 使用了numpy broadcasting,見 https://docs.scipy.org/doc/numpy-1.13.0/user/basics.broadcasting.html
    # np.sum() 函式的使用見 https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.sum.html
    # """
    dist = np.sum((inX - dataSet)**2, axis=1)**0.5
    
    # """
    # 2. k個最近的標籤
    
    # 對距離排序使用numpy中的argsort函式, 見 https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.sort.html#numpy.sort
    # 函式返回的是索引,因此取前k個索引使用[0 : k]
    # 將這k個標籤存在列表k_labels中
    # """
    k_labels = [labels[index] for index in dist.argsort()[0 : K]]
	# """
    # 3. 出現次數最多的標籤即為最終類別
    
    # 使用collections.Counter可以統計各個標籤的出現次數,most_common返回出現次數最多的標籤tuple,例如[('lable1', 2)],因此[0][0]可以取出標籤值
	# """
    label = Counter(k_labels).most_common(1)[0][0]
    return label

if __name__ == '__main__':
    test1()
    


輸入:
	dataSet:輸入的訓練樣本
	[[1.0,1.1]  #標籤對應A
    [1.0,1.0]   #標籤對應A
    [0,    0]   #標籤對應B
    [0, 0.1]]  #標籤對應B

	lables:標籤向量
    ['A', 'A', 'B', 'B']
    
    inX: 用於分類的輸入向量 輸入的測試資料
    [0.1, 0.1]
輸出:
	B
說明這個輸出的標籤更靠近B 輸入B類
###
詳細介紹看程式碼註釋哈

AIMI-CN AI學習交流群【1015286623】 獲取更多AI資料
掃碼加群:
在這裡插入圖片描述
分享技術,樂享生活:我們的公眾號每週推送“AI”系列資訊類文章,歡迎您的關注!
在這裡插入圖片描述

相關文章