Streaming+Python实现Itembased CF

1 数据描述

空白 sku1 sku2 skuN
session1 011
session2 101
sessionM 1100

2 向量相似度计算

下面给出计算向量 x,y 的相似度公式, x,y 的长度都为 N


  • 夹角余弦

Nk=1 xk×ykNk=1 x2kNk=1 y2k

  • 皮尔逊相似度

    (xx¯)×(yy¯)( xx¯)2( yy¯)2

  • 欧式距离

    k=1N(xkyk)2

  • 杰卡相似度

    |xy||x|+|y||xy|

  • 曼哈顿相似度
    k=1N |xkyk|
  • Log-likelihood ratio
    下面介绍 x y之间的loglikelihood ratio计算方式

  • x x没发生
    y
    k11
    k12
    y 没发生
    k21
    k22

    k11 x y共现的次数
    k12 y 发生,x未发生的次数
    k21 x 发生,y未发生的次数
    k21 x y都不发生的次数

    N=k11+k12+k21+k22

    其中rowEntropy,columnEntropy,matrixEntropy的计算公式如下
    loglikelihoodratio=2(rowEntropy+columnEntropymatrixEntropy)

    rowEntropy=k11+k12N×log2(k11+k12N)k21+k22N×log2(k21+k22N)

    columnEntropy=k11+k21N×log2(k11+k21N)k12+k22N×log2(k12+k22N)

    matrixEntropy=k11N×log2(k11N)k12N×log2(k12>N)k21N×log2(k21N)k22N×log2(k22N)

    3 普适化的相似度计算

    在MapReduce模式下计算商品(向量)间的相似度主要参考如下文章 **《Scalable Similarity-Based Neighborhood Methods with MapReduce》**

    3.1 三步标准化流程向量相似度

    • 在计算任意向量 x,y 的相似度时,只需要如下三步计算

      • a) 首先将 x,y 通过前处理计算函数得到 x¯,y¯

        x^=preprocess(x),y^=preprocess(y)

      • b) 将经过第一步处理后的向量 x¯,y¯ 输入到 norm() 函数中计算范数

        ni=norm(x¯),nj=norm(y¯)

      • c) 在a),b)的基础上计算出向量的相似度

        Sij=similarity(dotij,ni,nj),dotij=x^y^

    • 算子说明

      • bin(x) :对向量 x 中的值进行二值化
      • |x|1表示对向量 x 求1的范数
    • 三个步骤计算相似度的示例
      假设x,y是如下三个元素的向量

      x=103,y=215

      • a) 前处理

        x^=preprocess(x)=bin(x)=101

      • b) 计算范数

        ni=norm(x¯)=|x^|1=2,nj=norm(y¯)=|y^|1=3

      • c) 计算杰卡相似度

        jacc(i,j)=dotijni+njdotij=22+32=23

    3.2 常用相似度计算算子总结

    在3.1中介绍了如何用三步标准步骤计算向量间的相似度,对于不同的相似度计算方法,这三个步骤中所涉及到的计算算子是不相同的,下表总结了常用相似度的计算算子

    preprocess
    norm
    similarity
    余弦相似度
    vv2
    -
    dotij
    皮尔逊相似度
    vv¯vv¯2
    -
    dotij
    欧式距离-
    v^2
    ni2×dotij+nj
    杰卡距离
    bin(v)
    v^1
    dotijni+njdotij
    曼哈顿距离
    bin(v)
    v^1
    ni+nj2×dotij
    Log-likelihood ratio
    bin(v)
    v^1
    2×(H(dotij,nidotij,njdotij,|U|ninj+dotij)H(nj,|U|nj)H(ni,|U|ni))

    表1 标准化的计算算子

    4 MapReduce+Python实现Itembased CF

    经过前面的介绍,如果现在想计算1中介绍的数据格式中的商品相似度,只需要三个MapReduce任务就可以完成,下面分别介绍这两个MR任务的实现,由于Hadoop提供的Streaming接口能够通过标准输入、输出流和其它程序进行数据交互,只要算的上一种语言的基本都支持标准输入、输出流吧,这使其它语言也可以很方便的编写MR程序,具体的就不bb了,上干货。

    • 第一个MR程序,对应于 preprocess 方法,主要可以分为分为四种实现方式,
      • 分别对应于计算向量(sku向量)的2范数
      • 对向量进行归一化(中心化)
      • 计算向量(sku向量)的平均值
      • 对向量(sku向量)进行二值化。
        对于每种计算方法需要的计算算子见下表
    1
    2
    余弦相似度计算向量2的范数(Mapper:cfCosiPreMapper_1.py,Reducer:cfCosiPreReducer_1.py)数据归一化(Mapper:cfCosiPreMapper_2.py)
    皮尔逊相似度计算向量的和,平方和,2的范数(Mapper:cfPearPreMapper_1.py,Reduce:cfPearPreReducer_1.py)-
    杰卡距离、曼哈顿距离、Log-likelihood ratio对向量二值化(Mapper:cfPreMapper.py)-
    欧式距离--

    表2 各种相似度计算中所需要的计算算子

    • 前处理示意图

      • 余弦相似度的前处理1示意图
        这里写图片描述

        图1 余弦相似度的前处理1示意图

      • 余弦相似度的前处理2示意图
        这里写图片描述

        图2 余弦相似度的前处理2示意图

        这里写图片描述
        图3 皮尔逊相似度前处理1示意图

        这里写图片描述
        图4 杰卡距离、曼哈顿距离、Log-likelihood ratio前处理1示意图

    • 第二个MR程序,对应于 preprocess 方法,主要可以分为分为两种实现方式,

      • 在前处理数据基础上计算向量长度
      • 在前处理数据基础上计算向量的1范数
        对于每种计算方法需要的计算算子见下表
    norm
    欧式距离计算向量长度的平方(Mapper:cfNormMapper.py,Reducer:cfNormReducer.py)
    杰卡距离、曼哈顿距离、Log-likelihood ratio向量1的范数(Mapper:cfNormMapper.py,Reducer:cfNormReducer.py)
    余弦距离,皮尔逊相似度-

    表3 各种相似度计算中所需要的norm计算算子

    • 第三个MR程序,在第二个MR基础上,按照表1中的similarity算子计算各种不同的相似度

    5 代码解析

    代码分成两部分:

    • cfItemSim.sh是可执行脚本,其中封装了全部计算逻辑
    • 剩下的其它Python脚本都是MR任务中用到的Mpper和Reducer代码

    下面按照前处理,计算,后处理对cfItemSim.sh中的逻辑进行介绍

    • 前处理
      为了节省存储空间,在计算每个向量(sku向量)长度时需要采用数组额存储方式,所以在进行itembased cf计算之前需要将原始数据中的sku号编码成连续的id,这部分代码如下
      • 在${IN_TABLE}_id表中存储的数据供itembased cf方法使用
    #=======================================================#
    #===================数据预处理方法======================#
    #=======================================================#
    if [ ${pre} = 'TRUE' ]
    then
    echo "Preprocess the data......"
    #计算uuid1d对应的id
    hive -e "
    set mapreduce.job.reduce.slowstart.completedmaps=0.9;
    drop table ${IN_TABLE}_pin_id;
    create table ${IN_TABLE}_pin_id
    as
    select cc.uuid1d,cc.id-1 id from
    (select bb.uuid1d,row_number(bb.num) as id from 
    (select distinct 1 as num, uuid1d from ${IN_TABLE} order by num) bb) cc
    order by id
    "
    #计算sku对应的id
    hive -e "
    set mapreduce.job.reduce.slowstart.completedmaps=0.9;
    drop table ${IN_TABLE}_sku_id;
    create table ${IN_TABLE}_sku_id
    as
    select cc.sku,cc.id-1 id from
    (select bb.sku,row_number(bb.num) as id from 
    (select distinct 1 as num, sku from ${IN_TABLE} order by num) bb) cc
    order by id
    "
    
    #关联出uuid1d对应的数据
    hive -e "
    set mapreduce.job.reduce.slowstart.completedmaps=0.9;
    drop table ${IN_TABLE}_id;
    create table ${IN_TABLE}_id
    as
    select concat_ws('#',collect_set(sku)) from
    (select b.id,concat_ws('_',cast(bb.id as string),cast(a.weight as string)) sku from
    ${IN_TABLE} a
    join
    ${IN_TABLE}_pin_id b on (a.uuid1d=b.uuid1d)
    join
    ${IN_TABLE}_sku_id bb on (a.sku=bb.sku)) c
    group by c.id
    "
    fi
    • itembased cf计算部分
      在前处理之后就可以计算itembased cf了,根据不同的方法程序中会进行不同的计算,代码如下
      • 下面只列出了部分相似度计算代码,详见代码
    #在itembased cf计算之前一些数据准备工作
    N=`hive -e "select max(id)+1 from ${IN_TABLE}_pin_id"`
    lib=`echo ${IN_TABLE}_id | awk  'BEGIN{FS="."} {print $1}'`
    tab=`echo ${IN_TABLE}_id | awk  'BEGIN{FS="."} {print $2}'`
    input=/user/recsys/${lib}.db/${tab}
    output=${tmp}/${method}_output
    #=======================================================#
    #===============itemSimilarity计算方法==================#
    #=======================================================#
    echo "Calculate the item to item similarity using cf......"  
    #Log-likelihood ratio
    if [ $method = 'logl' ]
    then
        #预处理
        hadoop fs -rm -r ${tmp}/pre1
        cd /data0/recsys/zhongchao/recsys_work/cf;
        hadoop jar $HADOOP_HOME/share/hadoop/tools/lib/hadoop-streaming-2.2.0.jar \
        -Dmapreduce.job.reduce.slowstart.completedmaps=0.9 \
        -Dstream.non.zero.exit.is.failure=false \
        -Dmapred.reduce.tasks=0 \
        -Dmapred.map.child.java.opts=-Xmx2048m \
        -Dmapred.reduce.child.java.opts=-Xmx2048m \
        -input ${input} \
        -output ${tmp}/pre1 \
        -mapper "python ./cfPreMapper.py" \
        -file "cfPreMapper.py"
        #计算norm
        hadoop fs -rm -r ${tmp}/pre2
        cd /data0/recsys/zhongchao/recsys_work/cf;
        hadoop jar $HADOOP_HOME/share/hadoop/tools/lib/hadoop-streaming-2.2.0.jar \
        -Dmapreduce.job.reduce.slowstart.completedmaps=0.9 \
        -Dstream.non.zero.exit.is.failure=false \
        -Dmapred.reduce.tasks=20 \
        -Dmapred.map.child.java.opts=-Xmx2048m \
        -Dmapred.reduce.child.java.opts=-Xmx2048m \
        -input ${tmp}/pre1 \
        -output ${tmp}/pre2 \
        -mapper "python ./cfNormMapper.py" \
        -file "cfNormMapper.py" \
        -reducer "python ./cfNormReducer.py ${method}" \
        -file "cfNormReducer.py"
    
    
        norm_file='norm_'`echo $RANDOM`
        norm_sort_file='norm_sort'`echo $RANDOM`
        # echo $tmp_file1
        echo $norm_file
        echo $norm_sort_file
        hadoop fs -getmerge ${tmp}/pre2 ${norm_file}
        cat ${norm_file} | sort -k1 -n > ${norm_sort_file}
    
    
        #cat hdfs_data.txt | python ./cfNormMapper.py | sort -k1 -n | python ./cfNormReducer.py > norm_sort.txt  
        #计算相似度
    
    
        hadoop fs -rm -r ${output};
        cd /data0/recsys/zhongchao/recsys_work/cf;
        hadoop jar $HADOOP_HOME/share/hadoop/tools/lib/hadoop-streaming-2.2.0.jar \
        -Dmapreduce.job.reduce.slowstart.completedmaps=0.9 \
        -Dstream.non.zero.exit.is.failure=false \
        -Dmapred.reduce.tasks=80 \
        -Dmapred.map.child.java.opts=-Xmx2048m \
        -Dmapred.reduce.child.java.opts=-Xmx2048m \
        -input ${tmp}/pre1 \
        -output ${output} \
        -mapper "python ./cfSimMapper.py" \
        -file "cfSimMapper.py" \
        -reducer "python ./cfSimReducer.py ${method} $N ${norm_sort_file}" \
        -file "cfSimReducer.py" \
        -file ${norm_sort_file}
    
        [ $? -ne 0 ] && { echo "cfSimReducer 错误";exit 2;}
    
        \rm -r ${norm_file}
        \rm -r ${norm_sort_file}
        #cat pre1 | python ./cfSimMapper.py | sort -k1 -n | python ./cfSimReducer.py logl 12834737 norm_sort28075 | sort -k12 -n > logl.txt   
    fi
    
    #Jaccard coeffcient
    if [ $method = 'jacc' ]
    then
        #预处理
        hadoop fs -rm -r ${tmp}/pre1
        cd /data0/recsys/zhongchao/recsys_work/cf;
        hadoop jar $HADOOP_HOME/share/hadoop/tools/lib/hadoop-streaming-2.2.0.jar \
        -Dmapreduce.job.reduce.slowstart.completedmaps=0.9 \
        -Dstream.non.zero.exit.is.failure=false \
        -Dmapred.reduce.tasks=0 \
        -Dmapred.map.child.java.opts=-Xmx2048m \
        -Dmapred.reduce.child.java.opts=-Xmx2048m \
        -input ${input} \
        -output ${tmp}/pre1 \
        -mapper "python ./cfPreMapper.py" \
        -file "cfPreMapper.py"
        #计算norm
        hadoop fs -rm -r ${tmp}/pre2
        cd /data0/recsys/zhongchao/recsys_work/cf;
        hadoop jar $HADOOP_HOME/share/hadoop/tools/lib/hadoop-streaming-2.2.0.jar \
        -Dmapreduce.job.reduce.slowstart.completedmaps=0.9 \
        -Dstream.non.zero.exit.is.failure=false \
        -Dmapred.reduce.tasks=20 \
        -Dmapred.map.child.java.opts=-Xmx2048m \
        -Dmapred.reduce.child.java.opts=-Xmx2048m \
        -input  ${tmp}/pre1\
        -output ${tmp}/pre2 \
        -mapper "python ./cfNormMapper.py" \
        -file "cfNormMapper.py" \
        -reducer "python ./cfNormReducer.py ${method}" \
        -file "cfNormReducer.py"
    
        #cat hdfs_data.txt | python ./cfNormMapper.py | sort -k1 -n | python ./cfNormReducer.py > norm_sort.txt  
        #计算相似度
        norm_file='norm_'`echo $RANDOM`
        norm_sort_file='norm_sort'`echo $RANDOM`
        # echo $tmp_file1
        echo $norm_file
        echo $norm_sort_file
        hadoop fs -getmerge ${tmp}/pre2 ${norm_file}
        cat ${norm_file} | sort -k1 -n > ${norm_sort_file}
    
        hadoop fs -rm -r ${output};
        cd /data0/recsys/zhongchao/recsys_work/cf;
        hadoop jar $HADOOP_HOME/share/hadoop/tools/lib/hadoop-streaming-2.2.0.jar \
        -Dmapreduce.job.reduce.slowstart.completedmaps=0.9 \
        -Dstream.non.zero.exit.is.failure=false \
        -Dmapred.reduce.tasks=80 \
        -Dmapred.map.child.java.opts=-Xmx2048m \
        -Dmapred.reduce.child.java.opts=-Xmx2048m \
        -input ${tmp}/pre1 \
        -output ${output} \
        -mapper "python ./cfSimMapper.py" \
        -file "cfSimMapper.py" \
        -reducer "python ./cfSimReducer.py ${method} $N ${norm_sort_file}" \
        -file "cfSimReducer.py" \
        -file ${norm_sort_file}
    
        \rm -r ${norm_file}
        \rm -r ${norm_sort_file}
        #cat pre1 | python ./cfSimMapper.py | sort -k1 -n | python ./cfSimReducer.py jacc 100 norm_sort3284 | sort -k12 -n > text.txt   
    fi
    • 后处理
      早后处理部分就比较简单了,将itembased cf的计算结果还原成正常的sku号,得到召回模型,代码如下
    #=======================================================#
    #==============后处理,取topK,生成hive表===============#
    #=======================================================#
    hadoop fs -rm -r ${output}/_SUCCESS
    #建立外部临时表
    s="
    drop table ${OUT_TABLE}_t1;
    CREATE EXTERNAL TABLE ${OUT_TABLE}_t1(id1 string,id2 string,weight double)
    ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
    STORED AS TEXTFILE
    LOCATION '${output}'"
    hive -e "$s"
    [ $? -ne 0 ] && { echo "$s EXIT";exit 2;}
    #取临时表中的topK数据
    s="
    drop table ${OUT_TABLE}_t2;
    create table ${OUT_TABLE}_t2
    as
    select id1,id2,weight from
    (select id1,id2,weight,row_number(id1) id from
    (select id1,id2,weight from ${OUT_TABLE}_t1 distribute by id1 sort by id1,weight desc) a) b
    where b.id<${K}
    "
    #关联出最终结果
    hive -e "$s"
    [ $? -ne 0 ] && { echo "$s EXIT";exit 2;}
    
    s="
    drop table ${OUT_TABLE};
    create table ${OUT_TABLE}
    as
    select b.sku sku1,c.sku sku2,a.weight from
    ${OUT_TABLE}_t2 a
    join
    ${IN_TABLE}_sku_id b on (a.id1=b.id)
    join
    ${IN_TABLE}_sku_id c on (a.id2=c.id)
    "
    hive -e "$s"
    [ $? -ne 0 ] && { echo "$s EXIT";exit 2;}

    6 其它注意事项

    • 注意由-i参数控制的输入表的表结构必须为sessionid,sku,weight,否则程序会报错,因为程序中会用这几个字段从某些表中取数,改进的方法是将字段也作为参数从命令行输入或通过配置文件输入
    • 在计算余弦相似度和皮尔逊相似度的前处理程序可以进一步改进,随机现在的计算方式结果没有问题,实际上按照表1的计算方式会更加简单,不知道当时怎么想的没按照表1中的算子实现,以后更新。

    参考文献:《Scalable Similarity-Based Neighborhood Methods with MapReduce》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值