天天看點

KNN實戰——約會網站配對效果判定

 一 約會網站配對效果判定

上一小結學習了簡單的k-近鄰算法的實作方法,但是這并不是完整的k-近鄰算法流程,k-近鄰算法的一般流程:

  1. 收集資料:可以使用爬蟲進行資料的收集,也可以使用第三方提供的免費或收費的資料。一般來講,資料放在txt文本檔案中,按照一定的格式進行存儲,便于解析及處理。
  2. 準備資料:使用Python解析、預處理資料。
  3. 分析資料:可以使用很多方法對資料進行分析,例如使用Matplotlib将資料可視化。
  4. 測試算法:計算錯誤率。
  5. 使用算法:錯誤率在可接受範圍内,就可以運作k-近鄰算法進行分類。

    已經了解了k-近鄰算法的一般流程,下面開始進入實戰内容。

 實戰背景

    海倫女士一直使用線上約會網站尋找适合自己的約會對象。盡管約會網站會推薦不同的任選,但她并不是喜歡每一個人。經過一番總結,她發現自己交往過的人可以進行如下分類:

  • 不喜歡的人
  • 魅力一般的人
  • 極具魅力的人

       海倫收集約會資料已經有了一段時間,她把這些資料存放在文本檔案datingTestSet.txt中,每個樣本資料占據一行,總共有1000行。

                 data資料集

       海倫收集的樣本資料主要包含以下3種特征:

  • 每年獲得的飛行常客裡程數
  • 玩視訊遊戲所消耗時間百分比
  • 每周消費的冰淇淋公升數 

打開txt格式的檔案,資料格式如圖2.1所示

KNN實戰——約會網站配對效果判定

                                                               資料解析

       在将上述特征資料輸入到分類器前,必須将待處理的資料的格式改變為分類器可以接收的格式。分類器接收的資料是什麼格式的?從上小結已經知道,要将資料分類兩部分,即特征矩陣和對應的分類标簽向量。資料解析代碼如下:

# -*- coding: UTF-8 -*-
import numpy as np
"""
函數說明:打開并解析檔案,對資料進行分類:1 代表喜歡,2代表魅力一般,3代表極具魅力
filename -- 檔案名
Mat -- 特征向量
classLabelVector - 分類Label向量
Modify:
    2017-11-5
"""
def file(filename):
    fr = open(filename)               #打開檔案
    lines = fr.readlines()            #讀取檔案的所有資料
    numberlines = len(lines)          #檔案的行數
    Mat = np.zeros((numberlines,3))    #傳回來一個給定形狀和類型的用0填充的數組,傳回一個numbeilines行3列的數組
    classLabelVector = []             #定義一個清單
    index= 0                          #行的索引
    for line in lines :
        line = line.strip()           #str.strip()就是把這個字元串頭和尾的空格,以及位于頭尾的\n \t之類給删掉
        listFromLine = line.split('\t')   #line.split()字元串内按照空格進行分割
        Mat[index,:] = listFromLine[0:3]   #将資料前三列提取出來,index作為每一行的索引
        if listFromLine[-1] == 'didntLike':         #根據文本中标記的喜歡的程度進行分類,1代表不喜歡,2代表魅力一般,3代表極具魅力
            classLabelVector.append(1)              #在行後面進行标記,标記1,2,3
        elif listFromLine[-1] == 'smallDoses':
            classLabelVector.append(2)
        elif listFromLine[-1] == 'largeDoses':
            classLabelVector.append(3)
        index += 1
    return Mat, classLabelVector
if __name__ == '__main__':
    filename = "data.txt"   #打開的檔案名
    datingDataMat, datingLabels = file(filename)   #打開并處理資料
    print(datingDataMat)
    print(datingLabels)
           

運作結果如圖所示:

KNN實戰——約會網站配對效果判定

        可以看到,我們已經順利導入資料,并對資料進行解析,格式化為分類器需要的資料格式。接着我們需要了解資料的真正含義。可以通過友好、直覺的圖形化的方式觀察資料。

資料可視化

編寫名為showdatas的函數,用來将資料可視化。編寫代碼如下:

from matplotlib.font_manager import FontProperties
import matplotlib.lines as mlines
import matplotlib.pyplot as plt
import numpy as np
"""
函數說明:可視化資料
Parameters:
    datingDataMat - 特征矩陣
    datingLabels - 分類Label
Returns:
    無
Modify:
    2017-11-05
"""
def showdata(datingDataMat,datingLabels):
    font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)     #設定漢字格式
    fig, axs = plt.subplots(nrows=2, ncols=2,sharex=False, sharey=False, figsize=(13,8))  #将fig畫布分隔成1行1列,不共享x軸和y軸,fig畫布的大小為(13,8)
    numberOfLabels = len(datingLabels)                                                    #當nrow=2,nclos=2時,代表fig畫布被分為四個區域,axs[0][0]表示第一行第一個區域
    LabelsColors = []
    for i in datingLabels:
        if i == 1:
            LabelsColors.append('black')
        if i == 2:
            LabelsColors.append('orange')
        if i == 3:
            LabelsColors.append('red')                    #畫出散點圖,以datingDataMat矩陣的第一(飛行常客例程)、第二列(玩遊戲)資料畫散點資料,散點大小為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矩陣的第一(飛行常客例程)、第三列(冰激淩)資料畫散點資料,散點大小為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矩陣的第二(玩遊戲)、第三列(冰激淩)資料畫散點資料,散點大小為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()
           

資料分析整體的代碼如下:

# -*- coding: UTF-8 -*-
from matplotlib.font_manager import FontProperties
import matplotlib.lines as mlines
import matplotlib.pyplot as plt
import numpy as np
"""
函數說明:可視化資料
Parameters:
    datingDataMat - 特征矩陣
    datingLabels - 分類Label
Returns:
    無
Modify:
    2017-11-05
"""
def file(filename):
    fr = open(filename)               #打開檔案
    lines = fr.readlines()            #讀取檔案的所有資料
    numberlines = len(lines)          #檔案的行數
    Mat = np.zeros((numberlines,3))    #傳回來一個給定形狀和類型的用0填充的數組,傳回一個numbeilines行3列的數組
    classLabelVector = []             #定義一個清單
    index= 0                          #行的索引
    for line in lines :
        line = line.strip()           #str.strip()就是把這個字元串頭和尾的空格,以及位于頭尾的\n \t之類給删掉
        listFromLine = line.split('\t')   #line.split()字元串内按照空格進行分割
        Mat[index,:] = listFromLine[0:3]   #将資料前三列提取出來,index作為每一行的索引
        if listFromLine[-1] == 'didntLike':         #根據文本中标記的喜歡的程度進行分類,1代表不喜歡,2代表魅力一般,3代表極具魅力
            classLabelVector.append(1)              #在行後面進行标記,标記1,2,3
        elif listFromLine[-1] == 'smallDoses':
            classLabelVector.append(2)
        elif listFromLine[-1] == 'largeDoses':
            classLabelVector.append(3)
        index += 1
    return Mat, classLabelVector
def showdata(datingDataMat,datingLabels):
    font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)     #設定漢字格式
    fig, axs = plt.subplots(nrows=2, ncols=2,sharex=False, sharey=False, figsize=(13,8))  #将fig畫布分隔成1行1列,不共享x軸和y軸,fig畫布的大小為(13,8)
    numberOfLabels = len(datingLabels)                                                    #當nrow=2,nclos=2時,代表fig畫布被分為四個區域,axs[0][0]表示第一行第一個區域
    LabelsColors = []
    for i in datingLabels:
        if i == 1:
            LabelsColors.append('black')
        if i == 2:
            LabelsColors.append('orange')
        if i == 3:
            LabelsColors.append('red')                    #畫出散點圖,以datingDataMat矩陣的第一(飛行常客例程)、第二列(玩遊戲)資料畫散點資料,散點大小為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矩陣的第一(飛行常客例程)、第三列(冰激淩)資料畫散點資料,散點大小為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矩陣的第二(玩遊戲)、第三列(冰激淩)資料畫散點資料,散點大小為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()
if __name__ == '__main__':
    #打開的檔案名
    filename = "data.txt"
    #打開并處理資料
    datingDataMat, datingLabels = file(filename)
    showdata(datingDataMat, datingLabels)
           

運作結果如圖所示:

KNN實戰——約會網站配對效果判定

       通過資料可以很直覺的發現資料的規律,比如以玩遊戲所消耗時間占比與每年獲得的飛行常客裡程數,隻考慮這二維的特征資訊,給我的感覺就是海倫喜歡有生活品質的男人。為什麼這麼說呢?每年獲得的飛行常客裡程數表明,海倫喜歡能享受飛行常客獎勵計劃的男人,但是不能經常坐飛機,疲于奔波,滿世界飛。同時,這個男人也要玩視訊遊戲,并且占一定時間比例。能到處飛,又能經常玩遊戲的男人是什麼樣的男人?很顯然,有生活品質,并且生活悠閑的人。我的分析,僅僅是通過可視化的資料總結的個人看法。我想,每個人的感受應該也是不盡相同。

資料歸一化

下面給出四個樣本,計算樣本之間的距離

KNN實戰——約會網站配對效果判定

計算方法如圖所示。

KNN實戰——約會網站配對效果判定

       我們很容易發現,上面方程中數字內插補點最大的屬性對計算結果的影響最大,也就是說,每年擷取的飛行常客裡程數對于計算結果的影響将遠遠大于表2.1中其他兩個特征-玩視訊遊戲所耗時間占比和每周消費冰淇淋公斤數的影響。而産生這種現象的唯一原因,僅僅是因為飛行常客裡程數遠大于其他特征值。但海倫認為這三種特征是同等重要的,是以作為三個等權重的特征之一,飛行常客裡程數并不應該如此嚴重地影響到計算結果。

       在處理這種不同取值範圍的特征值時,我們通常采用的方法是将數值歸一化,如将取值範圍處理為0到1或者-1到1之間。下面的公式可以将任意取值範圍的特征值轉化為0到1區間内的值:

newValue = (oldValue - min) / (max - min)
           

其中min和max分别是資料集中的最小特征值和最大特征值。雖然改變數值取值範圍增加了分類器的複雜度,但為了得到準确結果,我們必須這樣做,編寫名為autoNorm的函數,用該函數自動将資料歸一化,代碼如下:

函數說明:對資料進行歸一化

Parameters:
    dataSet - 特征矩陣
Returns:
    normDataSet - 歸一化後的特征矩陣
    ranges - 資料範圍
    minVals - 資料最小值

Modify:
    2017-11-05
"""
def autoNorm(dataSet):
    minVals = dataSet.min(0)           #獲得資料的最小值
    maxVals = dataSet.max(0)           #獲得資料的最大值
    ranges = maxVals - minVals         #最大值和最小值的範圍
    normDataSet = np.zeros(np.shape(dataSet))               #shape(dataSet)傳回dataSet的矩陣行列數
    m = dataSet.shape[0]                                    #傳回dataSet的行數
    normDataSet = dataSet - np.tile(minVals, (m, 1))        #原始值減去最小值
    normDataSet = normDataSet / np.tile(ranges, (m, 1))     #除以最大和最小值的差,得到歸一化資料
    return normDataSet, ranges, minVals                    #傳回歸一化資料結果,資料範圍,最小值
           

是以,資料歸一化部分的整體代碼如下:

# -*- coding: UTF-8 -*-
from matplotlib.font_manager import FontProperties
import matplotlib.lines as mlines
import matplotlib.pyplot as plt
import numpy as np
"""
函數說明:可視化資料
Parameters:
    datingDataMat - 特征矩陣
    datingLabels - 分類Label
Returns:
    無
Modify:
    2017-11-05
"""
def file(filename):
    fr = open(filename)               #打開檔案
    lines = fr.readlines()            #讀取檔案的所有資料
    numberlines = len(lines)          #檔案的行數
    Mat = np.zeros((numberlines,3))    #傳回來一個給定形狀和類型的用0填充的數組,傳回一個numbeilines行3列的數組
    classLabelVector = []             #定義一個清單
    index= 0                          #行的索引
    for line in lines :
        line = line.strip()           #str.strip()就是把這個字元串頭和尾的空格,以及位于頭尾的\n \t之類給删掉
        listFromLine = line.split('\t')   #line.split()字元串内按照空格進行分割
        Mat[index,:] = listFromLine[0:3]   #将資料前三列提取出來,index作為每一行的索引
        if listFromLine[-1] == 'didntLike':         #根據文本中标記的喜歡的程度進行分類,1代表不喜歡,2代表魅力一般,3代表極具魅力
            classLabelVector.append(1)              #在行後面進行标記,标記1,2,3
        elif listFromLine[-1] == 'smallDoses':
            classLabelVector.append(2)
        elif listFromLine[-1] == 'largeDoses':
            classLabelVector.append(3)
        index += 1
    return Mat, classLabelVector
def showdata(datingDataMat,datingLabels):
    font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)     #設定漢字格式
    fig, axs = plt.subplots(nrows=2, ncols=2,sharex=False, sharey=False, figsize=(13,8))  #将fig畫布分隔成1行1列,不共享x軸和y軸,fig畫布的大小為(13,8)
    numberOfLabels = len(datingLabels)                                                    #當nrow=2,nclos=2時,代表fig畫布被分為四個區域,axs[0][0]表示第一行第一個區域
    LabelsColors = []
    for i in datingLabels:
        if i == 1:
            LabelsColors.append('black')
        if i == 2:
            LabelsColors.append('orange')
        if i == 3:
            LabelsColors.append('red')                    #畫出散點圖,以datingDataMat矩陣的第一(飛行常客例程)、第二列(玩遊戲)資料畫散點資料,散點大小為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矩陣的第一(飛行常客例程)、第三列(冰激淩)資料畫散點資料,散點大小為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矩陣的第二(玩遊戲)、第三列(冰激淩)資料畫散點資料,散點大小為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()
    """
函數說明:對資料進行歸一化

Parameters:
    dataSet - 特征矩陣
Returns:
    normDataSet - 歸一化後的特征矩陣
    ranges - 資料範圍
    minVals - 資料最小值

Modify:
    2017-11-05
"""
def autoNorm(dataSet):
    minVals = dataSet.min(0)           #獲得資料的最小值
    maxVals = dataSet.max(0)           #獲得資料的最大值
    ranges = maxVals - minVals         #最大值和最小值的範圍
    normDataSet = np.zeros(np.shape(dataSet))               #shape(dataSet)傳回dataSet的矩陣行列數
    m = dataSet.shape[0]                                    #傳回dataSet的行數
    normDataSet = dataSet - np.tile(minVals, (m, 1))        #原始值減去最小值
    normDataSet = normDataSet / np.tile(ranges, (m, 1))     #除以最大和最小值的差,得到歸一化資料
    return normDataSet, ranges, minVals                    #傳回歸一化資料結果,資料範圍,最小值

if __name__ == '__main__':
    filename = "data.txt"                           #打開的檔案名
    datingDataMat, datingLabelsa = file(filename)      #打開并處理資料
    normDataSet, ranges, minVals = autoNorm(datingDataMat)
    print(normDataSet)
    print(ranges)
    print(minVals)
           

運作結果如圖所示:

KNN實戰——約會網站配對效果判定

從圖中的運作結果可以看到,我們已經順利将資料歸一化了,并且求出了資料的取值範圍和資料的最小值,這兩個值是在分類的時候需要用到的,直接先求解出來,也算是對資料預處理了。

測試算法:驗證分類器

       機器學習算法一個很重要的工作就是評估算法的正确率,通常我們隻提供已有資料的90%作為訓練樣本來訓練分類器,而使用其餘的10%資料去測試分類器,檢測分類器的正确率。需要注意的是,10%的測試資料應該是随機選擇的,由于海倫提供的資料并沒有按照特定目的來排序,是以我麼你可以随意選擇10%資料而不影響其随機性。

       為了測試分類器效果,在kNN_test02.py檔案中建立函數datingClassTest,編寫代碼如下:

# -*- coding: UTF-8 -*-t
import numpy as np
import operator
def file(filename):
    fr = open(filename)               #打開檔案
    lines = fr.readlines()            #讀取檔案的所有資料
    numberlines = len(lines)          #檔案的行數
    Mat = np.zeros((numberlines,3))    #傳回來一個給定形狀和類型的用0填充的數組,傳回一個numbeilines行3列的數組
    classLabelVector = []             #定義一個清單
    index= 0                          #行的索引
    for line in lines :
        line = line.strip()           #str.strip()就是把這個字元串頭和尾的空格,以及位于頭尾的\n \t之類給删掉
        listFromLine = line.split('\t')   #line.split()字元串内按照空格進行分割
        Mat[index,:] = listFromLine[0:3]   #将資料前三列提取出來,index作為每一行的索引
        if listFromLine[-1] == 'didntLike':         #根據文本中标記的喜歡的程度進行分類,1代表不喜歡,2代表魅力一般,3代表極具魅力
            classLabelVector.append(1)              #在行後面進行标記,标記1,2,3
        elif listFromLine[-1] == 'smallDoses':
            classLabelVector.append(2)
        elif listFromLine[-1] == 'largeDoses':
            classLabelVector.append(3)
        index += 1
    return Mat, classLabelVector
def KNN(test,data,labels,k):
    dataSize = data.shape[0]                 #測試資料的行數
    Mat=np.tile(test,(dataSize,1))-data   #numpy.tile()是把數組沿各個方向複制的函數,此句相當于把Y軸複制dataSize倍,X不變
    squMat = Mat**2  # 二維特征相減後的平方
    Distance = squMat.sum(axis=1)  #sum=0,普通相加,sum(axis=0)每一列相加,sum(axis=1),每一行相加
    Dis = Distance**0.5
    sortDis = Dis.argsort()       #元素從小到大排序後的索引值
    classCount = {}               # 定義一個記錄類别次數的字典
    for i in range(k):
        voteIlabel = labels[sortDis[i]]   #取出前k個元素的類别
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1    #dict.get(key,default=None),
    sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)  #key=operator.itemgetter(1)根據字典的值進行排序
    return sortedClassCount[0][0]

def autoNorm(dataSet):
    minVals = dataSet.min(0)           #獲得資料的最小值
    maxVals = dataSet.max(0)           #獲得資料的最大值
    ranges = maxVals - minVals         #最大值和最小值的範圍
    normDataSet = np.zeros(np.shape(dataSet))               #shape(dataSet)傳回dataSet的矩陣行列數
    m = dataSet.shape[0]                                    #傳回dataSet的行數
    normDataSet = dataSet - np.tile(minVals, (m, 1))        #原始值減去最小值
    normDataSet = normDataSet / np.tile(ranges, (m, 1))     #除以最大和最小值的差,得到歸一化資料
    return normDataSet, ranges, minVals                    #傳回歸一化資料結果,資料範圍,最小值

def datingClassTest():
    #打開的檔案名
    filename = "data.txt"
    #将傳回的特征矩陣和分類向量分别存儲到datingDataMat和datingLabels中
    datingDataMat, datingLabels = file(filename)
    #取所有資料的百分之十
    hoRatio = 0.10
    #資料歸一化,傳回歸一化後的矩陣,資料範圍,資料最小值
    normMat, ranges, minVals = autoNorm(datingDataMat)
    #獲得normMat的行數
    m = normMat.shape[0]
    #百分之十的測試資料的個數
    numTestVecs = int(m * hoRatio)
    #分類錯誤計數
    errorCount = 0.0

    for i in range(numTestVecs):
        #前numTestVecs個資料作為測試集,後m-numTestVecs個資料作為訓練集
        classifierResult = KNN(normMat[i,:], normMat[numTestVecs:m,:],
            datingLabels[numTestVecs:m], 4)
        print("分類結果:%d\t真實類别:%d" % (classifierResult, datingLabels[i]))
        if classifierResult != datingLabels[i]:
            errorCount += 1.0
    print("錯誤率:%f%%" %(errorCount/float(numTestVecs)*100))

if __name__ == '__main__':
    datingClassTest()
           

運作效果如下:

KNN實戰——約會網站配對效果判定

 從圖中驗證分類器結果中可以看出,錯誤率是4%,這是一個想當不錯的結果。我們可以改變函數datingClassTest内變量hoRatio和分類器k的值,檢測錯誤率是否随着變量值的變化而增加。依賴于分類算法、資料集和程式設定,分類器的輸出結果可能有很大的不同。

使用算法搭建完整可用系統 檔案中建立函數classifyPerson,代碼如下:

# -*- coding: UTF-8 -*-t
import numpy as np
import operator
def file(filename):
    fr = open(filename)               #打開檔案
    lines = fr.readlines()            #讀取檔案的所有資料
    numberlines = len(lines)          #檔案的行數
    Mat = np.zeros((numberlines,3))    #傳回來一個給定形狀和類型的用0填充的數組,傳回一個numbeilines行3列的數組
    classLabelVector = []             #定義一個清單
    index= 0                          #行的索引
    for line in lines :
        line = line.strip()           #str.strip()就是把這個字元串頭和尾的空格,以及位于頭尾的\n \t之類給删掉
        listFromLine = line.split('\t')   #line.split()字元串内按照空格進行分割
        Mat[index,:] = listFromLine[0:3]   #将資料前三列提取出來,index作為每一行的索引
        if listFromLine[-1] == 'didntLike':         #根據文本中标記的喜歡的程度進行分類,1代表不喜歡,2代表魅力一般,3代表極具魅力
            classLabelVector.append(1)              #在行後面進行标記,标記1,2,3
        elif listFromLine[-1] == 'smallDoses':
            classLabelVector.append(2)
        elif listFromLine[-1] == 'largeDoses':
            classLabelVector.append(3)
        index += 1
    return Mat, classLabelVector
def KNN(test,data,labels,k):
    dataSize = data.shape[0]                 #測試資料的行數
    Mat=np.tile(test,(dataSize,1))-data   #numpy.tile()是把數組沿各個方向複制的函數,此句相當于把Y軸複制dataSize倍,X不變
    squMat = Mat**2  # 二維特征相減後的平方
    Distance = squMat.sum(axis=1)  #sum=0,普通相加,sum(axis=0)每一列相加,sum(axis=1),每一行相加
    Dis = Distance**0.5
    sortDis = Dis.argsort()       #元素從小到大排序後的索引值
    classCount = {}               # 定義一個記錄類别次數的字典
    for i in range(k):
        voteIlabel = labels[sortDis[i]]   #取出前k個元素的類别
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1    #dict.get(key,default=None),
    sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)  #key=operator.itemgetter(1)根據字典的值進行排序
    return sortedClassCount[0][0]
def autoNorm(dataSet):
    minVals = dataSet.min(0)           #獲得資料的最小值
    maxVals = dataSet.max(0)           #獲得資料的最大值
    ranges = maxVals - minVals         #最大值和最小值的範圍
    normDataSet = np.zeros(np.shape(dataSet))               #shape(dataSet)傳回dataSet的矩陣行列數
    m = dataSet.shape[0]                                    #傳回dataSet的行數
    normDataSet = dataSet - np.tile(minVals, (m, 1))        #原始值減去最小值
    normDataSet = normDataSet / np.tile(ranges, (m, 1))     #除以最大和最小值的差,得到歸一化資料
    return normDataSet, ranges, minVals                    #傳回歸一化資料結果,資料範圍,最小值
def datingClassTest():
    #打開的檔案名
    filename = "data.txt"
    #将傳回的特征矩陣和分類向量分别存儲到datingDataMat和datingLabels中
    datingDataMat, datingLabels = file(filename)
    #取所有資料的百分之十
    hoRatio = 0.10
    #資料歸一化,傳回歸一化後的矩陣,資料範圍,資料最小值
    normMat, ranges, minVals = autoNorm(datingDataMat)
    #獲得normMat的行數
    m = normMat.shape[0]
    #百分之十的測試資料的個數
    numTestVecs = int(m * hoRatio)
    #分類錯誤計數
    errorCount = 0.0

    for i in range(numTestVecs):
        #前numTestVecs個資料作為測試集,後m-numTestVecs個資料作為訓練集
        classifierResult = KNN(normMat[i,:], normMat[numTestVecs:m,:],
            datingLabels[numTestVecs:m], 4)
        print("分類結果:%d\t真實類别:%d" % (classifierResult, datingLabels[i]))
        if classifierResult != datingLabels[i]:
            errorCount += 1.0
    print("錯誤率:%f%%" %(errorCount/float(numTestVecs)*100))
def classifyPerson():
    #輸出結果
    resultList = ['讨厭','有些喜歡','非常喜歡']
    #三維特征使用者輸入
    precentTats = float(input("玩視訊遊戲所耗時間百分比:"))
    ffMiles = float(input("每年獲得的飛行常客裡程數:"))
    iceCream = float(input("每周消費的冰激淋公升數:"))
    #打開的檔案名
    filename = "data.txt"
    #打開并處理資料
    datingDataMat, datingLabels = file(filename)
    #訓練集歸一化
    normMat, ranges, minVals = autoNorm(datingDataMat)
    #生成NumPy數組,測試集
    inArr = np.array([precentTats, ffMiles, iceCream])
    #測試集歸一化
    norminArr = (inArr - minVals) / ranges
    #傳回分類結果
    classifierResult = KNN(norminArr, normMat, datingLabels, 3)
    #列印結果
    print("你可能%s這個人" % (resultList[classifierResult-1]))
if __name__ == '__main__':
    classifyPerson()
           

我們我們可以給海倫一個小段程式,通過該程式海倫會在約會網站上找到某個人并輸入他的資訊。程式會給出她對男方喜歡程度的預測值。

KNN實戰——約會網站配對效果判定