本專欄計劃藉助Pandas與sklearn重新實現書中的實戰案例。
k-近鄰算法
1. KNN算法流程
對未知類別屬性的數據集中的每個樣本依次執行以下操作:
1、計算已知類別數據集中的點與當前點之間的距離;
2、按照距離遞增次序排序;
3、選取與當前點距離最小的k個點;
4、確定前k個點所在類別的出現頻率;
5、返回前k個點所出現頻率最高的類別作爲當前點的預測分類。
2. KNN改進約會網站的配對效果
2.1 數據準備:從文本中解析數據
海倫女士一直使用在線約會網站尋找適合自己的約會對象,她發現自己交往過的人可以進行如下分類:
不喜歡的人、魅力一般的人、極具魅力的人
海倫收集約會數據已經有了一段時間,她把這些數據存放在文本文件datingTestSet2.txt
中,每個樣本數據佔據一行,總共有1000行。樣本主要包含以下三個特徵:
1.每年獲得的飛行常客里程數
2.玩視頻遊戲所消耗時間百分比
3.每週消費的冰淇淋公升數
#從文本中解析數據
import pandas as pd
def file2matrix(filename):
"""
parameters:
filename
return:
dataset, labels
"""
# provided sample is tab separated and no columns name
dataset = pd.read_csv(filename, sep='\t',header=None, names=["frequentFlyerMiles", "VideoGamePlayed", "IceCreamEaten", "labels"])
#print(dataset)
features_matrix = dataset.iloc[:, :3]
labels = dataset.iloc[:,3]
return features_matrix, labels
dataset, labels = file2matrix('datingTestSet2.txt')
dataset.head()
2.2 數據可視化:散點圖
import matplotlib.pyplot as plt
def show_data(dataset, labels):
color_label = []
for i in labels:
if i == 1:
color_label.append('green')#dislike
elif i == 2:
color_label.append('blue')#smallDoses
elif i == 3:
color_label.append('yellow')#largeDoses
fig = plt.figure(figsize=(15,5))
ax = fig.add_subplot(131)
ax.set_xlabel('玩視頻遊戲所佔時間比')
ax.set_ylabel('每週消費的冰淇淋公斤數')
ax.scatter(dataset.iloc[:,1], dataset.iloc[:,2], c=color_label)
ax = fig.add_subplot(132)
ax.set_xlabel('每年的飛行里程數')
ax.set_ylabel('玩視頻遊戲所佔時間比')
ax.scatter(dataset.iloc[:,0], dataset.iloc[:,1], c=color_label)
ax = fig.add_subplot(133)
ax.set_xlabel('每週消費的冰淇淋公斤數')
ax.set_ylabel('每年的飛行里程數')
ax.scatter(dataset.iloc[:,2], dataset.iloc[:,0], c=color_label)
plt.show()
show_data(dataset, labels)
通過可視化數據,我們似乎也能發現一些規律,例如從子圖二中可以發現海倫似乎更喜歡那些每年有一定飛行里程數並且又有一定的玩遊戲時間佔比的約會對象。
2.3 數據處理:歸一化數值
在使用歐氏距離計算樣本之間的距離時,差值大的屬性對計算結果的影響也較大,而在上述三個特徵中,每年獲取的飛行常客里程數對於計算結果的影響將遠遠大於其他兩個特徵-玩視頻遊戲所耗時間佔比和每週消費冰淇淋公斤數的影響。而產生這種現象的唯一原因,僅僅是因爲飛行常客里程數遠大於其他的特徵值。
海倫認爲這三種特徵是同等重要的,因此作爲三個等權重的特徵之一,飛行常客里程數並不應該如此嚴重地影響到計算結果。
在處理這種不同取值範圍的特徵值時,我們通常採用的辦法是將數值歸一化,例如將取值範圍變成0到1之間。
def normfeature(x):
x_min = x.min()
x_max = x.max()
x = (x-x_min) / (x_max-x_min)
return x, x_min, x_max-x_min
2.4 構建KNN模型
from sklearn import neighbors
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(dataset, labels, test_size=0.1)
clf = neighbors.KNeighborsClassifier(n_neighbors=3)
clf.fit(x_train, y_train)
#print(clf.get_params()) # 模型參數
clf.score(x_test, y_test) # 測試集得分
下面是對KNeighborsClassifier
模型的一些其餘實驗:
對測試集前10個樣本進行預測
print(y_test[:10])
print(x_test[:10])
clf.predict(x_test[:10]) #預測
可以發現編號爲200、624的樣本被預測錯了, 笑哭…,接下來的實驗以數據集中編號爲624的樣本進行。
print(dataset.iloc[624,:])
print(labels.iloc[624])
print(x_test.iloc[7].values.reshape(1,-1))
print(clf.predict(x_test.iloc[7].values.reshape(1,-1))) #預測
尋找測試樣本的K-近鄰
my_neighbors = clf.kneighbors(x_test.iloc[7].values.reshape(1,-1))
print(my_neighbors)
for i in my_neighbors[1][0]:
#print(labels[i]) 錯誤示範 注意是測試集中樣本索引
print(y_train.iloc[i])
可以看到,三個近鄰確實投票將測試樣本分成了類別1
2.5 模型使用:構建可用系統
接下來我們使用模型來對海倫的約會對象進行分類。
import numpy as py
def classifyperson():
resultlist = ['not at all', 'in small doses', 'in large doses']
percentTats = float(input("玩視頻遊戲所消耗時間百分比:"))
ffMiles = float(input("每年獲得的飛行常客里程數:"))
iceCream = float(input("每週消費的冰淇淋公升數:"))
# 生成NumPy數組,測試集
inArr = np.array([ffMiles, percentTats, iceCream])
# 測試集歸一化
norminArr = (inArr - x_min) / ranges
print(norminArr)
result = clf.predict(norminArr.values.reshape(1, -1))
#print(result, type(result))
print("你可能%s這個人" % (resultlist[result[0] - 1]))
classifyperson()
3. KNN實現手寫識別系統
3.1 數據準備:將圖像處理爲向量
import pandas as pd
#顯示所有列
pd.set_option('display.max_columns', None)
#顯示所有行
pd.set_option('display.max_rows', None)
import numpy as np
def img2vector(filename):
"""
convert img text file to vector
return: 1*1024 vector
"""
dataframe = pd.read_fwf(filename, widths=[1]*32, header=None)
return dataframe.values.reshape(-1)
#return np.ravel(dataframe) #亦可行
testvector = img2vector('testDigits/0_0.txt')
print(testvector)
3.2 數據集處理
import os
def handwriting():
# preparing training dataset
train_dir = os.listdir('trainingDigits')
m = len(train_dir)
#print(m)
train_labels = []
train_data = []
for i in range(m):
file_name = train_dir[i]
#print(file_name)
str_name = file_name.split('.')[0] # 0_0
str_class = str_name.split('_')[0] # 0
train_labels.append(int(str_class))
train_data.append(img2vector('trainingDigits/%s' % file_name))
#print(len(train_labels))
#print(len(train_data))
train_labels = pd.Series(train_labels)
#print(train_labels)
train_data = pd.DataFrame(train_data)
#print(train_data)
#print(type(train_data))
print('training dataset has been prepared')
test_dir = os.listdir('testDigits')
m_test = len(test_dir)
test_labels = []
test_data = []
for i in range(m_test):
file_name = test_dir[i]
#print(file_name)
str_name = file_name.split('.')[0] # 0_0
str_class = str_name.split('_')[0] # 0
test_labels.append(int(str_class))
test_data.append(img2vector('testDigits/%s' % file_name))
#print(len(train_labels))
#print(len(train_data))
test_labels = pd.Series(test_labels)
#print(train_labels)
test_data = pd.DataFrame(test_data)
#print(train_data)
#print(type(train_data))
print('test dataset has been prepared')
return train_data, train_labels, test_data, test_labels
train_data, train_labels, test_data, test_labels = handwriting()
3.3 模型構建
clf = neighbors.KNeighborsClassifier(n_neighbors=3)
clf.fit(train_data, train_labels)
clf.score(test_data, test_labels)
test_predict = clf.predict(test_data) #預測
print(test_predict)
print(sum(test_predict!=test_labels)) #12
4. KNN總結
KNN優點:
- 簡單易用,原理較爲簡單,本文中只給出了分類的實例,也可以用於迴歸問題。
- 可用於數值型和標稱型數據。
- 只需要記住所有的訓練樣本,無需額外的訓練過程等。
KNN缺點:
- 計算複雜度高,實際使用時可能非常耗時。
- 難以處理樣本不平衡問題。
- 無法給出數據的內在含義。
5. 參考鏈接
1、Pandas input/output 官方文檔
2、本書源代碼 Github地址 基於Python2.6
3、sklearn.neighbors 官方文檔