近來學習了最近鄰分類KNN(k Nearest Neighbors),寫下心得,以爲記錄。
K近鄰的原理很簡單,對於數據集,可以分爲訓練集和測試集,KNN使用訓練集全部數據作爲分類計算的依據。輸入一個待分類樣本,計算該樣本與訓練集所有樣本的距離,挑選出距離最小的k個樣本,統計這k個樣本的類別標籤,出現最多的那個類別就被認爲是待分類樣本的類別。
《機器學習實戰》中使用歐式距離作爲特徵向量距離的度量,其他距離也可以用,不說。
跟其他很多分類器一樣,由於特徵向量每個維度數據範圍不一樣,某個維度過大,會嚴重影響距離計算,因此要進行歸一化。
代碼本身很簡單,只是對Python和Python相關一系列的庫還不是很熟悉,因此花了點時間摸索。用KNN來做數字識別,訓練樣本2000個,測試樣本946,k取3,最終準確率爲98.8%,還可以,就是計算時間很長,計算時間與訓練樣本的數量是線性關係,這或許就是KNN的一個缺點,SVM就只需要留下支撐向量。計算時間還與k有關係,準確率也和k有關,k取10準確率就只有97.7了,這也許能說明什麼。
代碼中還有約會網站分類問題的代碼。數據集可以網上下載到,不傳了。
from pandas import Series, DataFrame
import numpy as np
import seaborn as sns
from matplotlib import pylab as plt
from os import listdir
# _dataset的shape[0]和labels的數量一樣
# DataFrame的index默認是range(n),後面用這一特性來查詢排序後標籤的位置
def create_dataset():
_dataset = DataFrame([[1., 1.1],
[1., 1.],
[0., 0.],
[0.1, 0.1]])
_label = Series(["A", "A", "B", "B"])
return _dataset, _label
def create_dataset1():
fr = open("datingTestSet.txt", "r")
d = fr.readlines()
li = []
for line in d:
li.append(line.strip().split())
_ds = DataFrame(li, columns=["flight", "gamepercent", "icecream", "labels"])
_label = _ds["labels"]
del _ds["labels"]
_ds = _ds.astype(float) #
# print(_ds)
# print(set(_label))
# print(_ds.shape)
return _ds, _label
# 讀取文本形式的圖像,轉換爲一維向量並返回
def img2vector(filepathname):
fr = open(filepathname, "r")
vector = np.zeros((1, 1024))
for i in range(32):
line = fr.readline()
for j in range(32):
vector[0, i*32 + j] = int(line[j])
return vector
# 讀取數字識別的數據集
def create_dataset2(filepath):
dir_list = listdir(filepath)
img_n = len(dir_list)
_labels = Series(index=range(img_n))
_dataset_list = np.zeros((img_n, 1024))
for i in range(img_n):
line = dir_list[i]
_name = line.split(".")[0]
_digit_lab = _name.split("_")[0]
_labels[i] = int(_digit_lab)
_dataset_list[i, :] = img2vector(filepath + "\\" + line)
_dataset = DataFrame(_dataset_list, columns=range(1024))
print(_labels.shape)
print(_dataset.shape)
return _dataset, _labels
# 將ratio的數據劃分爲訓練數據,其餘作爲測試數據
def split_dataset(_org_dataset, _org_labels, train_ratio):
_train_n = int(_org_dataset.shape[0] * train_ratio)
train_ds = _org_dataset[:][0:_train_n]
train_lb = _org_labels[0:_train_n]
test_ds = _org_dataset[:][_org_dataset.index > _train_n]
test_lb = _org_labels[_org_labels.index > _train_n]
return train_ds, train_lb, test_ds, test_lb
def knn_classify(dataset, labels, test_feature, k, is_normalize=False):
if k > dataset.shape[0]:
print("k should not large than the number of sample")
return None
# 計算歐式距離
norm_para = []
test_feature.index = dataset.columns
norm_ds = DataFrame(columns=dataset.columns, index=dataset.index)
norm_ts_f = Series(index=dataset.columns)
# 歸一化很浪費時間,本應該把dataset的歸一化放在外面,這裏作爲測試,懶得改了
if is_normalize:
for c in dataset:
norm_para.append([dataset[c].min(), dataset[c].max()])
# print(norm_para)
for c in range(dataset.shape[1]):
norm_ds[norm_ds.columns[c]] = (dataset[dataset.columns[c]] - norm_para[c][0])/(norm_para[c][1] - norm_para[c][0] + 0.000001)
for c in range(test_feature.shape[0]):
norm_ts_f[dataset.columns[c]] = (test_feature.values[c] - norm_para[c][0])/(norm_para[c][1] - norm_para[c][0] + 0.000001)
else:
norm_ds = dataset.copy()
norm_ts_f = test_feature.copy()
d = norm_ds - norm_ts_f
# print(d[0:2])
d = d**2
# print(d[0:2])
d = d.sum(axis=1)
d = d**0.5
# print(d[0:2])
# 對歐式距離進行降序排序,注意這裏用的是sort_values
# 數據的index也會跟隨數據的位置發生改變,也就是用鍵值對訪問d,d沒有發生任何變化
d = d.sort_values(ascending=True)
# combine 僅僅是爲了調試顯示使用
# combine = np.array([[d.index[i], d.values[i], labels[d.index[i]]] for i in range(len(d))])
# print("k nearest neighbor is:")
# for i in range(k):
# print(combine[i])
# print(d)
# 用set來保證類別標籤的唯一性,利用爲唯一標籤構建Series,統計前k個特徵類別出現的次數
# 然後對k個特徵的類別出現次數進行排序,返回出現最多的那一個類別標籤
unique_labels = set(labels)
labels_number = unique_labels.__len__()
knn_set = Series(np.zeros(labels_number), unique_labels)
# print("set for knn:")
for i in range(k):
# d.index是以列表訪問d的index,與現實的順序一樣
# labels[d.index[i]]其實就是獲取對應特徵的類別標籤而已
knn_set[labels[d.index[i]]] += 1
knn_set = knn_set.sort_values(ascending=False)
# print(knn_set)
# print(knn_set)
return knn_set.index[0]
def classify_dataset(dataset, labels, test_ds, test_lb, k, is_normalize):
res = []
n = test_ds.shape[0]
# show_ds = dataset.copy()
# show_ds["labels"] = labels
# sns.pairplot(show_ds, hue="labels")
# plt.show()
for i in range(n):
print("classify " + str(i) + ":")
fe = Series(test_ds.values[i], index=dataset.columns)
ans = knn_classify(dataset, labels, fe, k, is_normalize)
res.append(ans)
if ans != test_lb.values[i]:
print("error classify:" + str(test_ds.index[i]))
right_n = 0
# print("res:")
for i in range(n):
# print(str(test_lb.values[i]) + " " + str(res[i]))
if res[i] == test_lb.values[i]:
right_n += 1
acc = right_n * 1.0 / n
print("accurracy=" + str(acc))
# 測試約會網站分類
# org_dataset, org_label = create_dataset1()
# train_dataset, train_label, test_dataset, test_label = split_dataset(org_dataset, org_label, 0.8)
# classify_dataset(train_dataset, train_label, test_dataset, test_label, 10, True)
# 測試數字識別
train_dataset, train_label = create_dataset2("digits\\trainingDigits")
test_dataset, test_label = create_dataset2("digits\\testDigits")
classify_dataset(train_dataset, train_label, test_dataset, test_label, 3, False)