# 基于经典算法和BP神经网络的性别识别
## 1.文档说明
~~~
face_recignition_classic
├── README.md 说明文档
├── imgs 存放图片
│ ├── dataset.png
│ └── src
├── rawdata 人脸数据集
├── face 人脸卷标
│ ├── faceDR
│ └── faceDS
├── add_knn.ipynb 加权KNN算法
├── BP.ipynb bp神经网络
├── knn_svm_tree.ipynb 做特征降维的经典算法(knn、svm和决策树)
├── new_knn_svm_tree.ipynb 未做特征降维的经典算法(knn、svm和决策树)
├── raw_data_read_test.py 读rawdata内的文件
~~~
## 2.数据集
总数居4000张人脸图片,男女比例大约6:4,如下:
![](imgs/dataset.png)
### 2.1数据集处理
由于样本分配稍微有点不均匀,我们采取重采样方式,增加女性的数据,从而使得样本比例基本为1:1;
经过样本的重采样我们最终得到4860个数据。但是存在数据标签缺失的情况,我们实际可使用数据为4500个左右。
4500个数据中,存在相同的人的不同表情等的人脸数据,而且数据相似度较高,于是我们采用了数据增强的方式来提高训练效果。
在实践中发现在数据增强前,数据容易存在过拟合现象,进行了数据增强后,过拟合现象明显减缓。
### 2.2数据集分配
训练集:验证集:测试集 = 8:1:1
训练集和验证集进行k=10的k折交叉验证
## 3.KNN性别分类实现流程
### 3.1 读取数据集的图像和卷标处理<a id="3.1">_</a>
将处理好的数据集的图像读取存到train_images,卷标读取存到train_labels,由于输入的图像过大,我们对图像进行压缩处理,将维度压缩成了100x100的大小,卷标方面由于我们只识别性别,因而只取卷标中的性别分类,对其进行数值化处理,将male处理为1,female为0
~~~
#压缩图像
array_of_img = []
for img in img_list:
if img is not None:
img = img / 255.0
img = cv2.resize(img, (100, 100))
array_of_img.append(img)
train_images = np.array(array_of_img)
array_of_img = []
#性别标签数值化
array_of_labels = []
for label in label_one_hot:
if label is not None:
append_label = label[0]
array_of_labels.append(int(append_label))
train_labels = np.array(array_of_labels)
array_of_labels = []
~~~
### 3.2 划分训练集和测试集<a id="3.2">_</a>
在开始性别分类前,需要对数据集进行划分成训练集和测试集,训练集用来训练分类器(即确定模型的结构和参数),测试集用来评价分类器的性能(即评估模型的分类准确性)。划分完训练集和测试集需要对其重排列,方便之后的训练和评估
~~~
#划分训练集和测试集 训练集:测试集 = 4:1
X_train, X_test, y_train, y_test = train_test_split(train_images, train_labels, test_size=0.2, random_state=3)
X_train = X_train.reshape(X_train.shape[0], -1)
X_test = X_test.reshape(X_test.shape[0], -1)
~~~
### 3.3 特征降维<a id="3.3">_</a>
压缩后图像的特征空间是10000维度,其中包含与性别识别无关的特征(比如背景)。 可以采用特征降维算法(比如Isomap算法,一种类似PCA的特征提取算法), 将1000维度的特征降到85维度。在减少无关特征的同时还减少了训练时间。
~~~
# 特征降维
pca = PCA(n_components=85)
newX = pca.fit_transform(X_train)
xx = pca.transform(X_test)
~~~
### 3.4 用k邻近算法去完成分类
这里使用的是sklearn库里的KNeighborsClassifier,参数设为n_neighbors=3,其他值默认
~~~
knn = KNeighborsClassifier(n_neighbors=3)
~~~
通过比较预测结果和真实结果来得到分类准确率,下图得到的是knn训练的准确率约为82.30%
~~~
knn.fit(newX, y_train)
y_pred_on_train = knn.predict(xx)
acc = metrics.accuracy_score(y_test, y_pred_on_train)
~~~
![](imgs/knn_acc.png)
### 3.5 参数n_neighbors(k值)的选取
在关于k值的选取,我采用的是k=10的k折交叉验证去寻找knn算法的最优k值
交叉验证的作用有两点:
* 在数据集很小的时候能防止过拟合
* 找到合适的模型参数
交叉验证需要对数据集进行重新划分,可以先将数据集划分为训练集和测试集,再对训练集划分为子训练集和子测试集,如下图:
![](imgs/trainset.png)
我这里主要运用训练集来做交叉验证,从而找到最优k值,因为我们要同时观察训练集的子训练集和测试集的效果随着k的增加而变化情况,所以这里直接用 sklearn.model_selection 中的 vlidation_curve 来完成。
~~~
#交叉验证选取最优k值
from matplotlib import pyplot as plt
from sklearn.model_selection import validation_curve
param_name = 'n_neighbors'
param_range = range(1, 20)
train_scores, test_scores = validation_curve(
KNeighborsClassifier(), newX, y_train, cv=kfold,
param_name=param_name, param_range=param_range,
scoring='accuracy')
#训练集和测试集得分
train_scores_mean = np.mean(train_scores, axis=1)
test_scores_mean = np.mean(test_scores, axis=1)
plt.plot(param_range,train_scores_mean, color='red', label='train')
plt.plot(param_range, test_scores_mean, color='green', label='test')
plt.legend('best')
plt.xlabel('param range of k')
plt.ylabel('scores mean')
plt.show()
~~~
运行完上面代码可以得到下图结果,从图很容易看出k的最优值为1
![](imgs/k.png)
当k=1时,knn训练的准确率约为85.29%,此时k值为最优值
![](imgs/bestk.png)
取最优值k值再进行交叉验证观察评估情况,这里使用的是sklearn库里的cross_validate和cross_val_score,使用的是10折交叉验证
cross_val_score是先分片计算后平均的这种方式,可以认为是返回不同模型的平均准确率,cross_validate是会返回每次交叉验证的得分,下图为knn算法交叉验证的结果,从结果看我们的knn算法训练的结果不错,训练时间最短
~~~
kfold = KFold(n_splits=10)
knn_scores = cross_val_score(knn, newX, y_train, cv=kfold)
cv_cross_knn = cross_validate(knn, newX, y_train, cv=kfold, scoring=('accuracy', 'f1'))
print("knn Accuracy: %0.2f (+/- %0.2f)" % (knn_scores.mean(), knn_scores.std() * 2))
print("knn测试结果:",cv_cross_knn)
~~~
![](imgs/knn_kfold.png)
## 4.加权KNN性别分类实现流程
### 4.1加权KNN算法的原理
通过结合KNN本身的分类算法以及对前k个距离加权,来达到分类的目的 wk-nnc算法是对经典knn算法的改进,这种方法是对k个近邻的样本按照他们距离待分类样本的远近给一个权值w,w(i)是第i个近邻的权值,其中1 < i < k,h(i)是待测样本距离第i个近邻的距离,下面是代码实现流程
~~~
#加权KNN
def kNNClassify(inX, dataSet, labels, k = k):
distance = np.sum(np.power((dataSet - inX), 2), axis = 1) # 计算欧几里得距离
sortedArray = np.argsort(distance, kind = "quicksort")[:k]
# 给距离加入权重
w = []
for i in range(k):
w.append((distance[sortedArray[k-1]] - distance[sortedArray[i]]) / (distance[sortedArray[k-1]] - distance[sortedArray[0]]))
count = np.zeros(893)
temp = 0
for each in sortedArray:
count[labels[each]] += 1 + w[temp]
temp += 1
label = np.argmax(count) # 如果label中有多个一样的样本数,那么取第一个最大的样本类别
return label
~~~
由于<a href="#3.1">读取数据集的图像和卷标处理</a>、<a href="#3.2">划分训练集和数据集</a> 和<a href="#3.3">特征降维</a>的操作与KNN算法的一样,详情请点前面跳转
### 4.2用加权KNN算法进行分类
这里我选用了k的范围为1-20,运行add_knn.ipynb文件,可以得到下图结果
![](imgs/add_knn_acc.png)
将不同k值得到的训练准确率对比,如下图所示,可以看出当k=1时加权KNN算法的训练准确率最大约为86.21%
![](imgs/dif_k.png)
### 4.3加权KNN与