人工智能-模型调优:K-折交叉验证、网格搜索(可并行计算)、启发式搜索(随机搜索,遗传算法,贝叶斯优化)

人工智能-机器学习:模型调优【交叉验证、网格搜索(可并行计算)、启发式搜索(随机搜索,遗传算法,贝叶斯优化)】、模型评估【准确率、精确率、召回率、F1-Measure、ROC/AUC】、AIC、BIC

模型调优

一、交叉验证

交叉验证是一种通过估计模型的泛化误差,从而进行模型选择的方法。没有任何假定前提,具有应用的普遍性,操作简便, 是一种行之有效的模型选择方法。

1、交叉验证的产生

人们发现用同一数据集,既进行训练,又进行模型误差估计,对误差估计的很不准确,这就是所说的模型误差估计的乐观性。为了克服这个问题,提出了交叉验证。基本思想是将数据分为两部分,一部分数据用来模型的训练,称为训练集;另外一部分用于测试模型的误差,称为验证集。由于两部分数据不同,估计得到的泛化误差更接近真实的模型表现。数据量足够的情况下,可以很好的估计真实的泛化误差。但是实际中,往往只有有限的数据可用,需要对数据进行重用,从而对数据进行多次切分,得到好的估计。

2、交叉验证方法

  1. 留一交叉验证(leave-one-out):每次从个数为N的样本集中,取出一个样本作为验证集,剩下的N-1个作为训练集,重复进行N次。最后平均N个结果作为泛化误差估计。
  2. 留P交叉验证(leave-P-out):与留一类似,但是每次留P个样本。每次从个数为N的样本集中,取出P个样本作为验证集,剩下的N-P个作为训练集,重复进行CPNCNP次。最后平均N个结果作为泛化误差估计。
    以上两种方法基于数据完全切分,重复次数多,计算量大。因此提出几种基于数据部分切分的方法减轻计算负担。
  3. K-折交叉验证:把数据分成K份,每次拿出一份作为验证集,剩下k-1份作为训练集,重复K次。最后平均K次的结果,作为误差评估的结果。与前两种方法对比,只需要计算k次,大大减小算法复杂度,被广泛应用。
    • 将原始数据分为训练数据测试数据,训练数据是交给工程师用于生成模型用的。
    • 测试数据一般是客户用来检查模型质量的数据,一般不会给工程师
    • 工程师手里的训练数据,分为训练集和验证集。以下图为例:将数据分成10份(一般分为10份),其中一份作为验证集。然后经过10次(组)的测试,每次都更换不同的验证集。即得到10组模型的结果,取平均值作为最终结果。又称 十折交叉验证
      在这里插入图片描述
      交叉验证目的:为了让被评估的模型更加准确可信。
      在这里插入图片描述

3、模型选择方法的评价

衡量一个模型评估方法的好坏,往往从偏差和方差两方面进行。

3.1 偏差

交叉验证只用了一部分数据用于模型训练,相对于足够多的数据进行训练的方法来说,模型训练的不充分,导致误差估计产生偏差。

  • 相对来说,留一交叉验证,每次只留下一个作为验证集,其余数据进行训练,产生泛化误差估计结果相对真值偏差较小。很多文献表明留一交叉验证在回归下的泛化误差估计是渐进无偏的。
  • 留P交叉验证,取决于P的大小,P较小时,等同于留一交叉验证的情况。P较大,会产生较大的偏差,不可忽略。
  • K折交叉验证,同样取决于K的大小。K较大时,类似留一交叉验证;K较小时,会产生不可忽略的偏差。

训练数据越小,偏差越大。当偏差无法忽略时,需要对偏差进行纠正。

3.2 方差

  • 对于一个模型,训练数据固定后,不同的验证集得到的泛化误差评估结果的波动,称之为误差评估的方差。
  • 影响方差变化的因素,主要有数据的切分方法,模型的稳定性等。
  • 训练数据固定的情况下,验证集中样本数量越多,方差越小。
  • 模型的稳定性是指模型对于数据微小变化的敏感程度。

4、针对K-折交叉验证的 k k k 的选择,及偏差和方差分析

  • 对于 k k k 的选择,实践中一般取 k = 10 k =10 k=10
  • k = N k = N k=N时,(N为训练样本数量), k k k 折交叉验证退化为留一交叉验证(leave-one-out cross validation)。由于在留一交叉验证中,每一次训练模型的样本几乎是一样的,这样就会造成估计的偏差很小但方差很大的情况出现,另外,需要调用 N N N 次学习算法,这在 N N N 很大的时候,对于计算量也是不小的开销。
  • 如果取 k = 10 k = 10 k=10,那么交叉验证的方差会降低,但是偏差又相对于 k = N k = N k=N 时增大,这取决于训练样本的数量。当训练样本较小时,交叉验证很容易有较高的偏差,但是随着训练样本的增加,这种情况会得到改善。

问题:这个只是让被评估的模型更加准确可信,那么怎么选择或者调优参数呢?使用网格搜索。

5、单一“超参数”调优:cross_val_score

import numpy as np
from sklearn.neighbors import KNeighborsClassifier
from sklearn import datasets
import matplotlib.pyplot as plt
from sklearn.model_selection import cross_val_score

# model_selection:模型选择
# cross_val_score  cross:交叉,validation:验证(测试)
# 交叉验证
if __name__ == "__main__":
    # 一、数据加载
    X, y = datasets.load_iris(True)
    print('X.shape = ', X.shape)
    # cross_val_score交叉验证删选最合适参数
    # 二、交叉验证
    # 2.1 实例化k-近邻估计器
    knn = KNeighborsClassifier()
    # 2.1 实例化k-近邻估计器
    # 2.2 应用cross_val_score筛选最合适的邻居数量
    erros = []
    for k in range(1, 14):
        knn = KNeighborsClassifier(n_neighbors=k)
        score = cross_val_score(knn, X, y, scoring='accuracy', cv=6).mean()
        #   误差越小,说明k选择越合适,越好
        erros.append(1 - score)
    # 2.3 画图
    plt.plot(np.arange(1, 14), erros)   # 从图中可看出:k = 11时,误差最小,说明k = 11对鸢尾花来说,最合适的k值

    # 2.4 参数调节:多参数组合使用cross_val_score筛选最合适的参数组合
    result = {}
    weights = ['uniform', 'distance']
    for k in range(1, 14):
        for w in weights:
            knn = KNeighborsClassifier(n_neighbors=k, weights=w)
            sm = cross_val_score(knn, X, y, scoring='accuracy', cv=6).mean()
            result[w + str(k)] = sm
    print('result = ', result)
    max_value_index = np.array(list(result.values())).argmax()
    print('max_value_index = ', max_value_index)
    result_list = list(result)
    print('result_list = ', result_list)
    print('result_list[max_value_index] = ', result_list[max_value_index])

在这里插入图片描述
打印结果:

X.shape =  (150, 4)
result =  {
'uniform1': 0.9591049382716049,
'distance1': 0.9591049382716049,
'uniform2': 0.9390432098765431,
'distance2': 0.9591049382716049,
'uniform3': 0.9660493827160493,
'distance3': 0.9660493827160493,
'uniform4': 0.9660493827160493,
'distance4': 0.9660493827160493,
'uniform5': 0.9660493827160493,
'distance5': 0.9660493827160493,
'uniform6': 0.9729938271604938,
'distance6': 0.9729938271604938,
'uniform7': 0.9729938271604938,
'distance7': 0.9729938271604938,
'uniform8': 0.9591049382716049,
'distance8': 0.9729938271604938,
'uniform9': 0.9660493827160493,
'distance9': 0.9729938271604938,
'uniform10': 0.9729938271604938,
'distance10': 0.9729938271604938,
'uniform11': 0.98070987654321,
'distance11': 0.9799382716049383,
'uniform12': 0.9737654320987654,
'distance12': 0.9799382716049383,
'uniform13': 0.9737654320987654,
'distance13': 0.9729938271604938
}
max_value_index =  20
result_list =  [
'uniform1',
'distance1',
'uniform2',
'distance2',
'uniform3',
'distance3',
'uniform4',
'distance4',
'uniform5',
'distance5',
'uniform6',
'distance6',
'uniform7',
'distance7',
'uniform8',
'distance8',
'uniform9',
'distance9',
'uniform10',
'distance10',
'uniform11',
'distance11',
'uniform12',
'distance12',
'uniform13',
'distance13'

]
result_list[max_value_index] =  uniform11

二、多“超参数”组合调优:网格搜索

通常情况下,有很多参数是需要手动指定的(如k-近邻算法中的K值),这种叫超参数。但是手动过程繁杂,所以需要对模型预设几种超参数组合。每组超参数都采用交叉验证来进行评估。最后选出最优参数组合建立模型。
对估计器的指定参数值进行详尽搜索(排列组合,然后找出使得模型效果最好的最优参数组合)

在这里插入图片描述
比如,模型需要超参数(a,b)。给这参数a指定2个待评估参数3,7,给参数b指定3个待评估参数20,36,72,那么模型会对(3,20)、(3,36)、(3,72)、(7,20)、(7,36)、(7,72)这几组参数分别评估,然后通过准确率选出一个最优的参数组合来建立模型。

在这里插入图片描述
超参数调优-网格搜索Api:sklearn.model_selection.GridSearchCV(estimator, param_grid=None,cv=None)

  • estimator:估计器对象
  • param_grid:估计器参数(dict){“n_neighbors”:[1,3,5]}
  • cv:指定几折交叉验证
  • fit:输入训练数据
  • score:准确率

结果分析:

  • best_score_:在交叉验证中测试的最好结果
  • best_estimator_:最好的参数模型
  • cv_results_:每次交叉验证后的验证集准确率结果训练集准确率结果
import pandas as pd
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier


def knncls():
    """K-近邻预测用户签到位置"""
    # 1、读取数据(pandas)
    myDataFrame = pd.read_csv("I:/AI_Data/facebook-v-predicting-check-ins/train.csv")
    print('\nmyDataFrame.head(5) = \n', myDataFrame.head(5))

    # 2、处理数据(pandas)
    # 2.1 缩小数据,查询数据晒讯
    myDataFrame = myDataFrame.sort_values(by='row_id').query("x > 1.0 &  x < 1.25 & y > 2.5 & y < 2.75")
    print('\nmyDataFrame.count() = \n', myDataFrame.count())
    # 处理时间的数据:将时间戳转换为Series类型(格式为:index=时间戳, value=yyyy-mm-dd hh:mm:ss)
    dateTimeSeries = pd.to_datetime(myDataFrame['time'], unit='s')
    print('\ntype(dateTimeSeries) = ', type(dateTimeSeries))
    print('\ndateTimeSeries.head(5) = \n', dateTimeSeries.head(5))
    # 2.2 把dateTimeSeries转换成DatetimeIndex索引(字典类型)
    dateTimeIndexMap = pd.DatetimeIndex(dateTimeSeries)
    print('\ntype(dateTimeIndexMap) = ', type(dateTimeIndexMap))
    print('\ndateTimeIndexMap = \n', dateTimeIndexMap)
    # 2.3 构造一些特征(添加某些特征有可能会使预测准确度增加或减小)
    # myDataFrame['year'] = dateTimeIndexMap.year   # 年份没有意义,因为以后的预测不可能重复此年份
    myDataFrame['month'] = dateTimeIndexMap.month
    myDataFrame['day'] = dateTimeIndexMap.day
    myDataFrame['hour'] = dateTimeIndexMap.hour
    # myDataFrame['minute'] = dateTimeIndexMap.minute
    # myDataFrame['weekday'] = dateTimeIndexMap.weekday
    # 2.4 把时间戳特征删除
    myDataFrame = myDataFrame.drop(['time'], axis=1)
    print('\nmyDataFrame.head(5) = \n', myDataFrame.head(5))
    # 2.5 把签到数量少于n个目标位置删除
    # 分组然后统计每组数量,分组后place_id变为索引,其余特征的特征值变为当前分组下的成员数量
    placeCountDataFrame = myDataFrame.groupby('place_id').count()
    print('\ntype(placeCountDataFrame) = \n', type(placeCountDataFrame))
    print('\nplaceCountDataFrame = \n', placeCountDataFrame.head(5))
    # 选取组成员数量大于3的分组,然后通过reset_index将原来的索引place_id变为一列可以被引用的特征place_id,新索引变为0,1,2,3...
    placeCountDataFrame = placeCountDataFrame[placeCountDataFrame.row_id > 3].reset_index()  # 选择每组数量大于3的样本
    print('\nplaceCountDataFrame = \n', placeCountDataFrame.head(5))
    # 根据placeCountDataFrame里的place_id筛选myDataFrame中符合条件的样本
    myDataFrame = myDataFrame[myDataFrame['place_id'].isin(placeCountDataFrame.place_id)]
    print('\nmyDataFrame.head(5) = \n', myDataFrame.head(5))
    # 2.6 将数据当中的特征值和目标值分开
    y_Series = myDataFrame['place_id']  # 目标值
    x_DataFrame = myDataFrame.drop(['place_id'], axis=1)  # 特征值
    # 2.7 删除特征值里没有用的特征,来提高准确率
    x_DataFrame = x_DataFrame.drop(['row_id'], axis=1)
    print('\ny_Series = \n', type(y_Series), '\n', y_Series.head(5))
    print('\nx_DataFrame = \n', type(x_DataFrame), '\n', x_DataFrame.head(5))

    # 3、特征工程(scikit-learn)
    # 3.1 特征预处理(特征数据值的标准化,目标值不需要标准化),避免某一特征对最终结果造成比其他特征更大的影响,从而提高准确率。
    std = StandardScaler()
    x_DataFrame = std.fit_transform(x_DataFrame)
    print('\n标准化后的x_DataFrame:\n', x_DataFrame)
    # 3.2 进行数据的分割:训练集、测试集
    x_train_DataFrame, x_test_DataFrame, y_train_Series, y_test_Series = train_test_split(x_DataFrame, y_Series, test_size=0.25)
    print('\n特征数据值of训练集 x_train_DataFrame:\n', x_train_DataFrame)
    print('\n特征数据值of测试集 x_test_DataFrame:\n', x_test_DataFrame)
    print('\n目标值of训练集 y_train_Series:\n', y_train_Series.head(5))
    print('\n目标值of测试集 y_test_Series:\n', y_test_Series.head(5))

    # 4 算法工程
    # 4.1 实例化一个k-紧邻估计器对象
    knn_estimator = KNeighborsClassifier()  # 默认参数:n_neighbors=5
    # # 4.2 调用fit方法,进行训练
    # knn_estimator.fit(x_train_DataFrame, y_train_Series)
    # # 5 模型评估
    # # 5.1 数据预测,得出预测结果
    # predictTestSeries = knn_estimator.predict(x_test_DataFrame)
    # # 5.2 计算准确率
    # predictScore = knn_estimator.score(x_test_DataFrame, y_test_Series)  # 输入”测试集“的特征数据值、目标值

    # 网格搜索进行超参数调优(代替上述步骤4.2 & 5.1 & 5.2)
    # 构造一些参数的值进行搜索
    param = {"n_neighbors": [3, 5, 10]}
    gc = GridSearchCV(knn_estimator, param_grid=param, cv=2)    # 一般设置cv=10
    gc.fit(x_train_DataFrame, y_train_Series)  # 输入”训练集“的特征数据值、目标值
    # 模型评估
    print("设定的超参数n_neighbors: [3, 5, 10]分别交叉验证(cv=2,共2次交叉验证)的结果,cv_results_:\n", gc.cv_results_)
    print("在交叉验证当中准确率最好的结果best_score_:", gc.best_score_)
    print("选择最好的模型best_estimator_ = ", gc.best_estimator_)
    print("在测试集上准确率score() =", gc.score(x_test_DataFrame, y_test_Series))


if __name__ == "__main__":
    knncls()

打印结果:

myDataFrame.head(5) = 
    row_id       x       y  accuracy    time    place_id
0       0  0.7941  9.0809        54  470702  8523065625
1       1  5.9567  4.7968        13  186555  1757726713
2       2  8.3078  7.0407        74  322648  1137537235
3       3  7.3665  2.5165        65  704587  6567393236
4       4  4.0961  1.1307        31  472130  7440663949
myDataFrame.count() = 
 row_id      17710
x           17710
y           17710
accuracy    17710
time        17710
place_id    17710
dtype: int64
type(dateTimeSeries) =  <class 'pandas.core.series.Series'>
dateTimeSeries.head(5) = 
 600    1970-01-01 18:09:40
957    1970-01-10 02:11:10
4345   1970-01-05 15:08:02
4735   1970-01-06 23:03:03
5580   1970-01-09 11:26:50
Name: time, dtype: datetime64[ns]
type(dateTimeIndexMap) =  <class 'pandas.core.indexes.datetimes.DatetimeIndex'>
dateTimeIndexMap = 
 DatetimeIndex(['1970-01-01 18:09:40', '1970-01-10 02:11:10',
               '1970-01-05 15:08:02', '1970-01-06 23:03:03',
               '1970-01-09 11:26:50', '1970-01-02 16:25:07',
               '1970-01-04 15:52:57', '1970-01-01 10:13:36',
               '1970-01-09 15:26:06', '1970-01-08 23:52:02',
               ...
               '1970-01-07 10:03:36', '1970-01-09 11:44:34',
               '1970-01-04 08:07:44', '1970-01-04 15:47:47',
               '1970-01-08 01:24:11', '1970-01-01 10:33:56',
               '1970-01-07 23:22:04', '1970-01-08 15:03:14',
               '1970-01-04 00:53:41', '1970-01-08 23:01:07'],
              dtype='datetime64[ns]', name='time', length=17710, freq=None)
myDataFrame.head(5) = 
       row_id       x       y  accuracy    place_id  month  day  hour
600      600  1.2214  2.7023        17  6683426742      1    1    18
957      957  1.1832  2.6891        58  6683426742      1   10     2
4345    4345  1.1935  2.6550        11  6889790653      1    5    15
4735    4735  1.1452  2.6074        49  6822359752      1    6    23
5580    5580  1.0089  2.7287        19  1527921905      1    9    11
type(placeCountDataFrame) = 
 <class 'pandas.core.frame.DataFrame'>
placeCountDataFrame = 
             row_id     x     y  accuracy  month   day  hour
place_id                                                   
1012023972       1     1     1         1      1     1     1
1057182134       1     1     1         1      1     1     1
1059958036       3     3     3         3      3     3     3
1085266789       1     1     1         1      1     1     1
1097200869    1044  1044  1044      1044   1044  1044  1044
placeCountDataFrame = 
      place_id  row_id     x     y  accuracy  month   day  hour
0  1097200869    1044  1044  1044      1044   1044  1044  1044
1  1228935308     120   120   120       120    120   120   120
2  1267801529      58    58    58        58     58    58    58
3  1278040507      15    15    15        15     15    15    15
4  1285051622      21    21    21        21     21    21    21
myDataFrame.head(5) = 
       row_id       x       y  accuracy    place_id  month  day  hour
600      600  1.2214  2.7023        17  6683426742      1    1    18
957      957  1.1832  2.6891        58  6683426742      1   10     2
4345    4345  1.1935  2.6550        11  6889790653      1    5    15
4735    4735  1.1452  2.6074        49  6822359752      1    6    23
5580    5580  1.0089  2.7287        19  1527921905      1    9    11
y_Series = 
 <class 'pandas.core.series.Series'> 
 600     6683426742
957     6683426742
4345    6889790653
4735    6822359752
5580    1527921905
Name: place_id, dtype: int64
x_DataFrame = 
 <class 'pandas.core.frame.DataFrame'> 
            x       y  accuracy  month  day  hour
600   1.2214  2.7023        17      1    1    18
957   1.1832  2.6891        58      1   10     2
4345  1.1935  2.6550        11      1    5    15
4735  1.1452  2.6074        49      1    6    23
5580  1.0089  2.7287        19      1    9    11
标准化后的x_DataFrame:
 [[ 1.27892477  0.9941573  -0.58835492  0.         -1.50340614  0.94055369]
 [ 0.78467442  0.80524744 -0.21403874  0.          1.80968818 -1.36413448]
 [ 0.91794088  0.31723029 -0.6431329   0.         -0.03091978  0.50842466]
 ...
 [-1.27513331  1.3018514  -0.17752009  0.          1.07344499  0.50842466]
 [ 1.04344424  0.66928958  0.05072149  0.         -0.39904137 -1.65222051]
 [-0.20123858 -1.30138377  0.88152082  0.          1.07344499  1.66076875]]
特征数据值of训练集 x_train_DataFrame:
 [[ 1.15600911 -1.65630533 -0.69791088  0.         -0.76716296  0.22033864]
 [ 0.94381786  0.80667857 -0.23229806  0.          0.33720181  0.94055369]
 [-0.00845506 -0.034829    1.24670734  0.         -1.13528455  0.36438165]
 ...
 [-0.36814511 -0.24091249 -0.14100143  0.         -1.50340614 -0.35583341]
 [ 1.46653289  1.51079716 -0.25055739  0.          0.7053234   0.22033864]
 [ 0.39263815  0.43887679 -0.08622346  0.         -0.76716296 -1.5081775 ]]
特征数据值of测试集 x_test_DataFrame:
 [[ 1.41477892  0.62778666 -0.16839042  0.          0.33720181  0.07629563]
 [-1.24666863  0.61633757 -0.56096593  0.         -1.13528455 -1.5081775 ]
 [-0.42119292 -1.54753905 -0.7161702   0.         -1.50340614  0.79651068]
 ...
 [-1.52355234  1.49791694 -0.70704054  0.          1.44156658 -1.36413448]
 [ 0.14292528 -0.73751645 -0.1501311   0.          0.7053234   0.79651068]
 [-1.13539761 -0.73608531 -0.06796413  0.          1.44156658  1.66076875]]
目标值of训练集 y_train_Series:
 2157968     8695574026
11153682    8258328058
8086959     5606572086
304429      2355236719
14884334    6502303487
Name: place_id, dtype: int64
目标值of测试集 y_test_Series:
 29084739    2327054745
12213699    3312463746
15897007    6399991653
15606379    3533177779
997179      1228935308
Name: place_id, dtype: int64

设定的超参数n_neighbors: [3, 5, 10]分别交叉验证(cv=2,2次交叉验证)的结果,cv_results_:
 {
 'mean_fit_time': array([0.00499177, 0.00400281, 0.00498641]), 
 'std_fit_time': array([1.19209290e-06, 1.35898590e-05, 3.57627869e-07]), 
 'mean_score_time': array([0.17004263, 0.19496608, 0.19624221]), 
 'std_score_time': array([0.00249135, 0.01743865, 0.00222671]), 
 'param_n_neighbors': masked_array(data=[3, 5, 10],mask=[False, False, False],fill_value='?', dtype=object), 
 'params': [{'n_neighbors': 3}, {'n_neighbors': 5}, {'n_neighbors': 10}], 
 'split0_test_score': array([0.43350524, 0.45913424, 0.46726051]), 
 'split1_test_score': array([0.4326602 , 0.46668787, 0.47877246]), 
 'mean_test_score': array([0.43308638, 0.46287831, 0.47296658]), 
 'std_test_score': array([0.0004225 , 0.00377667, 0.00575576]), 
 'rank_test_score': array([3, 2, 1]), 
 'split0_train_score': array([0.65034187, 0.60740976, 0.55271108]), 
 'split1_train_score': array([0.65353962, 0.60571964, 0.54852321]), 
 'mean_train_score': array([0.65194074, 0.6065647 , 0.55061714]), 
 'std_train_score': array([0.00159887, 0.00084506, 0.00209394])
 }
在交叉验证当中准确率最好的结果best_score_: 0.4729665825977301
选择最好的模型best_estimator_ = KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski', metric_params=None, n_jobs=1, n_neighbors=10, p=2, weights='uniform')
在测试集上准确率score() = 0.502127659574468

三、网格搜索 V.S. 启发式搜索

  • 网格搜索虽然简单,但是可并行计算,
  • 启发式搜索不容易并行,并且寻找最优路径也很花时间



参考资料:
详细介绍机器学习中的7种交叉验证方法
k-折交叉验证

  • 0
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值