MapReduce之CWBTIAB (简单推荐系统)

MapReduce之CWBTIAB

背景

在大多数电商网站中使用 “ 购买过该商品的顾客还购买过哪些商品 “ 即(CWBTIAB)特性来推荐图书或其他商品

原理

通过统计交易集中购买过该商品的所有顾客所购买过的所有商品,对商品购买次数进行统计,进而得出购买过该商品的顾客可能会购买哪些商品

利用这个原理,可以制作一个简单的推荐系统,如:猜你喜欢

设计方案

利用MapReduce的两次迭代实现CWBTIAB功能

  • 阶段1:生成同一个用户购买的所有商品列表
  • 阶段2:解决列表商品的共现问题,使用stripes(条纹)设计模式,统计处5个最常见的共现商品
Stripes设计模式

该模式的设计思想主要是将键-值对分组为一个关联数组,这里给出一个映射器发出的键-值对


(k, k 1 k_1 k1)3
(k, k 2 k_2 k2)2
(k, k 3 k_3 k3)4
(k, k 4 k_4 k4)6
(z, z 1 z_1 z1)7
(z, z 2 z_2 z2)8
(z, z 3 z_3 z3)5

Stripes方法的基本思想则不是发出很多键-值对,只对每个条纹发出一个键-值对,如下


k{( k 1 k_1 k1,3),( k 2 k_2 k2,2),( k 3 k_3 k3,4),( k 4 k_4 k4,6)}
z{( z 1 z_1 z1,7),( z 2 z_2 z2,8),( z 3 z_3 z3,5)}

Stripes方法为每个自然键创建一个关联数组,并归约各个映射器发出的键-值对数,各个映射器发出的值转换为一个复杂的对象时,采用Stripes方法可以减少键-值对的排序和洗牌
Stripes方法中归约器的工作情况如下:归约器会对关联数组完成一个元素级的求和,如下:
K -> { ( a , 1 ) , ( b , 2 ) , ( c , 4 ) , ( d , 3 ) }
K -> { ( a , 2 ) , ( c , 2 ) }
K -> { ( a , 3 ) , ( b , 5 ) , ( d , 5 ) }
将生成以下输出:
K -> { ( a , 1+2+3) , ( b , 2+5 ) , ( c , 4+2 ) , ( d , 3+5 ) }
或:
K -> { ( a , 6) , ( b , 7 ) , ( c , 6 ) , ( d , 8 ) }
Stripes方法的优点如下:

  • 与传统方法相比,由于映射器生成的键-值对更少,所以需要的排序和洗牌也更少
  • 采用Stripes方法,可以充分利用组合器(完成个结点本地优化)
    Stripes方法的缺点如下:
  • 较难实现(因为各映射器发出的值是一个关联数组,必须为这个关联数组写一个串行化器和逆串行化器
  • 底层对象是更重量级的对象
  • Stripes在事件空间大小方面存在一个基本限制(需确保映射器有足够的RAM来保存关联数组)

样例输入

假设输入是一个大的交易集(包括顾客ID,交易时间,价格,商品ID)

样例输入程序设计如下
package com.deng.CWBTIAB;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Random;

public class create {
    public static void main(String[] args) throws IOException {
        String path="input/CWBTIAB.txt";
        File file=new File(path);
        if(!file.exists()){
            file.getParentFile().mkdirs();
        }
        file.createNewFile();
        FileWriter fw=new FileWriter(file,true);
        BufferedWriter bw=new BufferedWriter(fw);
//生成顾客id,交易id,时间,价格,商品id等
        for(int i=0;i<10000;i++){
            int id=(int)(Math.random()*400+1000);
            int time=(int)(Math.random()*20+2000);
            bw.write("UserId="+id+" dealId="+id+time+" dealTime="+time+" price="+(int)(Math.random()*1000)+" goodId="+(int)(Math.random()*20)+"\n");
        }
        bw.flush();
        bw.close();
        fw.close();
    }
}

结果如下
运行结果

MapReduce阶段1

mapper阶段思路

mapper阶段根据用户ID统计用户购买的商品,发送<userId,goodID>的键-值对

mapper阶段编码
package com.deng.CWBTIAB;

import java.io.IOException;
import java.util.Map;

import com.deng.MRDPUtil;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.mapreduce.Mapper;

public class findUserGoodsMapper extends Mapper<LongWritable,Text,Text,Text>{
    public void map(LongWritable key,Text value,Context context) throws IOException,InterruptedException{
        String line=value.toString();
        //MRDPUtil为工具类,后面给出
        Map<String,String> parsed= MRDPUtil.transInformation(line);
        String userId=parsed.get("UserId");
        String good=parsed.get("goodId");
        context.write(new Text(userId),new Text(good));
    }
}

MRDPUtil类如下

通过使用等号切割字符,生成<属性,值>的键-值对,直接通过商品属性查找值

package com.deng;

import java.util.HashMap;
import java.util.Map;

public class MRDPUtil {
    public static Map<String,String> transInformation(String s){
        Map<String,String> mp=new HashMap<String, String>();
        String[] tokens=s.split(" ");
        for(int i=0;i<tokens.length;i++){
            String key=tokens[i].substring(0,getlocation(tokens[i]));
            String val=tokens[i].substring(getlocation(tokens[i])+1);
            mp.put(key,val);
        }
        return mp;
    }
     public static int getlocation(String s){
        int location;
        for( location=0;location<s.length();location++){
            if(s.charAt(location)=='='){
                break;
            }
        }
        return location;
    }
}

reducer阶段任务

对用户的所有商品进行分组

reducer阶段编码
package com.deng.CWBTIAB;

import java.io.IOException;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

public class findUserGoodsReducer extends Reducer<Text,Text,Text,Text>{
    public void reduce(Text key, Iterable<Text> values,Context context) throws IOException,InterruptedException{
        StringBuilder goods=new StringBuilder();
        for(Text value:values){
            goods.append(value);
            goods.append("-");
        }
        //将所有的货物ID存储到一起,使用 ’ - ‘作为分割
        context.write(new Text("UserId="+key),new Text(" goods="+goods.toString()));
    }
}

Mapreduce阶段2

MapReduce阶段使用Stripes方法解决商品共现问题

mapper阶段任务

利用MapWritable统计每个商品出现的次数,发出<goodId,map>的键-值对

mapper阶段编码
package com.deng.CWBTIAB;

import com.deng.MRDPUtil;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.io.*;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;;

public class stripesMapper extends Mapper<LongWritable,Text,Text,MapWritable>{
    public IntWritable cnt=new IntWritable();
    public IntWritable ZERO=new IntWritable(0);
    public Text tag;

    public void map(LongWritable key,Text value,Context context) throws IOException,InterruptedException{
        String line=value.toString();
        Map<String,String> parsed= MRDPUtil.transInformation(line);
        String[] goods=parsed.get("goods").split("-");
        Map<String,Integer> count=new HashMap<String, Integer>();
        for(String good:goods){
            if(count.get(good)==null){
                count.put(good,1);
                MapWritable mp=new MapWritable();
                for(String item:goods){
                    tag=new Text(item);
                    if(mp.get(tag)==null){
                        cnt.set(0);
                    }else{
                        cnt=(IntWritable) mp.get(tag);
                    }
                    cnt.set(cnt.get()+1);
                    mp.put(tag,cnt);
                }
                context.write(new Text("good="+good),new MapWritable(mp));
            }else {
                continue;
            }
        }
    }
}


reducer阶段任务

对于交易集中的每一件商品,生成与这个商品最常购买的前五个商品

reducer阶段编码
package com.deng.CWBTIAB;

import java.io.IOException;
import java.util.Collections;
import java.util.TreeMap;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.MapWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.Writable;
import org.apache.hadoop.mapreduce.Reducer;

public class stripesReducer extends Reducer<Text, MapWritable,Text,Text>{
    public String element_key;
    public IntWritable cnt=new IntWritable();
    public void reduce(Text key,Iterable<MapWritable> values,Context context) throws IOException,InterruptedException{
    //使用TreeMap函数统计最受欢迎的前五个商品
        TreeMap<String,Integer> counts=new TreeMap<String, Integer>(Collections.<String>reverseOrder());
        for(MapWritable value:values){
            if(!value.isEmpty()){
                for(Writable elememt:value.keySet()){
                    cnt=((IntWritable) value.get(elememt));
                    element_key=elememt.toString();
                    //防止出现空值
                    if(counts.get(element_key)==null) counts.put(element_key,0);
                    counts.put(element_key,counts.get(element_key)+cnt.get());
                    if(counts.size()>5){
                        counts.remove(counts.firstKey());
                    }
                }
            }
        }
        StringBuffer goods=new StringBuffer();
        for(String s:counts.keySet()){
            goods.append(s);
            goods.append(' ');
        }
        context.write(key,new Text("Goods is : "+goods));
    }
}

驱动程序如下

使用作业链的方式完成

package com.deng.CWBTIAB;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.MapWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.MultipleOutputs;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;

public class CWBTIABDriver {
    public static void main(String[] args) throws Exception{
        FileUtil.deleteDirs("output");
        FileUtil.deleteDirs("output2");
        Configuration conf=new Configuration();
        String[] otherArgs=new String[]{"input/CWBTIAB.txt","output","output2"};
        Job userGoodsJob=new Job(conf,"findUserGoods");
        userGoodsJob.setJarByClass(CWBTIABDriver.class);
        userGoodsJob.setMapperClass(findUserGoodsMapper.class);
        userGoodsJob.setReducerClass(findUserGoodsReducer.class);
        userGoodsJob.setOutputKeyClass(Text.class);
        userGoodsJob.setOutputValueClass(Text.class);
        FileInputFormat.addInputPath(userGoodsJob,new Path(otherArgs[0]));
        FileOutputFormat.setOutputPath(userGoodsJob,new Path(otherArgs[1]));
        int code=userGoodsJob.waitForCompletion(true)?0:1;
        if(code==0){
            Job stripesJob=new Job(conf,"stripes");
            stripesJob.setJarByClass(CWBTIABDriver.class);
            stripesJob.setMapperClass(stripesMapper.class);
            stripesJob.setReducerClass(stripesReducer.class);
            stripesJob.setOutputKeyClass(Text.class);
            stripesJob.setOutputValueClass(MapWritable.class);
            FileInputFormat.addInputPath(stripesJob,new Path("output/part-r-00000"));
            FileOutputFormat.setOutputPath(stripesJob,new Path(otherArgs[2]));
            System.exit(stripesJob.waitForCompletion(true)?0:1);
        }
    }
}

运行结果如下:
在这里插入图片描述
应该是由于随机生成数的问题,各个数字出现的概论是均等的,所以才会产生如下结果

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值