java實現算法推薦:Mahout實踐

推薦系統概述

推薦系統概述html

推薦算法分類:

按數據使用劃分:java

  • 協同過濾算法:UserCF, ItemCF, ModelCF
  • 基於內容的推薦: 用戶內容屬性和物品內容屬性
  • 社會化過濾:基於用戶的社會網絡關係

按模型劃分:python

  • 最近鄰模型:基於距離的協同過濾算法
  • Latent Factor Mode(SVD):基於矩陣分解的模型
  • Graph:圖模型,社會網絡圖模型

基於用戶的協同過濾算法UserCF

基於用戶的協同過濾,經過不一樣用戶對物品的評分來評測用戶之間的類似性,基於用戶之間的類似性作出推薦。簡單來說就是:給用戶推薦和他興趣類似的其餘用戶喜歡的物品。
mysql

image

基於用戶的 CF 的基本思想至關簡單,基於用戶對物品的偏好找到相鄰鄰居用戶,而後將鄰居用戶喜歡的推薦給當前用戶。計算上,就是將一個用戶對全部物品的偏好做爲一個向量 來計算用戶之間的類似度,找到 K 鄰居後,根據鄰居的類似度權重以及他們對物品的偏好,預測當前用戶沒有偏好的未涉及物品,計算獲得一個排序的物品列表做爲推薦。圖 2 給出了一個例子,對於用戶 A,根據用戶的歷史偏好,這裏只計算獲得一個鄰居 – 用戶 C,而後將用戶 C 喜歡的物品 D 推薦給用戶 A。算法

基於物品的協同過濾算法ItemCF

於item的協同過濾,經過用戶對不一樣item的評分來評測item之間的類似性,基於item之間的類似性作出推薦。簡單來說就是:給用戶推薦和他以前喜歡的物品類似的物品。sql

image

基於物品的 CF 的原理和基於用戶的 CF 相似,只是在計算鄰居時採用物品自己,而不是從用戶的角度,即基於用戶對物品的偏好找到類似的物品,而後根據用戶的歷史偏好,推薦類似的物品給他。從計算 的角度看,就是將全部用戶對某個物品的偏好做爲一個向量來計算物品之間的類似度,獲得物品的類似物品後,根據用戶歷史的偏好預測當前用戶尚未表示偏好的 物品,計算獲得一個排序的物品列表做爲推薦。圖 3 給出了一個例子,對於物品 A,根據全部用戶的歷史偏好,喜歡物品 A 的用戶都喜歡物品 C,得出物品 A 和物品 C 比較類似,而用戶 C 喜歡物品 A,那麼能夠推斷出用戶 C 可能也喜歡物品 C。數據庫

注:基於物品的協同過濾算法,是目前商用最普遍的推薦算法。apache

協同過濾算法實現,分爲2個步驟

  1. 計算物品之間的類似度
  2. 根據物品的類似度和用戶的歷史行爲給用戶生成推薦列表

Mathout:

東西太多……仍是本身看博客吧api


實戰

數據源

取數據庫t_recent_listen_00數據,總數據5323626條,導出sql文件導入本地數據庫 導入時間大約15個小時後中斷,刪除user_id爲0的數據等數據清理操做,共導入了2019-02-16及以前的收聽記錄,總條數4866861條bash

  • 節目收聽記錄538805條
  • 書籍收聽記錄3842982條
  • 閱讀記錄484644條

image

生成csv格式文件

  • book_csv_file.csv
  • album_csv_file.csv
  • read_csv_file.csv

python生成csv

import pymysql
import csv
import sys
import os


class ToCsv:

    def __init__(self):
        self.db = pymysql.connect(host='', user='', passwd='', port=3306, db='hi')
        self.cursor = self.db.cursor()
        self.last_id = 0
        self.count = 0
        self.book_count = 0
        self.album_count = 0
        self.read_count = 0

    def _release_db(self):
        self.db.close()
        self.cursor.close()

    def _do(self):
        while 1:
            ret = self._search_data()
            if ret == 0:
                break

    def _search_data(self):
        self.cursor.size = 50
        sql = 'select * from t_recent_listen_00 where id>%s limit 50'
        self.cursor.execute(sql, self.last_id)
        lines = self.cursor.fetchall()
        if (len(lines) > 0):
            self.last_id = lines[len(lines)-1][0]
            self.count = len(lines) + self.count;
            self._write_cvs(lines)
        if (self.count % 1000 == 0):
            print(self.count)
        return len(lines)
        # print(self.last_id)

    def _write_cvs(self, lines):
        for item in lines:
            if (item[10] == 4):
                book_cvs_file = open('book_csv_file.csv', 'a', newline='')
                book_writers = csv.writer(book_cvs_file)
                # book_writers.writerow(['user_id', 'book_id'])
                user_id = item[1]
                book_id = item[2]
                self.book_count += 1
                if (self.book_count % 1000 == 0):
                    print("book count", self.book_count)

                book_writers.writerow([str(user_id), str(book_id)])
            if (item[10] == 2):
                album_cvs_file = open('album_csv_file.csv', 'a', newline='')
                album_writers = csv.writer(album_cvs_file)
                # album_writers.writerow(['user_id', 'book_id'])
                user_id = item[1]
                book_id = item[2]
                self.album_count += 1
                if (self.album_count % 1000 == 0):
                    print("album count", self.album_count)
                album_writers.writerow([str(user_id), str(book_id)])
            if (item[10] == 10):
                read_cvs_file = open('read_csv_file.csv', 'a', newline='')
                read_writers = csv.writer(read_cvs_file)
                # album_writers.writerow(['user_id', 'book_id'])
                user_id = item[1]
                book_id = item[2]
                self.read_count += 1
                if (self.read_count % 1000 == 0):
                    print("read count", self.read_count)
                read_writers.writerow([str(user_id), str(book_id)])

    def main(self):
        pass


if __name__ == '__main__':
    c = ToCsv()
    c._do()
    c._release_db()

複製代碼

簡單講解一下,就是讀數據庫,每次查詢50條並寫在csv文件最後,本身寫的demo寫註釋太累了

book_csv_file.csv文件大小60M

image

java實現推薦系統

pom.xml

<properties>
        <mahout.version>0.9</mahout.version>
    </properties>
    <dependencies>
        ...
        <dependency>
            <groupId>org.apache.mahout</groupId>
            <artifactId>mahout-core</artifactId>
            <version>${mahout.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.mahout</groupId>
            <artifactId>mahout-integration</artifactId>
            <version>${mahout.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.mortbay.jetty</groupId>
                    <artifactId>jetty</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.cassandra</groupId>
                    <artifactId>cassandra-all</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>me.prettyprint</groupId>
                    <artifactId>hector-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
複製代碼

MahoutController

@RestController
@Slf4j
@RequestMapping("/")
public class MahoutController {

    @GetMapping
    public List<RecommendedItem> mahout(@RequestParam String userId,@RequestParam int type){
        //數據模型
        DataModel model = null;
        List<RecommendedItem> list = Lists.newArrayList();
        File cvsFile = null;
        try {
            long startTime = System.currentTimeMillis();
//            File bookCsvFile = ResourceUtils.getFile("classpath:csv/book_cvs_file.csv");
            if (type == 1) {
                cvsFile = new File("E:\\code\\python\\to_csv\\book_csv_file.csv");
            }else if(type == 2){
                cvsFile = new File("E:\\code\\python\\to_csv\\read_csv_file.csv");
            }else if(type == 3){
                cvsFile = new File("E:\\code\\python\\to_csv\\album_csv_file.csv");
            }
            model = new GenericBooleanPrefDataModel(
                    GenericBooleanPrefDataModel
                            .toDataMap(new FileDataModel(cvsFile)));
            ItemSimilarity item=new LogLikelihoodSimilarity(model);
            //物品推薦算法
            Recommender r=new GenericItemBasedRecommender(model,item);
             list = r.recommend(Long.parseLong(userId), 10);
            list.forEach(System.out::println);
            long endTime = System.currentTimeMillis();
            log.info((endTime-startTime) +"");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TasteException e) {
            e.printStackTrace();
        }

        return list;
    }
    @GetMapping("/user")
    public List<RecommendedItem> mahoutUser(@RequestParam String userId,@RequestParam int type){
        //數據模型
        DataModel model = null;
        List<RecommendedItem> list = Lists.newArrayList();
        File cvsFile = null;
        try {
            long startTime = System.currentTimeMillis();
//            File bookCsvFile = ResourceUtils.getFile("classpath:csv/book_cvs_file.csv");
            if (type == 1) {
                cvsFile = new File("E:\\code\\python\\to_csv\\book_csv_file.csv");
            }else if(type == 2){
                cvsFile = new File("E:\\code\\python\\to_csv\\read_csv_file.csv");
            }else if(type == 3){
                cvsFile = new File("E:\\code\\python\\to_csv\\album_csv_file.csv");
            }
            model = new FileDataModel(cvsFile);
            //用戶相識度算法
            UserSimilarity userSimilarity=new LogLikelihoodSimilarity(model);
            UserNeighborhood neighborhood = new NearestNUserNeighborhood(20,userSimilarity, model);
            Recommender r=new GenericUserBasedRecommender(model, neighborhood, userSimilarity);

            list = r.recommend(Long.parseLong(userId), 10);
            list.forEach(System.out::println);
            long endTime = System.currentTimeMillis();
            log.info((endTime-startTime) +"");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TasteException e) {
            e.printStackTrace();
        }
        return list;
    }
}

複製代碼

根據傳入user_id來計算出推薦的物品id

結果

測試基於用戶協同過濾和基於物品協同過濾輸出結果

image

380萬的數據計算一次須要4-6秒,若是實時解析文件生成推薦內容明顯會拖累接口相應速度

測試結果還有一點,由於數據的問題csv只生成了用戶id與物品id的對應關係,並無生成用戶對物品的打分數據,在實際過程當中能夠經過最近收聽的數據庫記錄,計算用戶聽了改資源多長時間來判斷用戶對物品的喜愛程度。
基於用戶協同過濾和基於物品協同過濾輸出的結果迥異,代碼中物品協同過濾算法在不一樣用戶id下返回的數據重複性很高,不大使用,而相比之下,代碼中用戶協同過濾算法在不一樣用戶id下返回數據看起來比較可靠。實際開發過程當中也須要對實際的數據進行判斷,不然沒法進行很好的推薦,反而會變成一個用戶體驗不好的功能。

小結

單機版的mahout在實際開發過程當中沒法勝任實時推薦,特別是在大數量的狀況下。 能夠考慮後臺啓動定時任務,天天/每週生成csv文件(python代碼中生成csv的代碼查數據庫很慢……生成了N個小時,java應該會好一些),再經過定時任務計算用戶並保存不一樣用戶的不一樣推薦內容(須要根據實際狀況進行判斷,畢竟這個量也不小)

相關文章
相關標籤/搜索