NLP成长计划(三)

Setup

假设您已经完成了(一)和(二)所需的设置。

 

Train-Validation-Test Split

在开始在新数据集上拟合模型之前,您应该(并且尽量)将初始数据集划分为“训练集train“、“验证集validation”和“测试集test”。训练数据集为我们提供了一个让我们的模型学习的地方。验证数据集为我们提供了一种判断模型相对于其他潜在模型的性能的方法。测试数据集帮我们预估我们的模型推广到未被看见的数据的能力。

在实践中,我们将使用训练数据集来训练你所有的潜在模型。验证数据集将被传递给这些模型中的每一个,以判断这些模型中的每一个的性能,从而允许我们对模型进行比较。然后,一旦找到我们的最佳模型,我们最终通过测试数据集来判断该模型对未观测数据的性能,并且基于测试数据集的性能需要提供在你的学术论文或给老板的报告之中。

通常要保留20-50%的数据用于验证和测试集,并使用剩下的50-80%进行训练。

永远不要把你的数据分成第一个80%个和剩下20%个来验证和测试集。你应该尽可能地随机地分割数据。在训练集的选择中,几乎不包含非随机过程可以歪曲模型参数。数据经常以某种方式排序(按日期或甚至是你试图预测的值)。

有一种在Scikit中实现的方法,将数据集随机分割为我们称为train_test_split。我们可以使用这种方法两次来执行下面的列车验证测试分割。

        NLP成长计划(三)

from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split

# make the split reproducible
random_state = 42

# get some data
digits = load_digits()
X, y = digits.data, digits.target

# get our split percentages
validation_size = .25
test_size = .25
validation_test_size = validation_size + test_size
test_size_adjusted = test_size / validation_test_size

# perform the first split which gets us the train data and the validation/test data that
# we must split one more time
X_train, X_validation_test, y_train, y_validation_test =  train_test_split(X, y,\
                                                                           test_size = validation_test_size,\
                                                                           random_state = random_state)

# perform the second split which splits the validation/test data into two distinct datasets
X_validation, X_test, y_validation, y_test = train_test_split(X_validation_test, y_validation_test,\
                                                              test_size = test_size_adjusted,\
                                                              random_state = random_state)

 

 

Pipeline

在数据科学中,pipeline 是一系列与建模相关的任务。我们从一些初始输入开始,将其输入到第一建模任务中。第一建模任务的输出然后被馈送到下一个第二建模任务,等等,直到我们达到最终建模任务和输出。

在这个课程的背景下,我们使用一个管道与两个建模任务。最初的输入是一篇文章,我们想把它归类为假新闻。第一个建模任务是我们的文章,并嵌入它。第一模型的输出,嵌入,被馈送到最终的建模任务,分类器。最后我们的流水线输出,分类将指示初始输入,文章是否是假的。

当使用Scikit时,您可以使用其内置管道特性来使用Scikit模型构建管道。要看如何使用这个工具,你可以看看这个例子(http://scikit-learn.org/stable/auto_examples/model_selection/grid_search_text_feature_extraction.html#example-model-selection-grid-search-text-feature-extraction-py)。在这个例子中,文本特征提取器由使用随机梯度下降的线性分类器组成。

 

Hypertuning

在任何机器学习算法中,我们必须通过一些参数来初始化模型。对于任何模型,我们使用的超参数集合取决于我们试图训练的数据。为给定数据集找到模型的最优超参数集的过程称为“hypertuning”。hypertuning 的过程包括用不同的超参数集合训练多个模型,并使用一些度量或度量的顶点(即F1评分、精确度、回想等)来确定最优的超参数集合。我们基于模型使用产生用于验证和/或训练数据集的最佳总体度量的最优超参数集合来选择最优的超参数集合。

 

Grid Search

网格搜索通常用于当您不知道(并且通常不太关心)给定估计器或一组估计器的一组最佳参数的含义时。它们本质上是一组for循环,用于测试一系列参数,并为每种情况(因此是网格)构建单个模型。Scikits有一个网格搜索类(http://scikit-learn.org/stable/modules/grid_search.html#grid-search),它将自动对一个或多个估计器参数进行穷举或优化搜索。

还有点令人困惑的是,人们常常将“管道”和“网格搜索”混为一谈,有时用前者表示后者。您可以将网格搜索作为流水线的一部分,使用最终函数来估计模型质量,并将测试模型的输出作为输入。Scikits在这里有一个例子(http://scikit-learn.org/stable/modules/pipeline.html#pipeline)

网格搜索有两种,穷举型和随机型:

         NLP成长计划(三)

Exhaustive

穷举的网格搜索只不过是一系列for循环,每个循环遍历可能超参数值的字典。保持所有搜索参数的最佳性能,并返回所选择的超参数。Scikit有一个方法,尽管你可以自己编写类似伪代码的方法:

results = {}
parameter_vals = {'p1':[a_1,a_2...a_K], 'p2':[b_1, b_2, ... b_M], ... , 'pN':[zz_1, zz_2, ..., zz_N]}

parameter_sets = generate_parameter_grid by exhaustive combinations
for set in parameter_sets
    test accuracy of model(set)
results[set] = accuracy   
return argmax(results)

Random

对参数值的随机搜索使用生成函数(通常是选择的分布)来为超参数生成候选值集。这比穷举搜索有一个主要好处:用户只有一个参数来处理

下面是如何执行一个随机的和穷尽的网格搜索的例子:

import numpy as np

from time import time
from operator import itemgetter
from scipy.stats import randint as sp_randint
from sklearn.model_selection import GridSearchCV, RandomizedSearchCV
from sklearn.datasets import load_digits
from sklearn.ensemble import RandomForestClassifier

# load digit dataset
digits = load_digits()
# split data into inputs and output
X, y = digits.data, digits.target

# build a random forest classifier
clf = RandomForestClassifier(n_estimators=20)


# Utility function to report best scores
def report(grid_scores, n_top=3):
    # sort scores based on metric so we can grab the n_top models
    top_scores = sorted(grid_scores, key=itemgetter(1), reverse=True)[:n_top]
    # iterate over the n_top models
    for i in range(n_top):
        print("Model with rank: {0}".format(i + 1))
        print("Mean validation score: {0:.3f} (std: {1:.3f})".format(
              grid_scores['mean_test_score'][i],
              grid_scores['std_test_score'][i]))
        print("Parameters: {0}".format(grid_scores['params'][i]))
        print("")


# specify parameters and distributions to sample from - 
# what methods might we consider that would improve these estimates?
param_dist = {"max_depth": [3, None],
              "max_features": sp_randint(1, 11),
              "min_samples_split": sp_randint(2, 11),
              "min_samples_leaf": sp_randint(1, 11),
              "bootstrap": [True, False],
              "criterion": ["gini", "entropy"]}

# number of models we are going to train
n_iter_search = 20
# create our randomized gridsearch classifier
#      clf, is the model we are performing the search on
#      param_dist, is a dictionary of paramater distributions that we will sample over
#      n_iter_search, number of models we are going to train
#      True, the scores from our training for each model will be returned when we perform the gridsearch
random_search = RandomizedSearchCV(clf, param_distributions=param_dist,
                                   n_iter=n_iter_search, return_train_score=True)
# start a timer so we know how long the random gridsearch took
start = time()
# perform the random gridsearch
random_search.fit(X, y)
print("RandomizedSearchCV took %.2f seconds for %d candidates"
      " parameter settings." % ((time() - start), n_iter_search))
# print the top 3 model outputs from the random gridsearch
report(random_search.cv_results_)

# use a full grid over all parameters. 
# The grid search will generate parameter sets for each and every one of these
param_grid = {"max_depth": [3, None],
              "max_features": [1, 3, 10],
              "min_samples_split": [2,3,10],
              "min_samples_leaf": [1, 3, 10],
              "bootstrap": [True, False],
              "criterion": ["gini", "entropy"]}

# create an exhaustive gridsearch object
#      clf, is the model we are performing the search on
#      param_grid dictionary with the parameter settings the search will try
#      True, the scores from our training for each model will be returned when we perform the gridsearch
grid_search = GridSearchCV(clf, param_grid=param_grid, return_train_score=True)
# start a timer so we know how long the exhaustive gridsearch took
start = time()
# perform the exhaustive gridsearch
grid_search.fit(X, y)

print("GridSearchCV took %.2f seconds for %d candidate parameter settings."
      % (time() - start, len(grid_search.cv_results_)))
# print the top 3 model outputs from the exhaustive gridsearch
report(grid_search.cv_results_)

以及输出:

RandomizedSearchCV took 3.41 seconds for 20 candidates parameter settings.
Model with rank: 1
Mean validation score: 0.923 (std: 0.017)
Parameters: {'bootstrap': False, 'criterion': 'gini', 'max_depth': None, 'max_features': 7, 'min_samples_leaf': 4, 'min_samples_split': 2}

Model with rank: 2
Mean validation score: 0.905 (std: 0.013)
Parameters: {'bootstrap': True, 'criterion': 'gini', 'max_depth': None, 'max_features': 7, 'min_samples_leaf': 4, 'min_samples_split': 10}

Model with rank: 3
Mean validation score: 0.768 (std: 0.024)
Parameters: {'bootstrap': True, 'criterion': 'gini', 'max_depth': 3, 'max_features': 1, 'min_samples_leaf': 2, 'min_samples_split': 2}

GridSearchCV took 44.23 seconds for 22 candidate parameter settings.
Model with rank: 1
Mean validation score: 0.761 (std: 0.026)
Parameters: {'bootstrap': True, 'criterion': 'gini', 'max_depth': 3, 'max_features': 1, 'min_samples_leaf': 1, 'min_samples_split': 2}

Model with rank: 2
Mean validation score: 0.785 (std: 0.020)
Parameters: {'bootstrap': True, 'criterion': 'gini', 'max_depth': 3, 'max_features': 1, 'min_samples_leaf': 1, 'min_samples_split': 3}

Model with rank: 3
Mean validation score: 0.761 (std: 0.030)
Parameters: {'bootstrap': True, 'criterion': 'gini', 'max_depth': 3, 'max_features': 1, 'min_samples_leaf': 1, 'min_samples_split': 10}

 

Priotiritizing Parameters

当进行超调时,记住并非所有的超参数都同等重要。对于大多数模型,超参数子集将对模型的性能产生重大影响,而剩余的超参数对模型的性能几乎没有影响,或者无论数据如何,都应该为超参数使用已建立的值。因此,我们的超调应该着眼于寻找这个重要的超参数子集的最优值。

例如,用神经网络,调整的两个最重要的超参数是优化器的学习速率和权重正则化。这两个参数都控制神经网络学习的速率。如果你对这些参数是“积极”的,那么我们可能会超过最优权重,但是如果我们对这些参数太“宽容”,我们可能会低于最优权重。

 

Other Strategies

除了网格搜索之外,还有其他方法来执行超调。一种选择是贝叶斯优化。贝叶斯优化近似后验分布模型的基础上,你试图训练找到最优集合的超参数。这里是python中的一个实现(https://github.com/fmfn/BayesianOptimization)。

 

Troubleshooting

在数据科学中,存在大量的问题,这些问题可能出现在数据和/或建模方面。幸运的是,对于我们来说,我们在数据科学中面临的许多问题已经被其他人遇到,并且已经建立了解决这些问题的方法。在这里,我们将研究数据科学中出现的两个常见问题,以及如何解决这些问题的一些贸易工具。

 

Imbalanced Classes

当执行分类学习时,会发生不平衡类,我们可以输出的潜在类的子集构成了我们数据的大部分。

为了确保我们的模型能够了解所有的类,我们可以使用一个对阶级失衡有鲁棒性的模型。对类不平衡具有鲁棒性的模型的一个例子是类加权支持向量机。基本上,这个模型对少数类观测数据的错误分类给予了更高的惩罚,使得尽管每个类的观测数据数量不同,该模型仍然对每个类给予同等的重要性。Scikit的SVM版本可以在这里找到(http://scikit-learn.org/stable/auto_examples/svm/plot_separating_hyperplane_unbalanced.html#)。

虽然有时我们可能希望针对不平衡数据使用对不平衡类不鲁棒的模型(即,本课程使用XGBoost来处理不平衡的文章数据集)。在这些情况下,最好重新采样你的数据,这样你的采样数据就纠正了不平衡。这种平衡的采样数据然后被用来训练你的模型,而不受不平衡数据的影响。

                                  NLP成长计划(三)

 

 

Information Leakage

当训练数据具有额外的信息,使得我们的模型似乎比“真实世界”中的实际情况产生更好的结果时,就会发生信息泄漏。我们通常的方法是执行一个训练,验证和测试分割到我们的数据。我们只使用测试集来判断我们的最终模型在投产时的性能如何。然而,有时我们没有足够的数据量来拥有一个纯测试集。克服信息泄漏(即数据不足)的一种方法是执行k折叠交叉验证。

 

KFold Cross Validation

理想情况下,在训练模型时,我们希望在缺少足够数据时,仍然可以使用KFold Validation测量模型的性能。通过使用K折交叉验证,我们可以得到更准确的误差值。

基本上,我们把数据分解成k个组。我们采取这些小组之一,并将其设置为测试集。然后,我们对剩余的组进行数据训练,并计算测试组的误差。我们可以重复这个过程K次和平均所有的结果。这使我们对误差有了更准确的认识。

你可以使用此方法在 Scikit 中执行k折叠交叉验证(http://scikit-learn.org/stable/modules/generated/sklearn.model_selection.KFold.html#sklearn.model_selection.KFold)。

                                       NLP成长计划(三)