
搜索引擎通过分析每个用户的每次搜索与用户的每次点击,从中学习到哪个结果才是用户最需要的,最终可以针对每个用户改进搜索结果。这是机器学习一个实例。
在《智能Web算法》的2.4章,给出了简单的实现,利用用户点击数据来训练搜索引擎,得出每个结果的用户点击分值,然后综合索引分值和PageRank,改进了最终的结果:
package com.hankcs;
import iweb2.ch2.clicks.UserClick;
import iweb2.ch2.clicks.UserQuery;
import iweb2.ch2.shell.FetchAndProcessCrawler;
import iweb2.ch2.shell.LuceneIndexer;
import iweb2.ch2.shell.MySearcher;
import iweb2.ch2.shell.PageRank;
import iweb2.ch5.classification.bayes.NaiveBayes;
import iweb2.ch5.classification.core.TrainingSet;
/**
* @author hankcs
*/
public class ch2_4_UserClicks
{
public static void main(String[] args) throws Exception
{
//===========================================================================================
//
// -- Data (Business news URLs and 3 spam pages)
//
FetchAndProcessCrawler crawler = new FetchAndProcessCrawler("C:/iWeb2/data/ch02",5,200);
crawler.setUrls("biz");
crawler.addUrl("file:///c:/iWeb2/data/ch02/spam-biz-01.html");
crawler.addUrl("file:///c:/iWeb2/data/ch02/spam-biz-02.html");
crawler.addUrl("file:///c:/iWeb2/data/ch02/spam-biz-03.html");
// or use ALL the pages
// crawler.setAllUrls();
crawler.run();
//
// -- Lucene
//
LuceneIndexer luceneIndexer = new LuceneIndexer(crawler.getRootDir());
luceneIndexer.run();
MySearcher oracle = new MySearcher(luceneIndexer.getLuceneDir());
//
// -- PageRank
//
PageRank pageRank = new PageRank(crawler.getCrawlData());
pageRank.setAlpha(0.9);
pageRank.setEpsilon(0.00000001);
pageRank.build();
//
// -- Load the user clicks
//
// 捕获用户点击
UserClick aux = new UserClick();
// 载入训练实例
UserClick[] clicks = (UserClick[]) aux.load("C:/iWeb2/data/ch02/user-clicks.csv");
// 创建训练集
TrainingSet tSet = new TrainingSet(clicks);
//
// -- Create the classifier and train it
//
// 定义分类器
NaiveBayes naiveBayes = new NaiveBayes("Naive Bayes", tSet);
naiveBayes.trainOnAttribute("UserName");
naiveBayes.trainOnAttribute("QueryTerm_1");
naiveBayes.trainOnAttribute("QueryTerm_2");
naiveBayes.train();
oracle.setUserLearner(naiveBayes);
//
// -- Combined ranking
//
UserQuery dmitryQuery = new UserQuery("dmitry","google ads");
oracle.search(dmitryQuery,5,pageRank);
UserQuery babisQuery = new UserQuery("babis","google ads");
oracle.search(babisQuery,5,pageRank);
}
}
顺带一提,从Google Code检出的配书源码有一处错误,贝叶斯分类器的关注属性设置有误,会导致训练无效。
我调试了很久才发现不对劲,希望读者朋友不要重蹈覆辙。
贝叶斯分类器读取了用户点击数据,并且统计了每次点击的相关属性。
假如用户"dmitry"搜索"google ads",那么这次搜索被分类器分为三个属性:
dmitry google ads
用户的一次搜索被称为一次实例,每个实例I被分解出为若干个属性A,比如上面分解出了三个属性,分别是用户id、搜索词第一部分、搜索词第二部分(这是分词器的功劳)。两个实例只要任意属性不同,则被视为不同的实例。现在有一个备选的搜索结果C,怎么知道C就是用户想要的结果的概率有多大呢?这是一个后验概率的绝佳应用场景,记所求概率为P(C|I),即实例I发生了并且实例I属于概念C的概率。由贝叶斯公式可以知道P(C|I)=P(I|C)*P(C)/P(I)。
求P(I|C)对应于贝叶斯分类器/iweb2/ch5/classification/bayes/NaiveBayes.java的如下方法
/**
* 已知用户想要的的是c,用户查询i的概率
* @param i
* @param c
* @return
*/
public double getProbability(Instance i, Concept c)
{
double cP = 1;
// 遍历实例的每个属性
for (Attribute a : i.getAtrributes())
{
// 如果是训练集要用到这个属性
if (a != null && attributeList.contains(a.getName()))
{
Map<Attribute, AttributeValue> aMap = p.get(c);
AttributeValue aV = aMap.get(a);
if (aV == null)
{
// the specific attribute value is not present for the current concept.
// Can you justify the following estimate?
// Can you think of a better choice?
// 从来没有观察到的概念
cP *= ((double) 1 / (tSet.getSize() + 1));
}
else
{
// 实例i的似然率等于观测到各个属性的概率的乘积
// 一个属性的概率=属性出现的次数/训练集中概念的实例数量
cP *= (aV.getCount() / conceptPriors.get(c));
}
}
}
return (cP == 1) ? (double) 1 / tSet.getNumberOfConcepts() : cP;
}
在这个方法里统计了对于实例I的所有受关注的属性,在所有属于概念C的实例中的出现概率,并且将其累乘起来作为结果返回。
求P(C)则简单得多,对应于贝叶斯分类器/iweb2/ch5/classification/bayes/NaiveBayes.java的如下方法
/**
* 获取c的先验概率,根据训练集中该概念出现的次数取得
* @param c
* @return
*/
public double getProbability(Concept c)
{
Double trInstanceCount = conceptPriors.get(c);
if (trInstanceCount == null)
{
trInstanceCount = 0.0;
}
return trInstanceCount / tSet.getSize();
}
求P(I)对应于贝叶斯分类器/iweb2/ch5/classification/bayes/NaiveBayes.java的如下方法
/**
* 获取观察到实例i的概率 This method calculates the denumerator of Bayes theorem
* @param i
* @return the probability of observing <CODE>Instance</CODE> i
*/
public double getProbability(Instance i)
{
double cP = 0;
for (Concept c : getTset().getConceptSet())
{
cP += getProbability(i, c) * getProbability(c);
}
return (cP == 0) ? (double) 1 / tSet.getSize() : cP;
}
这里稍微转了个弯,统计了全部“用户实际需要概念小c,最终搜索I,小c是训练集中任意的概念”的概率之和,并将其作为结果返回。
搜索结果就是将三个分数统计起来:
double indexScore = docResults[i].getScore();
double pageRankScore = pR.getPageRank(url);
BaseConcept bC = new BaseConcept(url); // 创建一个实例,对应着文件名
double userClickScore = learner.getProbability(bC, uClick);
// 打印出来看看
System.out.println(uQuery.getUid() + " click " + url + " -> " + userClickScore);
// Create the final score
double hScore;
if (userClickScore == 0) // 计算最终(混合)分数
{
hScore = indexScore * pageRankScore * EPSILON;
}
else
{
hScore = indexScore * pageRankScore * userClickScore;
}
// Update the score of the results
docResults[i].setScore(hScore);
码农场
所以您修正過的部分是哪邊呢? 在下才疏學淺,能否明示一二?
两份代码对比一下就知道了