文章目录

  • 一、数据集处理
      • 1、下载数据集
      • 2、统一数据集格式
      • 3、加载数据集
  • 二、分离训练集、验证集
  • 三、定义KNN模型
      • 1、计算欧式距离
      • 2、对所有距离排序
      • 3、获取前k个样本的标签
      • 4、返回出现次数最多的标签
      • 5、KNN算法代码
  • 四 、测试模型
      • 1、K取值和正确率曲线
      • 2、结果分析

一、数据集处理

1、下载数据集

尽可能的寻找更多苹果,香蕉,杨桃的图片,作为本次的数据集。

2、统一数据集格式

利用代码统一图片大小和文件格式
统一图片名称,对应种类图片用 名称+下划线+编号 ,便于为图片加上标签

# 统一图片格式fileList = os.listdir(r"C:\Users\cx\Desktop\work\machine_learning\knn\fruit\carambola")# 输出此文件夹中包含的文件名称print("修改前:" + str(fileList)[1])# 得到进程当前工作目录currentpath = os.getcwd()# 将当前工作目录修改为待修改文件夹的位置os.chdir(r"C:\Users\cx\Desktop\work\machine_learning\knn\fruit\carambola")# 名称变量num = 1# 遍历文件夹中所有文件for fileName in fileList:    # 匹配文件名正则表达式    pat = ".+\ .(jpg|jpeg|JPG)"    # 进行匹配    pattern = re.findall(pat, fileName)    # 文件重新命名    os.rename(fileName, "carambola_" + str(num) + ".jpg")    # fileName.resize(256, 256)    # 改变编号,继续下一项    num = num + 1print("***************************************")# 改回程序运行前的工作目录# os.chdir(currentpath)# 刷新sys.stdin.flush()# 输出修改后文件夹中包含的文件名称print("修改后:" + str(os.listdir(r"C:\Users\cx\Desktop\work\machine_learning\knn\fruit\carambola"))[1])

统一文件大小

from PIL import Imageimport osimport glob# 修改图片文件大小# filename:图片文件名# outdir:修改后要保存的路径def convertImgSize(filename, outdir, width=256, height=256):    img = Image.open(filename)    try:        new = img.resize((width, height), Image.BILINEAR)        p = os.path.basename(filename)        print(p)        new.save(os.path.join(outdir, os.path.basename(filename)))    except Exception as e:        print(e)if __name__ == '__main__':    # 查找给定路径下图片文件,并修改其大小    for filename in glob.glob('C:/Users/cx/Desktop/work/machine_learning/knn/fruit/carambola/*.jpg"):        convertImgSize(filename, 'C:/Users/cx/Desktop/work/machine_learning/knn/fruit/carambola')

最后处理好的图片:

3、加载数据集

为数据集加上标签:苹果,香蕉,杨桃图片对应标签为: apple, banana,carambola
将图片做归一化处理,展平成为一维数组

# 加载数据集def lode_data():    data = []    labels = []    for img in os.listdir(r"./fruit"):        # 为图片贴标签        label = img.split("_")        labels.append(label[0])        #图片归一化         img = "./fruit/" + img        img = cv2.imread(img, 1)        img = (img - np.min(img)) / (np.max(img) - np.min(img))                            data.append(img.flatten())    data = np.array(data)    labels = np.array(labels)     return data, labels

二、分离训练集、验证集

我这里就直接用封装好的方法,将上面加载好的验证集,分成30%验证集,70验证集。

data, labels = lode_data()# 从样本中随机抽取30% 做验证集, 其余70% 做训练集train_data,test_data,train_labels,test_labels = train_test_split(data, labels, test_size = 0.30, random_state = 20, shuffle = True)        

三、定义KNN模型

KNN模型定义都有一个套路,按照对应的步骤就能很好实现出来,具体步骤包括:

  • 计算欧式距离
  • 按照计算距离排序
  • 获取前k个样本标签
  • 返回出现次数最多标签

1、计算欧式距离

将图片展开成为一维向量之后,可以计算测试集里面每一张图片与其他图片的欧式距离。对应每一个像素点先求差,在求平方和,最后开方就得到了欧式距离。

    dis = (np.tile(test_img, (data .shape[0], 1)) - data) ** 2    dis_sq = dis.sum(axis=1)    dis_res = dis_sq ** 0.5

2、对所有距离排序

利用argsort()函数对所有距离排序,返回对应的索引

 dis_sort = dis_res.argsort()

3、获取前k个样本的标签

构造一个分类器,遍历距离最短的前k个索引,根据索引得到对应的标签,最后将标签全部放到分类器中。

    classcount={}    for i in range(k):        # 取距离最近的前k个,获取对应标签        vote_label = labels[dis_sort[i]]        classcount[vote_label] = classcount.get(vote_label, 0) + 1

4、返回出现次数最多的标签

将所有标签降序排序,第一个就是出现次数最多的标签。

    # 将获取的标签进行降序排序    sorted_classcount = sorted(classcount.items(), key = operator.itemgetter(1), reverse = True)    # 返回出现次数最多的标签    return sorted_classcount[0][0]

5、KNN算法代码

# knn算法实现def knn(test_img, data , labels, k):    # 计算欧氏距离    dis = (np.tile(test_img, (data .shape[0], 1)) - data) ** 2    dis_sq = dis.sum(axis=1)    dis_res = dis_sq ** 0.5    # 按照距离依次排序, 返回索引    dis_sort = dis_res.argsort()    # 构造分类器    classcount={}    for i in range(k):        # 取距离最近的前k个,获取对应标签        vote_label = labels[dis_sort[i]]        classcount[vote_label] = classcount.get(vote_label, 0) + 1    # 将获取的标签进行降序排序    sorted_classcount = sorted(classcount.items(), key = operator.itemgetter(1), reverse = True)    # 返回出现次数最多的标签    return sorted_classcount[0][0]

四 、测试模型

在上面KNN模型定义中,输入一个测试数据,会返回一个距离为K中,出现次数最多的标签。用测试集里面每一个测试样本,利用模型返回的标签和自己正确的标签比对,最终可以得到正确率。K的值从0遍历到20,输出每个K值对应的正确率。

# 获取标签匹配成功的概率def test_all(train_data, train_labels, test_data, test_labels, k):    right = 0    for i in range(len(test_data)):        if knn(test_data[i], train_data, train_labels, k) == test_labels[i]:            right+=1    return right/len(test_data)# 训练def  train():    right = []    data, labels = lode_data()    # 从样本中随机抽取20% 做验证集, 其余80% 做训练集    for k in range(0, (len(labels)-1)):        train_data,test_data,train_labels,test_labels = train_test_split(data, labels, test_size = 0.20                                                 ,random_state = 20, shuffle = True)         right.append(test_all(train_data, train_labels, test_data, test_labels, k + 1))        i = str(k + 1)         print('K = {}, 正确率 = {}'.format(i, right[k]))    plt.plot( range(len(test_data) + 1) , right)    plt.show()train()

1、K取值和正确率曲线

2、结果分析

取几个比较具有代表性的k值分析:

  1. K = 5 时 ,训练的准确率只有0.59,导致这个结果原因可能是两张图片背景太简单,有很多空白的地方 ,而且两类水果形状和大小相似 。这些空白的地方灰度值十分相似,最后不同水果算出的欧式距离很小。在K值比较小时,训练集两张图片之间具有特殊性,导致结果不是很理想。例如下面两张图片,框出的大概就是图片大小,有很多灰度相同的空白区域,下面两种不同水果但距离比较小:

  2. K = 40 时,正确率为0.72。我的三种水果的样本集数量不是完全相等,苹果50张左右、杨桃40张左右、香蕉60张左右。最后k值太大时,在距离为k的测试结果中,样本数量比较多的标签更容易被统计。而且有的图片背景比较模糊,两张图片差异比较大,最后相同水果算出的距离比较大,导致匹配概率比较小。例如下面同种水果距离比较大:

  3. K = 30时,正确率为0.81。由于三种数据集总体上的差异比较明显,又避免了k值比较小时出现的不同水果算出的欧式距离很小、相同水果算出欧式距离比较大的情况,最后测试结果比较理想。不同水果总体差异明显: