1. 程式人生 > >2、K-近鄰演算法之約會網站預測

2、K-近鄰演算法之約會網站預測

k-近鄰演算法概述

定義:簡單地說,k近鄰演算法採用測量不同特徵值之間的距離進行分類
原理:存在一個樣本資料集合,也稱作訓練樣本集,並且樣本集中每個資料都存在標籤,即我們知道樣本集中每一資料與所屬分類的對應關係,輸入沒有標籤的新資料後,將新資料的特徵與樣本集中資料對應的特徵進行比較,然後計算出演算法提取樣本集中特徵最相似資料的分類標籤

演算法小解

這裡寫圖片描述

如上圖,已知六部電影,他們分別有自己的資料與標籤,即通過打鬥次數與親吻次數判斷出是愛情片還是動作片。現在另有一部電影,已知親吻次數與打鬥次數要推斷出其是愛情片還是動作片。我們在座標軸上標出其位置,然後找出與它距離最近的三部電影,發現這三部電影都為愛情片,則推斷出此電影為愛情片。

數學公式

這裡寫圖片描述

已知圖上兩點A,B 則要求AB兩點的距離 則需要公式

d=(xA0xB0)2+(xA1xB1)2
則A、B 的距離為d=(24)2+(42)2

演算法實現


from numpy import *
import operator # 運算子模組

def createDataSet():
    group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
    labels = ['A','A','B','B']
    return
group,labels group,labels = createDataSet() print("group:",group) print("labels",labels) # k-近鄰演算法 # 四個引數 用於分類的輸入向量 int X,輸入的訓練樣本集dataSet,標籤向量labels,k為選擇最近鄰居的數目 # def classify0(inX,dataSet,labels,k): # dataSet.shape[0] = 4 ,dataSet.shape[1] = 2 表示4行2列矩陣 dataSetSize = dataSet.shape[0] # tile()用來對陣列進行復制,inX表示要複製的陣列, 第一個引數 dataSetSize 表示要在行方向複製4次 列方向1次
diffMat = tile(inX,(dataSetSize,1)) - dataSet #print (tile(inX,(dataSetSize,1))) sqDiffMat = diffMat ** 2 # 求平方,可以直接運算元組,相對應的數字分別平方 print(sqDiffMat) # 如果axis=0,則沿著縱軸進行操作;axis=1,則沿著橫軸進行操作 # 如果是多維 設axis=i,則numpy沿著第i個下標變化的放下進行操作 sqDistance = sqDiffMat.sum(axis=1) # 對結果進行開平方 distances = sqDistance**0.5 # argsort函式返回的是陣列值從小到大的索引值 sortDistance = distances.argsort() classCount = { } for i in range(k): voteIlabel = labels[sortDistance[i]] classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 # sorted 可以對所有可迭代的物件進行排序操作 且返回的是一個新的list # iterable --可迭代物件 key--用來比較的元素,只有一個引數,引數就是可迭代物件中的一個元素來排序 True--降序 # operator.itemgetter(1) 獲取域中的第二個值 sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True) return sortedClassCount[0][0] result = classify0([1,1],group,labels,3) print(result)

簡單案例

案例分析

在約會網站中尋找適合自己的物件,儘管約會網站會推薦不同的人選,但它沒有找到喜歡的人,經過一番總結,她發現曾交往過的三種類型的人

  • 不喜歡的人
  • 魅力一般的人
  • 極具魅力的人
    現在使用者希望我們網站更好地幫助她將匹配物件劃分到確切到分類中

資料格式

資料儲存在檔案datingTestSet.txt 中,每個樣本資料佔據一行,總共有1000行,以下是樣本特徵

  • 每年獲得的飛行常客里程數
  • 玩視訊遊戲所耗時間百分比
  • 每週消費的冰激凌公升數

程式碼實現

def file2matrix1(filename):
    fr = open(filename)
    arrayLines = fr.readlines() # 讀取資料
    numberLines = len(arrayLines) # 記錄行數
    returnMat = zeros((numberLines,3))
    classlabelVector = []
    index = 0
    for line in arrayLines:
        line = line.strip()
        listFromLine = line.split('\t')
        returnMat[index,:] = listFromLine[0:3]
        classlabelVector.append(int(listFromLine[-1]))
        index +=1
    return returnMat,classlabelVector
def file2matrix(filename):
    love_dictionary={'largeDoses':3, 'smallDoses':2, 'didntLike':1}
    fr = open(filename)
    arrayOLines = fr.readlines()
    numberOfLines = len(arrayOLines)            #get the number of lines in the file
    # 建立一個1000行 3列矩陣 並用0來填充
    returnMat = zeros((numberOfLines,3))        #prepare matrix to return
    # 建立 目標變數列表
    classLabelVector = []                       #prepare labels return
    index = 0
    for line in arrayOLines:
        line = line.strip() # 擷取掉回車字元
        listFromLine = line.split('\t')
        returnMat[index,:] = listFromLine[0:3] # 將檔案切割後前三列儲存到 矩陣中
        print(returnMat)
        if(listFromLine[-1].isdigit()): # isdigit() 檢查是否為10進位制數字
            classLabelVector.append(int(listFromLine[-1])) # 將最右列元素新增到列表中
        else:
            classLabelVector.append(love_dictionary.get(listFromLine[-1])) # 通過字典轉換為數字儲存到列表
        index += 1
    return returnMat,classLabelVector

datingDataMat,datingLabels = file2matrix("datingTestSet.txt")

import matplotlib
import matplotlib.pyplot as plt
fig = plt.figure() # 建立畫板
ax = fig.add_subplot(111) # 新增一個子圖
#    x[:,1]獲取第二列作為一維陣列,x[:,0]獲取第一列作為一維陣列
ax.scatter(datingDataMat[:,1], datingDataMat[:,2], 15.0*array(datingLabels), 15.0*array(datingLabels))
ax.axis([-2,25,-0.2,2.0]) # x軸範圍 -2~25 y軸範圍 -0.2~2.0

plt.xlabel("df")
plt.ylabel('每週消耗冰激凌公升數')
plt.show()
result = classify0([1,1],group,labels,3)
print(result)

import matplotlib
print(matplotlib.matplotlib_fname())

歸一化特徵值

需求分析

這裡寫圖片描述

如上圖所示,三個特徵向量,每年獲得的飛行常客里程數 數值很大,所以再求距離的時候對值得影響很大。所以我們需要將這一特徵的值轉化為0~1之間

數學公式

下面的公式可以將任意取值範圍的的特徵值轉化為0~1之間

new value = (oldvalue-min)/(max-min)

歸一化特徵值程式碼實現

def autoNorm(dataSet):
        if __name__ == '__main__':
        # 取出每一列的最小值
            minVals = dataSet.min(0)
        # 取出每一列的最大值
            maxVals = dataSet.max(0)
            ranges = maxVals - minVals
        # 建立一個與dataSet 一樣大小的矩陣 用0填充
            normDataSet = zeros(shape(dataSet))
        # 去除data的行數
            m = dataSet.shape[0]
            normDataSet = dataSet - tile(minVals,(m,1))
        # 歸一化特徵 newValue = (oldvalue - min)/(max-min)
            normDataSet = normDataSet/tile(ranges,(m,1))
            return normDataSet,ranges,minVals

測試資料

以上我們已經對資料進行了處理,現在我們要進行測試。以上的datingTestSet共有1000行,現在我們取出900行做訓練資料,100行做測試資料。
錯誤率:就是分類器給出錯誤結果的次數除以測試資料的總數。

分類器針對約會網站的測試程式碼

norMat,ranges,minVals = autoNorm(datingDataMat)
for i in range(0,100):
    print("==============",norMat[2,:])
def datingClassTest():
    hoRation = 0.10
    datingDataMat,datingLabels = file2matrix('datingTestSet.txt')
    norMat,ranges,minVals = autoNorm(datingDataMat)
    m = norMat.shape[0] # 總資料1000行
    numTestVecs = int(m*hoRation) # 測試資料100行
    errorCount = 0.0
    for i in range(numTestVecs):
        # norMat[i,:]表示矩陣的切片,i表示測試資料為第i行, : 表示所有列
        # notMat[numTestVecs:m,:]  ,表示 從 100行到1000行 為訓練資料 ,沒測試一次都要訓練一遍
        classifierResult = classify0(norMat[i,:],norMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3)
        print("the classifier came back with: %d,the real answer is:%d"%(classifierResult,datingLabels[i]))
        if(classifierResult !=datingLabels[i]):errorCount +=1.0
        print("the total error rate is : %f" % (errorCount/float(numTestVecs)))

datingClassTest()網站

以上程式碼可以全部建立在一個.py檔案裡,具體程式碼以及資料可以參考以下
程式碼以及資料示例