一、数据集处理

1、下载数据集

尽可能的寻找更多苹果,香蕉,杨桃的图片,作为本次的数据集。
KNN——水果分类-LMLPHP

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 + 1
print("***************************************")
# 改回程序运行前的工作目录
# 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 Image
import os
import 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')

最后处理好的图片:
KNN——水果分类-LMLPHP

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取值和正确率曲线

KNN——水果分类-LMLPHP

KNN——水果分类-LMLPHP

2、结果分析

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

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

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

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

11-06 11:19