机器学习中不平衡类的处理
介绍
大多数现实世界的分类问题都表现出某种程度的类别不平衡,即每个类别在数据集中所占的比例不同。适当调整你的标准和方法以适应你的目标是很重要的。如果没有做到这一点,您可能会在您的用例环境中为一个无意义的度量进行优化。
例如,假设您有两个类——A 和 B。A 类占您数据集的 90 %, B 类占另外的 10%,但是您最感兴趣的是识别 B 类的实例。通过简单地每次预测 A 类,您可以达到 90%的准确率,但是这对于您的预期用例来说提供了一个无用的分类器。相反,正确校准的方法可能会实现较低的准确性,但会有更高的真实阳性率(或召回率),这确实是您应该优化的指标。这些场景通常发生在检测环境中,例如在线滥用内容或医疗数据中的疾病标记。
我现在将讨论几种可以用来减轻阶级不平衡的技术。一些技术适用于大多数分类问题,而其他技术可能更适合特定的不平衡水平。在本文中,我将从二元分类的角度来讨论这些问题,但是在大多数情况下,多类分类也是如此。我还假设的目标是识别少数类,否则,这些技术并不是真正必要的。
韵律学
一般来说,这个问题处理的是召回(被如此分类的真正肯定的实例的百分比)和精确度(真正肯定的肯定分类的百分比)之间的权衡。在我们想要检测少数类的实例的情况下,我们通常更关心召回率而不是精确度,因为在检测的上下文中,错过一个肯定的实例通常比错误地标记一个否定的实例代价更高。例如,如果我们试图检测滥用内容,让人工审查者发现内容实际上不是滥用的是微不足道的,但是识别从未被标记为滥用的内容要困难得多。因此,在比较不平衡分类问题的方法时,可以考虑使用准确度以外的指标,如召回率、精确度和 AUROC。在参数选择或模型选择期间切换您优化的度量可能足以提供检测少数类的理想性能。
成本敏感学习
在常规学习中,我们平等地对待所有错误分类,这导致不平衡分类问题,因为识别少数类而不是多数类没有额外的奖励。成本敏感学习改变了这一点,并使用函数 C(p,t) (通常表示为矩阵),该函数指定将类 t 的实例错误分类为类 p 的成本。这使得我们对少数阶级的错误分类的惩罚比对多数阶级的错误分类的惩罚更重,希望这能增加真正的阳性率。为此,一个常见的方案是使成本等于该类构成的数据集比例的倒数。随着班级人数的减少,这增加了惩罚。
Sample cost function matrix
抽样
修复不平衡数据集的一个简单方法是简单地平衡它们,或者通过过采样少数类的实例或者欠采样多数类的实例。这只是允许我们创建一个平衡的数据集,从理论上讲,不应该导致分类器偏向一个类或另一个类。然而,在实践中,这些简单的取样方法有缺陷。对少数进行过采样会导致模型过拟合,因为它会引入重复的实例,从已经很小的实例池中提取。类似地,对大多数进行欠采样可能会遗漏提供两个类之间重要差异的重要实例。
除了简单的过采样或欠采样之外,还有更强大的采样方法。最著名的例子是 SMOTE ,它通过形成相邻实例的凸组合来创建少数类的新实例。如下图所示,它可以有效地在特征空间中的少数点之间绘制直线,并沿着这些直线进行采样。这使我们能够平衡数据集,而不会过度拟合,因为我们创建了新的合成示例,而不是使用副本。然而,这并不能防止所有的过度拟合,因为这些仍然是从现有的数据点创建的。
Visualization of SMOTE
异常检测
在更极端的情况下,在异常检测的上下文中考虑分类可能更好。在异常检测中,我们假设存在数据点的“正态”分布,任何充分偏离该分布的都是异常。当我们将分类问题重新构建为异常检测问题时,我们将多数类视为点的“正常”分布,将少数类视为异常。有许多异常检测算法,如聚类方法、单类支持向量机和隔离森林。
Visualization of clustering method for anomaly detection
结论
希望这些方法的某种组合将允许您创建一个更好的分类器。就像我之前说的,这些技术中的一些更适合不同程度的不平衡。例如,简单的采样技术可以让您克服轻微的不平衡,而异常检测方法可能需要极端的不平衡。最终,对于这个问题,没有一个放之四海而皆准的方法,您只需要尝试每种方法,看看它们如何应用于您的特定用例以及成功的衡量标准。
亲爱的数学,我不是你的治疗师,解决你自己的问题。
或者实际上,我会建立一个神经网络来为你解决这些问题。
想象一下一个类似私人数学家教的网站。它可以解方程,甚至指导你解方程,这样你就可以自己解下一个方程了。现在想象一下,有一天你想和你的自动导师不仅通过方程进行交互,还可以使用你的自然语言。
好吧,别再想了。这个名为 Simplisico 的网站已经存在。为了让你能够用你的自然语言与它互动,在这个过程中需要发生的一件事是,Simplisico 将自己理解你实际上试图解决的方程。这就是我来的原因。在这篇文章中,我将分享我是如何创建一个 POC 来应对阅读一个文本数学问题并提取潜在方程的挑战的。
Don’t worry, we don’t really have 42 steps to cover, and you have less than 10 min. of reading ahead of you.
让我们做个计划吧!
在我们进入细节之前,让我们考虑一下我们将如何应对这个挑战。
步骤(1)——当面对一个文本数学问题时,确保问题是用我们能读懂的语言写的。我的意思是,如果问题是用巴斯克语写的,而我不会说巴斯克语,我就必须把问题翻译成我能理解的语言,然后才能尝试解决数学问题。
那么,解决数学任务本身的一个办法,就是:
(2) 【隐藏】带参数的题中数字, **【3】**了解方程的结构,
**【4】**将数字插入正确的地方。
在接下来的部分中,我们将看到我们如何使用现有工具执行步骤 (1)-(2) ,以及我们如何构建一个神经网络来实现步骤 (3)-(4) 。
预处理——教我们的机器阅读
让我们看看如何使用两个现有工具,分两步将问题翻译成机器能够理解的语言:
首先我做了一个叫 记号化 的东西,把问题中的数字换成变量名,把问题分成单词,去掉标点符号。我使用 NLTK 包轻松做到了这一切。
然后,我做了一件叫做 嵌入 的事情,这意味着我将单词“翻译”成高维空间中的向量,以一种保留它们的语义和句法关系的方式。由于 Gensim 包中的 Word2Vec 算法,这也是一个相当简单的步骤。
让我们想一想目前为止我们获得了什么。假设我的机器已经知道如何解决在公园里数树的问题。现在,如果它也知道公园里的树和解决方案中的步骤之间存在类似的关系,那么当我让它计算解决方案中的步骤时,它将有史以来第一次知道应用它已经知道的相同逻辑来计算树。
所以现在,实际上,我们教会了我们的机器阅读。
让我们继续主要部分,在这里我们讨论将提取基础方程的神经网络。
流行语驱动开发
如果你对神经网络有一点了解(如果没有,你可以阅读我写的这个快速介绍,那么我可以直接告诉你,我为这个任务构建的神经网络是一个具有长短期记忆和注意力的序列排列递归神经网络。
该死,这是一个口。
让我们一点一点地了解它。
首先,输入是一个文本数学问题,因此是单词的序列。为什么输出会是一个 排列 ?因为我们期望机器在输入问题中看到的每个数字,都会出现在输出等式中。然后,如果我们知道方程的结构是什么,那么我们就可以把问题简化为寻找输入问题中数字的正确排列。
现在, 递归神经网络【RNN】是指网络中包含某种反馈回路的通用架构。在我们的例子中,网络将一次读取每个单词,这是短期记忆部分,但它也将在它被读取的上下文中读取单词。这是长期记忆的部分。另外,假设我在问题的开头加了一句“我想在 Medium 上写一篇博文”。显然这并没有给我想要解的方程增加任何信息,所以我想让网络知道不要给这类句子太多的关注。
最后缺失的部分是我们想要重新引入之前放在一边的数字。我们可以通过将输入的数字与上一步的输出连接起来,让它们流经另一层神经元。
使用 Keras 包的一些基本功能可以获得网络架构的所有这些方面。
The internet is full of Lego blocks. We just need to learn how to connect them.
这是所有的乡亲
如果你遵循了这些步骤,瞧,你得到了一个可以阅读文本数学问题并提取潜在方程的算法!
更好的是,你现在知道了一种方法来应对涉及文本理解的挑战。
现在你可以想一想——你想解开什么语言之谜?你如何用你在这里学到的知识来解开这个谜?。
现在是探索这个迷人领域的真正激动人心的时刻,越来越复杂的工具变得越来越容易获得,天空不再是你对现有机制有基本了解就能实现的极限。网上到处都是乐高积木。我们只需要学会如何把它们联系起来。
【观看 这个 来了解更多我在这个项目上的冒险经历。】
数字颠覆中的伦理辩论
我们正处于也许是历史上最大的社会动荡之中。数字化正在改变我们工作、消费、交流、思考和生活的方式。这是在非常短的时间内完成的。18 世纪的工业革命用了大约 80 年。它导致了大规模的城市化和大规模的技术、经济、社会、社会、环境、地缘战略和政治变革。
数字化在许多方面超越了这一点。它的变化不仅更快,还加速了生命本身。它涉及到我们生活的各个方面。它正在革新生产、信息、移动性等等。
自动驾驶汽车,机器人,人工智能,自动化,社交媒体,都是变革性的。他们将定义我们;他们将定义社会。这是硅谷先驱们的梦想。利用数字化的变革力量让社会变得更好。
然而,这种蓝眼睛的理想主义有一些黑暗的缺点。我们意识到我们的社交媒体构建的信息茧和回音室,这导致了更大的社会两极分化和极端主义。我们知道数字化会影响注意力、记忆力、同理心和态度。我们知道这将影响就业市场。但是,我们不知道它将如何做到这一点,以何种速度和深度,以及它将对我们的社会产生何种元影响。
不同的末日场景正在浮现。在机器人和人工智能(AI)的世界里,以色列历史学家尤瓦尔·诺亚·哈拉里已经在谈论即将到来的“无用阶级”。一类人将生活在没有经济目的的后工作世界。微软研究院(Microsoft Research)首席研究员凯特克劳福德(Kate Crawford)警告称,在一个民族主义日益高涨的世界里,人工智能可能是一个“法西斯梦想”,让专制政权拥有前所未有的指挥和控制能力。英国首席科学顾问马克·沃尔波特爵士也警告说,不要在医学和法律等领域不受控制地使用人工智能,因为人工智能不可能是中立的。AI 是基于人类和人类数据的。这意味着人工智能可以接受我们固有的偏见,放大它们,然后采取行动。
在这场数字革命中,各种伦理问题层出不穷。无人驾驶汽车发生事故或机器人手术出错,谁将承担责任?自动驾驶汽车应该使用什么算法——如果它在交通状况下救了你的命,就开车撞死行人,或者撞车撞死自己,救了行人?在不影响言论自由的情况下,社交媒体需要多大程度的监管来打击假新闻和仇恨言论?机器人应该被禁止从事某些工作吗?是否应该规定最终决策仍必须由人类而非人工智能做出?类似于军事规则,即“杀死决定”必须由人类做出。我们如何分配机器人创造的财富?被机器人取代的工人会怎么样?
正如雷茵霍尔德·尼布尔在他的开创性著作《道德的人和不道德的社会》中指出的那样,问题在于“人类日益增长的智慧似乎增长得不够快,不足以解决技术进步造成的社会问题”。现在是时候就道德和数字技术展开公开辩论了。领先的人工智能公司已经在前进。脸书、亚马逊、谷歌 DeepMind、IBM、苹果和微软,这个由竞争对手组成的邪恶联盟,已经在人工智能方面携手合作,旨在引发更多关于人工智能的公共讨论。为什么会这样?因为这是他们的事。失去公众对这些新技术的信任可能会严重影响他们的业务,烧掉投入人工智能的数十亿美元研究预算。
公众讨论不应该由企业来决定。公共当局有领导责任将此列入政治议程,启动这一讨论并让公民参与其中。一些进展正在发生。英国在艾伦图灵研究所(Alan Turing Institute)成立了一个数据伦理小组,欧盟委员会(European Commission)也尤其值得称赞。与来自 19 个欧盟国家的媒体合作伙伴(如《国家报》、《卫报》、《法兰克福汇报》、《Gazeta Wyborcza》等。)它发起了一系列大规模的互联网咨询,让公民参与调查数字世界对就业、隐私、健康、民主、安全等的影响。
我们需要对数字技术进行伦理讨论,以确保我们为模拟世界制定的保护措施也适用于数字世界。数字化正在塑造和定义我们。但是我们需要成为塑造和定义它的人。以便给我们带来最大的好处和最小的坏处。
—
有意思?退房www.europecallingblog.com
调试一个用 TensorFlow 和 Keras 编写的机器学习模型
可能出错的事情,以及如果出错如何诊断。
在本文中,在我调试 TensorFlow 模型的过程中,您可以看到我的身后。我做了很多傻事,所以请不要评价。
Cheat sheet. The numbers refer to sections in this article (https://bit.ly/2PXpzRh)
1ML 模型的目标
你可以在 GitHub 上看到最终(工作)模型。我正在建立一个模型来预测 30 分钟后的闪电,并计划在美国气象学会上向展示它。基本思想是在红外和全球闪电测绘仪(GLM) GOES-16 数据的每个像素周围创建 64x64 图像补片,如果照明图像实际上在 30 分钟后出现在像素周围的 16x16 图像补片内,则将像素标记为“has_ltg=1”。
根据当前的红外和 GLM 数据,以这种方式训练的模型可以用于实时预测 30 分钟后的闪电。
1a。输入函数
我写了一个 convnet 模型,大量借鉴了为 TPU 编写的 ResNet 模型的训练循环,并修改了输入函数(读取我的数据,而不是 JPEG)和模型(一个简单的卷积网络,而不是 ResNet)。
将输入转换为张量的代码的关键位:
parsed = tf.parse_single_example(
example_data, {
**'ref'**: tf.VarLenFeature(tf.float32),
**'ltg'**: tf.VarLenFeature(tf.float32),
**'has_ltg'**: tf.FixedLenFeature([], tf.int64, 1),
})
parsed[**'ref'**] = _sparse_to_dense(parsed[**'ref'**], height * width)
parsed[**'ltg'**] = _sparse_to_dense(parsed[**'ltg'**], height * width)
label = tf.cast(tf.reshape(parsed[**'has_ltg'**], shape=[]), dtype=tf.int32)
本质上,每个 TensorFlow 记录(由 Apache 射束管道创建)由 ref、ltg 和 has_ltg 字段组成。ref 和 ltg 是可变长度数组,它们被整形为密集的 64x64 矩阵(使用 tf.sparse_tensor_to_dense)。标签只是 0 或 1。
然后,我将两张解析后的图像叠加在一起:
stacked = tf.concat([parsed[**'ref'**], parsed[**'ltg'**]], axis=1)
**img =** tf.reshape(stacked, [height, width, n_channels])
此时,我有一个张量是[?64,64,2],即一批 2 通道图像。make_input_fn 中输入管道的其余部分(列出文件、通过并行交错读取文件、预处理数据、使形状静态化以及预取)本质上只是从 ResNet 代码复制粘贴而来的。
Stacking the two 4096-length arrays into a 3D tensor of shape (64, 64, 2)
1b。运行代码
我通过在本地以小批量运行训练器几个步骤来开发代码:
gcloud ml-engine local train \
--module-name=trainer.train_cnn --package-path=${PWD}/ltgpred/trainer \
-- \
--train_steps=10 --num_eval_records=512 --train_batch_size=16 \
--job-dir=$OUTDIR --train_data_path=${DATADIR}/train* --eval_data_path=${DATADIR}/eval*
然后,我使用配有更大 GPU 的大型机器在 Cloud ML Engine 上的更大数据集上运行了它:
gcloud ml-engine jobs submit training $JOBNAME \
--module-name=trainer.train_cnn --package-path=${PWD}/ltgpred/trainer --job-dir=$OUTDIR \
--region=${REGION} --scale-tier=CUSTOM --config=largemachine.yaml \
--python-version=3.5 --runtime-version=1.8 \
-- \
--train_data_path=${DATADIR}/train* --eval_data_path=${DATADIR}/eval* \
--train_steps=5000 --train_batch_size=256 \
--num_eval_records=128000
这很有用。我的大部分调试和开发都是通过本地运行来完成的。这样,我可以在不连接的情况下工作,不需要在我的机器上安装 GPU。
2 模型不学习
一旦我写好代码并运行它,我发现这个模型很快就产生了令人怀疑的相同损失:
并达到了一个精度指标,这个指标从公元 1000 年开始就一直保持不变:
'rmse': 0.49927762, 'accuracy': 0.6623125,
2a。非常嫌疑犯
当机器学习模型不会学习时,有几个常见的疑点。我试着改变初始化。默认情况下,TensorFlow 使用 zeros _ initializer[编辑:事实证明我不需要这样做— tf.layers.conv2d 继承自 Keras 的 Conv2D ,它使用与 Xavier 相同的 glorot _ uniform。如何使用 Xavier(它使用小的初始值),并为可重复性设置随机种子?
xavier = tf.contrib.layers.xavier_initializer(seed=13)
*c1 = tf.layers.conv2d(
convout,
filters=nfilters,
kernel_size=ksize,* ***kernel_initializer=xavier,*** *strides=1,
padding='same',
activation=tf.nn.relu)*
将梯度从复杂的 AdamOptimizer 改为可靠的备用 GradientDescentOptimizer 如何?
把学习率从 0.01 降到 1e-6 怎么样?
这些都没用,但这是我第一次尝试。
2b。TensorFlow 打印
我的输入函数可能每次都返回相同的数据吗?这就解释了为什么模型被卡住了。我如何知道输入函数正在读入什么?
验证输入函数是否正确的一个简单方法是简单地打印出读取的值。然而,你不能只打印一个张量:
print(img)
这将只是打印出张量的元数据,而不是它的值。相反,您需要在程序执行时评估张量的值:
print(img.eval(sess=...))
即使这样也不行,因为 Estimator API 没有给你一个会话句柄。解决办法就是用 tf。打印:
img = tf.Print(img, [img], "image values=")
这样做的目的是在原始 img 节点和新 img 节点之间插入打印节点,以便在再次使用图像节点之前打印值:
tf.Print() allows you to insert a printing node in the TensorFlow graph so that you can print out the values of a Tensor as the program executes.
2c。张量的打印统计
但是,一旦我这样做了,我只得到前 3 或 4 个值(默认情况下,tf。打印不打印整个张量)它们大部分为零。它们是零,是因为卫星图像往往有很多零,还是因为我的输入管道有一个 bug?简单地打印图像并不是一个好主意。所以,我决定打印出输入的统计数据:
numltg = tf.reduce_sum(labels)
ref = tf.slice(img, [0, 0, 0, 0], [-1, -1, -1, 1])
meanref = tf.reduce_mean(ref, [1, 2])
ltg = tf.slice(img, [0, 0, 0, 1], [-1, -1, -1, 1])
meanltg = tf.reduce_mean(ltg, [1, 2])ylogits = tf.Print(ylogits,
[numltg, meanref, meanltg, ylogits], "...")
TensorFlow 中的 reduce_*函数允许您沿轴求和。因此,第一个函数 reduce_sum(labels)计算批次中标签的总和。由于标签是 0 或 1,这个总和告诉我该批中照明示例的数量。
我还想打印反射率和闪电输入图像补丁的平均值。为此,我想对高度和宽度求和,但保持每个示例和通道独立——这就是为什么在 reduce_mean 调用中看到[1,2]。第一个 tf.slice 获取第一个通道,第二个 slice 获取第二个通道(-TF . slice 中的 1 告诉 TensorFlow 获取那个维度中的所有元素)。
还要注意,我已经在 ylogits 的位置插入了 Print 节点,可以打印整个张量列表。这很重要——您必须将 Print()放入图中实际使用的节点。如果我做了:
numltg = tf.Print(numltg,
[numltg, meanref, meanltg], "...")
整个分支将被优化掉,因为我的模型实际上并没有在任何地方使用 numltg!
一旦我运行代码,我发现每一批都有一个好的(和不同的)闪光点组合,每一批的平均值看起来有点相似,但每一批都不同。
2d。调整洗牌
虽然这不是我们试图解决的问题,但这种批量方式的相似性是很奇怪的。怀着这种好奇心查看代码,我发现我已经硬编码了一个洗牌大小:
dataset = dataset.shuffle(1024)
将此更改为
dataset = dataset.shuffle(batch_size * 50)
解决了批处理问题。
Because successive patches are highly correlated, it is important to shuffle in a large buffer
问题是,因为数据是从滑动窗口创建的,所以连续的例子往往高度相关,所以我需要在足够大的缓冲区内移动,以便从卫星图像的不同部分(或者更好的是,不同的图像)获得例子。在 ResNet 的案例中,每个训练示例都是完全不同的图像,因此这不是他们关心的问题。复制粘贴再次来袭!
2e。ReLu 削波和饱和
但是回到最初的问题。为什么准确度和 RMSE 卡住了?我在这里运气不错。我不得不插入 tf。Print()在某个地方,所以我进入了一个我知道我需要的节点——在我的模型函数(ylogits)的输出节点上。我还碰巧打印出了 ylogits,瞧……每次输入都是不同的值,ylogits 开始是随机的,但很快就变成了零。
为什么 ylogits 为零?仔细查看 ylogits 的计算,我注意到我写了:
ylogits = tf.layers.dense(
convout, 1, activation=tf.nn.relu, kernel_initializer=xavier)
哎呀!通过在输出密集层上设置 ReLu 激活函数,我确保了 ylogits 永远不会为负。与此同时,闪电比非闪电更罕见,因此优化器将 ylogits 推到了它所能采取的最小可能值。也就是零。因为 ReLu 在零度以下饱和,所以东西有可能卡在那里。
分类网络的倒数第二层类似于回归网络的最后一层。它必须是:
ylogits = tf.layers.dense(
convout, 1, **activation=None**, kernel_initializer=xavier)
傻,傻,小虫。发现并修复。咻!
3.NaN 损失
现在,当我运行它,我没有得到一个卡住的准确性指标。更糟。我得了……”南在训练中失利如果有什么会让一个 ML 从业者感到恐惧的话,那就是 NaN 的损失。哦,好吧,如果我能解决这个问题,我会写一篇博文。
NaNs are spooky
与不训练的模型一样,NaN 的损失有几个常见的疑点。尝试自己计算交叉熵损失就是其中之一。但我没有那样做。我计算的损失是:
loss = tf.reduce_mean(
tf.nn.sigmoid_cross_entropy_with_logits(
logits=tf.reshape(ylogits, [-1]),
labels=tf.cast(labels, dtype=tf.float32)))
这应该是数值稳定的。另一个问题是学习率太高。我切换回 AdamOptimizer,尝试设置一个较低的学习率(1e-6)。不去。
另一个问题是输入数据本身可能包含 nan。这应该是不可能的—使用 TFRecords 的好处之一是 TFRecordWriter 不会接受 NaN 值。为了确保万无一失,我回到输入管道,将 np.nan_to_num 添加到将数组插入 TFRecord 的代码段中:
**def** _array_feature(value):value = np.nan_to_num(value.flatten())
**return** tf.train.Feature(float_list=tf.train.FloatList(value=value))
还是不行。
3a。更简单的深度学习模型
也许有太多的重量?我使模型中的层数可配置,并尝试了不同的层数和更小的内核大小:
*convout = img* **for** layer **in** range(nlayers):
nfilters = (nfil // (layer+1))nfilters = 1 **if** nfilters < 1 **else** nfilters
*# convolution* c1 = tf.layers.conv2d(
convout,
filters=nfilters,
kernel_size=ksize,
kernel_initializer=xavier,
strides=1,
padding=**'same'**,
activation=tf.nn.relu)
*# maxpool* convout = tf.layers.max_pooling2d(c1, pool_size=2, strides=2, padding=**'same'**)
**print**(**'Shape of output of {}th layer = {} {}'**.format(
layer + 1, convout.shape, convout))
outlen = convout.shape[1] * convout.shape[2] * convout.shape[3]
p2flat = tf.reshape(convout, [-1, outlen]) *# flattened* **print**(**'Shape of flattened conv layers output = {}'**.format(p2flat.shape))
还是不行。南的损失依然存在。
3b。回到线性
如果我完全去掉深度学习模型会怎么样?还记得我对输入图像的调试统计吗?如果我们试图训练一个模型,仅基于这些工程特征来预测照明,会怎么样?
halfsize = params[**'predsize'**]
qtrsize = halfsize // 2
ref_smbox = tf.slice(img, [0, qtrsize, qtrsize, 0], [-1, halfsize, halfsize, 1])
ltg_smbox = tf.slice(img, [0, qtrsize, qtrsize, 1], [-1, halfsize, halfsize, 1])
ref_bigbox = tf.slice(img, [0, 0, 0, 0], [-1, -1, -1, 1])
ltg_bigbox = tf.slice(img, [0, 0, 0, 1], [-1, -1, -1, 1])
engfeat = tf.concat([
tf.reduce_max(ref_bigbox, [1, 2]), *# [?, 64, 64, 1] -> [?, 1]* tf.reduce_max(ref_smbox, [1, 2]),
tf.reduce_mean(ref_bigbox, [1, 2]),
tf.reduce_mean(ref_smbox, [1, 2]),
tf.reduce_mean(ltg_bigbox, [1, 2]),
tf.reduce_mean(ltg_smbox, [1, 2])
], axis=1)ylogits = tf.layers.dense(
engfeat, 1, activation=None, kernel_initializer=xavier)
我决定创建两组统计数据,一组在 64x64 的盒子中,另一组在 16x16 的盒子中,并用这 6 个输入特征创建一个逻辑回归模型。
不去。还是南。这非常非常奇怪。一个线性模型应该永远不会过时。从数学上来说,这太疯狂了。
但是就在它结束之前,这个模型达到了 75%的准确率。这太有希望了。但是 NaN 的事越来越让人讨厌了。有趣的是,就在它与 loss = NaN“背离”之前,该模型根本没有背离,亏损一直在下降:
3c。验证损失的输入
四处寻找是否有另一种方法来计算损失,我发现现在有一个方便的函数:
loss = tf.losses.sigmoid_cross_entropy(labels,
tf.reshape(ylogits, [-1]))
当然,这可能不能解决任何问题,但是最好使用这个函数,而不是自己调用 reduce_mean。但是在这里,让我们添加断言来缩小问题的范围:
**with** tf.control_dependencies([
tf.Assert(tf.is_numeric_tensor(ylogits),[ylogits]),
tf.assert_non_negative(labels, [labels]),
tf.assert_less_equal(labels, 1, [labels])
]):
loss = tf.losses.sigmoid_cross_entropy(labels,
tf.reshape(ylogits, [-1]))
本质上,我断言 ylogits 是数值型的,每个标签介于 0 和 1 之间。只有满足这些条件,我才能计算损失。否则,程序应该抛出一个错误。
3d。剪辑渐变
断言不会触发,但是程序仍然以 NaN 丢失结束。在这一点上,似乎很清楚问题不在于输入数据(因为我在那里做了一个 nan_to_num)或我们的模型计算(因为断言不会触发)本身。它可能在反向传播中,可能在梯度计算中?
例如,也许一个不寻常(但正确)的例子会导致意想不到的高梯度幅度。让我们通过剪切梯度来限制这种不寻常示例的影响:
optimizer = tf.train.AdamOptimizer(learning_rate=0.001)
optimizer = **tf.contrib.estimator.clip_gradients_by_norm(
optimizer, 5)**
train_op = optimizer.minimize(loss, tf.train.get_global_step())
那也没用。
3e。L2 损失
如果有许多非常相似的输入,权重本身可能会爆炸。随着时间的推移,一个输入节点可能会产生非常高的正幅度,而下一个输入节点可能会产生非常高的负幅度。使网络避免这种情况的一种方法是通过在损失函数中增加一个额外项来惩罚高幅度权重:
l2loss = tf.add_n(
[tf.nn.l2_loss(v) **for** v **in** tf.trainable_variables()])
loss = loss + 0.001 * l2loss
这将获得所有可训练变量(权重和偏差),并根据这些可训练变量的值对损失进行惩罚。理想情况下,我们只惩罚体重(而不是偏见),但这只是为了尝试。
3f。亚当·艾司隆
再多逛逛,我意识到 AdamOptimizer 的文档解释默认的ε值 1e-8 可能有问题。本质上,小的ε值会导致不稳定,即使ε的目的是防止被零除。这回避了为什么这是默认值的问题,但是让我们试试推荐的更大的值。
optimizer = tf.train.AdamOptimizer(
learning_rate=params[**'**learning_rate**'**], **epsilon=0.1)**
这就把 NaN 推得更远了,但还是失败了。
3g。不同的硬件
另一个原因可能是 CUDA 错误之类的。让我们尝试在不同的硬件上进行训练(用 P100 代替特斯拉 K80 ),看看问题是否仍然存在。
trainingInput:
scaleTier: CUSTOM
masterType: complex_model_m_p100
还是不行。
4.重写为 Keras
有时,当留下一个难以解决的 bug 时,最好尝试用一种完全不同的方式重写模型。
Let’s start again
所以,我决定用 Keras 重写模型。这也给了我学习 Keras 的机会,这是我一直想做的事情。用柠檬做柠檬汁之类的。
4a。CNN 与 Batchnorm
在 Keras 中,用 batchnorm 创建 CNN(这将有助于保持范围内的渐变)非常容易。(谢谢你,弗朗索瓦)。简单到我到处都放了 batchnorm。
img = keras.Input(shape=[height, width, 2])
cnn = keras.layers.BatchNormalization()(img)
**for** layer **in** range(nlayers):
nfilters = nfil * (layer + 1)
cnn = keras.layers.Conv2D(nfilters, (ksize, ksize), padding=**'same'**)(cnn)
cnn = keras.layers.Activation(**'elu'**)(cnn)
cnn = keras.layers.BatchNormalization()(cnn)
cnn = keras.layers.MaxPooling2D(pool_size=(2, 2))(cnn)
cnn = keras.layers.Dropout(dprob)(cnn)
cnn = keras.layers.Flatten()(cnn)
ltgprob = keras.layers.Dense(10, activation='sigmoid')(cnn)
还要注意,最后一层直接增加了一个 s 形激活。没有必要在逻辑上浪费时间,因为优化为我们解决了数字问题:
optimizer = tf.keras.optimizers.Adam(lr=params[**'learning_rate'**])
model.compile(optimizer=optimizer,
loss=**'binary_crossentropy'**,
metrics=[**'accuracy'**, **'mse'**])
不错!
不幸的是(你现在知道了),我还是得了 NaNs!
啊。好吧,回到起点。让我们在喀拉斯做所有在 TensorFlow 做过的事情。
4b。Keras 中的特征工程
第一步是忘掉所有这些深度学习的东西,建立一个线性模型。你是怎么做到的?我有一个图像。我需要做特征工程,送到密集层。在 Keras 中,这意味着我必须编写自己的层来完成功能工程:
**def** create_feateng_model(params):
*# input is a 2-channel image* height = width = 2 * params[**’predsize’**]
img = keras.Input(shape=[height, width, 2])
engfeat = keras.layers.Lambda(
**lambda** x: engineered_features(x, height//2))(img)
ltgprob = keras.layers.Dense(1, activation=**’sigmoid’**)(engfeat)
*# create a model* model = keras.Model(img, ltgprob)
engineered_features 与之前的 TensorFlow 函数完全相同!关键思想是,要将 TensorFlow 函数包装到 Keras 层中,可以使用 Lambda 层并调用 TensorFlow 函数。
4c。打印图层
但是我想打印出图层,以确保流过的数字是正确的。我该怎么做?tf。Print()不行,因为,嗯,我没有张量。我有 Keras 层。
嗯,tf。Print()是一个张量流函数,因此,使用相同的 Lambda 层技巧:
**def** print_layer(layer, message, first_n=3, summarize=1024):
**return** keras.layers.Lambda((
**lambda** x: tf.Print(x, [x],
message=message,
first_n=first_n,
summarize=summarize)))(layer)
然后可以调用它作为:
engfeat = print_layer(engfeat, **"engfeat="**)
4d。剪裁渐变
在 Keras 中裁剪渐变?轻松点。每个优化器都支持 clipnorm。
optimizer = tf.keras.optimizers.Adam(lr=params[**'learning_rate'**],
clipnorm=1.)
嘿,我喜欢 Keras 这个东西——它给了我一个漂亮、简单的 API。此外,它与 TensorFlow 很好地互操作,在我需要的时候给我低级别的控制。
NaN is still there, slurping my milkshake
哦,对了。我仍然有南的问题
5.揭露数据
最后一件事,我有点忽略了。NaN 问题也可能由未缩放的数据引起。但是我的反射率和闪电数据都在范围[0,1]内。所以,我根本不需要缩放。
尽管如此,我还是闲着没事。不如我把图像数据归一化(减去均值,除以方差),看看有没有帮助。
Yes, I’m clutching at straws now
为了计算方差,我需要遍历整个数据集,所以这是 Beam 的工作。我可以用张量流变换来做这个,但是现在,让我用 Beam 来破解它。
5a。混洗波束中的训练数据
既然我正在重写我的管道,我也可以一劳永逸地解决洗牌的问题(见第 2d 节)。增加洗牌缓冲区大小是一个黑客。我真的不想按照创建图像补丁的顺序写入数据。让我们随机排列 Apache Beam 中图像补丁的顺序:
*# shuffle the examples so that each small batch doesn't contain
# highly correlated records* examples = (examples
| **'{}_reshuffleA'**.format(step) >> beam.Map(
**lambda** t: (random.randint(1, 1000), t))
| **'{}_reshuffleB'**.format(step) >> beam.GroupByKey()
| **'{}_reshuffleC'**.format(step) >> beam.FlatMap(**lambda** t: t[1]))
本质上,我给每个记录分配一个随机键(1 到 1000 之间),按随机键分组,删除键并写出记录。现在,连续的图像补片不会一个接一个地跟随。
5b。计算阿帕奇波束的方差
如何计算阿帕奇波束的方差?我像往常一样,在 StackOverflow 上搜索一些我可以复制粘贴的东西。不幸的是,我只找到了一个没有答案的问题。哦,好吧,认真编写一个定制的合并器:
import apache_beam as beam
import numpy as npclass MeanStddev(beam.CombineFn):
def create_accumulator(self):
return (0.0, 0.0, 0) # x, x^2, countdef add_input(self, sum_count, input):
(sum, sumsq, count) = sum_count
return sum + input, sumsq + input*input, count + 1def merge_accumulators(self, accumulators):
sums, sumsqs, counts = zip(*accumulators)
return sum(sums), sum(sumsqs), sum(counts)def extract_output(self, sum_count):
(sum, sumsq, count) = sum_count
if count:
mean = sum / count
variance = (sumsq / count) - mean*mean
# -ve value could happen due to rounding
stddev = np.sqrt(variance) if variance > 0 else 0
return {
'mean': mean,
'variance': variance,
'stddev': stddev,
'count': count
}
else:
return {
'mean': float('NaN'),
'variance': float('NaN'),
'stddev': float('NaN'),
'count': 0
}
[1.3, 3.0, 4.2] | beam.CombineGlobally(MeanStddev())
请注意这里的工作流程——我可以在一系列数字上快速测试它,以确保它正常工作。
然后,我去给 StackOverflow 添加了答案。也许我需要的只是一些好的因果报应…
5b。写出平均值、方差
然后,我可以转到我的管道代码并添加:
**if** step == **'train'**:
_ = (examples
| **'get_values'** >> beam.FlatMap(
**lambda** x : [(f, x[f]) **for** f **in** [**'ref'**, **'ltg'**]])
| **'compute_stats'** >> beam.CombinePerKey(MeanStddev())
| **'write_stats'** >> beam.io.Write(beam.io.WriteToText(
os.path.join(options[**'outdir'**], **'stats'**), num_shards=1))
)
本质上,我提取 example[‘ref’]和 example[‘ltg’]并创建元组,然后按键对其进行分组。然后,我可以计算整个数据集上这两幅图像中每个像素的平均值和标准差。
一旦我运行了管道,我就可以打印结果统计数据:
gsutil cat gs://$BUCKET/lightning/preproc/stats*('ltg', {'count': 1028242, 'variance': 0.0770683210620995, 'stddev': 0.2776118172234379, 'mean': 0.08414945119923131})('ref', {'count': 1028242, 'variance': **masked**, 'stddev': 0, 'mean': masked})
蒙面?@#KaTeX parse error: Expected 'EOF', got '#' at position 2: @#̲@#是什么意思?原来掩码数组是一个特殊的数字东西。被屏蔽的值不是 NaN,所以如果用 Numpy 处理它们,nan_to_num()不会对它做任何事情。另一方面,它看起来是数值,所以我所有的张量流断言都不成立。带有屏蔽值的数值运算会产生屏蔽值。
Masked? What the @#@$#@ does masked mean?
屏蔽值就像 NaN 一样——它们会从房间的另一端啜饮你的奶昔,但是常规的 numpy 和 TensorFlow 库方法对屏蔽一无所知。
5c。更改屏蔽值
由于屏蔽只发生在反射率网格中(我自己通过积累闪电创建闪电网格),我必须在读取后将屏蔽值转换成一个好的数字
ref = goesio.read_ir_data(ir_blob_path, griddef)
ref = np.ma.filled(ref, 0) *# mask -> 0*
现在,当我重新运行管道时,我得到了合理的值:
('ref', {'count': 1028242, 'variance': 0.07368491739234752, 'stddev': 0.27144965903892293, 'mean': 0.3200035849321707})('ltg', {'count': 1028242, 'variance': 0.0770683210620995, 'stddev': 0.2776118172234379, 'mean': 0.08414945119923131})
这些都是小数字。不应该有任何缩放的理由。因此,让我们在解决掩蔽问题的情况下进行训练。
这一次,没有楠。相反,程序崩溃,日志显示:
Filling up shuffle buffer (this may take a while): 251889 of 1280000
The replica master 0 ran out-of-memory and exited with a non-zero status of 9(SIGKILL)
5d。减小混洗缓冲区大小
副本零是输入管道(读取数据发生在 CPU 上)。为什么它要一次将 1280000 条记录全部读入 shuffle 缓冲区?
现在,输入数据已经被我的光束/数据流很好地混洗了,我甚至不需要那么大的混洗缓冲区(以前是 5000,见 2d 部分):
dataset = dataset.shuffle(batch_size * 50) *# shuffle by a bit*
12 分钟后,训练以 83%的准确率结束。!!).哪儿都没有 NaNs。
吼吼!
5e。连接模型
请记住,我们只做了一个由工程特征组成的线性模型。让我们并行添加回 convnet。想法是连接两个密集层,这样我的模型架构可以包含 CNN 层和特征工程:
A combined model would be great
这是如何在 Keras 中做到这一点的,其中加入了一些 batchnorm 和 dropouts,因为它们很容易添加:
cnn = keras.layers.BatchNormalization()(img)
**for** layer **in** range(nlayers):
nfilters = nfil * (layer + 1)
cnn = keras.layers.Conv2D(nfilters, (ksize, ksize), padding=**'same'**)(cnn)
cnn = keras.layers.Activation(**'elu'**)(cnn)
cnn = keras.layers.BatchNormalization()(cnn)
cnn = keras.layers.MaxPooling2D(pool_size=(2, 2))(cnn)
cnn = keras.layers.Flatten()(cnn)
cnn = keras.layers.Dropout(dprob)(cnn)
cnn = keras.layers.Dense(10, activation=**'relu'**)(cnn)
*# feature engineering part of model* engfeat = keras.layers.Lambda(
**lambda** x: engineered_features(x, height//2))(img)
*# concatenate the two parts* both = keras.layers.concatenate([cnn, engfeat])
ltgprob = keras.layers.Dense(1, activation=**'sigmoid'**)(both)
现在,我有 85%的准确率。这仍然是一个小数据集(只有 60 天的卫星数据),这可能是深度学习路径没有增加那么多价值的原因。所以,我会回去生成更多的数据,训练更长的时间,在 TPU 上训练等等。
但是这些事情可以等。现在,我要去庆祝了。
十二月版
我们今年阅读量最大的帖子
Join us as an Editorial Associate
我们希望你有一个伟大的一年。在 2018 年最后一期月刊中,我们为您带来了本年度从到数据科学的最热门帖子。如果您最近加入了我们的行列,或者已经阅读了我们的文章很长一段时间,我们将为您带来我们最新的编辑精选:
1.帮助您解开关键概念的谜团,了解更多关于数据科学的知识
2.鼓励您与我们一起发布或加入我们的数据科学社区
3.帮助您在工作场所、下一次黑客马拉松或大学中应用数据科学
James Le 发布 2018 年,概述面向初学者的机器学习 10 大算法。2 月 Nicklas Donges 带我们踏上了一段随机森林和决策树的旅程,而 Susan Li 演示了如何使用 Python 中的 SciKit-Learn 库进行多类文本分类。今年 3 月, Jonny Brooks-Bartlett 的讨论了“现实与期望”,提到了为什么这么多数据科学家离职。
Eugenio culrciello在四月为我们带来了递归神经网络的兴衰。5 月,詹姆斯·洛伊用一篇精彩的介绍使用 Python 构建神经网络的文章引起了希拉里·梅森的转发,并且在西蒙·格林曼的文章中比较了最佳人工智能芯片和人工智能平台云解决方案。最近在 7 月, Michael Galarnyk 的关于如何构建一个伟大的数据科学组合的文章激励了我和数据科学界的许多人。
2018 年还剩下四周,我们希望这些文章真的像一瓶香槟一样“流行”,因为无论你在世界的哪个角落,都欢迎你的新年——我们要求你再次或第一次阅读这些文章。一定要把它们收藏起来,以备将来参考,并在朋友和同事之间分享这些文章。分享就是关爱!
Wendy Wong ,TDS 编辑。
这就是这么多数据科学家离职的原因
由 Jonny Brooks-Bartlett — 8 分钟阅读
**是的,**我是数据科学家,**是的,**你确实没看错标题,但总得有人说出来。我们读到了很多关于数据科学是 21 世纪最性感的工作,以及作为数据科学家可以赚到诱人的钱的故事,这似乎是绝对的梦想工作。
机器学习新手的 10 大算法之旅
由詹姆斯·勒 — 11 分钟读完
在机器学习中,有一个叫做“没有免费的午餐”的定理。简而言之,它指出没有一种算法对每个问题都是最好的,并且它特别适用于监督学习(即预测建模)。
数据科学家需要了解的 5 种聚类算法
由乔治·赛义夫 — 11 分钟读完
聚类是一种涉及数据点分组的机器学习技术。给定一组数据点,我们可以使用聚类算法将每个数据点分类到特定的组中。
如何用 Python 从零开始构建自己的神经网络
由詹姆斯·洛伊 — 6 分钟读完
作为我个人更好地理解深度学习之旅的一部分,我决定在没有 TensorFlow 这样的深度学习库的情况下,从头构建一个神经网络。我认为,理解神经网络的内部工作对任何有抱负的数据科学家都很重要。
随机森林算法
由尼克拉斯·东格斯 — 8 分钟阅读
随机森林是一种灵活、易于使用的机器学习算法,即使没有超参数调整,在大多数情况下也能产生很好的结果。它也是最常用的算法之一,因为它简单,而且可以用于分类和回归任务。
RNN/LSTM 的陷落
通过Eugenio culrciello—9 分钟阅读
我们爱上了循环神经网络(RNN)、长短期记忆(LSTM)及其所有变体。现在是时候放下它们了!
如何构建数据科学产品组合
由迈克尔·加拉尼克 — 17 分钟读完
数据科学怎么找工作?了解足够的统计学、机器学习、编程等知识以便能够找到工作是很困难的。我最近发现的一件事是,相当多的人可能拥有找工作所需的技能,但没有作品集。
使用 Scikit-Learn 进行多类文本分类
由苏珊李 — 11 分钟读完
文本分类在商业领域有很多应用。例如,新闻故事通常是按主题组织的;内容或产品通常按类别进行标记;用户可以根据他们在网上谈论产品或品牌的方式进行分类…
超参数调整 Python 中的随机森林
购买威廉·科尔森 — 12 分钟阅读
因此,我们建立了一个随机森林模型来解决我们的机器学习问题(也许通过遵循这个端到端指南),但我们对结果并不太满意。我们有什么选择?
我们也感谢最近加入我们的所有伟大的新作家,瓦伦蒂诺·康斯坦蒂努,杰森·张硕,奥斯卡·奈格,艾琳娜·尼西奥蒂,约翰·布朗林,约翰·哈特奎斯特,梅兰妮·曾,亚当·拉德兹谢夫斯基,艾丹·莫里森,罗曼·博蒙特 萨拉·沃尔夫,潘卡杰·马图尔,萨比亚萨奇·萨胡,阮·范德梅尔韦,伊拉·科恩,MJ·巴赫马尼,乌里·梅尔哈夫,阿米蒂·拉蒂,梁兆兵,罗伯特·桑多, 我们邀请你看看他们的简介,看看他们的工作。
12 月版:深度学习
9 篇必读文章
人工智能从业者需要应用的 10 种深度学习方法
由詹姆斯·勒 — 13 分钟读完。
过去十年,对机器学习的兴趣激增。你几乎每天都能在计算机科学项目、行业会议和《华尔街日报》上看到机器学习。
订阅我们即将推出的作家快讯 !我们的目标是更好地支持我们的作者,让他们能够进一步参与我们出版物的发展。
理解神经网络中的目标函数
由 Lars Hulstaert — 9 分钟读取。
这篇博文的目标读者是有机器学习经验的人,他们希望对用于训练神经网络的不同目标函数有更好的直觉。
揭开“EM 路由矩阵胶囊”的神秘面纱第 1 部分:概述
通过 Sahaj Garg — 13 分钟读取。
最近,深度学习之父之一杰弗里·辛顿(Geoffrey Hinton)发布了一种革命性的计算机视觉架构:胶囊网络,在机器学习界掀起了波澜。
神经网络的软介绍
由舒邦德赛 — 9 分钟读完。
在过去的几年里,神经网络已经成为机器学习的同义词。最近,我们已经能够制造神经网络,它可以产生栩栩如生的面孔,转移动态艺术风格,甚至可以按年“老化”一个人的照片。
应用深度学习 ( 第一部分、第二部分、第三部分、第四部分)
由 Arden Dertat — 23 分钟读取。
欢迎来到应用深度学习教程系列。我们将从人工神经网络(ANN)开始,特别是前馈神经网络,对几种深度学习技术进行详细分析。
—
一种新型深度神经网络
通过Eugenio culrciello—7 分钟阅读。
又有新一波深度神经网络来了。它们是前馈模型的发展,我们之前详细分析过。
参加 Deeplearning.ai 课程后的感想
通过 Arvind N — 8 分钟读取。
在全职工作和家里蹒跚学步的孩子之间,我用业余时间学习认知科学和人工智能的思想。偶尔会出现一篇很棒的论文/视频/课程,你会立刻被吸引住。
深度网络架构的直观指南
由 Joyce Xu — 9 min 阅读。
在过去的几年里,计算机视觉深度学习的许多进展都可以归结为少数几个神经网络架构。
深度学习之美背后的秘密酱:激活功能初学者指南
由 Mate Labs — 7 分钟读取。
激活功能是获取输入信号并将其转换为输出信号的功能。激活函数将非线性引入网络,这就是为什么我们称之为非线性。
我们也感谢最近加入我们的所有伟大的新作家,萨哈吉·加尔格,克里斯·道塞特,佩曼·泰伊,蒂尔塔杰约蒂·萨卡尔,克莱尔·莱萨奇,贾斯汀·盖奇,鲍里斯·安德亚,贾斯帕·麦克切斯尼,德瓦尔·沙阿,瑞安 安东尼·雷佩托、本·韦伯、杰卡特琳娜·科卡特朱哈、菲利克斯·莫尔、乔治·克拉萨达基斯等等很多人。 我们邀请你看看他们的简介,看看他们的工作。
确定 CNN 的最佳核大小
卷积神经网络(CNN)是从像图像这样的数据点中自动提取有用特征(无需手动调整)以解决像图像分类或对象检测这样的给定任务的神经网络。现在您已经了解了它们在您的数据集上的用途,您开始想知道:除了调整您的网络的各种超参数之外,我如何知道什么是适合网络的内核大小?让我们进一步挖掘!
让我们设定一些共同的基本规则,以便在整个讨论过程中保持在同一平台上:
- 我们将主要研究图像上的 2D 卷积。这些概念也适用于 1D 和三维卷积,但可能不直接相关。
- 像 3x3 这样的 2D 卷积滤波器在尺寸上将总是具有第三维。第三维等于输入图像的通道数。例如,我们对灰度图像(具有 1 个黑白通道)应用 3x3x1 卷积滤镜,而对彩色图像(具有 3 个通道,红色、蓝色和绿色)应用 3x3x3 卷积滤镜。
- 在接下来的讨论中,我们假设零填充。
在卷积中,卷积滤波器滑过图像的所有像素,取它们的点积。我们这样做是希望卷积滤波器加权的像素的线性组合能够从图像中提取某种特征。记住这些事情就完成了:
- 图像中大多数有用的特征通常是局部的,一次取几个局部像素来应用卷积是有意义的。
- 这些有用的特征中的大多数可以在图像中的不止一个地方找到。因此,在整个图像上滑动单个核是有意义的,希望使用相同的核在图像的不同部分提取该特征。
- 此外,使用小内核而不是完全连接的网络的额外好处是受益于重量共享和计算成本的降低。简单解释一下这一点,因为我们对图像中的不同像素组使用相同的核,所以当我们对这些像素组进行卷积时,在这些像素组之间共享相同的权重。并且由于权重的数量少于完全连接的层,我们可以反向传播的权重较少。
既然我们已经将卷积滤波器大小作为可供选择的超参数之一,那么就需要在更小或更大的滤波器大小之间做出选择。让我们快速比较两者,选择最佳滤波器尺寸:
Comparing smaller and larger convolutional kernel sizes theoretically.
现在,我们对使用不同尺寸的提取有了一些了解,接下来我们将针对小尺寸(3x3)和大尺寸(5x5)滤波器使用卷积示例:
Comparing smaller and larger convolutional kernel sizes using a 3x3 and a 5x5 example.
基于上面的比较,我们可以得出结论,较小的内核比较大的内核更受欢迎。
此外,您可能会注意到,与 2x2 或 4x4 内核大小相比,奇数内核大小更受青睐。解释如下:
对于奇数大小的过滤器,所有先前层像素将对称地围绕输出像素。如果没有这种对称性,我们将不得不考虑在使用均匀大小的内核时发生的跨层失真。因此,为了提高实现的简单性,甚至大小的内核过滤器也大多被跳过。如果你认为卷积是从给定像素到中心像素的插值,我们不能使用偶数大小的滤波器插值到中心像素。
因此,一般来说,我们希望使用更小的奇数大小的内核过滤器。但是,由于提取的特征是细粒度的和局部的,没有来自相邻像素的信息,因此从候选最佳滤波器尺寸列表中排除了 1x1。此外,它并没有真正做任何有用的特征提取!
因此,3x3 卷积滤波器通常工作正常,并且经常是流行的选择!
原载于 2018 年 8 月 19 日【icecreamlabs.com】。
通过数据可视化解读印度:印度教育系统
tldr;我为什么这么做,我发现了什么?
当我开始我的数据科学之旅时,我一直知道我想研究具有社会影响的数据。为了给我的第一个项目寻找主题,我开始阅读大量的研究论文、项目、报纸文章并搜索相关数据。这就是我如何阅读 ASER 教育报告并了解 DISE 出版物的。当我调查这些数据时,各种各样的问题突然出现在我的脑海里,“学习水平如何?”、“有百分之多少的孩子不去上学?”、“私立学校的入学率是多少”、“女孩的入学率如何?”、‘学校基础设施的状况如何?’,‘参数是如何随时间变化的?’、'各州之间相比如何?'等等。所以,我决定用它来进行分析,并通过 4d 可视化来探索数据。为什么是 4d 可视化?因为它实现了多维度的探索和比较。
我的发现总结如下:
如果你觉得这个有趣就继续读下去……
阿塞尔和 DISE 前来救援
年度教育状况报告(ASER)是一项年度调查,自 2005 年以来报告了儿童的学校教育状况以及他们完成基本阅读和算术任务的能力。在非政府组织 Pratham 的协助下,这项调查由来自印度几乎所有农村地区的当地伙伴组织的志愿者进行。
地区教育信息系统(DISE)收集全国所有学校的入学率、基础设施、教师和其他可用设施的信息。该数据库由国立教育规划和管理大学开发和维护。自 2002 年以来,DISE 报告每年出版一次。
准备破译
**数据分析参数有哪些?**我选择了 2007 年到 2016 年间的 DISE 和 ASER 数据。DISE 提供了有关入学率、基础设施、学生教师比率和学生教室比率的信息。学习水平、学校类型和校外信息来自 ASER。基于与研究的相关性和随时间的一致性来选择参数。与教学语言和教师有关的参数可以在今后的研究中加以考虑。
R,d3 和酒窝太棒了!使用 R 编程语言进行数据探索和准备,R 编程语言是一种用于统计计算和图形的开源软件。用于 4d 可视化的气泡图是使用 d3 和 dimple javascript 库开发的。事实证明,它们与分析非常相关,非常有用
解冻数据到合适的级别: State 这里是分析的单位。汇总函数用于检测异常值和缺失值。认为没有必要进行异常值处理,并且删除具有缺失值的行。ASER 为新成立的特伦甘纳邦和安得拉邦提供综合信息,而 DISE 则提供单独的数据。这两个州的 DISE 数据已经使用平均值合并。
**数据结构:**在本研究使用的变量中,除总入学人数、家长教师比例和学校教室比例外,所有值均为百分比。每个州的区域是通过将州到区域映射文件与包含 DISE 和激光数据的文件合并而得到的。
可视化效果如何?
为动态 4d 可视化提供了选择下拉框。气泡图中的每个维度都有一个选择下拉框。
更改“选择”下拉列表中的选项会更新相应的轴。x 轴、y 轴和大小下拉框具有与入学、基础设施和学习水平信息相关的相同选项。颜色下拉菜单有两个选项“州”和“地区”。“州”选项为每个州显示不同的颜色,“地区”选项为该州所属的每个地区显示一种颜色。这有助于按州和地区分析信息。故事板设置为“年”。随着一年中的每一个分笔成交点,图表都会更新以显示该年的信息。播放\暂停按钮控制动画。
我发现了什么?
私立学校招生人数大增
尽管大多数儿童(5-16 岁)继续在公立学校接受教育,但近年来私立学校的入学率也在上升,尽管它们的学费很高。即使在像 Uttara Pradesh 这样人口众多且落后的邦,2016 年私立学校的入学率也在 50%左右。这是什么原因呢?是因为设施和老师比官校好吗?
学生教师比率和学生课堂比率正在下降
每名教师的学生人数(PTR)和一个教室的学生人数(SCR)正在下降。这是否意味着学校的数量在增加?私立学校有助于降低比率吗?
厕所、饮水设施等学校基础设施正在改善
大多数州在提供这些设施方面达到了 100%,尽管这些设施的状况可以作为另一项研究的主题。值得注意的是,2009 年生效的《实时教育法》所产生的补助金在改善这些设施方面发挥了作用。
那么,学习水平一点点提高了?
绝对不行!典型的例子是相邻的北方邦和比哈尔邦,这两个地方人口众多,社会经济落后。虽然 Uttara Pradesh 的私立学校入学率接近 50%,但比哈尔邦远远落后,只有 10%左右。但就学习水平而言,两者的表现同样糟糕,2016 年约有 20%的儿童(5-16 岁)无法阅读,类似数量的儿童无法识别数字。那么,影响学习水平的其他因素是什么呢?
自 2012 年以来学习水平急剧下降
自 2012 年以来,大多数州都显示出不能阅读和做数学的儿童(5-16 岁)的比例显著增加。与此同时,能够做除法(最高水平的数学测试)和阅读 std 2 文本(最高水平的阅读测试)的儿童(5-16 岁)的百分比也有所下降。是什么引发了 2012 年以来的这种下跌?
私立学校招生是唯一的地区性趋势
有趣的是,在数据分析参数中没有区域趋势。唯一的例外是私立学校的入学人数。在东部的比哈尔邦、恰尔肯德邦、奥里萨邦和西孟加拉邦,私立学校的入学率很低。私立学校入学率最高的是北部的北方邦、旁遮普邦和哈里亚纳邦。
女孩入学率保持稳定,没有区域障碍
另一个有趣的方面是女孩的入学率自 2007 年以来一直保持稳定。大多数州的女生入学率在总入学率的 45%到 48%之间。一个国家在经济和社会方面的落后并没有影响这个百分比。即使是女生比例较低的邦,如哈里亚纳邦和拉贾斯坦邦,女生入学率也超过了总入学率的 45%。
5%的儿童(5-16 岁)失学
大约 5%的儿童(5-16 岁)没有上学。自 2007 年以来一直保持稳定。他们根本没去上学吗?如果他们以前在学校,是在哪个阶段辍学的?如果不在学校,他们做什么?
结论
我列出了一些我觉得有趣的观察结果。你能找到更多吗?请访问我的 github 页面进行可视化。
总之,学习水平是衡量一个国家教育系统健康状况的最重要的标准。从数据来看,很明显印度做得不是特别好。尽管 2009 年实施了《实时教育法》,中产阶级不断壮大,补助金和其他资源也有所增加。这是什么原因呢?教师的质量影响结果吗?社会经济因素在起作用吗?成人识字和城市化怎么样?
我一定会带着进一步的更新回来…继续检查。
作为随机漫步的决策
Monte Carlo Simulation from Numex Exchange.
我做的每一个决定都是正确的吗?大概不会。当我承认自己会做出错误的决定时会发生什么——我如何利用这些知识来提高我的效率?这篇文章是关于决策是一个随机过程意味着什么,以及如何利用这些知识做得更好。
如果你将一枚公平的硬币抛 10000 次,每次它出现时正面你向前迈一步,而反面你后退一步——你预计能走多远?碰巧,不是很远。
但是假设这个硬币是不公平的——它的重量使得它有 65%的机会正面朝上。你希望这次能走多远?概率告诉我们你平均前进了 3000 步。20%上来个头?倒退六千步。
Random walk position simulation with forward p=0.65 with steps=10000; graphic from WolframAlpha.
这和决策有什么关系?嗯,去年我发了 10,487 封邮件。每一封电子邮件都可以被视为抛硬币,目的是推动我的组织向前发展。每封电子邮件也是一个决定:区分特性的优先级,跟踪构建的交付,起草 RFP 回复。
并非所有的决定都是平等的。在这里,我将讨论影响决策的不同因素,并检查它们的影响。然后,我将利用这种影响来展示决策成功不是二元的,并展示我们可以改进决策的不同方式。
首先,我将回顾以下因素:
- 正确决策的概率
- 决策速度
- 决策的规模或范围
正确决策的概率
这相当简单——如果你更频繁地做出正确的决定,你会在随机漫步中走得更远。如果你不经常做出正确的决定,你会变得更不积极,甚至倒退(并且偏离你的公司目标)。
Random walk position simulation with forward p=0.65 (left) and p=0.75 (right) with steps=10000; graphics from WolframAlpha.
然而,差异的大小相当重要。决策成功增加 10 个百分点——从 65%增加到 75% —会导致向前多走 2000 步。在这种情况下,净收益增加了 40%。因此,作为一名经理,专注于改善下属的决策可以带来巨大的回报——尤其是在表现较差的员工中。甚至可以改善决策的专业指导、高管教育和研讨会也可能值得 40%的净投资回报率。
同样,做出更糟糕的决定也同样有害。与最初的 65%相比,55%的决策成功率减少了 2,000 步的净前进步骤,导致总体结果减少了 67%,这相当具有破坏性。
决策速度
现在让我们看看决策的速度。让我们想象一下,我们做决定的速度慢了 10%,所以除了一个不愉快的收件箱,我们最终只做了 9000 个决定,而不是 10000 个。
Random walk position simulation with forward p=0.65 with steps=10000 (left) and steps = 9000 (right); graphics from WolframAlpha.
决策速度下降 10%会导致前进的步伐减少 250 步,或者前进的步伐减少 8.3%。速度没有产生如此显著影响的原因是,你不仅做出的正确决定更少,因此不会走得更远,而且做出的错误决定也更少,因此不会如此显著地阻碍进步。
Accuracy (left) versus precision (right). ThoughtCo.
另一个变化不大的因素是标准偏差——采用 9,000 步而不是 10,000 个导联,预期结果的偏差会小 5%左右。这可以被忽略,因为决策倾向于关注准确性而不是精确性。毕竟,获得精确的、平庸的结果通常不如分散的、积极的结果令人满意。
决策的规模
考察决策规模的最后一个因素是通过影响力来衡量的。为了模拟这一点,我们增加了步长——一个两倍重要的正确决定会让我们前进两步,而不是一步,但一个错误的决定会让我们后退两步。
Random walk position simulation with forward p=0.65 with steps=10000 and step size = 2; graphic edited, original from WolframAlpha.
当步长增加一倍时,我们的预期结果也会增加一倍,在这个场景中,从 3,000 步增加到 6,000 步。然而,如果你的预期结果是负的(比如如果 p=0.45),那么你就是双倍的结果,并且会进一步亏损。
有趣的是,步长的增加使标准差保持不变,所以你改变了结果,而精度没有任何变化。
综合
既然我们已经检查了影响随机漫步的因素,那么关键的要点是什么呢?
- 做出正确的决定比快速做出决定重要得多。因此,总的来说,花时间仔细考虑事情并做出正确的决定比做出更多的决定更有益。
- 重大决策会对你的结果产生巨大的影响,但不会牺牲精确性。如果我们认为精确度是风险的代表,那么做出更少、更重要的决定对结果更有利。
- 综合这些因素,要最大限度地提高性能,最重要的是做出高影响决策,但低影响决策可能可以忽略不计。因此,能够识别哪些决策可能具有最大的影响,从而能够花时间专注于做出正确的决策是有价值的。
我们应该如何利用这些要点来改进我们的决策?
- 快速确定哪些决策是最重要的,并专注于这些决策,指派你的最佳决策者。
- 可能很快就能做出的小决定是委派的好对象。这为团队成员提供了在迭代过程中实践和决策的机会。随着时间的推移,反馈循环和教育是改善结果的关键。
- 需要长时间的小决定应该妥协。举个例子,一个普通的产品经理可能会花费大量低效的时间来回答关于产品调色板的小决定——抛硬币会造成多大的伤害?或者,在你的组织中寻找快速做出这些决定的能力——在这个例子中,让一个 UI 设计师来负责这个决定可能会提高决策的速度,从而提高结果。
- 对决策过程进行时间限制,以避免浪费时间对决策结果做出微不足道的改进。最终,花费两倍的时间来提高微小的成功概率会成为一种浪费。
我们可以随机地思考决策吗?我们工作的其他哪些方面可以并且应该重新思考?请在下面的评论中告诉我你的想法!
决策树:一种像人脑一样工作的算法
“sun light passing through green leafed tree” by Jeremy Bishop on Unsplash
决策树是机器学习中最常用的算法之一,主要用于分类,也用于回归问题。每当我们在做决定前问自己一个问题时,我们的大脑就像决策树一样工作。比如:外面是阴天吗?如果是的话,我会带一把伞。
当训练一个数据集对一个变量进行分类时,决策树的思想是根据某个特征值将数据划分成更小的数据集,直到目标变量全部归入一个类别。当人脑根据经验(即多云的天空)决定选择“分裂特征”时,计算机根据最大信息增益分裂数据集。让我们定义一个简单的问题,然后进行一些计算,看看这到底意味着什么!
Cats and dogs dataset
假设我们要构建一个决策树,根据体重和身高来确定宠物是猫还是狗。我们可以根据两个特征之一的特定值来划分这些数据点,例如:对于大于 15 磅的体重,我确信该宠物是一只狗,至少根据这个非常简单的数据集是这样的。但是如果权重小于这个值,我的子集将包含两只猫和一只狗,所以我需要再次分割子集,直到只剩下一个类。换句话说,直到我的所有子集都是纯 T4。我实际上为这些数据值画了一个决策树,如下所示:
Decision tree example.
每棵树都从一个根节点开始,即第一次分裂。不需要想太多,我们可以很容易地找到不同类型的根节点来分割数据,并在相同的步骤中找到解决方案。但是计算机如何决定如何定义节点呢?毫无疑问,它将寻找最有效的方式,为了理解这是什么,我们需要引入基尼:“最常用的不平等衡量方法”。这个不等式指的是节点后面每个子集中的目标类。为此,它可以在每次拆分后计算,并且根据不等式在一个节点后如何变化,我们还可以定义“信息增益”。
Definition of Gini
为了计算基尼系数,我们考虑在一个节点后找到每个类的概率,我们对这些值的平方求和,然后从 1 中减去这个值。由于这个原因,当一个子集是纯的(即其中只有一个类)时,Gini 将是 0,因为找到那个类的概率确实是 1!在这种情况下,我们说我们已经到达了一片叶子,因为当我们达到目标时,没有必要再分裂了。但是如果我们看上面的图片,在错误情况下的根节点之后,我们有一个包含 3 个观察值的子集,其中 2 个是猫,1 个是狗。如果我们想计算该子集的基尼系数,我们有:
Gini of the resulting dataset for weight greater or equal to 15 lbs
除了基尼系数,另一个可以用来计算阶层不平等的指标是熵。它们有着相同的目的,但熵的大小略有不同;然而,为此目的,我们将只使用基尼系数。
根据我们选择的分裂策略,我们将为每个子集提供不同的基尼值,并且根据节点后的基尼值,我们可以定义信息增益:
Definition of Information Gain
这被定义为父母的基尼系数和子女基尼系数的加权平均值之差。如果我们参考上面的例子,通过简单地应用定义,知道初始数据集的基尼等于 0.48,我们可以计算根节点之后的信息增益(在 15 磅的权重值处分裂):
Information gain after root node
然后,决策树将考虑所有可能的拆分,并选择具有最高信息增益的拆分。其实我们做点编码,看看什么是按 Python 的决策树!
通过运行下面的代码,我们从零开始构建数据框架,只用几行代码就能适应模型。
注意:在训练模型之前训练/测试 split 是一个很好的做法,可以防止过度拟合,也可以仔细检查这种模型在看不见的数据上的表现。在这种情况下,我跳过了这一步,因为数据帧只包含少量的观察值。
需要指出的是,在实例化DecisionTreeClassifier
时,我没有在括号中指定任何参数。当处理非常大的数据集时,为了防止你的树失去控制和过度拟合,查看max_depth
来指定你的树的最大分裂级别是非常有用的。此外,设置max_features
也很有用,这是一个在搜索最佳分割时限制要查看的预测值数量的参数。此外,如果您希望您的树基于熵而不是基尼进行优化,只需在实例化对象时编写criterion = 'entropy'
即可。如果您想进一步了解如何调整模型,请参考决策树文档。
太好了,我们造好了模型!但是这到底意味着什么呢?决策树的妙处在于它很容易被解释,所以让我们来绘制它吧!为了运行下面的代码片段,你可能需要先在你的笔记本上运行!pip install pydotplus pydot2
。
这段代码的输出如下图所示。
很酷,对吧?在上面我“手工制作”的决策树中,我选择了 15 磅的权重作为根节点,算法决定对相同的变量进行分割,但值为 12。这创建了一个只有狗的叶子(事实上,对于大于 12 的重量,基尼= 0)。从根节点后的真实条件生成的子集,已根据值为 8.5 的高度变量进一步分割。这最后一次分裂产生了两个具有零基尼值的纯 T2 子集。
那么,我们为什么应该或不应该使用决策树呢?以下是利弊的简要列表:
优点
- 它非常容易理解,尤其是当我们需要将我们的发现传达给非技术观众的时候
- 它能很好地处理有噪声或不完整的数据
- 它可用于回归和分类问题
缺点
- 它可能是不稳定的,这意味着数据中的小变化可能会转化为模型中的大变化
- 它往往会过度拟合,这意味着低偏差但高方差:即,即使训练数据上的分数很高,在看不见的数据上也可能表现不佳
幸运的是,有许多技术可以处理过度拟合,这是决策树的主要缺点。通过使用 bagging 或 boosting 方法,我们可以从决策树概念出发,通过使用[RandomForestClassifier](http://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html)
或[AdaBoostClassifier](http://scikit-learn.org/stable/modules/generated/sklearn.ensemble.AdaBoostClassifier.html)
等模型来开发更精确的分析。这些都是集成方法,但是随机森林通过增强捕获生成了许多“新数据集”(即,用替换采样原始数据帧);它为每一个新的数据帧拟合一棵树,并通过对森林中的所有树进行平均来进行预测。相反,Ada Boost 是一个自适应树,它通过调整不正确的分类实例,同时始终使用相同的数据帧来进行自我学习。
在 R 中使用 mlr 进行机器学习:决策树的逐步方法
优化性能的超参数调整。
我个人喜欢使用mlr
来执行我的机器学习任务,但是你也可以使用任何你喜欢的其他库。
首先让我们加载相关的库:
mlr
用于机器学习算法FSelector
进行特征选择。(同样,你可以使用任何你想要的特征选择库)rpart.plot
因为我想可视化这棵树,所以我将使用rpart
决策树算法。
library(mlr)
library(FSelector)
library(rpart.plot) glimpse(Diabetes)Observations: 768 Variables: 6
$ Pregnancies <fct> Yes, Yes, Yes, Yes, No, Yes, Yes...
$ Glucose <fct> Hyperglycemia, Normal, Hyperglyc...
$ BMI <fct> Obese, Overweight, Normal, Overw...
$ DiabetesPedigreeFunction <dbl> 0.627, 0.351, 0.672, 0.167, 2.28... $ Age <int> 50, 31, 32, 21, 33, 30, 26, 29, ... $ Outcome <fct> Positive, Negative, Positive, Ne...
看看我在上一篇文章中处理过的数据集,它显示了我们将要处理的变量。
训练和测试设备
我将使用 80/20 训练/测试数据集。
set.seed(1000)
train_index <- sample(1:nrow(Diabetes), 0.8 * nrow(Diabetes))
test_index <- setdiff(1:nrow(Diabetes), train_index)
train <- Diabetes[train_index,] test <- Diabetes[test_index,]list( train = summary(train), test = summary(test) )
- 训练集显示我们的目标变量有 212 个正面结果和 402 个负面结果。
- 测试集显示我们有 56 个正面结果和 98 个负面结果。
我们的目标变量存在明显的类别不平衡,因为它偏向于“阴性”(无糖尿病),我们将发现更难建立一个“阳性”结果的预测模型。
你可以通过重新平衡类来解决这个问题,这需要重新采样。但是,我将在预测阶段调整概率阈值。我不知道这是否会解决任何潜在的问题,但阈值调整允许你改变预测,以给出一个完全不同的结果。
决策图表
(dt_task <- makeClassifTask(data=train, target="Outcome"))Supervised task: train
Type: classif
Target: Outcome
Observations: 614
Features: numerics factors ordered functionals
2 3 0 0
Missings: FALSE
Has weights: FALSE
Has blocking: FALSE
Has coordinates: FALSE
Classes: 2 Positive Negative
212 402
Positive class: Positive
首先,我们必须使用我们的训练集进行分类任务。在这里,我们可以定义我们试图解决的机器学习问题的类型,并定义目标变量。
我们可以看到,Outcome
的Positive
等级已经默认为机器学习任务中的正类。情况并非总是如此。您可以通过指定Positive=x
来更改它(其中‘x’是您想要预测的变量的目标级别)。在这种情况下,我们希望预测糖尿病患者(即Outcome
变量的Positive
水平)。
学习者
(dt_prob <- makeLearner('classif.rpart', predict.type="prob"))Learner classif.rpart from package rpart
Type: classif
Name: Decision Tree; Short name: rpart
Class: classif.rpart
Properties: twoclass,multiclass,missings,numerics,factors,ordered,prob,weights,featimp
Predict-Type: prob
Hyperparameters: xval=0
在创建分类任务后,我们需要创建一个学习器,它将在以后接受我们的任务来学习数据。我选择了 rpart 决策树算法。这是递归分割决策树。
特征选择
为了选择哪些特征提供了预测Positive
的最佳机会,generateFilterValuesData
给我们每个特征一个分数。这可以用PlotFilterValues
来绘制。每个变量的分数取决于您选择的标准。这里我选择信息增益、卡方和增益比作为我的标准。
generateFilterValuesData(dt_task, method = c("information.gain","chi.squared", "gain.ratio")) %>% plotFilterValues()
generateFeatureImportanceData
功能也以类似的方式工作。除了它将根据给定的性能标准向我们显示每个特性的重要性。我选择了真阳性率和曲线下面积。
generateFeatureImportanceData(task=dt_task, learner = dt_prob,measure = tpr, interaction = FALSE)FeatureImportance: Task: train Interaction: FALSE Learner: classif.rpart Measure: tpr Contrast: function (x, y) x - y Aggregation: function (x, ...) UseMethod("mean") Replace: TRUE Number of Monte-Carlo iterations: 50 Local: FALSE tpr
Pregnancies 0
Glucose -0.1869811
BMI -0.1443396
DiabetesPedigreeFunction -0.06339623
Age -0.06896226generateFeatureImportanceData(task=dt_task, learner = dt_prob,measure = auc, interaction = FALSE)FeatureImportance: Task: train
Interaction: FALSE
Learner: classif.rpart
Measure: auc
Contrast: function (x, y) x - y
Aggregation: function (x, ...)
UseMethod("mean")
Replace: TRUE
Number of Monte-Carlo iterations: 50
Local: FALSE auc
Pregnancies 0
Glucose -0.1336535
BMI -0.07317023
DiabetesPedigreeFunction -0.01907362
Age -0.08251478
从上面的输出中我们可以看到:
- 信息增益和增益率显示怀孕得分为零或低分。
generateFeatureImportanceData
将 TPR 和 AUC 作为绩效衡量指标时,显示妊娠得分为零。
纵观所有证据,Pregnancies
将是我丢弃的唯一变量。其他变量仍然显示出对某些标准的预测能力。
鉴于我们只剩下 4 个特征,可能存在不符合我的模型的风险。对此的另一个理由是,我首先没有很多行数据要处理。这是关于什么是合适的数据量和特征(维数灾难)的持续讨论。
看下面,我已经把怀孕从我们的训练和测试集中去掉了,并用我们的新训练集做了一个新的分类任务。
set.seed(1000)
train <- select(train, -Pregnancies)
test <- select(test, -Pregnancies)list( train = summary(train), test = summary(test) )
另一个问题是,在葡萄糖类别中,“低血糖症”在整个数据集中只有 5 种表现。当我们进行交叉验证时,这将是一个问题,因为几乎可以肯定,这个水平将在任何折叠中缺失。这将不允许模型在以后被适当地训练。因此,我们需要从两个数据集中去除低血糖:
train <- filter(train, Glucose!='Hypoglycemia') %>% droplevels()
test <- filter(test, Glucose!='Hypoglycemia') %>% droplevels()list( train = summary(train), test = summary(test) )
因为我们现在有了新的数据集,所以我们需要基于新的训练集进行新的分类任务。
(dt_task <- makeClassifTask(data=train, target="Outcome"))Supervised task: train
Type: classif
Target: Outcome
Observations: 609
Features: numerics factors ordered functionals
2 2 0 0
Missings: FALSE
Has
weights: FALSE
Has blocking: FALSE
Has coordinates: FALSE
Classes: 2
Positive Negative
210 399
Positive class: Positive
超参数调谐
现在,任何机器学习算法都需要我们根据自己的判断来调整超参数。调整超参数是为机器学习参数选择一个值的过程,目标是获得您想要的性能水平。
在mlr
中调整机器学习算法包括以下步骤:
- 定义搜索空间。
- 定义优化算法(又名调优方法)。
- 定义评估方法(即重新取样策略和性能测量)。
搜索空间
因此,定义搜索空间就是为给定的特征/变量指定参数。因为我们在这篇文章中关注决策树,使用getParamSet(learner)
函数,我们可以获得我们想要的算法的超参数。
我们需要rpart
学习者的参数。
getParamSet("classif.rpart")
getParamSet(“classif.rpart”)
我们可以看到rpart
有 10 个超参数,只有xval
是不可调整的(即不能改变)。
以下是对上述参数的解释:
minsplit
一个节点中的最小观察数,例程甚至会尝试为其计算一个分裂。默认值为 20。调整这个参数可以节省计算时间,因为较小的节点几乎总是被交叉验证删除。
minbucket
终端节点中的最小观察数。默认是 *minspit/3*
(虽然不知道这是不是最优选择)。
maxcompete
如果设置为 1,这将显示在节点上给出最佳分割的变量。如果设置大于 1,那么它会给你第二,第三等…最好的。它对计算时间没有影响,对使用的内存影响最小。
maxdepth
这控制了树可以建多深。
cp
这是复杂度参数。它越低,树就会长得越大。一个 *cp=1*
会导致根本没有树。这也有助于修剪树木。无论 *cp*
的值是多少,我们都需要小心不要让树过度生长。更高的复杂性参数会导致过度绘制的树。我个人发现一个非常高的复杂度参数值(在我的例子中超过 0.3)会由于过度运行而导致树的欠拟合,但是这也取决于你拥有的特性的数量。
我以前没有使用过代理变量,所以在这种情况下我将省略它们。我只是不想在没有足够的理解来解释我自己的情况下在这一点上继续下去。
所以现在我们需要把超参数设置成我们想要的。记住没有一个确切的正确答案。我们需要定义空间并运行搜索,以根据我们定义的空间自动找到哪些超参数值将给出最佳结果。这意味着超参数的变化(大或小)可能会影响性能,也可能不会。
所以,如果你时间紧迫,要么跟随你的直觉,要么为超参数定义一个大空间,如果你有一台强大的机器和非凡的耐心,让mlr
做繁重的工作。
dt_param <- makeParamSet(
makeDiscreteParam("minsplit", values=seq(5,10,1)), makeDiscreteParam("minbucket", values=seq(round(5/3,0), round(10/3,0), 1)),
makeNumericParam("cp", lower = 0.01, upper = 0.05), makeDiscreteParam("maxcompete", values=6), makeDiscreteParam("usesurrogate", values=0), makeDiscreteParam("maxdepth", values=10) )
对于我正在使用的模型,我将设置:
minsplit
= [5,6,7,8,9,10]minbucket
= [5/3,10/3]cp
= [0.01,0.05]maxcompete
= 6usesurrogate
= 0maxdepth
= 10
我没有定义大空间的主要原因是我以前定义过,它运行了大约 4 个小时,有 100,000 个超参数组合。对我个人来说,这是太多的时间了,除非我正在做一个能从中受益匪浅的项目。
最优化算法
我们可用的标准但缓慢的算法之一是网格搜索来选择一组适当的参数。
ctrl = makeTuneControlGrid()
这就是我们如何指定我们想要运行网格搜索。有了上面指定的空间,在dt_param
的情况下,我们得到了 120 种可能的组合。
使用重采样评估调优
指定上述内容后,我们现在可以进行调优过程了。我们定义一个重采样策略并记录性能。
我们将重采样策略设置为分层采样的三重交叉验证。如果你在目标变量中有阶级不平衡,分层抽样是有用的。它将尝试在每个文件夹中拥有相同数量的类。通常,为了从 k 重交叉验证中获得好的结果,3-5 重交叉验证将会很好。折叠多少次也取决于你有多少数据(即因素/类别的级别数,行数)。
rdesc = makeResampleDesc("CV", iters = 3L, stratify=TRUE)
调谐
我们现在可以使用tuneParams
向我们展示我们指定的超参数值的什么组合会给出最佳结果。
在measures
中,您可以定义想要查看的绩效标准。我还想在交叉验证时从测试集中得到真实阳性率的标准差。这个增加的度量应该给我们一个指示,表明这个度量的每个折叠之间的价差有多大。
set.seed(1000)
(dt_tuneparam <- tuneParams(learner=dt_prob,
resampling=rdesc,
measures=list(tpr,auc, fnr, mmce, tnr, setAggregation(tpr, test.sd)),
par.set=dt_param,
control=ctrl,
task=dt_task,
show.info = TRUE) )Tune result: Op. pars: minsplit=9; minbucket=2; cp=0.0144; maxcompete=6; usesurrogate=0; maxdepth=10 tpr.test.mean=0.6095238,auc.test.mean=0.7807376,fnr.test.mean=0.3904762,mmce.test.mean=0.2725780,tnr.test.mean=0.7894737,tpr.test.sd=0.0704698
在运行调谐器时,我们看到我们设置的参数有 120 种可能的组合。输出底部的最终结果(即[Tune] Result:...)
给出了我们的最佳组合。这将在每次运行时改变。只要您可以看到类似的性能结果,继续使用当前数据集应该没有危险。如果性能结果开始偏离太多,数据可能不充分。
在最优超参数中,测试集中的真阳性率的标准偏差是 0.0704698,这是相对较低的,并且可以给我们稍后在预测时将获得的真阳性率一个概念。如果预测的 TPR 与交叉验证期间获得的 TPR 接近或在 1 个标准偏差之间,则表明我们的模型运行良好(这只是我的观点,其他人可能需要更小的扩散)
注意 *tuneParams*
知道哪个绩效衡量标准最小化和最大化。例如,它知道如何最大化精确度和最小化错误率(mmce)。
另一方面,正如我之前提到的,我定义了一个大的搜索空间,花了大约 4 个小时完成,最终得到了 100,000 个组合。这是结果:
【调】结果:minsplit = 17min bucket = 7;cp = 0.0433max compete = 4;use surrogate = 0;max depth = 7:TPR . test . mean = 0.6904762,auc.test.mean=0.7277720,f1.test.mean=0.6156823,acc.test.mean=0.7283265,mmce.test.mean=0.2716735,timepredict.test.mean=0.000000,tnr.test.mean=0.7460928
虽然 TPR 更高,但我将使用我以前的超参数,因为它的计算成本更低。
最优超参数
list( `Optimal HyperParameters` = dt_tuneparam$x,
`Optimal Metrics` = dt_tuneparam$y )$`Optimal HyperParameters`
$`Optimal HyperParameters`$minsplit [1] 9
$`Optimal HyperParameters`$minbucket [1] 2
$`Optimal HyperParameters`$cp [1] 0.01444444
$`Optimal HyperParameters`$maxcompete [1] 6
$`Optimal HyperParameters`$usesurrogate [1] 0
$`Optimal HyperParameters`$maxdepth [1] 10 $`Optimal Metrics`
tpr.test.mean auc.test.mean fnr.test.mean mmce.test.mean
0.60952381 0.78073756 0.39047619 0.27257800
tnr.test.mean tpr.test.sd
0.78947368 0.07046976
使用dt_tuneparam$x
我们可以提取最优值,而dt_tuneparam$y
给我们相应的性能指标。
setHyperPars
将使用其最佳值调整学习者。
dtree <- setHyperPars(dt_prob, par.vals = dt_tuneparam$x)
模特培训
set.seed(1000) dtree_train <- train(learner=dtree, task=dt_task) getLearnerModel(dtree_train)n= 609 node), split, n, loss, yval, (yprob) * denotes terminal node 1) root 609 210 Negative (0.34482759 0.65517241) 2) Glucose=Hyperglycemia 149 46 Positive (0.69127517 0.30872483) 4) BMI=Obese 117 28 Positive (0.76068376 0.23931624) * 5) BMI=Normal,Overweight 32 14 Negative (0.43750000 0.56250000) * 3) Glucose=Normal 460 107 Negative (0.23260870 0.76739130) 6) Age>=28.5 215 78 Negative (0.36279070 0.63720930) 12) BMI=Underweight,Overweight,Obese 184 77 Negative (0.41847826 0.58152174) 24) DiabetesPedigreeFunction>=0.5275 61 23 Positive (0.62295082 0.37704918) * 25) DiabetesPedigreeFunction< 0.5275 123 39 Negative (0.31707317 0.68292683) * 13) BMI=Normal 31 1 Negative (0.03225806 0.96774194) * 7) Age< 28.5 245 29 Negative (0.11836735 0.88163265) *rpart.plot(dtree_train$learner.model, roundint=FALSE, varlen=3, type = 3, clip.right.labs = FALSE, yesno = 2)
Decision Tree Classification of the Pima Indian Diabetes dataset
rpart.rules(dtree_train$learner.model, roundint = FALSE)Outcome 0.24 when Glucose is Hyperglycemia & BMI is Obese 0.38 when Glucose is Normal & BMI is Underweight or Overweight or Obese & Age >= 29 & DiabetesPedigreeFunction >= 0.53 0.56 when Glucose is Hyperglycemia & BMI is Normal or Overweight
0.68 when Glucose is Normal & BMI is Underweight or Overweight or Obese & Age >= 29 & DiabetesPedigreeFunction < 0.53 0.88 when Glucose is Normal & Age < 29 0.97 when Glucose is Normal & BMI is Normal & Age >= 29
在训练决策树之后,我能够用rpart.plot
函数绘制它,并且我可以很容易地用rpart.rules
看到树的规则。因为mlr
是一个机器学习算法的包装器,我可以根据自己的喜好定制,这只是一个例子。
模型预测(测试)
现在,我们让经过训练的学习者使用我们的测试数据进行预测。
set.seed(1000)
(dtree_predict <- predict(dtree_train, newdata = test))Prediction: 154 observations predict.type: prob threshold: Positive=0.50,Negative=0.50 time: 0.00 truth prob.Positive prob.Negative response 1 Negative 0.3170732 0.6829268 Negative 2 Positive 0.6229508 0.3770492 Positive 3 Negative 0.4375000 0.5625000 Negative 4 Negative 0.3170732 0.6829268 Negative 5 Positive 0.7606838 0.2393162 Positive 6 Negative 0.1183673 0.8816327 Negative ... (#rows: 154, #cols: 4)
对每一行进行分类的阈值是 50/50。这是默认的,但以后可以更改(我将这样做)。
dtree_predict %>% calculateROCMeasures()
现在我们有了模型的混淆矩阵。我们看到它在预测Negative
结果方面表现出色,但在预测Positive
结果方面表现不佳。这本质上是由于我们数据集中的类别不平衡。这就是为什么阈值是一个更容易的策略来获得我们的首选模型。当然,如果我们的数据中有平衡的类和更多的观察行会更好,但这并不总是一个选择或现实。
为了更连贯地查看我们模型的性能,我们可以使用我编写的以下代码以一种可展示的方式来查看它。
Performance <- performance(dtree_predict, measures = list(tpr,auc,mmce, acc,tnr)) %>%
as.data.frame(row.names = c("True Positive","Area Under Curve", "Mean Misclassification Error","Accuracy","True Negative")) Performance %>% kable(caption="Performance of Decision Tree",digits = 2, format = 'html', col.names = "Result")
这些指标相当令人满意。但是我们仍然可以实现更高的 TPR,如下所述。在某些情况下,包括这一点,TPR 将是最重要的,而不是 TNR。但是在我看来,我需要达到一个令人满意的 TNR,因为如果不是这样,我的错误分类率将会很高。
阈值处理
(dtree_threshold <- generateThreshVsPerfData(dtree_predict, measures = list(tpr,auc, mmce,tnr)) %>% plotThreshVsPerf() + geom_point() )
Performance vs Classification Threshold
我个人对这个模型的目标是获得一个可接受的和令人满意的True Positive Rate
和True Negative Rate
。由于 AUC 在所有阈值上保持不变,我们不需要关心它。通过改变阈值,我故意创建了一个有偏见的模型,但这是一个正常的机器学习问题。偏差-方差权衡 是如此常见,我们需要学会驾驭它。这是一个完全不同的话题,任何做机器学习的人都需要一些这方面的知识。
下面您将看到 3 个不同的阈值:
- TPR 低于 100%的最大阈值。
- TPR 高于 80%的最低阈值。
- 两个阈值的平均值。
- 我们的 TNR 高于 70%的最大阈值。
*list(
`TPR Threshold for 100%` = tpr_threshold100 <- dtree_threshold$data$threshold[ which.max(dtree_threshold$data$performance[ dtree_threshold$data$measure=="True positive rate"]<1)], `TPR Threshold for 80%` = tpr_threshold80 <- dtree_threshold$data$threshold[ which.min(dtree_threshold$data$performance[ dtree_threshold$data$measure=="True positive rate"]>0.80)], `Average Threshold` = avg_threshold <- mean(c(tpr_threshold100,tpr_threshold80)), `TNR Threshold for 80%` = tnr_threshold80 <- dtree_threshold$data$threshold[ which.max(dtree_threshold$data$performance[ dtree_threshold$data$measure=="True negative rate"]>0.70)] )*
$ TPR Threshold for 100%
[1] 0.1212121
$TPR Threshold for 80%
[1]0.3232323
$ Average Threshold
[1] 0.222222
$ TNR Threshold for 80%
[1] 0.3232323
使用avg_threhsold
再次预测我们的模型,我们可以获得以下性能指标。然而,从图中的阈值和我在上面定义的阈值来看,我们的真实负利率将受到很大的冲击。
*DecisionTree <- dtree_predict %>% setThreshold(avg_threshold) (dt_performance <- DecisionTree %>% performance(measures = list(tpr,auc, mmce,tnr)) )tpr auc mmce tnr
0.8214286 0.7693149 0.3376623 0.5714286*
我们的 TPR 现在是 82.14 %。这与 50/50 的门槛有着巨大的差异。我们的 TNR 已经降低到 57.14 %,但这是我们的模型偏向 TPR 的结果。我在下面解释新的混淆矩阵。
*(dt_cm <- DecisionTree %>% calculateROCMeasures() )*
我们注意到以下其他变化:
- 由于阈值差异,TNR 减少到 57 %。
- FPR 增加到了 43 %。这意味着该模型具有增加的 1 型错误的可能性,其中当它实际上不存在时,它将检测到糖尿病。为了获得更高的 TPR,这是我们必须做出的牺牲。
- 准确率降到了 66 %。这是改变门槛的另一个后果。这不是一个值得关注的原因,因为我们的模型做了我想让它做的事情——具有很高的真实阳性率。如果我们只关心对某个结果的准确预测,那么准确性就不是模型性能的充分衡量标准。大多数情况下都是这样,但即使这样,您仍然需要深入研究模型性能。
*Performance_threshold <- performance(DecisionTree, measures = list(tpr,auc, mmce, acc, tnr)) %>%
as.data.frame(row.names = c("True Positive","Area Under Curve", "Mean Misclassification Error","Accuracy","True Negative")) Performance_threshold %>% kable(caption=paste("Performance of Decision Tree\n\nAfter Thresholding to",(avg_threshold*100) %>% round(0),'%'), digits = 2, format = 'html', col.names = 'RESULT')*
更改分类阈值后,我们可以看到新的性能指标。阈值对于用户和问题的领域来说是非常主观的。有时,根据错误分类的代价,有必要将模型的性能向一个方向倾斜。在这种情况下,对于患者来说会有许多假阳性,但是如果将这些患者错误分类的成本不是太高,那么获得正确诊断的更高可能性(即真阳性)是值得的。
最后,设置阈值是必要的,尤其是当你有一个不平衡的数据集时(例如,不平衡数量的目标水平)。在该数据集中,Negative
比Positives
多得多。如果更加平衡,则可能不需要根据模型对每个目标的分类程度来设定阈值。
如果您有任何问题或顾虑,请发表评论进行讨论,我邀请任何对此感兴趣的人加入。谢谢你
最初发表于 轻松数据科学与 R 和 Python 。
决策树——数据科学家解决哈姆莱特难题的灵丹妙药
source: my sketch book
决策树属于监督机器学习算法家族,被认为是所有数据科学问题的万能药。数据科学家经常会说一些诙谐的话,比如,“每当问题陈述让你陷入哈姆雷特式的困境,而你又想不出任何算法时(不管情况如何),就使用决策树吧!”。
无论是在工业界还是在 kaggle 竞赛中,经常可以看到决策树或者至少是从它演化而来的算法( Bagging,Boosting ensemble )被虔诚地实践着。
决策树是一种通用的机器学习方法,能够执行回归和分类任务。几乎所有现实世界的问题本质上都是非线性的,决策树可以帮助你消除数据中的非线性。这种算法非常直观,易于理解,可以直观地解释——这是每个企业首先想要的。
一个人还能从模特身上得到什么?简单神奇的✨
决策树是倒着画的,意思是根在上面,叶子在下面。决策树主要相信分而治之的规则。
基本术语
让我们看看决策树使用的基本术语:
- **根节点:**它代表整个群体或样本,并进一步分成两个或多个同类集合。
- **拆分:**是将一个节点分成两个或两个以上子节点的过程。
- **决策节点:**当一个子节点分裂成更多的子节点时,则称为决策节点。
- **叶/端节点:**不再分裂的节点称为叶或端节点。
- **剪枝:**当我们删除一个决策节点的子节点时,这个过程叫做剪枝。
- **分支/子树:**整个树的一个子部分称为分支或子树。
- **父节点和子节点:**被划分为子节点的节点称为子节点的父节点,子节点是父节点的子节点。
直觉
有两种类型决策树:
答。分类决策树 & B 。回归决策树
- 分类树帮助您对数据进行分类,因此它们可以处理分类数据,例如贷款状态(批准/未批准)、垃圾邮件/非垃圾邮件等。
- 回归树旨在帮助您预测结果,结果可以是一个真实的数字,例如一个人的收入,房子的销售价格等。
source: algobeans.com — Classification Tree illustration
假设我们有两个特征 X 和 Y,在右边的面板中,您可以观察到有几个分散的数据点。绿叶和灰叶是因变量中的两类。所以决策树基本上做的是,在几次迭代中把整个数据集切割成切片。如图所示,在 X = 0.5 处有分裂 1,在 Y =0.5 处有分裂 2,在 X = 0.25 处有分裂 3。
拆分是精心安排的,以最大化每个拆分中特定类别的数量,这意味着决策树试图在每个节点上实现同质分布。从右图中,您可以注意到绿叶类和灰色叶类的分离最终在每个隔间中形成同质结构。
算法背后的数学
决策树采用多种方法来拆分节点。最常用的是基尼系数、熵、卡方等。
我。基尼指数
- 根据它,如果我们从一个群体中随机选择两个项目,那么它们必须是同一类,如果群体是纯的,概率是 1。
- 这是衡量杂质的标准。因此,基尼系数越低,同质性越高。
- 数学上表示为
基尼指数= 1-[§ +(1-P) ]
其中 P 是该节点中阳性样本的比例。
- 基尼指数为“0”表示节点是纯的。因此,这意味着不需要进一步拆分。
- 涉及的步骤-
使用公式计算子节点的基尼系数。
使用分裂每个节点的加权基尼系数来计算分裂的基尼系数
二。卡方
- 这有助于找出子节点和父节点之间差异的统计显著性。
- 我们通过目标变量的观察频率和预期频率之间的标准化差异的平方和来衡量它。
- 数学上表示为
卡方=((实际—预期)/预期)/2
- 这是纯度的衡量标准。因此,卡方值越高,子节点和父节点之间差异的统计显著性越高。
- 涉及的步骤-
通过计算成功和失败的偏差来计算单个节点的卡方
使用拆分的每个节点的所有成功和失败卡方的总和计算拆分的卡方
三。熵
- 它是对正在处理的信息的随机性的一种度量。
- 熵值越大,就越难从这些信息中得出任何结论。
- 数学上表示为
熵= -plog§ - qlog(q)
- 这里 p 和 q 分别是该节点中成功和失败概率。
- 测井记录以“2”为基数。
- 涉及的步骤-
计算父节点的熵
计算 split 中每个单独节点的熵,并计算 split 中所有可用子节点的加权平均值。
四。方差减少
- 上面提到的所有方法都属于分类决策树。对于目标变量连续的回归决策树,采用方差缩减法。
- 它使用标准的方差公式来选择最佳分割。选择具有较低方差的分裂作为分裂群体的标准。
- 数学上表示为
Where, X -> actual values, X −> mean and N -> number of observations
- 涉及的步骤-
计算每个节点的方差。
将每个分割的方差计算为每个节点方差的加权平均值。
让我们举一个离散的例子,牢牢抓住这个主题
形象化
如前所述,决策树是可以解释和可视化的。Graphviz 库来拯救正在研究 python 的数据科学家。这里的 是下载 Graphviz 的指南。
我已经尝试使用 IRIS 数据集来可视化决策树和下图。这是通过一段代码实现的,你甚至不需要在你的机器上下载 Graphviz。
Find Hands-On-Code for visualizing decision treeHERE
有什么条件?
嗯,我们的灵丹妙药听起来的确像是生命的救星,但这太好了,令人难以置信,不是吗?!对决策树建模时最大的危险是它们的过度拟合倾向。如果在决策树上没有设置限制,它会给你 100%的训练集的准确性,因为在最坏的情况下,它会为每个观察结果建立 1 个终端节点。
因此,在对决策树进行建模时,防止过度拟合是至关重要的,这可以通过两种方式实现:
A. *对树大小设置约束(超参数调优)&*b .树修剪
Illustration of different Hyper-parameters used in Decision Tree
进一步阅读
请参考下面的链接,以便更深入地理解这个概念
*恭喜你!!*关于掌握机器学习中一个古老的算法:)
其他发表的文章:
如果你喜欢我的文章,那就花几分钟看看我的其他博客吧-
决策树集成——打包和提升
随机森林和梯度增强
我们每天都在使用决策树技术来规划我们的生活,我们只是没有给那些决策过程起一个花哨的名字。
企业使用这些有监督的机器学习技术,如决策树,来做出更好的决策,并获得更多利润。决策树已经存在了很长一段时间,而且众所周知也存在偏差和差异。简单的树会有很大的偏差,复杂的树会有很大的变化。
集成方法 ,将几个决策树结合起来,产生比利用单个决策树更好的预测性能。集成模型背后的主要原理是一组弱学习者聚集在一起形成一个强学习者。
让我们讨论几个执行集合决策树的技术:
1.制袋材料
2.助推
装袋 (Bootstrap Aggregation)在我们的目标是降低决策树的方差时使用。这里的想法是从随机选择的训练样本中创建几个数据子集并替换。现在,每个子集数据的集合都被用来训练他们的决策树。结果,我们得到了不同模型的集合。使用来自不同树的所有预测的平均值,这比单个决策树更健壮。
随机森林 是套袋的延伸。这需要一个额外的步骤,除了获取数据的随机子集,还需要随机选择特征,而不是使用所有特征来生成树。当你有很多随机的树时。它叫做随机森林😊
让我们看一下实施随机森林的步骤:
1 。假设训练数据集中有 N 个观测值和 M 个特征。首先,从训练数据集中随机抽取一个样本进行替换。
2 。随机选择 M 个特征的子集,并且使用给出最佳分裂的特征来迭代地分裂节点。
3 。这棵树长到最大。
4 。重复上述步骤,并基于来自 n 棵树的预测的集合给出预测。
使用随机森林技术的优势:
- 非常好地处理更高维度的数据。
- 处理缺失值并保持缺失数据的准确性。
使用随机森林技术的缺点:
- 由于最终预测基于子集树的平均预测,因此它不会为回归模型提供精确的值。
Boosting 是另一种创建预测器集合的集成技术。在这种技术中,学习者是顺序学习的,早期的学习者将简单的模型与数据拟合,然后分析数据的错误。换句话说,我们拟合连续的树(随机样本),并且在每一步,目标都是解决来自先前树的净误差。
当一个输入被一个假设错误分类时,它的权重会增加,以便下一个假设更有可能将其正确分类。通过在最后组合整个集合,将弱学习者转换成表现更好的模型。
梯度增强是对增强方法的扩展。
梯度推进=梯度下降+推进。
它使用梯度下降算法,可以优化任何可微损失函数。一个系综树被一个接一个地构建,并且各个树被顺序地求和。下一个采油树试图恢复损失(实际值和预测值之间的差异)。
使用梯度增强技术的优势:
- 支持不同的损失函数。
- 互动良好。
使用梯度增强技术的缺点:
- 容易过度拟合。
- 需要仔细调整不同的超参数
决策树拥抱
昨天我了解了决策树以及如何在 python shell 中计算它们。决策树是一个非常酷和强大的决策支持工具,它本质上显示了一个算法。如上面的简单图表所示,我们看到决策树从一个根节点开始,然后分支到不同深度的内部节点。在每一步,您的数据都按照不同的属性进行分类,从而将数据引导到更深的内部节点,或者最终到达决策点,这也称为树叶。
为了应用我新学到的知识,我得到了一个关于大学录取的小数据集。数据集有 4 列:学生是否被录取,学生的 GRE 成绩,学生的 GPA(4.0 分)和学校的声望(4 分)。在通过移除空值来清理数据之后,我面临的任务是在最大深度 1、2、3 和无限深度上构建决策树,以查看我是否能够根据其他 3 个值准确预测某个学生是否被录取。首先,我计算了交叉验证分数,以确定每个深度的准确性,结果按深度顺序分别为 67.50%、70.77%、71.55%和 60.96%。现在得到这些数字是很棒的,然而有趣的部分开始于我制作如下所示的可视化决策树:
Max Depth 1
Max Depth 2
Max Depth 3
Max Depth None
这真的很酷,能够看到到底发生了什么,以获得上述准确性数字。正如我们所看到的,深度 2 和 3 是最准确的,但是我也想运行深度 4 和 5,看看我是否能得到更准确的结果。我们还可以看到,将最大深度设置为“无”会给我们一个很好的过度拟合的例子。
虽然决策树和其他工具一样都是令人惊奇的工具,但是它们有两个最大的缺点:
- 不稳定性:决策树严重依赖于提供给它们的数据,尽管一棵树可以变得非常精确,但是如果你给它提供额外的数据,整棵树就可能被抛弃。
- 复杂性:与其他决策模型相比,决策树易于使用,但是一个非常复杂的具有多个分支的决策树在准备和计算上都非常耗时。
所有的决策树都是很好的工具,比如随机森林,它使用一种集合方法(另一个话题)。我肯定会在未来的数据科学职业生涯中使用这些工具,并且很高兴能够更深入地研究其他机器学习应用程序!
机器学习中的决策树
决策树是一种类似流程图的结构,其中每个内部节点代表一个特征上的test
(例如,硬币是正面还是反面),每个叶节点代表一个class label
(计算所有特征后做出的决定),分支代表导致这些类别标签的特征的结合。从根到叶的路径代表classification rules
。下图说明了用标签(下雨(是),不下雨(否))进行决策的决策树的基本流程。
Decision Tree for Rain Forecasting
决策树是statistics
、data mining
和machine learning
中使用的预测建模方法之一。
决策树是通过一种算法方法构建的,这种算法方法可以根据不同的条件识别分割数据集的方法。这是监督学习中最广泛使用和最实用的方法之一。决策树是一种非参数 监督学习方法,用于分类和回归任务。
目标变量可以取一组离散值的树模型称为分类树。目标变量可以取连续值(通常是实数)的决策树被称为回归树。分类和回归树(CART)是这个的通称。
在这篇文章中,我将尝试用例子来解释。
数据格式
数据以表格记录的形式出现。
(x,Y)=(x1,x2,x3,....,xk,Y)
因变量 Y 是我们试图理解、分类或概括的目标变量。向量 x 由特征 x1、x2、x3 等组成。,用于该任务。
示例
training_data = [
['Green', 3, 'Apple'],
['Yellow', 3, 'Apple'],
['Red', 1, 'Grape'],
['Red', 1, 'Grape'],
['Yellow', 3, 'Lemon'],
]
# Header = ["Color", "diameter", "Label"]
# The last column is the label.
# The first two columns are features.
决策树的生成方法
在做决策树的时候,在树的每个节点我们会问不同类型的问题。基于所提出的问题,我们将计算与之对应的信息增益。
信息增益
信息增益用于决定在构建树的每一步中分割哪个特征。简单是最好的,所以我们想保持我们的树小。要做到这一点,在每一步我们都应该选择产生最纯净子节点的分裂。一种常用的纯度测量方法叫做信息。对于树的每个节点,信息值测量一个特征给我们多少关于类的信息。具有最高信息增益的分裂将被作为第一分裂,并且该过程将继续,直到所有子节点都是纯的,或者直到信息增益为 0。
提问
class Question:
"""A Question is used to partition a dataset. This class just records a 'column number' (e.g., 0 for Color) and a
'column value' (e.g., Green). The 'match' method is used to compare
the feature value in an example to the feature value stored in the
question. See the demo below.
""" def __init__(self, column, value):
self.column = column
self.value = value def match(self, example):
# Compare the feature value in an example to the
# feature value in this question.
val = example[self.column]
if is_numeric(val):
return val >= self.value
else:
return val == self.value def __repr__(self):
# This is just a helper method to print
# the question in a readable format.
condition = "=="
if is_numeric(self.value):
condition = ">="
return "Is %s %s %s?" % (
header[self.column], condition, str(self.value))
让我们尝试查询问题及其输出。
Question(1, 3) ## Is diameter >= 3?
Question(0, "Green") ## Is color == Green?
现在,我们将尝试根据提问的问题对数据集进行分区。在每个步骤中,数据将被分为两类。
def partition(rows, question):
"""Partitions a dataset. For each row in the dataset, check if it matches the question. If
so, add it to 'true rows', otherwise, add it to 'false rows'.
"""
true_rows, false_rows = [], []
for row in rows:
if question.match(row):
true_rows.append(row)
else:
false_rows.append(row)
return true_rows, false_rows
# Let's partition the training data based on whether rows are Red.
true_rows, false_rows = partition(training_data, Question(0, 'Red'))
# This will contain all the 'Red' rows.
true_rows ## [['Red', 1, 'Grape'], ['Red', 1, 'Grape']]
false_rows ## [['Green', 3, 'Apple'], ['Yellow', 3, 'Apple'], ['Yellow', 3, 'Lemon']]
构建决策树的算法通常是自上而下的,在best
分割项目集的每一步选择一个变量。不同的算法使用不同的度量标准来测量best
。
基尼杂质
首先让我们了解一下纯和不纯的含义。
纯的
纯意味着,在数据集的选定样本中,所有数据都属于同一类(纯)。
肮脏的
不纯意味着,数据是不同类的混合物。
基尼系数的定义
基尼系数是对随机变量新实例不正确分类的可能性的一种度量,前提是该新实例是根据数据集中类别标签的分布随机分类的。
如果我们的数据集是Pure
,那么不正确分类的可能性是 0。如果我们的样本是不同类别的混合物,那么不正确分类的可能性将会很高。
计算基尼杂质。
def gini(rows):
"""Calculate the Gini Impurity for a list of rows.
There are a few different ways to do this, I thought this one was
the most concise. See:
https://en.wikipedia.org/wiki/Decision_tree_learning#Gini_impurity
"""
counts = class_counts(rows)
impurity = 1
for lbl in counts:
prob_of_lbl = counts[lbl] / float(len(rows))
impurity -= prob_of_lbl**2
return impurity
示例
# Demo 1:
# Let's look at some example to understand how Gini Impurity works.
#
# First, we'll look at a dataset with no mixing.
no_mixing = [['Apple'],
['Apple']]
# this will return 0
gini(no_mixing) ## output=0
## Demo 2:
# Now, we'll look at dataset with a 50:50 apples:oranges ratio
some_mixing = [['Apple'],
['Orange']]
# this will return 0.5 - meaning, there's a 50% chance of misclassifying
# a random example we draw from the dataset.
gini(some_mixing) ##output=0.5
## Demo 3:
# Now, we'll look at a dataset with many different labels
lots_of_mixing = [['Apple'],
['Orange'],
['Grape'],
['Grapefruit'],
['Blueberry']]
# This will return 0.8
gini(lots_of_mixing) ##output=0.8
#######
制作决策树的步骤
- 获取行(数据集)的列表,这些行被考虑用于生成决策树(在每个节点递归)。
- 计算我们数据集的
uncertanity
或Gini impurity
或我们的data is mixed up
有多少等等。 - 生成需要在该节点询问所有问题的列表。
- 根据每个问题将行划分为
True rows
和False rows
。 - 根据基尼系数和上一步数据划分计算信息增益。
- 根据每个问题更新最高信息增益。
- 基于信息增益(更高的信息增益)更新最佳问题。
- 在最佳问题上划分节点。从步骤 1 再次重复,直到我们得到纯节点(叶节点)。
上述步骤的代码
def find_best_split(rows):
"""Find the best question to ask by iterating over every feature / value
and calculating the information gain."""
best_gain = 0 # keep track of the best information gain
best_question = None # keep train of the feature / value that produced it
current_uncertainty = gini(rows)
n_features = len(rows[0]) - 1 # number of columns for col in range(n_features): # for each feature values = set([row[col] for row in rows]) # unique values in the column for val in values: # for each value question = Question(col, val) # try splitting the dataset
true_rows, false_rows = partition(rows, question) # Skip this split if it doesn't divide the
# dataset.
if len(true_rows) == 0 or len(false_rows) == 0:
continue # Calculate the information gain from this split
gain = info_gain(true_rows, false_rows, current_uncertainty) # You actually can use '>' instead of '>=' here
# but I wanted the tree to look a certain way for our
# toy dataset.
if gain >= best_gain:
best_gain, best_question = gain, question return best_gain, best_question
#######
# Demo:
# Find the best question to ask first for our toy dataset.
best_gain, best_question = find_best_split(training_data)
best_question
## output - Is diameter >= 3?
现在,根据上面讨论的步骤,在每个节点递归地构建决策树。
def build_tree(rows):
"""Builds the tree. Rules of recursion: 1) Believe that it works. 2) Start by checking
for the base case (no further information gain). 3) Prepare for
giant stack traces.
""" # Try partitioning the dataset on each of the unique attribute,
# calculate the information gain,
# and return the question that produces the highest gain.
gain, question = find_best_split(rows) # Base case: no further info gain
# Since we can ask no further questions,
# we'll return a leaf.
if gain == 0:
return Leaf(rows) # If we reach here, we have found a useful feature / value
# to partition on.
true_rows, false_rows = partition(rows, question) # Recursively build the true branch.
true_branch = build_tree(true_rows) # Recursively build the false branch.
false_branch = build_tree(false_rows) # Return a Question node.
# This records the best feature / value to ask at this point,
# as well as the branches to follow
# dependingo on the answer.
return Decision_Node(question, true_branch, false_branch)
构建决策树
让我们根据训练数据建立决策树。
training_data = [
['Green', 3, 'Apple'],
['Yellow', 3, 'Apple'],
['Red', 1, 'Grape'],
['Red', 1, 'Grape'],
['Yellow', 3, 'Lemon'],
]
# Header = ["Color", "diameter", "Label"]
# The last column is the label.
# The first two columns are features.
my_tree = build_tree(training_data)
print_tree(my_tree)
输出
Is diameter >= 3?
--> True:
Is color == Yellow?
--> True:
Predict {'Lemon': 1, 'Apple': 1}
--> False:
Predict {'Apple': 1}
--> False:
Predict {'Grape': 2}
从上面的输出我们可以看到,在每一步,数据被分成True
和False
行。这个过程一直重复,直到我们到达信息增益为 0 的叶节点,并且由于节点是纯的,所以进一步的数据分割是不可能的。
决策树的优势
- 易于使用和理解。
- 可以处理分类数据和数字数据。
- 抵抗离群值,因此需要很少的数据预处理。
决策树的缺点
- 容易过度拟合。
- 需要对他们的表现进行某种衡量。
- 需要小心调整参数。
- 如果某些职业占优势,可能会创建有偏见的学习树。
如何避免决策树模型过拟合
过拟合是机器学习中每个模型的主要问题之一。如果模型过度拟合,它将很难推广到新的样本。为了避免决策树过度拟合**,我们移除了利用低重要性特征的分支。这种方法被称为修剪或后修剪。**这样,我们将降低树的复杂性,并因此通过减少过拟合来提高预测准确性。
修剪应该减少学习树的大小,而不会降低由交叉验证集测量的预测准确性。有两种主要的修剪技术。
- *最小误差:*树被修剪回交叉验证误差最小的点。
- *最小的树:*树被修剪得比最小误差稍远。从技术上讲,修剪创建了交叉验证误差在最小误差的 1 个标准误差内的决策树。
提前停止或预修剪
防止过度拟合的另一种方法是,在生成样本非常少的叶子之前,尝试尽早停止树构建过程。这种试探法被称为提前停止,但有时也被称为预修剪决策树。
在分裂树的每个阶段,我们检查交叉验证错误。如果误差没有显著减小,那么我们停止。过早停止可能会因停止过早而不足。当前的拆分可能没什么好处,但在完成拆分后,后续的拆分会更显著地减少错误。
提前停止和修剪可以一起使用,单独使用,或者根本不使用。后期修剪决策树在数学上更加严格,找到一棵树至少和早期停止一样好。提前停止是一种快速解决的启发式方法。如果与修剪一起使用,尽早停止可以节省时间。毕竟,为什么建造一棵树只是为了再次修剪它?
现实生活中的决策树
- 选择要旅行的航班
假设您需要为下一次旅行选择航班。我们该如何着手呢?我们先查一下那天的航班是否有空。如果没有,我们会寻找其他日期,但是如果有,我们会寻找航班的持续时间。如果我们只想要直达航班,那么我们会查看该航班的价格是否在您预先定义的预算内。如果太贵的话,我们看看其他的航班,或者我们预订它!
- 处理深夜的渴望
Source: Google
决策树在现实生活中还有很多应用。更多决策树的应用可以查看这个和这个。
从这篇文章中,我试图解释决策树的基础知识以及它是如何工作的。您可以在 github 找到本文使用的源代码。
希望你喜欢这篇文章。如果有任何修改或建议,请直接在这篇文章或 LinkedIn 上给我发消息。快乐学习—干杯:)
决策树:完全介绍
决策树是许多监督学习算法中的一种,任何人都可以基于一些历史数据对未来事件进行预测,尽管没有一种通用工具对所有问题都是最佳的,但决策树非常受欢迎,并在许多机器学习应用中证明非常有效。
为了理解决策树背后的直觉,考虑这样一个问题:设计一个算法来自动区分苹果和梨(类 标签),仅给出它们的宽度和高度测量值(特征)。
苹果往往又矮又胖,而梨通常又高又瘦。基于这些知识,我们可以问一系列的问题,这些问题最终会导致对神秘水果的真实类别的有根据的猜测。例如,为了对一个水果进行分类,我们可能会首先询问宽度是否小于 7.35 厘米,然后我们可以询问高度是否小于 7.4 厘米。如果这两个问题的答案都是是,那么我们就可以(非常有把握地)断定这个没有标签的商品是苹果。事实上,我们刚刚描述的过程正是决策树的过程。
A visual representation of a decision tree and how this translates into 2D feature space
当我们看到我们所有的问题或分裂是如何源于一个根问题(根节点)并依次分支以最终到达某个终端决策(叶节点)时,(倒置)树的类比是有意义的。在树的旁边,我们可以确切地看到这是如何转换到二维特征空间的。分割将平面分成多个盒子,每个盒子对应一个观察分类(苹果/梨)。这个非常简单的概念在创建自动水果标签系统方面做得非常好,现代计算机可以很容易地采用这种方法来分类每秒成千上万的观察结果。
到目前为止,我们讨论的内容中还缺少一样东西:我们如何构建一个决策树?随着我们获得越来越复杂的数据,反复试验将成为一项相当艰巨的任务,即使我们采用这种方法,我们也有可能制作大量的树,我们如何设计一个最佳的树呢?下一节讨论从标记数据构建决策树的过程。
构建决策树
给定一组带标签的数据(训练数据),我们希望构建一个决策树,该决策树将对训练数据和任何新的未观察到的数据做出准确的预测。根据所讨论的数据,决策树可能需要比前一个例子更多的分裂,但概念总是相同的:进行一系列良好的分裂,以正确地将观察分类为其真正的类标签。那么,我们如何定义什么是好的分割呢?
嗯,在每次分割时,我们希望找到一个特征,以这样一种方式分割标记的数据,使子节点比它们来自的父节点更加同质。换句话说,随着我们沿着树向下移动,我们希望节点变得更加纯净,我们希望选择我们的分裂来最大化节点纯净度的增加。
从数字上衡量节点纯度有两种流行的选择,但现在我们将关注熵(及其替代物,基尼杂质,稍后讨论)。更具体地说,对于观测值{x₁,x₂,…,xn} ∈ X 其中 P(xᵢ)是观测值的相对频率 i ,那么熵被定义为:
这个等式给出了较纯节点的低值,因此我们希望选择最小化熵的分裂。这似乎有道理,但有一个问题:这种设置可能会被父节点的某些分裂选择所滥用,例如一个大的子节点包含几乎所有的观察值,而第二个小的子节点是纯的。这给树增加了另一个分裂,并没有真正提高整体性能(因为它只占一个极值),同时也给树增加了不必要的深度和复杂性,特别是如果这在同一个树中重复发生的话。
解决办法?信息增益。这是一种流行的信息论方法,该方法将分裂前父节点的熵与分裂后子节点的加权和的熵进行比较,其中权重与每个节点中的观察数量成比例。正如我们所要求的,这有效地惩罚了基于大小的小而纯的节点。更正式地,对于特征 F 和一组观察值 X,信息增益被定义为:
由于信息增益减去子节点的熵,我们选择的分裂将是最大化信息增益的分裂。通过研究上图中显示的示例,并比较我们从同一根节点上的两个不同的建议拆分中获得的信息增益值,这一切变得更加清楚。
注意,第一个提出的分裂仅略微提高了节点纯度,信息增益仅为 0.0213。然而,第二次分裂将根节点分裂成两个完全纯的节点,这通过信息增益分数 1 来反映。显然,更好的分裂选择可以通过选择基于熵值最大化信息增益的分裂来衡量。
现在这个难题只剩下最后一块了,然后我们准备从头开始构建我们自己的决策树,那就是选择在哪里分割连续的特征。连续数据(如高度或宽度)可以拆分为无限个值,而分类数据(如水果颜色或血型)只能拆分有限个值并计算信息增益。考虑以下两个特征:
- 水果颜色(分类)-{绿色,红色,黄色,其他}
- 果高(连续)- {5.65,5.78,6.34,6.84,6.86}
对于水果颜色,我们可以很容易地检查所有可能的分裂,并选择一个最大化信息增益,如前所述。当我们尝试用这种方法计算水果高度时,有太多的值可能会被分割。我们可以使用 5.92、5.93 甚至 5.92534……应该使用哪个?显而易见的选择是取两个值之间的中点,在这种情况下,我们将有四个值进行分割,并检查信息增益。不过,这并不能很好地扩展。如果我们在数据集中有 1000 个不同的水果块,我们将有 999 个可能的分裂来检查,这可能会变得相当计算量大。解决方案是应用一种流行的聚类算法,例如 k-means ,将水果高度分成预定义数量的组,这样我们的数据可能如下所示:
现在我们只需要检查更易管理的潜在分裂数量(那些是 6.06 和 6.59,你能看出为什么吗?).我们已经有效地将连续值转换为离散区间,并且在实践中,这通常能够获得最佳的信息增益,而不会浪费时间检查不必要的额外值。
差不多就是这样!这就是我们开始构建决策树所需要的所有理论。我们可以将所有这些放在一个决策树算法中,该算法大致基于最初的 ID3 实现。在下面的伪代码中,S
指的是一组数据(如苹果/梨数据),C
指的是类别标签(如苹果和梨),A
指的是特征(如高度或宽度),而v
指的是一个特征可能被分割成的值(如高度< 7.4 厘米)。
**ID3(S):**
IF all examples in S belong to class C:
- return new leaf node and label with class C
ELSE:
- select a feature A based on some feature selection criterion
- generate a new tree node with A as the test feature
- FOR EACH value v[i] of A:
- let S[i] ⊂ S contain all examples with A = v[i]
- build subtree by applying ID3(S[i])
下一节将继续探讨一些方法,我们可以通过防止一种常见的机器学习陷阱(称为过拟合)来提高实践中的性能。
过度拟合
我们前面提到过,我们的决策树(事实上,在所有的分类问题中)的一个可取的特征是良好的概括。这意味着,我们希望模型符合标记的训练数据,以做出与新的看不见的观察一样准确的预测。当这种情况没有发生时,通常是由于一种称为过度拟合的现象。在决策树中,当树中的拆分对于训练数据来说定义得过于具体时,就会出现这种情况。下面我们观察一个决策树,该决策树已经添加了三个连续的分裂以便对单个异常值进行分类,实际上这些额外的分裂不会提高新数据的性能,事实上可能会使其恶化。
Adding three new splits to capture a single data point is probably overfitting
有三种方法可以防止决策树过度拟合:
- 早期停止-构建决策树,同时应用一些标准,在决策树过度适应训练数据之前停止决策树的增长。
- 修剪-构建决策树并允许它过度适应训练数据,然后将其修剪回去以移除导致过度适应的元素。
- 数据预处理—在构建树之前对初始数据进行一些更改
我们将在这里讨论前两种方法。
提前停止(或预修剪)是指在决策树过度适应训练数据之前,我们提前停止决策树的增长。这里的想法是在树产生过度的小生境分裂之前停止树的生长,这不能很好地概括,在实践中,这经常被使用。回想一下,在前面的部分中,根节点顺序地添加分裂,直到子节点是纯的,现在我们需要一个替代规则,告诉树在节点是纯的之前停止增长,并根据它们的多数类对新的终端节点进行分类。我们可以设想出大量的潜在停止规则,因此下面是一些流行选择的非穷尽列表:
- 最大树深度—只需预先定义一个任意的最大深度值(或最大分裂数),一旦树达到该值,生长过程就会终止。
- 节点中的最小数量-定义出现在任何子节点中的最小观察数量,以使分割有效。
- 杂质的最小减少量——定义可接受的分流的最小可接受的杂质减少量。
- 最大特征-严格来说不是停止规则,而是仅考虑要分割的可用特征的子集,这可能会提高最终树的泛化能力。
We wish to find a tree that will generalise well on the population
另一方面,修剪(或后修剪)采用已经过度拟合的树,并进行一些调整以减少/消除观察到的过度拟合。一个好的剪枝规则通常会通过使用独立的测试集来查明没有很好概括的分裂,并将它们从树中移除。同样,有许多不同的方法来实现修剪,但有三种流行的选择:
- 临界值修剪—根据在树构建阶段完成的计算,回顾性地估计每个节点的强度。没有达到某个临界值的节点将被删除,除非该分支上更远的节点达到了临界值。
- 错误复杂性修剪—生成一系列树,每个树通过对整个树进行不同程度的修剪来生成,并通过使用独立数据集评估其性能来选择其中一个树。
- 减少错误修剪—在整个树中运行独立的测试数据,并且对于每个非叶节点,比较来自该节点的子树被保留还是被移除时的错误数量。使用新的测试数据,被修剪的节点通常会比子树产生更少的错误。发现性能差异最大的节点被剪除,并且继续这个过程,直到进一步的剪除将增加误分类率。
当谈到在实践中使用决策树时,对于过度拟合没有一个放之四海而皆准的解决方案。设计最佳决策树通常需要反复试验以及一些领域知识,因此,应该尝试许多上述方法。这是一个称为超参数优化的过程。
进一步的考虑
到目前为止所讨论的内容涵盖了决策树分类器背后的主要理论,这一部分(希望)回答我们可能有的任何剩余问题或考虑。
处理不一致的数据 —在现实世界中,数据集并不总是像我们希望的那样整齐,不一致的数据就是一个例子。当两个相同的观察值具有不同的类别标签时,会出现不一致的数据,在我们的数据中,如果两个水果具有相同的高度和宽度测量值,但由于一个是梨,另一个是苹果而具有不同的标签,则可能会出现这种情况。显然,在这种情况下,最终的叶节点不可能是纯的,因为没有我们可以选择的特征来区分这两种水果。当这种情况发生时,通常的方法是将所讨论的不纯节点的类预测设置为该节点中多数类的预测。换句话说,如果这个节点包含五个苹果和一个梨,决策树将预测这个节点中的新观察结果被标记为苹果。然而,如果节点具有来自多数类的相等观察值,则可能必须随机选择标签。
处理缺失值 —当一个或多个观测特征由于某种原因没有包含在数据中时,就会出现缺失值。如果我们的数据包含一个苹果,宽度值为 7.5 厘米,但没有高度测量,那么这将被认为是一个缺失值。缺失值观察是有问题的,因为任何缺失的特征都不能被考虑用于分割,并且这扰乱了构建决策树的整个过程。在决策树的早期版本中,带有缺失值的观察值被简单地从构建过程中丢弃。然而,最近出现了复杂的方法,允许保留缺失值的信息。
R 的rpart
包中使用了一种这样的方法,它确保任何具有因变量和至少一个自变量的值的观察值都将参与建模。使用这一程序,信息增益公式进行了微小的调整,以考虑到在选择分割时包含缺失值的观测值。一旦定义了分割,就引入了替代变量的概念来进行预测。这些可以被认为是在相关特征缺失的情况下使用的备份变量。创建一个备选替代变量的排序列表,如果它们产生的结果比盲目采用多数类更好,则保留它们。这种方法允许我们使用带有缺失值的观察值来构建决策树以及进行预测。
斜分割 —到目前为止,我们只考虑了基于单个特征的分割,但值得注意的是,我们可以扩展这种方法,考虑使用特征的线性组合。在某些适当的情况下,这些非正交或倾斜分割可能更有效。下面是苹果和梨数据集的倾斜分割示例,其中使用高宽比有利于在一次分割中正确分类所有观察值。然而,使用倾斜分割的好处也伴随着一些缺点,这些缺点包括可解释性降低以及缺失值和异常值的影响增加。
同样值得注意的是,上面的决策树也是所谓的决策树桩。决策树桩是一棵决策树,其根节点只有一个单独的分支,所有创建的节点都是终端节点。
基尼系数 —基尼系数是对随机变量新实例不正确分类的可能性的测量,前提是该新实例是根据数据集中类别标签的分布随机分类的。它可以定义为:
在实践中,除了由于使用对数函数而使熵的计算速度略有下降之外,基尼系数和熵的性能实际上没有差别。
回归树——从技术上讲,本博客中讨论的决策树是一种分类树。任务是从有限的可能性集合中识别一个观察值属于哪一类或哪一类(例如苹果或梨)。然而,有时我们的响应变量可以是数字而不是分类变量(例如水果价格),在这种情况下,我们解决的是一个稍微不同的问题。请注意,这仅指响应变量(我们试图预测的)是数字的情况,而不是预测变量(我们用来进行预测的),因为我们已经知道如何处理数字预测变量。在响应变量是数字的情况下,任务变成了回归问题,在这种情况下,我们正在制作所谓的回归树。
幸运的是,回归树和分类树没有太大的不同,我们需要做的唯一调整是如何测量杂质。我们现在希望选择使子节点中的误差平方和最小化的分裂,而不是测量节点的杂质。然后,在进行预测时,我们使用观察值所在的任何叶节点的平均值(来自训练数据)。更正式地,对于所有候选分裂,其中叶是由分裂创建的所有叶的集合,yᵢ是观察值 i 的数值响应值,并且
是给定叶片 c 的平均数值响应值,我们希望选择最小化以下表达式的分割:
R 实施
做得好,一路走到了最后,在 twitter 上关注我以了解未来的帖子。所有观想都是我自己的。
决策树——鸟瞰图和实现
本文正被移至我的 子栈发布 。这里 可以免费阅读文章 。这篇文章将于 2022 年 5 月 18 日被删除。
这篇文章的目的是什么?
在该数据集中实现了以下内容
- 理解决策树的定义
履行
- 加载数据
- 使用相关矩阵和配对图可视化数据
- 构建决策树分类器
- 使用混淆矩阵确定模型的准确性
- 将决策树可视化为流程图
什么是决策树?
决策树是一种类似流程图的结构,其中每个内部节点代表对一个属性的“测试”(例如,掷硬币是正面还是反面),每个分支代表测试的结果,每个叶节点代表一个类标签(在计算所有属性后做出的决定)。
(来源:维基百科)
更简单地说,决策树检查一个属性或一组属性是否满足条件,并根据检查结果执行后续检查。该树根据这些检查将数据分成不同的部分。
履行
本文其余部分已移至出版物 机器学习——科学、工程和 Ops 。你可以在这里 免费阅读整篇文章 。
决策树和随机森林
决策树是一种用于分类和回归的模型。树回答连续的问题,这些问题把我们沿着给出答案的树的特定路线送下去。该模型以“如果这个比那个”的条件运行,最终产生特定的结果。这一点很容易看出,下图显示了是否要打高尔夫球。
这棵树的流程是从顶部开始向下的。前景有三种选择:晴天、阴天或雨天。如果天气晴朗,我们就去下一层。会有风吗?真的还是假的?如果是真的,我们那天选择不打高尔夫。如果错误,我们选择玩。如果天气转阴,我们将在那里结束比赛。如果天气预报是多雨的,我们将会关注湿度。如果湿度高,我们就不玩,如果湿度正常,我们就玩。
树的深度是一个重要的概念。这表示在我们达到预测的分类之前有多少问题被询问。我们可以看到,在上面的例子中,树的最深处是 2。晴天和雨天的路线都有两个深度。虽然整个树的深度是由它最长的路线来表示的,但是阴路线只有一个深度。因此,这棵树的深度为 2。
使用决策树的优势:
1.易于解释和直观化。
2.内部工作能够被观察到,从而使复制工作成为可能。
3.可以处理数字和分类数据。
4.在大型数据集上表现出色
5.速度极快
决策树的缺点:
1.构建决策树需要能够在每个节点确定最优选择的算法。一种流行的算法是亨特算法。这是一个贪婪模型,这意味着它在每一步都做出最优决策,但没有考虑全局最优。这是什么意思?在每一步,算法都会选择最佳结果。然而,在给定的步骤中选择最佳结果并不能确保当您到达树的最后一个节点(称为叶节点)时,您将沿着通向最佳决策的路线前进。
2.决策树容易过度拟合,尤其是当一棵树特别深的时候。这是由于我们所观察的特异性的数量导致了满足先前假设的事件的较小样本。这个小样本可能导致不可靠的结论。这方面的一个例子是预测波士顿凯尔特人队是否会在今晚的篮球赛中击败迈阿密热火队。该树的第一层可以询问凯尔特人是主场还是客场比赛。第二层可能会问凯尔特人是否比他们的对手,在这种情况下是热火,有更高的胜率。第三关问凯尔特人的头号得分手是否上场?第四关问凯尔特人第二得分手是否上场。第五层询问凯尔特人是否从西海岸连续 3 场或更多的客场比赛回到东海岸。虽然所有这些问题可能都是相关的,但可能只有前两个游戏符合今晚游戏的条件。仅使用两种游戏作为我们分类的基础不足以做出明智的决定。解决这个问题的一个方法是设置最大深度。这将限制我们过度拟合的风险;但与往常一样,这将以偏差导致的误差为代价。因此,如果我们设置最大深度为 3,我们只会问比赛是主场还是客场,凯尔特人是否比他们的对手有更高的胜率,以及他们的头号得分手是否在比赛。这是一个更简单的模型,样本之间的差异更小,但最终不会是一个强预测模型。
理想情况下,我们希望将偏差引起的误差和方差引起的误差都最小化。进入随机森林。随机森林很好地缓解了这个问题。随机森林只是决策树的集合,其结果被聚合成一个最终结果。它们限制过度拟合而不会因偏差而大幅增加误差的能力是它们成为如此强大的模型的原因。
随机森林减少方差的一种方法是对不同的数据样本进行训练。第二种方法是使用随机的特征子集。这意味着如果我们有 30 个特征,随机森林将只在每个模型中使用一定数量的特征,比如 5 个。不幸的是,我们忽略了 25 个可能有用的特性。但是如上所述,随机森林是决策树的集合。因此,在每棵树中,我们可以利用五个随机特征。如果我们在森林中使用许多树木,最终我们的许多或所有特征都将被包括在内。这种包含许多特征的方法将有助于限制由偏差引起的误差和由方差引起的误差。如果特征不是随机选择的,我们森林中的基础树可能会变得高度相关。这是因为一些特征可能是特别具有预测性的,因此,相同的特征将在许多基础树中被选择。如果这些树中有许多包含相同的特征,我们就不会因为差异而产生错误。
也就是说,随机森林是一种强大的建模技术,比单一的决策树更加健壮。他们聚集了许多决策树来限制过度拟合以及由于偏差而产生的错误,从而产生有用的结果。
对于视频格式的教程,请访问我的数据科学课程,网址为https://www.youtube.com/watch?v=v32aJe9Hnag&list = plr CB-x 3137 c 09 bpxjdetzvbygg-hnwz 86&index = 1
用于分类和回归的决策树和随机森林第 1 部分
A light through a random forest.
亮点:
想要使用更具可解释性的东西,训练速度更快,表现与旧的逻辑回归甚至神经网络一样好的东西吗?你应该考虑用决策树进行分类和回归。这里是关于随机森林的第二部分。
- 与简单的神经网络相比,训练要快得多,以获得可比的性能(决策树的时间复杂度是[特征数、数据集中的行数]的函数,而对于神经网络,它是[特征数、数据集中的行数、隐藏层数、每个隐藏层中的节点数]的函数)
- 易于解释的,适用于变量选择
- 在较小的数据集上相当健壮
- 决策树和决策树学习很容易理解
链接到我的其他文章:
简介:
决策树及其扩展随机森林是健壮的和易于解释的机器学习算法,用于分类和回归任务。决策树和决策树学习一起构成了一种简单快速的学习函数的方式,该函数将数据 x 映射到输出 y ,其中 x 可以是分类变量和数值变量的混合,而 y 可以是分类变量,也可以是回归变量。像支持向量机、逻辑回归和深度神经网络这样的方法几乎做同样的事情。然而,尽管它们能够处理更大和更复杂的数据集,但它们非常难以解释,并且神经网络在得到好的结果之前可能需要多次迭代和超参数调整。同样,使用决策树和随机森林的最大优势之一是,我们可以根据它们在树中的深度位置,轻松地看到哪些特征或变量对分类或回归有贡献,以及它们的相对重要性。
我们将在本文中查看决策树,并使用从接收方操作特征(ROC)中获得的信息与逻辑回归和简单的神经网络比较它们的分类性能。
决策树:
决策树是一种树(和一种有向无环图),其中的节点代表决策(一个方形框)、随机转换(一个圆形框)或终端节点,而边或分支是二进制的(是/否,真/假),代表从一个节点到另一个节点的可能路径。用于机器学习的特定类型的决策树不包含随机转换。要使用决策树进行分类或回归,可以获取一行数据或一组特征,从根开始,然后通过每个后续决策节点到达终端节点。该过程非常直观且易于解释,这使得经过训练的决策树可用于变量选择,或者更一般地说,特征工程。为了说明这一点,假设你想买一辆新车,沿着一条随机的土路驶入一片随机的森林。您有一个包含三个特征的不同汽车的数据集:汽车行驶类型(分类)、排量(数字)和净空(数字)。下面是一个帮助您做出决策的学习型决策树示例:
I like to go off-roading when I’m not making machines learn.
树的根节点或最顶端节点(只有一个根节点)是使用变量或特征分割数据集的决策节点,该变量或特征导致为分割产生的数据集中的每个子集或类评估的最佳分割度量。决策树通过根据每个决策节点处的分裂度量从根开始递归地向前分裂数据集(以贪婪的、逐节点的方式)来学习。当分裂度量处于全局极值时,到达终端节点。流行的分割指标包括最小化基尼系数杂质(CART 使用)或最大化信息增益(ID3、C4.5 使用)。
举例:
现在我们已经看到了决策树训练是如何工作的,让我们使用 scikit-learn 包(scikit-learn 包含许多漂亮的数据处理、降维、聚类和浅层机器学习工具)并在葡萄酒数据集 (13 个特征/变量和 3 个类)上实现一个简单的分类决策树,然后用 Graphviz 可视化学习的树。
No need to waste money on wine tasting courses.
马上,从学习过的决策树中我们可以看到,特征脯氨酸(葡萄酒中的脯氨酸含量)是基尼杂质值最高的根节点,为 0.658,这意味着三种葡萄酒类别都以此为基础分离。这也意味着,原则上,如果我们在预测模型中仅使用一个特征,脯氨酸含量将允许我们在最多 1-0.658 = 0.342 = 34.2%的时间内正确预测,假设原始学习的决策树预测完美。然后,从根中我们看到,这些类进一步分裂,具有od 280/od 315 _ of _ dilute _ wines特性和 flavinoid 特性。我们还可以看到,大多数 class_1 葡萄酒(81.7%)的酒精含量≤ 13.175,黄素含量≤ 0.795。此外,回想一下,原始数据集中有 13 个特征,但是决策树只选择了 7 个特征的子集进行分类。
我们可以使用该信息来选择一般数据集中的哪些特征/变量对于更高级的模型(如深度神经网络)是重要的(在可能存在无用的、冗余的或有噪声的特征的情况下)。我们将在第 2 部分中看到如何用更健壮的随机森林来实现这一点。通过对一行输入数据进行简单的函数调用,学习到的决策树可用于预测数据。从输入要素预测数值输出值的回归树也可以非常容易地创建:查看这个 scikit-learn 教程。
性能:
接收器操作特性(ROC) 是一个曲线图,可用于确定二元或多类分类器的性能和鲁棒性。x 轴是假阳性率(FPR),y 轴是真阳性率(TPR)。ROC 图提供了关于真实阳性/阴性率和假阳性/阴性率的信息,以及分类器预测的每个类别的 C 统计量或 ROC 曲线下面积(AUROC)的信息(分类器预测的每个类别都有一个 ROC)。AUROC 定义为随机选择的阳性样本比随机选择的阴性样本具有更高预测值的概率。引用这篇关于这个主题的文章:
“假设人们对真阳性率和假阳性率之间的特定权衡(即 ROC 曲线上的特定点)不感兴趣,AUC [AUROC]是有用的,因为它汇总了整个权衡范围内的表现。AUC 的解释很简单:AUC 越高越好,0.50 表示随机表现,1.00 表示完美表现。”
不同分类器的 AUROCs 可以相互比较。该指标的替代方法包括使用 scikit-learn 混淆矩阵计算器计算预测结果,并使用结果矩阵得出基本的正/负准确度、F1-分数等。
{0: 0.98076923076923084, 1: 0.97499999999999998, 2: 1.0, 'micro': 0.97916666666666685}
上面的输出是决策树预测的每个类的 AUROC。相比之下,在 scikit-learn 中,我们在一个逻辑回归模型和一个浅层 MLP 神经网络模型上使用 20%的测试集重新运行相同的数据集。逻辑回归模型的性能(使用所有默认参数)如下:
{0: 0.96153846153846156, 1: 0.94999999999999996, 2: 1.0, 'micro': 0.95833333333333326}
对于浅层 MLP 网络:隐藏层= 2,每层节点= 25,优化器= adam,激活=逻辑,迭代= 50000:
{0: 1.0, 1: 1.0, 2: 1.0, 'micro': 1.0}
我们可以看到决策树优于逻辑回归,虽然神经网络打败了它,但它仍然训练快得多,并且具有可解释性的优势。
接下来是什么:
决策树应该总是在熟练的数据科学家和机器学习工程师的工具箱中。有关如何在 scikit-learm 中使用决策树的更详细的用户指南/手册,请参考http://sci kit-learn . org/stable/modules/tree . html # decision-trees。
然而,尽管决策树易于使用并且具有明显的能力,但是它们对贪婪学习策略的依赖可能会导致树在每个节点上分割错误的特征,或者导致树过拟合。请继续关注,在下一篇文章中,我将展示集成决策树,或所谓的随机森林和引导聚合,当它们一起使用时,会大大提高对更大和更复杂数据集的预测能力和鲁棒性。我们还将看到如何使用随机森林进行健壮的变量选择。