從一個最基本的算法示例來入門機器學習
第一個分類算法 --- k-近鄰算法(簡單地說,k近鄰算法采用測量不同特征值之間的距離方法進行分類。)
前言:
- 工具, 我用的 Pycharm ,Python V3.X和V2.x 隨意切換,建議初學者還是下個編輯工具方便些. Pycharm環境配置
- 本篇基于 Python 2.X 環境,Mac OS 系統
- 模塊導入 numpy,operator,matplotlib,不知道從哪導入?
- 這里用的比較的多的NumPy數組,建議先簡單了解下,NumPy使用手冊
- 建議第一遍先過一下算法大概的思想,之后再細看代碼
- 源碼在這里
需求分析,使用k-近鄰算法改進約會網站的配對效果
海倫一直使用在線約會網站尋找適合自己的約會對象。盡管約會網站會推薦不同的 人選,但她沒有從中找到喜歡的人。經過一番總結,她發現曾交往過三種類型的人:
- 不喜歡的人
- 魅力一般的人
- 極具魅力的人
這次的分類軟件可以更好地幫助她將匹配對象劃分到確切的分類中
k-近鄰算法流程
- 收集數據:提供文本文件
- 準備數據: 使用 Python 解析文本文件
- 分析數據:使用 Matplotlib 畫二維擴散圖
- 訓練算法:此步驟不適用于卜近鄰算法
- 使用海倫提供的部分數據作為測試樣本,測試樣本和非測試樣本的區別在于:測試樣本是已經完成分類的數據,如果預測分類與實際類別不同,則標記為一個錯誤
- 使用算法:產生簡單的命令行程序,然后海倫可以輸入一些特征數據以判斷對方是否
為自己喜歡的類型
準備數據:從文本文件中解析數據
本次數據來源海倫的約會數據,放在文本文件 datingTestSet2.txt 中,每 個樣本數據占據一行,總共有1000行。樣本主要包含以下3種特征:
- 每年獲得的飛行??屠锍虜?/li>
- 玩視頻游戲所耗時間百分比
- 每周消費的冰淇淋公升數
在將上述特征數據輸人到分類器之前,必須將待處理數據的格式改變為分類器可以接受的格式 。在kNN.py中創建名為file2matrix的函數,以此來處理輸人格式問題。該函數的輸人為文件名字符串,輸出為訓練樣本矩陣和類標簽向量。
def file2matrix(filename):
love_dictionary = {'非常喜歡': 3, '一般喜歡': 2, '討厭': 1}
fr = open(filename)
arrayOLines = fr.readlines()
numberOfLines = len(arrayOLines) # get the number of lines in the file
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]
if (listFromLine[-1].isdigit()):
classLabelVector.append(int(listFromLine[-1]))
else:
classLabelVector.append(love_dictionary.get(listFromLine[-1]))
index += 1
return returnMat, classLabelVector
首先我們需要知道文本文件包含多 少行。打開文件,得到文件的行數。然后創建以零填充的矩陣NumPy(實際上,NumPy是一 個二維數組,這里暫時不用考慮其用途)。為了簡化處理,我們將該矩陣的另一維度設置為固定 值3 , 你可以按照自己的實際需求增加相應的代碼以適應變化的輸人值。循環處理文件中的每行 數據 , 首先使用函數line.strip()截取掉所有的回車字符,然后使用tab字符\t將上一步得 到的整行數據分割成一個元素列表。接著,我們選取前3個元素,將它們存儲到特征矩陣中。Python 語言可以使用索引值-1表示列表中的最后一列元素,利用這種負索引,我們可以很方便地將列表 的最后一列存儲到向量classLabelVector中。需要注意的是,我們必須明確地通知解釋器,告 訴它列表中存儲的元素值為整型,否則Python語言會將這些元素當作字符串處理。以前我們必須自己處理這些變量值類型問題.
測試一下,讀出來的數據是這樣
散點圖,觀察數據的樣本分布
只是為了能比較直觀的看到樣本之間的模式,不在算法內
一般來說,圖形化的方式能更直觀地展示數據。接下來下面用 Matplotlib 來圖形化展示數據內容,看一下數據之間的模式,測試代碼。
def matplotTest():
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(datingDataMat[:,1],datingDataMat[:,2],15.0*array(datingLabels),15.0*array(datingLabels))
plt.show()
這是玩視頻游戲所耗時間百分比(X)與每周消費的冰激凌公升數的散點圖(Y),可以看到三個樣本的分布情況,不過區分度不是很清晰
接下來取矩陣列的0和1列
datingDataMat[:,0],datingDataMat[:,1]
這是每年飛行??屠锍虜?X)與玩視頻游戲所占百分比的約會數據散點圖(Y)
清晰地標識了三個不同的樣本分類區域,具有不同愛好的人其類別區域也不同。
準備數據:歸一化數值
計算倆點間的距離公式
很容易發現,上面方程中數字差值最大的屬性對計算結果的影響最大,也就是說,每年 獲取的飛行??屠锍虜祵τ谟嬎憬Y果的影響將遠遠大于表2-3中其他兩個特征— 玩視頻游戲的 和每周消費冰洪淋公升數— 的影響。而產生這種現象的唯一原因,僅僅是因為飛行??屠锍虜?遠大于其他特征值。但海倫認為這三種特征是同等重要的,因此作為三個等權重的特征之一,飛 行??屠锍虜挡⒉粦撊绱藝乐氐赜绊懙接嬎憬Y果。
在處理這種不同取值范圍的特征值時,我們通常采用的方法是將數值歸一化,如將取值范圍 處理為0到1或者-1到1之間。下面的公式可以將任意取值范圍的特征值轉化為0到1區間內的值:
newValue = {oldValue-min)/(max-min)
其中min和max分別是數據集中的最小特征值和最大特征值。雖然改變數值取值范圍增加了 分類器的復雜度,但為了得到準確結果,必須這樣做。定義一個函數,將數字特征值轉化為0到1的區間。
為了歸一化特征值,我們必須使用當前值減去最小值,然后除以取值范圍. 需要注意的是,特征 值矩陣有1000 * 3個值,而minVals和range的值都為1 * 3。為了解決這個冋題,我們使用NumPY函數將變量內容復制成輸入矩陣同樣大小的矩陣,注意這是具體特征值相除 ,而 對于某些數值處理軟件包,/可能意味著矩陣除法,但在NumPy庫中,矩陣除法需要使用函數 linalg .solve (matA,matB) 。
def autoNorm(dataSet):
minVals = dataSet.min(0)
maxVals = dataSet.max(0)
ranges = maxVals - minVals
normDataSet = zeros(shape(dataSet))
m = dataSet.shape[0]
normDataSet = dataSet - tile(minVals, (m, 1))
normDataSet = normDataSet / tile(ranges, (m, 1)) # element wise divide
return normDataSet, ranges, minVals
#將每列的最小值放在變量minVals中,將最大值放在變量 maxVals中,其中dataSet.min(0)中的參數0使得函數可以從列中選取最小值,而不是選取當前行的最小值。然后,函數計算可能的取值范圍,并創建新的返回矩陣
這里可以打印出歸一之后的結果,測試代碼
def solveTest():
datingDataMat,datingLabels = file2matrix('datingTestSet2.txt')
print autoNorm(datingDataMat)
輸出
測試算法:作為完整程序驗證分類器
機器學習算法一個很重要的工作就是評估算法的正確率,通常我們只提供已有數據的90%作為訓練樣本來訓練分類 器 ,而使用其余的10%數據去測試分類器,檢測分類器的正確率,這里隨意選擇10%數據而不影響其隨機性。
使用錯誤率來檢測分類器的性能。對于分類器來說,錯誤率就是分類 器給出錯誤結果的次數除以測試數據的總數,完美分類器的錯誤率為0,而錯誤率為1.0的分類器 不會給出任何正確的分類結果。代碼里我們定義一個計數器變量,每次分類器錯誤地分類數據, 計數器就加1, 程序執行完成之后計數器的結果除以數據點總數即是錯誤率
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): #表示提取前k個值
voteIlabel = labels[sortedDistIndicies[i]] #排在第i位置的類別
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1 #對 classCount[voteIlabel] 的值進行加一,如果不存在則初始化為1
sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True) #出現頻次列表classCount 進行倒序排序
return sortedClassCount[0][0]
#分類器
classify0分類算法流程,對未知類別屬性的數據集中的每個點依次執行以下操作:
(1)計算已知類別數據集中的點與當前點之間的距離;
(2)按照距離遞增次序排序;
(3)選取與當前點距離最小k幾個點;
(4)確定前k個點所在類別的出現頻率;
(5)返回前k個點出現頻率最高的類別作為當前點的預測分類。
def datingClassTest():
hoRatio = 0.50 # hold out 10%
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt') # load data setfrom file
normMat, ranges, minVals = autoNorm(datingDataMat)
m = normMat.shape[0]
numTestVecs = int(m * hoRatio)
errorCount = 0.0
for i in range(numTestVecs):
classifierResult = classify0(normMat[i, :], normMat[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))
print errorCount
從文件中讀取數據并將其轉換為歸一化特征值。接著計算測試向量的數量,此步決定了 normMat向量中哪些數據用于測試,哪些數據用于分類器的訓練樣本;然后將這兩部分數據輸人到原始kNN分類器函數classifyO。最后,函數計算錯誤率并輸出結果
調用測試函數 datingClassTest(),輸出
可以看到,約會數據集的錯誤率5%,還算是一個可以接受的結果,可以改變函數 datingClassTest內變量 hoRatio 和變量 k 的值,檢測錯誤率是否隨著變量值的變化而增加。依 賴于分類算法、數據集和程序設置,分類器的輸出結果可能有很大的不同
這個例子表明我們可以正確地預測分類,錯誤率是5%。海倫完全可以輸人未知對象的 屬性信息’由分類軟件來幫助她判定某一對象的可交往程度:討厭、一般喜歡、非常喜歡。
使用算法:構建完整可用系統
現在可以做出一個小程序,通過該程序海倫會在約會網站上找到某個人并輸入他的信息。 程序會給出她對對方喜歡程度的預測值
def classifyPerson():
resultList = ['討厭', '一般喜歡', '非常喜歡']
percentTats = float(raw_input( \
"percentage of time spent playing video games?"))
ffMiles = float(raw_input("frequent flier miles earned per year?"))
iceCream = float(raw_input("liters of ice cream consumed per year?"))
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
normMat, ranges, minVals = autoNorm(datingDataMat)
inArr = array([ffMiles, percentTats, iceCream, ])
classifierResult = classify0((inArr - \
minVals) / ranges, normMat, datingLabels, 3)
print "You will probably like this person: %s" % resultList[classifierResult - 1]
測試下,運行函數,依次輸入
玩視頻游戲的百分比? 12
每年的飛行公里數 30354
每周消費的冰淇淋公升數 2
然后可以得到 你對他的喜歡程度 非常喜歡 的結論
到此為止,算是初步摸到了機器學習的門檻,后續會有包括決策樹,樸素貝葉斯等進階算法的學習總結篇,共同進步.