k近鄰分類(KNN)
阿新 • • 發佈:2019-02-17
近來學習了最近鄰分類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)