算法学习---KNN--K 均值聚类

声明:如有侵权,请与本人联系,会尽快删除。

致谢:感谢网络中各位老师与前辈的分享,其中某些部分是在研读老师与前辈的文章之后才明白的,具体链接附在了对应代码附近。

KNN : 邻近算法,或者说K最近邻(kNN,k-NearestNeighbor)分类算法

图解

 KNN算法中不使用循环的方法原理:完全平方公式

参考链接:https://blog.csdn.net/qq_27261889/article/details/84891734

代码实现

分三个文件:1.数据加载  2.距离计算  3.训练与测试

所用数据集:cifar-10-batches-py

1. 数据加载部分:data_utils.py

import  pickle
import numpy as np
import os

def load_cifar_batch(filename):
with open(filename,'rb') as f :
datadict=pickle.load(f,encoding='bytes')
x=datadict[b'data'] # b表示以二进制的方式读取数据
y=datadict[b'labels']
x=x.reshape(10000,3,32,32).transpose(0,2,3,1).astype('float')
y=np.array(y)
return x,y

def load_cifar10(root):
xs=[]
ys=[]
for b in range(1,6):
f=os.path.join(root,'data_batch_%d' % (b,))
x,y=load_cifar_batch(f)
xs.append(x)
ys.append(y)
Xtrain=np.concatenate(xs) #1
Ytrain=np.concatenate(ys)
del x ,y
Xtest,Ytest=load_cifar_batch(os.path.join(root,'test_batch')) #2
return Xtrain,Ytrain,Xtest,Ytest

2. 预测与距离计算实现:knn.py  (利用到了 python 的广播机制)

import numpy as np

class KNearestNeighbor:

def __init__(self):
pass
def train(self,X,y):
self.X_train=X
self.y_train=y

def predict(self,X,k=1,num_loops=0): #1
if num_loops== 0:
dists=self.compute_distances_no_loops(X)
elif num_loops==1:
dists=self.compute_distances_one_loop(X)
elif num_loops==2:
dists=self.compute_distances_two_loops(X)
else:
raise ValueError('Invalid value %d for num_loops' % num_loops)
return self.predict_labels(dists,k=k)

def cumpute_distances_two_loops(self,X):
num_test=X.shape[0]
num_train=self.X_train.shape[0]
# 建立一个num_test 行,num_train列的二维矩阵,
# 矩阵中ij列表示:第i 个测试数据与第j个训练数据之间的距离
dists=np.zeros((num_test,num_train))
print(X.shape,self.X_train.shape)
for i in range(num_test):
for j in range(num_train):
dists[i,j]=np.sqrt(np.sum((X[i,:]-self.X_train[j,:])**2))
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):
dists[i,:]=np.sqrt(np.sum(np.square(self.X_train-X[i,:]),axis=1))
return dists

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))
test_sum=np.sum(np.square(X),axis=1)
train_sum=np.sum(np.square(self.X_train),axis=1)
inner_product=np.dot(X,self.X_train.T)
dists=np.sqrt(-2*inner_product+test_sum.reshape(-1,1)+train_sum)
return dists

def predict_labels(self,dists,k=1): #2
num_test=dists.shape[0] # dists.shape[0] 是测试样本的个数
y_pred=np.zeros(num_test)
for i in range(num_test):
closest_y=[]
# argsort函数返回的是数组值从小到大的索引值;
# 参考链接:https://blog.csdn.net/maoersong/article/details/21875705
y_indicies=np.argsort(dists[i,:],axis=0) #2.1 #dists按从小到大排序,并返回索引
closest_y=self.y_train[y_indicies[: k]] #2.2 #最近的k个值赋给closest_y

# numpy.squeeze()这个函数,此函数的作用是从数组的形状中删除单维条目,即把shape中为1的维度去掉
# 参考链接:https://blog.csdn.net/tracy_leaf/article/details/79297121
if np.shape(np.shape(closest_y))[0] != 1: ############增加程序
closest_y = np.squeeze(closest_y) ############增加程序
y_pred[i]=np.argmax(np.bincount(closest_y)) #2.3
return y_pred

3. 训练、测试与结果可视化:train.py

'''参考链接
https://zhuanlan.zhihu.com/p/28204173
https://blog.csdn.net/bingo_6/article/details/80205341
'''
import numpy as np
from data_utils import load_cifar10
import matplotlib.pyplot as plt
from knn import KNearestNeighbor
import time

plt.figure()
#1. 数据载入部分 数据集:共有50000张训练集,10000张测试集
#调用data_utils 中的 load_cifar10 进行加载数据
x_train,y_train,x_test,y_test=load_cifar10('../data/cifar-10-batches-py')
#查看数据载入的结果
print(" 1. 验证数据载入的结果")
print('training data shape:',x_train.shape)
print('training labels shape:',y_train.shape)
print('test data shape:',x_test.shape)
print('test labels shape:',y_test.shape)

#2. 50000 张训练集每一类中随机挑选samples_per_class张图片进行展示
print(" 2. 50000 张训练集每一类中随机挑选samples_per_class张图片进行展示")
classes=['plane','car','bird','cat','deer','dog','frog','horse','ship','truck']
num_claesses=len(classes)
#每类选取 samples_per_class(每个类的样本) 张图片进行展示
samples_per_class=7
for y ,cls in enumerate(classes): # enumerate 列举,classes中有10个元素,所以进行了10次循环
idxs=np.flatnonzero(y_train==y) # np.flatnonzero(y_train==y): 返回类标签是y的元素的下标
idxs=np.random.choice(idxs,samples_per_class,replace=False)
for i ,idx in enumerate(idxs): # samples_per_class 的值是7,所以最终idxs 中有7个元素,索引从0开始
# enumerate函数解释,参考链接:https://www.jb51.net/article/128304.htm
#对一个可遍历的数据对象(如列表、元组或字符串)enumerate会将该数据对象组合为一个索引序列,同时列出索引和数据。
plt_idx=i*num_claesses+y+1 # i 表示idxs中的第i个元素应该摆放在对应的第y类(列)中的第i行, +1 的原因是画图时,下标从1 开始
plt.subplot(samples_per_class,num_claesses,plt_idx) # samples_per_class行,num_claesses列,中第plt_idx 个元素
plt.imshow(x_train[idx].astype('uint8'))
plt.axis('off') # 不显示坐标尺寸; 参考链接:https://blog.csdn.net/haoji007/article/details/52063168/
if i ==0:
plt.title(cls)
plt.show()

#3. 为了加快我们的训练速度,我们只选取5000张训练集,500张测试集(读者可之后使用全部数据集进行训练和测试)
# 重新赋值 x_trainy_trainx_testy_test,取其中前多少张图片进行试验
num_training=5000
mask=range(num_training)
x_train=x_train[mask]
y_train=y_train[mask]
num_test=500
mask=range(num_test)
x_test=x_test[mask]
y_test=y_test[mask]

#4. 数据载入部分已经算是完成了,但是为了欧氏距离的计算,我们把得到的图像数据拉长成行向量
# 原始数据 x_train 5000 张图片,即 5000*32*32*3 ;通过该reshape函数之后x_train 变成:5000*3072,即将每一张图片拉成了一个行向量
x_train=np.reshape(x_train,(x_train.shape[0],-1))
x_test=np.reshape(x_test,(x_test.shape[0],-1))
print("4. 输出图像数据拉长成行向量后的shape")
print(x_train.shape,x_test.shape)

#5. 为了能够预测每个图像的类别,我们首先要计算每个测试集图像与训练集图像的欧氏距离,计算代码如下:
classifier=KNearestNeighbor()
# knn 的训练只是对数据进行保存(对图像和对应的类标签进行保存)
classifier.train(x_train,y_train)
# knn cumpute_distances_two_loops 是建立一个num_test 行,num_train列的二维矩阵,
# 矩阵中ij列表示:第i 个测试数据(图片)与第j个训练数据(图片)之间的距离
dists=classifier.cumpute_distances_two_loops(x_test)
print("5. 输出计算出的二维距离矩阵:")
print(dists)
# 距离得出之后,就可以预测测试集的类别了
y_test_pred = classifier.predict_labels(dists, k=1)
# 这里我们设置的k=1,也就是最近邻。

# 那么我们如何评判我们得到的预测结果是否正确呢?模型评估也是机器学习中的一个重要概念,这里我们使用准确率作为模型的评价指标,代码如下:
#6. 计算预测准确率
num_correct = np.sum(y_test_pred == y_test)
accuracy = float(num_correct) / num_test
print("6. 输出k=1 时的准确率:")
print('got %d / %d correct => accuracy: %f' % (num_correct, num_test, accuracy))

#7. 与其他两种计算方式进行比较
print("7. 从结果(具体数值)方面与其他两种计算方式进行比较:")
#compute_distances_one_loop的计算方式,代码如下:
dists_one=classifier.compute_distances_one_loop(x_test)
difference=np.linalg.norm(dists-dists_one,ord='fro')
print('difference was: %f' % difference)
# compute_distances_no_loop的计算方式,代码如下:
dists_no=classifier.compute_distances_no_loops(x_test)
difference2=np.linalg.norm(dists-dists_no,ord='fro')
print('difference was: %f' % difference2)

#8. 既然三种方法得到的答案是一样的,那么我采用任意一种方法计算都是可以的吧?答案是肯定的,
# 这三种方法的差别在于它们的计算时间不同,我们来做下比较。比较代码如下:
print("7. 从时间方面与其他两种计算方式进行比较")
def time_function(f,*args):
tic=time.time()
f(*args)
toc=time.time()
return toc-tictwo_loop_time

=time_function(classifier.cumpute_distances_two_loops,x_test)
print('two loops version took %f seconds' % two_loop_time)
one_loop_time
=time_function(classifier.compute_distances_one_loop,x_test)
print('one loop version took %f seconds' % one_loop_time)
no_loops_time
=time_function(classifier.compute_distances_no_loops,x_test)
print('no loops version took %f seconds' % no_loops_time)
#我们可以发现,同样的结果,但是计算所花费的时间却是不一样的,明显compute_distances_no_loops所花费的时间更少。

#9. 交叉验证
#在机器学习中,当数据量不是很充足时,交叉验证是一种不错的模型选择方法(深度学习数据量要求很大,
#一般是不采用交叉验证的,因为它太费时间),本节我们就利用交叉验证来选择最好的k值来获得较好的预测的准确率。
#这里,我们采用S折交叉验证的方法,即将数据平均分成S份,一份作为测试集,其余作为训练集,一般S=10,本文将S设为5,即代码中的num_folds=5。num_folds
=5k_choices
=[1,3,5,8,10,12,15,20,50,100]x_train_folds
=[]y_train_folds
=[]
y_train
=y_train.reshape(-1,1) # y 只带的是标签类,所以让其变为只有一列的数据,每一行对应一张图片的一个类标签
# x_train ,在第 54 行代码:x_train=np.reshape(x_train,(x_train.shape[0],-1)) ,中由一张图片被拉成了一个行向量x_train_folds
=np.array_split(x_train,num_folds) #1y_train_folds
=np.array_split(y_train,num_folds)
k_to_accuracies
={} #2

for k in k_choices:
# 参考链接:https://blog.csdn.net/weixin_45086637/article/details/90757027
# setdefault方法用于设置key的默认值。该方法接收两个参数,第1个参数表示key,第2个参数表示默认值。
# 如果key在字典中不存在,那么setdefault方法会向字典中添加这个key,并用第2个参数作为key的值。该方法会返回这个默认值。
# 如果未指定第2个参数,那么key的默认值是None。如果字典中已经存在这个keysetdefault不会修改key原来的值,
# 而且该方法会返回key原来的值。'''
k_to_accuracies.setdefault(k,[]) # k 指的是knn中的k

for i in range(num_folds): #3 num_folds 是交叉验证的折数,此处设置为 5 折交叉验证
classifier=KNearestNeighbor()
x_val_train=np.vstack(x_train_folds[0:i]+x_train_folds[i+1:]) #3.1 i 部分作为测试
y_val_train = np.vstack(y_train_folds[0:i] + y_train_folds[i + 1:])

# y_val_train[:,0] 表示取 y_val_train 数组中所有行的第1列数据
# 参考链接:https://blog.csdn.net/u014159143/article/details/80307717
y_val_train=y_val_train[:,0]
classifier.train(x_val_train,y_val_train) # knn的训练就是将数据保存下来

for k in k_choices:
y_val_pred=classifier.predict(x_train_folds[i],k=k) #3.2 x_train_folds[i] 部分作为测试,并指定 k 的值
num_correct=np.sum(y_val_pred==y_train_folds[i][:,0]) # y_val_train[[i][:,0] 表示第i个样本的类标签
accuracy=float(num_correct)/len(y_val_pred)
k_to_accuracies[k]=k_to_accuracies[k]+[accuracy] ################ 什么意思 ???????##########################
''' 参考链接:https://blog.csdn.net/autoliuweijie/article/details/51970753
故而猜测” k_to_accuracies[k]+[accuracy] 实现向每一行中添加元素,从而实现每一行中由空变为五个元素
k_to_accuracies 最终存储的内容如下:

k_to_accuracies = {
1: [0.24, 0.23, 0.24, 0.25, 0.29],
3: [0.17, 0.23, 0.32, 0.22, 0.23],
5: [0.12, 0.21, 0.27, 0.19, 0.18],
8: [0.13, 0.23, 0.26, 0.16, 0.2],
10: [0.16, 0.18, 0.24, 0.16, 0.19],
12: [0.17, 0.19, 0.24, 0.2, 0.26],
15: [0.17, 0.23, 0.19, 0.12, 0.14],
20: [0.12, 0.17, 0.19, 0.12, 0.2],
50: [0.2, 0.16, 0.17, 0.16, 0.14],
100: [0.16, 0.15, 0.19, 0.19, 0.19],}
'''
print("9. 交叉验证 -- 输出k_to_accuracies 的结果:")
print(k_to_accuracies)

for k in sorted(k_to_accuracies): #4
sum_accuracy=0
for accuracy in k_to_accuracies[k]:
print('k=%d, accuracy=%f' % (k,accuracy))
sum_accuracy+=accuracy
print('the average accuracy is :%f' % (sum_accuracy/5))
''' 输出样例:
k=1, accuracy=0.263000
k=1, accuracy=0.257000
k=1, accuracy=0.264000
k=1, accuracy=0.278000
k=1, accuracy=0.266000
the average accuracy is :0.265600
k=3, accuracy=0.239000
k=3, accuracy=0.249000
k=3, accuracy=0.240000
k=3, accuracy=0.266000
k=3, accuracy=0.254000
the average accuracy is :0.249600
'''
#通过比较,我们知道当k=10时,准确率最高。

#10. 为了更形象的表示准确率,我们借助matplotlib.pyplot.errorbar函数来均值和偏差对应的趋势线,代码如下:
for k in k_choices:
accuracies=k_to_accuracies[k]
plt.scatter([k]*len(accuracies),accuracies) # 画散点图
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=10时,准确率最高。

#11. 已经通过交叉验证选择了最好的k值,下面我们就要使用最好的k值来完成预测任务,代码如下:best_k
=10classifier
=KNearestNeighbor()classifier
.train(x_train,y_train)y_test_pred
=classifier.predict(x_test,k=best_k)
num_correct
=np.sum(y_test_pred==y_test)accuracy
=float(num_correct)/num_test
print('got %d / %d correct => accuracy: %f' % (num_correct,num_test,accuracy))

'''
总结:以上我们完成了模型构建、数据载入、预测输出工作,根据预测测试集准确率可知,
该分类器的准确率并不高(相对于卷积神经网络接近100%的准确率来说),
在实际中也很少使用knn算法,但是通过这个算法我们可以对图像分类问题的基本过程有个大致的了解。
'''
原文地址:https://www.cnblogs.com/lyj0123/p/11697632.html