機器學習是人工智能發展的助燃劑,人工智能如同一輛遲遲不來的列車,然而一旦當其前來,便會以令人瞠目的速度馳騁。而在人工智能的洪流之中,人類卻如葦草,渺小如你我,有且只有順應時代的潮流。
一、特點
剛開始學k-近鄰算法的時候,立馬就想起高中學的求二象限及三象限中兩點的距離。只不過k-近鄰算法將象限值擴大了一些,但總的來說原理還是類似的。
k-近鄰算法易于掌握(高中數學基礎)就可以輕松理解其原理,但計算復雜度比較高,相對而言它的精度也比較高哦。
二、栗子
舉一個栗子讓大家更好的理解k-近鄰算法。我喜歡喝Coco的金桔檸檬,主要就是因為它的甜度和酸度控制得非常合我口味,當我考量其它家的金桔檸檬時,就需要將其的甜度和酸度和Coco家的做個對比,對比結果越小,就越合我口味。
下面,我們來畫一張圖,橫軸表示酸度,縱軸表示甜度,將Coco家的金桔檸檬的甜度和酸度都量化為5。
那么該如何評判其它家金桔檸檬在我心里的地位呢?學過函數的你一定會想到根據其他家金桔檸檬酸甜度的量化結果求與Coco家酸甜度的距離吧,以x代表酸度,y代表甜度,那么這個距離就是√[(x-5)^2 +(y-5)^2]
。距離越短就說明這個口味越合我意。
在實際的數據分析中,可能不止甜度和酸度兩個特征值,這種情況下就需要算出多維度下亮點的距離了,如有5個特征值,則(1,0,2,1,0)與(3,2,1,5,6)的距離計算公式就是:
√[(3-1)^2 +(2-0)^2 +(1-2)^2 +(5-1)^2+ (6-0)^2]
三、更多的樣本
在以上栗子中,我按照酸度和甜度與Coco家金桔檸檬的匹配程度去衡量我對其它品牌金桔檸檬的喜愛程度。這樣的結果是否符合呢?
相信讀者已經提出異議了吧。
首先,僅憑我對一家金桔檸檬的喜愛程度(一個樣本)就去分析我對其它家金桔檸檬的喜愛未免太過武斷,因為我對金桔檸檬的喜好程度并不僅僅取決于甜度與酸度,還會有其它干擾因素比如茶的顏色、包裝之類,有可能我喜歡Coco家的金桔檸檬和它的甜度酸度沒有太大關系,僅僅是因為它家的包裝呢?
另一方面,我對甜度酸度均為5的金桔檸檬非常喜愛,也不能得出我對甜度為3酸度為4的金桔檸檬的喜愛程度。
所以,想讓結果更準確的話,我們還需要做兩個小小的改善:第一,多喝幾家的金桔檸檬,并記錄這些金桔檸檬的甜度酸度以及我對這些金桔檸檬的喜愛程度,樣本擴大之后,就能夠減少對某家非甜度和酸度兩個因素影響的喜愛程度產生的結果偏差;第二,將喜愛程度量化或分類,量化就是指對金桔檸檬茶打分,分類就是指對金桔檸檬茶的喜好程度進行一個分類,比如“非常喜歡”,“一般喜歡”,“普通喜歡”,“不喜歡”。
于是乎,我又嘗試了其它家的金桔檸檬茶,并記錄下它們的甜度、酸度并且對它們進行了打分。
酸度 | 甜度 | 打分 |
---|---|---|
5 | 5 | 5 |
7 | 3 | 4 |
5 | 6 | 5 |
3 | 6 | 3 |
2 | 3 | 3 |
6 | 5 | 5 |
7 | 8 | 3 |
以上的數據我們將其稱為訓練樣本,接著我們就可以嘗試測試一下我們的k-近鄰算法啦,大致的思路是這樣的,得到某個測試樣本的酸度與甜度,并計算出其與所有品嘗過的樣本的距離,取出距離最小的前k個樣本與其對應的標簽,得到出現次數最多的標簽,即為結果。
什么意思呢?比如說我得到一款新的金桔檸檬,然后測量出它的甜度與酸度,我用上述方法計算出其與所有品嘗過的樣本的酸甜度距離,然后得出酸甜度距離最小的前三家,也就是酸甜口味最相似的前三家,然后統計出那三家中喜好分數出現的次數,得出出現次數最多的那個分數,就是預測的我對這款新品的喜好程度。
四、上代碼
第一步,我們將訓練樣本解析成特征值數組以及標簽數組。
將訓練樣本的txt文件轉換為 特征值數組returnMat 以及相應的同長度的標簽數組 classLabelVector
def file_to_matrix(filename):
fr = open(filename)
numberOfLines = len(fr.readlines()) # get the number of lines in the file
returnMat = zeros((numberOfLines, 2)) # prepare matrix to return
classLabelVector = [] # prepare labels return
fr = open(filename)
index = 0
for line in fr.readlines():
line = line.strip()
listFromLine = line.split('\t')
returnMat[index, :] = listFromLine[0:2]
classLabelVector.append(int(listFromLine[-1]))
index += 1
return returnMat, classLabelVector
我們得到了兩個數組:
returnMat = [[5,5], [7,3], [5,6], [3,6], [2,3], [6,5], [7,8]]
classLabelVector = [5, 4, 5, 3, 3, 5, 3]
第二步,當得到了訓練樣本以及其對應標簽后,我們就可以計算出測試數據與每個樣本的距離,隨后我們可以選取前k個距離最小的樣本標簽,取出現最多次數的標簽作為結果。
def classify0(inX, dataSet, labels, k):
dataSetSize = dataSet.shape[0]
diffMat = tile(inX, (dataSetSize, 1)) - dataSet
sqDiffMat = diffMat ** 2
sqDistances = sqDiffMat.sum(axis=1)
distances = sqDistances ** 0.5
sortedDistIndicies = distances.argsort()
classCount = {}
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
其中,inX是輸入的測試特征值數組,dataSet是訓練特征值數組,labels是訓練標簽數組,k是用戶定義的距離最小的樣本標簽數量。
我將新產品的甜度酸度[5,3]作為inX參數傳入,dataSet和labels就是第一步得到的returnMat和classLabelVector,我取k為3,執行。
結果為5,非常喜歡,Done!
五、更多優化
使用上述方法,得到了結果,但是我們有沒有可以優化的地方呢?
我的想法是,可以將打分結果從離散值轉換為連續值,然后按照一定的權重去求得距離。
第二點就是,在這個栗子中我們的甜度和酸度的數值范圍是相似的,如果我們以mg/L和ph值來定義甜度和酸度的時候,就需要做一個歸一化處理。