Julia 数据科学(二)
原文:
annas-archive.org/md5/c00c9228e4b434716ed57438765bbb96译者:飞龙
第五章:通过可视化理解数据
Julia 的核心系统中没有可视化/图形包。因此,在没有添加和加载包的情况下,无法对数据集创建所需的可视化。
Julia 通过不包含可视化包,使核心系统保持干净,这样不同类型的后端(如不同操作系统上的 Qt 和 GTK)就不会干扰构建过程。
在这一章,我们将学习如何通过可视化数据,以及如何通过可视化帮助我们一眼理解数据。我们将涵盖以下包:
-
PyPlot
-
Unicodeplots
-
Vega
-
Gadfly
plot函数是绘制图形时常用的函数。当我们加载了多个绘图库时,哪个plot函数会被使用?
使用和importall的区别
假设我们想要扩展Foo包中的bar函数。当我们使用时,我们也需要包含包名:
julia> using Foo
julia> function Foo.bar(...)
但是当我们通过importall来实现时,不需要包含包名:
julia> importall Foo
julia> function bar(...)
当我们使用importall时,function bar(...)和function Foo.bar(...)是等价的。
这可以防止我们不小心扩展了一个我们不想扩展或不知道的函数,同时避免可能破坏未来Foo实现的情况。
Pyplot for Julia
这个包由 Steven G. Johnson 制作,将 Python 著名的matplotlib库提供给 Julia。如果你已经使用过matplotlib,你会对其pyplot模块非常熟悉。
我们在第一章中学习了 Julia 的 Pycall 包,PyPlot 通过相同的包直接调用 matplotlib 绘图库。这种调用几乎没有(或者根本没有)开销,数组也直接传递而不需要复制。
多媒体 I/O
基本的 Julia 运行时只提供纯文本显示。通过加载外部模块或使用如Jupyter笔记本等图形环境,可以提供丰富的多媒体输出。Julia 有一个标准化的机制来显示丰富的多媒体输出(如图像、音频和视频)。这一机制由以下提供:
-
display(x)是 Julia 对象的最丰富多媒体显示方式 -
任意的多媒体表示可以通过重载用户定义类型的
writemime来实现 -
通过子类化一个通用的显示类型,可以使用不同的多媒体支持的后端
PyPlot 利用 Julia 的多媒体 I/O API 进行绘图,支持任何 Julia 图形后端,包括 IJulia。
安装
使用matplotlib时,必须安装 Python 和 Matplotlib。推荐的方式是从任何科学 Python 的完整包中获取。
流行的工具有 Anaconda(由 Continuum analytics 提供)和 Canopy(由 Enthought 提供)。
你也可以使用 pip 安装matplotlib:
$ pip install matplotlib
在安装matplotlib之前,你需要先安装必要的依赖项。
安装matplotlib成功后,我们可以在 Julia 中添加 Pyplot 包:
julia> Pkg.update()
julia> Pkg.add("PyPlot")
它会自动添加依赖项。我们将在示例中使用 IJulia 进行内联绘图。
基本绘图
现在我们已经将该包添加到系统中,可以开始使用它了。我们将在示例中使用 IJulia(jupyter notebook):
using PyPlot
PyPlot.svg(true)
第二行,Pyplot.svg(true),将允许我们获得生成的图表和可视化的 SVG。可缩放矢量图形(SVG)是一种基于 XML 的标记语言,用于二维图形的矢量图像格式,支持互动性和动画:
x = [1:100]
y = [i² for i in x]
p = plot(x,y)
xlabel("X")
ylabel("Y")
title("Basic plot")
grid("on")
-
第一行和第二行定义了我们要生成图表的
x和y的值。 -
第三行,
plot(x,y),实际上生成了图表。 -
对于我们生成的图表,我们提供标签并改变美学设置。通过
xlabel和ylabel,我们为x轴和y轴提供了标签。在接下来的章节中,我们将探索plot函数的其他选项。
它已生成一个指数图。
使用正弦和余弦绘图
在以下代码中,我们使用函数初始化x和y:
x = linspace(0, 3pi, 1000)
y = cos(2*x + 3*sin(3*x));
plot(x, y, color="orange", linewidth=2.0, linestyle="--");
title("Another plot using sine and cosine");
让我们简要理解一下前面的代码:
-
在
plot函数中,我们传递了用于生成特定图表的参数。 -
我们可以通过传递参数来更改线条的样式、宽度和颜色。
在这里,我们可以看到线条样式与第一幅图的线条样式有很大不同。默认颜色是蓝色,但我们已指定该图表使用橙色线条。
Unicode 图表
Unicode 图表在我们需要在 REPL 中绘图时非常有用。它们极其轻量。
安装
没有依赖项,因此可以轻松安装:
Pkg.add("UnicodePlots")
using UnicodePlots
示例
让我们了解一下使用UnicodePlots轻松制作的基本图表。
生成 Unicode 散点图
散点图用于确定两个变量之间的相关性,也就是说,一个变量如何受另一个变量的影响:
生成 Unicode 线图
线性图以一系列数据点显示数据集:
使用 Vega 进行可视化
Vega 是由 John Myles White 提供的美丽可视化库。它作为一个注册的 Julia 包提供,因此可以轻松安装。
它建立在 D3.js 之上,使用 JSON 来创建美观的可视化。每当我们需要生成图表时,它都需要互联网连接,因为它不会存储所需的 JavaScript 库的本地副本。
安装
要安装 Vega,请使用以下命令:
Pkg.add("Vega")
using Vega
示例
让我们通过 Vega 走一遍各种可视化示例。
散点图
以下是散点图的参数:
-
x和y:AbstractVector -
组:AbstractVector
散点图用于确定两个变量之间的相关性,即一个如何受到另一个的影响:
scatterplot(x=rand(100), y=rand(100))
现在我们可以开始构建一个复杂的散点图:
这将生成以下散点图。我们可以清楚地看到 Vega 生成的两个聚类。这些是 d1 和 d2:
在这个特定的例子中,我们将数据分组并使用不同的颜色来可视化这些分组。
Vega 中的热图
Vega 中的热图易于生成。这帮助我们轻松地可视化数据点的密度。参数如下:
-
x和y -
颜色
x = Array(Int, 900)
y = Array(Int, 900)
color = Array(Float64, 900)
tmp = 0
for counter in 1:30
for counter2 in 1:30
tmp += 1
x[tmp] = counter
y[tmp] = counter2
color[tmp] = rand()
end
end
hm = heatmap(x = x, y = y, color = color)
使用 Gadfly 进行数据可视化
Gadfly 是一个由 Daniel Jones 使用 Julia 编写的全面的绘图和数据可视化包。它基于 Leland Wilkinson 的书籍《图形语法》。它在很大程度上受到 R 语言中 ggplot2 包的启发,后者是另一个出色的绘图和可视化工具。
安装 Gadfly
安装过程很简单,因为它是一个注册的 Julia 包:
Julia> Pkg.update()
Julia> Pkg.add("Gadfly")
这也会安装 Gadfly 所需的其他一些包。
要使用 Gadfly,请运行以下命令:
Julia> using Gadfly
在我们的示例中,我们将使用 IJulia(jupyter notebook)。
Gadfly 具有渲染高质量图形和可视化的能力,支持 PNG、SVG、Postscript 和 PDF 格式。SVG 后端使用嵌入的 JavaScript,实现与图形的交互功能,如缩放、平移和切换。
安装 Cairo 会比较好,因为 PNG、PostScript 和 PDF 需要它:
Julia> Pkg.add("Cairo")
假设我们创建了一个 exampleplot。为了在后端绘制它,我们使用 draw 函数:
julia> exampleplot = plot(....)
- 对于 SVG:
julia> draw(SVG("plotinFile.svg', 4inch, 4inch), exampleplot)
- 对于嵌入 JavaScript 的 SVG:
julia> draw(SVGJS("plotinFile.svg', 4inch, 4inch), exampleplot)
- 对于 PNG:
julia> draw(PNG("plotinFile.png', 4inch, 4inch), exampleplot)
- 对于 PostScript:
julia> draw(PS("plotinFile.ps', 4inch, 4inch), exampleplot)
- 对于 PDF:
julia> draw(PDF("plotinFile.pdf', 4inch, 4inch), exampleplot)
使用 plot 函数与 Gadfly 进行交互
plot 函数用于与 Gadfly 包进行交互并创建所需的可视化图形。美学元素映射到图形几何图形上,用于指定 plot 函数的工作方式。它们是特定命名的变量。
plot 元素可以是比例尺、坐标、引导线和几何图形。它在图形语法中定义,以避免特殊情况,而美学通过以明确定义的输入和输出方法处理问题,从而产生期望的结果。
Plot 可以处理以下数据源:
-
函数和表达式
-
数组和集合
-
数据框
示例
如果我们没有定义 plot 元素,默认情况下会使用点几何图形。在点几何图形中,x 和 y 输入被视为美学元素。
让我们绘制一个散点图:
在相同的美学中,我们可以使用多个元素来获得特定的输出。
例如,要在同一数据集上同时绘制线和点几何图形,我们可以使用以下方式创建分层图:
-
Geom.line:线图 -
Geom.point:点图
这生成了一个包含线条和点的分层图。通过组合不同的元素,可以生成一个复杂的图。
Guide:
-
xlabel和ylabel:Guide 可用于为我们使用的图表提供必要的标签。 -
title:用于为图表提供标题
刻度:
- 使用此功能可以放大或缩小图表的任何所需轴
让我们创建一个包含这些元素的类似图表。我们将添加 x 和 y 标签,给图表添加标题,并缩放图表:
图中的滑块可用于缩放。
使用 Gadfly 绘制 DataFrame
Gadfly 提供的对 DataFrame 的支持非常有用。在前几章中,我们学习了 DataFrame 的功能。它是一个强大的数据结构,用于表示和操作数据。
使用 Gadfly,我们可以轻松生成复杂的图表。DataFrame 作为第一个参数传递给绘图函数。
DataFrame 中的列通过名称或索引在美学中被绘图函数使用。我们将使用 RDatasets 来为绘图函数创建 DataFrame。RDatasets 提供了一些现实世界中的数据集,我们可以在这些数据集上进行可视化操作,以了解 Gadfly 包的功能:
Using Rdatasets, Gadfly
plot(iris, x=:SepalLength, y=:SepalWidth,
color=:Species, shape=:Species, Geom.point,
Theme(default_point_size=3pt))
这是一个非常著名的数据集——鸢尾花数据集——我们在前面的例子中也使用过。通过花萼长度和花萼宽度绘制数据集很容易,因为我们只需将它们作为 x 和 y 坐标传递。
现在,我们使用随机数生成器创建一个直方图。我们将传递一个由随机数生成器创建的数组,然后生成直方图。
这是一个相当简单的直方图。我们来使用 RDataset 中的数据集创建一个直方图:
前面的数据集来自 RDatasets,我们创建了一个直方图,用于查看学生在课程中获得的成绩和性别。
这可以通过创建散点图来扩展:
使用 Gadfly 可视化函数和表达式
在 Gadfly 中,绘制函数和表达式非常方便。
绘图函数的函数和表达式的签名如下:
plot(f::Function, a, b, elements::Element...)
plot(fs::Array, a, b, elements::Element...)
这表明我们可以将函数或表达式作为数组传递,数组中包含我们希望使用的元素。
这是一个简单的正弦和余弦函数的图。让我们从一个复杂的表达式创建一个图表:
这是我们尝试绘制的一个随机表达式。你可以看到,绘制一个稍微复杂的表达式非常简单。即使复杂性增加,Gadfly 也表现得很好。
生成多层图像
Gadfly 能够将多个图层绘制到同一图表上:
使用统计学生成具有不同美学的图表
Gadfly 中的统计函数通过接受一个或多个美学作为输入,并输出一个或多个美学。
让我们逐一探讨它们。
步骤函数
这用于在给定的点之间进行逐步插值。通过该函数在两个点之间引入一个新点,插值的方向取决于参数的方向:
-
x和y点是使用的美学元素 -
:vh用于垂直方向,:hv用于水平方向
分位数-分位数函数
这用于生成分位数-分位数图。将两个数值向量传递给函数,并进行它们分位数的比较。
传递给函数的 x 和 y 是分布或数值向量:
Gadfly 中的刻度线
刻度线用于包围坐标轴之间的数据
有两种类型的刻度线:xticks 和 yticks。
刻度线函数的参数包括:
-
ticks:特定的刻度线数组(当没有刻度线时,会计算出它们) -
granularity_weight:刻度线的数量(默认值为 1/4) -
simplicity_weight:包括零(默认值为 1/6) -
coverage_weight:紧密适配数据的跨度(默认值为 1/3) -
niceness_weight:编号(默认值为 1/4)
使用几何学生成具有不同美学的图表
几何学负责实际的绘制。一个或多个输入(美学)传递给函数。
箱型图
这也称为胡须图;这是一种基于四分位数显示数据的标准方法:
-
第一和第三四分位数由盒子的底部和顶部表示
-
盒子内的带状区域是第二四分位数(中位数)
-
盒子外的带状区域表示最小值和最大值
直接使用的美学包括:
-
x -
middle -
lower_hinge和upper_hinge -
lower_fence和upper_fence -
outliers
仅需要传递绘制箱型图的数据集。
使用几何学创建密度图
通过密度图可以有效地查看变量的分布:
上面的截图显示了特定范围内变量的密度。
使用几何学创建直方图
直方图有助于理解分布的形态。它将数字分组为范围。
美学包括:
-
x:用于绘制直方图的数据集 -
color(可选):不同的类别可以通过颜色进行分组
参数包括:
-
position:有两个选项,:stack或:dodge。这定义了条形图是并排放置还是堆叠在一起。 -
density:可选项 -
orientation:水平或垂直 -
bincount:箱数 -
maxbincount和minbincount: 当自动选择箱数时的上限和下限。
条形图
这些由平行条形图组成,用于图形化频率分布。
Aesthetics are:
-
y: 这是必需的。它是每个条形的高度。 -
颜色: 可选。用于将数据集分类。 -
x: 每个条形图的位置。
xmin 和 xmax 也可以代替 x,它们是每个条形图的起始和结束。
Arguments are:
-
position: 可以是:stack或:dodge -
orientation: 可以是:vertical或:horizontal
如果选择 :horizontal,则需要提供 y 作为美学(或 ymin/ymax)。
二维直方图 - Histogram2d
用于创建类似热图的直方图,其中矩形条形代表密度。
Aesthetics are:
x和y: 要绘制在坐标上的数据集
Arguments are:
-
xbincount: 指定 x 坐标的箱数当自动确定箱数时提供
xminbincount和xmaxbincount -
ybincount: 指定 y 坐标的箱数当自动确定箱数时提供
yminbincount和ymaxbincount
平滑线图
我们之前处理过线图的示例。我们还可以创建平滑线图,从数据中估计函数。
Aesthetics are:
-
x: 预测数据 -
y: 响应(函数)数据 -
颜色: 可作为可选参数用于分类数据集
Arguments are:
-
smoothing: 指定平滑的程度较小的值使用更多数据(更拟合),较大的值使用较少数据(拟合度较低)。
-
Method: 支持
:lm和:loess方法作为生成平滑曲线的参数
子图网格
可以将多个图一起作为网格制作,并根据几个分类向量进行组织:
Julia> Geom.subplot_grid(elements::Gadfly.ElementOrFunction...)
Aesthetics are:
-
xgroup和ygroup(可选):用于基于分类数据在 x 轴或 y 轴上排列子图。 -
free_y_axis和free_x_axis(可选):默认情况下,值为 false,这意味着在不同子图之间 y 轴或 x 轴的比例尺可以不同。如果值为 true,则为每个图设置比例尺。
-
如果
xgroup和ygroup都绑定,则形成一个网格。
使用固定比例尺:
水平和垂直线
使用 hline 和 vline,我们可以在画布上绘制水平和垂直线。
Aesthetics are:
-
xintercept: x 轴截距 -
yintercept: y 轴截距
Arguments are:
-
颜色: 生成线条的颜色 -
size: 我们还可以指定线条的宽度
绘制带状图
我们还可以在折线图上绘制带状图。
美学为:
-
x:x 轴 -
ymin和ymax:y 轴的下限和上限 -
color(可选):使用不同颜色将数据分组
示例:
xs = 0:0.1:20
df_cos = DataFrame(
x=xs,
y=cos(xs),
ymin=cos(xs) .- 0.5,
ymax=cos(xs) .+ 0.5,
f="cos"
)
df_sin = DataFrame(
x=xs,
y=sin(xs),
ymin=sin(xs) .- 0.5,
ymax=sin(xs) .+ 0.5,
f="sin"
)
df = vcat(df_cos, df_sin)
p = plot(df, x=:x, y=:y, ymin=:ymin, ymax=:ymax, color=:f, Geom.line, Geom.ribbon)
小提琴图
小提琴图非常特定于使用场景。它们用于显示密度。
美学为:
-
x和y:x 轴和 y 轴上的位置 -
width:这表示根据y值的密度
蜂群图
就像小提琴图一样,我们可以使用 beeswarm 图表示密度。
美学为:
-
x和y:x 轴和 y 轴的数据集 -
color(可选)
参数为:
-
orientation:这可以是:vertical或:horizontal -
padding:两点之间的最小距离
元素 - 比例
这用于转换原始数据,同时保留原始值。它将一个美学映射到相同的美学。
x_continuous 和 y_continuous
这些用于将值映射到 x 和 y 坐标。
美学为:
-
x,xmin/xmax和xintercept -
y,ymin/ymax和yintercept
参数为:
-
minvalue:最小x或y值 -
maxvalue:最大x或y值 -
labels:这可以是一个函数或不设置当传入一个函数时,字符串将映射到
x或y中的值 -
format:数字格式化
变体为:
-
Scale.x_continuous和Scale.y_continuous -
Scale.x_log10和Scale.ylog10 -
Scale.x_log2和Scale.ylog2 -
Scale.x_log和Scale.y_log -
Scale.x_asinh和Scale.y_asinh -
Scale.x_sqrt和Scale.y_sqrt
x_discrete 和 y_discrete
这些用于将分类数据映射到笛卡尔坐标系。不论值如何,每个值都映射到一个点。
美学为:
-
x,xmin/xmax和xintercept -
y,ymin/ymax和yintercept
参数为:
-
labels:这可以是一个函数或不设置当传入一个函数时,字符串将映射到
x或y中的值:
连续颜色比例
这创建一个使用连续颜色比例的图表。它用于表示密度。
美学为:
- color
参数为:
-
f:定义的返回颜色的函数 -
minvalues和maxvalue:颜色比例值的范围
元素 - 引导
这些提供了特别的布局考虑,有助于我们更好地理解数据。它们包含 xticks、yticks、xlabels、ylabels、标题和注释等内容。
理解 Gadfly 如何工作
本章我们已经介绍了各种图表。现在简要介绍一下 Gadfly 实际如何工作。
首先,数据源的子集被映射到图表中每一层的数据对象:
-
我们将不同的尺度传递给绘图函数,用来获取可绘制的美学效果。
-
进行了美学转换,既在图层上也在图形上进行处理。
-
创建一个 Compose 上下文,通过使用所有图层的美学,将数据适配到屏幕坐标。
-
每一层的几何图形都单独渲染。
-
最后,计算并在图形上方渲染了引导。
总结
在这一章节中,我们学习了如何使用不同的图形选项在 Julia 中进行可视化。
我们研究了 PyPlot 以及如何利用强大的 matplotlib 库,做了多个示例。我们还了解了 Unicode 图形,它们非常轻量,可以在终端中使用。本章节还解释了两大最受欢迎的图形库 Vega 和 Gadfly。通过使用散点图、折线图、箱线图、直方图、条形图和小提琴图等不同的图形,我们理解了可视化数据的重要性和帮助。
在下一章,我们将学习 Julia 中的机器学习。
参考资料
第六章 监督机器学习
人们通常认为数据科学就是机器学习,这意味着在数据科学中,我们只是训练机器学习模型。但数据科学远不止于此。数据科学涉及理解数据、收集数据、整理数据、从中获取意义,然后如果需要进行机器学习。
在我看来,机器学习是当今存在的最令人兴奋的领域。随着大量可用数据的出现,我们可以收集到宝贵的知识。许多公司已经使他们的机器学习库变得可访问,还有很多开源替代品存在。
在本章中,你将学习以下主题:
-
什么是机器学习?
-
机器学习的类型
-
过拟合和欠拟合是什么?
-
偏差-方差权衡
-
特征提取和选择
-
决策树
-
朴素贝叶斯分类器
什么是机器学习?
一般来说,当我们谈论机器学习时,我们会涉及到与我们创建但失控的智能机器争斗的想法。这些机器能够智胜人类,并对人类生存构成威胁。这些理论只是为了我们的娱乐而创造的。我们离这样的机器还非常遥远。
所以问题是:什么是机器学习?Tom M. Mitchell 给出了一个正式的定义:
"如果一个计算机程序在任务 T 的表现,以性能度量 P 来衡量,随着经验 E 的增加而提高,那么它就被认为是从经验 E 中学习。"
这意味着机器学习是教计算机使用数据生成算法,而不是明确编程它们。它将数据转化为可操作的知识。机器学习与统计学、概率论和数学优化密切相关。
随着技术的发展,有一样东西呈指数增长——数据。我们有大量的结构化和非结构化数据,以非常快的速度增长。太空观测站、气象学家、生物学家、健身传感器、调查等产生了大量数据。手动处理这么多数据并找出模式或洞察力是不可能的。这些数据对科学家、领域专家、政府、卫生官员甚至企业都非常重要。为了从这些数据中获取知识,我们需要自学习算法来帮助我们做决策。
机器学习作为人工智能的一个子领域发展,消除了手动分析大量数据的需要。我们通过使用自学习预测模型来进行数据驱动决策。机器学习已经成为我们日常生活中的重要组成部分。一些常见的应用包括搜索引擎、游戏、垃圾邮件过滤器和图像识别。自动驾驶汽车也使用机器学习。
机器学习中使用的一些基本术语包括:
-
特征:数据点或记录的独特特征。
-
训练集:这是我们输入算法进行训练的数据集,帮助我们发现关系或构建模型。
-
测试集:使用训练数据集生成的算法会在测试数据集上进行测试,以查找准确度。
-
特征向量:包含定义对象特征的 n 维向量。
-
样本:数据集中的一个项目或记录。
机器学习的应用
机器学习在某种程度上无处不在,它的应用几乎没有边界。我们来讨论一些非常常见的使用案例:
-
电子邮件垃圾邮件过滤:每个主要的电子邮件服务提供商都使用机器学习来将垃圾邮件从收件箱过滤到垃圾邮件文件夹。
-
预测风暴和自然灾害:气象学家和地质学家利用机器学习,通过天气数据预测自然灾害,这有助于我们采取预防措施。
-
定向促销/活动和广告:在社交网站、搜索引擎,甚至可能在邮箱中,我们看到的广告总是某种程度上符合我们的口味。这是通过对我们过去搜索记录、社交资料或电子邮件内容的数据进行机器学习来实现的。
-
自动驾驶汽车:科技巨头目前正致力于自动驾驶汽车。这是通过对实际驾驶数据、人类驾驶员的图像和声音处理以及其他各种因素进行机器学习实现的。
-
机器学习也被企业用来预测市场。
-
它还可以用来预测选举结果和选民对特定候选人的情感。
-
机器学习也被用来预防犯罪。通过理解不同罪犯的模式,我们可以预测未来可能发生的犯罪,并加以防范。
一个广受关注的案例是,美国一家大型零售商利用机器学习来识别孕妇。该零售商想出了通过在多种孕妇用品上提供折扣来吸引女性顾客,让她们成为忠实客户,并购买高利润的婴儿用品。
该零售商通过分析购买不同孕妇用品的模式,研究出了预测怀孕的算法。
曾经有一个人走到零售商面前,询问为什么他的青少年女儿会收到孕妇用品的折扣券。零售商道歉了,但后来父亲自己也道歉了,因为他了解到女儿确实怀孕了。
这个故事可能完全真实,也可能并非完全真实,但零售商确实定期分析客户数据,以发现用于定向促销、活动和库存管理的模式。
机器学习与伦理
让我们看看机器学习在哪些领域被广泛应用:
-
零售商:在之前的例子中,我们提到零售商如何使用数据来进行机器学习,从而增加收入并留住客户
-
垃圾邮件过滤:电子邮件通过各种机器学习算法进行垃圾邮件过滤
-
定向广告:在我们的邮箱、社交网站或搜索引擎中,我们会看到自己喜欢的广告
这些仅是现实世界中实施的部分实际用例。它们之间的共同点是用户数据。
在第一个例子中,零售商利用用户的交易历史进行定向推广和活动策划,以及库存管理等其他工作。零售巨头通过为用户提供忠诚卡或注册卡来实现这一点。
在第二个例子中,电子邮件服务提供商使用经过训练的机器学习算法来检测和标记垃圾邮件。它通过检查电子邮件内容/附件,并对电子邮件发送者进行分类来实现这一点。
在第三个例子中,同样是电子邮件提供商、社交网络或搜索引擎通过我们的 Cookies、个人资料或邮件来进行定向广告。
在所有这些例子中,当我们与零售商、电子邮件提供商或社交网络签约时,协议的条款和条件中都会提到将使用用户数据,但不会侵犯隐私。
在使用未公开的数据之前,我们必须获得必要的许可。这非常重要。此外,我们的机器学习模型不应在地域、种族、性别或任何其他方面存在歧视。提供的数据不应用于协议中未提及的目的,或在所在地区或国家是非法的。
机器学习 – 过程
机器学习算法的训练是根据人类大脑工作的方式进行的。它们有些相似。让我们来讨论整个过程。
机器学习过程可以分为三个步骤:
-
输入
-
抽象
-
泛化
这三个步骤是机器学习算法工作的核心。尽管算法的表现形式可能不同,但这解释了整体方法:
-
第一步集中在应该包含哪些数据以及不应包含哪些数据。根据这一点,它根据需求收集、存储并清理数据。
-
第二步涉及将数据转换为代表更大类的数据。这是必要的,因为我们无法捕捉到所有数据,且我们的算法不应该仅适用于我们拥有的数据。
-
第三步关注于创建模型或行动,这些模型或行动将使用这些抽象的数据,并适用于更广泛的群体。
那么,接近一个机器学习问题的流程应该是什么样的呢?
在这个特定的图示中,我们看到数据在用于创建机器学习算法之前,经过了抽象处理过程。这个过程本身是繁琐的。我们在与数据清洗相关的章节中学习了这个过程。
该过程紧随模型训练之后,模型的训练就是将模型拟合到我们拥有的数据集上。计算机并不会自主选择模型,而是依赖于学习任务。学习任务还包括将从我们尚未拥有的数据中获得的知识进行泛化。
因此,训练模型是基于我们当前拥有的数据,而学习任务包括将模型泛化到未来的数据。
它取决于我们的模型如何从我们当前拥有的数据集中推断知识。我们需要创建一个能够从之前不为我们所知的东西中汲取洞察的模型,这样它就能对未来数据产生有用的联系。
不同类型的机器学习
机器学习主要分为三类:
-
监督学习
-
无监督学习
-
强化学习
在监督学习中,模型/机器会接收输入以及与这些输入对应的输出。机器从这些输入中学习,并将这种学习应用于进一步的未见数据,以生成输出。
无监督学习没有所需的输出,因此由机器来学习并寻找之前未见的模式。
在强化学习中,机器与环境持续互动并通过这一过程学习。这包括一个反馈循环。
什么是偏差-方差权衡?
让我们理解一下什么是偏差和方差。首先,我们将讨论模型中的偏差:
-
偏差是模型生成的预测结果与预期的正确值之间的差异,或者说我们应该获得的值。
-
当我们获得新数据时,模型会进行计算并给出预测。因此,这意味着我们的模型有一个可以生成预测的范围。
-
偏差是这一预测范围的准确性。
现在,让我们理解方差以及它如何影响模型:
-
方差是当数据点发生变化或引入新数据时,模型的变异性
-
不应该在每次引入新数据时都需要调整模型
根据我们对偏差和方差的理解,我们可以得出结论,它们相互影响。因此,在创建模型时,我们会考虑这一权衡。
过拟合和欠拟合对模型的影响
过拟合发生在我们创建的模型开始考虑数据集中的异常值或噪声时。因此,这意味着我们的模型过度拟合了数据集。
这种模型的缺点是无法很好地进行泛化。这类模型具有低偏差和高方差。
欠拟合发生在我们创建的模型未能找到数据的模式或趋势时。因此,这意味着模型未能很好地适应数据集。
这种模型的缺点是无法给出良好的预测。这些模型具有高偏差和低方差。
我们应该尽量减少欠拟合和过拟合。这可以通过各种技术来实现。集成模型在避免欠拟合和过拟合方面非常有效。我们将在接下来的章节中学习集成模型。
理解决策树
决策树是“分治法”的一个很好的例子。它是最实用、最广泛使用的归纳推理方法之一。它是一种监督学习方法,可用于分类和回归。它是非参数的,目的是通过推断数据中的简单决策规则来学习,并创建一个能够预测目标变量值的模型。
在做出决策之前,我们会通过权衡不同选项来分析利弊。例如,我们想购买一部手机,并且有多个价格区间的选择。每款手机都有某些特别好的功能,可能比其他手机更好。为了做出选择,我们从考虑我们最重要的特征开始。基于此,我们创建了一系列需要满足的特征,最终选择将是最符合这些特征的那一款。
本节我们将学习:
-
决策树
-
熵度量
-
随机森林
我们还将学习著名的决策树学习算法,如 ID3 和 C5.0。
构建决策树 - 分治法
一种称为递归划分的启发式方法用于构建决策树。在这种方法中,随着推进,我们将数据划分为越来越小的相似类别。
决策树实际上是一个倒置的树。它从根节点开始,最终到达叶节点,这些叶节点是终端节点。节点的分支依据逻辑决策。整个数据集在根节点处表示。算法会选择一个对目标类别最具预测性的特征。然后,它根据这个特征将样本划分为不同的值组。这代表了我们树的第一组分支。
采用分治法,直到达到终点。在每一步,算法会继续选择最佳的候选特征。
当以下条件满足时,定义终点:
-
在某个节点,几乎所有的样本都属于同一类别
-
特征列表已用尽
-
达到预定义的树大小限制
上述图像是一个非常著名的决策树示例。在这里,决策树是用来判断是否外出:
-
Outlook 是根节点。这指的是环境中所有可能的类别。
-
Sunny、overcast 和 Rain 是分支。
-
湿度和风速是叶节点,这些节点又被分为分支,决策是根据有利的环境做出的。
这些树也可以重新表示为 if-then 规则,这样会更容易理解。决策树是非常成功且受欢迎的算法之一,应用范围广泛。
以下是决策树的一些应用:
-
信用卡/贷款批准决策:信用评分模型基于决策树,每个申请人的信息被输入,以决定是否批准信用卡/贷款。
-
医学诊断:许多疾病通过基于症状、测量和检测的经过充分定义和测试的决策树进行诊断。
我们应该在什么情况下使用决策树学习?
虽然有多种决策树学习方法可以用于各种问题,但决策树最适合以下场景:
-
属性-值对是指通过固定集的属性和相应的值来描述实例的场景。在前面的例子中,我们有属性“风速”和值“强”和“弱”。这些互斥的可能值使得决策树学习变得容易,尽管也可以使用具有实数值的属性。
-
目标函数的最终输出是离散值,类似于前面的例子,其中我们有“是”或“否”。决策树算法可以扩展为具有多个可能的目标值。决策树也可以扩展为具有实数值作为输出,但这种情况很少使用。
-
决策树算法对于训练数据集中的错误具有鲁棒性。这些错误可能出现在示例的属性值、分类或者两者都有。
-
决策树学习也适用于数据集中缺失值的情况。如果某些示例中的值缺失,而其他示例中的相同属性有值,那么可以使用决策树。
决策树的优点
-
决策树容易理解和解释,决策树的可视化也很简单。
-
在其他算法中,必须先进行数据归一化才能应用。归一化是指创建虚拟变量并去除空值。而决策树则需要的准备工作较少。
-
使用决策树进行预测的成本与训练树所使用的示例数量呈对数关系。
-
与其他算法不同,决策树可以同时应用于数值型和类别型数据。其他算法通常专门用于处理其中一种类型的变量。
-
决策树可以轻松处理可能有多个输出的情况。
-
决策树遵循白盒模型,这意味着如果情况在模型中是可观察的,使用布尔逻辑可以轻松解释条件。另一方面,在黑盒模型(如人工神经网络)中,结果相对难以解释。
-
统计测试可以用来验证模型。因此,我们可以测试模型的可靠性。
-
即使数据源的真实模型假设被违反,它也能表现良好。
决策树的缺点
我们已经介绍了决策树适用的情况及其优势。现在我们将讨论决策树的缺点:
-
决策树存在过拟合数据的可能性。这通常发生在创建过于复杂且难以泛化的树时。
-
为了避免这种情况,可以采取多种步骤。其中一种方法是修剪。顾名思义,这是一种方法,我们在其中设置树可以生长到的最大深度。
-
决策树总是存在不稳定性的问题,因为数据的微小变化可能导致生成完全不同的树。
-
这种场景的解决方案是集成学习,在下一章中我们将学习它。
-
决策树学习有时可能导致创建偏向某些类的树,而忽视其他类的情况。在将数据拟合到决策树算法之前,解决这种情况的方法是平衡数据集。
-
决策树学习被认为是 NP 完全的,考虑到优化的几个方面。即使对于基本概念也是如此。
-
通常使用贪婪算法等启发式算法,在每个节点都做出局部最优决策。这并不保证我们将得到一个全局最优的决策树。
-
对于诸如奇偶性、XOR 和多路复用器问题等概念,学习可能会很困难,决策树无法轻松表示它们。
决策树学习算法
有多种决策树学习算法,实际上是核心算法的变体。核心算法实际上是一种自顶向下的、贪婪的搜索所有可能树的方法。
我们将讨论两种算法:
-
ID3
-
C4.5 和 C5.0
第一个算法,ID3(迭代二分器 3),是由 Ross Quinlan 在 1986 年开发的。该算法通过创建一个多路树来进行,它使用贪婪搜索找到每个节点和可以产生最大信息增益的特征,由于树可以增长到最大尺寸,这可能导致数据过拟合,因此使用修剪来创建泛化模型。
C4.5 是在 ID3 之后发展起来的,消除了所有特征必须是分类变量的限制。它通过基于数值变量动态定义离散特征来实现这一点。它将连续的属性值划分为一组离散区间。C4.5 从 ID3 算法的训练树中创建 if-then 规则集合。C5.0 是最新版本;它创建了更小的规则集,并且使用相对较少的内存。
决策树算法如何工作
决策树算法构建自顶向下的树。它遵循以下步骤:
-
为了知道哪一个元素应该出现在树的根节点,算法会对每个属性实例进行统计测试,以确定仅使用该属性时训练示例能够被多好地分类。
-
这导致在树的根节点选择最佳特征。
-
现在,在这个根节点上,对于每个属性的可能值,都创建后代节点。
-
我们训练数据集中的示例会被分配到每一个这些后代节点。
-
对于这些单独的后代节点,之前的所有步骤会针对训练数据集中剩余的示例重复进行。
-
这会通过贪婪搜索创建一个可接受的训练数据集决策树。算法永不回溯,这意味着它永远不会重新考虑之前的选择,而是继续向树的下方发展。
理解与衡量节点的纯度
决策树是自顶向下构建的。每个节点选择分裂的属性可能会很困难。因此,我们寻找能够最好地分裂目标类别的特征。纯度是指一个节点只包含一个类别的度量。
C5.0 中的纯度是通过熵来衡量的。样本的熵指示了类别值在示例之间的混合程度:
-
0: 最小值表示样本中类别值的同质性
-
1: 最大值表示样本中类别值的最大无序程度
熵的计算公式为:
在前面的公式中,S表示我们拥有的数据集,c表示类别水平。对于给定类别i,p是该类别值的比例。
当纯度度量确定后,算法必须决定数据应该根据哪个特征进行分裂。为了决定这一点,算法使用熵度量来计算在每个可能的特征上分裂时,同质性如何变化。算法进行的这种计算叫做信息增益:
数据集分割前的熵(S1)与分割后得到的子集熵(S2)之间的差异叫做信息增益(F)。
一个示例
让我们将所学的知识应用于使用 Julia 创建决策树。我们将使用 scikit-learn.org/ 上为 Python 提供的示例和 Cedric St-Jean 开发的 Scikitlearn.jl。
我们首先需要添加所需的包:
julia> Pkg.update()
julia> Pkg.add("DecisionTree")
julia> Pkg.add("ScikitLearn")
julia> Pkg.add("PyPlot")
ScikitLearn 提供了一个接口,将著名的机器学习库从 Python 转换到 Julia:
julia> using ScikitLearn
julia> using DecisionTree
julia> using PyPlot
在添加所需包之后,我们将创建我们将在示例中使用的数据集:
julia> # Create a random dataset
julia> srand(100)
julia> X = sort(5 * rand(80))
julia> XX = reshape(X, 80, 1)
julia> y = sin(X)
julia> y[1:5:end] += 3 * (0.5 - rand(16))
这将生成一个包含 16 个元素的 Array{Float64,1}。
现在我们将创建两个不同模型的实例。一个模型不限制树的深度,另一个模型则根据纯度修剪决策树:
现在我们将对现有的数据集拟合模型。我们将拟合这两个模型。
这是第一个模型。这里我们的决策树有 25 个叶节点,深度为 8。
这是第二个模型。这里我们修剪了决策树。这个模型有 6 个叶节点,深度为 4。
现在我们将使用模型在测试数据集上进行预测:
julia> # Predict
julia> X_test = 0:0.01:5.0
julia> y_1 = predict(regr_1, hcat(X_test))
julia> y_2 = predict(regr_2, hcat(X_test))
这将创建一个包含 501 个元素的 Array{Float64,1}。
为了更好地理解结果,让我们将这两个模型在我们拥有的数据集上进行可视化:
julia> # Plot the results
julia> scatter(X, y, c="k", label="data")
julia> plot(X_test, y_1, c="g", label="no pruning", linewidth=2)
julia> plot(X_test, y_2, c="r", label="pruning_purity_threshold=0.05", linewidth=2)
julia> xlabel("data")
julia> ylabel("target")
julia> title("Decision Tree Regression")
julia> legend(prop=Dict("size"=>10))
决策树可能会过拟合数据。为了使其更加通用,必须修剪决策树。但如果修剪过度,可能会导致模型不正确。因此,必须找到最优化的修剪级别。
很明显,第一个决策树对我们的数据集过拟合,而第二个决策树模型则相对更加通用。
使用朴素贝叶斯的有监督学习
朴素贝叶斯是目前最著名的机器学习算法之一,广泛应用于文本分类技术。
朴素贝叶斯方法属于有监督学习算法。它是一种概率分类器,基于贝叶斯定理。它假设每对特征彼此独立,这一假设被称为“朴素”假设。
尽管做出这些假设,朴素贝叶斯分类器仍然表现得非常好。它们最著名的应用是垃圾邮件过滤。该算法的有效性体现在它对训练数据的需求非常小,能够估计出所需的参数。
与其他方法相比,这些分类器和学习器的速度相当快。
在这个公式中:
-
A 和 B 是事件。
-
P(A) 和 P(B) 分别是 A 和 B 的概率。
-
这些是先验概率,它们彼此独立。
-
P(A | B) 是在条件 B 为真的情况下,A 的概率。它是给定预测变量(B,属性)时,类别(A,目标)的后验概率。
-
P(B | A) 是在 A 为真时,B 的概率。它是预测给定类别的可能性,也就是预测器给定类别的概率。
朴素贝叶斯的优点
以下是朴素贝叶斯的一些优点:
-
它相对简单,易于构建和理解
-
它可以很容易地训练,并且不需要庞大的数据集
-
它相对较快
-
不受无关特征的影响
朴素贝叶斯的缺点
朴素贝叶斯的缺点是“朴素”假设,即每个特征都是独立的。这并非总是正确的。
朴素贝叶斯分类的用途
以下是朴素贝叶斯分类的一些应用:
-
朴素贝叶斯文本分类:这是一种概率学习方法,实际上是最成功的文档分类算法之一。
-
垃圾邮件过滤:这是朴素贝叶斯最知名的应用场景。朴素贝叶斯用于区分垃圾邮件和合法邮件。许多服务器端的邮件过滤机制与其他算法一起使用朴素贝叶斯。
-
推荐系统:朴素贝叶斯也可用于构建推荐系统。推荐系统用于预测并建议用户可能在未来喜欢的产品。它基于未见过的数据,并与协同过滤结合使用。这种方法更具可扩展性,通常比其他算法表现更好。
要理解朴素贝叶斯分类器如何实际工作,我们需要理解贝叶斯定理。它由托马斯·贝叶斯在 18 世纪提出。他发展了各种数学原理,这些原理今天被我们称为贝叶斯方法。这些方法有效地描述了事件的概率,以及当我们获得额外信息时,如何修正这些概率。
基于贝叶斯方法的分类器使用训练数据集,根据所有特征的值计算每个类别的观测概率。因此,当该分类器用于未标记或未见过的数据时,它会利用观测到的概率来预测新特征属于哪个类别。尽管这是一种非常简单的算法,但其性能可与大多数其他算法相媲美,甚至更好。
贝叶斯分类器最适用于以下情况:
-
包含大量属性的数据集,在计算结果的概率时需要同时考虑所有这些属性。
-
对于影响较弱的特征,通常会被忽略,但贝叶斯分类器仍然会使用它们来生成预测。许多此类弱特征可能会导致决策的重大变化。
贝叶斯方法的工作原理
贝叶斯方法依赖于一个概念,即事件发生的可能性估计是基于现有证据的。事件的可能结果就是事件本身;例如,在抛硬币时,我们可能得到正面或反面。同样,邮件可能是“正常”邮件或“垃圾”邮件。试验是指发生事件的单次机会。在我们之前的例子中,抛硬币就是试验。
后验概率
后验概率 = 条件概率 * 先验概率 / 证据
在分类中,后验概率指的是在给定观察到的特征值时,一个特定对象属于某个类 x 的概率。例如,“给定温度和湿度,概率它会下雨是多少?”
P(rain | xi), xi = [45 度, 95%湿度]
-
设xi为样本i的特征向量,其中i属于*{1,2,3,...n}*。
-
设wj为类j的符号,其中j属于*{1,2,3,...n}*。
-
P(xi | wi) 是观察样本xi的概率,前提是它属于类wj。
后验概率的通用表示法是:
P(wj | xi) = P(xi | wj) * P(wj)/P(xi)
Naïve Bayes 的主要目标是最大化给定训练数据的后验概率,以便形成一个决策规则。
类条件概率
贝叶斯分类器假设数据集中的所有样本是独立同分布的。这里的独立性意味着一个观察的概率不受另一个观察概率的影响。
我们讨论过的一个非常著名的例子是抛硬币。在这里,第一次抛硬币的结果不会影响后续的抛硬币结果。对于一个公平的硬币,得到正面或反面的概率始终为 0.5。
一个额外的假设是特征具有条件独立性。这是另一个“天真”的假设,意味着可以直接从训练数据中估计似然或类条件概率,而无需评估所有 x 的概率。
让我们通过一个例子来理解。假设我们必须创建一个服务器端的电子邮件过滤应用程序,以决定邮件是否是垃圾邮件。假设我们有大约 1000 封电子邮件,其中 100 封是垃圾邮件。
现在,我们收到了一封新邮件,内容是“Hello Friend”。那么,我们应该如何计算这封新邮件的类条件概率呢?
文本的模式由两个特征组成:“hello”和“friend”。现在,我们将计算新邮件的类条件概率。
类条件概率是当邮件是垃圾邮件时遇到“hello”的概率 * 当邮件是垃圾邮件时遇到“friend”的概率:
P(X=[hello, world] | w=spam) = P(hello | spam) * P(friend | spam)
我们可以轻松找出包含“hello”一词的邮件数量,以及包含“spam”一词的邮件数量。然而,我们做出了一个“天真”的假设,即一个单词不会影响另一个单词的出现。我们知道,“hello”和“friend”经常一起出现。因此,我们的假设被违反了。
先验概率
先验概率是关于事件发生的先验知识。它是特定类别发生的总概率。如果先验分布为均匀分布,则后验概率是通过类别条件概率和证据项来确定的。
先验知识是通过对训练数据的估计获得的,当训练数据是整个群体的样本时。
证据
计算后验概率还需要一个值,那就是“证据”。证据 P(x)是特定模式 x 发生的概率,它与类标签无关。
词袋模型
在前面的例子中,我们进行的是电子邮件的分类。为此,我们对一个模式进行分类。要对一个模式进行分类,最重要的任务是:
-
特征提取
-
特征选择
那么,如何识别好的特征呢?好的特征有一些特征:
-
特征必须对我们为其构建分类器的用例有重要意义
-
选择的特征应该包含足够的信息,能够很好地区分不同的模式,并可以用于训练分类器。
-
特征不应容易受到失真或缩放的影响
我们需要先将电子邮件文本表示为特征向量,然后才能将其适配到我们的模型并应用机器学习算法。文本文件的分类使用的是词袋模型。在这个模型中,我们创建词汇表,这是一个包含所有电子邮件(训练集)中出现的不同单词的集合,然后统计每个单词出现的次数。
使用朴素贝叶斯作为垃圾邮件过滤器的优点
以下是使用朴素贝叶斯作为垃圾邮件过滤器的优点:
-
它可以个性化。这意味着它可以基于每个用户进行训练。我们有时会订阅新闻简报、邮件列表或关于产品的更新,这些对于其他用户来说可能是垃圾邮件。此外,我收到的邮件中包含一些与我的工作相关的词汇,这些对其他用户来说可能被归类为垃圾邮件。所以,作为一个合法用户,我不希望我的邮件进入垃圾邮件箱。我们可以尝试使用规则或过滤器,但贝叶斯垃圾邮件过滤比这些机制更为优秀。
-
贝叶斯垃圾邮件过滤器在避免误报方面非常有效,因此合法邮件被分类为垃圾邮件的可能性非常小。例如,我们都会收到包含“尼日利亚”一词或声称来自尼日利亚的邮件,这些邮件实际上是钓鱼诈骗。但是,我可能在那儿有亲戚或朋友,或者我在那里有生意;因此,这封邮件对我来说可能并不不合法。
朴素贝叶斯过滤器的缺点
贝叶斯过滤器容易受到贝叶斯中毒的影响,这是一种通过将大量合法文本与垃圾邮件一起发送的技术。因此,贝叶斯过滤器在此处失败,并将其标记为“ham”或合法邮件。
朴素贝叶斯的例子
让我们使用 Julia 创建一些 Naïve Bayes 模型:
julia> Pkg.update
julia> Pkg.add("NaiveBayes")
我们添加了所需的NaiveBayes包。
现在,让我们创建一些虚拟数据集:
julia> X = [1 1 0 2 1;
0 0 3 1 0;
1 0 1 0 2]
julia> y = [:a, :b, :b, :a, :a]
我们创建了两个数组X和y,其中y中的每个元素表示X中的一列:
julia> m = MultinomialNB(unique(y), 3)
julia> fit(m, X, y)
我们加载了 MultinomialNB 的实例,并将我们的数据集拟合到它上:
julia> Xtest = [0 4 1;
2 2 0;
1 1 1]
现在我们将使用它对我们的测试数据集进行预测:
julia> predict(m, Xtest)
我得到的输出是:
julia> 3-element Array{Symbol,1}:
:b
:a
:a
这意味着第一列是b,第二列是a,第三列也是a。
这个例子使用了一个虚拟数据集。让我们在一个实际数据集上应用 Naïve Bayes。我们将在这个例子中使用著名的鸢尾花数据集:
julia> #import necessary libraries
julia> using NaiveBayes
julia> using RDatasets
julia> iris = dataset("datasets", "iris")
julia> #observations in columns and variables in rows
julia> x = array(iris[:, 1:4])
julia> p,n = size(x)
julia> # By default species is a PooledDataArray
julia> y = [species for species in iris[:,5]]
我们加载了 RDatasets,它包含了鸢尾花数据集。我们为特征向量(花萼长度、花萼宽度、花瓣长度和花瓣宽度)创建了数组。
现在我们将拆分数据集进行训练和测试。
这是相当简单的,将数据集拟合到 Naïve Bayes 分类器上。我们还计算了模型的准确性。我们可以看到准确率是 1.0,也就是 100%。
总结
在这一章中,我们学习了机器学习及其应用。赋予计算机学习和改进的能力在这个世界上有着深远的应用。它被用于预测疾病爆发、天气预测、游戏、机器人、自动驾驶汽车、个人助手等众多领域。
机器学习有三种不同的类型:监督学习、无监督学习和强化学习。
在这一章中,我们学习了监督学习,特别是 Naïve Bayes 和决策树。在接下来的章节中,我们将学习更多关于集成学习和无监督学习的内容。
参考文献
第七章 无监督机器学习
在上一章,我们学习了监督式机器学习算法,以及如何在现实世界的场景中使用它们。
无监督学习有些不同且更加复杂。其目标是让系统学习某些东西,但我们自己并不知道要学什么。无监督学习有两种方法。
一种方法是寻找数据集中的相似性/模式。然后,我们可以将这些相似的点创建为聚类。我们假设找到的聚类是可以分类的,并且可以为其分配标签。
算法本身无法分配名称,因为它没有任何标签。它只能基于相似性找到聚类,但仅此而已。为了真正能够找到有意义的聚类,需要一个足够大的数据集。
它在寻找相似用户、推荐系统、文本分类等方面被广泛使用。
我们将详细讨论各种聚类算法。在本章中,我们将学习:
-
与无标签数据进行处理。
-
什么是无监督学习?
-
什么是聚类?
-
不同类型的聚类。
-
K 均值算法和二分 K 均值算法,它们的优缺点。
-
层次聚类。
-
聚合聚类,它的优缺点。
-
DBSCAN 算法。
在深入探讨聚类之前,我们还应讨论第二种方法。它将告诉我们聚类与这种方法的不同之处以及使用场景。第二种方法是一种强化学习,涉及通过奖励来指示算法的成功。没有明确的分类,这种算法最适合应用于现实世界。系统根据之前的奖励或惩罚来调整行为。这种学习方式可能很强大,因为它没有偏见,也没有预先分类的观察结果。
它计算每个动作的可能性,并提前知道哪个动作会导致什么样的结果。
这种试错方法计算量大且耗时。让我们讨论一种不依赖于试错的聚类方法。
理解聚类
聚类是一种将数据划分为有用且有意义的组(聚类)的技术。这些聚类是通过捕捉数据的自然结构而形成的,彼此之间具有有意义的关系。也有可能它仅在其他算法或进一步分析的准备或总结阶段使用。聚类分析在许多领域都有应用,如生物学、模式识别、信息检索等。
聚类在不同领域有广泛应用:
-
信息检索:将信息划分为特定聚类是从众多来源或大量数据池中搜索和检索信息的重要步骤。以新闻聚合网站为例,它们创建了相似类型新闻的聚类,使得用户更容易浏览感兴趣的部分。
这些新闻类型也可以有子类,形成层次结构。例如,在体育新闻部分,我们可以有足球、板球、网球等其他运动。
-
生物学:聚类在生物学中有很大的应用价值。经过多年的研究,生物学家已将大多数生物分类为不同的层级。利用这些类别的特征,可以对未知生物进行分类。同时,现有的数据可以用来寻找相似性和有趣的模式。
-
营销:公司通过使用客户和销售数据,创建相似用户或细分群体,以便进行有针对性的促销/活动,从而获得最大的投资回报。
-
天气:聚类分析在气候和天气分析中得到了广泛应用。气象站生成大量数据。聚类用于从这些数据中提取见解,发现模式和重要信息。
聚类是如何形成的?
形成聚类的方法有很多。我们来讨论一些基本的聚类创建方法:
-
从对数据对象进行分组开始。这种分组应仅基于描述对象的数据进行。
-
相似的对象被聚集在一起。它们之间可能存在某种关系。
-
不相似的对象被保留在其他聚类中。
- 上述图清晰地展示了当不同数据对象在聚类内相似度较高,而与其他聚类的对象不相似时,形成的一些不同聚类。
但是在这种数据点的表示中,我们可以看到没有可以形成的确定性聚类。这是因为不同聚类的数据对象之间存在一些相似性。
聚类的类型
聚类机制有多种类型,取决于不同的因素:
-
嵌套或非嵌套——层次或划分
-
重叠、排他和模糊
-
部分与完全
层次聚类
如果聚类没有形成子集,则该聚类被称为非嵌套的。因此,划分聚类被定义为创建明确定义的聚类,这些聚类彼此之间不重叠。在这种聚类中,数据点仅位于一个聚类中。
如果聚类中有子聚类,则称为层次聚类。
上述图表示一个层次聚类。层次聚类是按树形结构组织的聚类。
在这里,每个聚类都有自己的子聚类。每个节点也可以被看作是一个独立的系统,具有通过划分得到的自己的聚类。
重叠、独占和模糊聚类
导致不同类型聚类创建的技术可以分为三种方法:
-
独占聚类:在“聚类如何形成?”这一节中,我们看到两张图分别表示了两种不同类型的聚类。在第一张图中,聚类被清晰地定义,并且它们之间有很好的分离。这些叫做独占聚类。在这些聚类中,数据点与其他聚类的数据点有明显的异质性。
-
重叠聚类:在第二张图中,我们看到没有明确的边界来分隔两个聚类。在这里,一些数据点可以出现在任意一个聚类中。这种情况出现在没有明显特征将数据点区分到某一聚类时。
-
模糊聚类:模糊聚类是一个独特的概念。在这里,数据点属于每一个聚类,并且其关系通过权重来定义,权重范围从 1(完全属于)到 0(不属于)。因此,聚类被视为模糊集合。根据概率规则,添加了一个约束条件,即所有数据点的权重之和应该等于 1。
模糊聚类也称为概率聚类。通常,为了具有明确的关系,数据点与其具有最高隶属度的聚类关联。
部分聚类与完全聚类的区别
在完全聚类中,所有数据点都会被分配到一个聚类中,因为它们准确地表示了聚类的特征。这些类型的聚类称为完全聚类。
可能有一些数据点不属于任何聚类。这是因为这些数据点代表噪声或是聚类的异常值。此类数据点不会被包含在任何聚类中,这种情况称为部分聚类。
K-means 聚类
K-means 是最流行的聚类技术,因为它易于使用和实现。它也有一个名为 K-medoid 的伴侣。这些划分方法创建了数据集的一级划分。让我们详细讨论 K-means。
K-means 算法
K-means 从原型开始。它从数据集中获取数据点的质心。这种技术用于位于 n 维空间中的对象。
该技术涉及选择 K 个质心。这个 K 是由用户指定的,根据各种因素进行选择。它定义了我们希望有多少个聚类。因此,选择比所需的 K 高或低可能会导致不理想的结果。
现在,继续进行,每个点被分配到其最近的质心。随着许多点与特定质心关联,形成一个聚类。质心可以根据当前聚类中所包含的点进行更新。
这个过程会反复进行,直到质心保持不变。
K-means 算法
理解 K-means 算法将帮助我们更好地理解如何解决这个问题。让我们一步步理解 K-means 算法:
-
根据定义的 K,选择质心的数量。
-
将数据点分配给最近的质心。此步骤将形成聚类。
-
再次计算聚类的质心。
-
重复步骤 2 和 3,直到质心保持不变。
在第一步中,我们使用均值作为质心。
第 4 步要求重复之前的算法步骤。这有时会导致大量迭代且变化很小。因此,我们通常只有在新计算的质心变化超过 1%时,才会重复步骤 2 和 3。
将数据点与最接近的质心关联
我们如何衡量计算出的质心与数据点之间的距离?
我们使用欧几里得(L2)距离作为度量,并假设数据点位于欧几里得空间中。如果需要,我们也可以使用不同的接近度度量,例如,曼哈顿(L1)距离也可以用于欧几里得空间。
当算法处理不同数据点之间的相似性时,最好只使用数据点的必要特征集。对于高维数据,计算量急剧增加,因为必须对每个维度进行迭代计算。
有一些可以使用的距离度量选择:
-
曼哈顿(L1):这将中位数作为质心。它作用于该函数,最小化一个对象与聚类质心之间的 L1 距离之和。
-
平方欧几里得(L2²):这将均值作为质心。它作用于该函数,最小化一个对象与聚类质心之间的 L2 距离平方和。
-
余弦:这将均值作为质心。它作用于该函数,最大化一个对象与聚类质心之间的余弦相似度之和。
-
Bregman 散度:这将均值作为质心。它最小化一个对象与聚类质心之间的 Bregman 散度之和。
如何选择初始质心?
这是 K-means 算法中非常重要的一步。我们首先随机选择初始质心。这通常会导致非常差的聚类结果。即使这些质心分布良好,我们也无法接近理想的聚类。
有一种解决此问题的技术——使用不同初始质心的多次运行。之后,选择具有最小平方和误差(SSE)的聚类集。由于数据集的大小和所需计算能力,这种方法可能并不总是有效或可行。
在重复随机初始化时,质心可能无法克服问题,但我们可以使用其他技术:
-
使用层次聚类,我们可以从一些样本点开始,使用层次聚类来形成一个聚类。现在我们可以从这个聚类中取出 K 个聚类,并使用这些聚类的质心作为初始质心。这种方法有一些约束:
-
样本数据不应该很大(计算昂贵)。
-
对于所需的聚类数,K 应该较小。
-
-
另一种技术是获取所有点的质心。从这个质心中,我们找到分离最大的点。我们按照这个过程获取最大距离的质心,这些质心也是随机选择的。但是这种方法存在一些问题:
-
找出最远点是计算密集型的。
-
当数据集中存在异常值时,这种方法有时会产生不良结果。因此,我们可能无法获得所需的密集区域。
-
K-means 算法的时间空间复杂度
K-means 并不需要那么多的空间,因为我们只需要存储数据点和质心。
K-means 算法的存储需求 O((m+K)n),其中:
-
m 是点数
-
n 是属性数
K-means 算法的时间要求可能会有所变化,但通常也是适度的。随着数据点数量的增加,时间会线性增加。
K-means 算法的时间要求:O(IKmn)*,其中:
- I 是收敛到一个质心所需的迭代次数
如果所需的聚类数远远小于 K-means 所基于的数据点数,则 K-means 效果最佳。
K-means 的问题
基本的 K-means 聚类算法存在一些问题。让我们详细讨论这些问题。
K-means 中的空聚类
可能会出现空聚类的情况。当在分配点的阶段没有分配到特定给定聚类的点时,就会发生这种情况。可以按以下方式解决:
-
我们选择一个不同的质心作为当前选择。如果不这样做,平方误差将远远大于阈值。
-
要选择一个不同的质心,我们遵循找到当前质心的最远点的相同方法。这通常会消除导致平方误差的点。
-
如果我们得到多个空聚类,则必须再次重复此过程。
数据集中的异常值
当我们使用平方误差时,异常值可能是决定性因素,可以影响形成的聚类。这意味着当数据集中存在异常值时,我们可能无法达到期望的聚类,或者真正代表分组数据点的聚类可能没有相似的特征。
这也导致较高的平方误差和。因此,在应用聚类算法之前,通常会先移除异常值。
也可能出现一些我们不希望移除离群点的情况。例如,一些点,如 Web 上的异常活动、过度的信用等,对于业务来说是有趣和重要的。
不同类型的聚类
K-means 有一些限制。K-means 最常见的限制是它在识别自然聚类时遇到困难。所谓自然聚类是指:
-
非球形/圆形的形状
-
不同大小的聚类
-
不同密度的聚类
提示
如果存在几个密集的聚类和一个不太密集的聚类,K-means 可能会失败。
下面是不同大小的聚类示意图:
上述图像有两张。第一张是原始点,第二张是三个 K-means 聚类。我们可以看到这些聚类并不准确。这种情况发生在聚类的大小不一样时。
下面是不同密度的聚类示意图:
上面的图有两张图片。第一张是原始点,第二张是三个 K-means 聚类。聚类具有不同的密度。
下面是非球形聚类的示意图:
上述图像有两张。第一张是原始点,第二张是两个 K-means 聚类。这些聚类是非圆形或非球形的,K-means 算法未能正确检测到它们。
K-means —— 优势与劣势
K-means 有许多优势,也有一些劣势。我们先来讨论其优势:
-
K-means 可以用于各种类型的数据。
-
它易于理解和实现。
-
它即使在重复和多次迭代的情况下也很高效。
-
二分 K-means 是简单 K-means 的一种变体,更加高效。我们将在后面详细讨论这一点。
K-means 聚类的一些劣势或缺点包括:
-
它并不适用于所有类型的数据。
-
如前面示例所见,它在处理不同密度、大小或非球形聚类时效果不好。
-
当数据集中存在离群点时,会出现问题。
-
K-means 存在一个大限制,它通过计算中心来形成聚类。因此,我们的数据应该能够有一个“中心”。
二分 K-means 算法
二分 K-means 是简单 K-means 算法的扩展。在这里,我们通过将所有点集合分成两个聚类来找出 K 个聚类。然后我们选择其中一个聚类,再次进行分割。这个过程会持续进行,直到形成 K 个聚类。
二分 K-means 的算法是:
-
首先,我们需要初始化聚类列表,列表中将包含由所有数据点组成的一个聚类。
-
重复以下步骤:
-
现在我们从聚类列表中移除一个聚类
-
接下来我们多次尝试对聚类进行二分处理。
-
对于 n=1 到前一步骤中试验的次数
-
-
该簇使用 K-means 被二分:
-
从结果中选择两个总平方误差最小的簇
-
这两个簇被添加到簇的列表中
-
-
之前的步骤会一直执行,直到我们在列表中得到 K 个簇。
我们可以通过几种方式来拆分一个簇:
-
最大簇
-
总平方误差最大的簇
-
两者
我们将在这个例子中使用来自 RDatasets 的鸢尾花数据集:
这是著名的鸢尾花数据集的一个简单例子。我们使用 PetalLength 和 PetalWidth 来对数据点进行聚类。
结果如下:
深入了解层次聚类
这是仅次于 K-means 的第二大常用聚类技术。我们再用相同的例子来说明:
在这里,最上面的根节点表示所有数据点或一个簇。现在我们有三个子簇,用节点表示。所有这三个簇都有两个子簇,而这些子簇中还有进一步的子簇。这些子簇有助于找到纯粹的簇——也就是说,具有大多数共同特征的簇。
我们可以用两种方法来进行层次聚类:
-
聚合法:这是基于簇之间的接近度概念。我们一开始将每个点视为一个独立的簇,然后逐步合并最接近的簇对。
-
分割法:这里我们从一个包含所有数据点的簇开始,然后开始拆分,直到每个簇只包含一个数据点为止。在这种情况下,我们决定如何进行拆分。
层次聚类以树形图表示,也称为树状图(dendogram)。这用于表示簇与子簇之间的关系,以及簇是如何合并或拆分的(聚合法或分割法)。
聚合层次聚类
这是层次聚类的自底向上方法。在这里,每个观察点被视为一个独立的簇。这些簇对会根据相似性进行合并,然后我们向上移动。
这些簇根据最小的距离进行合并。当这两个簇合并时,它们会被当作一个新簇。这个步骤会在数据点池中只剩一个簇时重复。
聚合层次聚类的算法是:
-
首先,计算邻接矩阵。
-
最接近的两个簇会被合并。
-
在第一步中创建的邻接矩阵会在合并两个簇后进行更新。
-
第 2 步和第 3 步会重复执行,直到只剩下一个簇。
如何计算接近度
前述算法中的第 3 步是一个非常重要的步骤。它是衡量两个簇之间接近度的标准。
有多种方法可以定义这一点:
-
MIN:不同聚类中最接近的两个点定义了这些聚类的接近度。这是最短的距离。
-
MAX:与 MIN 相反,MAX 取聚类中的最远点,计算这两个点之间的接近度,并将其作为这两个聚类的接近度。
-
平均法:另一种方法是取不同聚类的所有数据点的平均值,并根据这些点计算接近度。
上述图示展示了使用 MIN 计算的接近度度量。
上述图示展示了使用 MAX 计算的接近度度量。
上述图示展示了使用平均法计算的接近度度量。
这些方法也被称为:
-
单链接法:MIN
-
完全链接法:MAX
-
平均链接法:平均
还有另一种方法,称为质心法。
在质心法中,接近度是通过聚类的两个均值向量来计算的。在每个阶段,两个聚类将根据哪一个具有最小的质心距离来合并。
我们来看以下例子:
上述图示展示了一个 x-y 平面中的七个点。如果我们开始进行凝聚型层次聚类,过程将如下:
-
{1},{2},{3},{4},{5},{6},{7}。
-
{1},{2,3},{4},{5},{6},{7}。
-
{1,7},{2,3},{4},{5},{6},{7}。
-
{1,7},{2,3},{4,5},{6}。
-
{1,7},{2,3,6},{4,5}。
-
{1,7},{2,3,4,5,6}。
-
{1,2,3,4,5,6,7}。
该过程被分解为七个步骤,以形成完整的聚类。
这也可以通过以下树状图表示:
这表示了前述的七个步骤,属于凝聚型层次聚类。
层次聚类的优缺点
之前讨论的层次聚类方法有时更适合某些问题。我们可以通过理解层次聚类的优缺点来理解这一点:
-
凝聚型聚类缺乏全局目标函数。这类算法的好处是没有局部最小值,且在选择初始点时没有问题。
-
凝聚型聚类可以很好地处理不同大小的聚类。
-
认为凝聚型聚类可以产生更好的聚类质量。
-
凝聚型聚类通常计算量大,并且在处理高维数据时效果不佳。
了解 DBSCAN 技术
DBSCAN是基于密度的空间聚类应用噪声(Density-based Spatial Clustering of Applications with Noise)的缩写。它是一种数据聚类算法,通过基于密度的扩展种子(起始)点来找到聚类。
它定位高密度区域,并通过它们之间的低密度区域将它们与其他区域分离。
那么,什么是密度呢?
在基于中心的方法中,密度是通过计算数据集中特定点在指定半径内的点数来得出的。这种方法实现起来很简单,且点的密度依赖于指定的半径。
例如,一个较大的半径对应于点m处的密度,其中 m 是半径内的数据点数量。如果半径较小,则密度可以为 1,因为只有一个点存在。
如何使用基于中心的密度来分类点
-
核心点:位于基于密度的聚类内部的点就是核心点。这些点位于密集区域的内部。
-
边界点:这些点位于聚类内部,但不是核心点。它们位于核心点的邻域内,位于密集区域的边界或边缘。
-
噪声点:那些既不是核心点也不是边界点的点就是噪声点。
DBSCAN 算法
非常接近的点被放在同一个聚类中。靠近这些点的点也会被放在一起。非常远的点(噪声点)会被丢弃。
DBSCAN 算法如下:
-
点被标记为核心点、边界点或噪声点。
-
噪声点会被剔除。
-
核心点之间通过特殊半径形成边缘。
-
这些核心点被组成一个聚类。
-
与这些核心点相关的边界点被分配到这些聚类中。
DBSCAN 算法的优缺点
如前所述,层次聚类有时更或少适合某些特定问题。我们可以通过理解层次聚类的优缺点来更好地理解这一点。
-
DBSCAN 能够处理不同形状和大小的聚类。它之所以能够做到这一点,是因为它通过密度定义了聚类。
-
它对噪声具有较强的抗干扰性。在找到更多聚类方面,它比 K-means 表现得更好。
-
DBSCAN 在面对具有不同密度的数据集时会遇到问题。
-
此外,它在处理高维数据时也有问题,因为在这种数据中找到密度变得困难。
-
计算最近邻时,它的计算开销较大。
聚类验证
聚类验证很重要,因为它告诉我们生成的聚类是否相关。在进行聚类验证时,需要考虑的重要点包括:
-
它有能力区分数据中是否真正存在非随机结构。
-
它能够确定实际的聚类数量。
-
它具有评估数据是否适合聚类的能力。
-
它应该能够比较两组聚类,找出哪一个聚类更好。
示例
我们将在层次聚类和 DBSCAN 的示例中使用ScikitLearn.jl。
如前所述,ScikitLearn.jl旨在提供一个类似于 Python 中实际 scikit-learn 的库。
我们首先需要将所需的包添加到环境中:
julia> Pkg.update()
julia> Pkg.add("ScikitLearn")
julia> Pkg.add("PyPlot")
这也要求我们在 Python 环境中安装 scikit-learn。如果尚未安装,我们可以使用以下命令进行安装:
$ conda install scikit-learn
接下来,我们可以从我们的示例开始。我们将尝试在ScikitLearn.jl中使用不同的聚类算法。这些算法在ScikitLearn.jl的示例中有提供:
julia> @sk_import datasets: (make_circles, make_moons, make_blobs)
julia> @sk_import cluster: (estimate_bandwidth, MeanShift, MiniBatchKMeans, AgglomerativeClustering, SpectralClustering)
julia> @sk_import cluster: (DBSCAN, AffinityPropagation, Birch)
julia> @sk_import preprocessing: StandardScaler
julia> @sk_import neighbors: kneighbors_graph
我们从官方的 scikit-learn 库导入了数据集和聚类算法。由于其中一些算法依赖于邻居的距离度量,我们还导入了 kNN:
julia> srand(33)
julia> # Generate datasets.
julia> n_samples = 1500
julia> noisy_circles = make_circles(n_samples=n_samples, factor=.5, noise=.05)
julia> noisy_moons = make_moons(n_samples=n_samples, noise=.05)
julia> blobs = make_blobs(n_samples=n_samples, random_state=8)
julia> no_structure = rand(n_samples, 2), nothing
这段代码将生成所需的数据集。生成的数据集大小足够大,可以用来测试这些不同的算法:
julia> colors0 = collect("bgrcmykbgrcmykbgrcmykbgrcmyk")
julia> colors = vcat(fill(colors0, 20)...)
julia> clustering_names = [
"MiniBatchKMeans", "AffinityPropagation", "MeanShift",
"SpectralClustering", "Ward", "AgglomerativeClustering",
"DBSCAN", "Birch"];
我们为这些算法指定了名称,并为图像填充了颜色:
julia> figure(figsize=(length(clustering_names) * 2 + 3, 9.5))
julia> subplots_adjust(left=.02, right=.98, bottom=.001, top=.96, wspace=.05, hspace=.01)
julia> plot_num = 1
julia> datasets = [noisy_circles, noisy_moons, blobs, no_structure]
现在,我们为不同的算法和数据集指定图像的生成方式:
在这里,我们对数据集进行标准化,以便更轻松地选择参数,并为需要距离度量的算法初始化kneighbors_graph:
在这里,我们正在创建聚类估算器,这些估算器是算法根据使用案例行为所必需的:
不同算法的类似估算器。
之后,我们将这些算法应用于我们的数据集:
for (name, algorithm) in zip(clustering_names, clustering_algorithms)
fit!(algorithm, X)
y_pred = nothing
try
y_pred = predict(algorithm, X)
catch e
if isa(e, KeyError)
y_pred = map(Int, algorithm[:labels_])
clamp!(y_pred, 0, 27) # not sure why some algorithms return -1
else rethrow() end
end
subplot(4, length(clustering_algorithms), plot_num)
if i_dataset == 1
title(name, size=18)
end
for y_val in unique(y_pred)
selected = y_pred.==y_val
scatter(X[selected, 1], X[selected, 2], color=string(colors0[y_val+1]), s=10)
end
xlim(-2, 2)
ylim(-2, 2)
xticks(())
yticks(())
plot_num += 1
end
得到的结果如下:
-
我们可以看到,凝聚型聚类和 DBSCAN 在前两个数据集上表现非常好
-
凝聚型聚类在第三个数据集上表现不佳,而 DBSCAN 表现良好
-
凝聚型聚类和 DBSCAN 在第四个数据集上都表现不佳
摘要
在本章中,我们学习了无监督学习及其与监督学习的区别。我们讨论了无监督学习的各种应用场景。
我们回顾了不同的无监督学习算法,并讨论了它们的算法、优缺点。
我们讨论了各种聚类技术以及聚类是如何形成的。我们学习了不同的聚类算法之间的差异,以及它们如何适应特定的应用场景。
我们了解了 K-means、层次聚类和 DBSCAN。
在下一章中,我们将学习集成学习。
参考文献
第八章 创建集成模型
一组人往往比单个个体做出更好的决策,特别是当每个组员都有自己的偏见时。这一理念同样适用于机器学习。
当单一算法无法生成真实的预测函数时,便会使用集成机器学习方法。当模型的性能比训练时间和模型复杂度更重要时,集成方法是首选。
在本章中,我们将讨论:
-
什么是集成学习?
-
构建集成模型。
-
组合策略。
-
提升法、装袋法和引入随机性。
-
随机森林。
什么是集成学习?
集成学习是一种机器学习方法,其中多个模型被训练用于解决相同的问题。这是一个过程,其中生成多个模型,并将从它们得到的结果结合起来,产生最终结果。此外,集成模型本质上是并行的;因此,如果我们可以访问多个处理器,它们在训练和测试方面要高效得多:
-
普通机器学习方法:这些方法使用训练数据来学习特定的假设。
-
集成学习:它使用训练数据构建一组假设。这些假设被组合以构建最终模型。
因此,可以说集成学习是一种为目标函数准备不同单独学习器的方法,采用不同的策略,并在长期内结合这些学习成果。
理解集成学习
正如我们所讨论的,集成学习将多个单独学习者的学习过程结合起来。它是多个已学习模型的聚合,旨在提高准确性:
-
基学习器:每个单独的学习器称为基学习器。基学习器可能适用于特定情境,但在泛化能力上较弱。
-
由于基学习器的泛化能力较弱,它们并不适用于所有场景。
-
集成学习使用这些基(弱)学习器来构建一个强学习器,从而得到一个相对更准确的模型。
-
通常,决策树算法作为基学习算法使用。使用相同类型的学习算法会产生同质学习器。然而,也可以使用不同的算法,这将导致异质学习器的产生。
如何构建集成模型
推荐基学习器应尽可能多样化。这使得集成能够以更高的准确性处理和预测大多数情况。可以通过使用数据集的不同子集、操控或转换输入,以及同时使用不同的学习技术来实现这种多样性。
此外,当单个基学习器具有较高准确性时,集成学习通常也能得到较好的准确性。
通常,集成的构建是一个两步过程:
-
第一步是创建基础学习器。它们通常是并行构建的,但如果基础学习器受到之前形成的基础学习器的影响,则会以顺序方式构建。
-
第二步是将这些基础学习器结合起来,创建一个最适合特定用例的集成模型。
通过使用不同类型的基础学习器和组合技术,我们可以共同产生不同的集成学习模型。
有不同的方法可以实现集成模型:
-
对训练数据集进行子采样
-
操控输入特征
-
操控输出特征
-
注入随机性
-
分类器的学习参数可以被修改
组合策略
组合策略可以分为两类:
-
静态组合器
-
自适应组合器
静态组合器:组合器的选择标准独立于组件向量。静态方法可以大致分为可训练和不可训练。
可训练的:组合器经历不同的训练阶段,以提高集成模型的表现。这里有两种广泛使用的方法:
-
加权平均法:每个分类器的输出根据其自身的表现度量进行加权:
- 在不同验证集上衡量预测的准确性
-
堆叠泛化:集成的输出被视为元分类器的特征向量
不可训练的:个别分类器的表现不会影响投票。可能会使用不同的组合器,这取决于分类器所生成的输出类型:
-
投票法:当每个分类器生成一个类标签时使用此方法。每个分类器为某一特定类别投票,获得最多票数的类别获胜。
-
平均法:当每个分类器都生成一个置信度估计时,使用平均法。集成中后验概率最大的类别获胜。
-
博尔达计数法:当每个分类器生成一个排名时使用此方法。
自适应组合器:这是一种依赖于输入的特征向量的组合函数:
-
每个区域局部的一个函数
-
分而治之的方法创建了模块化的集成和简单的分类器,它们专门处理不同区域的输入输出空间。
-
各个专家只需在其能力范围内表现良好,而不必对所有输入都有效
对训练数据集进行子采样
如果输出分类器在训练数据出现小的变化时需要经历剧烈的变化,那么学习器被认为是不稳定的:
-
不稳定学习器:决策树、神经网络等
-
稳定学习器:最近邻、线性回归等
这种特定的技术更适用于不稳定学习器。
子采样中常用的两种技术是:
-
自助法
-
提升法
自助法
袋装法也称为自助聚合。它通过对同一数据集进行有放回的子抽样生成额外的训练数据。它通过重复组合生成与原始数据集大小相同的训练数据集。
由于采用了有放回的抽样,每个分类器平均训练 63.2%的训练样本。
在这些多个数据集上训练后,袋装法通过多数投票来组合结果。获得最多票数的类别获胜。通过使用这些多个数据集,袋装法旨在减少方差。如果引入的分类器是无关的,则可以提高准确性。
随机森林是一种集成学习方法,采用袋装法,是最强大的方法之一。
让我们来看看袋装算法。
训练:
-
对于迭代,t=1 到 T:
-
从训练数据集中随机采样 N 个样本,采用有放回抽样
-
基础学习器在该样本上训练(例如决策树或神经网络)
-
测试:
-
对于测试样本,t=1 到 T:
-
启动所有已训练的模型
-
预测是基于以下内容进行的:
-
回归:平均
-
分类:多数投票
-
-
什么情况下袋装法有效?
袋装法在如果不使用的话可能会发生过拟合的场景中有效。我们来看一下这些场景:
-
欠拟合:
-
高偏差:模型不够好,未能很好地拟合训练数据
-
小方差:每当数据集发生变化时,分类器需要做出的调整非常小
-
-
过拟合:
-
小偏差:模型对训练数据拟合得过好
-
大方差:每当数据集发生小的变化时,分类器需要做出很大或剧烈的调整
-
袋装法旨在减少方差而不影响偏差。因此,模型对训练数据集的依赖减少。
提升
提升与袋装法不同。它基于 PAC 框架,即“可能近似正确”框架。
PAC 学习:具有比误分类错误更大的置信度和更小的准确度的概率:
-
准确度:这是测试数据集中正确分类样本的百分比
-
置信度:这是在给定实验中达到准确度的概率
提升方法
提升方法基于“弱学习器”的概念。当一个算法在二分类任务中的表现略好于 50% 时,它被称为弱学习器。该方法的目的是将多个弱学习器组合在一起,目标是:
-
提高置信度
-
提高准确性
这是通过在不同数据集上训练不同的弱学习器来完成的。
提升算法
-
训练:
-
从数据集中随机抽取样本。
-
首先,学习器 h1 在样本上进行训练。
-
评估学习器 h1 在数据集上的准确度。
-
使用不同的样本为多个学习者执行类似的过程。它们被划分为能够做出不同分类的子集。
-
-
测试:
- 在测试数据集上,学习通过所有学习者的多数投票来应用
信心也可以通过类似方式增强,就像通过某些权衡来提升准确性一样。
提升方法更像是一个“框架”而非一个算法。该框架的主要目标是将一个弱学习算法 W 转化为强学习算法。我们现在将讨论 AdaBoost,简称为“自适应提升算法”。AdaBoost 因为是最早成功且实用的提升算法之一而广为人知。
它不需要定义大量的超参数,并且能够在多项式时间内执行。该算法的优势在于它能够自动适应给定的数据。
AdaBoost – 通过采样进行提升
-
在 n 次迭代之后,Boosting 提供了一个在训练集上有分布 D 的弱学习器:
- 所有例子都有相同的概率被选为第一个组成部分
-
训练集的子采样是根据分布 Dn 进行的,并通过弱学习器训练模型
-
错误分类实例的权重通过调整使得后续分类器能够处理相对较难的案例
-
生成分布 D(n+1),其中错误分类样本的概率增加,而正确分类样本的概率减少
-
在 t 次迭代之后,根据模型的表现,对个别假设的投票进行加权
AdaBoost 的强大之处在于其通过自适应重采样样本,而非最终加权组合
提升方法在做什么?
-
每个分类器在特定数据子集上有其专长
-
一种算法集中处理具有不断增加难度的例子
-
提升方法能够减少方差(类似于 Bagging)
-
它还能够消除弱学习器的高偏差所带来的影响(这在 Bagging 中不存在)
-
训练与测试误差的表现:
-
我们可以将训练误差减少到接近 0
-
不会出现过拟合,测试误差体现了这一点
-
偏差与方差分解
让我们讨论 Bagging 和 Boosting 如何影响分类误差的偏差-方差分解:
-
可以从学习算法中预期的错误特征:
-
偏差项是衡量分类器相对于目标函数的表现的度量
-
方差项衡量分类器的鲁棒性;如果训练数据集发生变化,模型将如何受其影响?
-
-
Bagging 和 Boosting 都能够减少方差项,从而减少模型中的误差
-
还证明了提升方法试图减少偏差项,因为它专注于错误分类的样本
操作输入特征
另一种生成多个分类器的技术是通过操控我们输入给学习算法的特征集。
我们可以选择不同的特征子集和不同大小的网络。这些输入特征子集可以手动选择,而非自动生成。这种技术在图像处理领域广泛使用:一个非常著名的例子是主成分分析(PCA)。
在许多实验中生成的集成分类器能够像真实的人类一样执行任务。
还发现,当我们删除输入的某些特征时,会影响分类器的性能。这会影响整体投票结果,因此生成的集成分类器无法达到预期效果。
注入随机性
这是另一种普遍有效的生成分类器集成的方法。在这种方法中,我们将随机性注入到学习算法中。反向传播神经网络也是使用相同的技术生成的,针对隐藏权重。如果计算应用于相同的训练样本,但使用不同的初始权重,则得到的分类器可能会有很大差异。
决策树模型中最具计算成本的部分之一是准备决策树。对于决策树桩,这个过程是快速的;然而,对于更深的树结构,这个过程可能会非常昂贵。
昂贵的部分在于选择树的结构。一旦选择了树结构,利用训练数据填充叶节点(即树的预测)是非常便宜的。一种令人惊讶的高效且成功的选择是使用具有不同结构和随机特征的树。由此生成的树的集合被称为森林,因此这样构建的分类器被称为随机森林。
它需要三个参数:
-
训练数据
-
决策树的深度
-
数字形式
该算法独立生成每一棵 K 棵树,这使得并行化变得简单。对于每一棵树,它都会构建一个给定深度的完整二叉树。树枝上使用的特征是随机选择的,通常是有替换地选择,这意味着同一个特征可以多次出现在同一分支上。根据训练数据,叶节点将进行实际预测的填充。这个最后的步骤是唯一使用训练数据的时刻。随后的分类器仅依赖于 K 棵随机树的投票。
这种方法最令人惊讶的地方在于它表现得非常出色。它通常在大多数特征不重要时效果最佳,因为每棵树选择的特征数量很少。一些树将会查询那些无用的特征。
这些树基本上会做出随机预测。然而,其中一些树会查询好的特征并做出准确预测(因为叶子是基于训练数据进行评估的)。如果你有足够多的树,随机的预测会作为噪声被消除,最终只有好的树会影响最终分类。
随机森林
随机森林由 Leo Breiman 和 Adele Cutler 开发。它们在机器学习领域的优势在 2012 年 Strata 博客中得到了很好的展示:“决策树集成(通常称为随机森林)是现代最成功的通用算法”,因为它们“能够自动识别数据中的结构、交互和关系”。
此外,已注意到“许多 Kaggle 解决方案至少有一个顶级条目,强烈利用这一方法”。随机森林也成为了微软 Kinect 的首选算法,Kinect 是一个用于 Xbox 主机和 Windows 电脑的运动检测信息设备。
随机森林由一组决策树组成。因此,我们将开始分析决策树。
如前所述,决策树是一种类似树状的图表,每个节点都有一个基于单一特征的选择。给定一组特征,树会根据这些决策从节点到节点进行导航,直到到达叶子节点。这个叶子的名称即为给定特征列表的预测。一个简单的决策树可以用来决定你出门时需要带什么。
每棵树的构建方式如下:
-
随机选取一个n样本案例,其中 n 是训练集中的案例数量。
- 使用替代法选择这 n 个案例。这一特定数据集用于构建树。
-
节点根据x<X进行拆分,其中X是输入变量的数量。X在森林生长过程中不会改变。
-
由于树木允许达到最大深度,因此没有进行修剪。
随机森林的错误率依赖于以下因素(如原始论文所述):
-
当树之间的相关性增加时,随机森林的错误率也会增加。
-
当单个树较弱时,错误率会增加,而当单个树得到加强时,错误率会减少。
发现之前提到的x对相关性和强度有影响。增加x会增加强度和相关性,而减少x则会减少两者。我们试图找到x应该处于的特定值或范围,以实现最小错误。
我们使用oob(袋外)误差来寻找最佳的x值或范围。
这棵树并不能有效地分类所有的点。我们可以通过增加树的深度来改进这一点。这样,树就能 100%准确地预测样本数据,只需学习样本中的噪声。在极端情况下,这个算法相当于一个包含所有样本的词典。这就是过拟合,它会导致在进行测试外的预测时产生糟糕的结果。为了克服过拟合,我们可以通过为样本引入权重,并且每次分裂时仅考虑特征的一个随机子集来训练多个决策树。随机森林的最终预测将由大多数树的投票决定。这个方法也叫做集成(bagging)。它通过减少方差(训练集中的噪声错误)而不增加偏差(模型不足灵活所带来的错误)来提高预测的准确性。
随机森林的特征
-
与现有算法相比,随机森林具有很高的准确性。
-
它可以在大数据上有效且高效地使用。它们速度快,并且不需要昂贵的硬件来运行。
-
随机森林的一个关键特性是它能够处理多个输入变量。
-
随机森林可以在构建森林的过程中展示泛化误差的估计。它还可以提供分类的重要变量。
-
当数据稀疏时,随机森林是一种有效且准确的算法。它还可以用于预测缺失数据。
-
生成的模型可以在将来接收到的数据上使用。
-
在不平衡数据集中,它提供了平衡类人口中存在的错误的特性。
-
其中一些特征也可以用于无监督聚类,并且也可以作为一种有效的异常检测方法。
-
随机森林的另一个关键特性是它不会发生过拟合。
随机森林是如何工作的?
为了理解和使用不同的选择,关于如何计算这些选择的额外数据是有帮助的。大多数选项依赖于随机森林生成的两个数据对象。
当当前树的训练集通过有放回的抽样绘制时,大约三分之一的样本被保留下来,用于示例。
随着越来越多的树被添加到随机森林中,oob 数据有助于生成分类错误的估计。在每棵树构建完成后,大部分数据都会沿着树向下运行,并计算每对样本的接近度。
另一方面,如果两个样本共享相同的终端节点,则通过增加它们的接近度来增加 1。经过完整分析后,这些接近度会被归一化。接近度被用作替代缺失数据、发现异常值以及提供数据的深入视角。
袋外(oob)误差估计
随机森林消除了交叉验证的需求,以获得无偏的测试集误差估计。在构建过程中,误差估计如下:
-
每棵树的构建都使用了来自原始数据的交替自助样本。大约 33%的案例将留作自助测试,不参与第 k 棵树的构建。
-
可能存在一些在构建树时没有考虑到的情况。我们将这些情况放到第 k 棵树下进行分类。大约 33%的树会生成分类测试集。在运行的后期,每次当案例n为 oob 时,将j视为获得绝大多数投票的类别。j与n的真实类别在所有案例中点的中间位置不相等的次数就是 oob 误差估计。这符合在各种测试中保持无偏的原则。
基尼重要性
纯度度量通常用于决策树。误分类被度量,并称为基尼纯度,这适用于存在多个分类器的情境。
还有一个基尼系数。这适用于二分类。它需要一个能够根据属于正确类别的概率对样本进行排名的分类器。
接近度
如前所述,在构建随机森林时我们不会修剪树。因此,终端节点不会有很多实例。
为了找到接近度度量,我们将所有训练集中的案例运行到树中。假设案例 x 和案例 y 到达了同一个终端节点,那么我们就增加接近度 1。
运行结束后,我们将树的数量乘以 2,然后将增加了 1 的案例的接近度除以该数字。
在 Julia 中的实现
随机森林可以在 Kenta Sato 注册的 Julia 包中使用:
Pkg.update() Pkg.add("RandomForests")
这是基于 CART 的随机森林在 Julia 中的实现。这个包支持:
-
分类模型
-
回归模型
-
袋外(OOB)误差
-
特征重要性
-
各种可配置的参数
该包中有两个独立的模型:
-
分类
-
回归
每个模型都有自己的构造函数,通过应用fit方法进行训练。我们可以使用以下列出的一些关键字参数来配置这些构造函数:
RandomForestClassifier(;n_estimators::Int=10,
max_features::Union(Integer, FloatingPoint, Symbol)=:sqrt,
max_depth=nothing,
min_samples_split::Int=2,
criterion::Symbol=:gini)
这个适用于分类:
RandomForestRegressor(;n_estimators::Int=10,
max_features::Union(Integer, FloatingPoint, Symbol)=:third,
max_depth=nothing,
min_samples_split::Int=2)
这个适用于回归:
-
n_estimators:这是弱估计器的数量 -
max_features:这是每次分裂时候候选特征的数量-
如果给定的是整数(Integer),则使用固定数量的特征。
-
如果给定的是浮动点(FloatingPoint),则使用给定值的比例(0.0, 1.0]。
-
如果给定的是符号(Symbol),则候选特征的数量由策略决定。
-
:sqrt: ifloor(sqrt(n_features)) -
:third: div(n_features, 3)
-
-
-
max_depth:每棵树的最大深度- 默认参数
nothing表示没有最大深度的限制。
- 默认参数
-
min_samples_split:分裂节点时尝试的最小子样本数量 -
criterion:分类的杂质度量准则(仅限分类)-
:gini:基尼指数 -
:entropy:交叉熵
-
RandomForestRegressor 总是使用均方误差作为其杂质度量。当前,回归模型没有可配置的准则。
学习与预测
在我们的示例中,我们将使用 Ben Sadeghi 提供的令人惊叹的 "DecisionTree" 包。
该包支持以下可用模型:
-
DecisionTreeClassifier -
DecisionTreeRegressor -
RandomForestClassifier -
RandomForestRegressor -
AdaBoostStumpClassifier
安装很简单:
Pkg.add("DecisionTree")
让我们从分类示例开始:
using RDatasets: dataset
using DecisionTree
现在我们使用著名的鸢尾花数据集:
iris = dataset("datasets", "iris")
features = convert(Array, iris[:, 1:4]);
labels = convert(Array, iris[:, 5]);
这会生成一个修剪后的树分类器:
# train full-tree classifier
model = build_tree(labels, features)
# prune tree: merge leaves having >= 90% combined purity (default: 100%)
model = prune_tree(model, 0.9)
# pretty print of the tree, to a depth of 5 nodes (optional)
print_tree(model, 5)
它生成了前面图像中展示的树。现在我们应用这个学习到的模型:
# apply learned model
apply_tree(model, [5.9,3.0,5.1,1.9])
# get the probability of each label
apply_tree_proba(model, [5.9,3.0,5.1,1.9], ["setosa", "versicolor", "virginica"])
# run n-fold cross validation for pruned tree,
# using 90% purity threshold pruning, and 3 CV folds
accuracy = nfoldCV_tree(labels, features, 0.9, 3)
它生成了以下结果:
Fold 1
Classes:
3x3 Array{Int64,2}:
15 0 0
1 13 0
0 1 20
Any["setosa","versicolor","virginica"]
Matrix:
Accuracy:
3x3 Array{Int64,2}:
18 0 0
0 18 5
0 1 8
3x3 Array{Int64,2}:
17 0 0
0 11 2
0 3 17
0.96
Kappa: 0.9391727493917275
Fold 2
Classes: Any["setosa","versicolor","virginica"]
Matrix:
Accuracy: 0.88
Kappa: 0.8150431565967939
Fold 3
Classes: Any["setosa","versicolor","virginica"]
Matrix:
Accuracy: 0.9
Kappa: 0.8483929654335963
Mean Accuracy: 0.9133333333333332
现在让我们训练随机森林分类器:
# train random forest classifier
# using 2 random features, 10 trees, 0.5 portion of samples per tree (optional), and a maximum tree depth of 6 (optional)
model = build_forest(labels, features, 2, 10, 0.5, 6)
它生成了随机森林分类器:
3x3 Array{Int64,2}:
14 0 0
2 15 0
0 5 14
3x3 Array{Int64,2}:
19 0 0
0 15 3
0 0 13
3x3 Array{Int64,2}:
17 0 0
0 14 1
0 0 18
现在我们将应用这个学习到的模型并检查其准确性:
# apply learned model
apply_forest(model, [5.9,3.0,5.1,1.9])
# get the probability of each label
apply_forest_proba(model, [5.9,3.0,5.1,1.9], ["setosa", "versicolor", "virginica"])
# run n-fold cross validation for forests
# using 2 random features, 10 trees, 3 folds and 0.5 of samples per tree (optional)
accuracy = nfoldCV_forest(labels, features, 2, 10, 3, 0.5)
结果如下:
Fold 1
Classes: Any["setosa","versicolor","virginica"]
Matrix:
Accuracy: 0.86
Kappa: 0.7904191616766468
Fold 2
Classes: Any["setosa","versicolor","virginica"]
Matrix:
Accuracy: 0.94
Kappa: 0.9096929560505719
Fold 3
Classes: Any["setosa","versicolor","virginica"]
Matrix:
Accuracy: 0.98
Kappa: 0.9698613622664255
Mean Accuracy: 0.9266666666666666
3-element Array{Float64,1}:
0.86
0.94
0.98
现在让我们训练一个回归树:
n, m = 10³, 5 ;
features = randn(n, m);
weights = rand(-2:2, m);
labels = features * weights;
# train regression tree, using an averaging of 5 samples per leaf (optional)
model = build_tree(labels, features, 5)
apply_tree(model, [-0.9,3.0,5.1,1.9,0.0])
# run n-fold cross validation, using 3 folds, averaging of 5 samples per leaf (optional)
# returns array of coefficients of determination (R²)
r2 = nfoldCV_tree(labels, features, 3, 5)
它生成了以下树:
Fold 1
Mean Squared Error: 3.300846200596437
Correlation Coeff: 0.8888432175516764
Coeff of Determination: 0.7880527098784421
Fold 2
Mean Squared Error: 3.453954624611847
Correlation Coeff: 0.8829598153801952
Coeff of Determination: 0.7713110081750566
Fold 3
Mean Squared Error: 3.694792045651598
Correlation Coeff: 0.8613929927227013
Coeff of Determination: 0.726445409019041
Mean Coeff of Determination: 0.7619363756908465
3-element Array{Float64,1}:
0.788053
0.771311
0.726445
现在,训练回归森林变得更加简单,通过这个包:
# train regression forest, using 2 random features, 10 trees,
# averaging of 5 samples per leaf (optional), 0.7 of samples per tree (optional)
model = build_forest(labels,features, 2, 10, 5, 0.7)
# apply learned model
apply_forest(model, [-0.9,3.0,5.1,1.9,0.0])
# run n-fold cross validation on regression forest
# using 2 random features, 10 trees, 3 folds, averaging of 5 samples/leaf (optional),
# and 0.7 porition of samples per tree (optional)
# returns array of coefficients of determination (R²)
r2 = nfoldCV_forest(labels, features, 2, 10, 3, 5, 0.7)
它生成了以下输出:
Fold 1
Mean Squared Error: 1.9810655619597397
Correlation Coeff: 0.9401674806129654
Coeff of Determination: 0.8615574830022655
Fold 2
Mean Squared Error: 1.9359831066335886
Correlation Coeff: 0.950439305213504
Coeff of Determination: 0.8712750380735376
Fold 3
Mean Squared Error: 2.120355686915558
Correlation Coeff: 0.9419270107183548
Coeff of Determination: 0.8594402239360724
Mean Coeff of Determination: 0.8640909150039585
3-element Array{Float64,1}:
0.861557
0.871275
0.85944
为什么集成学习更优?
为了理解集成学习的泛化能力为何优于单一学习器,Dietterich 提出了三个理由。
这三个原因帮助我们理解集成学习的优越性,从而得出更好的假设:
-
训练信息不会提供足够的数据来选择单一的最佳学习器。例如,可能有多个学习器在训练数据集上表现得同样优秀。因此,将这些学习器结合起来可能是一个更好的选择。
-
第二个原因是,学习算法的搜索过程可能存在缺陷。例如,即使存在一个最佳假设,学习算法可能因为各种原因(如生成了一个优于平均水平的假设)而无法达到该假设。集成学习通过增加达到最佳假设的可能性来改善这一点。
-
第三个原因是,目标函数可能不存在于我们正在搜索的假设空间中。这个目标函数可能存在于不同假设空间的组合中,这类似于将多个决策树结合起来生成随机森林。
关于备受推崇的集成技术,已有许多假设研究。例如,提升(boosting)和袋装(bagging)是实现这些讨论的三点方法。
还观察到,提升方法即使在经过无数次训练后,也不会受到过拟合的影响,有时甚至能够在训练误差为零后进一步减少泛化误差。尽管许多科学家已经研究了这一现象,但理论上的解释仍然存在争议。
偏差-方差分解常用于研究集成方法的表现。观察到 Bagging 几乎能够消除方差,从而使它特别适合与经历巨大方差的学习器结合,如不稳定的学习器、决策树或神经网络。
提升方法能够最小化偏差,尽管它会减少方差,通过这种方式,使得它更适合用于像决策树这样的弱学习器。
集成学习的应用
集成学习广泛应用于以下场景:
-
光学字符识别
-
文本分类
-
面部识别
-
计算机辅助医学诊断
集成学习几乎可以应用于所有使用机器学习技术的场景。
总结
集成学习是一种通过结合弱分类器或不太准确的分类器来生成高精度分类器的方法。在本章中,我们讨论了构建集成的一些方法,并讲解了集成方法能够超越集成中任何单一分类器的三个基本原因。
我们详细讨论了 Bagging 和 Boosting。Bagging,亦称为 Bootstrap 聚合,通过对相同数据集进行有放回的子采样来生成用于训练的附加数据。我们还学习了为何 AdaBoost 表现如此优秀,并详细了解了随机森林。随机森林是高精度且高效的算法,不会发生过拟合。我们还研究了它们为何被认为是最佳的集成模型之一。我们使用“DecisionTree”包在 Julia 中实现了一个随机森林模型。