采样方法(二)MCMC相关算法介绍及代码实现

采样方法(二)MCMC相关算法介绍及代码实现

2017-12-30 15:32:14 Dark_Scope 阅读数 10509更多

分类专栏: 机器学习

版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。

本文链接:https://blog.csdn.net/Dark_Scope/article/details/78937731

0.引子

书接前文,在采样方法(一)中我们讲到了拒绝采样、重要性采样一系列的蒙特卡洛采样方法,但这些方法在高维空间时都会遇到一些问题,因为很难找到非常合适的可采样Q分布,同时保证采样效率以及精准度。
本文将会介绍采样方法中最重要的一族算法,MCMC(Markov Chain Monte Carlo),在之前我们的蒙特卡洛模拟都是按照如下公式进行的:
E[f(x)]≈1m∑mi=1f(xi).  xi∼p.iid{E}[f(x)] \approx \frac{1}{m}\sum_{i=1}^m{f(x_i)}.\ \ x_i \sim p.iidE[f(x)]≈m1?i=1∑m?f(xi?).  xi?∼p.iid
我们的x都是独立采样出来的,而在MCMC中,它变成了
E[f(x)]≈1m∑mi=1f(xi).  (x0,x1,...,xm)∼MC(p){E}[f(x)] \approx \frac{1}{m}\sum_{i=1}^m{f(x_i)}.\ \ (x_0,x_1,...,x_m)\sim MC(p)E[f(x)]≈m1?i=1∑m?f(xi?).  (x0?,x1?,...,xm?)∼MC(p)
其中的MC(p)MC(p)MC(p)就是我们本文的主角之一,马尔可夫过程(Markov Process)生成的马尔可夫链(Markov Chain)。

1.Markov Chain(马尔可夫链)

序列的算法(一·a)马尔可夫模型中我们就说到了马尔可夫模型的马尔可夫链,简单来说就是满足马尔可夫假设
P(sn∣s0,s1,...,sn−1)=P(sn∣sn−1)P(s_n|s_0,s_1,...,s_{n-1}) = P(s_n|s_{n-1})P(sn?∣s0?,s1?,...,sn−1?)=P(sn?∣sn−1?)
的状态序列,由马尔可夫过程(Markov Process)生成。
一个马尔可夫过程由两部分组成,一是状态集合Ω\OmegaΩ,二是转移概率矩阵TTT。
其流程是:选择一个初始的状态分布π0\pi_0π0?,然后进行状态的转移:
πn=πn−1T\pi_n = \pi_{n-1}Tπn?=πn−1?T
得到的π0,π1,π2...πn\pi_0,\pi_1,\pi_2...\pi_nπ0?,π1?,π2?...πn?状态分布序列。

注意:我们在这里讲的理论和推导都是基于离散变量的,但其实是可以直接推广到连续变量。

马尔可夫链在很多场景都有应用,比如大名鼎鼎的pagerank算法,都用到了类似的转移过程;
马尔可夫链在某种特定情况下,有一个奇妙的性质:

在某种条件下,当你从一个分布π0\pi_0π0?开始进行概率转移,无论你一开始π0\pi_0π0?的选择是什么,最终会收敛到一个固定的分布π\piπ,叫做稳态(steady-state)。
稳态满足条件:
π=πT\pi = \pi Tπ=πT
这里可以参考《LDA数学八卦0.4.2》的例子,非常生动地描述了社会阶层转化的一个例子,也对MCMC作了非常好的讲解

书归正传,回到我们采样的场景,我们知道,采样的难点就在于概率密度函数过于复杂而无法进行有效采样,如果我们可以设计一个马尔可夫过程,使得它最终收敛的分布是我们想要采样的概率分布,不就可以解决我们的问题了么。

前面提到了在某种特定情况下,这就是所有MCMC算法的理论基础Ergodic Theorem:
如果一个离散马尔可夫链(x0,x1...xm)(x_0,x_1...x_m)(x0?,x1?...xm?)是一个与时间无关的Irreducible的离散,并且有一个稳态分布π\piπ,则:
E[f(x)]≈1m∑mi=1f(xi).  x∼π{E}[f(x)] \approx \frac{1}{m}\sum_{i=1}^m{f(x_i)}.\ \ x\sim \piE[f(x)]≈m1?i=1∑m?f(xi?).  x∼π
它需要满足的条件有这样几个,我们直接列在这里,不作证明:

1.Time homogeneous: 状态转移与时间无关,这个很好理解。
2.Stationary Distribution: 最终是会收敛到稳定状态的。
3.Irreducible: 任意两个状态之间都是可以互相到达的。
4.Aperiodic:马尔可夫序列是非周期的,我们所见的绝大多数序列都是非周期的。

虽然这里要求是离散的马尔可夫链,但实际上对于连续的场景也是适用的,只是转移概率矩阵变成了转移概率函数。

2.MCMC

在上面马尔可夫链中我们的所说的状态都是某个可选的变量值,比如社会等级上、中、下,而在采样的场景中,特别是多元概率分布,并不是量从某个维度转移到另一个维度,比如一个二元分布,二维平面上的每一个点都是一个状态,所有状态的概率和为1! 这里比较容易产生混淆,一定小心。

在这里我们再介绍一个概念:
Detail balance: 一个马尔可夫过程是细致平稳的,即对任意a,b两个状态:
π(a)Tab=π(b)Tba\pi(a)T_{ab} = \pi(b)T_{ba}π(a)Tab?=π(b)Tba?细致平稳条件也可以推导出一个非周期的马尔可夫链是平稳的,因为每次转移状态i从状态j获得的量与j从i获得的量是一样的,那毫无疑问最后πT=π\pi T=\piπT=π.

所以我们的目标就是需要构造这样一个马尔可夫链,使得它最后能够收敛到我们期望的分布π\piπ,而我们的状态集合其实是固定的,所以最终目标就是构造一个合适的T,就大功告成了。

一般来说我们有:
π(x)=π˜(x)Z\pi(x) = \frac {\tilde{\pi}(x)}{Z}π(x)=Zπ~(x)?
其中ZZZ是归一化参数Z=Σx′π˜(x′)Z=\Sigma_{x'}{\tilde{\pi}(x')}Z=Σx′?π~(x′),因为我们通常能够很方便地计算分子π˜(x)\tilde{\pi}(x)π~(x),但是分母的计算因为要枚举所有的状态所以过于复杂而无法计算。我们希望最终采样出来的样本符合π\piπ分布。

2.1.Metropolis

2.1.1原理描述

Metropolis算法算是MCMC的开山鼻祖了,它这里假设我们已经有了一个状态转移概率函数T来表示转移概率,T(a,b)表示从a转移到b的概率(这里T的选择我们稍后再说),显然通常情况下一个T是不满足细致平稳条件的:
π(a)T(a,b)≠π(b)T(b,a)\pi(a)T(a,b) \ne \pi(b)T(b,a)π(a)T(a,b)??=π(b)T(b,a)
所以我们需要进行一些改造,加入一项QQQ使得等式成立:
π(a)T(a,b)Q(a,b)=π(b)T(b,a)Q(b,a)\pi(a)T(a,b)Q(a,b) = \pi(b)T(b,a)Q(b,a)π(a)T(a,b)Q(a,b)=π(b)T(b,a)Q(b,a)
基于对称的原则,我们直接让
Q(a,b)=π(b)T(b,a).Q(b,a)=π(a)T(a,b).Q(a,b) = \pi(b)T(b,a).\\Q(b,a) = \pi(a)T(a,b).Q(a,b)=π(b)T(b,a).Q(b,a)=π(a)T(a,b).
所以我们改造后的满足细致平稳条件的转移矩阵就是:
T′(a,b)=T(a,b)Q(a,b)=T(a,b)(π(b)T(b,a))\begin{aligned}T'(a,b) &= T(a,b)Q(a,b)\\ &=T(a,b)\left(\pi(b)T(b,a)\right)\end{aligned}T′(a,b)?=T(a,b)Q(a,b)=T(a,b)(π(b)T(b,a))?
在Metropolis算法中,这个加入的这个Q项是此次转移的接受概率,是不是和拒绝采样有点神似。

MCMC采样算法
1.有初始状态x0x_0x0?
2.从t=0,1,2,3开始:

  • 根据T(x∣xt)T(x|x_t)T(x∣xt?)采样出x′x'x′
  • 以概率Q(xt,x′)=π(x′)T(x′,xt)Q(x_t,x')=\pi(x')T(x',x_t)Q(xt?,x′)=π(x′)T(x′,xt?)接受,即xt+1=x′x_{t+1}=x'xt+1?=x′
  • 否则使xt+1=xtx_{t+1} = x_txt+1?=xt?

但这里还有一个问题,我们的接受概率Q可能会非常小,而且其中还需要精确计算出π(x′)\pi(x')π(x′),这个我们之前提到了是非常困难的,再回到我们的细致平稳条件:
π(a)T(a,b)Q(a,b)=π(b)T(b,a)Q(b,a)\pi(a)T(a,b)Q(a,b) = \pi(b)T(b,a)Q(b,a)π(a)T(a,b)Q(a,b)=π(b)T(b,a)Q(b,a)
如果两边同时乘以一个数值,它也是成立的,比如
π(a)T(a,b)Q(a,b)∗10=π(b)T(b,a)Q(b,a)∗10\pi(a)T(a,b)Q(a,b)*10 = \pi(b)T(b,a)Q(b,a)*10π(a)T(a,b)Q(a,b)∗10=π(b)T(b,a)Q(b,a)∗10
所以我们可以同步放大Q(a,b)和Q(b,a),使得其中最大的一个值为1,也就是说:
Q¯¯¯(a,b)=min(1,Q(a,b)Q(b,a))=min(1,π(b)T(b,a)π(a)T(a,b))=min(1,π˜(b)T(b,a)π˜(a)T(a,b))\begin{aligned}\bar Q(a,b) &= \min\left(1,\frac{Q(a,b)}{Q(b,a)}\right)\\&= \min\left(1,\frac{\pi(b)T(b,a)}{\pi(a)T(a,b)}\right)\\&= \min\left(1,\frac{\tilde\pi(b)T(b,a)}{\tilde\pi(a)T(a,b)}\right)\end{aligned}Qˉ?(a,b)?=min(1,Q(b,a)Q(a,b)?)=min(1,π(a)T(a,b)π(b)T(b,a)?)=min(1,π~(a)T(a,b)π~(b)T(b,a)?)?
这样在提高接受率的同时,因为除式的存在我们还可以约掉难以计算的Z。

Metropolis-Hastings算法
1.有初始状态x0x_0x0?
2.从t=0,1,2,3…开始:

  • 根据T(x∣xt)T(x|x_t)T(x∣xt?)采样出x′x'x′
  • 以概率Q¯¯¯(a,b)=min(1,π˜(b)T(b,a)π˜(a)T(a,b))\bar Q(a,b)=\min\left(1,\frac{\tilde\pi(b)T(b,a)}{\tilde\pi(a)T(a,b)}\right)Qˉ?(a,b)=min(1,π~(a)T(a,b)π~(b)T(b,a)?)接受,即xt+1=x′x_{t+1}=x'xt+1?=x′
  • 否则使xt+1=xtx_{t+1} = x_txt+1?=xt?

2.1.2.代码实验

我们之前提到状态转移函数T的选择,可以看到如果我们选择一个对称的转移函数,即T(a,b)=T(b,a)T(a,b)=T(b,a)T(a,b)=T(b,a),上面的接受概率还可以简化为
Q¯¯¯(a,b)=min(1,π˜(b)π˜(a))\bar Q(a,b)=\min\left(1,\frac{\tilde\pi(b)}{\tilde\pi(a)}\right)Qˉ?(a,b)=min(1,π~(a)π~(b)?)
这也是一般Metropolis算法中采用的方法,T使用一个均匀分布即可,所有状态之间的转移概率都相同。
实验中我们使用一个二元高斯分布来进行采样模拟

其概率密度函数这样计算的,x是一个二维坐标:
p(x)=12πe−x(0)2−x(1)2p(x) = \frac{1}{2\pi}e^{-{x(0)}^2-{x(1)}^2}p(x)=2π1?e−x(0)2−x(1)2

def get_p(x):
    # 模拟pi函数
    return 1/(2*PI)*np.exp(- x[0]**2 - x[1]**2)
def get_tilde_p(x):
    # 模拟不知道怎么计算Z的PI,20这个值对于外部采样算法来说是未知的,对外只暴露这个函数结果
    return get_p(x)*20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

每轮采样的函数:

def domain_random(): #计算定义域一个随机值
    return np.random.random()*3.8-1.9
def metropolis(x):
    new_x = (domain_random(),domain_random()) #新状态
    #计算接收概率
    acc = min(1,get_tilde_p((new_x[0],new_x[1]))/get_tilde_p((x[0],x[1])))
    #使用一个随机数判断是否接受
    u = np.random.random()
    if u<acc:
        return new_x
    return x
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

然后就可以完整地跑一个实验了:

def testMetropolis(counts = 100,drawPath = False):
    plotContour() #可视化
    #主要逻辑
    x = (domain_random(),domain_random()) #x0
    xs = [x] #采样状态序列
    for i in range(counts):
        xs.append(x)
        x = metropolis(x) #采样并判断是否接受
    #在各个状态之间绘制跳转的线条帮助可视化
    if drawPath:
        plt.plot(map(lambda x:x[0],xs),map(lambda x:x[1],xs),‘k-‘,linewidth=0.5)
    ##绘制采样的点
    plt.scatter(map(lambda x:x[0],xs),map(lambda x:x[1],xs),c = ‘g‘,marker=‘.‘)
    plt.show()
    pass
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15


可以看到采样结果并没有想象的那么密集,因为虽然我们提高了接受率,但还是会拒绝掉很多点,所以即使采样了5000次,绘制的点也没有密布整个画面。

2.2.Gibbs Sampling

2.2.1.算法分析

通过分析Metropolis的采样轨迹,我们发现前后两个状态之间并没有特别的联系,新的状态都是从T采样出来的,而因为原始的分布很难计算,所以我们选择的T是均匀分布,因此必须以一个概率进行拒绝,才能保证最后收敛到我们期望的分布。
如果我们限定新的状态只改变原状态的其中一个维度,即
xnew=(x0t,x1t,..xjnew..,xnt)x_{new} = (x_t^0,x_t^1,..x_{new}^j..,x_t^n)xnew?=(xt0?,xt1?,..xnewj?..,xtn?)
只改变了其中第j个维度,则有π(xt)=P(x−jt)∗P(xjt∣x−jt)π(xnew)=P(x−jt)∗P(xjnew∣x−jt)\pi(x_{t}) = P(x_{t}^{-j})*P(x_{t}^j|x_{t}^{-j})\\\pi(x_{new}) = P(x_{t}^{-j})*P(x_{new}^j|x_{t}^{-j})π(xt?)=P(xt−j?)∗P(xtj?∣xt−j?)π(xnew?)=P(xt−j?)∗P(xnewj?∣xt−j?)其中x−jx^{-j}x−j表示除了第j元其他的变量。
所以有(以P(x−jt)P(x_{t}^{-j})P(xt−j?)为桥梁作转换很好得到):
π(xt)∗P(xjnew∣x−jt)=π(xnew)∗P(xjt∣x−jt)\pi(x_{t})*P(x_{new}^j|x_{t}^{-j}) = \pi(x_{new})*P(x_{t}^j|x_{t}^{-j})π(xt?)∗P(xnewj?∣xt−j?)=π(xnew?)∗P(xtj?∣xt−j?)

所以我们如果构造这样一个转移概率函数T:
1.如果状态a和b之间只在第j个元上值不同,则是的
$T(a,b)=P(b{(j)}|a{(-j)}) $
2.否则T(a,b)=0T(a,b) = 0T(a,b)=0

结论很清晰:这样一个转移概率函数T是满足细致平稳条件的,而且和Metropolis里面不同:它不是对称的
我们能够以1为概率接受它的转移结果。

以一个二元分布为例,在平面上:

A只能跳转到位于统一条坐标线上的B,C两个点,对于D,它无法一次转移到达,但是可以通过两次变换到达,仍然满足Irreducible的条件。

这样构造出我们需要的转移概率函数T之后,就直接得到我们的Gibbs采样算法了:

Gibbs Sampling算法
1.有初始状态x0x_0x0?
2.从t=0,1,2,3…开始进行循环采样:

  • 将xtx_txt?复制过来,主要是为了循环采样:xt+1=xtx_{t+1} = x_txt+1?=xt?
  • 枚举维度j = 0…N,修改每个维度的值:
      • 使得xjt+1∼P(xj∣x−jt+1)x_{t+1}^j\sim P(x^j|x_{t+1}^{-j})xt+1j?∼P(xj∣xt+1−j?)

2.2.2.代码实验

想必大家发现了,如果要写代码,我们必须要知道如何从P(xj∣x−jt+1)P(x^j|x_{t+1}^{-j})P(xj∣xt+1−j?)进行采样,这是一个后验的概率分布,在很多应用中是能够定义出函数表达的。

在我们之前实验的场景(二元正态分布),确实也能精确地写出这个概率分布的概率密度函数(也是一个正态分布)。

但退一步想,现在我们只关心一元的采样了,所以其实是有很多方法可以用到的,比如拒绝采样等等。

而最简单的,直接在这一维度上随机采几个点,然后按照它们的概率密度函数值为权重选择其中一个点作为采样结果即可。

代码里这样做的目的主要是为了让代码足够简单,只依赖一个均匀分布的随机数生成器。

def partialSampler(x,dim):
    xes = []
    for t in range(10): #随机选择10个点
        xes.append(domain_random())
    tilde_ps = []
    for t in range(10): #计算这10个点的未归一化的概率密度值
        tmpx = x[:]
        tmpx[dim] = xes[t]
        tilde_ps.append(get_tilde_p(tmpx))
    #在这10个点上进行归一化操作,然后按照概率进行选择。
    norm_tilde_ps = np.asarray(tilde_ps)/sum(tilde_ps)
    u = np.random.random()
    sums = 0.0
    for t in range(10):
        sums += norm_tilde_ps[t]
        if sums>=u:
            return xes[t]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

主程序的结构基本上和之前是一样的,

def gibbs(x):
    rst = np.asarray(x)[:]
    path = [(x[0],x[1])]
    for dim in range(2): #维度轮询,这里加入随机也是可以的。
        new_value = partialSampler(rst,dim)
        rst[dim] = new_value
        path.append([rst[0],rst[1]])
    #这里最终只画出一轮轮询之后的点,但会把路径都画出来
    return rst,path

def testGibbs(counts = 100,drawPath = False):
    plotContour()

    x = (domain_random(),domain_random())
    xs = [x]
    paths = [x]
    for i in range(counts):
        xs.append([x[0],x[1]])
        x,path = gibbs(x)
        paths.extend(path) #存储路径
    if drawPath:
        plt.plot(map(lambda x:x[0],paths),map(lambda x:x[1],paths),‘k-‘,linewidth=0.5)
    plt.scatter(map(lambda x:x[0],xs),map(lambda x:x[1],xs),c = ‘g‘,marker=‘.‘)
    plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

采样的结果:

其转移的路径看到都是与坐标轴平行的直线,并且可以看到采样5000词的时候跟Metropolis相比密集了很多,因为它没有拒绝掉的点。

后注

本文我们讲述了MCMC里面两个最常见的算法Metropolis和Gibbs Sampling,以及它们各自的实现,从采样路径来看:

  • Metropolis是完全随机的,以一个概率进行拒绝;
  • 而Gibbs Sampling则是在某个维度上进行转移。


如果我们仍然希望最后使用独立同分布的数据进行蒙特卡洛模拟,只需要进行多次MCMC,然后拿每个MCMC的第n个状态作为一个样本使用即可。

完整的代码见链接:
https://github.com/justdark/dml/blob/master/dml/tool/mcmc.py
因为从头到尾影响分布的只有get_p()函数,所以如果我们想对其他分布进行实验,只需要改变这个函数的定义就好了,比如说我们对一个相关系数为0.5的二元正态分布,只需要修改get_p()函数:
p(x)=12π1−0.52√exp(−12(1−0.52)(x(0)2−x(0)x(1)+x(1)2))p(x) = \frac{1}{2\pi\sqrt{1-0.5^2}}exp\left({-\frac{1}{2(1-0.5^2)}({x(0)}^2 - x(0)x(1) + {x(1)}^2)}\right)p(x)=2π1−0.52?1?exp(−2(1−0.52)1?(x(0)2−x(0)x(1)+x(1)2))

def get_p(x):
    return 1/(2*PI*math.sqrt(1-0.25))*np.exp(-1/(2*(1-0.25))*(x[0]**2 -x[0]*x[1] + x[1]**2))
  • 1
  • 2

就可以得到相应的采样结果:

而且因为这里并不要求p是一个归一化后的分布,可以尝试任何>0的函数,比如

def get_p(x):
    return np.sin(x[0]**2 + x[1]**2)+1
  • 1
  • 2

也可以得到采样结果:

PPS

终于赶在2017年结束之前填了一个小坑,很久没写博客了。

Reference

【1】LDA数学八卦
【2】Pattern Recognition and Machine Learning
【3】Mathematicalmonk’s machine learning video

原文地址:https://www.cnblogs.com/think90/p/11612293.html

时间: 2024-11-10 07:03:39

采样方法(二)MCMC相关算法介绍及代码实现的相关文章

浅谈算法和数据结构: 十二 无向图相关算法基础

从这篇文章开始介绍图相关的算法,这也是Algorithms在线课程第二部分的第一次课程笔记. 图的应用很广泛,也有很多非常有用的算法,当然也有很多待解决的问题,根据性质,图可以分为无向图和有向图.本文先介绍无向图,后文再介绍有向图. 之所以要研究图,是因为图在生活中应用比较广泛: 无向图 图是若干个顶点(Vertices)和边(Edges)相互连接组成的.边仅由两个顶点连接,并且没有方向的图称为无向图. 在研究图之前,有一些定义需要明确,下图中表示了图的一些基本属性的含义,这里就不多说明. 图的

Hibernate基础学习(二)&mdash;Hibernate相关API介绍

一.Hibernate的核心接口      所有的Hibernate应用中都会访问Hibernate的5个核心接口.      (1)Configuration接口: 配置Hibernate,启动Hibernate,创建SessionFactory对象.      (2)SessionFactory接口: 初始化Hibernate,创建Session.      (3)Session接口: 负责保存.更新.删除.加载和查询对象.      (4)Transaction接口: 管理事务.     

【STL学习】堆相关算法详解与C++编程实现(Heap)

转自:https://blog.csdn.net/xiajun07061225/article/details/8553808 堆简介 堆并不是STL的组件,但是经常充当着底层实现结构.比如优先级队列(Priority Queue)等等. 堆是一种完全二叉树,因此我们可以用数组来存储所有节点.在这里的实现中,采用了一个技巧:将数组中索引为0的元素保留,设置为极大值或者为极小值(依据大顶堆或者小顶堆而定).那么当某个节点的索引是i时,其左子节点索引为2*i,右子节点索引为2*i+1.父节点是i/2

十三种基于直方图的图像全局二值化算法原理、实现、代码及效果(转)

十三种基于直方图的图像全局二值化算法原理.实现.代码及效果(转) http://www.cnblogs.com/carekee/articles/3643394.html 图像二值化的目的是最大限度的将图象中感兴趣的部分保留下来,在很多情况下,也是进行图像分析.特征提取与模式识别之前的必要的图像预处理过程.这个看似简单的问题,在过去的四十年里受到国内外学者的广泛关注,产生了数以百计的阈值选取方法,但如同其他图像分割算法一样,没有一个现有方法对各种各样的图像都能得到令人满意的结果. 在这些庞大的分

Jsoup代码解读之二-DOM相关对象

Jsoup代码解读之二-DOM相关对象 之前在文章中说到,Jsoup使用了一套自己的DOM对象体系,和Java XML API互不兼容.这样做的好处是从XML的API里解脱出来,使得代码精炼了很多.这篇文章会说明Jsoup的DOM结构,DOM的遍历方式.在下一篇文章,我会并结合这两个基础,分析一下Jsoup的HTML输出功能. DOM结构相关类 我们先来看看nodes包的类图: 这里可以看到,核心无疑是Node类. Node类是一个抽象类,它代表DOM树中的一个节点,它包含: 父节点parent

数据结构(C语言版)顺序栈相关算法的代码实现

这两天完成了栈的顺序存储结构的相关算法,包括初始化.压栈.出栈.取栈顶元素.判断栈是否为空.返回栈长度.栈的遍历.清栈.销毁栈.这次的实现过程有两点收获,总结如下: 一.清楚遍历栈的概念 栈的遍历指的是从栈底想栈顶方向运行visit()函数,这是之前的学习中所忽略的:栈的遍历解除了栈的输出顺序只能从栈顶像栈底方向的限制. 二.清空栈时要不要将stacksize重置 网上看到有的人在实现清空栈这一功能时,将stacksize重置为0,我觉得有点问题,起初的想法是将其重置为初始化时的值,在与同学讨论

编程算法 - 二叉搜索树(binary search tree) 代码(C)

二叉搜索树(binary search tree) 代码(C) 本文地址: http://blog.csdn.net/caroline_wendy 二叉搜索树(binary search tree)能够高效的进行插入, 查询, 删除某个元素, 时间复杂度O(logn). 简单的实现方法例如以下. 代码: /* * main.cpp * * Created on: 2014.7.20 * Author: spike */ /*eclipse cdt, gcc 4.8.1*/ #include <s

探索推荐引擎内部的秘密,第 2 部分: 深入推荐引擎相关算法 - 协同过滤(转)

第 2 部分: 深入推荐引擎相关算法 - 协同过滤 本系列的第一篇为读者概要介绍了推荐引擎,下面几篇文章将深入介绍推荐引擎的相关算法,并帮助读者高效的实现这些算法. 在现今的推荐技术和算法中,最被大家广泛认可和采用的就是基于协同过滤的推荐方法.它以其方法模型简单,数据依赖性低,数据方便采集 , 推荐效果较优等多个优点成为大众眼里的推荐算法“No.1”.本文将带你深入了解协同过滤的秘密,并给出基于 Apache Mahout 的协同过滤算法的高效实现.Apache Mahout 是 ASF 的一个

探索推荐引擎内部的秘密,第 3 部分: 深入推荐引擎相关算法 - 聚类

聚类分析 什么是聚类分析? 聚类 (Clustering) 就是将数据对象分组成为多个类或者簇 (Cluster),它的目标是:在同一个簇中的对象之间具有较高的相似度,而不同簇中的对象差别较大.所以,在很多应用中,一个簇中的数据对象可以被作为一个整体来对待,从而减少计算量或者提高计算质量. 其实聚类是一个人们日常生活的常见行为,即所谓"物以类聚,人以群分",核心的思想也就是聚类.人们总是不断地改进下意识中的聚类模式来学习如何区分各个事物和人.同时,聚类分析已经广泛的应用在许多应用中,包