文章目录
- 一、数据集处理
- 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值分析:
K = 5 时 ,训练的准确率只有0.59,导致这个结果原因可能是两张图片背景太简单,有很多空白的地方 ,而且两类水果形状和大小相似 。这些空白的地方灰度值十分相似,最后不同水果算出的欧式距离很小。在K值比较小时,训练集两张图片之间具有特殊性,导致结果不是很理想。例如下面两张图片,框出的大概就是图片大小,有很多灰度相同的空白区域,下面两种不同水果但距离比较小:
K = 40 时,正确率为0.72。我的三种水果的样本集数量不是完全相等,苹果50张左右、杨桃40张左右、香蕉60张左右。最后k值太大时,在距离为k的测试结果中,样本数量比较多的标签更容易被统计。而且有的图片背景比较模糊,两张图片差异比较大,最后相同水果算出的距离比较大,导致匹配概率比较小。例如下面同种水果距离比较大:
K = 30时,正确率为0.81。由于三种数据集总体上的差异比较明显,又避免了k值比较小时出现的不同水果算出的欧式距离很小、相同水果算出欧式距离比较大的情况,最后测试结果比较理想。不同水果总体差异明显: