2022.08.06开始新的一章!
2022.08.07继续学习
2022.08.08继续学习
2022.08.09继续学习
2022.08.10继续学习
2022.08.11继续学习。结束这一章!

不过大数据集学习有其特有的问题,具体来说,是计算问题。假定我们的训练集的大小
m
m
m 为
100
,
000
,
000
100,000,000
100,000,000,对于许多现代数据集而言,这个数据量是很实际的,例如流行网站获取到的流量数据,我们得到的训练集会比数亿条数据还大得多。假设我们想训练一个线性回归模型或是一个逻辑回归模型,其梯度下降规则如下:
θ
j
:
=
θ
j
−
α
1
m
∑
i
=
1
m
(
h
θ
(
x
(
i
)
)
−
y
(
i
)
)
x
j
(
i
)
同时,我们再看看需要计算梯度的项(蓝色标注)。当 m m m 的值为 100 , 000 , 000 100,000,000 100,000,000 时,我们需要对一亿项进行求和,这是为了计算导数项以及演算单步下降。因为计算超过一亿项的代价太大了,为了计算梯度下降中的一步,接下来的几节我们将会讲述某个能够替代这个算法的算法,或是寻找更有效的计算这个导数的方法。
当你学完这一章,你就可以知道如何处理模型、线性回归、逻辑回归、神经网络等等,甚至是现在有一亿个样本的数据集。当然在我们把精力花在用一亿个样本训练模型之前,我们应该自问一下,为什么不只用一千个样本,也许我们可以随机选择一亿个样本中的一千个样本的子集,然后仅用这一千个样本来训练算法。所以在投入精力到实际开发软件前,需要训练大量的模型,预先检查往往是个明智的选择,如果用一千个例子的训练效果也是一样的话。

使用一个非常小的训练集的完整性检查的方法结果可能也是一样的,即如果使用一个非常小的 m m m 的大小为 1000 1000 1000 的训练集,效果可能相同,它也是绘制学习曲线的常用方法。如果我们要绘制学习曲线,同时如果我们的训练目标看起来像蓝线这样,即 J t r a i n ( θ ) J_{train}(θ) Jtrain(θ) ,并且如果交叉验证集合目标 J c v ( θ ) J_{cv}(θ) Jcv(θ) 看起来像红线这样的话,那这看起来像一个高方差学习算法(上图左),我们更能确信增加额外的训练用例能够提升效果。相反如果绘制的学习曲线像这样的话(上图右),那么这看起来像是经典的高偏差学习算法。在后一种情况中,如果取最大值到 m = 1000 m=1000 m=1000,那么观察 m = 500 m=500 m=500 到 m = 1000 m=1000 m=1000 的图像,我们可以发现增加 m m m 到一亿效果不太可能会更好,因此坚持将 m m m 取值为 1000 1000 1000 也是可以的,而不是投入了很大精力去弄清楚算法的训练集规模。
当然,如果我们处于图右的情况,那么自然而然会添加额外的特征项或在神经网络中添加额外的隐藏单元等等,这样我们最终会得到类似图左的情况,它的 m m m 最大可能取到 1000 1000 1000,这也能令我们更确定应该尝试增加基础结构改变算法,使用多于一千的样本集,这样可能会充分利用我们的时间。
在大规模的机器学习中,我们喜欢找出合理的计算方法或高效的计算方法,用来处理庞大的数据集。在接下来的几节内容中,我们将了解两个主要方法,第一个称为随机梯度下降(Stochastic gradient descent),第二个为映射化简(Map reduce),用来处理海量的数据集。希望在了解了这些方法之后,我们能够将大数据应用到我们的学习算法之中,同时在许多不同的应用中得到更好的效果。
Linear
regression
with
gradient
descent
h
θ
(
x
)
=
∑
j
=
0
n
θ
j
x
j
J
t
r
a
i
n
(
θ
)
=
1
2
m
∑
i
=
1
m
(
h
θ
(
x
(
i
)
)
−
y
(
i
)
)
2
假设我们正在用梯度下降法来训练一个线性回归模型,快速回顾一下,假设函数是如上面
h
θ
(
x
)
h_θ(x)
hθ(x) 这样的,而代价函数是
J
t
r
a
i
n
(
θ
)
J_{train}(θ)
Jtrain(θ) 对应的式子,它是我们的
m
m
m 个训练样本的假设函数的平方误差的平均值再乘以
1
2
\frac{1}{2}
21,我们之前看到的代价函数都是这样的弓形函数(下图),因此,在图中画出参数
θ
0
θ_0
θ0、
θ
1
θ_1
θ1 对应的坐标轴和代价函数
J
J
J,它看起来是一个弓形的函数。

而梯度下降算法是这样的:
Repeat
{
θ
j
:
=
θ
j
−
α
1
m
∑
i
=
1
m
(
h
θ
(
x
(
i
)
)
−
y
(
i
)
)
x
j
(
i
)
(
f
o
r
e
v
e
r
y
j
=
0
,
.
.
.
,
n
)
}
在梯度下降的内部循环中,我们要用上面的式子反复更新参数
θ
θ
θ 的值。接下来,我们将继续使用线性回归作为我们的例子,不过,这里的随机梯度下降的思想是一种很常见的思想,它也同时应用于其它算法,比如逻辑回归,神经网络或者其他基于梯度下降的对应特定训练集的算法。

上图表示梯度下降的做法,假设上图中最外侧的红叉表示参数的初始位置,当我们运行梯度下降时,不断地迭代,将使参数达到全局最小值。因此,它将会以类似上图中红叉的运动轨迹来达到全局最小值。
但是现在有一个问题是,当 m m m 值很大的时候,计算这个微分项时(上图蓝色标注),计算量会变得非常大,因为需要对 m m m 个样本进行求和,假设 m m m 的值为 3 3 3 亿,表示在美国大约 3 3 3 亿人口,因此,美国的人口普查数据就有这种量级的数据记录。如果我们想要用这些数据去拟合一个线性回归模型的话,那么我们就需要对这 3 3 3 亿的数据进行求和,计算量就太大了。
这种梯度下降算法有另一个名字,叫做批量梯度下降(Batch gradient descent ),批量这个词指的是我们每次都要同时考虑所有的训练样本,我们称之为一批训练样本,可能这个名字不算最恰当的,但做机器学习的人都习惯这么称呼它。想象一下,如果我们真的有 3 3 3 亿人口普查的数据存在硬盘里,那么这种算法需要将这 3 3 3 亿人口的数据读入计算机中,仅仅为了计算出这个微分项,我们需要不断地将这些数据传入计算机中,但计算机的内存存不下这么多数据,所以我们得慢慢读取这些数据,然后进行一次求和,再算出这个微分,做完这些以后,我们才完成了梯度下降的其中一步,我们还得重头再来一遍,遍历这 3 3 3 亿个数据,然后计算和值,做完这些之后,我们依然只是完成了梯度下降的一小步,然后又要再来一遍,得到第三次迭代,一直这样下去。为了收敛计算结果,我们需要花费很长的时间。
对比这种批量梯度下降,我们要介绍的一种新算法就完全不同了,这种方法在每次迭代中不需要考虑全部的训练样本,仅仅只需要考虑一个训练样本。
1.
R
a
n
d
o
m
l
y
s
h
u
f
f
l
e
(
r
e
o
r
d
e
r
)
t
r
a
i
n
i
n
g
e
x
a
m
p
l
e
s
2.
R
e
p
e
a
t
{
f
o
r
i
:
=
1
,
.
.
.
,
m
{
θ
j
:
=
θ
j
−
α
(
h
θ
(
x
(
i
)
)
−
y
(
i
)
)
x
j
(
i
)
(
f
o
r
e
v
e
r
y
j
=
0
,
.
.
.
,
n
)
}
}

第一次迭代可能会让参数朝着这个方向移动(上图3),然后第二次迭代,只考虑第二个训练样本,假如偶然情况下,我们很不幸让参数走了一个错误的方向(上图4),但是在第三次迭代中,又会修改参数,使其更好的拟合第三组训练样本,可能最终会得到这个方向(上图5),然后第四组样本,然后第五第六第七等等。
在运行随机梯度下降的过程中我们会发现,总的来看,我们的参数是朝着全局最小值的方向移动的,虽然偶尔也有例外,不过整个过程还是以随机而迂回的路径朝着全局最小值前进(上图6)。实际上,当我们运行随机梯度下降时,和批量梯度下降相比,收敛的形式是不同的。随机梯度下降所做的就是连续不断地在某个区域中朝着全局最小值的方向徘徊,而不是直接达到全局最小值。在实际中其实完全可行,只要参数最终能移动到靠近全局最小值的区域内,所以只要参数最后能够非常接近全局最小值,我们就能得到一个很好的假设。因此,通常我们用随机梯度下降法,能得到一个很接近全局最小值的参数,对于实际应用的目的来说已经足够了。
最后一点细节,在随机梯度下降法中,有一个外层循环,它决定了内层循环的执行次数。所以外层循环应该执行多少次呢?这取决于训练集的大小,通常一次就够了,最多到 10 10 10 次,但那是特殊情况,所以,最终内层循环次数在 1 1 1 到 10 10 10 次之间。如果我们有一个非常大的数据集,比如美国人口普查的数据,也就是我们讨论的那3亿个样本,很可能,当我们仅遍历一次训练集时,外层的 i i i 就是从 1 1 1 亿到 3 3 3 亿了,很可能我们只遍历一次训练集就能得到一个非常好的假设,因为这是 m m m 非常大,所以内层循环只用做一次就够了。但通常来说,循环 1 1 1 到 10 10 10 次,都是非常合理的,但这还是取决于我们的训练样本的大小。
如果我们用它与批量下降算法相比的话,批量梯度下降仅仅在其中一步梯度下降的过程中,就需要考虑全部的训练样本,并且这只是梯度下降的一个小小的步骤,但它却需要遍历整个数据集,同时也说明了为什么随机梯度下降算法要快得多。
这就是随机梯度下降算法,如果我们能够亲自实现它,我们将能够将这种思想应用到很多学习算法中,来适应更大的数据集,从而提高算法的性能。
总结一下迄今为止讲过的算法:
Batch
gradient
descent:
U
s
e
a
l
l
m
e
x
a
m
p
l
e
s
i
n
e
a
c
h
i
t
e
r
a
t
i
o
n
Stochastic
gradient
descent:
U
s
e
1
e
x
a
m
p
l
e
i
n
e
a
c
h
i
t
e
r
a
t
i
o
n
Mini–batch
gradient
descent:
U
s
e
b
e
x
a
m
p
l
e
s
i
n
e
a
c
h
i
t
e
r
a
t
i
o
n
批量梯度下降算法中,每次迭代我们都要用到所有的
m
m
m 个样本,而在随机梯度下降算法中,每次迭代只需要使用一个样本。
M
i
n
i
–
B
a
t
c
h
\rm Mini\text{\textendash}Batch
Mini–Batch 梯度下降算法则是介于两者之间。具体来说,这个算法每次迭代会使用
b
b
b 个样本(这里
b
b
b 是一个称为
M
i
n
i
–
B
a
t
c
h
\rm Mini\text{\textendash}Batch
Mini–Batch 大小的参数),所以,它是介于批量梯度下降算法和随机梯度下降算法之间的算法。这与批量梯度下降算法有些相似,只不过我们会用一个小得多的批量大小。通常会选择
b
b
b 的值为
b
=
10
b=10
b=10,同时
b
b
b 取值范围为
b
=
2
b=2
b=2 到
b
=
100
b=100
b=100,对于
M
i
n
i
–
B
a
t
c
h
\rm Mini\text{\textendash}Batch
Mini–Batch 大小的选取而言,这是一个常用的取值范围,它的思想是既不一次只用一个样本,也不一次用
m
m
m 个样本,而是一次用
b
b
b 个样本。
G
e
t
b
=
10
e
x
a
m
p
l
e
s
(
x
(
i
)
,
y
(
i
)
)
,
.
.
.
,
(
x
(
i
+
9
)
,
y
(
i
+
9
)
)
θ
j
:
=
θ
j
−
α
1
10
∑
k
=
1
i
+
9
(
h
θ
(
x
(
k
)
)
−
y
(
k
)
)
x
j
(
k
)
我们会得到,比如 b b b,这个例子中假设 b = 10 b=10 b=10,我们将得到训练集中的 10 10 10 个样本,可能是某个 ( x ( i ) , y ( i ) ) (x^{(i)},y^{(i)}) (x(i),y(i)) 到 ( x ( i + 9 ) , y ( i + 9 ) ) (x^{(i+9)},y^{(i+9)}) (x(i+9),y(i+9)),所以这一共是 10 10 10 个样本,然后我们用这 10 10 10 个样本来执行梯度下降算法,以完成更新,即线性速率乘以 1 10 \frac{1}{10} 101,再乘以一个求和项为当 k = i k=i k=i 到 k = i + 9 k=i+9 k=i+9 时 h θ ( x ( k ) ) h_\theta(x^{(k)}) hθ(x(k)) 减 y ( k ) y^{(k)} y(k) 之差的和,再乘以 x j ( k ) x_j^{(k)} xj(k)。
在上面的式子中,是对 10 10 10 个样本进行梯度求和的,分母上的 10 10 10 就是 M i n i – B a t c h \rm Mini\text{\textendash}Batch Mini–Batch 大小的值(红色标注),并且 i + 9 i+9 i+9 中, 9 9 9 取自对参数 b b b 的选择(蓝色标注),运算完后,我们将增大 i i i 的值为 10 10 10 ,然后在使用后 10 10 10 个样本,并像之前那样继续进行下去。
写一下完整的算法:
Mini-Batch
gradient
descent
S
a
y
b
=
10
,
m
=
1000
.
R
e
p
e
a
t
{
f
o
r
i
:
=
1
,
11
,
21
,
31
,
.
.
.
,
991
{
θ
j
:
=
θ
j
−
α
1
10
∑
k
=
1
i
+
9
(
h
θ
(
x
(
k
)
)
−
y
(
k
)
)
x
j
(
k
)
(
f
o
r
e
v
e
r
y
j
=
0
,
.
.
.
,
n
)
}
}
为了简化下标,我们假设 M i n i – B a t c h \rm Mini\text{\textendash}Batch Mini–Batch 大小为 10 10 10(蓝色标注),训练样本大小为 1000 1000 1000(绿色标注),然后使用 R e p e a t Repeat Repeat 下括号里的内容进行循环,当 i = 1 , 11 , 21 , . . . i=1,11,21,... i=1,11,21,... 进行循环,同时步长为 10 10 10 ,因为我们每次使用 10 10 10 个样本,然后执行梯度下降算法,一次用 10 10 10 个样本进行更新,那么这个 10 10 10 和 i + 9 i+9 i+9 (红色标注)都表明 M i n i – B a t c h \rm Mini\text{\textendash}Batch Mini–Batch 大小选定为 10 10 10(蓝色标注),并且这个 f o r for for 循环会在 i = 991 i=991 i=991 时结束,因为如果我们有 1000 1000 1000 个训练样本,需要循环 100 100 100 次,每次 10 10 10 步,才能遍历我们的整个训练集。
这是几幅所画的图的例子:

假设我们已经画出了前 1000 1000 1000 组样本的 c o s t cost cost 函数平均值,由于它们只是 1000 1000 1000 组样本的平均值,因此看起来会有很多噪声,它可能不是每一步迭代都在下降。假如我们得到这样的图像(上图 1 蓝线),这个图有很多噪声,因为它只是对一小部分样本求平均值,在该例中,是 1000 1000 1000 个样本。如果我们得到像这样的图像,那么这是一个很不错的下降过程,可以看出代价函数的值在下降,然后从这个点开始(上图 1 绿色标注),图像开始变得平缓。通过这副图像,可以得知,学习算法已经收敛了。
如果我们尝试用一个更小的学习速率,那么我们可能会看到算法的学习变得更慢了,所以代价函数下降也变缓了。但是由于使用了更小的学习速率,最后可能会让算法收敛到一个更好的结果,这条红色的曲线(上图 1)就代表用更小学习速率来进行随机梯度下降。这种情况是因为随机梯度下降算法不是直接收敛到全局最小值,而是在一个范围内反复震荡,最后逐渐接近全局最小值。如果用一个更小的学习速率,最后这种振荡就会更小,不过两种曲线的这点差别有时是可以忽略的,但也有时候用更小的学习速率可以得到更好的参数的值。
P
l
o
t
c
o
s
t
(
θ
,
(
x
(
i
)
,
y
(
i
)
)
)
,
a
v
e
r
a
g
e
d
o
v
e
r
t
h
e
l
a
s
t
1000
(
5000
)
e
x
a
m
p
l
e
s
.
接下来再看几种其它情况,假如我们运行随机梯度下降,对 1000 1000 1000 组样本取 c o s t cost cost 函数平均值,并且画出图像,那么这可能是另一种可能出现的情况(上图 2 蓝线),看起来算法大概已经收敛了。如果我们把这 1000 1000 1000 组样本(上式紫色标注)提高到要去计算 5000 5000 5000 组样本的均值(上式红色标注),那么我们可能会得到一条更平滑的曲线,像这样(上图 2 红线)。求出均值以后,我们会发现 5000 5000 5000 组样本比起 1000 1000 1000 组样本得到的曲线更为平滑,这就是如果我们增大训练样本的数量所得到的情形。当然它的缺点就是,每隔 5000 5000 5000 个样本,我们才能得到一个数据点,因此,我们所得到的关于算法表现有多好的反馈,就显得有一些延迟,因为图中每一个数据点都是从 5000 5000 5000 个样本中得到的,而不是之前的 1000 1000 1000 个样本。
同样地,有时候我们运行梯度下降可能也会得到这样的图像(上图 3),如果出现这种情况,看起来我们的代价函数完全没有在减小,看起来算法没有进行学习,因为曲线整体看起来是平的,代价函数的值好像没有下降。但如果我们增加这里的数量(上式红色),来对更多的样本进行求均值,那么很可能会观察到红线所示的情况(上图 3),我们能看出,实际上代价函数是在下降的,只不过蓝线求均值的样本太少了,所以包含了太多的噪声,导致看不出函数实际上是趋向于减少的(上图 3 绿框中蓝线范围)。所以如果用 5000 5000 5000 个样本求均值,会比用 1000 1000 1000 个样本更好。当然,如果我们用更多的样本来求均值,可能我们会得到一条这样的学习曲线(上图 3 粉线),即使我们使用了更大数量的样本,曲线还是很平坦。如果得到这样的结果,很明显也很不幸,那就代表算法不知道出于何种原因没有进行学习,那么这时就需要调整学习速率或调整特征或者调整算法的其他东西。
最后,我们可能会遇到一条这样的曲线(上图 4),我们会发现曲线是这样的(上图 4 蓝线)它看起来是在上升的,这种情况就是算法发散的信号,这时要做的就是用一个更小的学习速率 α α α 。
通过上面的内容我们可以明白,当我们画出某个范围内样本的 c o s t cost cost 函数均值时,各种可能出现的情况,同时也说明了,我们在遇到这些情况时应该采取怎样的措施:如果曲线的噪声太大,或者老是上下振动,我们就可以试着增加求均值样本的数量,这样就能更好地看出函数变化的趋势,如果我们发现误差在上升,或者 c o s t cost cost 函数的值在上升,那么就用一个更小的 α α α 值。
S
h
i
p
p
i
n
g
s
e
r
v
i
c
e
w
e
b
s
i
t
e
w
h
e
r
e
u
s
e
r
c
o
m
e
s
,
s
p
e
c
i
f
i
e
s
o
r
i
g
i
n
a
n
d
d
e
s
t
i
n
a
t
i
o
n
,
y
o
u
o
f
f
e
r
t
o
s
h
i
p
t
h
e
i
r
p
a
c
k
a
g
e
f
o
r
s
o
m
e
a
s
k
i
n
g
p
r
i
c
e
,
a
n
d
u
s
e
r
s
s
o
m
e
t
i
m
e
s
c
h
o
o
s
e
t
o
u
s
e
y
o
u
r
s
h
i
p
p
i
n
g
s
e
r
v
i
c
e
(
y
=
1
)
,
s
o
m
e
t
i
m
e
s
n
o
t
(
y
=
0
)
.
O
t
h
e
r
e
x
a
m
p
l
e
s
:
C
h
o
o
s
i
n
g
s
p
e
c
i
a
l
o
f
f
e
r
s
t
o
s
h
o
w
u
s
e
r
;
c
u
s
t
o
m
i
z
e
d
s
e
l
e
c
t
i
o
n
o
f
n
e
w
s
a
r
t
i
c
l
e
s
;
p
r
o
d
u
c
t
r
e
c
o
m
m
e
n
d
a
t
i
o
n
;
…
我们还有一些其他的例子,其中一个例子是,如果我们有一个网站,我们想要决定给用户展示什么样的特别优惠,这与手机那个例子非常类似;或者我们有一个网站,我们给不同的用户展示不同的新闻文章,如果我们是一个新闻抓取网站,那么我们同样可以使用一个一个类似的系统来选择,来展示给用户他们最优可能感兴趣的新闻文章,以及那些他们最有可能点击的新闻文章;与特别优惠类似的还有商品推荐。而且实际上,如果我们有一个协作过滤系统(Collaborative filtering system),它可以给你更多的特征输入到逻辑回归分类器以此预测可能推荐给用户的不同产品的点击率。
当然我们需要说明的是,这些问题中的任何一个都可以被转变为标准的、拥有一个固定的样本集的机器学习问题,或许我们可以让网站先运行几天,然后保存一个数据集,一个固定的数据集,然后对其运行一个学习算法,但是这些问题在实际中,我们会看到大公司会获取如此多的数据,所以真的没有必要来保存一个固定的数据集,你可以使用一个在线学习算法来连续的学习,从这些用户不断产生的数据中来学习。

最后再提一点,目前只讨论了运用
M
a
p
R
e
d
u
c
e
\rm MapReduce
MapReduce 算法在多台电脑上并行计算,可能是计算机集群中的多台电脑,或者是数据中心的多台电脑,但是有时也可以在单机上进行
M
a
p
R
e
d
u
c
e
\rm MapReduce
MapReduce 计算,因为现在很多电脑可以有多个处理核心(processing cores) 有多个
C
P
U
\rm CPU
CPU ,
C
P
U
\rm CPU
CPU 又有多个核心。

如果我们有一个很大的数据集,然后比如我们有一个四核的电脑(就是四个计算核心),那么即使在单机上,我们也可以把训练集分成多份,然后把训练集发送给多个核心,在一台机器中完成,只用一个台式机或者一个服务器,用 M a p R e d u c e \rm MapReduce MapReduce 方法分摊工作,然后每个核心计算 1 4 \frac{1}{4} 41 训练样本的总和,再把每一部分之和加起来,最终得到整个训练集之和。把 M a p R e d u c e \rm MapReduce MapReduce 看成是一台机器中不同核心的并行,而不是多台机器并行,这样的好处是我们可以不用担心网络延迟问题了。因为通信传输收发变量 t e m p j temp_j tempj 都在同一台机器里,网络延迟是很小的,相比较于我们在数据中心的不同机器上进行并行计算。
最后关于多核机器并行计算还需要提到一点,取决于不同的实现,如果我们有一个多核机器,然后我们有某些线性代数的库,实际上,有些线性代数库可以自动在一台机器的不同核心上进行并行代数运算,如果我们幸运地用到这样的线性代数库(当然并不是每一个库都适用),并且如果我们的学习算法有非常好的向量化表示,我们就可以直接以向量化的形式应用标准学习算法,不用担心并行,因为我们的线性代数库会帮我们处理好,所以我们可以不应用 M a p R e d u c e \rm MapReduce MapReduce 。但是对于其它学习问题,利用 M a p R e d u c e \rm MapReduce MapReduce 的实现,或者使用 M a p R e d u c e \rm MapReduce MapReduce 的形式用不同核心并行计算,可能是个好办法,可以加速我们的学习算法。