NN,Nearest Neighbor,最近鄰html
KNN,K-Nearest Neighbor,K最近鄰python
KNN分類的思路:git
- 分類的過程實際上是直接將測試集的每個圖片和訓練集中的全部圖片進行比較,計算距離(這裏使用L2距離)。
- 距離越遠,表明圖片之間的類似度越低;距離越近,表明圖片之間越類似。
- 找到和測試圖片距離最近的K個圖,統計它們的分類,數量最多的分類就做爲測試圖片的分類。
Python實現:github
一、加載CIFAR-10數據,參考前一篇 CIFAR-10和python讀取數組
- X_train,訓練集 (50000,32,32,3)
- y_train, 訓練分類集 (50000,)
- X_test, 測試集 (5000,32,32,3)
- y_test,測試分類集 (5000,)
# Load the raw CIFAR-10 data. cifar10_dir = 'cs231n/datasets/cifar-10-batches-py' # Cleaning up variables to prevent loading data multiple times (which may cause memory issue) try: del X_train, y_train #del 只刪除變量,不刪變量引用的數據 del X_test, y_test print('Clear previously loaded data.') except: pass X_train, y_train, X_test, y_test = load_CIFAR10(cifar10_dir)
爲了提升執行效率,只從中取出5000和500個訓練和測試數據,並變形爲(5000,3072)和(500,3072)。app
其中,3072=32*32*3,表明一個圖片。dom
二、建立KNN分類器函數
from cs231n.classifiers import KNearestNeighbor classifier = KNearestNeighbor() classifier.train(X_train, y_train)
KNN分類並不對訓練集作處理,只是單純的保存下來。oop
class KNearestNeighbor(object): def __init__(self): pass def train(self, X, y): self.X_train = X self.y_train = y
三、計算距離測試
下面給出了三種計算距離的方式,最後能夠看出向量計算的效率是最高的,
- 雙層循環
- 單層循環
- 無循環
雙層循環:效率低
def compute_distances_two_loops(self, X): num_test = X.shape[0] num_train = self.X_train.shape[0] dists = np.zeros((num_test, num_train)) for i in range(num_test): for j in range(num_train): # numpy中的array能夠直接逐元素相減 # square能夠對整個array中的某一行中的每一個元素作平方 dists[i, j] = np.sqrt( np.sum( np.square( self.X_train[ j, : ] - X[ i, : ]) ) )
return dists
單層循環:
和雙層循環的區別在於,直接用整個數組減去另外一個數組的一行,實現的就是每行相減的效果。
def compute_distances_one_loop(self, X): num_test = X.shape[0] num_train = self.X_train.shape[0] dists = np.zeros((num_test, num_train)) for i in range(num_test): # self.X_train - X[ i, : ], 前者的每行減去後者 # np.sum中的axis =1, 表示每行中的全部列相加 dists[i, :] = np.sqrt( np.sum( np.square( self.X_train - X[ i, : ]), axis=1 ) ) return dists
無循環:
利用
- L2距離平方和的展開
- numpy的廣播機制(broadcast)https://docs.scipy.org/doc/numpy-1.13.0/user/basics.broadcasting.html
def compute_distances_no_loops(self, X): num_test = X.shape[0] num_train = self.X_train.shape[0] dists = np.zeros((num_test, num_train)) # 思路:L2距離展開,x-y的平方等於 x的平方 + y的平方 - 2xy, 2xy中的y須要從行轉爲列,再和x作點積。 # 最後生成的矩陣的每一個元素,就是x-y的平方 # 2xy d1 = np.dot(X, self.X_train.T) #x的平方,keepdims=True,是保持矩陣的維度;500*1 # False,結果是一維的, 1*500 d2 = np.sum( np.square(X), axis=1, keepdims=True) #y的平方 d3 = np.sum( np.square(self.X_train), axis=1, keepdims=True)
# 廣播 # 500*1 和5000* 1是不能相加的 # 500*1 和5000*1的轉置(1*5000)相加,會獲得500 * 5000矩陣 dists = np.sqrt(d2 + d3.T - 2*d1) return dists
能夠計算三個方式獲取的dists之間的差異,沒有問題的話,difference應該是0
# np.linalg.norm, 計算範數 # fro,F-範數,矩陣範數,矩陣中各項元素的絕對值平方的總和 difference = np.linalg.norm(dists - dists_one, ord='fro')
四、分類
def predict_labels(self, dists, k=1): num_test = dists.shape[0] y_pred = np.zeros(num_test) for i in range(num_test): closest_y = [] # np.argsort,排序後的索引list,對應原array_like labels = self.y_train[np.argsort(dists[i, :])].flatten() # [0:k] 取0到k-1 closest_y = labels[0:k] # Counter, 統計每一個元素出現的次數 c = Counter(closest_y) # most_common(n) 取數量最多的前n種 # most_common(1) = [(2, 3)], list, 2是種類,3是次數 # c.most_common(1)[0] = (2, 3), tuple # c.most_common(1)[0][0] = 2 y_pred[i] = c.most_common(1)[0][0] return y_pred
五、交叉驗證
- 交叉驗證就是將訓練集分爲N等分,
- 取其中一份做爲驗證集,其餘做爲訓練集。
- 每一個等分分別作一次驗證集,實現交叉驗證。
- 交叉驗證能夠減小過擬合。
num_folds = 5 k_choices = [1, 3, 5, 8, 10, 12, 15, 20, 50, 100] X_train_folds = [] y_train_folds = [] # array_split,將數組等分 # 和split的區別是,在不能等分的狀況下,也能作到儘可能等分。 X_train_folds = np.array_split(X_train, num_folds) y_train_folds = np.array_split(y_train, num_folds) k_to_accuracies = {} for k in k_choices: k_to_accuracies[k] = [] for i in range(num_folds): y_pred = classifier.predict_labels(X_train_folds[i], k=k) # ==比較兩個數組,能夠獲得相等元素的個數 num_correct = np.sum(y_pred == y_train_folds[i]) accuracy = float(num_correct) / len(X_train_folds[i]) k_to_accuracies[k].append(accuracy)
準確率統計圖
# plot the raw observations for k in k_choices: accuracies = k_to_accuracies[k] plt.scatter([k] * len(accuracies), accuracies) # plot the trend line with error bars that correspond to standard deviation # 平均值 accuracies_mean = np.array([np.mean(v) for k,v in sorted(k_to_accuracies.items())]) # 標準差 accuracies_std = np.array([np.std(v) for k,v in sorted(k_to_accuracies.items())]) plt.errorbar(k_choices, accuracies_mean, yerr=accuracies_std) # 偏差圖 plt.title('Cross-validation on k') plt.xlabel('k') plt.ylabel('Cross-validation accuracy') plt.show()
每一個k對應5個交叉驗證數據集,獲得5個準確率,再取均值。最後的連線就是均值的連線。
python相關:
一、ndarray之間能夠直接算術運算,相同維度的能夠,不一樣維度的,經過廣播也能夠作到。
二、一些函數也能夠直接對整個ndarray操做,其實是對其中的每一個元素操做。np.square
用到的一些方法:
- np.sqrt, np.sum
- np.argsort,排序後獲得的對應原數組的索引數組。
- flattern,轉爲1維數組
- Counter,from collections import Counter, 統計list中每一個元素出現的次數
- most_common(n),取數量最多的前n種
- np.array_split,等分ndarray
- ==,能夠取得數組中對應的元素個數
- np.linalg.norm, 計算範數
- np.random.choice, 隨機選擇
- np.flatnonzero, 返回不等於0 的索引集合
- np.mean, 計算平均值
- np.std, 計算標準差
- %load_ext autoreload,# 引用的其餘模塊更改了,能夠自動從新加載
Reference: