1. 实验目的
- 掌握k近邻分类算法的原理
- 掌握基于k近邻分类算法的情感识别基本过程
- 利用MATLAB、python实现该情感识别过程
2. 实验原理
KNN分类的思想是给定一个在特征空间中的待分类样本,如果其附近的K个训练样本大多数属于一个类别,那么他也是这个类别。已知特征集为 {X1,X2,...,XK} ,待分类样本为 X ,计算 X 与 Xl 的距离 D(X,Xl)=∑i=1N[X(i)−Xl(i)]2 , min{D(X,Xl)} 是最近邻,选出 D(X,Xl) 最小的K个,然后投票决定。
- 提取特征,构造特征向量 X1,X2,...,Xn
- 设定K的值
- 提取待识别样本的向量 X ,计算 X 与 Xl 的距离
- 选出 D(X,Xl) 最小的K个,然后投票决定。
3. 实验步骤
3.1 特征提取
见阿木:02 语音情感识别数据集及特征提取,得到5个140*50的不同情绪文件。
3.2 k值设定与训练集、测试集划分
通过k折交叉验证选取k值,或因为该数据集比较小,所以可以令k从1取到设定的最大值,比较不同k值下的识别准确率。
KMAX = 30;
NumberOfTrain=size(fearVec,2)* 0.5; %一半测试用,一半训练用
trainVector=[fearVec(:,1:NumberOfTrain),hapVec(:,1:NumberOfTrain),neutralVec(:,1:NumberOfTrain),sadnessVec(:,1:NumberOfTrain),angerVec(:,1:NumberOfTrain)]; % 构建训练样本集
testVector=[fearVec(:,(NumberOfTrain+1):size(fearVec,2)),hapVec(:,(NumberOfTrain+1):size(hapVec,2)),neutralVec(:,(NumberOfTrain+1):size(neutralVec,2)),sadnessVec(:,(NumberOfTrain+1):size(sadnessVec,2)),angerVec(:,(NumberOfTrain+1):size(angerVec,2))]; % 构建测试样本集
K = 9
data = np.hstack((fear, happy, neutral, sadness, anger))
y = np.array([[i] * 50 for i in range(5)]).flatten()
per = np.random.permutation(250)
data_train = data[:, per[:180]]
label_train = y[per[:180]]
data_test = data[:, per[180:]]
label_test = y[per[180:]]
label_pred = np.zeros(250 - 180)
j = 0
3.3 计算待识别样本与已知样本距离
distanceMatrix=zeros(size(trainVector,2),size(testVector,2)); % 每一列表示某个测试语音与所有训练集样本的距离
%% 计算每个测试样本和训练样本集各样本的距离
for i=1:size(testVector,2)
for j=1:size(trainVector,2)
distanceMatrix(j,i)=norm(testVector(:,i)-trainVector(:,j)); %计算欧氏距离
end
end
python版本:
for test in data_test.T:
scores = np.zeros(len(data_train.T))
for i in range(len(data_train.T)):
scores[i] = np.sum(np.power(test - data_train[:, i], 2))
pos = np.argsort(scores)[:K]
result = label_train[pos]
label = get_most_label(result)
label_pred[j] = label
j += 1
def get_most_label(result):
rst = {}
for r in result:
if r not in rst.keys():
rst[r] = 1
else:
rst[r] += 1
m = sorted(rst.items(), key=lambda x: x[1], reverse=True)
return m[0][0]
3.4 投票表决
%% 统计分类结果 (根据相应的特征向量在数组trainVector或testVector中所处的位置来辨别类型)
totalTestNumber=size(fearVec,2)-NumberOfTrain;
n1=NumberOfTrain;
n2=n1+NumberOfTrain;
n3=n2+NumberOfTrain;
n4=n3+NumberOfTrain;
n5=n4+NumberOfTrain;
p1=size(fearVec,2)-NumberOfTrain;
p2=p1+size(hapVec,2)-NumberOfTrain;
p3=p2+size(neutralVec,2)-NumberOfTrain;
p4=p3+size(sadnessVec,2)-NumberOfTrain;
p5=p4+size(angerVec,2)-NumberOfTrain;
if(n5~=size(trainVector,2)||p5~=size(testVector,2))
disp('data error')
return;
end
acc = zeros(1,KMAX);
for k=1:KMAX
emtionCounter=zeros(1,5);
for i=1:size(distanceMatrix,2)
flag=zeros(1,5);
[sortVec,index]=sort(distanceMatrix(:,i));
% 统计K个近邻中各类别的数量
for j=1:k
if(n1>=index(j)&&index(j)>=1)
flag(1)=flag(1)+1;
elseif(n2>=index(j)&&index(j)>n1)
flag(2)=flag(2)+1;
elseif(n3>=index(j)&&index(j)>n2)
flag(3)=flag(3)+1;
elseif(n4>=index(j)&&index(j)>n3)
flag(4)=flag(4)+1;
else
flag(5)=flag(5)+1;
end
end
[~,index1]=sort(flag);
% 如果K个近邻中数量最多的类别与该样本实际的类别一致,则认为算法识别正确,相应counter加一。
if((p1>=i&&i>=1)&&index1(5)==1)
emtionCounter(index1(5))=emtionCounter(index1(5))+1;
elseif((p2>=i&&i>p1)&&index1(5)==2)
emtionCounter(index1(5))=emtionCounter(index1(5))+1;
elseif((p3>=i&&i>p2)&&index1(5)==3)
emtionCounter(index1(5))=emtionCounter(index1(5))+1;
elseif((p4>=i&&i>p3)&&index1(5)==4)
emtionCounter(index1(5))=emtionCounter(index1(5))+1;
elseif((p5>=i&&i>p4)&&index1(5)==5)
emtionCounter(index1(5))=emtionCounter(index1(5))+1;
end
end
acc(k) = sum(emtionCounter)/(totalTestNumber*5);
end
[acc2,index2]=sort(acc);
k=index2(KMAX);
acc_max = acc2(KMAX);
emtionCounter=zeros(1,5);
4. 实验结果
%% 显示结果
ratio=emtionCounter./totalTestNumber;
bar(ratio);
ylim([0,1])
set(gca,'XTickLabel',{'惊恐','高兴','中性','高兴','生气'});
n=1:5;
for i = 1:length(ratio)
text(n(i)-0.18,ratio(i)+0.02,num2str(ratio(i)));
end
title(strcat('KNN 算法识别结果\_ ',strcat('K = ',num2str(k))));
xlabel('情感类别')
ylabel('识别率')
disp('识别率为');
disp(acc_max);
k取不同值时不同情绪的识别率:
识别率为0.7840
def confusion_matrix_info(y_true, y_pred, labels=['fear', 'happy', 'neutr', 'sad', 'anger'],
title='confusion matrix'):
"""
计算混淆矩阵以及一些评价指标,并将混淆矩阵绘图出来
:param y_true: 真实标签,非one-hot编码
:param y_pred: 预测标签,非one-hot编码
:param labels: 标签的含义
:param title: 绘图的标题
:return:
"""
C2 = confusion_matrix(y_true, y_pred)
C = pd.DataFrame(C2, columns=labels, index=labels)
m, _ = C2.shape
for i in range(m):
precision = C2[i, i] / sum(C2[:, i])
recall = C2[i, i] / sum(C2[i, :])
f1 = 2 * precision * recall / (precision + recall)
print('In class {}:\t total samples: {}\t true predict samples: {}\t'
'acc={:.4f},\trecall={:.4f},\tf1-score={:.4f}'.format(
labels[i], sum(C2[i, :]), C2[i, i], precision, recall, f1))
print('-' * 100, '\n', 'average f1={:.4f}'.format(f1_score(y_true, y_pred, average='micro')))
f, ax = plt.subplots()
sns.heatmap(C, annot=True, ax=ax, cmap=plt.cm.binary)
ax.set_title(title)
ax.set_xlabel('predict')
ax.set_ylabel('true')
plt.show()