机器学习

大数据和机器学习研究

[原]史上最直白的pca教程 之 二

pca的博文已经整理成一个完整的pdf文档,在这里下载:

http://download.csdn.net/detail/u011539200/9305773

 

不需要积分,累计人品,^_^

作者:u011539200 发表于2015/11/27 16:48:26 原文链接
阅读:15 评论:0 查看评论
 
[原]史上最直白的pca教程 之 一

PCA理论推导

 

X=⎛⎝⎜⎜⎜⎜x1,1x2,1...xm,1x1,2x2,2...xm,2............x1,nx2,n...xm,n⎞⎠⎟⎟⎟⎟

 

XRm×n,式的每列是一个样本,每个样本有m个属性,一共有n个样本。注意,这里的每个样本都经过均值化处理。

数据通常是含糊的,有噪声的,不明确的。这种含糊和不明确,体现在它的协方差阵的多数元素都是非零值。比如,X的协方差阵就是: 

CX=1n1XXT

其中,1n1是一个实数系数,CXRm×m

 

需要从数据找到一个不含糊的,低噪声的方向。这个需求,在本质上就是寻找一个矩阵,用它对X做变换,使得变换后的新矩阵的协方差阵大多数元素的值是零,最好的情况是,只有主对角线非零,其他都是零。令P表示这个矩阵,则: 

Y=PX

其中,PRm×m,YRm×n

 

根据上两式,令 

A=XXT

则: 
 

 

A是一个对称阵,对它进行对角化,可以写成A=EDET,其中,D是对角阵,{A,E,D}Rm×m

如果要将CY转化成对角阵,观察上式可知,如果令P=ET,根据矩阵对角化性质可知,P1=PTI表示单位阵,则上式就变成: 

 

 

均值化

PCA理论推导里的X经过均值化处理的。均值化过程如下: 

Z=⎛⎝⎜⎜⎜⎜z1,1z2,1...zm,1z1,2z2,2...zm,2............z1,nz2,n...zm,n⎞⎠⎟⎟⎟⎟

 

其中,ZRm×n,是原始数据。

令: 

zi¯¯¯=1nj=1nzi,j

 

那么,均值化就是:

 

Z¯¯¯=⎛⎝⎜⎜⎜⎜z1,1z1¯¯¯z2,1z2¯¯¯...zm,1zm¯¯¯¯z1,2z1¯¯¯z2,2z2¯¯¯...zm,2zm¯¯¯¯............z1,nz1¯¯¯z2,nz2¯¯¯...zm,nzm¯¯¯¯⎞⎠⎟⎟⎟⎟

 

实现

用Python2.7,matplotlib和numpy实现pca算法。

#!/usr/bin/env python 
#! -*- coding:utf-8 -*-

import matplotlib.pyplot as plt
from numpy import *

#create two data set
def create_dataset(n):
    data_r = random.randn(n, 2)
    #squeez y
    for i in range(data_r.shape[0]):
        data_r[i, 1] = 0.1*data_r[i, 1]
    #rotate
    theta = -0.25*3.14
    tran = zeros((2, 2))
    tran[1, 1] = tran[0, 0] = cos(theta)
    tran[0, 1] = -sin(theta)
    tran[1, 0] = sin(theta)
    data = dot(data_r, tran)
    #move
    data[:, 0] += 3
    data[:, 1] += 1
    return data.transpose()

def do_mean(X):
    means =  zeros((X.shape[0],1))
    means[0,0] = mean(X[0,:])
    means[1,0] = mean(X[1,:])
    for i in range(X.shape[0]):
        X[i,:] -= means[i,0]
    return X

def do_pca(X):
    A = dot(X, X.transpose())
    _ , E = linalg.eig(A)
    P = E.transpose()
    return dot(P, X)

def draw_x(X):
    plt.axis([-3, 6, -6, 6])
    plt.plot([z for z in X[0, :]], [z for z in X[1, :]], 'r.')
    plt.show()

def main():
    X = create_dataset(1000) 
    draw_x(X)
    do_mean(X)
    new_X = do_pca(X)
    draw_x(new_X)

if __name__ == "__main__":
    main()

该实现创建出一组数据,它近似高斯分布,且主方向在45度角方向的直线上。

这里写图片描述

经过PCA算法处理后,数据X成为以原点为中心,水平方向是主方向的新数据。究其本质来说,new_X是数据X在以P为基的二维空间的图像。 
这里写图片描述

作者:u011539200 发表于2015/11/27 16:43:48 原文链接
阅读:68 评论:0 查看评论
 
[原]史上最直白的logistic regression教程 之 五

史上最直白的logistic regression教程整理稿,将4篇博文整理成一个完整的pdf文档,且修改成学术语境。

链接在这里:

http://download.csdn.net/detail/u011539200/9290695

0积分下载,求rp,^_^

作者:u011539200 发表于2015/11/22 15:57:46 原文链接
阅读:46 评论:0 查看评论
 
[原]史上最直白的logistic regression教程 之 四

接上篇,用python实现logisitic regression,代码如下:

#!/usr/bin/env python 
#! -*- coding:utf-8 -*-

import matplotlib.pyplot as plt
from numpy import *

#创建数据集
def load_dataset():
    n = 100
    X = [[1, 0.005*xi] for xi in range(1, 100)]
    Y = [2*xi[1]  for xi in X]
    return X, Y

def sigmoid(z):
    t = exp(z)
    return t/(1+t)

#让sigmodi函数向量化,可以对矩阵求函数值,矩阵in,矩阵out
sigmoid_vec = vectorize(sigmoid)

#梯度下降法求解线性回归
def grad_descent(X, Y):
    X = mat(X)
    Y = mat(Y)
    row, col = shape(X)
    alpha = 0.05
    maxIter = 5000
    W = ones((1, col))
    V = zeros((row, row), float32)
    for k in range(maxIter):
        L = sigmoid_vec(W*X.transpose())
        for i in range(row):
            V[i, i] = L[0, i]*(L[0,i] - 1)
        W = W - alpha * (Y - L)*V*X
    return W

def main():
    X, Y = load_dataset()
    W = grad_descent(X, Y)
    print "W = ", W

    #绘图
    x = [xi[1] for xi in X]
    y = Y
    plt.plot(x, y, marker="*")
    xM = mat(X)
    y2 =  sigmoid_vec(W*xM.transpose())
    y22 = [y2[0,i] for i in range(y2.shape[1]) ]
    plt.plot(x, y22, marker="o")
    plt.show()

if __name__ == "__main__":
    main()

跟前面相对,多了一点变化,sigmoid_vec是对sigmoid函数的向量化,以及计算对对V的计算。

我们看看计算结果: 
迭代5次,拟合结果 
 
迭代50次,拟合结果 
 
迭代500次,拟合结果 
 
迭代5000次,拟合结果 
 
由于sigmoid函数的原因,拟合函数不是直线,这就是跟线性拟合的差别。

Logistic regression教程到此结束,就酱!

作者:u011539200 发表于2015/11/19 16:19:26 原文链接
阅读:71 评论:0 查看评论
 
[原]史上最直白的logistic regression教程 之 三

在线性拟合的基础上,我们实现logistic regression。

如前所述,样本集是 

{x1,y1},{x2,y2},...,{xn,yn}[1]

其中,xi=[1,xi,1,xi,2,xi,3,...,xi,k]T,且yi(0,1)。注意,这里对yi有值上的要求,必须如此,如果值不再这个区间,要以归一化的方式调整到这个区间。对于分类问题,则yi的取值或者是0,或者是1,也就是yi{0,1}

 

当然,从严格的意义上说,logistic regression拟合后,yi的值只能无限地逼近0和1,而不能真正达到0和1,但在处理实际问题上,可以设定成形如 ifyi>0.5thenyi=1ifyi<=0.5thenyi=0解决。

Logistic regression的拟合形式如下: 

yi=f(zi)[2]
zi=Wxi[3]

其中,f(z)=11+ez[4],也就是Logistic函数。

 

根据公式[2]和公式[3],则: 

yi=f(Wxi)[4]

 

那么,如果用公式[4]拟合xiyi的关系,需要求解W,使得在公式[1]上误差最小。对应的损失函数就是 

Loss=12i=1n(yif(Wxi))2[5]

 

跟前面的一样,我们用梯度下降法求解。 
所以,要对公式[5]wj的一阶偏导,于是有 

Losswj=i=1n(yif(Wxi))×(1)×f(Wxi)wj=i=1n(yif(Wxi))×(1)×f(zi)zi×ziwj[6]

 

注意,问题来了,公式[6]的最后一步,实际上是将Wxi视为一个变量zi,分别求导。这一步是在高等数学有详细描述了,不解释。

公式[6]中的f(zi)zi等价于f(z),因为只有一个自变量z。根据公式[4],可以求出

f(z)=ez(ez+1)2[7]

对公式[7]可以做一次变形,以方便求解: 
根据公式[4],可以知道
ez=f(z)1f(z)[8]

将公式[8]代入到公式[7],就可以得到
f(z)=f(z)×(1f(z))[9]

也就是说,我们可以根据f(z)得到f(z),而且计算量很小。

 

把公式[9]代入公式[6],就得到 

Losswj=i=1n(yif(Wxi))×(1)×f(zi)zi×ziwj=i=1n(yif(Wxi))×(1)×f(zi)×(1f(zi))×ziwj=i=1n(yif(Wxi))×(1)×f(Wxi)×(1f(Wxi))×(Wxi)wj=i=1n(yif(Wxi))×(1)×f(Wxi)×(1f(Wxi))×(Wxi)wj=i=1n(yif(Wxi))×(1)×f(Wxi)×(1f(Wxi))×xi,j=i=1n(yif(Wxi))×f(Wxi)×(f(Wxi)1)×xi,j[10]

于是公式[10]可以写成 
Losswj=i=1n(yif(Wxi))f(Wxi)(f(Wxi)1)xi,j[11]

那么,wj在梯度下降法的迭代公式就是 
wj=wj+wj=wjLosswj[12]

现在,我们开始做最麻烦的一步,将公式[11]进行矩阵化 
令 
Y=[y1,y2,...,yn][13]

W=[w0,w1,w2,...,wk][14]

X=⎛⎝⎜⎜⎜⎜11...1x1,1x2,1...xn,1x1,2x2,2...xn,2............x1,kx2,k...xn,k⎞⎠⎟⎟⎟⎟[15]

V=⎛⎝⎜⎜⎜⎜f(Wx1)(f(Wx1)1)0...00f(Wx2)(f(Wx2)1)...0............00...f(Wxn)(f(Wxn)1)⎞⎠⎟⎟⎟⎟[16]

L=[f(Wx1),f(Wx2),...,f(Wxn)][17]

公式[16]略有一点复杂,它是对角矩阵。 
根据上述设定,公式[11]的矩阵化形式就是 
Losswj=(YL)V⎛⎝⎜⎜⎜⎜x1,jx2,j...xn,j⎞⎠⎟⎟⎟⎟[18]

那么,对W而言,更新公式就是 
W=W(YL)VX[19]

到这里,logisitci regression的梯度下降法推导就结束了。下一篇我们用python实现求解过程。

 

作者:u011539200 发表于2015/11/19 16:09:49 原文链接
阅读:74 评论:0 查看评论
 
[原]史上最直白的logistic regression教程 之 一
本系列前四篇是随手涂鸦,只为讲清问题,有口语化,且有少数符号误写,以及重复絮叨,且不打算修改:) 第5篇提供了一个严谨的学术语言的完整pdf文档,敬请下载!

Logistic Regession是什么

Logistic Regression是线性回归,但最终是用作分类器:它从样本集中学习拟合参数,将目标值拟合到[0,1]之间,然后对目标值进行离散化,实现分类。

为什么叫Logistic呢?因为它使用了Logisitic函数,形如: 

f(z)=ezez+1=11+ez

这个函数有一些很有趣的性质,后面会谈到。

 

Logistic regression有一定的复杂度。对新人来说,最好有一个完整的推导指南,然后反复推导N遍(N>=5),直至能独立推导,再用python或者java实现这个推导,然后用这个实现解决一个实际应用,这样差不多算是掌握Logistic regression了。上述过程缺一不可,而且是成本最小的学习方案。

Logistic regression很重要,据说google的Ads广告使用的预测算法就是一个大Logistic regression模型。

Logistic regression涉及机器学习的多个重要概念,样本集,特征,向量,损失函数,最优化方法,梯度下降。如果对logistic regression能做到庖丁屠牛的程度,对未来进行模式识别和机器学习有事半功倍的收益。

我们现从一个最简单的问题开始,然后逐步增加功能,最终实现logistic regression。

先从一个最简单的问题开始

假如有一组样本,形如

{x1,y1},{x2,y2},...,{xi,yi},...{xn,yn}[1]

xi的值决定yi的值,也就是说xi是自变量,yi是因变量,每个xi对应一个yi。从脚标可以看出,这组样本一共有n个。

 

xi是一个向量,也就是说,xi里有多个元素,也就是可以表示为

xi=[xi,1,xi,2,...,xi,j,...xi,k]T[2]
显然,k表示xi的第k维。

 

实际上xi也可以写成 
xi=[xi,1,xi,2,...,xi,j,...xi,k],如果这样的话,后面的W和公式[6]就要做一点改动。如果推导过程很熟悉,可以将WxiXyiY等根据需求随意改变,不作限定。

我们拟合xiyi的关系,有了拟合,就可以根据xi计算yi。最简单的拟合方式是线性拟合,也就是形如:

yi=w0+w1×xi,1+w2×xi,2+...+wk×xi,k[3]

 

w0看起来不够和谐,跟其他元素不太一样,对xi做一点修改可以解决这个问题,将所有的xi重新表示成 

xi=[1,xi,1,xi,2,...,xi,j,...,xi,k]T[4]

那么,我们就得到: 
yi=w0×1+w1×xi,1+w2×xi,2+...+wk×xi,k[5]

这个公式的好处是,我们可以用向量方式表示了: 
yi=Wxi[6]

也就是说,
W=[w0,w1,...,wk]

注意,此时的xi不再是k维了,而是k+1维,同样W也是k+1维,以数学形式表示为xiR(k+1)×1WR1×(k+1),后面我们一直使用这种表示方式。

 

如果我们在公式[1]的样本集上做拟合,就要是公式[6]在公式[1]上误差最小。通常选择的误差形式是平方和误差,因为它求导方便,做梯度优化的时候计算便捷。误差形式如下 

Loss=12i=1n(yiWxi)2[7]

公式[7]是二次方程,有最小值,当它取最小值的时候的W就是最优拟合参数。

 

求解优化问题

公式[7]的求解不难,它有精确解,但当样本量很大的时候,精确解的求解是有问题的,比如矩阵是奇异阵不能求逆。所以通常会使用梯度下降法求解。

梯度下降法的方式是,先随机给W赋值,然后沿着公式[7]一阶偏导的反方向计算下降量值,多次重复,最终会让公式[7]收敛到一个极小值。那么,这个更新公式就是: 

W=W+W=WLossW[8]

公式[8]有些复杂,用更简单的方式,可以写成如下方式: 
wi=wi+wi=wiLosswi[9]

现在,我们用最原始的方式求解Losswj

 

 

Losswj=12[(y1(w0×1+w1× x1,1+...+wk×x1,k))2+(y2(w0×1+w1× x2,1+...+wk×x2,k))2+...+(yn(w0×1+w1× xn,1+...+wk×xn,k))2]/wi=[(y1(w0×1+w1× x1,1+...+wk×x1,k))×w1,j+(y2(w0×1+w1× x2,1+...+wk×x2,k))×w2,j+...+(yn(w0×1+w1× xn,1+...+wk×xn,k))×wn,j]=i=1n(yi(w0×1+w1×xi,1+...+wk×xi,k))×xi,j=i=1n(yi×xi,j)+i=1nWxixi,j[10]

 

注意我们要把公式[10]改写成矩阵形式,因为矩阵计算更有效率,方便实现。 
yi写成矩阵形式,令

Y=[y1,y2,...,yn][11]

xi写成矩阵形式,令
X=⎛⎝⎜⎜⎜⎜11...1x1,1x2,1...xn,1x1,2x2,2...xn,2............x1,jx2,j...xn,j............x1,kx2,kxn,k⎞⎠⎟⎟⎟⎟[12]

显然,XRn×(k+1)。 
那么,公式[10]最后一个等式中的ni=1(yi×xi,j)就可以写成 
i=1n(yi×xi,j)=Y⎛⎝⎜⎜⎜⎜x1,jx2,j...xn,j⎞⎠⎟⎟⎟⎟[13]

ni=1Wxixi,j可以写成 
i=1nWxixi,j=WXT⎛⎝⎜⎜⎜⎜x1,jx2,j...xn,j⎞⎠⎟⎟⎟⎟[14]

所以,公式[10]最终可以表示成 
Losswj=(YWXT)⎛⎝⎜⎜⎜⎜x1,jx2,j...xn,j⎞⎠⎟⎟⎟⎟[15]

注意,现在有一个很有意思的地方,(YWXT)是拟合误差,在更新W的过程中,每轮会进行一次计算。 
根据公式[15],对W而言,我们有了一个更为整体的计算方式: 
W=(YWXT)X[16]

所以,以梯度下降法计算最优W的更新公式是: 
W=W+(YWXT)X[17]

 

下一篇我们用python实现公式[17]

作者:u011539200 发表于2015/11/17 15:11:38 原文链接
阅读:187 评论:0 查看评论
 
[原]史上最直白的logistic regression教程 之 二

实现线性拟合

我们用python2.7实现上一篇的推导结果。请先安装python matplotlib包和numpy包。

具体代码如下:

#!/usr/bin/env python 
#! -*- coding:utf-8 -*-

import matplotlib.pyplot as plt
from numpy import *

#创建数据集
def load_dataset():
    n = 100
    X = [[1, 0.005*xi] for xi in range(1, 100)]
    Y = [2*xi[1]  for xi in X]
    return X, Y

#梯度下降法求解线性回归
def grad_descent(X, Y):
    X = mat(X)
    Y = mat(Y)
    row, col = shape(X)
    alpha = 0.001
    maxIter = 5000
    W = ones((1, col))
    for k in range(maxIter):
        W = W + alpha * (Y - W*X.transpose())*X
    return W

def main():
    X, Y = load_dataset()
    W = grad_descent(X, Y)
    print "W = ", W

    #绘图
    x = [xi[1] for xi in X]
    y = Y
    plt.plot(x, y, marker="*")
    xM = mat(X)
    y2 = W*xM.transpose()
    y22 = [y2[0,i] for i in range(y2.shape[1]) ]
    plt.plot(x, y22, marker="o")
    plt.show()

if __name__ == "__main__":
    main()

代码超级简单,load_dataset函数创建了一个y=2x的数据集,grad_descent函数求解优化问题。

在grad_descent里多了两个小东西,alpha是学习速率,一般取0.001~0.01,太大可能会导致震荡,求解不稳定。maxIter是最大迭代次数,它决定结果的精确度,通常是越大越好,但越大越耗时,所以通常需要试算以下,也可以另外写一个判定标准,比如当YWXT小于多少的时候就不再迭代。

我们来看一下效果: 
当maxIter=5时,拟合结果是这样的: 
 
如果maxIter=50,拟合结果是这样的: 
 
如果maxIter=500,拟合结果是这样的: 
 
如果maxIter=1000,拟合结果是这样的: 
 
如果maxIter=5000,拟合结果是这样的: 
 
5000次的结果几乎完美,两条曲线图形重合。就酱。 
本篇到此结束,下一篇,我们开始把logistic函数加进来,推导logistic regression。

作者:u011539200 发表于2015/11/17 15:02:48 原文链接
阅读:79 评论:0 查看评论
 
[原]TensorFlow试用

Google发布了开源深度学习工具TensorFlow。

 

根据官方教程  http://tensorflow.org/tutorials/mnist/beginners/index.md  试用。

 

操作系统是ubuntu 14.04,64位,python 2.7,已经安装足够的python包。

 

 

 

1. 安装

    1.1 参考文档 http://tensorflow.org/get_started/os_setup.md#binary_installation
    
    1.2 用pip安装,需要用代理,否则连不上,这个是本地ssh到vps出去的。

    sudo pip install https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-0.5.0-cp27-none-linux_x86_64.whl --proxy http://127.0.0.1:3128

    1.3 注意,我的py2.7已经安装了足够的包,如python-dev,numpy,swig等等。如果遇到缺少相应包的问题,先安装必须的包。

2. 第一个demo,test.py
------------------------------
import tensorflow as tf

hello = tf.constant('Hello, TensorFlow!')
sess = tf.Session()
print sess.run(hello)

a = tf.constant(10)
b = tf.constant(32)
print sess.run(a+b)

------------------------------


3. mnist手写识别
    3.1 下载数据库 
    在http://yann.lecun.com/exdb/mnist/下载上面提到的4个gz文件,放到本地目录如 /tmp/mnist

    3.2 下载input_data.py,放在/home/tim/test目录下
    https://tensorflow.googlesource.com/tensorflow/+/master/tensorflow/g3doc/tutorials/mnist/input_data.py

    3.3 在/home/tim/test目录下创建文件test_tensor_flow_mnist.py,内容如下
-----------------------
#!/usr/bin/env python 

import input_data
import tensorflow as tf

mnist = input_data.read_data_sets("/tmp/mnist", one_hot=True)

x = tf.placeholder("float", [None, 784])
W = tf.Variable(tf.zeros([784,10]))
b = tf.Variable(tf.zeros([10]))
y = tf.nn.softmax(tf.matmul(x,W) + b)
y_ = tf.placeholder("float", [None,10])
cross_entropy = -tf.reduce_sum(y_*tf.log(y))
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)
init = tf.initialize_all_variables()
sess = tf.Session()
sess.run(init)

for i in range(1000):
    batch_xs, batch_ys = mnist.train.next_batch(100)
    sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})

correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
print sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels})
-----------------------

3.4 运行。大概之需要几秒钟时间,输出结果是91%左右。

 

 

4. 关于版本

4.1  pip version


pip 1.5.4 from /usr/lib/python2.7/dist-packages (python 2.7)


4.2 已经安装的python包

    有一些是用easy_install安装的,大部分是pip安装的。

pip freeze


Jinja2==2.7.2
MarkupSafe==0.18
MySQL-python==1.2.3
PAM==0.4.2
Pillow==2.3.0
Twisted-Core==13.2.0
Twisted-Web==13.2.0
adium-theme-ubuntu==0.3.4
apt-xapian-index==0.45
argparse==1.2.1
beautifulsoup4==4.2.1
chardet==2.0.1
colorama==0.2.5
command-not-found==0.3
cvxopt==1.1.4
debtagshw==0.1
decorator==3.4.0
defer==1.0.6
dirspec==13.10
duplicity==0.6.23
fp-growth==0.1.2
html5lib==0.999
httplib2==0.8
ipython==1.2.1
joblib==0.7.1
lockfile==0.8
lxml==3.3.3
matplotlib==1.4.3
nose==1.3.1
numexpr==2.2.2
numpy==1.9.2
oauthlib==0.6.1
oneconf==0.3.7
openpyxl==1.7.0
pandas==0.13.1
patsy==0.2.1
pexpect==3.1
piston-mini-client==0.7.5
pyOpenSSL==0.13
pycrypto==2.6.1
pycups==1.9.66
pycurl==7.19.3
pygobject==3.12.0
pygraphviz==1.2
pyparsing==2.0.3
pyserial==2.6
pysmbc==1.0.14.1
python-apt==0.9.3.5
python-dateutil==2.4.2
python-debian==0.1.21-nmu2ubuntu2
pytz==2012c
pyxdg==0.25
pyzmq==14.0.1
reportlab==3.0
requests==2.2.1
scipy==0.13.3
sessioninstaller==0.0.0
simplegeneric==0.8.1
simplejson==3.3.1
six==1.10.0
software-center-aptd-plugins==0.0.0
ssh-import-id==3.21
statsmodels==0.5.0
sympy==0.7.4.1
system-service==0.1.6
tables==3.1.1
tensorflow==0.5.0
tornado==3.1.1
unity-lens-photos==1.0
urllib3==1.7.1
vboxapi==1.0
wheel==0.24.0
wsgiref==0.1.2
xdiagnose==3.6.3build2
xlrd==0.9.2
xlwt==0.7.5
zope.interface==4.0.5

 

 

作者:u011539200 发表于2015/11/10 16:10:42 原文链接
阅读:829 评论:3 查看评论
 
[原]weka实战005:基于HashSet实现的apriori关联规则算法

这个一个apriori算法的演示版本,所有的代码都在一个类。仅供研究算法参考

 

 

package test;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Vector;

//用set写的apriori算法
public class AprioriSetBasedDemo {

	class Transaction {
		/*
		 * 购物记录,用set保存多个货物名
		 */
		private HashSet<String> pnSet = new HashSet<String>();

		public Transaction() {
			pnSet.clear();
		}

		public Transaction(String[] names) {
			pnSet.clear();
			for (String s : names) {
				pnSet.add(s);
			}
		}

		public HashSet<String> getPnSet() {
			return pnSet;
		}

		public void addPname(String s) {
			pnSet.add(s);
		}

		public boolean containSubSet(HashSet<String> subSet) {
			return pnSet.containsAll(subSet);
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			Iterator<String> iter = pnSet.iterator();
			while (iter.hasNext()) {
				sb.append(iter.next() + ",");
			}
			return "Transaction = [" + sb.toString() + "]";
		}

	}

	class TransactionDB {
		// 记录所有的Transaction
		private Vector<Transaction> vt = new Vector<Transaction>();

		public TransactionDB() {
			vt.clear();
		}

		public int getSize() {
			return vt.size();
		}

		public void addTransaction(Transaction t) {
			vt.addElement(t);
		}

		public Transaction getTransaction(int idx) {
			return vt.elementAt(idx);
		}

	}

	public class AssoRule implements Comparable<AssoRule> {
		private String ruleContent;
		private double confidence;

		public void setRuleContent(String ruleContent) {
			this.ruleContent = ruleContent;
		}

		public void setConfidence(double confidence) {
			this.confidence = confidence;
		}

		public AssoRule(String ruleContent, double confidence) {
			this.ruleContent = ruleContent;
			this.confidence = confidence;
		}

		@Override
		public int compareTo(AssoRule o) {
			if (o.confidence > this.confidence) {
				return 1;
			} else if (o.confidence == this.confidence) {
				return 0;
			} else {
				return -1;
			}
		}

		@Override
		public String toString() {
			return ruleContent + ", confidence=" + confidence * 100 + "%";
		}

	}

	public static String getStringFromSet(HashSet<String> set) {
		StringBuilder sb = new StringBuilder();
		Iterator<String> iter = set.iterator();
		while (iter.hasNext()) {
			sb.append(iter.next() + ", ");
		}
		if (sb.length() > 2) {
			sb.delete(sb.length() - 2, sb.length() - 1);
		}
		return sb.toString();
	}

	// 计算具有最小支持度的一项频繁集 >= minSupport
	public static HashMap<String, Integer> buildMinSupportFrequenceSet(
			TransactionDB tdb, int minSupport) {
		HashMap<String, Integer> minSupportMap = new HashMap<String, Integer>();

		for (int i = 0; i < tdb.getSize(); i++) {
			Transaction t = tdb.getTransaction(i);
			Iterator<String> it = t.getPnSet().iterator();
			while (it.hasNext()) {
				String key = it.next();
				if (minSupportMap.containsKey(key)) {
					minSupportMap.put(key, minSupportMap.get(key) + 1);
				} else {
					minSupportMap.put(key, new Integer(1));
				}
			}
		}

		Iterator<String> iter = minSupportMap.keySet().iterator();
		Vector<String> toBeRemoved = new Vector<String>();
		while (iter.hasNext()) {
			String key = iter.next();
			if (minSupportMap.get(key) < minSupport) {
				toBeRemoved.add(key);
			}
		}

		for (int i = 0; i < toBeRemoved.size(); i++) {
			minSupportMap.remove(toBeRemoved.get(i));
		}

		return minSupportMap;
	}

	public void buildRules(TransactionDB tdb,
			HashMap<HashSet<String>, Integer> kItemFS, Vector<AssoRule> var,
			double ruleMinSupportPer) {

		// 如果kItemFS的成员数量不超过1不需要计算
		if (kItemFS.size() <= 1) {
			return;
		}

		// k+1项频项集
		HashMap<HashSet<String>, Integer> kNextItemFS = new HashMap<HashSet<String>, Integer>();

		// 获得第k项频项集
		@SuppressWarnings("unchecked")
		HashSet<String>[] kItemSets = new HashSet[kItemFS.size()];
		kItemFS.keySet().toArray(kItemSets);

		/*
		 * 根据k项频项集,用两重循环获得k+1项频项集 然后计算有多少个tranction包含这个k+1项频项集
		 * 然后支持比超过ruleMinSupportPer,就可以生成规则,放入规则向量
		 * 然后,将k+1项频项集及其支持度放入kNextItemFS,进入下一轮计算
		 */
		for (int i = 0; i < kItemSets.length - 1; i++) {
			HashSet<String> set_i = kItemSets[i];
			for (int j = i + 1; j < kItemSets.length; j++) {
				HashSet<String> set_j = kItemSets[j];
				// k+1 item set
				HashSet<String> kNextSet = new HashSet<String>();
				kNextSet.addAll(set_i);
				kNextSet.addAll(set_j);
				if (kNextSet.size() <= set_i.size()
						|| kNextSet.size() <= set_j.size()) {
					continue;
				}

				// 计算k+1 item set在所有transaction出现了几次
				int count = 0;
				for (int k = 0; k < tdb.getSize(); k++) {
					if (tdb.getTransaction(k).containSubSet(kNextSet)) {
						count++;
					}
				}
				if (count <= 0) {
					continue;
				}

				Integer n_i = kItemFS.get(set_i);
				double per = 1.0 * count / n_i.intValue();
				if (per >= ruleMinSupportPer) {
					kNextItemFS.put(kNextSet, new Integer(count));
					HashSet<String> tmp = new HashSet<String>();
					tmp.addAll(kNextSet);
					tmp.removeAll(set_i);
					String s1 = "{" + getStringFromSet(set_i) + "}" + "(" + n_i
							+ ")" + "==>" + getStringFromSet(tmp).toString()
							+ "(" + count + ")";
					var.addElement(new AssoRule(s1, per));
				}
			}
		}

		// 进入下一轮计算
		buildRules(tdb, kNextItemFS, var, ruleMinSupportPer);
	}

	public void test() {
		// Transaction数据集
		TransactionDB tdb = new TransactionDB();

		// 添加Transaction交易记录
		tdb.addTransaction(new Transaction(new String[] { "a", "b", "c", "d" }));
		tdb.addTransaction(new Transaction(new String[] { "a", "b" }));
		tdb.addTransaction(new Transaction(new String[] { "b", "c" }));
		tdb.addTransaction(new Transaction(new String[] { "b", "c", "d", "e" }));

		// 规则最小支持度
		double minRuleConfidence = 0.5;
		Vector<AssoRule> vr = computeAssociationRules(tdb, minRuleConfidence);
		// 输出规则
		int i = 0;
		for (AssoRule ar : vr) {
			System.out.println("rule[" + (i++) + "]: " + ar);
		}
	}

	public Vector<AssoRule> computeAssociationRules(TransactionDB tdb,
			double ruleMinSupportPer) {
		// 输出关联规则
		Vector<AssoRule> var = new Vector<AssoRule>();

		// 计算最小支持度频项
		HashMap<String, Integer> minSupportMap = buildMinSupportFrequenceSet(
				tdb, 2);

		// 计算一项频项集
		HashMap<HashSet<String>, Integer> oneItemFS = new HashMap<HashSet<String>, Integer>();
		for (String key : minSupportMap.keySet()) {
			HashSet<String> oneItemSet = new HashSet<String>();
			oneItemSet.add(key);
			oneItemFS.put(oneItemSet, minSupportMap.get(key));
		}

		// 根据一项频项集合,递归计算规则
		buildRules(tdb, oneItemFS, var, ruleMinSupportPer);
		// 将规则按照可信度排序
		Collections.sort(var);
		return var;
	}

	public static void main(String[] args) {
		AprioriSetBasedDemo asbd = new AprioriSetBasedDemo();
		asbd.test();
	}

}

运行结果如下:

 

rule[0]: {d }(2)==>b (2), confidence=100.0%
rule[1]: {d }(2)==>c (2), confidence=100.0%
rule[2]: {d, a }(1)==>c (1), confidence=100.0%
rule[3]: {d, a }(1)==>b (1), confidence=100.0%
rule[4]: {d, a }(1)==>b (1), confidence=100.0%
rule[5]: {d, c }(2)==>b (2), confidence=100.0%
rule[6]: {d, b, a }(1)==>c (1), confidence=100.0%
rule[7]: {d, b, a }(1)==>c (1), confidence=100.0%
rule[8]: {d, c, a }(1)==>b (1), confidence=100.0%
rule[9]: {b }(4)==>c (3), confidence=75.0%
rule[10]: {b, c }(3)==>d (2), confidence=66.66666666666666%
rule[11]: {b, c }(3)==>d (2), confidence=66.66666666666666%
rule[12]: {d }(2)==>a (1), confidence=50.0%
rule[13]: {b }(4)==>a (2), confidence=50.0%
rule[14]: {d, c }(2)==>b, a (1), confidence=50.0%
rule[15]: {d, b }(2)==>a (1), confidence=50.0%

 

作者:u011539200 发表于2015/5/27 6:33:28 原文链接
阅读:113 评论:0 查看评论
 
[原]weka实战004:fp-growth关联规则算法

apriori算法的计算量太大,如果数据集略大一些,会比较慢,非常容易内存溢出。

 

我们可以算一下复杂度:假设样本数有N个,样本属性为M个,每个样本属性平均有K个nominal值。

1. 计算一项频繁集的时间复杂度是O(N*M*K)。

2. 假设具有最小支持度的频繁项是q个,根据它们则依次生成一项频繁集,二项频繁集,....,r项频繁集合,它们的元素数量分别是:c(q, 1), c(q,2), ...,c(q, r)。那么频繁集的数量是极大的,单机肯定不能支持,比如说,假设q=10000--其实很小,电商/零售商的数据比这大太多了--此时生成的二项频繁集合的元素数量是5千万,三项频繁集超过1000亿... 打住吧,不要再往下算了...

3. 如果transaction有100万个,这也不算多,但计算二项频繁集的关联规则就要扫描数据库100万*5千万。

 

所以快速算法是必须,否则搞不下去。

 

fp-growth就是一种快速算法,设计非常巧妙,它的流程是这样的:

 

1. 计算最小支持度频繁项,并按照支持度从大到小排列,形如{'f':100, 'c':84, 'd':75, 'a':43, 'q':19, ...}

 

2. 把transaction的所有记录,都按照最小支持度频繁项进行排列,如果没有某个频繁项,就空下来,于是,transaction就是如下的形式:

{'f', 'd', 'q', ....}  //前面是频繁项,后面是非频繁项

{'c', 'd', 'a', ...}

...

 

3. 然后,建立一个fp-tree,树结构:

    3.1 树的根节点是null

    3.2 把transaction的记录向树结构做插入:

        3.2.1 第一次插入{'f', 'd', 'q', ....},此时null的子节点没有'f',那就建立一个名为'f'的节点,将它的次数计为1,然后将这个transaction的id存储在节点。

        3.2.2 第二次插入{'c', 'd', 'a', ...},此时null的子节点没有'c',那就建立一个名为'f'的节点,将它的次数计为1,然后将这个transaction的id存储在节点。

        3.2.3 以此类推,继续插入其他所有记录,如果遇到节点已经存在,把节点次数+1,再把transaction加入到节点。

        3.2.4 当所有的transaction被加入到fp-tree之后,fp-tree的第一层子节点有若干个,就把所有transaction的第一个元素进行了分类。

        3.2.5 再按照这个方式,再对所有transaction的第二个元素进行分类,也就是在fp-tree的根节点的子节点进行上述3.2.1~3.2.3的操作。

        3.2.6 知道将所有transaction分到不在有符合最小支持度的元素为止。这样fp-tree就建成了。

    3.3 计算关联规则,这就是很简单啦,凡是需要计算的频繁项集合,都在fp-tree上按照支持度列出来了,从根节点挨个往下薅就行了,而且,再也不需要遍历所有的transaction了,计算量大大减少。

    3.4 fp-tree的结构,很容易拆分到并行或者分布式计算。

 

4. 实际上,在原作paper,构造fp-tree的方式和本文的方式略有差别,它是深度优先的,比如说,对第一个transaction是一次性建立'f'-->'d'-->'q'三个节点,然后计数,其他以此类推。本文的方式为了方便理解。

 

 

 

 

 

 

 

作者:u011539200 发表于2015/5/24 21:45:38 原文链接
阅读:307 评论:0 查看评论
 
[原]weka实战003:apriori关联规则算法的实现

weka实现的apriori算法是在weka.associations包的Apriror类。

 

在这个类,挖掘关联规则的入口函数是public void buildAssociations(Instances instances),而instances就是数据集,检查数据,设置参数,初始化变量,然后,用一个do-while循环计算关联规则。如果你看过上一篇,就知道其实就是从一项频繁集开始,逐级二项频繁集... N项频繁集,直到达到停止准则,终止循环。

 

在这个循环里,根据条件有若干种计算关联规则的方式,其中一种方式,是执行两个函数:findLargeItemSets()和findRulesBruteForce()。

 

findLargeItemSets()函数的作用,就是计算出所有的超过最小支持度的频繁集。所有的操作都是用集合来的,这里最重要的类就是AprioriItemSet,它做各种集合操作,也计算关联规则。

 

findRulesBruteForce()函数,就是暴力直接粗鲁地一个个取出前面算好的AprioriItemSet,计算关联规则。

 

整个过程就这么简单。

 

 

 

 

 

作者:u011539200 发表于2015/5/23 21:36:12 原文链接
阅读:98 评论:0 查看评论
 
[原]weka实战002:apriori关联规则算法

关联规则算法最出名的例子就是啤酒和尿布放一起卖。

 

假如我们去超市买东西,付款后,会拿到一张购物清单。这个清单就是一个Transaction。对关联规则算法来说,每个产品的购买数量是无意义的,不参与计算。

 

许许多多的人买东西,生成了N个购物清单,也就是N个Transaction。

 

那么,这些Transaction上的货物之间有什么有用的关系呢?这些关系可以用什么方式表达出来呢?这就是关联规则算法要解决的问题。

 

下面,我们用一个具体的例子解释这个问题:

 

1. 假设有三个Transaction分别是:

t1 = {'a', 'b', 'c', 'd'}

t2 = {'a', 'c', 'e'};

t3 = {'b', 'c', 'f'}

其中,abcdef都是货物的ID,简写是为了方便理解。

 

2. 我们看一下,就知道只要买了'a',就可能会买'c',或者说,只要买了'c'就很可能买了'a',而且,在2个Transaction上都出现了。这个规律可以表达成:

  'c' ==> 'a'(66.67%)

后面的66.67%叫支持度,也就是'a'和'c'在一起出现的次数,处以c的次数,也就2/3=66.67%。

 

3. 这就是关联规则,各种关联规则算法要解决的是在样本数据很大或者样本数量很多的情况下计算关联规则,以及减少内存,提高计算速度。

 

4. 那么,apriori算法是如何做的呢?算法流程是这样的:

    4.1 先从所有的transaction遍历出所有货物id,也就{'a', 'b', 'c', 'd', 'e', 'f'}

    4.2 再计算每个货物id在所有transaction上出现次数总和,也就是{'a':2, 'b':2, 'c':3, 'd':1, 'e':1, 'f':1}

    4.3 有经验的同学可以知道上述两个步骤用HashMap能一次性搞定

    4.4 对4.2的结果,将出现次数少于一个最小支持数阈值的货物id删除,如果阈值是1,则剩下的结果就是{'a':2, 'b':2, 'c':3}

    4.5 对4.4的结果,生成一项频繁集,也就是{{'a'}, {'b'}, {'c'}}

    4.6 到这里为止,就得到了apriori算法的核心,频繁集,以后的所有计算都是在频繁集上进行:

        4.6.1 根据一项频繁集,生成二项频繁集,也就是{{'a','b'}, {'a','c'}, {'b','c'}},也就是任意两个一项频繁集的组合。

        4.6.2 计算二项频繁集的货物id同时在所有transaction上的出现次数:{{'a','b'}:1, {'a','c'}:2, {'b','c'}:2}

        4.6.3 根据最小支持数阈值=1,删除4.6.2的低值二项频繁集,其结果就是{{'a','c'}:2, {'b','c'}:2}

        4.6.4 根据二项频繁集合计算关联规则:

                  'c' ==> 'a'(66.67%)

                  'c' ==> 'b'(66.67%)

         4.6.5 根据二项频繁集,计算三项频繁集以及在三项频繁集上的关联规则,其步骤类似4.6.1~4.6.4。

         4.6.6 上述计算步骤,可以写成一个while循环,计算到高次频繁集为空,也就是不在有新规则产生为止。然后输出所有的规则。算法结束。

 

5. 几个问题

    5.1 关联规则不能处理连续值属性,所有要将连续值属性转化成nominal属性进行计算。

    5.2. 如果样本的属性值很多,或者transaction总数很多,apriori算法会很慢,因为每一轮计算都需要查询整个数据库。为此,学者们提出很多优化算法,剪切,并行,fp-growth等等。

 

 

 

 

 

 

作者:u011539200 发表于2015/5/22 7:30:15 原文链接
阅读:107 评论:0 查看评论
 
[原]Hadoop 1.x的Task,ReduceTask,MapTask随想

Hadoop的技术体系,最令人称赞的是细节。它的基本原理是非常容易理解的,细节是魔鬼。

 

hadoop的hdfs是文件系统存储,它有三类节点namenode, scondraynamenode, datanode,前两种在集群分别只有一个节点,而datanode在集群有很多个。hdfs的解耦做的非常好,以至于它可以单独运行,做一个海量数据的文件存储系统。它可以跟mapreduce分别运行。

 

对mapreduce任务来说,它有两类节点, jobtracker,tasktracker。前者每个集群之后一个,后者有许多个。顾名思义,tasktracker就是运行任务task。task有两种,maptask和reducertask。

 

一个mapreduce任务job,要做拆分,拆分成若干个inputsplit。每个inputsplit对应一个maptask。maptask执行完,将结果传给reducetask。然后reduecetask处理后将最终结果输出到hdfs存储。

 

MapTask和ReducerTask的基类是抽象类Task,它们在抽象的层次上近似,只是处理数据的流程不同。每个tasktracker节点可以同时运行这两种task。

 

这里有复杂的细节。tasktracker和jobtracker通过远程rpc的方式进行心跳服务。心跳服务调用会带上各种信息,有些是tasktracker报告自己的状态和任务执行情况,有些是jobtracker在应答里让tasktracker执行任务,不一而足。

 

每个job有jobid,拆分成若干个maptask和reducertask之后,又有taskid。每个maptask执行结束,将结果写入hdfs,又通过http的方式传递给reducertask。

 

于是:

1. 一切通讯由远程rpc调用实现。

2. hdfs是存储,可单独运行。

3. mapreduce是分布式计算,它使用hdfs。

4. task是节点计算的核心。

5. 大量的细节实现以保证可靠性和稳定性。

作者:u011539200 发表于2015/5/14 21:05:39 原文链接
阅读:349 评论:0 查看评论
 
[原]Hadoop 1.x的Shuffle源码分析之3

shuffle有两种,一种是在内存存储数据,另一种是在本地文件存储数据,两者几乎一致。

 

以本地文件进行shuffle的过程为例:

 

mapOutput = shuffleToDisk(mapOutputLoc, input, filename, 
              compressedLength)

shuffleToDisk函数如下:

 

 

private MapOutput shuffleToDisk(MapOutputLocation mapOutputLoc,
                                      InputStream input,
                                      Path filename,
                                      long mapOutputLength) 
      throws IOException {
        // Find out a suitable location for the output on local-filesystem
        //在本地文件系统做输出,输出文件的path
        Path localFilename = 
          lDirAlloc.getLocalPathForWrite(filename.toUri().getPath(), 
                                         mapOutputLength, conf);

        //创建Map输出
        MapOutput mapOutput = 
          new MapOutput(mapOutputLoc.getTaskId(), mapOutputLoc.getTaskAttemptId(), 
                        conf, localFileSys.makeQualified(localFilename), 
                        mapOutputLength);


        // Copy data to local-disk
        //从input读取数据,写入到本地文件,这个input是http连接创建的流式输入
        OutputStream output = null;
        long bytesRead = 0;
        try {
          output = rfs.create(localFilename);
          
          byte[] buf = new byte[64 * 1024];
          int n = -1;
          try {
            n = input.read(buf, 0, buf.length);
          } catch (IOException ioe) {
            readError = true;
            throw ioe;
          }
          while (n > 0) {
            bytesRead += n;
            shuffleClientMetrics.inputBytes(n);
            output.write(buf, 0, n);

            // indicate we're making progress
            reporter.progress();
            try {
              n = input.read(buf, 0, buf.length);
            } catch (IOException ioe) {
              readError = true;
              throw ioe;
            }
          }

          LOG.info("Read " + bytesRead + " bytes from map-output for " +
              mapOutputLoc.getTaskAttemptId());

          //正常取完数据,关闭。
          output.close();
          input.close();
        } catch (IOException ioe) {
          LOG.info("Failed to shuffle from " + mapOutputLoc.getTaskAttemptId(), 
                   ioe);

          // Discard the map-output
          try {
            mapOutput.discard();
          } catch (IOException ignored) {
            LOG.info("Failed to discard map-output from " + 
                mapOutputLoc.getTaskAttemptId(), ignored);
          }
          mapOutput = null;

          // Close the streams
          IOUtils.cleanup(LOG, input, output);

          // Re-throw
          throw ioe;
        }

        // Sanity check
        //检查读取是否正常
        if (bytesRead != mapOutputLength) {
          try {
            mapOutput.discard();
          } catch (Exception ioe) {
            // IGNORED because we are cleaning up
            LOG.info("Failed to discard map-output from " + 
                mapOutputLoc.getTaskAttemptId(), ioe);
          } catch (Throwable t) {
            String msg = getTaskID() + " : Failed in shuffle to disk :" 
                         + StringUtils.stringifyException(t);
            reportFatalError(getTaskID(), t, msg);
          }
          mapOutput = null;

          throw new IOException("Incomplete map output received for " +
                                mapOutputLoc.getTaskAttemptId() + " from " +
                                mapOutputLoc.getOutputLocation() + " (" + 
                                bytesRead + " instead of " + 
                                mapOutputLength + ")"
          );
        }

        return mapOutput;

      }

所以说,这一段shuffle的本质就是,从http的输入流读取数据,然后存放在本地文件系统的磁盘文件,写完之后,把taskId, jobid,本地文件名等等诸多参数放在MapOutput对象记录下来,然后返回一个MapOutput对象。

 

 

java的代码很直接,没有花花绕的东东,除了略有一点冗长,实在没什么缺点  :)

作者:u011539200 发表于2015/5/13 21:59:11 原文链接
阅读:296 评论:0 查看评论
 
[原]Hadoop 1.x的Shuffle源码分析之2

ReduceTask类的内嵌类ReduceCopier的内嵌类MapOutputCopier的函数copyOutput是Shuffle里最重要的一环,它以http的方式,从远程主机取数据:创建临时文件名,然后用http读数据,再保存到内存文件系统或者本地文件系统。它读取远程文件的函数是getMapOutput。

 

getMapOutput函数如下:

 

private MapOutput getMapOutput(MapOutputLocation mapOutputLoc, 
                                     Path filename, int reduce)
      throws IOException, InterruptedException {
        //建立http链接
        URL url = mapOutputLoc.getOutputLocation();
        HttpURLConnection connection = (HttpURLConnection)url.openConnection();
        //创建输入流
        InputStream input = setupSecureConnection(mapOutputLoc, connection);

        //检查连接姿势是否正确
        int rc = connection.getResponseCode();
        if (rc != HttpURLConnection.HTTP_OK) {
          throw new IOException(
              "Got invalid response code " + rc + " from " + url +
              ": " + connection.getResponseMessage());
        }
 
        //从http链接获取mapId
        TaskAttemptID mapId = null;
        try {
          mapId =
            TaskAttemptID.forName(connection.getHeaderField(FROM_MAP_TASK));
        } catch (IllegalArgumentException ia) {
          LOG.warn("Invalid map id ", ia);
          return null;
        }
</pre><pre code_snippet_id="665348" snippet_file_name="blog_20150513_3_7696491" name="code" class="java">        //检查mapId是否一致
        TaskAttemptID expectedMapId = mapOutputLoc.getTaskAttemptId();
        if (!mapId.equals(expectedMapId)) {
          LOG.warn("data from wrong map:" + mapId +
              " arrived to reduce task " + reduce +
              ", where as expected map output should be from " + expectedMapId);
          return null;
        }
        //如果数据有压缩,要获取压缩长度
        long decompressedLength = 
          Long.parseLong(connection.getHeaderField(RAW_MAP_OUTPUT_LENGTH));  
        long compressedLength = 
          Long.parseLong(connection.getHeaderField(MAP_OUTPUT_LENGTH));

        if (compressedLength < 0 || decompressedLength < 0) {
          LOG.warn(getName() + " invalid lengths in map output header: id: " +
              mapId + " compressed len: " + compressedLength +
              ", decompressed len: " + decompressedLength);
          return null;
        }
        int forReduce =
          (int)Integer.parseInt(connection.getHeaderField(FOR_REDUCE_TASK));
        
        if (forReduce != reduce) {
          LOG.warn("data for the wrong reduce: " + forReduce +
              " with compressed len: " + compressedLength +
              ", decompressed len: " + decompressedLength +
              " arrived to reduce task " + reduce);
          return null;
        }
        if (LOG.isDebugEnabled()) {
          LOG.debug("header: " + mapId + ", compressed len: " + compressedLength +
              ", decompressed len: " + decompressedLength);
        }

        //We will put a file in memory if it meets certain criteria:
        //1. The size of the (decompressed) file should be less than 25% of 
        //    the total inmem fs
        //2. There is space available in the inmem fs
        
        // Check if this map-output can be saved in-memory
        boolean shuffleInMemory = ramManager.canFitInMemory(decompressedLength); 

        // Shuffle
        MapOutput mapOutput = null;
        if (shuffleInMemory) {
          if (LOG.isDebugEnabled()) {
            LOG.debug("Shuffling " + decompressedLength + " bytes (" + 
                compressedLength + " raw bytes) " + 
                "into RAM from " + mapOutputLoc.getTaskAttemptId());
          }
          //在内存做shuffle处理
          mapOutput = shuffleInMemory(mapOutputLoc, connection, input,
                                      (int)decompressedLength,
                                      (int)compressedLength);
        } else {
          if (LOG.isDebugEnabled()) {
            LOG.debug("Shuffling " + decompressedLength + " bytes (" + 
                compressedLength + " raw bytes) " + 
                "into Local-FS from " + mapOutputLoc.getTaskAttemptId());
          }
          //在本地做shuffle处理
          mapOutput = shuffleToDisk(mapOutputLoc, input, filename, 
              compressedLength);
        }
        mapOutput.decompressedSize = decompressedLength;    
        return mapOutput;
      }

 

作者:u011539200 发表于2015/5/13 7:59:01 原文链接
阅读:406 评论:0 查看评论
 
[原]Hadoop 1.x的Shuffle源码分析之1

先参考董西成的博文  http://dongxicheng.org/mapreduce/hadoop-shuffle-phase/   

Hadoop中shuffle阶段流程分析

 

 

Hadoop的一个任务执行过程,分为Map和Reduce两个阶段。而shuffle发生在Reducer阶段。Hadoop 1.2.1里,Reduce类的源码在org.apache.hadoop.mapreduce包的Reducer.java文件,这里有一份详细的reduce过程的解释。shuffle是reduce的第一个阶段,以http的方式从map任务的输出获取数据。reduce的第二个阶段是sort,根据key对来reducer的输入进行排序,因为不同的map任务可能会产生相同key的输出。reducer的第三个阶段就是做最终处理,根据Reducer的reduce函数处理数据。

 

Reducer类只是提供了一个可Override的reduce函数,Shuffle实际上在ReduceTask类执行。ReduceTask类在org.apache.hadoop.mapred包里。

 

ReduceTask类比较复杂,有7个内嵌类,有些内嵌类里也有自己的内嵌类,毕其功于一役的做法。它的主要代码在run函数执行:

这是reduce的三个阶段:

 

if (isMapOrReduce()) {
      copyPhase = getProgress().addPhase("copy");
      sortPhase  = getProgress().addPhase("sort");
      reducePhase = getProgress().addPhase("reduce");
    }

shuffle阶段在这里

 

 

reduceCopier = new ReduceCopier(umbilical, job, reporter);
      if (!reduceCopier.fetchOutputs()) {
        if(reduceCopier.mergeThrowable instanceof FSError) {
          throw (FSError)reduceCopier.mergeThrowable;
        }
        throw new IOException("Task: " + getTaskID() + 
            " - The reduce copier failed", reduceCopier.mergeThrowable);
      }


 

fetchOutputs函数接近400行,比较长,略坑,在这个函数,

 

copiers = new ArrayList<MapOutputCopier>(numCopiers);

 

 

创建了一组MapOutputCopier,它是个线程类,负责取数据。

//start the on-disk-merge thread
      localFSMergerThread = new LocalFSMerger((LocalFileSystem)localFileSys);
      //start the in memory merger thread
      inMemFSMergeThread = new InMemFSMergeThread();
      localFSMergerThread.start();
      inMemFSMergeThread.start();
      
      // start the map events thread
      getMapEventsThread = new GetMapEventsThread();
      getMapEventsThread.start();

再创建几个线程,有的负责取map任务的信息,有的负责对结果做归并。

 

继续往下,是一个while循环,这个循环处理取数据,有一百多行代码,当数据取完或者达到失败上限就终止循环。

 

循环结束后,依次终止 获取map事件线程, 取数据线程,shuffl内存管理线程,排序线程,至此shuffle就结束了。

作者:u011539200 发表于2015/5/6 22:10:42 原文链接
阅读:145 评论:0 查看评论
 
[原]weka实战001:一篇博文简单了解weka

weka是java写的开源模式识别和数据挖掘软件,已经有十多年的历史了。weka的官网在http://www.cs.waikato.ac.nz/ml/weka/。

 

模式识别和数据挖掘有四个问题,

第一:问题是什么

第二:数据是什么

第三:如何学习

第四:学习结果可靠吗?

 

第一个问题来自需求。分析需求是很难的:严密的逻辑,深入了解行业宏观和细节,熟悉技术领域和学术领域的进展,有多个成功项目的实践经验,这四个因素缺一不可,所以通常由一个团队不同领域的精英合作完成。

 

weka不解决需求问题。

 

第二个问题是数据。每个样本对应一个weka的Instance,由多个样本组成的数据集对应weka的Instances,这是存储。对数据集,需要选择各种样本进行训练和测试,这里存在诸多的选择方法。比如,只选择部分样本进行训练和测试,处理属性缺失的样本,只选择部分属性进行训练和测试,如何对样本次序重排以改变训练和测试效果。如何以有监督或者无监督的方式选择样本及其属性。

 

第三个问题是学习。如果能精确定义第一个问题,那么第三个问题的答案也必然是清晰的。weka提供大量的算法,分类,回归,聚类,关联规则等等。对初学者而言,选择算法是个大问题,每种算法都各有好处,但又没有一种算法在大多数指标上好过其他算法。这里的诀窍就是大量的做实验并分析结果,做的多了自然就知道什么是好的。

 

第四个问题是验证学习器是否可靠。常用的方式就是交叉验证,五倍交叉或者十倍交叉。再配合网格调参。常规问题就可以解决了。

 

对大数据big data,weka的建议是,用命令行操作数据和训练,如果有可能,自己用groovy或者jython实现算法,或者使用可以增量学习的算法。它这么说的意思其实表明,weka目前还没有对big data做好准备,所以最好用它解决单机能搞定的问题。

 

 

作者:u011539200 发表于2015/5/3 11:39:35 原文链接
阅读:400 评论:0 查看评论
 
[原]HBase 二次开发 java api和demo
1. 试用thrift python/java以及hbase client api,结论如下:
    1.1 thrift的安装和发布繁琐,可能会遇到未知的错误,且hbase.thrift的版本在变化中。优点代码简单,需要打包的内容少。
    1.2 hbase client api,需要的jar很多,发布版的容量也很大,打包后近百兆。优点是,明确,无歧义。
 
2. 推荐用hbase client api的方式搞定。
 
3. 以下均为技术细节。
 
4. 有一台机器/一个集群,在运行hadoop,也运行了基于这个hadoop集群的hbase集群,同时,也运行了一个zookeeper集群,我们统称它是A。
 
5. 有一台集群负责开发,我们在上面写代码,编译代码,运行代码,我们称它是B。
 
6. 在B上,要修改/etc/hosts,把A的任意一台zookeeper服务器的hostname和对应的ip地址放进去,因为hbase client需要连接到zookeeper,以便获得hbase的hmast信息---hbase集群有多个hmast,一个是主hmast,其他是备用hmaster,如果主hmaster挂了,备用的会顶上,避免单点故障问题。
 
7. 在B上开发,在elipse建立一个java项目,添加一个lib目录,把A上的hadoop, hbase, zookeeper的所有jar包,注意,是所有jar包,各级子目录的也算在内,都复制到lib目录,大概有130个左右,90M。然后,再把它们添加到buildpath。这么做的好处是,不用一点点找究竟哪个类在哪个包,生命短暂,不要把时间浪费在这里,浪费点磁盘空间没关系。
    如果hadoop,hbase, zookeeper都安装在一个目录下,可以用一个shell语句搞定:
    for i in `find . -name "*.jar"`;      do cp $i ~/alljars;    done;
    然后再把alljars下的jar包都复制到B的lib目录。
 
8. 写一个最简单的hbase demo,在hbase里检查一个表是否存在,如果不存在,就创建它。
-----------------------------------------
package hbasedemo;

import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.TableName;

public class Main {

public static void main(String[] args) throws IOException{
Configuration hbase_conf = new Configuration();
hbase_conf.set("hbase.zookeeper.quorum", "brianxxxooo"); //brianxxxooo是A里的zookeeper机器的hostname
hbase_conf.set("hbase.zookeeper.property.clientPort","2181");
Configuration conf = HBaseConfiguration.create(hbase_conf);

String tablename="scores";
String[] familys = {"grade", "course"};

HBaseAdmin admin = new HBaseAdmin(conf);
if (admin.tableExists(tablename)){
System.out.println("table exist, return!");
return;
}

HTableDescriptor td = new HTableDescriptor(TableName.valueOf(tablename));
for(int i = 0; i < familys.length; i++){
td.addFamily(new HColumnDescriptor(familys[i]));
}
admin.createTable(td);
System.out.println("create table "+tablename+" ok.");

}
-----------------------------------------
 
9. 注意事项,hbase client的版本变化甚多,具体api调用要根据版本来,有时候需要参考多个版本来。比如,0.96.x的HTableDescripter更接近http://hbase.apache.org/apidocs/index.html  , 而不是0.94的api。但HBaseAdmin在0.94的api是有的,在2.0.0里没有。非常混乱。估计这个局面还要持续一段时间。
 
10. 更详细的例子
------------------------------------------
package hbasedemo;

import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.*;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;

public class Main {

public static void main(String[] args) throws IOException{
Configuration hbase_conf = new Configuration();
hbase_conf.set("hbase.zookeeper.quorum", "brianvxxxxooooo");
hbase_conf.set("hbase.zookeeper.property.clientPort","2181");
Configuration conf = HBaseConfiguration.create(hbase_conf);

String tablename="scores";
String[] familys = {"grade", "course"};

HBaseAdmin admin = new HBaseAdmin(conf);
if (admin.tableExists(tablename)){
System.out.println("table exist!");
}else{
HTableDescriptor td = new HTableDescriptor(TableName.valueOf(tablename));
for(int i = 0; i < familys.length; i++){
td.addFamily(new HColumnDescriptor(familys[i]));
}
admin.createTable(td);
System.out.println("create table "+tablename+" ok.");
}

HTable table = new HTable(conf, "scores");
Put put = new Put(Bytes.toBytes("row1"));

//create
put.add(Bytes.toBytes("grade"), Bytes.toBytes("g1"), Bytes.toBytes(781));
put.add(Bytes.toBytes("grade"), Bytes.toBytes("g2"), Bytes.toBytes("this is test"));
table.put(put);

//read
Get get = new Get(Bytes.toBytes("row1"));
get.addColumn(Bytes.toBytes("grade"), Bytes.toBytes("g1"));
Result result = table.get(get);
byte[] val = result.getValue(Bytes.toBytes("grade"), Bytes.toBytes("g1"));
System.out.println(Bytes.toInt(val));


}
------------------------------------------
 
其他各种操作于此相似,不再一一列出。
作者:u011539200 发表于2014/11/12 11:23:49 原文链接
阅读:858 评论:0 查看评论
 
[原]apache oozie安装试用
oozie是hadoop的工作流Scheduler,最新的版本到4.0.1了。试用了下,小坑还蛮多的。

1. 编译
我的主机上跑的是Hadoop 1.1.2,选的是oozie 3.3.0版本,下载源码,解压缩。
首先,要把源码里的javaversion从1.6改成1.7,主机是用jdk1.7。
编译oozie,命令是'./bin/mkdistro.sh -DskipTests -Dhadoop.version=1.0.1',跳过测试,另外,不管Hadoop的版本是多少,只要它是1.x的,就只能是-Dhaoop.version=1.0.1,填其他版本号编译不通过。如果是Hadoop 2.x,只能填'-Dhadoop.version=2.0.0-alpha',填其他版本号编译不通过。

2. 安装
编译结果在oozie-3.3.0/distro/target/oozie-3.3.0-distro/oozie-3.3.0,这个目录有
--------------------
bin lib oozie-core oozie-sharelib-3.3.0.tar.gz
conf libtools oozie-examples.tar.gz oozie.war
docs.zip oozie-client-3.3.0.tar.gz oozie-server release-log.txt
--------------------
把这些文件复制到安装目录/usr/local/lib/oozie-3.3.0,或者其他地方比如我的是~/usr/oozie-3.3.0。以后的操作都是安装目录进行。

3. 配置hadoop
在hadoop的core-site.xml里添加oozie的配置
--------------------
  <!-- OOZIE -->
  <property>
    <name>hadoop.proxyuser.[youname].hosts</name>
    <value>*</value>
  </property>
  <property>
    <name>hadoop.proxyuser.brian.groups</name>
    <value>*</value>
  </property>
--------------------
把[youname]替换成你的当前用户名。
然后启动Hadoop。

4. 官档提到要把oozie-3.3.0目录下hadooplibs tar.gz解压缩,但如果你的系统已经安装hadoop了,编译的时候不会生成这个文件,这一步可以忽略。

5. 创建oozie-3.3.0/libext目录。下载http://extjs.com/deploy/ext-2.2.zip放到这个目录,不需要解压,再把主机上hadoop-1.1.2的几个主jar文件复制到这个目录,也就是:
---------------------------
hadoop-client-1.1.2.jar hadoop-examples-1.1.2.jar hadoop-test-1.1.2.jar
hadoop-ant-1.1.2.jar hadoop-core-1.1.2.jar hadoop-minicluster-1.1.2.jar hadoop-tools-1.1.2.jar
---------------------------

6. 在oozie-3.3.0目录下,运行'./bin/oozie-setup.sh',执行安装步骤。

7. 在oozie-3.3.0目录下,运行'./bin/ooziedb.sh create -sqlfile oozie.sql -run',创建数据库。

8. 在ooize-3.3.0目录下,运行'./bin/oozie-start.sh',启动oozie。

9. 在ooize-3.3.0目录下,运行'./bin/oozie admin -oozie http://localhost:11000/oozie -status',检查oozie是否启动成功,正常情况下,输出值是normal。

10.在ooize-3.3.0目录下,解压缩oozie-sharelib-3.3.0.tar.gz,得到一个目录share,用'hadoop fs -put share share'将它放到hdfs上。

11.在ooize-3.3.0目录下,解压缩oozie-examples.tar.gz,得到目录examples。修改examples/apps/mao-reduce/jobproperties文件的前两句,替换成:
------------
nameNode=hdfs://localhost:9000
jobTracker=localhost:9001
------------
这是下一步要运行的demo,修改namenode和jobtracker的配置,默认值不对。
然后将examples目录也put到hdfs上。

12. 在oozie-3.3.0目录下,运行'./bin/oozie job -oozie http://localhost:11000/oozie -config examples/apps/map-reduce/job.properties -run',注意端口号是11000,官档是8080,参数不对。执行后,输出hadoop jod id,形如‘0000000-140826104216537-oozie-cke-W’

13. 根据job id检查运行结果'./bin/oozie job -oozie http://localhost:11000/oozie -info 0000000-140826104216537-oozie-cke-W',看到success即表明成功。

14. 显示运行结果:'hadoop fs -cat examples/output-data/map-reduce/part-00000'

流程是完整的,步骤是最简的,解释是忽略的 :),每一步的具体解释请参考官档。
作者:u011539200 发表于2014/8/26 16:07:05 原文链接
阅读:1087 评论:0 查看评论
 
[原]一个网站的诞生10--自动化部署

所谓自动部署就是说,如果用10台机器跑tornado程序提供Web服务,它们上面的代码都是一样的,这也叫生产环境。在公司写新代码,写好了,测试通过,这叫开发环境。然后执行自动部署程序,它把新代码提交到版本管理服务器,然后连上生产环境的10台服务器,让它们更新代码,再重启tornado程序,新代码就上线了,用户看到的就是新发布的网站。

自动部署的关键是两个东西,一个是版本服务器,一个是远程操作。

推荐用git版本服务器,推荐寥雪峰的git教程,写得非常清晰,是目前为止我见到的最容易入手的,链接在这里http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000

推荐用python fabric远程操作,http://www.fabfile.org/

做自动化部署,第一步是创建一个公网IP的git版本服务器,这样10台web server能访问git服务器。然后在开发环境把代码提交到git版本服务器。这个过程请参考寥同学的教程,这里就不重复了。

第二步,ssh登录到任意一台web server,手动执行命令,从git服务器更新代码,然后把tornado程序开起来,记下这个过程的所有步骤。

第三部,用fabric把第二步的步骤写一遍,填上10台web server的ip地址,将来就可以一次性更新10台机器了。

zuijiacanting.com的自动化部署脚本类似如下,remote_deploy.py:

#!/usr/bin/env python
#! -*- coding:utf-8 -*-

from __future__ import with_statement
import os
from fabric.api import *

#ip of web server
env.hosts=['106.111.111.111']

def commit_to_remote():
    d = os.path.abspath("xx/yy")
    local("cd %s;git push origin master" % d)

def deploy_zjct():
    code_dir="/home/xxx/yyy"
    with cd(code_dir):
        run("sudo git pull origin master")
        run("sudo reboot")
在开发环境下,如果新代码已经准备好了,执行命令"fab -f remote_deploy.py commit_to_remote",把新代码提交到git版本服务器,然后再执行"fab -f remote_deploy.py deploy_zjct",fabric以ssh的方式登录到web server,更新代码,然后重启。重启运行新代码。不重启也可以,kill掉tornado进程,自动监控会重启tornado进程,重启后也是运行新代码。如果要更新多台机器,在env.hosts把ip地址加进去就可以了。

fabric能做的事情非常多,不仅仅是上面这么简单,可以实现各种酷炫的特效。

作者:u011539200 发表于2014/8/20 13:02:28 原文链接
阅读:850 评论:0 查看评论
 
 
posted @ 2015-12-03 01:06  吃饭了吗  阅读(363)  评论(0编辑  收藏  举报