機器學習實戰刻意練習 —— Task 01. K-鄰近算法

機器學習實戰刻意練習

第 1 周任務
  分類問題:K-鄰近算法
  分類問題:決策樹

第 2 周任務
  分類問題:樸素貝葉斯
  分類問題:邏輯迴歸

第 3 周任務
  分類問題:支持向量機

第 4 周任務
  分類問題:AdaBoost

第 5 周任務
  迴歸問題:線性迴歸、嶺迴歸、套索方法、逐步迴歸等
  迴歸問題:樹迴歸

第 6 周任務
  聚類問題:K均值聚類
  相關問題:Apriori

第 7 周任務
  相關問題:FP-Growth

第 8 周任務
  簡化數據:PCA主成分分析
  簡化數據:SVD奇異值分解
    



K-鄰近算法



1. K-近鄰算法

 1.1. K-近鄰法簡介

   K-近鄰法(K-nearest neighbor, K-NN)是1967年由Cover T和Hart P提出的一種基本分類與迴歸方法。
  它的工作原理是:存在一個樣本數據集合,也稱作爲訓練樣本集,並且樣本集中每個數據都存在標籤,即我們知道樣本集中每一個數據與所屬分類對應關係。輸入沒有標籤的新數據後,將新的數據的每個特徵與樣本集中數據對應的特徵進行比較,然後算法提取樣本最相似數據(最近鄰)的分類標籤。
  一般來說,我們只選擇樣本數據集中前K個最相似的數據,這就是K-近鄰算法中K的出處,通常K是不大於20整數。最後,選擇K個最相似數據中出現次數最多的分類,作爲新數據的分類。

  舉個簡單的例子,我們可以使用k-近鄰算法分類一個電影是動作片還是喜劇片。

電影名稱 打鬥鏡頭 搞笑鏡頭 電影類型
電影1 10 99 喜劇片
電影2 5 111 喜劇片
電影3 90 17 動作片
電影4 70 30 動作片
表1.1 每部電影的打鬥鏡頭數、搞笑鏡頭數以及電影類型

  表1.1就是我們已有的數據集合,也就是訓練樣本集。這個數據集有兩個特徵,即打鬥鏡頭數搞笑鏡頭數。除此之外,我們也知道每個電影的所屬類型,即分類標籤。用肉眼粗略地觀察,搞笑鏡頭多的,是喜劇片。打鬥鏡頭多的,是動作片。以我們多年的看片經驗,這個分類還算合理。如果現在給我一部電影,你告訴我這個電影打鬥鏡頭數和搞笑鏡頭數。不告訴我這個電影類型,我可以根據你給我的信息進行判斷,這個電影是屬於喜劇片還是動作片。而K-近鄰算法也可以像我們人一樣做到這一點,不同的地方在於,我們的經驗更豐富,而K-鄰近算法是靠已有的數據。比如,你告訴我這個電影打鬥鏡頭數爲2,搞笑鏡頭數爲102,我的經驗會告訴你這個是喜劇片,k-近鄰算法也會告訴你這個是喜劇片。你又告訴我另一個電影打鬥鏡頭數爲49,搞笑鏡頭數爲51,我們會意識到,這有可能是個喜劇&動作片,但是K-近鄰算法不會告訴你這些,因爲在它的眼裏,電影類型只有喜劇片和動作片,它會提取樣本集中特徵最相似數據(最鄰近)的分類標籤,得到的結果可能是喜劇片,也可能是動作片,但絕不會是喜劇&動作片。當然,這些取決於數據集的大小以及最近鄰的判斷標準等因素。


 1.2. 距離度量

  我們已經知道K-近鄰算法根據特徵比較,然後提取樣本集中特徵最相似數據(最鄰近)的分類標籤。那麼,如何進行比較呢?比如,我們還是以表1.1爲例,怎麼判斷紅色圓點標記的電影所屬的類別呢?如圖1.1所示。

在這裏插入圖片描述

圖1.1 電影分類

  我們可以從散點圖大致推斷,這個紅色圓點標記的電影可能屬於動作片,因爲距離已知的那兩個動作片的圓點更近。k-近鄰算法用什麼方法進行判斷呢?沒錯,就是距離度量。這個電影分類的例子有2個特徵,也就是在2維實數向量空間,可以使用我們高中學過的兩點距離公式計算距離,如圖1.2所示。

在這裏插入圖片描述
  通過計算,我們可以得到如下結果:

  • (40,60)到喜劇片(10,99)的距離約爲49.20
  • (40,60)到喜劇片(5,111)的距離約爲61.85
  • (40,60)到動作片(90,19)的距離約爲64.66
  • (40,60)到動作片(70,30)的距離約爲42.43

  通過計算可知,未知標記的電影到動作片 (70,30)的距離最近,爲42.43。如果算法直接根據這個結果,判斷該紅色圓點標記的電影爲動作片,這個算法就是最近鄰算法,而非K-近鄰算法。那麼K-鄰近算法是什麼呢?K-近鄰算法步驟如下:

  1. 計算已知類別數據集中的點與當前點之間的距離;
  2. 按照距離遞增次序排序;
  3. 選取與當前點距離最小的K個點;
  4. 確定前K個點所在類別的出現頻率;
  5. 返回前K個點所出現頻率最高的類別作爲當前點的預測分類。

  比如,現在我這個K值取3,那麼在電影例子中,按距離依次排序的三個點分別是動作片(70,30)、喜劇片(10,99)、喜劇片(5,111)。在這三個點中,喜劇片出現的頻率爲三分之二,動作片出現的頻率爲三分之一,所以該紅色圓點標記的電影爲喜劇片。這個判別過程就是K-近鄰算法。


 1.3. Python3代碼實現

  我們已經知道了k-近鄰算法的原理,那麼接下來就是使用Python3實現該算法,依然以電影分類爲例。

  1.3.1 準備數據集

  對於表1.1中的數據,我們可以使用numpy直接創建,代碼如下:

import numpy as np

"""
Parameters:
    無
Returns:
    group - 數據集
    labels - 分類標籤
"""
def createDataSet():
    #四組二維特徵
    group = np.array([[10,99],[5,111],[90,17],[70,30]])
    #四組特徵的標籤
    labels = ['喜劇片','喜劇片','動作片','動作片']
    return group, labels
if __name__ == '__main__':
    #創建數據集
    group, labels = createDataSet()
    #打印數據集
    print(group)
    print(labels)

"""
[[10,99]
 [5,111]
 [90,17]
 [70,30]]
['喜劇片','喜劇片','動作片','動作片']
"""

  1.3.2. K-近鄰算法

  根據兩點距離公式,計算距離,選擇距離最小的前k個點,並返回分類結果。

import numpy as np
import operator

"""
Parameters:
    inX - 用於分類的數據(測試集)
    dataSet - 用於訓練的數據(訓練集)
    labes - 分類標籤
    K - KNN算法參數,選擇距離最小的K個點
Returns:
    sortedClassCount[0][0] - 分類結果
"""
def classify0(inX, dataSet, labels, K):
    #numpy函數shape[0]返回dataSet的行數
    dataSetSize = dataSet.shape[0]
    #在列向量方向上重複inX共1次(橫向),行向量方向上重複inX共dataSetSize次(縱向)
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    #二維特徵相減後平方
    sqDiffMat = diffMat**2
    #sum()所有元素相加,sum(0)列相加,sum(1)行相加
    sqDistances = sqDiffMat.sum(axis=1)
    #開方,計算出距離
    distances = sqDistances**0.5
    #返回distances中元素從小到大排序後的索引值
    sortedDistIndices = distances.argsort()
    #定一個記錄類別次數的字典
    classCount = {}
    for i in range(K):
        #取出前k個元素的類別
        voteIlabel = labels[sortedDistIndices[i]]
        #dict.get(key,default=None),字典的get()方法,返回指定鍵的值,如果值不在字典中返回默認值。
        #計算類別次數
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
    #python3中用items()替換python2中的iteritems()
    #key=operator.itemgetter(1)根據字典的值進行排序
    #key=operator.itemgetter(0)根據字典的鍵進行排序
    #reverse降序排序字典
    sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
    #返回次數最多的類別,即所要分類的類別
    return sortedClassCount[0][0]

  1.3.3 整體代碼

  這裏預測紅色圓點標記的電影(40,60)的類別,K-NN的K值爲3。創建KNN_test01.py文件,編寫代碼如下:

import numpy as np
import operator

"""
Parameters:
    無
Returns:
    group - 數據集
    labels - 分類標籤
"""
def createDataSet():
    #四組二維特徵
    group = np.array([[10,99],[5,111],[90,17],[70,30]])
    #四組特徵的標籤
    labels = ['喜劇片','喜劇片','動作片','動作片']
    return group, labels


"""
Parameters:
    inX - 用於分類的數據(測試集)
    dataSet - 用於訓練的數據(訓練集)
    labes - 分類標籤
    K - KNN算法參數,選擇距離最小的K個點
Returns:
    sortedClassCount[0][0] - 分類結果
"""
def classify0(inX, dataSet, labels, K):
    #numpy函數shape[0]返回dataSet的行數
    dataSetSize = dataSet.shape[0]
    #在列向量方向上重複inX共1次(橫向),行向量方向上重複inX共dataSetSize次(縱向)
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    #二維特徵相減後平方
    sqDiffMat = diffMat**2
    #sum()所有元素相加,sum(0)列相加,sum(1)行相加
    sqDistances = sqDiffMat.sum(axis=1)
    #開方,計算出距離
    distances = sqDistances**0.5
    #返回distances中元素從小到大排序後的索引值
    sortedDistIndices = distances.argsort()
    #定一個記錄類別次數的字典
    classCount = {}
    for i in range(K):
        #取出前k個元素的類別
        voteIlabel = labels[sortedDistIndices[i]]
        #dict.get(key,default=None),字典的get()方法,返回指定鍵的值,如果值不在字典中返回默認值。
        #計算類別次數
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
    #python3中用items()替換python2中的iteritems()
    #key=operator.itemgetter(1)根據字典的值進行排序
    #key=operator.itemgetter(0)根據字典的鍵進行排序
    #reverse降序排序字典
    sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
    #返回次數最多的類別,即所要分類的類別
    return sortedClassCount[0][0]

if __name__ == '__main__':
    #創建數據集
    group, labels = createDataSet()
    #測試集
    test = [40,60]
    #KNN分類
    test_class = classify0(test, group, labels, 3)
    #打印分類結果
    print(test_class)

"""
喜劇片
"""



2. 項目案例

 2.1. 優化約會網站的配對效果

  2.1.1. 項目概述

  海倫使用約會網站尋找約會對象。經過一段時間之後,她發現曾交往過三種類型的人:

  1. 不喜歡的人
  2. 魅力一般的人
  3. 極具魅力的人

  她希望:

  1. 不喜歡的人則直接排除掉
  2. 工作日與魅力一般的人約會
  3. 週末與極具魅力的人約會

  現在她收集到了一些約會網站未曾記錄的數據信息,這更有助於匹配對象的歸類。

  2.1.2. 開發流程

   2.1.2.1. 收集數據

  此案例書中提供了文本文件。

  海倫把這些約會對象的數據存放在文本文件 datingTestSet2.txt 中,總共有 1000 行。海倫約會的對象主要包含以下 3 種特徵:

  • Col1:每年獲得的飛行常客里程數
  • Col2:玩視頻遊戲所耗時間百分比
  • Col3:每週消費的冰淇淋公升數

  文本文件數據格式如下:

40920	8.326976	0.953952	3
14488	7.153469	1.673904	2
26052	1.441871	0.805124	1
75136	13.147394	0.428964	1
38344	1.669788	0.134296	1

   2.1.2.2. 準備數據

  使用 Python 解析文本文件。將文本記錄轉換爲 NumPy 的解析程序如下所示:

import numpy as np

def file2matrix(filename):
    """
    Desc:
        導入訓練數據
    parameters:
        filename: 數據文件路徑
    return:
        數據矩陣 returnMat 和對應的類別 classLabelVector
    """
    fr = open(filename)
    # 獲得文件中的數據行的行數
    lines = fr.readlines()
    numberOfLines = len(lines)  # type: int
    # 生成對應的空矩陣
    # 例如:zeros(2,3)就是生成一個 2*3的矩陣,各個位置上全是 0
    returnMat = np.zeros((numberOfLines, 3))  # prepare matrix to return
    classLabelVector = []  # prepare labels return
    index = 0
    for line in lines:
        # str.strip([chars]) --返回已移除字符串頭尾指定字符所生成的新字符串
        line = line.strip()
        # 以 '\t' 切割字符串
        listFromLine = line.split('\t')
        # 每列的屬性數據
        returnMat[index, :] = listFromLine[0:3]
        # 每列的類別數據,就是 label 標籤數據
        classLabelVector.append(int(listFromLine[-1]))
        index += 1
    # 返回數據矩陣returnMat和對應的類別classLabelVector
    return returnMat, classLabelVector

   2.1.2.3. 分析數據

  使用 Matplotlib 畫二維散點圖。

import matplotlib.pyplot as plt

if __name__ == '__main__':
    datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
    color = ['r', 'g', 'b']
    fig = plt.figure()
    ax = fig.add_subplot(311)
    for i in range(1, 4):
        index = np.where(np.array(datingLabels) == i)
        ax.scatter(datingDataMat[index, 0], datingDataMat[index, 1], c=color[i - 1], label=i)
    plt.xlabel('Col.0')
    plt.ylabel('Col.1')
    plt.legend()
    bx = fig.add_subplot(312)
    for i in range(1, 4):
        index = np.where(np.array(datingLabels) == i)
        bx.scatter(datingDataMat[index, 0], datingDataMat[index, 2], c=color[i - 1], label=i)
    plt.xlabel('Col.0')
    plt.ylabel('Col.2')
    plt.legend()
    cx = fig.add_subplot(313)
    for i in range(1, 4):
        index = np.where(np.array(datingLabels) == i)
        cx.scatter(datingDataMat[index, 1], datingDataMat[index, 2], c=color[i - 1], label=i)
    plt.xlabel('Col.1')
    plt.ylabel('Col.2')
    plt.legend()
    plt.show()

  圖中清晰地標識了三個不同的樣本分類區域,具有不同愛好的人其類別區域也不同。
在這裏插入圖片描述

  歸一化特徵值,消除特徵之間量級不同導致的影響。

def autoNorm(dataSet):
    """
    Desc:
        歸一化特徵值,消除特徵之間量級不同導致的影響
    parameter:
        dataSet: 數據集
    return:
        歸一化後的數據集 normDataSet.ranges和minVals即最小值與範圍,並沒有用到

    歸一化公式:
        Y = (X-Xmin)/(Xmax-Xmin)
        其中的 min 和 max 分別是數據集中的最小特徵值和最大特徵值。該函數可以自動將數字特徵值轉化爲0到1的區間。
    """
    # 計算每種屬性的最大值、最小值、範圍
    minVals = np.min(dataSet, axis=0)
    maxVals = np.max(dataSet, axis=0)
    # 極差
    ranges = maxVals - minVals
    m = dataSet.shape[0]
    # 生成與最小值之差組成的矩陣
    normDataSet = dataSet - np.tile(minVals, (m, 1))
    # 將最小值之差除以範圍組成矩陣
    normDataSet = normDataSet / np.tile(ranges, (m, 1))  # element wise divide
    return normDataSet, ranges, minVals

   2.1.2.4. 訓練算法

  此步驟不適用於 k-近鄰算法。因爲測試數據每一次都要與全部的訓練數據進行比較,所以這個過程是沒有必要的。

import operator

def classify0(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0]
    # 距離度量 度量公式爲歐氏距離
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    sqDiffMat = diffMat ** 2
    sqDistances = np.sum(sqDiffMat, axis=1)
    distances = sqDistances ** 0.5
    # 將距離排序:從小到大
    sortedDistIndicies = distances.argsort()
    # 選取前K個最短距離, 選取這K箇中最多的分類類別
    classCount = {}
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]

   2.1.2.5. 測試算法

  計算錯誤率,使用海倫提供的部分數據作爲測試樣本。如果預測分類與實際類別不同,則標記爲一個錯誤。

def datingClassTest():
    """
    Desc:
        對約會網站的測試方法
    parameters:
        none
    return:
        錯誤數
    """
    # 設置測試數據的的一個比例
    hoRatio = 0.1  # 測試範圍,一部分測試一部分作爲樣本
    # 從文件中加載數據
    datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')  # load data setfrom file
    # 歸一化數據
    normMat, ranges, minVals = autoNorm(datingDataMat)
    # m 表示數據的行數,即矩陣的第一維
    m = normMat.shape[0]
    # 設置測試的樣本數量, numTestVecs:m表示訓練樣本的數量
    numTestVecs = int(m * hoRatio)
    print('numTestVecs=', numTestVecs)
    errorCount = 0.0
    for i in range(numTestVecs):
        # 對數據測試
        classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 3)
        print("分類器返回結果: %d, 實際結果: %d" % (classifierResult, datingLabels[i]))
        if classifierResult != datingLabels[i]:
            errorCount += 1.0
    print("錯誤率: %f" % (errorCount / float(numTestVecs)))
    print(errorCount)

   2.1.2.6. 使用算法

  產生簡單的命令行程序,然後海倫可以輸入一些特徵數據以判斷對方是否爲自己喜歡的類型。

  約會網站預測函數如下:

def classifyPerson():
    resultList = ['不喜歡的人', '魅力一般的人', '極具魅力的人']
    ffMiles = float(input("每年獲得的飛行常客里程數?"))
    percentTats = float(input("玩視頻遊戲所耗時間百分比?"))
    iceCream = float(input("每週消費的冰淇淋公升數?"))
    datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
    normMat, ranges, minVals = autoNorm(datingDataMat)
    inArr = np.array([ffMiles, percentTats, iceCream])
    intX = (inArr - minVals) / ranges
    classifierResult = classify0(intX, normMat, datingLabels, 3)
    print("這個人屬於: ", resultList[classifierResult - 1])

  實際運行效果如下:

if __name__ == '__main__':
    classifyPerson()

'''
每年獲得的飛行常客里程數? 10000
玩視頻遊戲所耗時間百分比? 10
每週消費的冰淇淋公升數? 0.5
這個人屬於:  魅力一般的人
'''

未完待續...





參考資料

  • https://blog.csdn.net/c406495762/article/details/75172850
  • https://blog.csdn.net/LSGO_MYP/article/details/103048796
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章