文章目录
一、数据集处理
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 + 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')
最后处理好的图片:
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值比较小时出现的不同水果算出的欧式距离很小、相同水果算出欧式距离比较大的情况,最后测试结果比较理想。不同水果总体差异明显: