全文检索、数据挖掘、推荐引擎系列7---条目相似度算法
2011-08-29 17:11 java ee spring 阅读(473) 评论(0) 编辑 收藏 举报在实际的项目中,有许多场合需要进行条目相似度计算,比如在电商系统中,经常有喜欢这个商品的用户还喜欢,通常计算商品的相似度是实现这种功能的方法之一,这可以视为一种基于内容的推荐系统的应用。同时,计算相似度不仅可以用于推荐商品,利用同样的算法,我们还可以计算出用户的相似度,可以向用户推荐其感兴趣的其他用户。与文本分析不同,对相似度的计算一般基于与用户的交互数据,如用户对商品进行投票、打分、浏览、购买等行为,经过适当的流程,将这些交互数据进行数字化,如浏览、购买、投票与否用0/1表示,对打分用实际的分数计算。
这类算法与文本分析算法相比具有两个明显的优势:第一是文本分析算法需要处理英文和中文问题,并且不同语言的处理方法会相当不同,如中文分词就比英文分词复杂很多,但是采用相似度计算方法,就不存在不同语言处理问题;第二是相似度计算以用户与系统间交互数据为依据,这样可以更好的反映某些热点条目,从而使推荐结果更具有时效性。
当然,利用计算相似度来进行推荐,由于是基于数据挖掘技术,难免会受原始数据中噪音的影响,而且由于当前网络环境,水军、门户网站位置营销或SEO等技术的采用,可能使某些质量低劣的项目成为热点,因此而降低了推荐质量。因此在使用相似度推荐时,需要综合考虑各种因素,找到最适合的推荐算法。
下面我们以用户对商品进行打分为例,描述相似度的计算方法:
product1 | product2 | product3 | |
user1 | 3 | 4 | 2 |
user2 | 2 | 2 | 4 |
user3 | 1 | 3 | 5 |
表1
为了简单起见,我们假设三个用户对三个商品进行了打分,分数如表1所示。我们首先想通过该数据计算产品的相似度,因此我们需要将数据进行重新整理:
user1 | user2 | user3 | |
product1 | 3 | 2 | 1 |
product2 | 4 | 2 | 3 |
product3 | 2 | 4 | 5 |
表2
到此原始数据准备完毕,下面就是计算相似度算法了。
通常相似度需要按相似度大小进行排序,我们定义了ItemInfo类来保存相似度排序列表中所需要的信息。
public class ItemInfo implements Comparable<ItemInfo> {
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public double getVal() {
return val;
}
public void setVal(double val) {
this.val = val;
}
private String key = null;
private double val = 0.0;
@Override
public int compareTo(ItemInfo o) {
// TODO Auto-generated method stub
ItemInfo info = (ItemInfo)o;
if (val > info.getVal()) {
return -1;
} else {
return 1;
}
}
}
我们实现了比较接口,这样便于对本类组成的List列表进行排序操作。
我们需要定义一个某个产品与其它相品相似度存储列表类:
public class SmlrList {
public SmlrList() {
rates = new Hashtable<String, Double>();
sortedKeys = new Vector<String>();
sortedVals = new Vector<ItemInfo>();
}
public List<ItemInfo> getSortedVals() {
return sortedVals;
}
public void setSortedVals(List<ItemInfo> sortedVals) {
this.sortedVals = sortedVals;
}
public List<String> getSortedKeys() {
return sortedKeys;
}
public void setSortedKeys(List<String> sortedKeys) {
this.sortedKeys = sortedKeys;
}
public Hashtable<String, Double> getRates() {
return rates;
}
public void setRates(Hashtable<String, Double> rates) {
this.rates = rates;
}
private List<ItemInfo> sortedVals = null;
private List<String> sortedKeys = null; // 存储按照相似度排序的key值
private Hashtable<String, Double> rates = null; // 与其他元素的相似度列表
}
由上面的代码可以看出,该产品与其他产品相似度存储在rates列表中,为调试方便,我们加入了sortedKeys,可以按产品序号顺序显示相似度,按相似度由大到小排序的列表是sortedVals。注意:在实际系统中,可以只取sortedVals即可,其它属性需是用于调试目的。
具体调用代码如下所示:
/**
* 计算条目的相似度列表,可以是如产品、地点的相似度,也可以是用户的相似度
* 原始数据如下所示:
*
*/
public void test() {
Hashtable<String, SmlrList> itemsSmlrList = null;
Vector<String> sortedItemId = null; // 经过排序的条目编号
Hashtable<String, Hashtable<String, Double>> rateData = null; // 条目被每个用户评分的信息
Hashtable<String, Double> itemRateData = null;
// 初始化原始数据
rateData = new Hashtable<String, Hashtable<String, Double>>();
int rowId = 1;
int colId = 1;
// 加入第三行
rowId = 3;
itemRateData = new Hashtable<String, Double>();
colId = 1;
itemRateData.put("" + colId, 2.0);
colId = 2;
itemRateData.put("" + colId, 4.0);
colId = 3;
itemRateData.put("" + colId, 5.0);
rateData.put("" + rowId, itemRateData);
// 加入第一行
rowId = 1;
itemRateData = new Hashtable<String, Double>();
colId = 1;
itemRateData.put("" + colId, 3.0);
colId = 2;
itemRateData.put("" + colId, 2.0);
colId = 3;
itemRateData.put("" + colId, 1.0);
rateData.put("" + rowId, itemRateData);
// 加入第二行
rowId = 2;
itemRateData = new Hashtable<String, Double>();
colId = 1;
itemRateData.put("" + colId, 4.0);
colId = 2;
itemRateData.put("" + colId, 2.0);
colId = 3;
itemRateData.put("" + colId, 3.0);
rateData.put("" + rowId, itemRateData);
sortedItemId = new Vector<String>(rateData.keySet());
Collections.sort(sortedItemId);
// 对原始数据进行归一化并显示
Hashtable<String, Double> normUserRateData = null;
Vector<String> sortedUk = null;
for (String rowKey : sortedItemId) {
double sum = 0.0;
for (Double dbl : rateData.get(rowKey).values()) {
sum += dbl.doubleValue() * dbl.doubleValue();
}
sum = Math.sqrt(sum);
normUserRateData = new Hashtable<String, Double>();
itemRateData = rateData.get(rowKey);
for (String colKey : itemRateData.keySet()) {
normUserRateData.put(colKey, itemRateData.get(colKey).doubleValue() / sum);
}
rateData.remove(rowKey);
rateData.put(rowKey, normUserRateData);
// 打印
sortedUk = new Vector<String>(rateData.get(rowKey).keySet());
Collections.sort(sortedUk);
for (String suk : sortedUk) {
System.out.print(" " + suk + ":" + rateData.get(rowKey).get(suk).doubleValue());
}
System.out.print("\r\n");
}
// 计算条目之间的相似度
itemsSmlrList = new Hashtable<String, SmlrList>();
SmlrList smlrList = null;
ItemInfo itemInfo = null;
int i = 0;
int j = 0;
double smlrVal = 0.0;
for (i=0; i<sortedItemId.size(); i++) {
smlrList = new SmlrList();
for (j=0; j<sortedItemId.size(); j++) {
smlrVal = calDotProd(new Vector<Double>(rateData.get(sortedItemId.get(i)).values()),
new Vector<Double>(rateData.get(sortedItemId.get(j)).values()));
smlrList.getSmlrs().put(sortedItemId.get(j), smlrVal);
smlrList.getSortedKeys().add(sortedItemId.get(j));
itemInfo = new ItemInfo();
itemInfo.setKey(sortedItemId.get(j));
itemInfo.setVal(smlrVal);
smlrList.getSortedVals().add(itemInfo);
}
Collections.sort(smlrList.getSortedKeys());
Collections.sort(smlrList.getSortedVals());
itemsSmlrList.put(sortedItemId.get(i), smlrList);
}
// 显示相似度结果
SmlrList sl02 = null;
for (String uk2 : sortedItemId) {
sl02 = itemsSmlrList.get(uk2);
System.out.print(uk2 + ":");
for (String uk3 : sl02.getSortedKeys()) {
System.out.print(" " + sl02.getSmlrs().get(uk3) + "[" + uk3 + "] ");
}
System.out.print("\r\n");
}
System.out.println("**************************************");
for (String rowKey : sortedItemId) {
smlrList = itemsSmlrList.get(rowKey);
System.out.print(rowKey + ":");
for (ItemInfo itemInfo1 : smlrList.getSortedVals()) {
if (!itemInfo1.getKey().equals(rowKey)) {
System.out.print(" " + itemInfo1.getVal() + "[" + itemInfo1.getKey() + "] ");
}
}
System.out.print("\r\n");
}
}
程序运行结果为:
1:0.8017837257372732 2:0.5345224838248488 3:0.2672612419124244
1:0.7427813527082074 2:0.3713906763541037 3:0.5570860145311556
1:0.29814239699997197 2:0.5962847939999439 3:0.7453559924999299
1: 1.0[1] 0.9429541672723838[2] 0.756978119245116[3]
2: 0.9429541672723838[1] 1.0[2] 0.8581366251553131[3]
3: 0.756978119245116[1] 0.8581366251553131[2] 1.0[3]
**************************************
1: 0.9429541672723838[2] 0.756978119245116[3]
2: 0.9429541672723838[1] 0.8581366251553131[3]
3: 0.8581366251553131[2] 0.756978119245116[1]
同理,如果我们需要计算用户的相似度,只需要将原始数据变为表1格式读入程序即可。
如上所述,有了上述类和方法,我们可以很容易求出任意两个条目的相似度,同时可以对某个条目按相似度从大到小的方式,显示该条目与其他条目相似度。
相似度度量这里采用向量的点积,点积值越大,表明对应条目的相似度越大,具体计算方法如下所示:
public double calDotProd(List<Double> vec1, List<Double> vec2) {
double dotProd = 0.0;
for (int i=0; i<vec1.size(); i++) {
dotProd += vec1.get(i).doubleValue() * vec2.get(i).doubleValue();
}
return dotProd;
}