基于PCA降维的模式识别系统的设计与实现
1.1 主要研究内容
(1)工作的主要描述
本次作业的主要目的是结合课内课外所学知识设计一个简单的模式识别系统对电离层公开数据进行分类、通过主成分分析(PCA)特征提取方法探索降维对分类性能的影响并学习一些常见分类器的基本原理及程序实现。
(2)系统流程图
系统流程图如图1所示,为方便理解,在此对两个循环进行解释:内层循环是为了探索在使用相同训练集和测试集的情况下不同程度的降维对不同分类器分类性能的影响;外层循环是为了使结果更具普遍性,使用不同的训练集和测试集进行重复实验,最后图像展示的结果是每次外层循环所记录结果的平均值。
1.2 工作基础或实验条件
(1)硬件环境
主机:CPU:Intel(R) Core(TM) i5-6300HQ 2.30GHz
内存:8G
操作系统:Windows10
(2)软件环境
编程平台:Visual Studio 2017
编程语言:Python3.6
1.3 数据集描述
电离层数据集实际上起源于1989年,该数据集包含由拉布拉多鹅湾的雷达系统收集的数据,该系统由16个高频天线的相控阵列组成,旨在检测电离层中的自由电子。一般来说,电离层有两种类型的结构:“好”和“坏”,雷达会检测并传递这些信号结构。数据集中有34个自变量(特征值)和1个因变量(目标值),总共有351个观测值,其中无缺失值。
1.4 特征提取过程描述
通过正交变换将一组可能存在相关性的变量转换为一组线性不相关的变量,转换后的这组变量叫主成分。求得降维后的特征值的根本在于原始特征空间的重构,它有两个出发点:一个是最大投影方差、另一个是最小重构距离,两种方法的基本原理是相似的。以下使PCA降维的步骤:
(1) 将原始数据进行标准化(一般是去均值,如果特征在不同的数量级上,则还要将其除以标准差);
(2) 计算标准化数据集的协方差矩阵;
(3) 计算协方差矩阵的特征值;
(4) 保留最重要(特征值最大)的前k个特征(k表示降维后的维度);
(5) 计算这k个特征值对应的特征向量;
(6) 将标准化数据集乘以该k个特征向量,得到降维后的结果。
1.5 分类过程描述
(1) K均值
K均值聚类算法(k-means clustering algorithm)是一种迭代求解的聚类分析算法,其步骤是,预将数据分为k组,则随机选取k个样本作为初始的聚类中心,然后计算每个样本与各个初始聚类中心之间的距离,把每个样本分配给距离它最近的聚类中心。所有样本分配完后,聚类的聚类中心会根据聚类中现有的样本被重新计算,直到聚类中心不再变化。
(2) 最小风险贝叶斯
最小风险贝叶斯算法将样本划分到后验概率大的那一类中去。后验概率=(先验概率×条件概率)/全概率,由于在判断的时候全概率的值是相同的所以不需计算,只需计算先验概率和条件概率。
(3) 决策树
决策树分类算法是一种基于实例的归纳学习方法,它能从给定的无序的训练样本中,提炼出树型的分类模型。树中的每个非叶子节点记录了使用哪个特征来进行类别的判断,每个叶子节点则代表了最后判断的类别。根节点到每个叶子节点均形成一条分类的路径规则。而对新的样本进行测试时,只需要从根节点开始,在每个分支节点进行测试,沿着相应的分支递归地进入子树再测试,一直到达叶子节点,该叶子节点所代表的类别即是当前测试样本的预测类别。
(4) KNN
KNN可以用于分类和回归,是一种监督学习算法。它的思路是如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。也就是说,该方法在分类决策上只依据最邻近的一个或者几个样本的类别来决定待分样本所属的类别。
(5) 逻辑回归
逻辑回归就是在多元线性回归的基础之上把结果缩放到0-1之间,缩放使用的函数是Sigmoid函数,经过Sigmoid缩放后的结果以0.5作为分界线,越接近于正1越是正例,越接近于0越是负例。
1.6 主要程序代码
(1) PCA特征提取代码:
def pca(data, n):
mean = np.mean(data, axis=0)
meanX = data - mean # 标准化(去均值)
cov = np.cov(meanX, rowvar=False)
eig, eig1 = np.linalg.eig(np.mat(cov)) # 计算矩阵的特征值和特征向量
eigindex = np.argsort(eig) # 将特征值从小到大排序,返回的是特征值对应的数组里的下标
eigindex = eigindex[:-(n + 1):-1] # 保留最大的前K个特征值
eigVects = eig1[:, eigindex] # 对应的特征向量
Date = meanX * eigVects # 将数据转换到低维新空间
return Date
(2) K均值
def k_means_score(c, data, max, label,y_test): #调用自定义的K均值算法
t0 = time.time()
classifier=k_means(c, data, max, label,y_test)
'''for i in range(len(classifier)):
if(classifier[i]==0):
classifier[i]==True'''
score=sum(classifier==y_test)/len(y_test)
t1 = time.time()
print('k均值准确率:',score)
kmeans.append(score)
kmeans_time.append(t1-t0)
def k_means(c, data, max,label,y_test):
max = max - 1
num = len(data)
metrix = [[eucliDist(a, b) for a in data] for b in c] #计算欧式距离
#print('metrix',metrix)
classifier = []
for (d, e) in zip(metrix[0], metrix[1]): # 比较矩阵同一列的数值大小,将对应的样本归为距离更近的聚类中心所在类别
m = min(d, e)
if d == m:
classifier.append(label[0])
elif e == m:
classifier.append(label[1])
#print(classifier)
n1, n2 = 0, 0
c1 = [0]*int(len(c[0]))
c2 = c1
for i in range(0, num): # 重新计算聚类中心
if classifier[i] == label[0]:
c1 = [a + b for (a, b) in zip(c1, data[i])]
n1 = n1 + 1
elif classifier[i] == label[1]:
c2 = [a + b for (a, b) in zip(c2, data[i])]
n2 = n2 + 1
c1 = [a / n1 for a in c1]
c2 = [a / n2 for a in c2]
#print(max)
#print([c1,c2])
# 迭代,直至聚类中心不发生变化
if ((c[0] is not c1) and (c[1] is not c2) and max > 0):
c = [c1, c2]
#print('c',c)
k_means(c, data, max, label,y_test
return classifier
(3) 最小错误率贝叶斯
def fitB(train_data, train_target):#计算先验概率
train_target_list = [] # 目标值类别集合[0,1]
p_train_target = {} # 保存个目标值概率
split_data_lis = []
train_length = train_data.shape[0]
length = train_length
target_list = list(set(train_target)) # 对训练集目标值去重
train_target_list = target_list # 写入对象特征
#print('类别',train_target_list)
#print(len(train_target_list))
target_classifier = dict(Counter(train_target)) # 保存目标值的分类计数(字典格式)
#print('target_classifier',target_classifier)
train_data = pd.DataFrame(train_data)
train_data['target'] = train_target # 将数据转换为DataFrame格式方便后续聚合
#print(train_data)
for target in train_target_list:
p_train_target[target] = target_classifier[target]/length # 先验概率
split_data = train_data[train_data['target'] == target]
#print('split_data',split_data)#按类别分3类
split_data_lis.append(split_data)
#print('split_data_lis',split_data_lis)
return train_target_list,p_train_target,split_data_lis
def M(target,train_target_list,p_train_target,split_data_lis):#计算均值向量
sum=split_data_lis[target].sum(axis=0)
avg_sum=sum/split_data_lis[target].shape[0
m=np.array(avg_sum[0:-1])
#print('m',m)
return m
def C2(target,train_target_list,p_train_target,split_data_lis): #利用现有函数求取协方差矩阵
m=M(target,train_target_list,p_train_target,split_data_lis)
x=split_data_lis[target].drop('target', axis=1).values
temp_c=np.cov(x.T)
#print(m)
#print('temp_c',temp_c)
return temp_c,m
def C(target): #自定义方法求取协方差矩阵
m=M(target)
temp_c=np.zeros((4,4))
for i in range(split_data_lis[target].shape[0]):
x=split_data_lis[target][i:i+1].values
x=x[0][0:4]
#print('x',x)
temp=x-m
final=[]
for i in range(len(temp)):
final.append([temp[i]])
c=np.dot(final,[temp])
#print('c',c)
temp_c=temp_c+c
temp_c=temp_c/(split_data_lis[target].shape[0]-1)
#print('temp_c',temp_c)
return temp_c,m
def mc(train_target_list,p_train_target,split_data_lis):调用求取均值向量和协方差矩阵的函数
#self.split_data_lis 数据
#self.p_train_target 先验概率
#print('p_train_target',p_train_target)
m=[]
c=[]
#print('len(train_target_list)',len(train_target_list))
#print(train_target_list)
for i in range(len(train_target_list)):
c_temp,m_temp=C2(i,train_target_list,p_train_target,split_data_lis)
m.append(m_temp)
c.append(c_temp)
#print(c_temp)
#print('m',m)
#print(c[1][0])
#print('c',c[0][0])
#print(len(c),len(c[0][0]))
return m,c
def minrisk(x_test,y_test,train_target_list,p_train_target,split_data_lis): #求条件概率及最终判别式
t0 = time.time()
m,c=mc(train_target_list,p_train_target,split_data_lis)
pmax=[]
#print('len(train_target_list)',len(train_target_list))
#print(train_target_list)
for j in range(len(x_test)):#样本个数
p=[]
x=x_test[j]
for i in range(len(train_target_list)):#
n=len(x_test[0])
'''a=np.dot((x-m[i]),np.linalg.inv(c[i]))
print('1111111',a)
b=np.dot(a,((x-m[i]).T))
print('b',b)'''
#print('n',n)
temp_left=1/((float(2*math.pi)**(n/2))*(float(np.linalg.det(c[i])**0.5)))
#print('temp_left',temp_left)
try:
temp_right=float(math.exp(-0.5*(np.dot(np.dot((x-m[i]),np.linalg.inv(c[i])),(x-m[i]).T))))
except OverflowError:
temp_right=0
temp=temp_left*temp_right*p_train_target[i]
#print('p_train_target',self.p_train_target)
#print('temp',temp)
p.append(temp)
pmax.append(p.index(max(p)))
#print(pmax)
#print(pmax==y_test)
t1 = time.time()
accurance=sum(pmax==y_test)/len(y_test)
byes.append(accurance)
byes_time.append(t1-t0)
print('最小风险贝叶斯准确率',accurance)
(4) KNN
def KNN(x_train,x_test,y_train,y_test):
knn_clf = KNeighborsClassifier(n_neighbors=4)
t0 = time.time()
knn_clf.fit(x_train, y_train)
score = knn_clf.score(x_test, y_test)
t1 = time.time()
print('knn准确率:', score)
knn.append(score)
knn_time.append(t1-t0)
(5) 决策树
def decisiontree(x_train,x_test,y_train,y_test):
# 4、预估器
estimator = DecisionTreeRegressor()
t0 = time.time()
estimator.fit(x_train, y_train)
# 5、模型评估
y_predict = estimator.predict(x_test)
t1 = time.time()
#print("预测:\n", y_predict)
score = estimator.score(x_test, y_test)
print('决策树准确率:', score)
det.append(score)
det_time.append(t1-t0)
(6) 逻辑回归
def Logistic(x_train,x_test,y_train,y_test):
log_clf = LogisticRegression()
t0 = time.time()
log_clf.fit(x_train, y_train)
score = log_clf.score(x_test, y_test)
t1 = time.time()
print('逻辑回归准确率:', score)
logistic.append(score)
logistic_time.append(t1-t0)
1.7 运行结果及分析
(1) 准确率
各分类模型在不同维度准确率的可视化结果如图2所示(横坐标为特征维度、纵坐标为分类准确率)。
图2 各分类模型在不同维度准确率的可视化结果图
从不同分类器的分类能力上看,最小错误率贝叶斯(粉线)、KNN(黑线)以及逻辑回归(黄线)分类准确率较高;K均值(蓝线)和决策树(绿线)分类准确率较低。
从特征维数对分类器的影响上看,逻辑回归(黄线)的分类准确率在一开始随特征维数的增加而上升随后便趋于平衡;K均值(蓝线)受特征维数影响较小;最小错误率贝叶斯(粉线)、KNN(黑线)以及决策树(绿线)的分类准确率在一开始随特征维数的增加而上升随后便趋于平衡接着还有小幅的下降。
(2) 分类时间
各分类算法在不同维度分类所花时间的可视化结果如图3所示(横坐标为特征维度、纵坐标为分类所花时间)。
图3 各分类算法在不同维度分类所花时间的可视化结果图
由于该数据集的样本数量并不多,而且逻辑回归(黄线)、KNN(黑线)以及决策树(绿线)这三种分类器使用的是Python机器学习库中的函数,算法本身经过优化所以在分类时间上看不出随维度的增加有何明显的变化;最小错误率贝叶斯(粉线)和K均值(蓝线)由于是自定义的函数,未进行优化,计算量随着维数的上升增加地较为明显,因此从图中可以较明显地看出这两种分类器分类所花时间随维度的增加而增加)。
综上,通过对电离层数据集在不同分类模型和不同维度的分类性能发现:适当的降维并不会使准确率大幅下降甚至可以起到防止过拟合的作用并且可以降低不适合高维度算法的计算量,减少时间成本。