基於使用者(user-based)的協同過濾推薦演算法的初步理解以及程式碼實現
阿新 • • 發佈:2019-01-05
總論
協同過濾是目前最經典的推薦演算法。
分而理之,協同,指通過線上資料找到使用者可能喜歡的物品;過濾,濾掉一些不值得推薦的資料。
協同過濾推薦分為三種類型。第一種是基於使用者(user-based)的協同過濾,第二種是基於專案(item-based)的協同過濾,第三種是基於模型(model based)的協同過濾。
我認為,選擇哪種型別,取決於業務場景。需要考慮的是,user和item的數量比,誰的數量級小就選擇哪個模式的cf;在業務處於的場景中,user和item的多變性比較,選擇多變性較小的一方,在後續的維護,會方便很多;最後一點,明確你的推薦覆蓋要求。
今天主要講基於使用者。
凡是推薦演算法離不開大資料的基礎,基於使用者型別的資料一般是如下格式:
使用者id ,外物id,使用者對外物的評分,以上是一般物聯網的演算法模型,(舉一反三,分析系統日誌,建立如下模型,使用者id,資源id,使用者對資源的訪問次數,--->實現推薦頁面 )
演算法基礎實現
看這塊的時候,我是真的後悔大學沒好好學數學!!!
首先讓我們拆分下過程。 基於使用者的協同過濾 可分為2步,
1.找到相似度高的使用者
2.根據1步驟獲取的資訊,推薦源使用者相對喜歡,並且未採取過的行為
下面這個是大名鼎鼎的Jaccard公式,很簡單, 取2個使用者的選擇集的交集,跟2個使用者的選擇集的並集,進行計算。但很顯然,若要計算整個使用者集合,
時間複雜度是O(n^2 ),太慢了。
後續就有了利用倒排查表進行優化如下: 可以建立個5X5矩陣,以使用者、行為為維度。
行為 使用者
1 1,2,3
2 2,3,5
3 1,3,5
4 3,4,5
5 2,3,4
到這裡,已經可以獲得相似使用者了。
下面這個公式可以完成之前提到的步驟二,p(u,i)-使用者u對行為i的權重,S(u,k)表示和使用者u相似的K個使用者,N(i)表示採取過行為i的使用者集合,Wuv表示使用者u和使用者v的相似度,Rvi表示使用者v對行為i的權重。
Rvi部分可以大做文章,使用者對某個行為的權重值定義,需要以業務為基礎。在這裡舉個例子,如果某個行為在整個使用者群裡執行的次數不多,但某兩個使用者多次執行,可判斷這兩個使用者對該行為權重極大,也就是對該行為興趣濃厚,也就是這兩個使用者極其得相似,我把這個現象稱為,單熱群冷現象。
演算法數學總結到此結束,感覺數學功底是真的差,上面那個公式看了半天,後來還是諮詢了大學裡數學師父~~~
Mahout實現
Mahout是hadoop全家桶裡的一員,提供一些可擴充套件的機器學習領域經典演算法的實現,旨在幫助開發人員更加方便快捷地建立智慧應用程式。
之前說的一大堆,其實開發中,都是用不到的,它已經在它的庫裡給你實現了~~直接上程式碼,程式碼也很簡單。
public class MahoutTest {
public static void main(String[] args) throws IOException, TasteException {
String file = "D:\\test.txt";
//模型建立
DataModel model = new FileDataModel(new File(file));
//根據模型獲取userId迭代器
LongPrimitiveIterator iter = model.getUserIDs();
UserSimilarity user = new EuclideanDistanceSimilarity(model);
//2代表--限制在模型中的使用者數量
NearestNUserNeighborhood neighbor = new NearestNUserNeighborhood(2, user, model);
Recommender r = new GenericUserBasedRecommender(model, neighbor, user);
while (iter.hasNext()) {
long uid = iter.nextLong();
//3代表--所需要的行為數
List<RecommendedItem> list = r.recommend(uid, 3);
System.out.printf("uid:%s", uid);
for (RecommendedItem ritem : list) {
System.out.printf("(%s,%f)", ritem.getItemID(), ritem.getValue());
}
System.out.println();
}
}
}
原始碼解析
核心類如下,粗略講一下我這裡用到的genericUserBasedRecommender
與上圖程式碼順序一致
1.校驗傳參
2.獲取相似使用者id
3.獲取相似使用者的所有行為資訊
4.獲取評估資訊
5.獲取結果集 getTopItems方法如下
public static List<RecommendedItem> getTopItems(int howMany,
LongPrimitiveIterator possibleItemIDs,
IDRescorer rescorer,
Estimator<Long> estimator) throws TasteException {
Preconditions.checkArgument(possibleItemIDs != null, "argument is null");
Preconditions.checkArgument(estimator != null, "argument is null");
Queue<RecommendedItem> topItems = new PriorityQueue<RecommendedItem>(howMany + 1,
Collections.reverseOrder(ByValueRecommendedItemComparator.getInstance()));
boolean full = false;
double lowestTopValue = Double.NEGATIVE_INFINITY;
while (possibleItemIDs.hasNext()) {
long itemID = possibleItemIDs.next();
if (rescorer == null || !rescorer.isFiltered(itemID)) {
double preference;
try {
preference = estimator.estimate(itemID);
} catch (NoSuchItemException nsie) {
continue;
}
double rescoredPref = rescorer == null ? preference : rescorer.rescore(itemID, preference);
if (!Double.isNaN(rescoredPref) && (!full || rescoredPref > lowestTopValue)) {
topItems.add(new GenericRecommendedItem(itemID, (float) rescoredPref));
if (full) {
topItems.poll();
} else if (topItems.size() > howMany) {
full = true;
topItems.poll();
}
lowestTopValue = topItems.peek().getValue();
}
}
}
int size = topItems.size();
if (size == 0) {
return Collections.emptyList();
}
List<RecommendedItem> result = Lists.newArrayListWithCapacity(size);
result.addAll(topItems);
Collections.sort(result, ByValueRecommendedItemComparator.getInstance());
return result;
}
這裡用了PriorityQueue,利用了其特殊建構函式 指定比較器,指定初始容量。
public PriorityQueue(int initialCapacity,
Comparator<? super E> comparator) {
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}
查看了優先順序佇列的原始碼,其本質是:PriorityQueue會對入隊的元素進行排序,所以在佇列頂端的總是最小的元素。
end!