如何选择模型?如何评估模型?如何调整模型的超参数?模型评估要在测试集上进行,不能在训练集上进行,否则评估的准确率总是100%,模型调参要在验证集上迭代进行。所以,一般我们准备好数据集后,要将其分为训练集和测试集,分配比例一般在5:5到8:2之间,即最多训练集80%,测试集20%,验证集包含在训练集中。sklearn中有sklearn.model_selection.train_test_split方法来实现数据集的拆分。
一、如何选择一个合适的模型呢?
模型的选择需要考虑很多因素,比如模型的灵活度或复杂度(即模型支持的超参数),并不是越多越好,主要考虑的核心点在于模型的偏差和方差之间的平衡点,偏差相当于模型预测的准确度,方差相当于模型在整个测试数据预测的稳定性或鲁棒性,一般来说,欠拟合就是训练集过少,导致预测准确度差(即偏差大,波动或方差小),过拟合就是训练集数据过多,过度学习了训练集的所有波动数据,导致预测准确度好(即偏差小,波动或方差大),两种情况都不好,两者的平衡点是最好的。
二、最常用的四种模型评估方法
1.一般的评估验证:将数据集拆分为训练集和测试集,在训练集上完成训练,形成模型,在测试集上进行预测, 进而评估模型的准确率,这样会带来两个问题,问题1由于拆分比例的没有一个标准方法,导致模型有可能欠拟合或过拟合,问题2是由于测试集数据的随机选择,有可能正好选择了一组适合该模型的预测数据,导致准确率偏高(好),或者出现相反情况。示例如下:
#加载花萼数据
from sklearn.datasets import load_iris
iris = load_iris()
import numpy as np
X = iris.data.astype(np.float32)
y = iris.target
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=37,train_size=0.8) #8:2拆分,随机
knn = cv2.ml.KNearest_create()
knn.setDefaultK(1) #设置K近邻的数量为1
knn.train(X_train, cv2.ml.ROW_SAMPLE, y_train)
_, y_test_hat = knn.predict(X_test)
accuracy_score(y_test, y_test_hat) #96.7%
2.K折交叉验证:相当于将N个数据集分为K折,每组相当于N/K个数据,后续将K-1个数据集用于训练了,另一个用于测试,优点是更高效的使用数据,通过多次迭代循环,得到更高的准确率。示例如下:
#加载花萼数据
from sklearn.datasets import load_iris
import numpy as np
iris = load_iris()
X = iris.data.astype(np.float32)
y = iris.target
from sklearn.model_selection import train_test_split
#将数据分为两等分,各50%,相当于二折
X_fold1, X_fold2, y_fold1, y_fold2 = train_test_split(X, y, random_state=37, train_size=0.5)
#opencv方式
import cv2
knn = cv2.ml.KNearest_create()
knn.setDefaultK(1) #K=1
knn.train(X_fold1, cv2.ml.ROW_SAMPLE, y_fold1) #第一折训练
_, y_hat_fold2 = knn.predict(X_fold2) #第一折预测
knn.train(X_fold2, cv2.ml.ROW_SAMPLE, y_fold2) #第二折训练
_, y_hat_fold1 = knn.predict(X_fold1) #第二折预测
from sklearn.metrics import accuracy_score
accuracy_score(y_fold1, y_hat_fold1) #第一折评估
accuracy_score(y_fold2, y_hat_fold2) #第二折评估
#sklearn方式
from sklearn.neighbors import KNeighborsClassifier
model = KNeighborsClassifier(n_neighbors=1)
from sklearn.model_selection import cross_val_score
scores = cross_val_score(model, X, y, cv=5) #cv指定折数,这里是5折,无需手动分割数据集,cross_val_score会根据折数自动分割。
scores.mean(), scores.std() #评估的平均值和标准差
#留一法交叉验证,这是交叉验证的一种特殊方法,相当于将K=N,在K或N次迭代中,留一个数据点进行测试验证评估,具体实现如下
from sklearn.model_selection import LeaveOneOut
scores = cross_val_score(model, X, y, cv=LeaveOneOut())
scores.mean(), scores.std()
3.自举验证:用于评估模型的鲁棒性,示例如下:
#加载花萼数据
from sklearn.datasets import load_iris
iris = load_iris()
import numpy as np
X = iris.data.astype(np.float32)
y = iris.target
idx_boot = np.random.choice(len(X), size=len(X), replace=True) #随机以替换的方式选择N个样本
X_boot = X[idx_boot, :]
y_boot = y[idx_boot]
idx_oob = np.array([x not in idx_boot for x in np.arange(len(X))], dtype=np.bool)
X_oob = X[idx_oob, :]
y_oob = y[idx_oob]
knn = cv2.ml.KNearest_create()
knn.setDefaultK(1)
knn.train(X_boot, cv2.ml.ROW_SAMPLE, y_boot)
_, y_hat = knn.predict(X_oob)
accuracy_score(y_oob, y_hat)
acc=list(yield_bootstrap(knn, X, y, n_iter=10))
print(acc)
acc = list(yield_bootstrap(knn, X, y, n_iter=1000))#迭代1000次
np.mean(acc), np.std(acc)
#迭代调用n_iter次的函数,实现模型训练预测和准确率评估
def yield_bootstrap(model, X, y, n_iter=10000):
for _ in range(n_iter):
# train the classifier on bootstrap
idx_boot = np.random.choice(len(X), size=len(X),replace=True)
X_boot = X[idx_boot, :]
y_boot = y[idx_boot]
model.train(X_boot, cv2.ml.ROW_SAMPLE, y_boot)
# test classifier on out-of-bag examples
idx_oob = np.array([x not in idx_boot for x in np.arange(len(X))],dtype=np.bool)
X_oob = X[idx_oob, :]
y_oob = y[idx_oob]
_, y_hat = model.predict(X_oob)
# return accuracy
yield accuracy_score(y_oob, y_hat)
4.T检验
T检验测试确定两个数据样本是否来自于相同的平均值或期望值的潜在分布。示例如下:
#加载花萼数据
from sklearn.datasets import load_iris
iris = load_iris()
import numpy as np
X = iris.data.astype(np.float32)
y = iris.target
k1 = KNeighborsClassifier(n_neighbors=1)
scores_k1 = cross_val_score(k1, X, y, cv=10)
np.mean(scores_k1), np.std(scores_k1)
k3 = KNeighborsClassifier(n_neighbors=3)
scores_k3 = cross_val_score(k3, X, y, cv=10)
np.mean(scores_k3), np.std(scores_k3)
from scipy.stats import ttest_ind
ttest_ind(scores_k1, scores_k3)
三、模型的超参数选择及调优
模型的参数调优一般采用网格搜索,网格搜索其实就是多个for循环嵌套调整,一般一个参数采用一个for循环,示例如下:
#加载花萼数据
from sklearn.datasets import load_iris
import numpy as np
iris = load_iris()
X = iris.data.astype(np.float32)
y = iris.target
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=37)
best_acc = 0
best_k = 0
import cv2
from sklearn.metrics import accuracy_score
for k in range(1, 20):
knn = cv2.ml.KNearest_create()
knn.setDefaultK(k)
knn.train(X_train, cv2.ml.ROW_SAMPLE, y_train)
_, y_test_hat = knn.predict(X_test)
acc = accuracy_score(y_test, y_test_hat)
if acc > best_acc:
best_acc = acc
best_k = k
print(best_acc, best_k)
#训练集、验证集和测试集的拆分:在网格搜索过程中,如果将数据集还分为训练集和测试集,利用测试集来评估模型并更新超参数,就会出现将测试集信息暴漏给模型,导致评估不准确,因此需要将数据集拆分为训练集、验证集和测试集,训练集用于训练数据,验证集用于选择模型的最佳参数,测试集用于评估模型。
X_trainval, X_test, y_trainval, y_test = train_test_split(X, y, random_state=37) #数据集分为训练验证集和测试集
X_train, X_valid, y_train, y_valid = train_test_split(X_trainval, y_trainval, random_state=37)#训练验证集进一步分为训练集和验证集
best_acc = 0.0
best_k = 0
for k in range(1, 20):
knn = cv2.ml.KNearest_create()
knn.setDefaultK(k)
knn.train(X_train, cv2.ml.ROW_SAMPLE, y_train) #在训练集训练模型
_, y_valid_hat = knn.predict(X_valid) #在验证集上预测数据
acc = accuracy_score(y_valid, y_valid_hat) #根据验证集预测数据情况评估准确率,不断更新最佳超参数
if acc >= best_acc:
best_acc = acc
best_k = k
print(best_acc, best_k) #1.0,7
knn = cv2.ml.KNearest_create()
knn.setDefaultK(best_k) #best_k=7,是前面迭代得到的最近k值
knn.train(X_trainval, cv2.ml.ROW_SAMPLE, y_trainval) #在训练验证集上重新训练模型
_, y_test_hat = knn.predict(X_test)
print(accuracy_score(y_test, y_test_hat))
print(best_k)
#网格搜索结合交叉验证实现超参数调优,利用sklearn提供的GridSearchCV类,实现在网格搜索中加入交叉验证机制
param_grid = {'n_neighbors': range(1, 20)} #搜索n_neighbors的最佳参数,范围1~19,多个其他参数也用类似方式设置
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import GridSearchCV
grid_search = GridSearchCV(KNeighborsClassifier(), param_grid, cv=5)
grid_search.fit(X_trainval, y_trainval) #在训练集训练模型
print(grid_search.best_score_, grid_search.best_params_) #获得最佳的验证得分和最佳超参数k值
print(grid_search.score(X_test, y_test)) #测试集上的评估
四、利用管道Pipeline实现机器学习各步骤的连接
机器学习算法需要很多步骤,比如数据预处理、训练、预测、评估等步骤,而sklearn的Pipeline类本身就有fit、predict和score方法,因此他可以将分类器不同模型和处理步骤连接起来,相当于一个管道。示例如下:
#加载花萼数据
from sklearn.datasets import load_breast_cancer
import numpy as np
cancer = load_breast_cancer()
X = cancer.data.astype(np.float32)
y = cancer.target
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=37)
#采用SVM
from sklearn.svm import SVC
svm = SVC()
#svm.fit(X_train, y_train)
#svm.score(X_test, y_test)
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import MinMaxScaler
pipe = Pipeline([("scaler", MinMaxScaler()), ("svm", SVC())])
pipe.fit(X_train, y_train)
pipe.score(X_test, y_test)
#结合sklearn的网格搜索
param_grid = {'svm__C': [0.001, 0.01, 0.1, 1, 10, 100],
'svm__gamma': [0.001, 0.01, 0.1, 1, 10, 100]}
from sklearn.model_selection import GridSearchCV
grid = GridSearchCV(pipe, param_grid=param_grid, cv=10)
grid.fit(X_train, y_train)
print(grid.best_score_)
print(grid.best_params_)
print(grid.score(X_test, y_test))
五、模型的评估指标主要有那些
1.分类模型
1)准确率:就是前面各种示例关注的评估指标,测试集中预测正确的数据占比。
2)精确率:表示模型未将正样本预测为负的能力。
3)召回率:表示模型预测所有正样本的能力,还有一个F值=2*(精确率*召回率)/(精确率+召回率),相当于两者的调和均值。
2.回归模型
1)均方误差:预测值与真实值之间的平方误差,也是回归算法中经常用到的指标。
2)可解释方差:预测的方差或离散度。
3)R2值:无偏方差估计,详见sklearn.metrics.r2_score类,也是回归算法中经常用到的指标。