Scikit-Learn 教程:如何安装与 Scikit-Learn 示例

什么是 Scikit-learn?

Scikit-learn 是一个用于机器学习的开源 Python 库。它支持最先进的算法,如 KNN、XGBoost、随机森林和 SVM。它构建在 NumPy 之上。Scikit-learn 被广泛用于 Kaggle 竞赛以及知名科技公司。它有助于数据预处理、降维(参数选择)、分类、回归、聚类和模型选择。

Scikit-learn 拥有所有开源库中最棒的文档。它在 https://scikit-learn.cn/stable/tutorial/machine_learning_map/index.html 提供交互式图表。

How Scikit Learn Works
Scikit Learn 如何工作

Scikit-learn 并不难用,并且能提供出色的结果。然而,scikit learn 不支持并行计算。虽然可以用它来运行深度学习算法,但这并非最优解决方案,尤其是当您知道如何使用 TensorFlow 时。

如何下载和安装 Scikit-learn

现在,在本 Python Scikit-learn 教程中,我们将学习如何下载和安装 Scikit-learn。

选项 1: AWS

scikit-learn 可在 AWS 上使用。请 参考 预装了 scikit-learn 的 Docker 镜像。

要使用开发版本,请在 Jupyter 中使用以下命令:

import sys
!{sys.executable} -m pip install git+git://github.com/scikit-learn/scikit-learn.git

选项 2: 使用 Anaconda 的 Mac 或 Windows

有关 Anaconda 安装,请参阅 https://guru99.com.cn/download-install-tensorflow.html

最近,scikit 的开发者发布了一个开发版本,解决了当前版本常见的用户问题。我们发现使用开发版本比当前版本更方便。

如何使用 Conda 环境安装 scikit-learn

如果您使用 conda 环境安装了 scikit-learn,请按照步骤更新到版本 0.20。

步骤 1) 激活 tensorflow 环境

source activate hello-tf

步骤 2) 使用 conda 命令删除 scikit lean

conda remove scikit-learn

步骤 3) 安装开发版本。
安装 scikit learn 开发版本以及必要的库。

conda install -c anaconda git
pip install Cython
pip install h5py
pip install git+git://github.com/scikit-learn/scikit-learn.git

注意:Windows 用户需要安装 Microsoft Visual C++ 14。您可以从 此处 获取。

Scikit-Learn 机器学习示例

本 Scikit 教程分为两部分:

  1. 使用 scikit-learn 进行机器学习
  2. 使用 LIME 信任您的模型

第一部分详细介绍了如何构建管道、创建模型和调整超参数;第二部分则提供了模型选择方面的最先进技术。

步骤 1) 导入数据

在本 Scikit learn 教程中,您将使用 adult 数据集。

有关此数据集的背景信息,请参考。如果您有兴趣了解更多描述性统计信息,请使用 Dive and Overview 工具。

请参阅 本教程 了解更多关于 Dive and Overview 的信息。

您可以使用 Pandas 导入数据集。请注意,您需要将连续变量的类型转换为 float 格式。

此数据集包含八个分类变量。

分类变量列在 CATE_FEATURES 中。

  • workclass
  • education
  • marital
  • occupation
  • relationship
  • race
  • sex
  • native_country

此外,还有六个连续变量。

连续变量列在 CONTI_FEATURES 中。

  • age
  • fnlwgt
  • education_num
  • capital_gain
  • capital_loss
  • hours_week

请注意,我们手动填充列表是为了让您更好地了解我们正在使用哪些列。构造分类或连续变量列表的更快方法是使用

## List Categorical
CATE_FEATURES = df_train.iloc[:,:-1].select_dtypes('object').columns
print(CATE_FEATURES)

## List continuous
CONTI_FEATURES =  df_train._get_numeric_data()
print(CONTI_FEATURES)

以下是导入数据的代码:

# Import dataset
import pandas as pd

## Define path data
COLUMNS = ['age','workclass', 'fnlwgt', 'education', 'education_num', 'marital',
           'occupation', 'relationship', 'race', 'sex', 'capital_gain', 'capital_loss',
           'hours_week', 'native_country', 'label']
### Define continuous list
CONTI_FEATURES  = ['age', 'fnlwgt','capital_gain', 'education_num', 'capital_loss', 'hours_week']
### Define categorical list
CATE_FEATURES = ['workclass', 'education', 'marital', 'occupation', 'relationship', 'race', 'sex', 'native_country']

## Prepare the data
features = ['age','workclass', 'fnlwgt', 'education', 'education_num', 'marital',
           'occupation', 'relationship', 'race', 'sex', 'capital_gain', 'capital_loss',
           'hours_week', 'native_country']

PATH = "https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.data"

df_train = pd.read_csv(PATH, skipinitialspace=True, names = COLUMNS, index_col=False)
df_train[CONTI_FEATURES] =df_train[CONTI_FEATURES].astype('float64')
df_train.describe()
age fnlwgt education_num capital_gain capital_loss hours_week
计数 32561.000000 3.256100e+04 32561.000000 32561.000000 32561.000000 32561.000000
平均值 38.581647 1.897784e+05 10.080679 1077.648844 87.303830 40.437456
std 13.640433 1.055500e+05 2.572720 7385.292085 402.960219 12.347429
min 17.000000 1.228500e+04 1.000000 0.000000 0.000000 1.000000
25% 28.000000 1.178270e+05 9.000000 0.000000 0.000000 40.000000
50% 37.000000 1.783560e+05 10.000000 0.000000 0.000000 40.000000
75% 48.000000 2.370510e+05 12.000000 0.000000 0.000000 45.000000
max 90.000000 1.484705e+06 16.000000 99999.000000 4356.000000 99.000000

您可以检查 native_country 特征的唯一值计数。您可以看到只有一个家庭来自 Holand-Netherlands。这个家庭不会给我们带来任何信息,但会在训练过程中引发错误。

df_train.native_country.value_counts()
United-States                 29170
Mexico                          643
?                               583
Philippines                     198
Germany                         137
Canada                          121
Puerto-Rico                     114
El-Salvador                     106
India                           100
Cuba                             95
England                          90
Jamaica                          81
South                            80
China                            75
Italy                            73
Dominican-Republic               70
Vietnam                          67
Guatemala                        64
Japan                            62
Poland                           60
Columbia                         59
Taiwan                           51
Haiti                            44
Iran                             43
Portugal                         37
Nicaragua                        34
Peru                             31
France                           29
Greece                           29
Ecuador                          28
Ireland                          24
Hong                             20
Cambodia                         19
Trinadad&Tobago                  19
Thailand                         18
Laos                             18
Yugoslavia                       16
Outlying-US(Guam-USVI-etc)       14
Honduras                         13
Hungary                          13
Scotland                         12
Holand-Netherlands                1
Name: native_country, dtype: int64

您可以排除此无信息行。

## Drop Netherland, because only one row
df_train = df_train[df_train.native_country != "Holand-Netherlands"]

接下来,您将连续特征的位置存储在一个列表中。您将在下一步中使用它来构建管道。

下面的代码将遍历 CONTI_FEATURES 中的所有列名,获取其位置(即其编号),然后将其附加到一个名为 conti_features 的列表中。

## Get the column index of the categorical features
conti_features = []
for i in CONTI_FEATURES:
    position = df_train.columns.get_loc(i)
    conti_features.append(position)
print(conti_features)  
[0, 2, 10, 4, 11, 12]

下面的代码执行与上面相同的工作,但针对分类变量。下面的代码重复了您之前所做的,只是使用了分类特征。

## Get the column index of the categorical features
categorical_features = []
for i in CATE_FEATURES:
    position = df_train.columns.get_loc(i)
    categorical_features.append(position)
print(categorical_features)  
[1, 3, 5, 6, 7, 8, 9, 13]

您可以查看数据集。请注意,每个分类特征都是一个字符串。您不能将字符串值输入模型。您需要使用虚拟变量转换数据集。

df_train.head(5)

事实上,您需要为特征中的每个组创建一个列。首先,您可以运行下面的代码来计算所需的总列数。

print(df_train[CATE_FEATURES].nunique(),
      'There are',sum(df_train[CATE_FEATURES].nunique()), 'groups in the whole dataset')
workclass          9
education         16
marital            7
occupation        15
relationship       6
race               5
sex                2
native_country    41
dtype: int64 There are 101 groups in the whole dataset

如上所示,整个数据集包含 101 个组。例如,workclass 特征有九个组。您可以使用以下代码可视化组名:

unique() 返回分类特征的唯一值。

for i in CATE_FEATURES:
    print(df_train[i].unique())
['State-gov' 'Self-emp-not-inc' 'Private' 'Federal-gov' 'Local-gov' '?'
 'Self-emp-inc' 'Without-pay' 'Never-worked']
['Bachelors' 'HS-grad' '11th' 'Masters' '9th' 'Some-college' 'Assoc-acdm'
 'Assoc-voc' '7th-8th' 'Doctorate' 'Prof-school' '5th-6th' '10th'
 '1st-4th' 'Preschool' '12th']
['Never-married' 'Married-civ-spouse' 'Divorced' 'Married-spouse-absent'
 'Separated' 'Married-AF-spouse' 'Widowed']
['Adm-clerical' 'Exec-managerial' 'Handlers-cleaners' 'Prof-specialty'
 'Other-service' 'Sales' 'Craft-repair' 'Transport-moving'
 'Farming-fishing' 'Machine-op-inspct' 'Tech-support' '?'
 'Protective-serv' 'Armed-Forces' 'Priv-house-serv']
['Not-in-family' 'Husband' 'Wife' 'Own-child' 'Unmarried' 'Other-relative']
['White' 'Black' 'Asian-Pac-Islander' 'Amer-Indian-Eskimo' 'Other']
['Male' 'Female']
['United-States' 'Cuba' 'Jamaica' 'India' '?' 'Mexico' 'South'
 'Puerto-Rico' 'Honduras' 'England' 'Canada' 'Germany' 'Iran'
 'Philippines' 'Italy' 'Poland' 'Columbia' 'Cambodia' 'Thailand' 'Ecuador'
 'Laos' 'Taiwan' 'Haiti' 'Portugal' 'Dominican-Republic' 'El-Salvador'
 'France' 'Guatemala' 'China' 'Japan' 'Yugoslavia' 'Peru'
 'Outlying-US(Guam-USVI-etc)' 'Scotland' 'Trinadad&Tobago' 'Greece'
 'Nicaragua' 'Vietnam' 'Hong' 'Ireland' 'Hungary']

因此,训练数据集将包含 101 + 7 列。最后七列是连续特征。

Scikit-learn 可以处理转换。这分两步完成:

  • 首先,您需要将字符串转换为 ID。例如,State-gov 将具有 ID 1,Self-emp-not-inc ID 2,依此类推。LabelEncoder 函数会为您完成此操作。
  • 将每个 ID 转换为新列。如前所述,该数据集有 101 个组 ID。因此,将有 101 列用于捕获所有分类特征的组。Scikit-learn 有一个名为 OneHotEncoder 的函数来执行此操作。

步骤 2)创建训练/测试集

现在数据集准备好了,我们可以将其拆分为 80/20。

80% 用于训练集,20% 用于测试集。

您可以使用 train_test_split。第一个参数是特征的 DataFrame,第二个参数是标签的 DataFrame。您可以使用 test_size 指定测试集的大小。

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(df_train[features],
                                                    df_train.label,
                                                    test_size = 0.2,
                                                    random_state=0)
X_train.head(5)
print(X_train.shape, X_test.shape)
(26048, 14) (6512, 14)

步骤 3)构建管道

管道使输入模型的数据更加一致。

其思想是将原始数据放入“管道”中以执行操作。

例如,对于当前数据集,您需要标准化连续变量并将分类数据进行转换。请注意,您可以在管道内执行任何操作。例如,如果数据集中有“NA”,您可以将其替换为均值或中位数。您还可以创建新变量。

您可以选择:硬编码这两个过程,或创建一个管道。第一种选择可能导致数据泄露并随时间产生不一致。更好的选择是使用管道。

from sklearn.preprocessing import StandardScaler, OneHotEncoder, LabelEncoder
from sklearn.compose import ColumnTransformer, make_column_transformer
from sklearn.pipeline import make_pipeline
from sklearn.linear_model import LogisticRegression

管道将在输入逻辑分类器之前执行两个操作:

  1. 标准化变量:`StandardScaler()`
  2. 转换分类特征:OneHotEncoder(sparse=False)

您可以使用 make_column_transformer 执行这两个步骤。此函数在 scikit-learn 的当前版本 (0.19) 中不可用。使用当前版本无法在管道中执行标签编码器和独热编码器。这是我们决定使用开发版本的原因之一。

make_column_transformer 易于使用。您需要定义要应用转换的列以及要执行的转换。例如,要标准化连续特征,您可以这样做:

  • conti_features、StandardScaler() 在 make_column_transformer 中。
    • conti_features:包含连续变量的列表
    • StandardScaler:标准化变量

make_column_transformer 中的 OneHotEncoder 对象会自动编码标签。

preprocess = make_column_transformer(
    (conti_features, StandardScaler()),
    ### Need to be numeric not string to specify columns name 
    (categorical_features, OneHotEncoder(sparse=False))
)

您可以使用 fit_transform 测试管道是否正常工作。数据集的形状应为:26048, 107

preprocess.fit_transform(X_train).shape
(26048, 107)

数据转换器已准备就绪。您可以使用 make_pipeline 创建管道。一旦数据被转换,您就可以输入逻辑回归。

model = make_pipeline(
    preprocess,
    LogisticRegression())

使用 scikit-learn 训练模型非常简单。您需要使用 fit 对象,前面加上管道,即 model。您可以使用 scikit-learn 库中的 score 对象打印准确率。

model.fit(X_train, y_train)
print("logistic regression score: %f" % model.score(X_test, y_test))
logistic regression score: 0.850891

最后,您可以使用 predict_proba 预测类别。它返回每个类别的概率。请注意,它们的总和为一。

model.predict_proba(X_test)
array([[0.83576663, 0.16423337],
       [0.94582765, 0.05417235],
       [0.64760587, 0.35239413],
       ...,
       [0.99639252, 0.00360748],
       [0.02072181, 0.97927819],
       [0.56781353, 0.43218647]])

步骤 4)在网格搜索中使用我们的管道

调整超参数(决定网络结构如隐藏单元的变量)可能非常繁琐且耗时。

评估模型的一种方法是更改训练集的大小并评估性能。

您可以重复此方法十次以查看分数指标。然而,这太费力了。

相反,scikit-learn 提供了一个函数来执行参数调整和交叉验证。

交叉验证

交叉验证意味着在训练期间,训练集会被分成 n 个折叠(folds),然后对模型进行 n 次评估。例如,如果 cv 设置为 10,则训练集将训练和评估十次。在每次迭代中,分类器随机选择九个折叠来训练模型,第十个折叠用于评估。

网格搜索

每个分类器都有需要调整的超参数。您可以尝试不同的值,或者设置一个参数网格。如果您访问 scikit-learn 官方网站,可以看到逻辑分类器有不同的参数可以调整。为了加快训练速度,我们选择调整 C 参数。它控制正则化参数。它应该是正数。较小的值会使正则化器获得更高的权重。

您可以使用 GridSearchCV 对象。您需要创建一个包含要调整的超参数的字典。

您列出超参数,然后是您想要尝试的值。例如,要调整 C 参数,您可以使用:

  • ‘logisticregression__C’: [0.1, 1.0, 1.0]:参数前面是分类器名称(小写)和两个下划线。

模型将尝试四个不同的值:0.001、0.01、0.1 和 1。

您使用 10 折进行模型训练:cv=10。

from sklearn.model_selection import GridSearchCV
# Construct the parameter grid
param_grid = {
    'logisticregression__C': [0.001, 0.01,0.1, 1.0],
    }

您可以使用参数网格 gri 和 cv 通过 GridSearchCV 训练模型。

# Train the model
grid_clf = GridSearchCV(model,
                        param_grid,
                        cv=10,
                        iid=False)
grid_clf.fit(X_train, y_train)

输出

GridSearchCV(cv=10, error_score='raise-deprecating',
       estimator=Pipeline(memory=None,
     steps=[('columntransformer', ColumnTransformer(n_jobs=1, remainder='drop', transformer_weights=None,
         transformers=[('standardscaler', StandardScaler(copy=True, with_mean=True, with_std=True), [0, 2, 10, 4, 11, 12]), ('onehotencoder', OneHotEncoder(categorical_features=None, categories=None,...ty='l2', random_state=None, solver='liblinear', tol=0.0001,
          verbose=0, warm_start=False))]),
       fit_params=None, iid=False, n_jobs=1,
       param_grid={'logisticregression__C': [0.001, 0.01, 0.1, 1.0]},
       pre_dispatch='2*n_jobs', refit=True, return_train_score='warn',
       scoring=None, verbose=0)

要访问最佳参数,请使用 best_params_。

grid_clf.best_params_

输出

{'logisticregression__C': 1.0}

在用四种不同的正则化值训练模型后,最佳参数为:

print("best logistic regression from grid search: %f" % grid_clf.best_estimator_.score(X_test, y_test))

来自网格搜索的最佳逻辑回归:0.850891

要访问预测概率:

grid_clf.best_estimator_.predict_proba(X_test)
array([[0.83576677, 0.16423323],
       [0.9458291 , 0.0541709 ],
       [0.64760416, 0.35239584],
       ...,
       [0.99639224, 0.00360776],
       [0.02072033, 0.97927967],
       [0.56782222, 0.43217778]])

使用 scikit-learn 的 XGBoost 模型

让我们尝试 Scikit-learn 示例,在市场上最好的分类器之一上进行训练。XGBoost 是随机森林的改进。该分类器的理论背景超出了本 Python Scikit 教程的范围。请记住,XGBoost 赢得了许多 Kaggle 竞赛。对于中等大小的数据集,它的性能可以与深度学习算法相媲美,甚至更好。

该分类器训练起来很困难,因为它需要调整的参数很多。当然,您可以使用 GridSearchCV 来为您选择参数。

相反,让我们看看如何用更好的方法找到最佳参数。GridSearchCV 可能很繁琐,而且如果传递的值很多,训练起来会非常慢。搜索空间随着参数数量的增加而增长。一个更可取的解决方案是使用 RandomizedSearchCV。此方法包括在每次迭代后随机选择每个超参数的值。例如,如果分类器训练 1000 次迭代,则评估 1000 个组合。它的工作原理与 GridSearchCV 大致相同。

您需要导入 xgboost。如果库未安装,请使用 pip3 install xgboost 或

use import sys
!{sys.executable} -m pip install xgboost

在 Jupyter 环境中

下一步,

import xgboost
from sklearn.model_selection import RandomizedSearchCV
from sklearn.model_selection import StratifiedKFold

本 Scikit Python 教程的下一步包括指定要调整的参数。您可以参考官方文档查看所有要调整的参数。为了本 Python Sklearn 教程的方便,我们只选择两个超参数,每个超参数有两个值。XGBoost 需要很长时间来训练,网格中包含的超参数越多,您需要等待的时间就越长。

params = {
        'xgbclassifier__gamma': [0.5, 1],
        'xgbclassifier__max_depth': [3, 4]
        }

我们用 XGBoost 分类器构建了一个新的管道。我们选择定义 600 个估计器。请注意,n_estimators 是一个可以调整的参数。较高的值可能导致过拟合。您可以自行尝试不同的值,但请注意这可能需要数小时。我们使用其他参数的默认值。

model_xgb = make_pipeline(
    preprocess,
    xgboost.XGBClassifier(
                          n_estimators=600,
                          objective='binary:logistic',
                          silent=True,
                          nthread=1)
)

您可以使用 Stratified K-Folds 交叉验证器来改进交叉验证。这里我们只构建了三个折叠以加快计算速度,但降低了质量。在家中将此值增加到 5 或 10 以改进结果。

我们选择在四次迭代中训练模型。

skf = StratifiedKFold(n_splits=3,
                      shuffle = True,
                      random_state = 1001)

random_search = RandomizedSearchCV(model_xgb,
                                   param_distributions=params,
                                   n_iter=4,
                                   scoring='accuracy',
                                   n_jobs=4,
                                   cv=skf.split(X_train, y_train),
                                   verbose=3,
                                   random_state=1001)

随机搜索已准备就绪,您可以训练模型。

#grid_xgb = GridSearchCV(model_xgb, params, cv=10, iid=False)
random_search.fit(X_train, y_train)
Fitting 3 folds for each of 4 candidates, totalling 12 fits
[CV] xgbclassifier__max_depth=3, xgbclassifier__gamma=0.5 ............
[CV] xgbclassifier__max_depth=3, xgbclassifier__gamma=0.5 ............
[CV] xgbclassifier__max_depth=3, xgbclassifier__gamma=0.5 ............
[CV] xgbclassifier__max_depth=4, xgbclassifier__gamma=0.5 ............
[CV]  xgbclassifier__max_depth=3, xgbclassifier__gamma=0.5, score=0.8759645283888057, total= 1.0min
[CV] xgbclassifier__max_depth=4, xgbclassifier__gamma=0.5 ............
[CV]  xgbclassifier__max_depth=3, xgbclassifier__gamma=0.5, score=0.8729701715996775, total= 1.0min
[CV]  xgbclassifier__max_depth=3, xgbclassifier__gamma=0.5, score=0.8706519235199263, total= 1.0min
[CV] xgbclassifier__max_depth=4, xgbclassifier__gamma=0.5 ............
[CV] xgbclassifier__max_depth=3, xgbclassifier__gamma=1 ..............
[CV]  xgbclassifier__max_depth=4, xgbclassifier__gamma=0.5, score=0.8735460094437406, total= 1.3min
[CV] xgbclassifier__max_depth=3, xgbclassifier__gamma=1 ..............
[CV]  xgbclassifier__max_depth=3, xgbclassifier__gamma=1, score=0.8722791661868018, total=  57.7s
[CV] xgbclassifier__max_depth=3, xgbclassifier__gamma=1 ..............
[CV]  xgbclassifier__max_depth=3, xgbclassifier__gamma=1, score=0.8753886905447426, total= 1.0min
[CV] xgbclassifier__max_depth=4, xgbclassifier__gamma=1 ..............
[CV]  xgbclassifier__max_depth=4, xgbclassifier__gamma=0.5, score=0.8697304768486523, total= 1.3min
[CV] xgbclassifier__max_depth=4, xgbclassifier__gamma=1 ..............
[CV]  xgbclassifier__max_depth=4, xgbclassifier__gamma=0.5, score=0.8740066797189912, total= 1.4min
[CV] xgbclassifier__max_depth=4, xgbclassifier__gamma=1 ..............
[CV]  xgbclassifier__max_depth=3, xgbclassifier__gamma=1, score=0.8707671043538355, total= 1.0min
[CV]  xgbclassifier__max_depth=4, xgbclassifier__gamma=1, score=0.8729701715996775, total= 1.2min
[Parallel(n_jobs=4)]: Done  10 out of  12 | elapsed:  3.6min remaining:   43.5s
[CV]  xgbclassifier__max_depth=4, xgbclassifier__gamma=1, score=0.8736611770125533, total= 1.2min
[CV]  xgbclassifier__max_depth=4, xgbclassifier__gamma=1, score=0.8692697535130154, total= 1.2min
[Parallel(n_jobs=4)]: Done  12 out of  12 | elapsed:  3.6min finished
/Users/Thomas/anaconda3/envs/hello-tf/lib/python3.6/site-packages/sklearn/model_selection/_search.py:737: DeprecationWarning: The default of the `iid` parameter will change from True to False in version 0.22 and will be removed in 0.24. This will change numeric results when test-set sizes are unequal. DeprecationWarning)
RandomizedSearchCV(cv=<generator object _BaseKFold.split at 0x1101eb830>,
          error_score='raise-deprecating',
          estimator=Pipeline(memory=None,
     steps=[('columntransformer', ColumnTransformer(n_jobs=1, remainder='drop', transformer_weights=None,
         transformers=[('standardscaler', StandardScaler(copy=True, with_mean=True, with_std=True), [0, 2, 10, 4, 11, 12]), ('onehotencoder', OneHotEncoder(categorical_features=None, categories=None,...
       reg_alpha=0, reg_lambda=1, scale_pos_weight=1, seed=None,
       silent=True, subsample=1))]),
          fit_params=None, iid='warn', n_iter=4, n_jobs=4,
          param_distributions={'xgbclassifier__gamma': [0.5, 1], 'xgbclassifier__max_depth': [3, 4]},
          pre_dispatch='2*n_jobs', random_state=1001, refit=True,
          return_train_score='warn', scoring='accuracy', verbose=3)

正如您所见,XGBoost 的得分比之前的逻辑回归更好。

print("Best parameter", random_search.best_params_)
print("best logistic regression from grid search: %f" % random_search.best_estimator_.score(X_test, y_test))
Best parameter {'xgbclassifier__max_depth': 3, 'xgbclassifier__gamma': 0.5}
best logistic regression from grid search: 0.873157
random_search.best_estimator_.predict(X_test)
array(['<=50K', '<=50K', '<=50K', ..., '<=50K', '>50K', '<=50K'],      dtype=object)

在 scikit-learn 中使用 MLPClassifier 创建 DNN

最后,您可以使用 scikit-learn 训练深度学习算法。方法与其他分类器相同。该分类器可在 MLPClassifier 中找到。

from sklearn.neural_network import MLPClassifier

我们定义了以下深度学习算法:

  • Adam 优化器
  • Relu 激活函数
  • Alpha = 0.0001
  • 批量大小为 150
  • 两个隐藏层,分别有 100 和 50 个神经元。
model_dnn = make_pipeline(
    preprocess,
    MLPClassifier(solver='adam',
                  alpha=0.0001,
                  activation='relu',
                    batch_size=150,
                    hidden_layer_sizes=(200, 100),
                    random_state=1))

您可以更改层数以改进模型。

model_dnn.fit(X_train, y_train)
  print("DNN regression score: %f" % model_dnn.score(X_test, y_test))

DNN 回归得分:0.821253

LIME:信任您的模型

现在您有了一个好的模型,您需要一个工具来信任它。机器学习算法,尤其是随机森林和神经网络,众所周知是黑盒算法。换句话说,它能工作,但没人知道为什么。

三位研究人员提出了一种很棒的工具来查看计算机如何做出预测。论文名为“Why Should I Trust You?”

他们开发了一种名为局部可解释模型无关解释 (LIME) 的算法。

举个例子

有时您不确定是否可以信任机器学习预测。

例如,医生不能仅仅因为计算机说了就信任诊断。您还需要了解在将模型投入生产之前是否可以信任它。

想象一下,如果我们能理解任何分类器为何做出预测,即使是极其复杂的模型,如神经网络、随机森林或带有任何核的 SVM,

如果我们能理解预测背后的原因,那么它们将更容易被信任。以医生为例,如果模型告诉他哪些症状很重要,您就会信任它,也很容易弄清楚您是否不应该信任该模型。

Lime 可以告诉您哪些特征会影响分类器的决策。

数据准备

要使用 LIME 配合 Python 运行,您需要更改几件事。首先,您需要在终端中安装 lime。您可以使用 pip install lime。

Lime 使用 LimeTabularExplainer 对象来局部近似模型。此对象需要:

  • numpy 格式的数据集
  • 特征名称:feature_names
  • 类别名称:class_names
  • 分类特征的列索引:categorical_features
  • 每个分类特征的组名:categorical_names

创建 numpy 训练集

您可以非常轻松地将 pandas 的 df_train 复制并转换为 numpy

df_train.head(5)
# Create numpy data
df_lime = df_train
df_lime.head(3)

获取类别名称: 标签可以通过 unique() 对象访问。您应该看到:

  • ‘<=50K’
  • ‘>50K’
# Get the class name
class_names = df_lime.label.unique()
class_names
array(['<=50K', '>50K'], dtype=object)

分类特征的列索引

您可以使用之前学到的方法获取组名。您可以使用 LabelEncoder 对标签进行编码。您对所有分类特征重复此操作。

## 
import sklearn.preprocessing as preprocessing
categorical_names = {}
for feature in CATE_FEATURES:
    le = preprocessing.LabelEncoder()
    le.fit(df_lime[feature])
    df_lime[feature] = le.transform(df_lime[feature])
    categorical_names[feature] = le.classes_
print(categorical_names)    
{'workclass': array(['?', 'Federal-gov', 'Local-gov', 'Never-worked', 'Private',
       'Self-emp-inc', 'Self-emp-not-inc', 'State-gov', 'Without-pay'],
      dtype=object), 'education': array(['10th', '11th', '12th', '1st-4th', '5th-6th', '7th-8th', '9th',
       'Assoc-acdm', 'Assoc-voc', 'Bachelors', 'Doctorate', 'HS-grad',
       'Masters', 'Preschool', 'Prof-school', 'Some-college'],
      dtype=object), 'marital': array(['Divorced', 'Married-AF-spouse', 'Married-civ-spouse',
       'Married-spouse-absent', 'Never-married', 'Separated', 'Widowed'],
      dtype=object), 'occupation': array(['?', 'Adm-clerical', 'Armed-Forces', 'Craft-repair',
       'Exec-managerial', 'Farming-fishing', 'Handlers-cleaners',
       'Machine-op-inspct', 'Other-service', 'Priv-house-serv',
       'Prof-specialty', 'Protective-serv', 'Sales', 'Tech-support',
       'Transport-moving'], dtype=object), 'relationship': array(['Husband', 'Not-in-family', 'Other-relative', 'Own-child',
       'Unmarried', 'Wife'], dtype=object), 'race': array(['Amer-Indian-Eskimo', 'Asian-Pac-Islander', 'Black', 'Other',
       'White'], dtype=object), 'sex': array(['Female', 'Male'], dtype=object), 'native_country': array(['?', 'Cambodia', 'Canada', 'China', 'Columbia', 'Cuba',
       'Dominican-Republic', 'Ecuador', 'El-Salvador', 'England',
       'France', 'Germany', 'Greece', 'Guatemala', 'Haiti', 'Honduras',
       'Hong', 'Hungary', 'India', 'Iran', 'Ireland', 'Italy', 'Jamaica',
       'Japan', 'Laos', 'Mexico', 'Nicaragua',
       'Outlying-US(Guam-USVI-etc)', 'Peru', 'Philippines', 'Poland',
       'Portugal', 'Puerto-Rico', 'Scotland', 'South', 'Taiwan',
       'Thailand', 'Trinadad&Tobago', 'United-States', 'Vietnam',
       'Yugoslavia'], dtype=object)}

df_lime.dtypes
age               float64
workclass           int64
fnlwgt            float64
education           int64
education_num     float64
marital             int64
occupation          int64
relationship        int64
race                int64
sex                 int64
capital_gain      float64
capital_loss      float64
hours_week        float64
native_country      int64
label              object
dtype: object

现在数据集已准备好,您可以按照下面 Scikit learn 示例中的说明构建不同的数据集。实际上,您将在管道外部转换数据,以避免 LIME 出现错误。LimeTabularExplainer 中的训练集应该是没有字符串的 numpy 数组。通过上述方法,您已经得到了一个已转换的训练数据集。

from sklearn.model_selection import train_test_split
X_train_lime, X_test_lime, y_train_lime, y_test_lime = train_test_split(df_lime[features],
                                                    df_lime.label,
                                                    test_size = 0.2,
                                                    random_state=0)
X_train_lime.head(5)

我们可以使用 XGBoost 的最佳参数创建管道。

model_xgb = make_pipeline(
    preprocess,
    xgboost.XGBClassifier(max_depth = 3,
                          gamma = 0.5,
                          n_estimators=600,
                          objective='binary:logistic',
                          silent=True,
                          nthread=1))

model_xgb.fit(X_train_lime, y_train_lime)
/Users/Thomas/anaconda3/envs/hello-tf/lib/python3.6/site-packages/sklearn/preprocessing/_encoders.py:351: FutureWarning: The handling of integer data will change in version 0.22. Currently, the categories are determined based on the range [0, max(values)], while in the future they will be determined based on the unique values.
If you want the future behavior and silence this warning, you can specify "categories='auto'."In case you used a LabelEncoder before this OneHotEncoder to convert the categories to integers, then you can now use the OneHotEncoder directly.
  warnings.warn(msg, FutureWarning)
Pipeline(memory=None,
     steps=[('columntransformer', ColumnTransformer(n_jobs=1, remainder='drop', transformer_weights=None,
         transformers=[('standardscaler', StandardScaler(copy=True, with_mean=True, with_std=True), [0, 2, 10, 4, 11, 12]), ('onehotencoder', OneHotEncoder(categorical_features=None, categories=None,...
       reg_alpha=0, reg_lambda=1, scale_pos_weight=1, seed=None,
       silent=True, subsample=1))])

您会收到一个警告。警告解释说您不需要在管道之前创建标签编码器。如果您不想使用 LIME,可以使用机器学习 Scikit-learn 教程第一部分的方法。否则,您可以继续使用此方法,先创建编码后的数据集,然后在管道中获取独热编码器。

print("best logistic regression from grid search: %f" % model_xgb.score(X_test_lime, y_test_lime))
best logistic regression from grid search: 0.873157
model_xgb.predict_proba(X_test_lime)
array([[7.9646105e-01, 2.0353897e-01],
       [9.5173013e-01, 4.8269872e-02],
       [7.9344827e-01, 2.0655173e-01],
       ...,
       [9.9031430e-01, 9.6856682e-03],
       [6.4581633e-04, 9.9935418e-01],
       [9.7104281e-01, 2.8957171e-02]], dtype=float32)

在使用 LIME 之前,让我们创建一个带有错误分类特征的 numpy 数组。稍后您可以使用此列表来了解是什么误导了分类器。

temp = pd.concat([X_test_lime, y_test_lime], axis= 1)
temp['predicted'] = model_xgb.predict(X_test_lime)
temp['wrong']=  temp['label'] != temp['predicted']
temp = temp.query('wrong==True').drop('wrong', axis=1)
temp= temp.sort_values(by=['label'])
temp.shape

(826, 16)

我们创建一个 lambda 函数来从模型中检索新数据的预测。您很快就需要它。

predict_fn = lambda x: model_xgb.predict_proba(x).astype(float)
X_test_lime.dtypes
age               float64
workclass           int64
fnlwgt            float64
education           int64
education_num     float64
marital             int64
occupation          int64
relationship        int64
race                int64
sex                 int64
capital_gain      float64
capital_loss      float64
hours_week        float64
native_country      int64
dtype: object
predict_fn(X_test_lime)
array([[7.96461046e-01, 2.03538969e-01],
       [9.51730132e-01, 4.82698716e-02],
       [7.93448269e-01, 2.06551731e-01],
       ...,
       [9.90314305e-01, 9.68566816e-03],
       [6.45816326e-04, 9.99354184e-01],
       [9.71042812e-01, 2.89571714e-02]])

将 pandas DataFrame 转换为 numpy 数组。

X_train_lime = X_train_lime.values
X_test_lime = X_test_lime.values
X_test_lime
array([[4.00000e+01, 5.00000e+00, 1.93524e+05, ..., 0.00000e+00,
        4.00000e+01, 3.80000e+01],
       [2.70000e+01, 4.00000e+00, 2.16481e+05, ..., 0.00000e+00,
        4.00000e+01, 3.80000e+01],
       [2.50000e+01, 4.00000e+00, 2.56263e+05, ..., 0.00000e+00,
        4.00000e+01, 3.80000e+01],
       ...,
       [2.80000e+01, 6.00000e+00, 2.11032e+05, ..., 0.00000e+00,
        4.00000e+01, 2.50000e+01],
       [4.40000e+01, 4.00000e+00, 1.67005e+05, ..., 0.00000e+00,
        6.00000e+01, 3.80000e+01],
       [5.30000e+01, 4.00000e+00, 2.57940e+05, ..., 0.00000e+00,
        4.00000e+01, 3.80000e+01]])
model_xgb.predict_proba(X_test_lime)
array([[7.9646105e-01, 2.0353897e-01],
       [9.5173013e-01, 4.8269872e-02],
       [7.9344827e-01, 2.0655173e-01],
       ...,
       [9.9031430e-01, 9.6856682e-03],
       [6.4581633e-04, 9.9935418e-01],
       [9.7104281e-01, 2.8957171e-02]], dtype=float32)
print(features,
      class_names,
      categorical_features,
      categorical_names)
['age', 'workclass', 'fnlwgt', 'education', 'education_num', 'marital', 'occupation', 'relationship', 'race', 'sex', 'capital_gain', 'capital_loss', 'hours_week', 'native_country'] ['<=50K' '>50K'] [1, 3, 5, 6, 7, 8, 9, 13] {'workclass': array(['?', 'Federal-gov', 'Local-gov', 'Never-worked', 'Private',
       'Self-emp-inc', 'Self-emp-not-inc', 'State-gov', 'Without-pay'],
      dtype=object), 'education': array(['10th', '11th', '12th', '1st-4th', '5th-6th', '7th-8th', '9th',
       'Assoc-acdm', 'Assoc-voc', 'Bachelors', 'Doctorate', 'HS-grad',
       'Masters', 'Preschool', 'Prof-school', 'Some-college'],
      dtype=object), 'marital': array(['Divorced', 'Married-AF-spouse', 'Married-civ-spouse',
       'Married-spouse-absent', 'Never-married', 'Separated', 'Widowed'],
      dtype=object), 'occupation': array(['?', 'Adm-clerical', 'Armed-Forces', 'Craft-repair',
       'Exec-managerial', 'Farming-fishing', 'Handlers-cleaners',
       'Machine-op-inspct', 'Other-service', 'Priv-house-serv',
       'Prof-specialty', 'Protective-serv', 'Sales', 'Tech-support',
       'Transport-moving'], dtype=object), 'relationship': array(['Husband', 'Not-in-family', 'Other-relative', 'Own-child',
       'Unmarried', 'Wife'], dtype=object), 'race': array(['Amer-Indian-Eskimo', 'Asian-Pac-Islander', 'Black', 'Other',
       'White'], dtype=object), 'sex': array(['Female', 'Male'], dtype=object), 'native_country': array(['?', 'Cambodia', 'Canada', 'China', 'Columbia', 'Cuba',
       'Dominican-Republic', 'Ecuador', 'El-Salvador', 'England',
       'France', 'Germany', 'Greece', 'Guatemala', 'Haiti', 'Honduras',
       'Hong', 'Hungary', 'India', 'Iran', 'Ireland', 'Italy', 'Jamaica',
       'Japan', 'Laos', 'Mexico', 'Nicaragua',
       'Outlying-US(Guam-USVI-etc)', 'Peru', 'Philippines', 'Poland',
       'Portugal', 'Puerto-Rico', 'Scotland', 'South', 'Taiwan',
       'Thailand', 'Trinadad&Tobago', 'United-States', 'Vietnam',
       'Yugoslavia'], dtype=object)}
import lime
import lime.lime_tabular
### Train should be label encoded not one hot encoded
explainer = lime.lime_tabular.LimeTabularExplainer(X_train_lime ,
                                                   feature_names = features,
                                                   class_names=class_names,
                                                   categorical_features=categorical_features, 
                                                   categorical_names=categorical_names,
                                                   kernel_width=3)

让我们选择测试集中的一个随机家庭,看看模型的预测以及计算机是如何做出选择的。

import numpy as np
np.random.seed(1)
i = 100
print(y_test_lime.iloc[i])
>50K
X_test_lime[i]
array([4.20000e+01, 4.00000e+00, 1.76286e+05, 7.00000e+00, 1.20000e+01,
       2.00000e+00, 4.00000e+00, 0.00000e+00, 4.00000e+00, 1.00000e+00,
       0.00000e+00, 0.00000e+00, 4.00000e+01, 3.80000e+01])

您可以使用 explainer 的 explain_instance 来检查模型背后的解释。

exp = explainer.explain_instance(X_test_lime[i], predict_fn, num_features=6)
exp.show_in_notebook(show_all=False)

Data Preparation

我们可以看到分类器正确地预测了这个家庭。收入确实高于 50k。

我们可以说的第一件事是,分类器对预测概率不是那么确定。该模型以 64% 的概率预测该家庭的收入超过 50k。这个 64% 由资本收益和婚姻状况组成。蓝色表示对正类有负贡献,橙色线表示有正贡献。

分类器之所以感到困惑,是因为这个家庭的资本收益为零,而资本收益通常是财富的一个良好预测指标。此外,该家庭每周工作时间少于 40 小时。年龄、职业和性别对分类器有积极贡献。

如果婚姻状况是单身,分类器将预测收入低于 50k (0.64-0.18 = 0.46)。

我们可以尝试另一个被错误分类的家庭。

temp.head(3)
temp.iloc[1,:-2]
age                  58
workclass             4
fnlwgt            68624
education            11
education_num         9
marital               2
occupation            4
relationship          0
race                  4
sex                   1
capital_gain          0
capital_loss          0
hours_week           45
native_country       38
Name: 20931, dtype: object
i = 1
print('This observation is', temp.iloc[i,-2:])
This observation is label        <=50K
predicted     >50K
Name: 20931, dtype: object
exp = explainer.explain_instance(temp.iloc[1,:-2], predict_fn, num_features=6)
exp.show_in_notebook(show_all=False)

Data Preparation

分类器预测收入低于 50k,但实际上并非如此。这个家庭看起来很奇怪。它没有资本收益,也没有资本损失。他已离婚,60 岁,受过良好教育,即 education_num > 12。根据整体模式,这个家庭应该像分类器所解释的那样,收入低于 50k。

您尝试玩弄 LIME。您会注意到分类器存在一些明显的错误。

您可以查看该库所有者的 GitHub。他们为图像和文本分类提供了额外的文档。

摘要

以下是一些适用于 scikit learn 版本 >=0.20 的有用命令列表:

创建训练/测试数据集 训练集拆分
构建管道
选择列并应用转换 makecolumntransformer
转换类型
标准化 StandardScaler
最小-最大标准化 MinMaxScaler
归一化 Normalizer
填充缺失值 Imputer
转换分类变量 OneHotEncoder
拟合和转换数据 fit_transform
创建管道 make_pipeline
基本模型
逻辑回归 LogisticRegression
XGBoost XGBClassifier
神经网络 MLPClassifier
网格搜索 GridSearchCV
随机搜索 RandomizedSearchCV