你的机器“不肯”学习,怎么办?

640?wx_fmt=jpeg

给你讲讲机器学习数据预处理中,归一化(normalization)的重要性。

前情回顾

Previously, on 玉树芝兰 ……

我给你写了一篇《如何用 Python 和 Tensorflow 2.0 神经网络分类表格数据?》,为你讲解了 Tensorflow 2.0 处理结构化数据的分类。

结尾处,我给你留了一个问题

把测试集输入模型中,检验效果。结果是这样的:

model.evaluate(test_ds)
640?wx_fmt=jpeg

准确率接近80%,看起来很棒,对吗?

但是,有一个疑问:

640?wx_fmt=jpeg

注意这张截图。训练的过程中,除了第一个轮次外,其余4个轮次的这几项重要指标居然都没变

它们包括:

  • 训练集损失

  • 训练集准确率

  • 验证集损失

  • 验证集准确率

所谓机器学习,就是不断迭代改进。

如果每一轮下来,结果都一模一样,这里八成有鬼。

我给了你提示:

看一个分类模型的好坏,不能只看准确率(accuracy)。对于二元分类问题,你可以关注一下 f1 score,以及混淆矩阵(confusion matrix)。

这段时间,你通过思考,发现问题产生原因,以及解决方案了吗?

从留言的反馈来看,有读者能够正确指出了问题。

但很遗憾,我没有能见到有人提出正确和完整的解决方案。

这篇文章,咱们就来谈谈,机器为什么“不肯学习”?以及怎么做,才能让它“学得进去”。

环境

本文的配套源代码,我放在了这个 Github 项目中。请你点击这个链接(http://t.cn/ESJmj4h)访问。

640?wx_fmt=jpeg

如果你对我的教程满意,欢迎在页面右上方的 Star 上点击一下,帮我加一颗星。谢谢!

注意这个页面的中央,有个按钮,写着“在 Colab 打开”(Open in Colab)。请你点击它。

然后,Google Colab 就会自动开启。

640?wx_fmt=jpeg

我建议你点一下上图中红色圈出的 “COPY TO DRIVE” 按钮。这样就可以先把它在你自己的 Google Drive 中存好,以便使用和回顾。

640?wx_fmt=jpeg

Colab 为你提供了全套的运行环境。你只需要依次执行代码,就可以复现本教程的运行结果了。

如果你对 Google Colab 不熟悉,没关系。我这里有一篇教程,专门讲解 Google Colab 的特点与使用方式。

为了你能够更为深入地学习与了解代码,我建议你在 Google Colab 中开启一个全新的 Notebook ,并且根据下文,依次输入代码并运行。在此过程中,充分理解代码的含义。

这种看似笨拙的方式,其实是学习的有效路径

代码

请你在 Colab Notebook 里,找到这一条分割线:

640?wx_fmt=jpeg

用鼠标点击它,然后从菜单里面选择 Runtime -> Run Before

640?wx_fmt=jpeg

运行结束后,你会获得如下图的结果:

640?wx_fmt=jpeg

如何用 Python 和 Tensorflow 2.0 神经网络分类表格数据?》一文的结果已经成功复现。

下面我们依次来解读后面的语句。

首先,我们利用 Keras API 中提供的 predict 函数,来获得测试集上的预测结果。

pred = model.predict(test_ds)

但是请注意,由于我们的模型最后一层,用的激活函数是 sigmoid , 因此 pred 的预测结果,会是从0到1区间内的小数。

而我们实际需要输出的,是整数0或者1,代表客户“流失”(1)或者“未流失”(0)。

幸好, numpy 软件包里面,有一个非常方便的函数 rint ,可以帮助我们四舍五入,把小数变成整数。

pred = np.rint(pred)

我们来看看输出结果:

pred
640?wx_fmt=jpeg

有了预测输出结果,下面我们就可以用更多的方法,检验分类效果了。

根据前文的提示,这里我们主要用到两项统计功能:

  • 分类报告

  • 混淆矩阵

我们先从 Scikit-learn 软件包导入对应的功能。

from sklearn.metrics import classification_report, confusion_matrix

然后,我们对比测试集实际标记,即 test['Exited'] ,和我们的预测结果。

print(classification_report(test['Exited'], pred))
640?wx_fmt=jpeg

这里,你立刻就能意识到出问题了——有一个分类,即“客户流失”(1)里,三项重要指标(precision, recall 和 f1-score)居然都是0!

我们用同样的数据查看混淆矩阵,看看到底发生了什么。

print(confusion_matrix(test['Exited'], pred))
640?wx_fmt=jpeg

混淆矩阵的读法是,行代表实际分类,列代表预测分类,分别从0到1排列。

上图中,矩阵的含义就是:模型预测,所有测试集数据对应的输出都是0;其中预测成功了1585个(实际分类就是0),预测错误415个(实际分类其实是1)。

也就是说,咱们费了半天劲,训练出来的模型只会傻乎乎地,把所有分类结果都设置成0.

在机器学习里,这是一个典型的笨模型(dummy model)。

如果咱们的测试集里面,标签分类0和1的个数是均衡的(一样一半),那这种笨模型,应该获得 50% 的准确率。

然而,我们实际看看,测试集里面,分类0(客户未流失)到底占多大比例:

len(test[test['Exited'] == 0])/len(test)

结果是:

0.7925

这个数值,恰恰就是《如何用 Python 和 Tensorflow 2.0 神经网络分类表格数据?》一文里面,我们在测试集上获得了准确率。

640?wx_fmt=jpeg

一开始我们还认为,将近80%的准确率,是好事儿。

实际上,这模型着实很傻,只有一根筋。

设想我们拿另外一个测试集,里面只有 1% 的标注是类别0,那么测试准确率也就只有 1% 。

为了不冤枉模型,咱们再次确认一下。

使用 numpy 中的 unique 函数,查看一下预测结果 pred 中,到底有几种不同的取值。

np.unique(pred)

结果是:

array([0.], dtype=float32)

果不其然,全都是0.

果真是“人工不智能”啊!

分析

问题出在哪里呢?

模型根本就没有学到东西。

640?wx_fmt=jpeg

每一轮下来,结果都一样,毫无进步。

说到这里,你可能会有疑惑:

老师,是不是你讲解出错了?

两周前,我在 UNT 给学生上课的时候,他们就提出来了这疑问。

我早有准备,立即布置了一个课堂练习。

640?wx_fmt=jpeg

让他们用这套流程,处理另外的一个数据集。

640?wx_fmt=jpeg

这个数据集你也见过,就是我在《贷还是不贷:如何用Python和机器学习帮你决策?》里面用过的贷款审批数据。

我把数据放在了这个链接(http://t.cn/ESJ3x3o),你如果感兴趣的话,不妨也试着用前文介绍的流程,自己跑一遍。

学生们有些无奈地做了这个练习。

他们的心理活动大概是这样的:

你教的这套流程,连演示数据都搞不定,更别说练习数据了。做了也是错的。是不是先纠正了错误,再让我们练啊?

然后,当运行结果出来的时候,我在一旁,静静看着他们惊诧、沉思,以至于抓狂的表情。

同一套流程,在另外的数据上使用,机器确实学习到了规律

数据集的细节里面,藏着什么魔鬼?

归一

直接说答案:

流程上确实有问题。数值型数据没有做归一化(normalization)。

归一化是什么?

就是让不同特征列上的数值,拥有类似的分布区间。

最简单的方法,是根据训练集上的对应特征,求 Z 分数。

Z 分数的定义是:

640?wx_fmt=jpeg
  • Mean 是均值

  • Standard Deviation 是标准差

为什么一定要做这一步?

回顾一下咱们的数据。

640?wx_fmt=jpeg

我这里用红色标出来了所有数值特征列。

看看有什么特点?

对,它们的分布迥异。

NumOfProducts 的波动范围,比起 Balance 或者 EstimatedSalary,要小得多。

机器学习,并不是什么黑科技。

它的背后,是非常简单的数学原理。

最常用的迭代方法,是梯度下降(Gradient descent)。如下图所示:

640?wx_fmt=gif

其实就是奔跑着下降,找局部最优解。

如果没跑到,继续跑。

如果跑过辙了,再跑回来。

但问题在于,你看到的这张图,是只有1维自变量的情况。

咱们观察的数据集,仅数值型数据,就有6个。因此至少是要考察这6个维度。

不好意思,我无法给你绘制一个六维图形,自己脑补吧。

但是注意,对这六个维度,咱们用的,却是同一个学习速率(learning rate)。

就好像同一个老师,同时给6个学生上数学课。

如果这六个维度分布一致,这样是有意义的。

这也是为什么大多数学校里面,都要分年级授课。要保证授课对象的理解能力,尽量相似。

但假如这“6个学生”里,有一个是爱因斯坦,一个是阿甘。

你说老师该怎么讲课?

爱因斯坦听得舒服的进度,阿甘早就跟不上了。

阿甘能接受的进度,爱因斯坦听了可能无聊到想撞墙。

最后老师决定——太难了,我不教了!

于是谁都学不到东西了。

对应到我们的例子,就是因为数据分布差异过大,导致不论往哪个方向尝试改变参数,都按下葫芦浮起瓢,越来越糟。

于是模型判定,呆在原地不动,是最好的策略

所以,它干脆不学了。

怎么办?

这个时候,就需要归一化了。

对应咱们这个不恰当的举例,就是在课堂上,老师要求每个人都保持每天一单位(unit)的学习进度。

只不过,爱因斯坦的一单位,是100页书。

阿甘同学……两行,还能接受吧?

新代码

请你点击这个链接(http://t.cn/ESJBJHW)访问更新后的代码。

640?wx_fmt=jpeg

按照之前的方式,点击“在 Colab 打开”(Open in Colab)。使用 “COPY TO DRIVE” 按钮,存放在你自己的 Google Drive 中。

640?wx_fmt=jpeg

对比观察后,你会发现,改动只有1个代码段落

就是把原先的数值型特征采集从这样:

for header in numeric_columns:
  feature_columns.append(feature_column.numeric_column(header))

变成这样:

for header in numeric_columns:
  feature_columns.append(
      feature_column.numeric_column(
          header,
          normalizer_fn=lambda x: (tf.cast(x, dtype=float)-train[header].mean())/train[header].std()))

尤其要注意,我们要保证平均值和标准差来自于训练集。只有这样,才能保证模型对验证集和测试集的分布一无所知,结果的检验才有意义。否则,就如同考试作弊一样。

这就是为了归一化,你所需做的全部工作。

这里我们依然保持原先的随机种子设定。也就是凡是使用了随机函数的功能(训练集、验证集和测试集的划分等),都与更新代码之前完全一致

这样做,改变代码前后的结果才有可对比性

下面我们使用菜单栏里面的 "Run All" 运行一下代码。

640?wx_fmt=jpeg

之后查看输出。

首先我们可以注意到,这次的训练过程,数值终于有变化了。

640?wx_fmt=jpeg

因为其他变量全都保持一致。所以这种变化,没有别的解释,只能是因为使用了归一化(normalization)。

我们更加关心的,是这次的分类报告,以及混淆矩阵。

分类报告是这样的:

640?wx_fmt=jpeg

注意这一次,类别1上面的几项指标,终于不再是0了。

640?wx_fmt=jpeg

混淆矩阵中,类别1里,也有36个预测正确的样本了。

成功了!

……

别急着欢呼。

虽然机器在学习和改进,但是效果好像也不是很好嘛。例如类别1的 Recall 简直惨不忍睹。

有没有什么办法改进呢?

这个问题,就需要你了解如何微调模型,以及超参数的设定了。

如果你需要详细的学习资料,推荐给你这本经典教材。

小结

这篇文章里,我为你介绍了以下知识点:

  • 分类模型性能验证(尤其是 Accuracy 之外的)评测指标;

  • 预处理过程中数值数据归一化(Normalization)的重要性;

  • 如何在 Tensorflow 2.0 的数据预处理和特征抽取中使用归一化;

  • 如何利用模型预测分类结果,并且使用第三方软件包功能快速统计汇报。

希望上述内容,能对你使用深度神经网络进行机器学习有帮助。

祝深度学习愉快!

延伸阅读

你可能也会对以下话题感兴趣。点击链接就可以查看。

由于微信公众号外部链接的限制,文中的部分链接可能无法正确打开。如有需要,请点击文末的“阅读原文”按钮,访问可以正常显示外链的版本。

订阅我的微信公众号“玉树芝兰”,第一时间免费收到文章更新。

640?wx_fmt=png

知识星球入口在这里:

640?wx_fmt=png

欢迎点“喜欢作者”,请我喝杯咖啡。谢谢!

题图:来自于 freepixels


发布了97 篇原创文章 · 获赞 272 · 访问量 23万+

猜你喜欢

转载自blog.csdn.net/nkwshuyi/article/details/89838044