在word2vec学习小记一文中我们曾经学习了word2vec这个工具,它基于神经网络语言模型并在其基础上进行优化,最终能获取词向量和语言模型。在我们的商品搜索系统里,采用了word2vec的方式来计算用户向量和商品向量,并通过Elasticsearch的function_score评分机制和自定义的脚本插件来实现个性化搜索。
先来看下维基百科上对于个性化搜索的定义和介绍:
Personalized search refers to web search experiences that are tailored specifically to an individual's interests by incorporating information about the individual beyond specific query provided. Pitkow et al. describe two general approaches to personalizing search results, one involving modifying the user's query and the other re-ranking search results.
由此我们可以得到两个重要的信息:
而对我们电商网站来说,个性化搜索的重点是当用户搜索某个关键字,如【卫衣】时,能将用户最感兴趣最可能购买的商品(如用户偏好的品牌或款式)优先展示给用户,以提升用户体验和点击转化。
select concat_ws(' ', collect_set(product_skn)) as skns
from
(select uid, cast(product_skn as string) as product_skn, click_time_stamp
from product_click_record
where date_id <= $date_id and date_id >= $date_id_30_day_ago
order by uid, click_time_stamp) as a
group by uid;time ./word2vec -train $prepare_file -output $result_file -cbow 1 -size 20
-window 8 -negative 25 -hs -sample 1e-4 -threads 20 -iter 15vec_list = []
for i in range(feature_length):
vec_list.append("avg(coalesce(b.vec[%s], 0))" % (str(i)))
vec = ', '.join(vec_list)
select a.uid as uid, array(%s) as vec
from
(select * from product_click_record where date_id <= $date_id and date_id >= $date_id_30_day_ago) as a
left outer join
(select * from product_w2v where date_id = $date_id) as b
on a.product_skn = b.product_skn
group by a.uid;Map<String, Object> scriptParams = new HashMap<>();
scriptParams.put("field", "productFeatureVector");
scriptParams.put("inputFeatureVector", userVector);
scriptParams.put("version", version);
Script script = new Script("feature_vector_scoring_script", ScriptService.ScriptType.INLINE, "native", scriptParams);
functionScoreQueryBuilder.add(ScoreFunctionBuilders.scriptFunction(script));这是我自己写的一个插件,具体的使用可以看下项目主页,其核心也就一个类,我将其主要的代码和注释贴一下:
public class FeatureVectorScoringSearchScript extends AbstractSearchScript {
public static final ESLogger LOGGER = Loggers.getLogger("feature-vector-scoring");
public static final String SCRIPT_NAME = "feature_vector_scoring_script";
private static final double DEFAULT_BASE_CONSTANT = 1.0D;
private static final double DEFAULT_FACTOR_CONSTANT = 1.0D;
// field in index to store feature vector
private String field;
// version of feature vector, if it isn't null, it should match version of index
private String version;
// final_score = baseConstant + factorConstant * cos(X, Y)
private double baseConstant;
// final_score = baseConstant + factorConstant * cos(X, Y)
private double factorConstant;
// input feature vector
private double[] inputFeatureVector;
// cos(X, Y) = Σ(Xi * Yi) / ( sqrt(Σ(Xi * Xi)) * sqrt(Σ(Yi * Yi)) )
// the inputFeatureVectorNorm is sqrt(Σ(Xi * Xi))
private double inputFeatureVectorNorm;
public static class ScriptFactory implements NativeScriptFactory {
@Override
public ExecutableScript newScript(@Nullable Map<String, Object> params) throws ScriptException {
return new FeatureVectorScoringSearchScript(params);
}
@Override
public boolean needsScores() {
return false;
}
}
private FeatureVectorScoringSearchScript(Map<String, Object> params) throws ScriptException {
this.field = (String) params.get("field");
String inputFeatureVectorStr = (String) params.get("inputFeatureVector");
if (this.field == null || inputFeatureVectorStr == null || inputFeatureVectorStr.trim().length() == ) {
throw new ScriptException("Initialize script " + SCRIPT_NAME + " failed!");
}
this.version = (String) params.get("version");
this.baseConstant = params.get("baseConstant") != null ? Double.parseDouble(params.get("baseConstant").toString()) : DEFAULT_BASE_CONSTANT;
this.factorConstant = params.get("factorConstant") != null ? Double.parseDouble(params.get("factorConstant").toString()) : DEFAULT_FACTOR_CONSTANT;
String[] inputFeatureVectorArr = inputFeatureVectorStr.split(",");
int dimension = inputFeatureVectorArr.length;
double sumOfSquare = .0D;
this.inputFeatureVector = new double[dimension];
double temp;
for (int index = ; index < dimension; index++) {
temp = Double.parseDouble(inputFeatureVectorArr[index].trim());
this.inputFeatureVector[index] = temp;
sumOfSquare += temp * temp;
}
this.inputFeatureVectorNorm = Math.sqrt(sumOfSquare);
LOGGER.debug("FeatureVectorScoringSearchScript.init, version:{}, norm:{}, baseConstant:{}, factorConstant:{}."
, this.version, this.inputFeatureVectorNorm, this.baseConstant, this.factorConstant);
}
@Override
public Object run() {
if (this.inputFeatureVectorNorm == ) {
return this.baseConstant;
}
if (!doc().containsKey(this.field) || doc().get(this.field) == null) {
LOGGER.error("cannot find field {}.", field);
return this.baseConstant;
}
String docFeatureVectorStr = ((ScriptDocValues.Strings) doc().get(this.field)).getValue();
return calculateScore(docFeatureVectorStr);
}
public double calculateScore(String docFeatureVectorStr) {
// 1. check docFeatureVector
if (docFeatureVectorStr == null) {
return this.baseConstant;
}
docFeatureVectorStr = docFeatureVectorStr.trim();
if (docFeatureVectorStr.isEmpty()) {
return this.baseConstant;
}
// 2. check version and get feature vector array of document
String[] docFeatureVectorArr;
if (this.version != null) {
String versionPrefix = version + "|";
if (!docFeatureVectorStr.startsWith(versionPrefix)) {
return this.baseConstant;
}
docFeatureVectorArr = docFeatureVectorStr.substring(versionPrefix.length()).split(",");
} else {
docFeatureVectorArr = docFeatureVectorStr.split(",");
}
// 3. check the dimension of input and document
int dimension = this.inputFeatureVector.length;
if (docFeatureVectorArr == null || docFeatureVectorArr.length != dimension) {
return this.baseConstant;
}
// 4. calculate the relevance score of the two feature vector
double sumOfSquare = .0D;
double sumOfProduct = .0D;
double tempValueInDouble;
for (int i = ; i < dimension; i++) {
tempValueInDouble = Double.parseDouble(docFeatureVectorArr[i].trim());
sumOfProduct += tempValueInDouble * this.inputFeatureVector[i];
sumOfSquare += tempValueInDouble * tempValueInDouble;
}
if (sumOfSquare == ) {
return this.baseConstant;
}
double cosScore = sumOfProduct / (Math.sqrt(sumOfSquare) * inputFeatureVectorNorm);
return this.baseConstant + this.factorConstant * cosScore;
}
}来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/31509949/viewspace-3164052/ ,如需转载,请注明出处,否则将追究法律责任。