创建和使用闭包

巧了我就是萌 提交于 2020-03-23 13:18:35

函数内部定义的函数称为闭包(closure)。闭包的特点是在其函数体中,可以使用局部
参数,也可以使用其父环境中的变量。
举个例子,假设我们有如下函数:
add <- function(x, y) {
x + y
}
此函数有两个参数。每次调用 add( ) 函数时,都需要提供这两个参数。如果使用闭
包,就可以生成带有事先指定参数的特殊版本。在下一节中,我们将创建一个简单的闭包。
1.创建一个简单的闭包
现在我们创建一个名为 addn( ),包含一个参数 y 的函数。此函数不执行加法运算,
而是在其内部创建一个子函数,并将 y 加到其参数 x 上:
addn <- function(y) {
function(x) {
x + y
}
}
这里大家可能需要多想几遍才能理解,addn( ) 函数并不会像一般函数那样返回一个
数值,而是返回一个闭包,即定义在一个函数内部的函数。此闭包计算 x+y 的值,其中 x 是
局部参数,y 是其封闭环境中的参数。换句话说,addn( ) 并不是一个“计算器”,而是
一个产生“计算器”的“计算器工厂”。
函数工厂使我们能够创建专用的计算函数。例如,可以创建两个函数,给一个数值向
量分别加上 1 和 2:
add1 <- addn(1)
add2 <- addn(2)
这两个函数的作用就相当于 add(x, y) 的第 2 个参数 y 分别固定为 1 和 2。下面的
代码证实了 addn( ) 函数生成的“计算器”的作用:
add1(10)
## [1] 11
add2(10)
## [1] 12
以 add1( )为例。代码 add1<-addn(1) 是指执行 addn(1),并将生成的函数赋给 add1:
add1
## function(x) {
## x + y
## }
## <environment: 0x00000000139b0e58>
当我们输出 add1 时,与其他函数有点不同的是,输出结果中附带了 add1 的环境。如
果一个函数不在当前环境(本例中为全局环境)下,那么输出该函数时就会一并显示其所属
的环境。在 add1 的环境中,y 是在 addn(1) 中确定的,我们可以运行如下代码来证实:
environment(add1)$y
## [1] 1
我们可以对 add1 调用 environment( ) 函数来访问其封闭环境,并捕获到 y。这
个过程就是闭包的工作方式。同样地,我们也可以对 add2 调用 environment( ) 函数,
查看 addn(2) 中确定的 y 的值:
environment(add2)$y
## [1] 2
2.创建专用函数
闭包对于创建专用函数非常有用。例如,鉴于绘图的灵活性,绘图函数通常需要提供
许多参数。如果大多数情况下,只使用部分参数,就可以创建一个专用的简版函数,这样
可以使代码更容易编写和阅读。
下面这个 color_line( ) 函数就是固定了图形和线条类型参数的 plot( ) 函数的
简易版本,专门用来选择绘图颜色的函数。它就像一个可以造出所有颜色画笔的工厂:
color_line <- function(col) {
function(...) {
plot(..., type = "l", lty = 1, col = col)
}
}
如果我们想要一支红色的画笔,就可以调用 color_line( ) 函数生成一个专门画红
色线条的函数。此函数同样可以设置其他参数,诸如标题和字体等:
red_line <- color_ _line("red")
red_ _line(rnorm(30), main = "Red line plot")
该函数生成的折线图如图 9-1 所示。

 


图 9-1
相比于不使用专用函数的原版函数,上述代码看起来可读性更高一些:
plot(rnorm(30), type = "l", lty = 1, col = "red",
main = "Red line plot")
3.用极大似然估计拟合正态分布
在使用一个含有给定数据的算法时,闭包是很有用的。例如,最优化问题就是在给定
约束条件和数据,找出使目标函数最大化或最小化的一组参数。在统计学中,很多参数估
计问题本质上就是最优化问题。极大似然估计(maximum likelihood estimation,MLE)
是演示闭包的一个很好的例子。用数据估计统计模型的参数时,我们经常使用极大似然估
计(MLE,参见 https://en.wikipedia.org/wiki/Maximum_likelihood)。MLE 的想法很简单:
给定一个模型,参数的估计值应使观测数据最有可能发生。
对参数进行极大似然估计时,我们需要一个函数来衡量在给定模型下观测到一组给定
数据的可能性,然后运用最优化技术找出使上述概率最大化的参数值。
例如我们知道一组由正态分布产生的观测数据,但是不知道其参数:均值和标准差。
这里根据给出的数据,用 MLE 估计这两个参数的值。
首先,均值为 μ ,标准差为 σ 的正态分布的密度函数为:

 


因此,给定观测数据 x ,其似然函数为:

 


为了简化计算,我们对似然函数取自然对数,并在等式两边添加负号,得到负对数似
然函数:

 


负对数似然函数与原函数单调性相同,所以其最优化的解也与原函数相同,但求解过
程却简单得多。因此在 MLE 中通常使用对数似然函数来求解。
下面定义一个 nloglik( )函数,给定观测数据 x,该函数返回一个包含正态分布的
两个参数的闭包:
nloglik <- function(x) {
n <- length(x)
function(mean, sd) {
log(2 * pi) * n /2 + log(sd ^2) * n / 2 +sum((x - mean) ^2) /(2 * sd ^2)
}
}
这样对于任何给定的观测数据集,我们都可以调用 nloglik( ) 函数,得到参数为均
值和标准差的负对数似然函数。它说明了在假定真实模型的两个参数分别为 mean 和
sd 时,不能观测到给定数据的可能性有多大。
例如,我们用rnorm( )生成 10000 个均值为 1 ,标准差为 2 的正态分布随机数。mean =
1 和 sd = 2 就是该分布参数的真实值:
data <- rnorm(10000, 1, 2)
然后,调用 stats4 包中的 mle( )函数。此函数可使用多种数值方法求解给定参数
的负对数似然函数的最小值。需要为它设定一个数值搜索的起点和解的上下界:
fit <- stats4::mle(nloglik(data),
start = list(mean = 0, sd = 1), method = "L-BFGS-B",
lower = c(-5, 0.01), upper = c(5, 10))
经过多次迭代后,函数找到一个 MLE 解,并返回一个 S4 对象,其中包括解的相关数
据。要查看估计值与真实值的差距,可以从对象中提取参数 coef:
fit@coef
## mean sd
## 1.007548 1.990121
显然,估计值非常接近真实值。相对而言,我们可以证实两个估计值都有不到 1% 的
误差:
(fit@coef -c(1, 2)) /c(1, 2)
## mean sd
## 0.007547752 -0.004939595
以下代码的结果给出了数据直方图、使用真实参数的正态密度曲线(红线)和使用估
计参数的正态密度曲线(蓝线)的组合图:
hist(data, freq = FALSE, ylim = c(0, 0.25))
curve(dnorm(x, 1, 2), add = TRUE, col = rgb(1, 0, 0, 0.5), lwd = 6)
curve(dnorm(x, fit@coef[["mean"]], fit@coef[["sd"]]),
add = TRUE, col = "blue", lwd = 2)
结果生成了一个直方图,并且添加了拟合正态密度曲线,如图 9-2 所示。

 


图 9-2
可以看到用估计参数画出的密度曲线和真实模型非常接近。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!