老闆:小韓啊,別忘了去改進一下約會網站的配對效果。前端
我:好嘞好嘞!立刻工做!!python
好了,又要開始一天的工做啦。接着上篇文章老闆佈置的任務,咱們來看一下此次實戰的相關信息。程序員
老闆的朋友,卡特琳娜一直在使用約會網站尋找適合本身的約會對象。儘管約會網站會給她推薦不一樣的人選,但她並非喜歡每個人。通過一番總結,她發現她曾經交往過三種類型的人:算法
儘管發現了這樣的規律,可是卡特琳娜仍是沒辦法及那個網站推薦的人分類,這可把她給愁壞了!因此,她找到咱們,但願咱們能夠寫一個分類軟件,來幫助她將匹配對象劃分到確切的分類中。app
此外,她還手機了一些約會網站不曾記錄的信息,她認爲這能過夠幫到咱們!機器學習
示例:在約會網站上使用k-近鄰算法 (1) 收集數據:卡特琳娜提供的文本文件。ide
(2) 準備數據:使用Python解析文本文件。函數
(3) 分析數據:使用Matplotlib畫二維擴散圖。工具
(4) 訓練算法:此步驟不適用於k-近鄰算法。性能
(5) 測試算法:使用卡特琳娜提供的部分數據做爲測試樣本。 測試樣本和非測試樣本的區別在於:測試樣本是已經完成分類的數據,若是預測分類與實際類別不一樣,則標記爲一個錯誤。
(6) 使用算法:產生簡單的命令行程序,而後卡特琳娜能夠輸入一些特徵數據以判斷對方是否爲本身喜歡的類型。
咱們須要進行的第一步就是數據解析。咱們必須弄明白咱們的數據以什麼形式存儲的,數據中的每一項都表明什麼含義。
卡特琳娜提供給咱們一個txt文件,叫作datingSet.txt,其中每一個樣本佔一行,總共有1000行。每一個樣本主要包括如下三個特徵:
也就是下面這樣的形式:
在將上述特徵數據輸入到分類器以前,必須先進行數據處理,將待處理數據的格式改變爲分類器能夠接受的格式。接着咱們上次寫過的kNN.py文件,咱們在這個文件中建立名爲file2matrix的函數,以此來處理輸入格式問題。
該函數的輸入爲文件名字符串,輸出爲訓練樣本矩陣和類標籤向量。
def file2matrix(filename): # 1.獲得文件行數 fr = open(filename) arrayOLines = fr.readlines() numberOfLines = len(arrayOLines) # 2.建立返回的NumPy矩陣 returnMat = zeros((numberOfLines, 3)) classLabelVector = [] index = 0 # 3.解析文件數據到列表 for line in arrayOLines: line = line.strip() listFromLine = line.split('\t') returnMat[index, :] = listFromLine[0:3] # 樣本每一行的前三列表示特徵 classLabelVector.append(int(listFromLine[-1])) # 樣本每一行的最後一列表示分類標籤 index += 1 return returnMat, classLabelVector
代碼貼在上面了,很簡單的解析文件,詳細介紹我都放在註釋裏了,相信這對你們沒有什麼難度。
而後,咱們在Python命令提示符下輸入下面的命令:
>>> reload(kNN) >>> datingDataMat, datingLabels = kNN.file2matrix('datingSet.txt')
注意:由於書上使用的python2,因此在這裏直接使用reload便可。
可是對於 python版本 <= Python 3.3,則須要使用如下命令:
import imp imp.reload(kNN)對於python版本 >= Python 3.4,則須要使用如下命令:
import importlib importlib.reload(sys)
好了,在輸入命令的時候,還有下面兩點須要注意:
在不報錯的狀況下,咱們看一下咱們生成的數據:
>>> datingDataMat array([[4.0920000e+04, 8.3269760e+00, 9.5395200e-01], [1.4488000e+04, 7.1534690e+00, 1.6739040e+00], [2.6052000e+04, 1.4418710e+00, 8.0512400e-01], ..., [2.6575000e+04, 1.0650102e+01, 8.6662700e-01], [4.8111000e+04, 9.1345280e+00, 7.2804500e-01], [4.3757000e+04, 7.8826010e+00, 1.3324460e+00]]) >>> datingLabels [3, 2, 1, 1, 1, 1, 3, 3, 1, 3, 1, 1, 2, 1, ......]
咱們如今已經從文本文件中導入了數據,並將其轉換成了咱們須要的格式。可是這樣有一個問題,咱們沒辦法直觀地看出數據的含義,因此咱們接下來就使用Python工具來圖形化展現數據內容。
注意:在分析數據的時候,圖形化是一個很是好用的方法。Python中的Matplotlib模塊爲咱們提供了這樣的功能。
這裏咱們使用Matplotlib來製做原始數據的散點圖,我本人也是剛入手機器學習和Python語言,因此對於這個庫的使用還不是很瞭解,後期我也會慢慢出一個Matplotlib的教程,最好可以以通俗的語言來爲你們講明白。
(給本身立的flag,哭着也要讓它站穩)
好了,咱們繼續在上面的Python命令行環境中,輸入如下命令:
>>> import matplotlib >>> import matplotlib >>> import matplotlib.pyplot as plt >>> fig = plt.figure() >>> ax = fig.add_subplot(111) >>> ax.scatter(datingDataMat[:,1], datingDataMat[:, 2]) <matplotlib.collections.PathCollection object at 0x00000111E23668D0> >>> plt.show()
輸出效果以下圖所示(我是使用Pycharm下的終端輸出的,輸入命令的時候有些粗心,你們千萬不要向我這樣):
這樣看,生成的圖片雖然能看,可是咱們不懂它是什麼含義,這咱們就得從源碼入手了。
datingDataMat[:, 1] 這個表明的是datingDataMat矩陣的第二列數據,表明特徵‘玩視頻遊戲所耗時間百分比’ datingDataMat[:, 2] 這個表明的是datingDataMat矩陣的第三列數據,表明特徵‘每週所消費的冰淇淋公升數’
而後,咱們看看ax.scatter()這個函數,其原型是:
scatter(x, y, s=None, c=None, marker=None, cmap=None, norm=None, vmin=None, vmax=None, alpha=None, linewidths=None, verts=None, edgecolors=None, hold=None, data=None, **kwargs)
暫且不看後面的參數(後面的教程會詳細介紹),咱們看到,一個x表明橫軸,一個y表明縱軸。
好了,這樣咱們就能夠知道這個圖的基本含義了,用書上的圖表示以下:
因爲咱們沒有使用樣本分類的特徵值,咱們很難從上面的圖中看出有用的信息來。通常來講,咱們會採用色彩或其餘的記號來標記不一樣樣本分類,以便更好地理解數據信息。
Matplotlib 庫提供的scatter函數支持個性化標記散點圖上的點。從新輸入上面的代碼,調用scatter函數 時使用下列參數:
>>> fig = plt.figure() >>> ax = fig.add_subplot(111) >>> ax.scatter(datingDataMat[:,1], datingDataMat[:, 2], 15.0 * array(datingLabels), 15.0 * array(datingLabels)) <matplotlib.collections.PathCollection object at 0x00000111E41CC9B0> >>> plt.show()
咱們就是利用了變量datingLabels存儲的類標籤屬性,在散點圖上繪製了色彩不等,尺寸不一樣的點,輸出效果以下:
這樣,咱們就能夠基本上看到數據點所屬三個樣本分類的區域輪廓。
爲何要歸一化數值?這個問題咱們慢慢來解決!
咱們先來看一組數據:
玩視頻遊戲所耗時間百分比 | 每一年得到的飛行常客里程數 | 每週消費的冰淇淋公升數 | 樣本分類 | |
---|---|---|---|---|
1 | 0.8 | 400 | 0.5 | 1 |
2 | 12 | 134000 | 0.9 | 3 |
3 | 0 | 20000 | 1.1 | 2 |
4 | 67 | 32000 | 1 | 2 |
計算如下樣本3和樣本4之間的距離,以下:
$$
d = \sqrt{(0-67)^2 + (20000 - 32000)^2 + (1.1 - 1.0)^2}
$$
咱們很容易發現,上面方程中數字差值最大的屬性對計算結果的影響最大。也就是說,每一年獲取的飛行常客里程數對於計算結果的影響將遠遠大於其餘兩個特徵——玩視頻遊戲所耗時間百分比和每週消費冰淇淋公升數——的影響。而產生這種現象的惟一緣由,僅僅是由於飛行常客里程數遠大於其餘特徵值。
但卡特琳娜認爲這三種特徵是同等重要的,所以做爲三個等權重的特徵之一,飛行常客里程數並不該該如此嚴重地影響到計算結果。
因此,在處理這種不一樣取值範圍的特徵值時,咱們一般就須要採用歸一化,如將取值範圍處理爲0到1或者-1到1之間。下面的公式能夠將任意取值範圍的特徵值轉化爲0-到1區間的值:
$$
newValue = (oldValue - min) / (max - min)
$$
其中min和max分別是數據集中當前特徵的最小值和最大值。雖然改變數值取值範圍增長了分類器的複雜度,但爲了獲得準確結果,咱們必須這樣作。
好了,理論講了一大堆,咱們仍是來寫代碼。繼續在kNN.py中編寫代碼,建立一個函數autoNorm()。
def autoNum(dataSet): minVals = dataSet.min(0) # 存儲每列的最小值,參數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)) return normDataSet, ranges, minVals
上面的代碼,有一點須要咱們注意:特徵值矩陣有1000×3個值,而minVals和ranges的值都爲1×3。爲了解決這個問題,咱們使用NumPy庫中tile()函數將變量內容複製成輸入矩陣一樣大小的矩陣,注意這是具體特徵值相除 ,而 對於某些數值處理軟件包,/可能意味着矩陣除法。
好了,寫完以後,咱們繼續在Pyhon命令提示符下,從新載入kNN.py模塊,執行autoNorm函數:
>>> reload(kNN) >>> normMat, ranges, minVals = kNN.autoNorm(datingDataMat) >>> normMat array([[0.44832535, 0.39805139, 0.56233353], [0.15873259, 0.34195467, 0.98724416], [0.28542943, 0.06892523, 0.47449629], ..., [0.29115949, 0.50910294, 0.51079493], [0.52711097, 0.43665451, 0.4290048 ], [0.47940793, 0.3768091 , 0.78571804]]) >>> ranges array([9.1273000e+04, 2.0919349e+01, 1.6943610e+00]) >>> minVals array([0. , 0. , 0.001156])
這裏咱們也能夠只返回normMat矩陣,可是下一節咱們須要將取值範圍和最小值歸一化測試數據。
老闆:小韓啊,寫的怎麼樣了啊?
我:數據可視化,數據處理以及數據歸一化我都作完了,接下來,把數據丟到昨天寫的分類器就能看到效果了!!
老闆:不錯不錯!繼續努力啊!年終獎到時候少不了你的!
我:(美滋滋)沒問題,你就放100個心吧。
好了,總算是處理完數據了。接下來咱們就能夠去看一下咱們的分類器效果怎麼樣了,想一想就很開心呢!,若是分類器的正確率知足要求,卡特琳娜就可使用這個軟件來處理約會網站提供的約會名單了!!!
機器學習算法一個很重要的工做就是評估算法的正確率,一般咱們只提供已有數據的90%做爲訓練樣原本訓練分類器,而使用其他的10%數據去測試分類器,檢測分類器的正確率。須要注意的是,10%的測試數據應該是隨機選擇的,因爲卡特琳娜提供的數據並無按照特定目的來排序,因此咱們能夠隨意選擇10%數據而不影響其隨機性。
話很少說,直接上代碼,咱們仍是在kNN.py中繼續編寫:
def datingClassTest(): hoRatio = 0.10 datingDataMat, datingLabels = file2matrix('datingTestSet2.txt') # 從文本中解析數據 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)))
其實,代碼流程仍是很簡單的。
寫完以後,咱們直接在咱們的Python命令提示符下從在kNN.py模塊,而後輸入kNN.datingClassTest(),執行分類測試程序,咱們能夠獲得如下結果:
Yes Yes Yes!!! 效果仍是不錯的,只有5%的錯誤率!!!固然,咱們也能夠改變函數 datingClassTest內變量hoRatio和變量k的值,檢測錯誤率是否隨着變量值的變化而增長。不一樣的參數,分類器的性能會有很大不一樣的。
老闆:哎呦,不錯哦!只有5%的錯誤率
我:那是必須的!
老闆:那你在改改,咱們就能夠給卡特琳娜用了!!
我:沒問題,您就等着給我加雞腿吧!!
好了,程序員測試完了,準備發佈吧!!!
這裏咱們就使用這個簡答的分類器來幫助卡特琳娜對網站給出的候選人進行分類!!!
def classifyPerson(): resultList = ['not at all', 'in small doses', 'in large doses'] percentTats = float(input('percentage of time spent playing video games?')) ffMiles = float(input('frequent flier miles earned per year?')) iceCream = float(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: ', resultList[classifierResult - 1])
代碼,我也就很少解釋了,大部分咱們都見過,惟一要注意的就是raw_input(),這是Python 2的函數,Python3就直接使用input就行。
咱們直接看結果:
總算是寫完了,雖然都是黑乎乎的控制檯程序,可是算法的核心及應用方法咱們都會了,前端的包裝就不是咱們的鍋了!!!hiahiahia!!!
最後,仍是熟悉的配方!
歡迎你們關注個人公衆號,有什麼問題也能夠給我留言哦!