模型测试

在我最近的几篇文章中,我一直在涉足“机器学习”的领域——这是一个强大的概念,它一直在稳步进入计算领域的主流,并且有可能以多种方式改变生活。机器学习的目标是生成一个“模型”——一段软件,它可以根据从旧数据中学到的知识,对新数据进行预测。

机器学习可以帮助解决的一种常见问题是分类。给定一些新数据,你如何对其进行分类?例如,如果你是一家信用卡公司,并且你拥有关于新购买的数据,那么这笔购买看起来是合法的还是欺诈性的?你能够准确分类购买行为的程度取决于你的模型的质量。而且,你的模型的质量通常不仅取决于你选择的算法,还取决于你用来“训练”该模型的数据的数量和质量。

上述声明暗示,给定相同的输入数据,不同的算法可能会产生不同的结果。因此,仅仅选择一种机器学习算法是不够的。你还必须测试生成的模型,并将其质量与其他模型进行比较。

因此,在本文中,我探讨了测试模型的概念。我将展示如何使用 Python 的 scikit-learn 包来构建和训练模型,它也提供了测试模型的能力。我还将描述 scikit-learn 如何提供工具来比较模型的有效性。

模型测试

“测试”模型到底意味着什么?毕竟,如果你已经基于可用数据构建了一个模型,那么模型将来可以使用新数据,这难道不是理所当然的吗?

也许是这样,但你需要检查一下,以确保万无一失。也许该算法不太适合你正在检查的数据类型,或者可能没有足够的数据来充分训练模型。或者,也许数据存在缺陷,因此没有有效地训练模型。

但是,建模的最大问题之一是“过拟合”。过拟合意味着模型在描述训练数据方面做得很好,但它与训练数据紧密且具体地联系在一起,以至于无法进一步推广。

例如,假设一家信用卡公司想要对欺诈行为进行建模。你知道在很多情况下,人们使用信用卡购买昂贵的电子产品。一个过拟合的模型不仅仅会给购买昂贵电子产品的人在判断欺诈行为时增加额外的权重;它可能会查看所购买电子产品的确切价格、地点和类型。换句话说,该模型将精确地描述过去发生的事情,从而限制其推广和预测未来的能力。

想象一下,如果你只能阅读以前学过的字体的字母,你就可以进一步理解过拟合的局限性。

你如何避免过拟合模型?你使用各种输入数据来检查它们。如果模型在许多不同的输入下表现良好,那么它应该在许多不同的输出下也能良好工作。

在我上一篇文章中,我继续研究一项半幽默研究的数据,该研究评估了南加州多家餐厅的墨西哥卷饼。检查这些数据可以识别出墨西哥卷饼的哪些元素对于整体卷饼质量评估是重要的(或不重要的)。这里,总结一下,为了创建和评估数据,我在 Jupyter notebook 窗口中采取的步骤


%pylab inline
import pandas as pd                     # load pandas with an alias
from pandas import Series, DataFrame    # load useful Pandas classes
df = pd.read_csv('burrito.csv')         # read into a data frame

burrito_data = df[range(11,24)]
burrito_data.drop(['Circum', 'Volume', 'Length'], axis=1, inplace=True)
burrito_data.dropna(inplace=True, axis=0)

y = burrito_data['overall']
X = burrito_data.drop(['overall'], axis=1)

from sklearn.neighbors import KNeighborsRegressor  # import
                                                   # classifier
KNR = KNeighborsRegressor()                        # create a model
KNR.fit(X, y)                                      # train the model

那么,模型是好还是不好呢?你只有在你尝试对你已知答案的预测进行一些预测,并查看模型是否正确预测事物时才能知道。

在哪里可以找到你已经知道答案的数据?当然是在输入数据中!你可以要求模型 (KNR) 对 X 进行预测,并将这些预测与 y 进行比较。如果模型正在执行分类,你甚至可以手动检查它以获得基本评估。但是使用回归,甚至是大型分类模型,你将需要一套更严肃的指标。

幸运的是,scikit-learn 自带了许多你可以使用的指标。如果你说


from sklearn import metrics

那么你就可以访问可以用来比较你的预测值(即来自原始“y”向量的值)与模型计算出的值的方法。你可以将几个分数应用于模型;其中一个分数将是“解释方差得分”。你可以按如下方式获得它


y_test = KNR.predict(X)

from sklearn import metrics
metrics.mean_squared_error(y_test, y)

请注意这里发生了什么。你正在重用输入矩阵 X,要求模型预测其输出。但是,你已经知道这些输出;它们在 y 中。所以现在你看到模型与预测已经输入到其中的输出有多接近。

在我的系统上,我得到 0.64538408898281541。理想情况下,对于一个完美的模型,你会得到 1,这意味着该模型还可以,但不是非常出色。

但是,至少你现在有了一种评估模型并将其与可能更好或更差的其他模型进行比较的方法。你甚至可以为不同的邻居数量运行 KNR,并查看每个模型的表现如何(或多差)


for k in range(1,10):
    print(k)
    KNR = KNeighborsRegressor(n_neighbors=k)
    KNR.fit(X, y)
    y_test = KNR.predict(X)
    print "\t", metrics.mean_squared_error(y_test, y)
    print "\t", metrics.explained_variance_score(y_test, y)

好消息是,你现在已经了解了当配置不同的 n_neighbors 值时,KNR 模型如何变化。此外,你看到当 n_neighbors = 1 时,你没有得到误差,并且解释方差为 100%。该模型取得了成功!

分割测试

但是等等。上面的测试有点傻。如果你使用作为训练一部分的数据来测试模型,如果模型没有至少部分正确,你可能会感到惊讶。模型真正的测试是当它遇到新数据时,它的工作效果如何。

这有点进退两难。你想用真实世界的数据来测试模型,但如果你这样做,你并不一定知道应该出现什么答案。这意味着你毕竟无法真正测试它。

建模世界对此问题有一个简单的解决方案。仅使用一部分训练数据来训练模型,并使用其余部分来测试它。

scikit-learn 具有支持此“训练-测试分割”的功能。你可以在原始 X 和 y 值上调用 train_test_split 函数,获得两个 X 值(用于训练和测试)和两个 y 值(用于训练和测试)。正如你可能期望的那样,你可以使用 X_trainy_train 值训练模型,并使用 X_testy_test 值测试它


from sklearn.cross_validation import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y,
 ↪test_size=0.25)

KNR = KNeighborsRegressor(n_neighbors=1)
KNR.fit(X_train, y_train)
y_pred = KNR.predict(X_test)
print "\t", metrics.mean_squared_error(y_test, y_pred)
print "\t", metrics.explained_variance_score(y_test, y_pred)

突然之间,这个惊人的模型似乎不再那么惊人了。通过针对它以前从未见过的值进行检查,它给出的均方误差为 0.4,解释方差为 0.2。

这并不意味着模型很糟糕,但这确实意味着你可能需要进一步检查它。也许你应该(再次)检查 n_neighbors 的其他值。或者,也许你应该尝试 KNeighborsRegressor 以外的其他方法。不过,再次强调,关键的收获是,你现在正在使用一种真实的、合理的方式来评估该模型,而不是仅仅凭经验估计数字并假设(希望)一切都很好。

多次分割

你进行的分割测试可能会以某种方式触动模型,使其给出特别好(或坏)的结果。你真正需要做的是尝试不同的分割,这样你才能确保无论你使用什么训练数据,模型都能发挥最佳性能。然后,你可以对一堆不同的分割结果进行平均。

在 scikit-learn 的世界中,这是使用 KFold 完成的。你需要指示你想要创建多少个不同的模型实例,以及你想要运行的“折叠”(即分割测试)的数量


from sklearn.cross_validation import KFold, cross_val_score
kfold = KFold(n=len(X), n_folds=10)

kfold 对象就位后,你可以将其传递给 cross_validation 模块中的 cross_val_score 方法。你将模型(在本例中为 KNR)、X、y 和你创建的 kfold 对象传递给它


v_results = cross_val_score(KNR, X, y, cv=kfold)

你获得的 cv_results 对象描述了交叉验证,通常通过查看其平均值(即,这些运行的平均得分是多少)和标准偏差(即,运行之间的差异有多大)进行分析


print cv_results.mean()
print cv_results.std()

在这种特定情况下,结果并不是那么有希望


0.310254620082
0.278746712239

换句话说,尽管当首先分析时,使用所有训练数据进行测试时,n_neighbors=1 似乎非常出色,但这似乎不再是这种情况。

即使你坚持使用 KNR 作为你的分类器,你仍然可以结合 KFold,检查何时(如果)不同的 n_neighbors 值可能比你在此处给出的值 1 更好


from sklearn.cross_validation import KFold, cross_val_score

for k in range(1,10):
    print(k)
    KNR = KNeighborsRegressor(n_neighbors=k)
    kfold = KFold(n=len(X), n_folds=10)
    cv_results = cross_val_score(KNR, X, y, cv=kfold)
    print "\t", cv_results.mean()
    print "\t", cv_results.std()

果然,当 k=9 时,你得到的结果明显优于 k=1 时


0.594573190846
0.161443573949

也就是说,我确实相信你有可能创建一个更好的模型。也许更好的回归分类器会改善情况。也许使用分类而不是回归,在分类中,你将 y 中的值四舍五入到最接近的整数,并将分数视为 5 个不同的类别,可能会奏效。也许,正如之前提到的,我应该更多地关注哪些列最重要(和最不重要),并进行一些更好的特征选择。

无论如何,有了适当的测试系统,你现在能够开始智能地处理这些问题,并有一种方法来评估你的进展。

总结

仅仅创建一个机器学习模型是不够的;测试它也很重要。正如你在这里看到的,scikit-learn 使创建、分割测试,然后评估一个模型甚至是一大堆模型变得相对容易。

监督学习并不是唯一的机器学习类型。在许多情况下,你可以要求计算机根据它开发的启发式方法,而不是你训练的类别,将你的数据划分为多个组。在我的下一篇文章中,我计划研究如何(以及何时)构建“无监督学习”模型。

Reuven Lerner 在世界各地向公司教授 Python、数据科学和 Git。你可以订阅他免费的每周“更好的开发者”电子邮件列表,并从他的书籍和课程中学习,网址为 http://lerner.co.il。Reuven 与他的妻子和孩子住在以色列的莫迪因。

加载 Disqus 评论