機器學習實戰_K近鄰算法 —— 約會預測

一、代碼

說明:step1、step2是爲了方便數據查看;step3、step4可單獨運行。

import numpy as np
import operator


def classify0(inX, dataSet, labels, k):
    """
    函數說明:kNN算法,分類器

    Parameters:
        inX - 用於分類的數據(測試集)(1*m向量)
        dataSet - 用於訓練的數據(訓練集)(n*m向量array)
        labels - 分類標準(n*1向量array)
        k - kNN算法參數,選擇距離最小的k個點

    Returns:
        sortedClassCount[0][0] - 分類結果

    """
    # numpy函數shape[0]獲取dataSet的行數
    dataSetSize = dataSet.shape[0]
    # 將inX重複dataSetSize次並排成一列,即將inX賦值dataSetSize行、1列
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet   # tile:複製函數
    # 矩陣數乘:矩陣對應位置元素相乘(array()函數中矩陣的乘積可以使用np.matmul或者.dot()函數。而星號乘 (*)則表示矩陣對應位置元素相乘,與numpy.multiply()函數結果相同)
    sqDiffMat = diffMat ** 2  # 每個元素 ** 2
    # sum()所有元素相加,sum(0)列相加,sum(1)行相加
    sqDistances = sqDiffMat.sum(axis=1)
    # 開方,計算出距離
    distances = sqDistances ** 0.5  # 每個元素 ** 0.5
    # argsort函數返回的是distances值從小到大排序後的索引值
    sortedDistIndicies = distances.argsort()
    # 定義一個記錄類別次數的字典
    classCount = {}
    # 選擇距離最小的k個點
    for i in range(k):
        # 取出前k個元素的類別
        voteIlabel = labels[sortedDistIndicies[i]]
        # 字典的get()方法,返回指定鍵的值,如果值不在字典中返回0
        # 計算類別次數
        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]


def file2matrix(filename):
    """
    函數說明:打開解析文件,對數據進行分類:不喜歡,魅力一般,極具魅力

    Parameters:
        filename - 文件名

    Returns:
        returnMat - 特徵矩陣
        classLabelVector - 分類label向量
    """
    with open(filename) as fr:
        # 讀取文件所有內容
        arrayOlines = fr.readlines()
        # 得到文件行數
        numberOfLines = len(arrayOlines)
        # 返回的NumPy矩陣numberOfLines行,3列
        returnMat = np.zeros((numberOfLines, 3))
        # 創建分類標籤向量
        classLabelVector = []
        # 行的索引值
        index = 0
        # 讀取每一行
        for line in arrayOlines:
            # 去掉每一行首尾的空白符,例如'\n','\r','\t',' '
            line = line.strip()
            # 將每一行內容根據'\t'符進行切片,本例中一共有4列
            listFromLine = line.split('\t')
            # 將數據的前3列進行提取保存在returnMat矩陣中,也就是特徵矩陣
            returnMat[index, :] = listFromLine[0:3]
            # 根據文本內容進行分類1:不喜歡;2:一般;3:喜歡
            classLabelVector.append(listFromLine[-1])
            index += 1
        # 返回標籤列向量以及特徵矩陣
    return returnMat, classLabelVector


def showdatas(datingDataMat, datingLabels):
    """
    函數說明:可視化數據

    Parameters:
        datingDataMat - 特徵矩陣
        datingLabels - 分類Label

    Returns:
        None
    """

    from matplotlib.font_manager import FontProperties
    import matplotlib.lines as mlines
    import matplotlib.pyplot as plt
    # 設置漢字格式爲14號簡體字
    font = FontProperties(fname=r"C:\Windows\Fonts\simsun.ttc", size=14)
    # 將fig畫布分隔成1行1列,不共享x軸和y軸,fig畫布的大小爲(13,8)
    # 當nrows=2,ncols=2時,代表fig畫布被分爲4個區域,axs[0][0]代表第一行第一個區域
    fig, axs = plt.subplots(nrows=2, ncols=2, sharex=False, sharey=False, figsize=(13, 8))
    # 獲取datingLabels的行數作爲label的個數
    # numberOfLabels = len(datingLabels)
    # label的顏色配置矩陣
    LabelsColors = []
    for i in datingLabels:
        # didntLike
        if i == "didntLike":
            LabelsColors.append('black')
        # smallDoses
        if i == "smallDoses":
            LabelsColors.append('orange')
        # largeDoses
        if i == "largeDoses":
            LabelsColors.append('red')

    # 圖一:每年獲得的飛行常客里程數與玩視頻遊戲所消耗時間佔比
    # 畫出散點圖,以datingDataMat矩陣第一列爲x,第二列爲y,散點大小爲15, 透明度爲0.5
    axs[0][0].scatter(x=datingDataMat[:, 0], y=datingDataMat[:, 1], color=LabelsColors, s=15, alpha=.5)
    # 設置標題,x軸label, y軸label
    axs0_title_text = axs[0][0].set_title(u'每年獲得的飛行常客里程數與玩視頻遊戲所消耗時間佔比', FontProperties=font)
    axs0_xlabel_text = axs[0][0].set_xlabel(u'每年獲得的飛行常客里程數', FontProperties=font)
    axs0_ylabel_text = axs[0][0].set_ylabel(u'玩視頻遊戲所消耗時間佔比', FontProperties=font)
    plt.setp(axs0_title_text, size=9, weight='bold', color='red')
    plt.setp(axs0_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(axs0_ylabel_text, size=7, weight='bold', color='black')

    # 圖二:每年獲得的飛行常客里程數與每週消費的冰淇淋公升數
    # 畫出散點圖,以datingDataMat矩陣第一列爲x,第三列爲y,散點大小爲15, 透明度爲0.5
    axs[0][1].scatter(x=datingDataMat[:, 0], y=datingDataMat[:, 2], color=LabelsColors, s=15, alpha=.5)
    # 設置標題,x軸label, y軸label
    axs1_title_text = axs[0][1].set_title(u'每年獲得的飛行常客里程數與每週消費的冰淇淋公升數', FontProperties=font)
    axs1_xlabel_text = axs[0][1].set_xlabel(u'每年獲得的飛行常客里程數', FontProperties=font)
    axs1_ylabel_text = axs[0][1].set_ylabel(u'每週消費的冰淇淋公升數', FontProperties=font)
    plt.setp(axs1_title_text, size=9, weight='bold', color='red')
    plt.setp(axs1_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(axs1_ylabel_text, size=7, weight='bold', color='black')

    # 圖三:玩視頻遊戲所消耗時間佔比與每週消費的冰淇淋公升數
    # 畫出散點圖,以datingDataMat矩陣第二列爲x,第三列爲y,散點大小爲15, 透明度爲0.5
    axs[1][0].scatter(x=datingDataMat[:, 1], y=datingDataMat[:, 2], color=LabelsColors, s=15, alpha=.5)
    # 設置標題,x軸label, y軸label
    axs2_title_text = axs[1][0].set_title(u'玩視頻遊戲所消耗時間佔比與每週消費的冰淇淋公升數', FontProperties=font)
    axs2_xlabel_text = axs[1][0].set_xlabel(u'玩視頻遊戲所消耗時間佔比', FontProperties=font)
    axs2_ylabel_text = axs[1][0].set_ylabel(u'每週消費的冰淇淋公升數', FontProperties=font)
    plt.setp(axs2_title_text, size=9, weight='bold', color='red')
    plt.setp(axs2_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(axs2_ylabel_text, size=7, weight='bold', color='black')

    # 設置圖例
    didntLike = mlines.Line2D([], [], color='black', marker='.', markersize=6, label='didntLike')
    smallDoses = mlines.Line2D([], [], color='orange', marker='.', markersize=6, label='smallDoses')
    largeDoses = mlines.Line2D([], [], color='red', marker='.', markersize=6, label='largeDoses')
    # 添加圖例
    axs[0][0].legend(handles=[didntLike, smallDoses, largeDoses])
    axs[0][1].legend(handles=[didntLike, smallDoses, largeDoses])
    axs[1][0].legend(handles=[didntLike, smallDoses, largeDoses])
    # 顯示圖片
    plt.show()


def autoNorm(dataSet):
    """
    函數說明:對數據進行歸一化

    Parameters:
        dataSet - 特徵矩陣

    Returns:
        normDataSet - 歸一化後的特徵矩陣
        ranges - 數據範圍
        minVals - 數據最小值
    """
    # 獲取數據的最小值
    minVals = dataSet.min(0)
    # 獲取數據的最大值
    maxVals = dataSet.max(0)
    # 最大值和最小值的範圍
    ranges = maxVals - minVals
    # numpy函數shape[0]返回dataSet的行數
    m = dataSet.shape[0]
    # 原始值減去最小值(x-xmin)
    normDataSet = dataSet - np.tile(minVals, (m, 1))
    # 差值處以最大值和最小值的差值(x-xmin)/(xmax-xmin)
    normDataSet = normDataSet / np.tile(ranges, (m, 1))
    # 歸一化數據結果,數據範圍,最小值
    return normDataSet, ranges, minVals


def datingClassTest():
    """
    函數說明:分類器測試函數

    Parameters:
        None

    Returns:
        normDataSet - 歸一化後的特徵矩陣
        ranges - 數據範圍
        minVals - 數據最小值

    """
    # 測試數據:取所有數據的10% hoRatio越小,錯誤率越低
    hoRatio = 0.10

    # 將返回的特徵矩陣和分類向量分別存儲到datingDataMat和datingLabels中
    datingDataMat, datingLabels = file2matrix("datingTestSet.txt")
    # 數據歸一化,返回歸一化數據結果,數據範圍,最小值
    normMat, ranges, minVals = autoNorm(datingDataMat)

    # 獲取normMat的行數
    m = normMat.shape[0]
    # 10%的測試數據的個數
    numTestVecs = int(m * hoRatio)
    # 分類錯誤計數
    errorCount = 0.0
    for i in range(numTestVecs):
        # 前numTestVecs個數據作爲測試集,後m-numTestVecs個數據作爲訓練集
        # k選擇label數+1(結果比較好)
        classifierResult = classify0(normMat[i, :],                 # 遍歷數據集前m個樣本
                                     normMat[numTestVecs:m, :],     # 數據集從m開始爲訓練樣本
                                     datingLabels[numTestVecs:m],   # 標籤集從m開始爲訓練樣本的真實標籤
                                     4)
        print("分類結果:%s\t真實類別:%s" % (classifierResult, datingLabels[i]))
        if classifierResult != datingLabels[i]:
            errorCount += 1.0
    print("錯誤率:%f%%" % (errorCount / float(numTestVecs) * 100))


def classifyPerson():
    """
    函數說明:通過輸入一個人的三種特徵,進行分類輸出

    Parameters:
        None

    Returns:
        None
    """
    # 三維特徵用戶輸入
    percentTats = float(input("每年獲得的飛行常客里程數:"))
    ffMiles = float(input("玩視頻遊戲所消耗時間百分比:"))
    iceCream = float(input("每週消費的冰淇淋公升數:"))

    # 打開數據文件並處理數據
    datingDataMat, datingLabels = file2matrix("datingTestSet.txt")
    # 訓練集歸一化
    normMat, ranges, minVals = autoNorm(datingDataMat)
    # 生成NumPy數組,測試集
    inArr = np.array([percentTats, ffMiles, iceCream])
    # 測試集歸一化
    norminArr = (inArr - minVals) / ranges
    # 返回分類結果
    classifierResult = classify0(norminArr, normMat, datingLabels, 4)
    # 打印結果
    print("你可能%s這個人" % (classifierResult))


if __name__ == '__main__':

    # step1: 獲取數據,格式化數據,圖表化
    datingDataMat, datingLabels = file2matrix('datingTestSet.txt')
    print(datingDataMat)
    print(datingLabels)
    showdatas(datingDataMat, datingLabels)

    # step2:數據預處理(歸一化)
    normDataSet, ranges, minVals = autoNorm(datingDataMat)
    print(normDataSet)
    print(ranges)
    print(minVals)

    # step3:測試算法
    datingClassTest()

    # step4: 個人約會喜好的預測
    classifyPerson()

二、運行結果

step1 的運行結果
在這裏插入圖片描述
在這裏插入圖片描述
step2 運行結果
在這裏插入圖片描述
step3 運行結果
在這裏插入圖片描述
step4 運行結果
在這裏插入圖片描述

三、資源

datingTestSet.txt下載

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章