基于OpenCV和Keras的人脸识别系列手记:
项目完整代码参见Github仓库。
项目完整代码参见Github仓库。
本篇手记是上面这一系列的第七篇。
在系列的上一篇手记里,我完成了将图片转化为128维特征向量的工作,这篇要用这些128维特征向量的数据来训练一个KNN模型。
KNN算法的基本思想
先来看看KNN算法的基本思想,KNN是K nearest neighbor的缩写,中文名称是k-近邻算法。一开始学习这个算法,我是通过斯坦福的计算机视觉课程cs231n,里面有一节专门介绍了KNN算法并且还有配套的编程作业,学过以后我觉得KNN算法就是对需要预测的数据,将其与所有训练数据比较,得到训练数据中k个距离(通常是欧氏距离)预测数据最近的样本,统计其分类,最后将k个样本中占最多数的分类作为对预测数据的分类预测。KNN算法中,k值是一个需要调整的超参数。
如果说上面的通用描述还比较抽象,不好理解,我来把它引申到这个人脸识别项目里:假如现在摄像头实时探测到一张人脸,先计算这张人脸图像和我之前准备的两千多张人脸图像128维特征向量的欧氏距离,就会得到两千多个欧氏距离,假设k取7,在这两千多个欧氏距离里取7个最小的,如果其中有四个以上欧氏距离所对应的分类标签都是“我”,那么就预测这张探测到的人脸就是我。
可以看到,KNN算法的思想很简单,也确实是所有机器学习算法里最简单的之一,不过,我在实际使用的时候发现还是有许多细节需要注意。下面就来讲解我是如何实现用KNN算法识别人脸的。
scikit-learn
在这个项目里,我是用scikit-learn这个基于Python的机器学习库来实现KNN算法的,scikit-learn覆盖了数据集的加载、预处理,模型的建立、训练、预测和持久化以及性能评估等机器学习的完整流程,利用scikit-learn能很方便地实现KNN、SVM等机器学习常用算法。
Holdout验证与K折交叉验证
Facenet+KNN方案整体的思路其实和利用人脸数据训练一个简单的神经网络模型里类似,只是训练数据由人脸图片变成了经过Facenet提取的128维人脸特征,最终的预测模型由CNN变成了KNN,然后值得一提的一个不同就是我在这里使用了K折交叉验证。
继续抽象的理论之前还是先上一些代码,首先是建立一个KNN模型的类,用来建立、训练、测试和持久化KNN模型:
class Knn_Model:
# 初始化构造方法
def __init__(self):
self.model = None
在系列前一篇手记的最后我提到了,在准备训练数据的时候我没有像之前用简单CNN的方案中那样,将准备好的训练数据随机按70%/30%的比例划分为训练集和测试集,这种一部分数据集用于训练,一部分数据集用于测试的方式叫做Holdout验证,由于训练数据和测试数据并没有交叉使用,这种验证方式并不能算是交叉验证。一开始训练KNN模型的时候我是沿用了Holdout验证,将Facenet提取的128维人脸特征七比三随机划分为训练集和测试集:
X_train, X_test, y_train, y_test = train_test_split(X_embedding, labels, test_size = 0.3, random_state = random.randint(0, 100))
上面train_test_split方法的random_state参数的值是一个0到100之间的随机整数,因此每次运行程序时数据的划分都不一样,我在实际运行程序的时候发现,有的时候KNN模型的测试准确率能达到90%+,有的时候只有80%+。虽然最后实际运行人脸识别程序的时候效果差不多,但是在训练一个模型的时候,我肯定希望准确率越高越好,因此,我改用了K折交叉验证:
K次交叉验证,将训练集分割成K个子样本,一个单独的子样本被保留作为验证模型的数据,其他K-1个样本用来训练。交叉验证重复K次,每个子样本验证一次,平均K次的结果或者使用其它结合方式,最终得到一个单一估测。这个方法的优势在于,同时重复运用随机产生的子样本进行训练和验证,每次的结果验证一次,10次交叉验证是最常用的。
在K折交叉验证里,由于训练数据和测试数据交叉使用,最终的结果会更稳定、准确,受数据划分的影响也大大减小。
下面在Knn_Model类里定义一个cross_val_and_build_model方法用来实现K折交叉验证并选择最佳的模型(超参数k):
def cross_val_and_build_model(self, dataset):
k_range = range(1,31)
# k_range = range(1,60)
k_scores = []
print("k vs accuracy:")
for k in k_range:
knn &#