前两天收到了论文的拒稿意见,其中一条是“PLSDA的表示错误,应为PLS-DA”,好吧,以后都写PLS-DA!虚心接受专家意见。
由于之前偷懒,都是用PLS toolbox完成相关偏最小二乘法的数据分析工作,借此机会,就把PLS-DA的python实现好好唠唠。查过不少资料中,没有详细说调包sklearn实现的,废话不多说,进入正题。
sklearn中的偏最小二乘函数为PLSRegression(),这是一个回归函数,如果直接拿来做分类,显然得不到想要的结果。调用格式如下:
from sklearn.cross_decomposition import PLSRegression
model = PLSRegression()
解决方法是:把标签矩阵(比如0,1,2,3,4的一个列向量)使用get_dummies()函数转换为类别矩阵,拿我的数据举例:
import numpy as np
from sklearn.cross_decomposition import PLSRegression
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler #归一化
from sklearn.metrics import confusion_matrix,recall_score,classification_report,accuracy_score
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import GridSearchCV
import warnings
from sklearn.model_selection import validation_curve
from sklearn.model_selection import KFold
#读取特征矩阵
spec = pd.read_excel('correct_spec.xlsx')
x = np.array(spec)
#我这里的特征x形状: (939, 150)
#做一个标签向量,标签y形状: (939,)
y1 = np.zeros((189,1))
y2 = 1*np.ones((188,1))
y3 = 2*np.ones((185,1))
y4 = 3*np.ones((188,1))
y5 = 4*np.ones((189,1))
y = np.vstack((y1,y2,y3,y4,y5))
y = y.ravel()
#先做一个数据集的划分
train_X,test_X, train_y, test_y = train_test_split(x, y, test_size=0.2)
#然后对y进行转换
train_y = pd.get_dummies(train_y)
#建模
model = PLSRegression(n_components=8)
model.fit(train_X,train_y)
#预测
y_pred = model.predict(test_X)
#将预测结果(类别矩阵)转换为数值标签
y_pred = np.array([np.argmax(i) for i in y_pred])
#模型评价
print('测试集混淆矩阵为:\n',confusion_matrix(test_y,y_pred))
print('平均分类准确率为:\n',accuracy_score(test_y,y_pred))
接下里的问题就是函数中主成分数n_components的取值,如果直接使用网格搜索函数GridSearchCV()对n_components的值在一个范围内进行搜索,比如(1,20)一般情况下是行不通的,会发生过拟合,直接搜索到指定范围内的最大值。
这时候想到的是使用validation_curve()函数绘制验证曲线,来选择这个超参数,但是依然会报错,原因大致是:这是一个回归函数,我们用它来做分类,y值不连续,所以我就要报错了,叭叭叭叭······ 即使按照上述方法事先转换y的类型,再调用validation_curve依然不行······
所以这里只能自己写一个验证函数用来调参,选取原则就是训练集和验证集的得分最高时候对应的主成分数,且验证集的得分不能高于训练集。
def accuracy_component(xc,xv,yc,yv,component,n_fold):
k_range = np.linspace(1, component,component)
kf=KFold(n_splits=n_fold,random_state=None, shuffle=True)
accuracy_validation=np.zeros((1,component))
accuracy_train=np.zeros((1,component))
for j in range(component):
p=0
acc=0
model_pls=PLSRegression(n_components=j+1)
model_pls.fit(xc,yc_labels)
y_pred = model_pls.predict(xv)
y_pred = np.array([np.argmax(i) for i in y_pred])
accuracy_train[:,j]=accuracy_score(yv,y_pred)
for train_index, test_index in kf.split(xc):
X_train, X_test = xc[train_index], xc[test_index]
y_train, y_test = yc[train_index], yc[test_index]
YC_labels = pd.get_dummies(y_train)
YV_labels=pd.get_dummies(y_test)
model_1=PLSRegression(n_components=j+1)
model_1.fit(X_train,YC_labels)
Y_pred = model_1.predict(X_test)
Y_pred = np.array([np.argmax(i1) for i1 in Y_pred])
acc=accuracy_score(y_test,Y_pred)+acc
p=p+1
accuracy_validation[:,j]=acc/p
print(accuracy_validation)
plt.plot(k_range, accuracy_validation.T, 'o-',label="Training",color="r")
plt.plot(k_range, accuracy_train.T, 'o-',label="Cross-validation",color="b")
plt.xlabel("N components")
plt.ylabel("Score")
plt.legend(loc="best")
plt.rc('font',family='Times New Roman')
plt.rcParams['font.size'] = 10
plt.show()
return accuracy_validation,accuracy_train
得到最佳主成分n_components,重复上述过程,对模型进行训练即可,于是直接调包又香了