Sklearn-机器学习应用实用指南-三-

137 阅读43分钟

Sklearn 机器学习应用实用指南(三)

原文:Hands-on Scikit-Learn for Machine Learning Applications

协议:CC BY-NC-SA 4.0

七、回归调优

回归预测建模(或简称为回归)就是学习自变量(或特征)和连续因变量(或结果)之间关联强度的问题。优化回归算法类似于优化分类算法。也就是说,我们调整模型的超参数,直到我们达到最优解。

不同之处在于,回归调优的目标是降低均方根误差(RMSE),而分类调优的目标是最大化精度。 RMSE 的一个好处是误差分数的单位与预测值相同。虽然回归预测可以使用 RMSE 进行评估,但分类预测却不能。

小费

回归调优的目标是最小化 RMSE。

为我们的调整示例选择的机器学习算法不是巧合。我选择它们是基于许多小时的实验、阅读和洞察力。对于给定的数据集,表现最好的算法被包括在内,表现差的算法则不包括在内。

对于本章中的回归实验,我们利用 GridSearchCV 进行调优。

小费

在给定足够的计算资源的情况下,使用 GridSearchCV 进行调优适合于彻底搜索性能最佳的超参数。使用 RandomizedSearchCV 进行调优适用于良好的搜索,或者如果调优高维数据。

学习调整回归算法可以通过使用各种数据集和回归变量的例子来加速。但是,我也建议遵循结构化流程:

  1. 总是从使用基线算法的默认超参数开始。

  2. 尝试训练和测试规模。

  3. 处理高维数据时使用降维。

  4. 处理大型数据集时,随机抽取样本。

  5. 缩放数据(在适当的情况下)以潜在地提高性能。

  6. 使用 GridSearchCV 或 RandomizedSearchCV 进行调整。

  7. 一旦使用基线算法进行了调整,就可以使用复杂的算法进行实验。

小费

从基线算法(及其默认的超参数)开始调优,以建立基线性能。

调整数据集

我们专注于四个数据集:小费、波士顿和葡萄酒(红和白)。小费数据由餐馆的服务员小费和相关因素组成,包括小费、餐费和时间。波士顿数据由波士顿不同地点的房价组成。葡萄酒数据由两个数据集(红色和白色)组成,这两个数据集由葡萄牙 Vinho Verde 葡萄酒的变体组成。

调谐提示

清单 7-1 中所示的代码示例基于未缩放和缩放的数据计算各种回归算法的 RMSE。因为 tips 是如此小的数据集,所以运行这种类型的实验在计算上是廉价的。

import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
from sklearn.ensemble import RandomForestRegressor as rfr,\
     AdaBoostRegressor as ada, GradientBoostingRegressor as gbr
from sklearn.linear_model import LinearRegression as lr,\
     BayesianRidge as bay, Ridge as rr, Lasso as l,\
     LassoLars as ll, ElasticNet as en,\
     ARDRegression as ard, RidgeCV as rcv
from sklearn.svm import SVR
from sklearn.tree import DecisionTreeRegressor as dtr
from sklearn.neighbors import KNeighborsRegressor as knn
from sklearn.preprocessing import StandardScaler

def get_error(model, Xtest, ytest):
    y_pred = model.predict(Xtest)
    return np.sqrt(mean_squared_error(ytest, y_pred)),\
           model.__class__.__name__

if __name__ == "__main__":
    br = '\n'
    X = np.load('data/X_tips.npy')
    y = np.load('data/y_tips.npy')
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, random_state=0)
    regressors = [lr(), bay(), rr(alpha=.5, random_state=0),
                  l(alpha=0.1, random_state=0), ll(), knn(),
                  ard(), rfr(random_state=0, n_estimators=100),
                  SVR(gamma='scale', kernel="rbf"),
                  rcv(fit_intercept=False), en(random_state=0),
                  dtr(random_state=0), ada(random_state=0),
                  gbr(random_state=0)]
    print ('unscaled:', br)
    for reg in regressors:
        reg.fit(X_train, y_train)
        rmse, name = get_error(reg, X_test, y_test)
        name = reg.__class__.__name__
        print (name + '(rmse):', end=' ')
        print (rmse)
    print ()
    print ('scaled:', br)
    scaler = StandardScaler()
    X_train_std = scaler.fit_transform(X_train)
    X_test_std = scaler.fit_transform(X_test)
    for reg in regressors

:
        reg.fit(X_train_std, y_train)
        rmse, name = get_error(reg, X_test_std, y_test)
        name = reg.__class__.__name__
        print (name + '(rmse):', end=' ')
        print (rmse)

Listing 7-1Calculating RMSE for tips data with regression algorithms

继续执行清单 7-1 中的代码。请记住,您可以从本书的示例下载中找到示例。您不需要手动键入示例。更容易访问示例下载和复制/粘贴。

执行清单 7-1 的输出应该如下所示:

unscaled:

LinearRegression(rmse): 0.9474705746817206
BayesianRidge(rmse): 0.9245282337469829
Ridge(rmse): 0.9471900902779103
Lasso(rmse): 0.9158574785712037
LassoLars(rmse): 1.333812899498391
KNeighborsRegressor(rmse): 1.086204460049883
ARDRegression(rmse): 0.9264801346401996
RandomForestRegressor(rmse): 0.8850975551298138
SVR(rmse): 0.9441992099702836
RidgeCV(rmse): 0.9426372075893412
ElasticNet(rmse): 0.9307377813721578
DecisionTreeRegressor(rmse): 1.2994272932036561
AdaBoostRegressor(rmse): 0.932681302158466
GradientBoostingRegressor(rmse): 0.9112440690311495

scaled:

LinearRegression(rmse): 0.9007751177881488
BayesianRidge(rmse): 0.9096801291989541

Ridge(rmse): 0.9010890080377257
Lasso(rmse): 0.8785977911833892
LassoLars(rmse): 1.333812899498391
KNeighborsRegressor(rmse): 0.9613578099280607
ARDRegression(rmse): 0.8745960871430548
RandomForestRegressor(rmse): 0.893772251516372
SVR(rmse): 0.9749204385201592
RidgeCV(rmse): 3.1960055364135638
ElasticNet(rmse): 1.1310151423347359
DecisionTreeRegressor(rmse): 1.1835900827021861
AdaBoostRegressor(rmse): 0.986987944835978
GradientBoostingRegressor(rmse): 0.8908489427010696

代码从导入必要的包和各种回归算法开始。函数 get_error 返回型号名称和 RMSE。主程序块从从 NumPy 文件加载预处理的 tips 数据开始。请记住,我们在第四章中对 tips 数据进行了编码并保存,以备将来处理。

小费

Scikit-Learn 允许您试验各种算法来测试性能,而不需要它们的上下文知识。

代码继续将数据分割成训练测试子集。接下来,我们创建一个回归算法列表。代码继续在未缩放的数据上训练每个算法并显示结果。然后,代码缩放数据,根据缩放后的数据训练每个算法,并显示结果。

缩放数据是这个实验的一个非常重要的部分,因为许多算法报告的 RMSE 结果比它们未缩放的兄弟要低。对缩放数据执行最好的算法是 Lasso 和 ARDRegression。

小费

在调优过程中,缩放可能是一项非常重要的技术。

所以,实验成功了!它引导我们找到了两种算法,我们可以集中精力进行调优。

清单 7-2 中显示的下一个代码示例用套索调整提示。

import numpy as np, humanfriendly as hf
import time
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.linear_model import Lasso
from sklearn.model_selection import GridSearchCV,\
     cross_val_score
from sklearn.metrics import mean_squared_error

def get_error(model, Xtest, ytest):
    y_pred = model.predict(Xtest)
    return np.sqrt(mean_squared_error(ytest, y_pred)),\
           model.__class__.__name__

def see_time(note):
    end = time.perf_counter()
    elapsed = end - start
    print (note, hf.format_timespan(elapsed, detailed=True))

def get_cross(model, data, target, groups=10):
    return cross_val_score(model, data, target, cv=groups,
                           scoring='neg_mean_squared_error')

if __name__ == "__main__":
    br = '\n'
    X = np.load('data/X_tips.npy')
    y = np.load('data/y_tips.npy')
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, random_state=0)
    scaler = StandardScaler()
    X_train_std = scaler.fit_transform(X_train)
    X_test_std = scaler.fit_transform(X_test)
    lasso = Lasso(random_state=0, alpha=0.1)
    print (lasso, br)
    lasso.fit(X_train_std, y_train)
    rmse, name = get_error(lasso, X_test_std, y_test)
    print (name + '(rmse):', end=' ')
    print (rmse, br)
    alpha_lasso = [1e-1]
    params = {'alpha': alpha_lasso, 'positive': [True, False],
              'max_iter': [10, 50, 100]}
    grid = GridSearchCV(lasso, params, cv=5, n_jobs=-1, verbose=1)
    start = time.perf_counter()
    grid.fit(X_train, y_train)
    see_time('training time:')
    bp = grid.best_params_

    print (bp, br)
    lasso = Lasso(**bp, random_state=0).fit(X_train_std, y_train)
    rmse, name = get_error(lasso, X_test_std, y_test)
    print (name + '(rmse):', end=' ')
    print (rmse, br)
    start = time.perf_counter()
    scores = get_cross(lasso, X, y)
    see_time('cross-validation rmse:')
    rmse = np.sqrt(np.mean(scores) * -1)
    print (rmse)

Listing 7-2Tuning tips with Lasso

执行清单 7-2 的输出应该如下所示:

Lasso(alpha=0.1, copy_X=True, fit_intercept=True, max_iter=1000,
   normalize=False, positive=False, precompute=False,
   random_state=0, selection="cyclic", tol=0.0001,
   warm_start=False)

Lasso(rmse): 0.8785977911833892

Fitting 5 folds for each of 6 candidates, totalling 30 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 8 concurrent workers.
[Parallel(n_jobs=-1)]: Done  30 out of  30 | elapsed:    2.1s finished
training time: 2 seconds and 246.86 milliseconds
{'alpha': 0.1, 'max_iter': 10, 'positive': True}

Lasso(rmse): 0.8781319871042923

cross-validation rmse: 8.58 milliseconds
1.0379804468729155

代码从导入必需的包开始。函数 get_error 返回 RMSE。函数 see_time 返回经过的时间。函数 get_cross 返回交叉验证 RMSE。

主程序块从加载预处理的 tips 数据开始。代码继续将数据分割成训练测试子集。接下来,我们扩展数据。然后,我们用 Lasso 训练数据,并显示结果,以便与调整后的 RMSE 进行基线比较。

Lasso 是一种使用 L1 罚函数进行正则化的算法。我们根据之前的实验调整α最大值超参数。

超参数 alpha 是乘以 L1 罚项的常数。也是用套索调音最重要的超参数。超参数强制系数为正。超参数 max_iter 代表最大迭代次数。

使用 GridSearchCV 和网格参数开始调优。通过调整,我们能够将 RMSE 降低一个非常小的量。交叉验证表明我们做得很好。

小费

请记住,函数 get_error 会返回负的均方误差,因此我们必须将结果乘以-1 并取结果的平方根以获得 RMSE,从而使结果为正。

清单 7-3 中显示的下一个代码示例使用 ARDRegression 调整提示。

import numpy as np, humanfriendly as hf
import time
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.linear_model import ARDRegression
from sklearn.model_selection import GridSearchCV,\
     cross_val_score
from sklearn.metrics import mean_squared_error

def get_error(model, Xtest, ytest):
    y_pred = model.predict(Xtest)
    return np.sqrt(mean_squared_error(ytest, y_pred)),\
           model.__class__.__name__

def see_time(note):
    end = time.perf_counter()
    elapsed = end - start
    print (note, hf.format_timespan(elapsed, detailed=True))

def get_cross(model, data, target, groups=10):
    return cross_val_score(model, data, target, cv=groups,
                           scoring='neg_mean_squared_error')

if __name__ == "__main__":
    br = '\n'
    X = np.load('data/X_tips.npy')
    y = np.load('data/y_tips.npy')
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, random_state=0)
    scaler = StandardScaler()
    X_train_std = scaler.fit_transform(X_train)
    X_test_std = scaler.fit_transform(X_test)
    ard = ARDRegression().fit(X_train_std, y_train)
    print (ard, br)
    rmse, name = get_error(ard, X_test_std, y_test)
    print (name + '(rmse):', end=' ')
    print (rmse, br)
    iters = [50]
    a1 = [1e5, 1e4]
    a2 = [1e5, 1e4]
    params = {'n_iter': iters, 'alpha_1': a1, 'alpha_2': a2}
    grid = GridSearchCV(ard, params, cv=5, n_jobs=-1, verbose=1)
    start = time.perf_counter()
    grid.fit(X_train, y_train)
    see_time('training time:')
    bp = grid.best_params_

    print (bp, br)
    ard = ARDRegression(**bp).fit(X_train_std, y_train)
    rmse, name = get_error(ard, X_test_std, y_test)
    print (name + '(rmse):', end=' ')
    print (rmse, br)
    start = time.perf_counter()
    scores = get_cross(ard, X, y)
    see_time('cross-validation rmse:')
    rmse = np.sqrt(np.mean(scores) * -1)
    print (rmse)

Listing 7-3Tuning tips with ARDRegression

执行清单 7-3 的输出应该如下所示:

ARDRegression(alpha_1=1e-06, alpha_2=1e-06, compute_score=False,
       copy_X=True, fit_intercept=True, lambda_1=1e-06,
       lambda_2=1e-06, n_iter=300, normalize=False,
       threshold_lambda=10000.0, tol=0.001, verbose=False)

ARDRegression(rmse): 0.8745960871430548

Fitting 5 folds for each of 4 candidates, totalling 20 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 8 concurrent workers.
[Parallel(n_jobs=-1)]: Done  20 out of  20 | elapsed:    3.5s finished
training time: 4 seconds and 286.03 milliseconds
{'alpha_1': 10000.0, 'alpha_2': 100000.0, 'n_iter': 50}

ARDRegression(rmse): 0.8645625277607758

cross-validation rmse: 4 seconds and 10.17 milliseconds
1.0376527153700184

代码从导入必需的包开始。函数 get_error 返回 RMSE。函数 see_time 返回经过的时间。函数 get_cross 返回交叉验证 RMSE。

主程序块从加载预处理的 tips 数据开始。代码继续将数据分割成训练测试子集。接下来,我们扩展数据。然后,我们使用 ARDRegression 训练数据,并显示结果,以便与调整后的 RMSE 进行基线比较。

ARDRegression (自动相关性确定回归)用贝叶斯岭回归拟合回归模型。模型的估计是通过迭代最大化观测值的边际对数似然来完成的。

我们用 n_iteralpha_1alpha_2 调谐。超参数 n_iter 是最大迭代次数。超参数 alpha_1 是优先于 alpha 参数的伽马分布的形状参数。超参数 alpha_2 是优先于 alpha 参数的伽马分布的逆比例参数(或速率参数)。

我们能够通过调谐来降低 RMSE。此外,交叉验证显示我们做得非常好。

调谐波士顿

清单 7-4 中所示的代码示例基于未缩放和缩放的数据计算各种回归算法的 RMSE。由于波士顿是一个相对较小的数据集,所以运行这种类型的实验在计算上并不昂贵。

import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
from sklearn.ensemble import RandomForestRegressor as rfr,\
     AdaBoostRegressor as ada, GradientBoostingRegressor as gbr
from sklearn.linear_model import LinearRegression as lr,\
     BayesianRidge as bay, Ridge as rr, Lasso as l,\
     LassoLars as ll, ElasticNet as en,\
     ARDRegression as ard, RidgeCV as rcv
from sklearn.svm import SVR
from sklearn.tree import DecisionTreeRegressor as dtr
from sklearn.neighbors import KNeighborsRegressor as knn
from sklearn.preprocessing import StandardScaler

def get_error(model, Xtest, ytest):
    y_pred = model.predict(Xtest)
    return np.sqrt(mean_squared_error(ytest, y_pred)),\
           model.__class__.__name__

if __name__ == "__main__":
    br = '\n'
    X = np.load('data/X_boston.npy')
    y = np.load('data/y_boston.npy')
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, random_state=0)
    regressors = [lr(), bay(), rr(alpha=.5, random_state=0),
                  l(alpha=0.1, random_state=0), ll(), knn(),
                  ard(), rfr(random_state=0, n_estimators=100),
                  SVR(gamma='scale', kernel="rbf"),
                  rcv(fit_intercept=False), en(random_state=0),
                  dtr(random_state=0), ada(random_state=0),
                  gbr(random_state=0)]
    print ('unscaled:', br)
    for reg in regressors:
        reg.fit(X_train, y_train)
        rmse, name = get_error(reg, X_test, y_test)
        name = reg.__class__.__name__
        print (name + '(rmse):', end=' ')
        print (rmse)
    print ()
    print ('scaled:', br)
    scaler = StandardScaler()
    X_train_std = scaler.fit_transform(X_train)
    X_test_std = scaler.fit_transform(X_test)
    for reg in regressors:
        reg.fit(X_train_std, y_train)
        rmse, name = get_error(reg, X_test_std, y_test)
        name = reg.__class__.__name__
        print (name + '(rmse):', end=' ')
        print (rmse)

Listing 7-4Calculating RMSE for boston data with regression algorithms

执行清单 7-4 的输出应该如下所示:

unscaled:

LinearRegression(rmse): 4.236710574387242
BayesianRidge(rmse): 4.317939916221959
Ridge(rmse): 4.243658717030716
Lasso(rmse): 4.300740333025026
LassoLars(rmse): 8.754893348840868
KNeighborsRegressor(rmse): 5.9934937623789
ARDRegression(rmse): 4.28415048500826
RandomForestRegressor(rmse): 3.37169151536684
SVR(rmse): 7.100029068343849
RidgeCV(rmse): 4.392246392993031
ElasticNet(rmse): 4.88844846745213
DecisionTreeRegressor(rmse): 4.346328232622458
AdaBoostRegressor(rmse): 3.652816906059683
GradientBoostingRegressor(rmse): 3.1941117128039194

scaled:

LinearRegression(rmse): 4.398269524691269
BayesianRidge(rmse): 4.419543929268475
Ridge(rmse): 4.400075160458176
Lasso(rmse): 4.489952156682322
LassoLars(rmse): 8.754893348840868
KNeighborsRegressor(rmse): 4.757936288305807
ARDRegression(rmse): 4.383622227159
RandomForestRegressor(rmse): 4.053037237125816
SVR(rmse): 5.083294658978756
RidgeCV(rmse): 22.34757636411328
ElasticNet(rmse): 5.277752330669967
DecisionTreeRegressor(rmse): 5.2796587719252726
AdaBoostRegressor(rmse): 4.100148076529094
GradientBoostingRegressor(rmse): 3.7490071027496015

代码从导入必要的包和各种回归算法开始。函数 get_error 返回型号名称和 RMSE。主程序块首先从 NumPy 文件加载清理后的波士顿数据。请记住,我们清理了波士顿数据,并在第四章中将其保存以备将来处理。

代码继续将数据分割成训练测试子集。接下来,我们创建一个回归算法列表。代码继续在未缩放的数据上训练每个算法并显示结果。然后,代码缩放数据,根据缩放后的数据训练每个算法,并显示结果。

在这个实验中表现最好的算法是 GradientBoostingRegressor 和 RandomForestRegressor(两者都具有未缩放的数据)。所以,缩放数据并没有增加这个数据集的价值。

清单 7-5 中显示的下一个代码示例使用 GradientBoostingRegressor 调优波士顿数据集。

import numpy as np, humanfriendly as hf, warnings, sys
import time
from sklearn.model_selection import train_test_split
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.model_selection import GridSearchCV,\
     cross_val_score
from sklearn.metrics import mean_squared_error

def get_error(model, Xtest, ytest):
    y_pred = model.predict(Xtest)
    return np.sqrt(mean_squared_error(ytest, y_pred)),\
           model.__class__.__name__

def see_time(note):
    end = time.perf_counter()
    elapsed = end - start

    print (note, hf.format_timespan(elapsed, detailed=True))

def get_cross(model, data, target, groups=10):
    return cross_val_score(model, data, target, cv=groups,
                           scoring='neg_mean_squared_error')

if __name__ == "__main__":
    br = '\n'
    if not sys.warnoptions:
        warnings.simplefilter('ignore')
    X = np.load('data/X_boston.npy')
    y = np.load('data/y_boston.npy')
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, random_state=0)
    gbr = GradientBoostingRegressor(random_state=0)
    print (gbr, br)
    gbr.fit(X_train, y_train)
    rmse, name = get_error(gbr, X_test, y_test)
    print (name + '(rmse):', end=' ')
    print (rmse, br)
    loss = ['ls', 'lad', 'huber']
    lr = [1e-2, 1e-1, 1e-0]
    n_est = [150, 200, 300, 500]
    alpha = [0.9]
    params = {'loss': loss, 'learning_rate': lr,
              'n_estimators': n_est, 'alpha': alpha}
    grid = GridSearchCV(gbr, params, cv=5, n_jobs=-1,
                        verbose=1, refit=False)
    start = time.perf_counter()
    grid.fit(X_train, y_train)
    see_time('training time:')
    bp = grid.best_params_

    print (bp, br)
    gbr = GradientBoostingRegressor(**bp, random_state=0)
    gbr.fit(X_train, y_train)
    rmse, name = get_error(gbr, X_test, y_test)
    print (name + '(rmse):', end=' ')
    print (rmse, br)
    start = time.perf_counter()
    scores = get_cross(gbr, X, y)
    see_time('cross-validation rmse:')
    rmse = np.sqrt(np.mean(scores) * -1)
    print (rmse)

Listing 7-5Tuning boston data with GradientBoostingRegressor

执行清单 7-5 的输出应该如下所示:

GradientBoostingRegressor(alpha=0.9, criterion="friedman_mse",
             init=None, learning_rate=0.1, loss="ls",
             max_depth=3, max_features=None,
             max_leaf_nodes=None, min_impurity_decrease=0.0,
             min_impurity_split=None,
             min_samples_leaf='deprecated', min_samples_split=2,
             min_weight_fraction_leaf='deprecated',
             n_estimators=100, n_iter_no_change=None,
             presort='auto', random_state=0, subsample=1.0,
             tol=0.0001, validation_fraction=0.1, verbose=0,
             warm_start=False)

GradientBoostingRegressor(rmse): 3.1941117128039194

Fitting 5 folds for each of 36 candidates, totalling 180 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 8 concurrent workers.
[Parallel(n_jobs=-1)]: Done  34 tasks      | elapsed:    3.1s
[Parallel(n_jobs=-1)]: Done 180 out of 180 | elapsed:    9.1s finished
training time: 9 seconds and 170.11 milliseconds
{'alpha': 0.9, 'learning_rate': 0.1, 'loss': 'huber', 'n_estimators': 300}

GradientBoostingRegressor(rmse): 3.0839764165411934

cross-validation rmse: 3 seconds and 258.29 milliseconds
3.7929403445012064

代码从导入 GradientBoostingRegressor 以及其他必需的包开始。GradientBoostingRegressor通过建立一个前向模式的附加模型来执行回归的梯度推进。

函数 get_error 返回给定算法的 RMSE 和模型名称。函数 see_time 返回经过的时间。函数 get_cross 返回负的均方误差。

主块加载波士顿数据,将其分成训练测试子集,并使用 GradientBoostingRegressor 训练数据。代码继续显示带有默认参数的 RMSE,以提供与调优的 RMSE 进行比较的基线分数。接下来,用超参数损失学习率n 估计量α来调整模型。

超参数损失是要优化的损失函数。超参数 learning_rate 控制我们相对于损失梯度调整模型学习的程度。超参数 n_estimators 是要执行的升压阶段的数量。Hypeparameter alpha 是 huber 损失函数的 alpha 分位数。

通过调整可以降低 RMSE。我们通过运行交叉验证来结束。因为我们调整的 RMSE 低于交叉验证的 RMSE,所以我们的情况很好。

小费

您可能需要偶尔重启计算机,因为调整需要大量的计算资源。

本节的最后一个代码示例(如清单 7-6 所示)使用 RandomForestRegressor 调优波士顿数据集。

import numpy as np, humanfriendly as hf, warnings, sys
import time
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import GridSearchCV,\
     cross_val_score
from sklearn.metrics import mean_squared_error

def get_error(model, Xtest, ytest):
    y_pred = model.predict(Xtest)
    return np.sqrt(mean_squared_error(ytest, y_pred)),\
           model.__class__.__name__

def see_time(note):
    end = time.perf_counter()
    elapsed = end - start
    print (note, hf.format_timespan(elapsed, detailed=True))

def get_cross(model, data, target, groups=10):
    return cross_val_score(model, data, target, cv=groups,
                           scoring='neg_mean_squared_error')

if __name__ == "__main__":
    br = '\n'
    if not sys.warnoptions:
        warnings.simplefilter('ignore')
    X = np.load('data/X_boston.npy')
    y = np.load('data/y_boston.npy')
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, random_state=0)
    rfr = RandomForestRegressor(random_state=0)
    print (rfr, br)
    rfr.fit(X_train, y_train)
    rmse, name = get_error(rfr, X_test, y_test)
    print (name + '(rmse):', end=' ')
    print (rmse, br)
    n_est = [100, 500, 1000]
    boot = [True, False]
    params = {'n_estimators': n_est, 'bootstrap': boot}
    grid = GridSearchCV(rfr, params, cv=5, n_jobs=-1,
                        verbose=1, refit=False)
    start = time.perf_counter()
    grid.fit(X_train, y_train)
    see_time('training time:')
    bp = grid.best_params_
    print (bp, br)
    rfr = RandomForestRegressor(**bp, random_state=0)
    rfr.fit(X_train, y_train)
    rmse, name = get_error(rfr, X_test, y_test)
    print (name + '(rmse):', end=' ')
    print (rmse, br)
    start = time.perf_counter()
    scores = get_cross(rfr, X, y)
    see_time('cross-validation rmse:')
    rmse = np.sqrt(np.mean(scores) * -1)
    print (rmse)

Listing 7-6Tuning boston data with RandomForestRegressor

执行清单 7-6 的输出应该如下所示:

RandomForestRegressor(bootstrap=True, criterion="mse",
           max_depth=None, max_features="auto",
           max_leaf_nodes=None, min_impurity_decrease=0.0,
           min_impurity_split=None,
           min_samples_leaf='deprecated', min_samples_split=2,
           min_weight_fraction_leaf='deprecated',
           n_estimators='warn', n_jobs=None, oob_score=False,
           random_state=0, verbose=0, warm_start=False)

RandomForestRegressor(rmse): 3.5587794792757004

Fitting 5 folds for each of 6 candidates, totalling 30 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 8 concurrent workers.
[Parallel(n_jobs=-1)]: Done  30 out of  30 | elapsed:    8.3s finished
training time: 8 seconds and 453.84 milliseconds
{'bootstrap': True, 'n_estimators': 100}

RandomForestRegressor(rmse): 3.37169151536684

cross-validation rmse: 1 second and 845.76 milliseconds
3.6815463792891623

代码从导入 RandomForestRegressor 以及其他必需的包开始。 RandomForestRegressor 在数据集的各种子样本上拟合多个分类决策树,并使用平均来提高预测精度和控制过度拟合。

函数 get_error 返回给定算法的 RMSE 和模型名称。函数 see_time 返回经过的时间。函数 get_cross 返回负的均方误差。

主块加载波士顿数据,将其分成训练测试子集,并用 RandomForestRegressor 训练数据。代码继续显示带有默认参数的 RMSE,以提供与调优的 RMSE 进行比较的基线分数。接下来,用超参数 n 估计器自助来调整模型。

超参数 n_estimators 是森林中树木的数量。超参数 bootstrap 决定构建树时是否使用 bootstrap 样本。

通过调整可以降低 RMSE。我们通过运行交叉验证来结束。因为我们调整的 RMSE 低于交叉验证的 RMSE,所以我们的情况很好。

调酒

通过运行类似于清单 7-1 和 7-4 中所示的实验,我们发现 RandomForestRegressor(具有未缩放的数据)为红葡萄酒和白葡萄酒数据提供了最低的 RMSE。如果你愿意,继续做你自己的实验来验证我们的结果。

清单 7-7 中的代码示例用 RandomForestRegressor 调优红酒数据集。

import numpy as np, humanfriendly as hf
import time
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import GridSearchCV,\
     cross_val_score
from sklearn.metrics import mean_squared_error

def get_error(model, Xtest, ytest):
    y_pred = model.predict(Xtest)
    return np.sqrt(mean_squared_error(ytest, y_pred)),\
           model.__class__.__name__

def see_time(note):
    end = time.perf_counter()
    elapsed = end - start
    print (note, hf.format_timespan(elapsed, detailed=True))

def get_cross(model, data, target, groups=10):
    return cross_val_score(model, data, target, cv=groups,
                           scoring='neg_mean_squared_error')

if __name__ == "__main__":
    br = '\n'
    X = np.load('data/X_red.npy')
    y = np.load('data/y_red.npy')
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, random_state=0)
    rfr = RandomForestRegressor(random_state=0, n_estimators=10)
    print (rfr, br)
    rfr.fit(X_train, y_train)
    rmse, name = get_error(rfr, X_test, y_test)
    print (name + '(rmse):', end=' ')
    print (rmse, br)
    n_est = [100, 500]
    boot = [True, False]
    params = {'n_estimators': n_est, 'bootstrap': boot}
    grid = GridSearchCV(rfr, params, cv=5, n_jobs=-1, verbose=1)
    start = time.perf_counter()
    grid.fit(X_train, y_train)
    see_time('training time:')
    bp = grid.best_params_
    print (bp, br)
    rfr = RandomForestRegressor(**bp, random_state=0)
    rfr.fit(X_train, y_train)
    rmse, name = get_error(rfr, X_test, y_test)
    print (name + '(rmse):', end=' ')
    print (rmse, br)
    start = time.perf_counter()
    scores = get_cross(rfr, X, y)
    see_time('cross-validation rmse:')
    rmse = np.sqrt(np.mean(scores) * -1)
    print (rmse)

Listing 7-7Tuning red wine data with RandomForestRegressor

执行清单 7-7 的输出应该如下所示:

RandomForestRegressor(bootstrap=True, criterion="mse",
                      max_depth=None, max_features="auto",
                      max_leaf_nodes=None,
                      min_impurity_decrease=0.0,
                      min_impurity_split=None,
                      min_samples_leaf='deprecated',
                      min_samples_split=2,
                      min_weight_fraction_leaf='deprecated',
                      n_estimators=10, n_jobs=None,
                      oob_score=False, random_state=0, verbose=0,
                      warm_start=False)

RandomForestRegressor(rmse): 0.626079068488957

Fitting 5 folds for each of 4 candidates, totalling 20 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 8 concurrent workers.
[Parallel(n_jobs=-1)]: Done  20 out of  20 | elapsed:    7.1s finished
training time: 7 seconds and 629.56 milliseconds
{'bootstrap': True, 'n_estimators': 100}

RandomForestRegressor(rmse): 0.5847897057917487

cross-validation rmse: 4 seconds and 804.96 milliseconds
0.6498982966515346

代码从导入必需的包开始。函数 get_error 返回给定算法的 RMSE 和模型名称。函数 see_time 返回经过的时间。函数 get_cross 返回负的均方误差。

主块加载红酒数据,将其分成训练测试子集,并用 RandomForestRegressor 训练数据。代码继续显示带有默认参数的 RMSE,以提供与调优的 RMSE 进行比较的基线分数。接下来,用超参数 n 估计器自助来调整模型。

通过调整可以降低 RMSE。我们通过运行交叉验证来结束。因为我们调整的 RMSE 低于交叉验证的 RMSE,所以我们的情况很好。

清单 7-8 中显示的最后一个代码示例用 RandomForestRegressor 调优了白葡萄酒数据集。

import numpy as np, humanfriendly as hf
import time
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import GridSearchCV,\
     cross_val_score
from sklearn.metrics import mean_squared_error

def get_error(model, Xtest, ytest):
    y_pred = model.predict(Xtest)
    return np.sqrt(mean_squared_error(ytest, y_pred)),\
           model.__class__.__name__

def see_time(note):
    end = time.perf_counter()
    elapsed = end - start
    print (note, hf.format_timespan(elapsed, detailed=True))

def get_cross(model, data, target, groups=10):
    return cross_val_score(model, data, target, cv=groups,
                           scoring='neg_mean_squared_error')

if __name__ == "__main__":

    br = '\n'
    X = np.load('data/X_white.npy')
    y = np.load('data/y_white.npy')
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, random_state=0)
    rfr = RandomForestRegressor(random_state=0, n_estimators=10)
    print (rfr, br)
    rfr.fit(X_train, y_train)
    rmse, name = get_error(rfr, X_test, y_test)
    print (name + '(rmse):', end=' ')
    print (rmse, br)
    n_est = [100, 500]
    boot = [True, False]
    params = {'n_estimators': n_est, 'bootstrap': boot}
    grid = GridSearchCV(rfr, params, cv=5, n_jobs=-1, verbose=1)
    start = time.perf_counter()
    grid.fit(X_train, y_train)
    see_time('training time:')
    bp = grid.best_params_
    print (bp, br)
    rfr = RandomForestRegressor(**bp, random_state=0)
    rfr.fit(X_train, y_train)
    rmse, name = get_error(rfr, X_test, y_test)
    print (name + '(rmse):', end=' ')
    print (rmse, br)
    start = time.perf_counter()
    scores = get_cross(rfr, X, y)
    see_time('cross-validation rmse:')
    rmse = np.sqrt(np.mean(scores) * -1)
    print (rmse)

Listing 7-8Tuning white wine data with RandomForestRegressor

执行清单 7-8 的输出应该如下所示:

RandomForestRegressor(bootstrap=True, criterion="mse",
           max_depth=None, max_features="auto",
           max_leaf_nodes=None, min_impurity_decrease=0.0,
           min_impurity_split=None,
           min_samples_leaf='deprecated', min_samples_split=2,
           min_weight_fraction_leaf='deprecated',n_estimators=10,
           n_jobs=None, oob_score=False, random_state=0,
           verbose=0, warm_start=False)

RandomForestRegressor(rmse): 0.6966098665124181

Fitting 5 folds for each of 4 candidates, totalling 20 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 8 concurrent workers.
[Parallel(n_jobs=-1)]: Done  20 out of  20 | elapsed:   18.7s finished
training time: 25 seconds and 709.64 milliseconds
{'bootstrap': True, 'n_estimators': 500}

RandomForestRegressor(rmse): 0.6728175517621279

cross-validation rmse: 1 minute, 24 seconds and 70.99 milliseconds 0.7183073387927801

代码从导入必需的包开始。函数 get_error 返回给定算法的 RMSE 和模型名称。函数 see_time 返回经过的时间。函数 get_cross 返回负的均方误差。

主块加载白葡萄酒数据,将其分成训练测试子集,并用 RandomForestRegressor 训练数据。代码继续显示带有默认参数的 RMSE,以提供与调优的 RMSE 进行比较的基线分数。接下来,用超参数 n 估计器自助来调整模型。

通过调整可以降低 RMSE。我们通过运行交叉验证来结束。因为我们调整的 RMSE 低于交叉验证的 RMSE,所以我们的情况很好。

八、把所有的放在一起

旅程

在我们的机器学习之旅中,我们使用了 11 个数据集。七个是分类数据集,而四个是预测建模回归数据集。

四个分类数据集是简单的。三是复杂的。简单数据集是那些特征很少的数据集。具有很少特征的数据集通常被称为具有低维特征空间的数据集。复杂数据集是具有许多特征的数据集。这种数据集通常被称为具有高维特征空间的数据集。所有四个预测建模回归(或只是回归)数据集都很简单,因为它们各自的特征空间具有很少的特征。

机器学习中的特征空间维度依赖于每个特征代表一个维度的概念。因此,具有少量特征的特征集具有低维度,而具有大量维度的特征集具有高维度。特征空间维度在机器学习中很重要,因为具有高维特征空间的数据集在计算上是昂贵的。也就是说,用这样的数据进行算法机器学习,需要丰富的计算机资源。

分类预测数据所属的类别,而回归预测基于以前观察到的数据的数值。因此,分类被用来预测离散的反应,如性别或一种水果。回归用于预测连续值,如房价或利润。

我们通过演示机器学习算法如何从数据中学习来继续我们的旅程。在第 2 和 3 章中,我们开始用各种分类算法训练简单和复杂的分类数据集。然后我们在第四章中用各种回归算法训练回归数据集。

对于分类和回归学习,我们演示了如何通过训练好的算法进行预测。预测让我们看到算法训练的结果。通过分类,我们可以根据新数据预测谨慎的目标。通过回归,我们可以根据新数据预测连续的结果。在这两种情况下,我们都使用从完整数据集中分离出来的测试数据作为新数据。然而,预测只和我们的训练一样好。

要评估培训绩效,我们必须知道如何衡量学习。对于分类,我们向您展示了如何获得准确性。准确性是我们预测正确的百分比。对于回归,我们向您展示了如何推导 RMSE(均方根误差)。RMSE 是预测值和观察值之间的差异。简单地说,RMSE 测量误差。

虽然我们向您展示了如何使用机器学习算法来训练数据,但通过模型调整来提高性能是可能的。因此,我们在第五章和第六章和第七章中向您展示了如何调整分类算法以及回归算法。如您所知,调优是一个非常复杂、艰巨、耗时且需要实验的过程。因此,您需要耐心和毅力通过调优来提高性能。

调整也是减少过度拟合的一种非常有效的方法。过度拟合是指算法在记忆数据,而不是从中学习。当你的训练精度比测试精度高很多时,你知道你的模型是过度拟合的。

然而,机器学习之旅才刚刚开始。

价值和成本

学习机器学习的技术层面是不够的。工业中的数据集往往很大,甚至非常大。即使具有低维特征空间的大型数据集在计算上也可能是昂贵的。想象一下用高维特征空间训练一个极其庞大的数据集的计算开销!如果包括调优,计算费用和数据科学家的时间可能会过高。

数据科学家希望数据处于自然原始状态。他们想在它产生的时候从源头收集它。他们不想访问关系数据库中的数据或遗留系统中以各种形式存储的数据。然而,当前的遗留系统通常将数据存储在关系数据库中。此外,新数据经常被放入相同的系统中。最后,组织制定了关于谁可以访问数据、可以访问多少数据以及何时可以访问数据的规则。

数据科学家希望生成的原始数据与自然界中实际发生的情况相匹配。也就是说,他们想要模仿现实。由于机器学习的想法是从数据中学习,如何、在哪里、何时以及为什么收集数据是至关重要的。如果组织将数据收集并处理到系统中,数据的自然含义(和时间)就会丢失。而且,数据科学家甚至可能很难获得他们需要的数据。

因此,数据科学家不仅要努力获取自然状态的数据,他们还必须浏览所有的规则、安全策略和政策,以访问当前或新的数据。当前的组织结构并不是为了让数据科学家能够在他们想要的时候以他们想要的形式获得他们想要的数据。此外,数据科学家在组织中是一个相对较新的现象。因此,他们的政治影响力往往较小,他们的角色可能会被误解,他们对数据所做的事情与过去所做的事情不符。

政治影响力的减弱使得很难说服那些拥有金融资源的人(或投资人)在当前分配的基础上为昂贵的计算资源分配更多资金。如果有钱人误解了数据科学家的作用,误解了过去资源是如何分配的,数据科学家可能得不到自己需要的资源。此外,数据科学家的工资很高,而且受过良好的教育。自然地,得到更多报酬的人会更加嫉妒和争权夺利。此外,数据科学家可以被视为无所不知,因为他们的教育,组织的新成员,以及对数据的不同看法。

那么,如何才能说服有钱人为数据科学家的数据需求做预算呢?首先,我们必须了解什么对他们来说是重要的。第二,我们必须意识到,他们自然希望我们成功,因为工业界正以不可思议的速度拥抱机器学习。

对于理财人士来说,只有两件事至关重要——价值和成本。价值是决定组织健康状况的因素。成本是任何有损组织健康和福利的东西。因此,我们必须能够向投资者展示我们的案例,证明我们所做的既有价值又能降低成本。我们还必须认识到,投资人不是数据科学家,很可能不是技术导向型的。

本章的剩余部分通过用非常简单的术语展示本书中构建的三个复杂的代码示例,展示了资金的价值和成本。每个代码示例都有输出,并通过强调价值和成本节约进行简单解释。代码不解释。它只是通过展示数据科学家实际做的事情来展示算法学习的复杂性。最后,我们选择了最复杂的数据集来证明它们可以向非技术人员解释。

MNIST 价值和成本

我们从之前调优的 MNIST 代码示例开始,如清单 8-1 所示。我们加载数据,运行机器学习算法,并显示结果。然后,我们从商业价值和成本的角度解释结果。

import numpy as np, humanfriendly as hf
import time
from sklearn.model_selection import train_test_split
from sklearn.ensemble import ExtraTreesClassifier
from sklearn.metrics import confusion_matrix
import matplotlib.pyplot as plt
import seaborn as sns

def get_scores(model, xtrain, ytrain, xtest, ytest):
    ypred = model.predict(xtest)
    train = model.score(xtrain, ytrain)
    test = model.score(xtest, y_test)
    name = model.__class__.__name__
    return (name, train, test, ypred)

def see_time(note):
    end = time.perf_counter()
    elapsed = end - start
    print (note, hf.format_timespan(elapsed, detailed=True))

def find_misses(test, pred):
    return [i for i, row in enumerate(test) if row != pred[i]]

if __name__ == "__main__":
    br = '\n'
    X_file = 'data/X_mnist'
    y_file = 'data/y_mnist'
    X = np.load('data/X_mnist.npy')
    y = np.load('data/y_mnist.npy')
    X = X.astype(np.float32)
    bp = np.load('data/bp_mnist_et.npy')
    bp = bp.tolist()
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, random_state=0)
    et = ExtraTreesClassifier(**bp, random_state=0, n_estimators=200)
    start = time.perf_counter()
    et.fit(X_train, y_train)
    et_scores = get_scores(et, X_train, y_train, X_test, y_test)
    see_time('total time:')
    print (et_scores[0] + ' (train, test):')
    print (et_scores[1], et_scores[2], br)
    y_pred = et_scores[3]
    cm = confusion_matrix(y_test, y_pred)
    plt.figure(1)
    ax = plt.axes()
    sns.heatmap(cm.T, annot=True, fmt="d", cmap="gist_ncar_r", ax=ax)
    ax.set_title(et_scores[0] + 'confustion matrix')
    plt.xlabel('true value')
    plt.ylabel('predicted value')
    indx = find_misses(y_test, y_pred)
    print ('pred', 'actual')
    misses = [(y_pred[row], y_test[row], i)
              for i, row in enumerate(indx)]
    [print (row[0], '  ', row[1]) for i, row in

enumerate(misses)
     if i < 10]
    print()
    img_act = y_test[indx[0]]
    img_pred = y_pred[indx[0]]
    print ('actual', img_act)
    print ('pred', img_pred)
    text = str(img_pred)
    test_images = X_test.reshape(-1, 28, 28)
    plt.figure(2)
    plt.imshow(test_images[indx[0]], cmap="gray", interpolation="gaussian")
    plt.text(0, 0.05, text, color="r", bbox=dict(facecolor='white'))
    title = str(img_act) + ' misclassified as ' + text
    plt.title(title)
    plt.show()

Listing 8-1MNIST value and cost

执行清单 8-1 的输出应该如下所示:

total time: 1 minute, 8 seconds and 650.43 milliseconds
ExtraTreesClassifier (train, test):
1.0 0.9732

pred actual
3.0    9.0
7.0    3.0
4.0    9.0
2.0    3.0
3.0    2.0
6.0    5.0
9.0    7.0
9.0    3.0
8.0    6.0
9.0    4.0

actual 9.0
pred 3.0

列表 8-1 还显示了数字 8-1 和 8-2 。图 8-1 显示混淆矩阵,图 8-2 显示第一次误分类。

img/481580_1_En_8_Fig2_HTML.jpg

图 8-2

第一次错误预测

img/481580_1_En_8_Fig1_HTML.jpg

图 8-1

混淆矩阵

向有钱人解释 MNIST

代码示例显示了我们从训练 MNIST 数据集中学到了多少。MNIST 数据代表从 0 到 9 的数字图像。数据集中的每个元素都由作为一组像素的图像以及该图像所代表的内容组成。也就是说,这组像素(就像我们在电视屏幕上看到的图像)代表 0 到 9 之间的一个数字。

例如,MNIST 数据中的第一个数据元素由一组表示数字 0 的像素组成。因此,我们可以基于这样的知识来训练整个数据集,即每个数据元素由一组像素组成,在这些像素上组成数字图像,并且实际数字是 0 到 9 之间的数字。

向投资者解释产出

输出显示我们了解了数据集的所有内容,因为训练性能是 1.0(或 100%)。这听起来很棒,但我们必须考虑到我们从新数据中学习的程度。

通常,机器学习实践者从数据集中切下一部分数据,并在训练过程中隐藏这些数据。也就是说,他们只对一部分数据进行训练,并使用在训练前切掉的隐藏(或未触及)数据上所学到的东西。我们从中学习的数据称为训练数据,而在训练过程中未被触及的隐藏数据称为测试数据。

在新数据(或测试数据)上使用我们从训练数据中学到的东西是至关重要的,因为我们根据测试数据来预测未来的性能。我们使用测试数据预测未来的原因是,技术培训过程从未见过测试数据。因此,测试数据是我们收集的未来数据的代表。

在测试数据(或新数据)上的训练性能为 97.32%。所以,训练是成功的,因为我们有信心超过 97%的预测是正确的。

输出的其余部分只是显示一些错误分类(或错误),以帮助机器学习专家验证结果。请记住,虽然性能超过 97%,但我们仍然有 2.68%的误差。

向理财人士解释混淆矩阵

混淆矩阵之所以得名,是因为它可能会混淆矩阵中每个数字的推导方式。然而,解释矩阵的实际输出非常简单。

左侧下方的数字表示每个数字的预测值(标记为预测值),底部的数字表示每个数字的实际值(标记为真值)。正确的预测是从左上角到右下角对角线上的数字。不正确的预测是对角线上的数字而不是

我们的混淆矩阵显示,我们正确预测了 1621 0 位数字(左上角)。当我们沿着对角线向下移动时,我们看到我们正确地预测了 2011 1 位数,等等。

要更详细地了解预测性能,我们可以查看每个数字的预测或实际数字是如何预测的。为了分析每个数字的预测,我们查看行值。为了分析实际数字是如何预测的,我们来看看列值。

让我们先来看看数字 0 的预测,它们从左到右位于顶行。第一行值是 1621(混淆矩阵的左上角),代表正确的预测。因此,我们正确地预测了数字 0 的 1621 次。这一行剩余的数字代表我们预测的数字 0,但实际的数字是其他值。例如,顶行的最后一个值是 11。因此,当实际值是数字 9 时,我们做了 11 个不正确的数字 0 预测。

现在,让我们来看看当实际值为数字 0 时,位于第一列上下的预测。第一列值是 1621。因为这个数字在对角线上,所以它代表数字 0 的正确预测。但是,数字 0 被错误地预测为数字 2 两次,数字 5 一次,数字 6 四次,数字 7 一次,数字 8 七次,数字 9 一次。

因此,混淆矩阵的结果可以有两种解释。一种方法是看预测性能。另一种方法是观察实际值是如何预测的。然而,这两种方式都导致相同的结果。

例如,进一步研究的明确领域是找出为什么数字 9 有这么多不正确的预测。最坏的原因是显示二十八(28)个不正确预测的值。我们可以认为这是 28 次错误地预测了数字 9,而实际值是数字 4。相反,我们可以把这看作是数字 4 被错误地预测为数字 9 28 次。

混淆矩阵是一个很好的方法来看看我们基于新数据的预测有多好。它还提供了一种识别问题区域的方法。例如,我们错误预测数字 9 的次数比任何其他数字都多。因此,机器学习专家可以确定哪些地方需要做更多的工作来提高性能。

向金融人士解释可视化

可视化显示了我们在训练中做出的第一个错误预测。我们可以清楚地看到,实际数字是 9(中间的大图像),但它被归类为数字 3(位于左上方的红色小图像)。像这种可视化的线索可以节省时间和金钱,因为我们至少可以推测为什么训练认为数字是 3,而实际上是 9。也许训练不知何故看到了左上角开口关闭的图像,这将使它看起来像一个 9。

价值和成本

数字识别是更复杂的图像识别项目(如人脸识别)的一个有价值的起点。我们从图像识别中学到的东西直接有助于人脸识别学习练习。

我们证明了我们的训练对新数据提供了超过 97%的准确率。也就是说,我们知道 100 次预测中至少有 97 次是正确的。因此,我们可以放心地使用我们在其他机器学习项目中所学到的东西。

我们也能够评估字面上的成本。我们的培训成本是 2.68%。也就是说,在 100 次预测中,我们不正确的预测不到 3 次。

最后,借助混淆矩阵和可视化,我们可以轻松确定潜在的改进和成本节约领域。混乱矩阵向我们展示了训练失败的地方。可视化给出了为什么训练可能做出不正确预测的线索。虽然我们只展示了一个可视化,但我们可以为进一步的研究制作所有不正确预测的可视化。

高效定位潜在问题领域的能力节省了机器学习专家的时间和金钱,因为他们至少有集中精力的教育线索。没有这样的线索,他们将在黑暗中进行实验。

具有图像识别的训练练习也提供了潜在的竞争优势。我们可以使用这样的练习来进行更复杂的图像识别实验,如人脸和文本识别。图像识别提供了人工识别人们正在交流什么以及他们如何出现的基础。由于进入机器学习的热潮令人难以置信,我们可以从这个例子中了解其潜在的好处和成本。

最后,像 MNIST 这样的图像数据有力地证明了为什么机器学习专家需要原始形式的数据。(任何类型的)图像数据都不适合存储在关系系统或遗留系统中,也不能从这些系统中访问。机器学习专家需要在其他人处理和稀释原始形式的数据之前,在数据进入组织时获得这些数据。

当然,向有钱人展示计算开销要困难得多。然而,这个极其简单的例子仅仅运行一个已经调好的算法就要花费一分多钟。因此,当我们创建更复杂、计算成本更高的例子时,我们可以向有钱人展示更直接的好处,比如准确破译书面数字的能力。

fetch_lfw_people 价值和成本

我们继续之前调优的 fetch_lfw_people 代码示例,如清单 8-2 所示。我们加载数据,运行机器学习算法,并显示结果。然后,我们从商业价值和成本的角度解释结果。

import numpy as np
from random import randint
from sklearn.decomposition import PCA
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.metrics import classification_report
import matplotlib.pyplot as plt
import seaborn as sns

def find_misses(test, pred):
    return [i for i, row in enumerate(test) if row != pred[i]]

def find_hit(n, ls):
    return True if n in ls else False

def build_fig(indx, pos, color, one, two):
    X_i = np.array(X_test[indx]).reshape(50, 37)
    t = targets[y_test[indx]]
    p = targets[y_pred[indx]]
    ax = fig.add_subplot(pos)
    image = ax.imshow(X_i,  cmap='bone')
    ax.set_axis_off()
    ax.set_title(t)
    ax.text(one, two, p, color=color,
            bbox=dict(facecolor='white'))

def chk_acc(rnds):
    logic = [1 if y_test[row] == y_pred[row] else 0
             for row in rnds]
    colors = ['g' if row == 1 else 'r' for row in logic]
    return colors

if __name__ == "__main__":
    br = '\n'

    X = np.load('data/X_faces.npy')
    y = np.load('data/y_faces.npy')
    bp = np.load('data/bp_face.npy')
    bp = bp.tolist()
    images = np.load('data/faces_images.npy')
    targets = np.load('data/faces_targets.npy')
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, random_state=0)
    pca = PCA(n_components=0.95, whiten=True, random_state=0)
    pca.fit(X_train)
    X_train_pca = pca.transform(X_train)
    X_test_pca = pca.transform(X_test)
    svm = SVC(**bp)
    svm.fit(X_train_pca, y_train)
    y_pred = svm.predict(X_test_pca)
    print ()
    cr = classification_report(y_test, y_pred)
    print (cr)
    misses = find_misses(y_test, y_pred)
    miss = misses[0]
    hit = 1
    X_hit = np.array(X_test[hit]).reshape(50, 37)
    y_test_hit = targets[y_test[hit]]
    y_pred_hit = targets[y_pred[hit]]
    X_miss = np.array(X_test[miss]).reshape(50, 37)
    y_test_miss = targets[y_test[miss]]
    y_pred_miss = targets[y_pred[miss]]
    fig = plt.figure('1st Hit and Miss')
    fig.suptitle('Visualize 1st Hit and Miss',
                 fontsize=18, fontweight="bold")
    build_fig(hit, 121, 'g', 0.4, 1.9)
    build_fig(miss, 122, 'r', 0.4, 1.9)
    rnd_ints = [randint(0, y_test.shape[0]-1)
                for row in range(4)]
    colors = chk_acc(rnd_ints)
    fig = plt.figure('Four Random Predictions')
    build_fig(rnd_ints[0], 221, colors[0], .9, 4.45)
    build_fig(rnd_ints[1], 222, colors[1], .9, 4.45)
    build_fig(rnd_ints[2], 223, colors[2], .9, 4.45)
    build_fig(rnd_ints[3], 224, colors[3], .9, 4.45)
    plt.tight_layout()
    plt.show()

Listing 8-2fetch_lfw_people value and cost

执行清单 8-2 的输出应该如下所示:

              precision    recall  f1-score   support

           0       1.00      0.64      0.78        28
           1       0.76      0.92      0.83        63
           2       0.91      0.88      0.89        24
           3       0.88      0.92      0.90       132
           4       0.74      0.85      0.79        20
           5       1.00      0.64      0.78        22
           6       0.90      0.85      0.88        33

   micro avg       0.86      0.86      0.86       322
   macro avg       0.89      0.81      0.84       322
weighted avg       0.87      0.86      0.86       322

列表 8-2 还显示了数字 8-3 和 8-4 。图 8-3 显示了第一个正确预测和第一个错误预测的可视化。图 8-4 显示了四个随机预测的可视化。

img/481580_1_En_8_Fig4_HTML.jpg

图 8-4

四个随机预测

img/481580_1_En_8_Fig3_HTML.jpg

图 8-3

首次正确和首次错误预测

向投资者解释投资人

该代码示例显示了我们从 fetch_lfw_people 数据集的训练中学习的程度。fetch_lfw_people 数据集是名人的 JPEG 图像集合。数据集中的每个元素由图像和图像所代表的人组成。

向投资者解释产出

测试数据(或新数据)的训练性能为 87%。该值显示在左侧精度栏的底部。所以,我们有信心 87%的预测是正确的。但是,这也意味着我们有 13%的时间是不正确的。当然,这个例子是非常简单和便宜的。随着面部识别技术的不断进步,学习精度接近完美。然而,与捕获图像、提取样本以创建模板、将收集的数据与现有模板进行比较以及将收集的数据与工业水平的模板进行匹配相关联的成本可能是昂贵的。

虽然面部识别是一个非常复杂的话题,但可视化使我们很容易看到我们从数据中学到了什么。我们只显示了四个随机预测,但我们可以显示更多的预测以供进一步分析。

向金融人士解释可视化

图 8-3 和图 8-4 中显示的两种可视化表示我们根据从数据中了解到的情况做出的预测。图 8-3 显示一个正确的预测(绿色中的名字嵌入图中)和一个错误的预测(红色中的名字嵌入图中)。图 8-4 显示了四个随机预测。请注意,我们做出了三个正确的预测,只有一个错误的预测。也就是说,我们正确地预测了科林·鲍威尔、乔治·w·布什和阿里尔·沙龙,而我们错误地预测了乌戈·查韦斯是乔治·w·布什。

价值和成本

面部识别是发展最快的生物识别技术,其唯一目的是识别人脸。苹果的 iPhone X 已经在使用出色的面部识别技术来解锁智能手机。

面部识别至关重要的一个相关领域是安全性。组织可以通过跟踪安全区域中的员工和访客移动,使用这种技术来保护他们的场所。

另一个领域是与现有软件的集成。目前的面部识别技术工具与现有的安全软件配合良好。这种简单的集成对企业来说非常重要,因为组织不需要花费额外的资金和时间来重新开发自己的系统以使用面部识别技术。

随着 3D 面部识别技术和红外摄像机的出现,当前面部识别技术的准确性比以往任何时候都高。当然,准确性是极其重要的,因为错误的识别可能是有害的。

面部识别系统可以完全自动化。因此,组织不需要员工监控摄像头。

最后,时间欺诈可以大幅减少。由于每个人都必须通过面部扫描设备来登记(或检查)工作或参观场所,所以组织不必担心安全人员的友情。此外,由于技术控制了入住或退房流程,这一过程要快得多。如果出现问题,技术还可以记录活动。

但是,根据需要收集和处理的数据量,数据捕获和处理的成本可能会很高。初始和持续的技术成本也可能很高。不要忘记,组织仍然需要有能力的人来监控、修复、更新和升级技术。想法是将成本从手工流程转移到自动化流程。从短期来看,成本可能会很高,但从长期来看,自动化应该会显著降低成本,提高流程效率,并减少人为错误。

图像质量也是一个问题。例如,组织可以存储不同质量级别的图像。高质量的图像需要更多的存储空间,但在实时匹配实际人脸时会更好。当然,更多的存储是昂贵的。

监视角度也是一个问题。捕捉新图像必须从不同的角度进行处理,因为可以从不同的角度实时看到真实的人脸。戴墨镜、不刮胡子、散发不同的面部表情,甚至是不同的发型也会让人看起来与众不同。

最好的技术会考虑这些问题。当然,最好的技术也是要花钱的。然而,计算机能做的事,人做不到。一个例子是同时将人脸与数千人的数据库进行比较。

随着面部识别选项变得更具成本竞争力,组织可能别无选择,只能参与这项技术。我们已经讨论了面部识别的价值和成本。因此,自动化带来的成本节约、更高的准确性和更少的欺诈提供了不可忽视的竞争优势。

fetch _ 新闻组值和开销

我们以之前调优的 fetch_20newsgroups 代码示例结束,如清单 8-3 所示。我们采用了一个更现实的场景,从我们训练的文档子集中删除了页眉、页脚和引号。

我们这样做的原因是为了消除关于文档主题的线索,以确保机器学习算法可以正确地从不容易识别的文本中识别含义。请记住,页眉、页脚和引号通常不会出现在大多数书面文件和书面交流中。

我们加载数据,运行机器学习算法,并显示结果。然后,我们从商业价值和成本的角度解释结果。

import numpy as np
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import make_pipeline
from sklearn.metrics import f1_score, confusion_matrix
import matplotlib.pyplot as plt
import seaborn as sns

def predict_category(s, m, t):
    pred = m.predict([s])
    return t[pred[0]]

if __name__ == "__main__":
    br = '\n'
    train = fetch_20newsgroups(subset='train')
    test = fetch_20newsgroups(subset='test')
    categories = ['rec.autos', 'rec.motorcycles', 'sci.space', 'sci.med']
    train = fetch_20newsgroups(subset='train', categories=categories, remove=('headers', 'footers', 'quotes'))
    test = fetch_20newsgroups(subset='test', categories=categories, remove=('headers', 'footers', 'quotes'))
    targets = train.target_names
    print ('targets:')
    print (targets, br)
    bp = np.load('data/bp_news.npy')
    bp = bp.tolist()
    print ('best parameters:')
    print (bp, br)
    mnb = MultinomialNB(alpha=0.01, fit_prior=False)
    tf = TfidfVectorizer(ngram_range=(1, 1), use_idf=False)
    pipe = make_pipeline(tf, mnb)
    pipe.fit(train.data, train.target)
    labels = pipe.predict(test.data)
    f1 = f1_score(test.target, labels, average="micro")
    print ('f1_score', f1, br)
    labels = pipe.predict(test.data)
    cm = confusion_matrix(test.target, labels)
    plt.figure('confusion matrix')
    sns.heatmap(cm.T, square=True, annot=True, fmt="d", xticklabels=train.target_names, yticklabels=train.target_names, cbar=False)
    plt.xlabel('true label')
    plt.ylabel('predicted label')
    plt.tight_layout()
    print ('***PREDICTIONS***:')
    doc1 = 'imagine the stars ...'
    doc2 = 'crashed on highway without seatbelt

'
    doc3 = 'dad hated the medicine ...'
    y_pred = predict_category(doc1, pipe, targets)
    print (y_pred)
    y_pred = predict_category(doc2, pipe, targets)
    print (y_pred)
    y_pred = predict_category(doc3, pipe, targets)
    print (y_pred)
    plt.show()

Listing 8-3fetch_20newsgroups value and cost

执行清单 8-3 的输出应该如下所示:

targets:
['rec.autos', 'rec.motorcycles', 'sci.med', 'sci.space']

best parameters:
{'tfidfvectorizer__use_idf': False, 'tfidfvectorizer__ngram_range': (1, 1), 'multinomialnb__fit_prior': False, 'multinomialnb__alpha': 0.01}

f1_score 0.8680555555555556

***PREDICTIONS***:
sci.space
rec.autos
sci.med

列表 8-3 也显示图 8-5 。图 8-5 显示了混淆矩阵。

img/481580_1_En_8_Fig5_HTML.jpg

图 8-5

混淆矩阵

向金融人士解释 fetch _ 新闻组

该代码示例演示了我们从 fetch_20newsgroups 数据集的训练中学到了什么。fetch_20newsgroups 数据集是 20 个主题的 18000 篇新闻组帖子的集合。对于这个训练示例,我们将数据集过滤为四个子集——汽车、摩托车、空间和医药——以简化和减少计算开销。

向投资者解释产出

我们首先显示从中学习的子集。接下来,我们显示最佳参数,以便机器学习专家可以适当地构建提供最佳性能的算法。

测试数据(或新数据)上的训练性能几乎是 87%。该值显示为 f1_score。因此,我们有信心几乎 87%的预测是正确的,但这也意味着我们有超过 13%的时间是不正确的。

最后,我们从三个简单的文档中进行预测。请注意,作为人类,我们可以很容易地预测*想象中的星星……*属于哪一类,但学习实验并不像我们一样拥有如此丰富的词汇知识。它与复杂的文档操作技术和机器学习算法一起工作,产生相当好的结果。

向投资者解释混淆矩阵

因为我们已经在第一个代码示例中解释了什么是混淆矩阵以及如何分析它,所以我们在这里不再重复。然而,它确实展示了一些有趣的见解。

注意第一行第二列,我们做了 47 个错误的预测。在这种情况下,我们错误地预测了汽车,而实际值是摩托车。这有一定的意义,因为学习算法在区分两种类型的车辆方面比其他不正确的预测更困难。

相反,当实际值是汽车时,我们错误地预测了 34 次摩托车。我们再次看到,学习算法很难区分这两种类型的车辆,所以大多数不正确的预测是在试图区分两种类型的车辆时做出的。

价值和成本

从文本中提取意义通常被称为文本挖掘。文本挖掘是从文本中获取高质量信息的一种手段。高质量的信息没有价值,除非它能带来商业洞察力(或价值)。

由于组织收集的大多数数据从未被分析过,因此文本挖掘提供了一种高效且有望有效的分析方法。一个组织高效的收集和挖掘文本,并不代表它能创造价值。

至少可以在三个有影响力的领域创造价值。首先,它可以增强合规性和威胁检测。其次,它可以培养客户参与度。第三,它可以促进更好的决策。

通过提供早期欺诈检测(如洗钱),挖掘文本可以极大地影响合规性问题。组织还需要自动遵守策略和程序的方法。文本挖掘可以通过检测文本输入(如在线表单、电子邮件、文本和其他消息服务)中的不符合性来自动化此类过程。对安全的威胁也可以通过挖掘进出组织的消息来检测。

通过法规遵从性和威胁检测服务的自动化,成本肯定会降低,因为管理此类程序所需的人员减少了。如果涉及的人员较少,人为错误也会减少。

与客户的互动为文本挖掘提供了不可思议的机会。例如,亚马逊分析用户偏好,以便更好地告知客户他们可能想要购买的产品。

文本挖掘是了解客户想法的自然工具。也许顾客对目前的服务不满意。也许客户想要目前没有提供的产品或服务。一旦一个组织想出了如何从客户那里获得洞察力,它就可以自动化这样的过程。自动化节省了时间和金钱,减少了人为错误。

如果一个组织能够洞察客户的想法,价值就被创造出来了。如果一个组织能够自动化这种洞察力,竞争优势就产生了。想象一下,如果你的竞争对手不参与文本挖掘。即使他们参与文本挖掘,您的组织的竞争优势是更高效和有效地管理客户洞察的能力。

最后,文本挖掘可以带来更好的商业决策。数据分析师和经理需要数据来为业务提供准确的见解。文本挖掘为获得准确的洞察力提供了一个强大的工具。

当然,启动、实现、管理和监控文本挖掘活动是有成本的。首先,您需要胜任的文本挖掘人员、足够的计算资源、利用文本挖掘创建业务洞察力的顶级支持,以及组建由技术和业务成员混合组成的团队的能力。

文本挖掘专家擅长实现算法,但在定义组织正在寻求的适当业务洞察力方面需要帮助。因此,非常需要一个由来自组织不同方面的各种团队成员组成的多样化团队。只有当你的组织比你的竞争对手更擅长文本挖掘时,你才能获得竞争优势。