0%

基于LDA的人脸识别方法-fisherface

基于LDA的人脸识别方法–fisherface

LDA几何意义

最小化类内散度Jw,最大化类间散度Jb。这样的目的是使得投影后同一类的数据尽可能聚集,不同类的数据尽可能分散,以达到更好的分类效果。

LDA的数学推导过程

Sw:类内散度,操作对象是类内数据,w表示within

Sb:类间散度,操作对象是类的均值,b表示between

St:总散度,操作对象是所有数据,t表示total

假设一共有N个元素,其中有C个类,每个类都有一定数量的元素,我们的目的是找到一个投影矩阵,使得对原数据进行投影后,得到的数据的Jw最小,Jb最大。为了达到Jw最小,Jb最大的目的,可以有多种目标函数,比如使得Jb/Jw最大,比如使得Jw/Jb最小,比如使得Jb-Jw最大,都是可以的。这里以第一种目标,即使得投影后数据的Jb/Jw最大为目标,进行推导。

先证明类间散度Jb可以用类间散度矩阵Sb来表示:

证明过程:

1

推导到这里还没完,我们需要用到St = Sw + Sb这个性质,后面会进行推导。通过这个性质,我们可以得到Sb的另一种计算方法,如下图所示:

通过这个结论,进一步得到类间散度Jb用类间散度矩阵Sb表示的结果:

下面证明:St = Sw + Sb,即:总体散度矩阵 = 类内散度矩阵 + 类间散度矩阵。注意下面这个证明过程的N和C与LDA推导过程的N、C不同,不要搞混。

2

3

4

下面证明,类内散度Jw可以用类内散度矩阵表示:

下面推导LDA投影矩阵的计算方法:

若Sw是可逆的,或者说是非奇异的,那么投影矩阵W的求解就变成了一个特征分解问题。

若Sw不可逆,问题就成了所谓的“小样本问题”,常见的解决方法有:

  • 引入正则项进行扰动,比如对Sw + 10^(-6)*单位矩阵,适用于样本较小的情况。
  • 使用PCA降维——矩阵非奇异的本质是矩阵维数不等于矩阵的秩,那么使用PCA将矩阵降至满秩的状态即可解决问题。
  • 寻找一些其他衍生算法,很多论文为了解决该问题提出了一些衍生算法,比如NLDA(null space LDA)。

LDA与PCA的比较

LDA与PCA实际上十分类似,LDA和PCA本质上都是对矩阵(人脸)进行降维,并且降维后仍然可以重建人脸。

它们的不同之处主要在于它们降维目的的不同,PCA降维是朝着”我要最小化重构误差“的思路去降维的,而PCA是朝着”我要最小化类内散度,最大化类间散度“的思路去降维的。换句话说,PCA是为了更好的压缩与重建,而LDA是为了更好地分类。相应地,PCA降维不需要数据标签,而LDA降维则需要数据标签,也就是说,PCA是无监督学习,LDA是有监督学习。

在进行小数据集的人脸识别时,PCA和LDA的准确率相差不大,甚至PCA的准确率会略高于LDA。但是在大数据集中,LDA对比PCA有着明显的优势。

一些有趣的可视化

这里将一些人脸使用LDA方法投影到了三维空间,同一种颜色代表同一个人脸的不同照片。

fisherface算法步骤

  1. 先计算每个类的均值,算Sb和Sw的时候均需要用到。
  2. 计算类间散度矩阵Sb。
  3. 计算类内散度矩阵Sw。
  4. 构造目标函数(多种不同的目标函数)。
  5. 对目标函数进行特征分解。
  6. 取出一定数量的特征向量得到投影矩阵。
  7. 将测试数据投影到子空间中,使用KNN进行分类。

代码和数据集

这里代码适配了四个不同的数据集,可以在前面修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
clear all;

% 框架适用于:每个人拥有相同数量的图片

% 可选数据集:"ORL" "Yale" "COIL""AR"
dataset_name = "ORL";

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% ORL
if (dataset_name == "ORL")
load('ORL4646.mat');
people_num = 40; % 人(类)数
total_pic_num = 400; % 总图片数量
row = 46; % 行
column = 46; % 列
train_pic_num_of_each = 6; % 每个人的训练图片数
data = ORL4646;
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% Yale
if (dataset_name == "Yale")
load('Yale5040165.mat');
people_num = 15; % 人(类)数
total_pic_num = 165; % 总图片数量
row = 50; % 行
column = 40; % 列
train_pic_num_of_each = 7; % 每个人的训练图片数
data = Yale5040165;
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% COIL
if (dataset_name == "COIL")
load('COIL100.mat');
people_num = 100; % 人(类)数
total_pic_num = 7200; % 总图片数量
row = 32; % 行
column = 32; % 列
train_pic_num_of_each = 44; % 每个人的训练图片数
data = double(COIL100); % 原始数据
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% AR
if (dataset_name == "AR")
load('AR120p20s50by40.mat');
people_num = 120; % 人(类)数
total_pic_num = 2400; % 总图片数量
row = 50; % 行
column = 40; % 列
train_pic_num_of_each = 12; % 每个人的训练图片数
data = AR120p20s50by40; % 原始数据
end

% 以上是不同数据集的数据初始化处理
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 数据分类

dimension = row * column; % 图片维度
pic_num_of_each = total_pic_num / people_num; % 每个人的图片数
test_pic_num_of_each = pic_num_of_each - train_pic_num_of_each; % 每个人的测试图片数

% 重塑后一列是一张脸
reshaped_faces = reshape(data, dimension, total_pic_num);

% 取出每个人图片的一定数量作为测试数据,剩下作为训练数据
test_data_index = [];
train_data_index = [];
for i=0:people_num-1
test_data_index = [test_data_index pic_num_of_each*i+1:pic_num_of_each*i+test_pic_num_of_each];
train_data_index = [train_data_index pic_num_of_each*i+test_pic_num_of_each+1:pic_num_of_each*(i+1)];
end
test_data = reshaped_faces(:, test_data_index);
train_data = reshaped_faces(:,train_data_index);
% waitfor(show_faces(train_data));

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 降维算法

% 算每个类的平均
p = 1; % k 表示每一张图
people_mean = zeros(dimension, people_num); % 每个人的所有脸的平均为一列
for i=1:people_num
% 求一列(即一个人)的均值
temp = people_mean(:,i);
% 遍历每个人的train_pic_num_of_each张用于训练的脸,相加算平均
for j=1:train_pic_num_of_each
temp = temp + train_data(:,p);
p = p + 1;
end
people_mean(:,i) = temp / train_pic_num_of_each;
end

% 算类类间散度矩阵Sb
Sb = zeros(dimension, dimension);
all_mean = mean(train_data, 2); % 全部的平均
for i=1:people_num
% 以每个人的平均脸进行计算,这里减去所有平均,中心化
centered_data = people_mean(:,i) - all_mean;
Sb = Sb + centered_data * centered_data';
end
Sb = Sb / people_num;

% 算类内散度矩阵Sw
Sw = zeros(dimension, dimension);
p = 1; % p表示每一张图片
for i=1:people_num % 遍历每一个人
for j=1:train_pic_num_of_each % 遍历一个人的所有脸计算后相加
centered_data = train_data(:,p) - people_mean(:,i);
Sw = Sw + centered_data * centered_data';
p = p + 1;
end
end
Sw = Sw / (people_num * train_pic_num_of_each);

% 目标函数一:不可逆时需要正则项扰动
% Sw = Sw + eye(dimension)*10^-6;
% target = Sw^-1 * Sb;

% 目标函数二:无正则项扰动,经典形式
target = Sw^-1 * Sb;

% 目标函数三:相减形式(也符合几何意义,即最大化Sb,最小化Sw)
% target = Sb - Sw;

% 目标函数四:NLDA 零空间LDA方法
% Q = null(Sw);
% target = Q*Q'*Sb*(Q*Q')';

% 求特征值、特征向量
[eigen_vectors, dianogol_matrix] = eig(target);
eigen_values = diag(dianogol_matrix);

% 对特征值、特征向量进行排序
[sorted_eigen_values, index] = sort(eigen_values, 'descend');
eigen_vectors = eigen_vectors(:, index);

% fisherface
for i=1:8
subplot(2, 4, i);
fig = show_face(eigen_vectors(:, i), row, column);
end
waitfor(fig);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 识别与可视化

index = 1;
X = [];
Y = [];
for i=1:5:101

% 投影矩阵
project_matrix = eigen_vectors(:,1:i);
projected_train_data = project_matrix' * (train_data - all_mean);
projected_test_data = project_matrix' * (test_data - all_mean);

% KNN的k值
K=1;

% 用于保存最小的k个值的矩阵
% 用于保存最小k个值对应的人标签的矩阵
minimun_k_values = zeros(K,1);
label_of_minimun_k_values = zeros(K,1);

% 测试脸的数量
test_face_number = size(projected_test_data, 2);

% 识别正确数量
correct_predict_number = 0;

% 遍历每一个待测试人脸
for each_test_face_index = 1:test_face_number

each_test_face = projected_test_data(:,each_test_face_index);

% 先把k个值填满,避免在迭代中反复判断
for each_train_face_index = 1:K
minimun_k_values(each_train_face_index,1) = norm(each_test_face - projected_train_data(:,each_train_face_index));
label_of_minimun_k_values(each_train_face_index,1) = floor((train_data_index(1,each_train_face_index) - 1) / pic_num_of_each) + 1;
end

% 找出k个值中最大值及其下标
[max_value, index_of_max_value] = max(minimun_k_values);

% 计算与剩余每一个已知人脸的距离
for each_train_face_index = K+1:size(projected_train_data,2)

% 计算距离
distance = norm(each_test_face - projected_train_data(:,each_train_face_index));

% 遇到更小的距离就更新距离和标签
if (distance < max_value)
minimun_k_values(index_of_max_value,1) = distance;
label_of_minimun_k_values(index_of_max_value,1) = floor((train_data_index(1,each_train_face_index) - 1) / pic_num_of_each) + 1;
[max_value, index_of_max_value] = max(minimun_k_values);
end
end

% 最终得到距离最小的k个值以及对应的标签
% 取出出现次数最多的值,为预测的人脸标签
predict_label = mode(label_of_minimun_k_values);
real_label = floor((test_data_index(1,each_test_face_index) - 1) / pic_num_of_each)+1;

if (predict_label == real_label)
%fprintf("预测值:%d,实际值:%d,正确\n",predict_label,real_label);
correct_predict_number = correct_predict_number + 1;
else
%fprintf("预测值:%d,实际值:%d,错误\n",predict_label,real_label);
end
end

correct_rate = correct_predict_number/test_face_number;

X = [X i];
Y = [Y correct_rate];

fprintf("k=%d,i=%d,总测试样本:%d,正确数:%d,正确率:%1f\n", K, i,test_face_number,correct_predict_number,correct_rate);

if (i == 101)
waitfor(plot(X,Y));
end
end

% 二三维可视化
class_num_to_show = 3;
pic_num_in_a_class = test_pic_num_of_each;
pic_to_show = class_num_to_show * pic_num_in_a_class;
for i=[2 3]

% 取出相应数量特征脸
project_matrix = eigen_vectors(:,1:i);

% 投影
projected_test_data = project_matrix' * (test_data - all_mean);
projected_test_data = projected_test_data(:,1:pic_to_show);

color = [];
for j=1:pic_to_show
color = [color floor((j-1)/pic_num_in_a_class)*20];
end

% 显示
if (i == 2)
subplot(1, 7, [1, 2, 3, 4]);
scatter(projected_test_data(1, :), projected_test_data(2, :), [], color, 'filled');
for j=1:3
subplot(1, 7, j+4);
fig = show_face(test_data(:,floor((j - 1) * pic_num_in_a_class) + 1), row, column);
end
waitfor(fig);
else
subplot(1, 7, [1, 2, 3, 4]);
scatter3(projected_test_data(1, :), projected_test_data(2, :), projected_test_data(3, :), [], color, 'filled');
for j=1:3
subplot(1, 7, j+4);
fig = show_face(test_data(:,floor((j - 1) * pic_num_in_a_class) + 1), row, column);
end
waitfor(fig);
end
end

% 输入向量,显示脸
function fig = show_face(vector, row, column)
fig = imshow(mat2gray(reshape(vector, [row, column])));
end

用到的数据集:

链接:https://pan.baidu.com/s/1QtnUV_SUaYj6E1PCVAJldw
提取码:n3ne

参考资料

  1. A. M. Martinez and A. C. Kak, “PCA versus LDA,” in IEEE Transactions on Pattern Analysis and Machine Intelligence, vol. 23, no. 2, pp. 228-233, Feb. 2001, doi: 10.1109/34.908974.
  2. https://www.cnblogs.com/liuwu265/p/4724758.html
  3. https://blog.csdn.net/itplus/article/details/12038357

本文地址: https://www.chimaoshu.top/基于LDA的人脸识别方法-fisherface/
版权声明:本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议,转载请注明出处。

Welcome to my other publishing channels