推荐系统 - 隐语义LFM召回算法

说明

分为三部分:

    (1)Corpus类:相当于是一个语料库吧,用来存储 训练用的样本的, 产生过交互的为正样本, 以没有在当前用户产生交互的样本为负例样本。

   (2)LFM类:主模型部分,包含数据加载、训练、预测的过程。

         这里是目的是构建一个兴趣矩阵, 这个兴趣矩阵的形状为(len(self.user_ids),len(self.item_ids)) 然而这样一个非常大的兴趣矩阵是无法直接保存创建的,所以这里的方法是 利用矩阵分解的方式,使用(len(self.user_ids),假定类目分类数量class_count) * (假定类目分类数量class_count,len(self.user_ids))来代表 通过这样矩阵分解的方式。

        我们要维护的矩阵就可以小的多的多,但是对于以上两个矩阵该怎么确定,矩阵内的参数呢,这个时候就需要使用梯度下降的方式了。 所以这里其实训练的就是上面两个矩阵的参数,利用两个矩阵来计算还原 用户对商品的兴趣度。 而对矩阵参数训练的方式则是使用梯度下降的方式,具体训练的样本来说的话,可以主要看下Corpus函数,这里是采取与用户产生过交互的样本 为正例样本,以整个样本集中没有产生过的样本为负例样本。 这意思就是 对应相当于标识看过的为兴趣度高 1,没看过的兴趣度低 0。

        具体应用可以参照下_predict 函数,是找潜在的用户还没看过的电影里面,逐一根据特征 利用得到q和p矩阵,代入计算得到兴趣值。 针对每个用户,利用兴趣值进行排序, 找到前n个作为推荐吧。

   (3)主训练训练启动部分,这里使用的数据集此处下载 https://pan.baidu.com/s/1RXUuqSqkCJNp4fY5ZdPqKA  提取码:jya7 
 

    注:训练学习到一个整体的兴趣矩阵,利用已知的交互情况作为训练集,让模型学习到对未知的情况的兴趣捕获, 但是这里有个问题是只基于位置感知,而没有特征作为支撑的话,这样的兴趣预测能准吗?

代码

  Corpus类

# coding: utf-8 -*-
import random
import pickle
import pandas as pd
import numpy as np
from math import exp


class Corpus:
    '''
      这个类是用来干嘛的呢?

      看明白了,这相当于是一个语料库吧,用来存储  训练用的样本的,  产生过交互的为正样本,   以没有在当前用户产生交互的样本为负例样本


      set([3,4])^set([2,3,4]) =  {2}  (看明白 ^的作用)

    '''
    items_dict_path = '../data/lfm_items.dict'

    @classmethod
    def pre_process(cls):
        file_path = '../data/ratings.csv'
        cls.frame = pd.read_csv(file_path)
        cls.user_ids = set(cls.frame['UserID'].values)
        cls.item_ids = set(cls.frame['MovieID'].values)
        cls.items_dict = {user_id: cls._get_pos_neg_item(user_id) for user_id in list(cls.user_ids)}
        cls.save()

    @classmethod
    def _get_pos_neg_item(cls, user_id):
        """
        Define the pos and neg item for user.
        pos_item mean items that user have rating, and neg_item can be items
        that user never see before.
        Simple down sample method to solve unbalance sample.
        """
        print('Process: {}'.format(user_id))
        pos_item_ids = set(cls.frame[cls.frame['UserID'] == user_id]['MovieID'])
        neg_item_ids = cls.item_ids ^ pos_item_ids
        # neg_item_ids = [(item_id, len(self.frame[self.frame['MovieID'] == item_id]['UserID'])) for item_id in neg_item_ids]
        # neg_item_ids = sorted(neg_item_ids, key=lambda x: x[1], reverse=True)
        neg_item_ids = list(neg_item_ids)[:len(pos_item_ids)]
        item_dict = {}
        for item in pos_item_ids: item_dict[item] = 1
        for item in neg_item_ids: item_dict[item] = 0
        return item_dict

    @classmethod
    def save(cls):
        f = open(cls.items_dict_path, 'wb')
        pickle.dump(cls.items_dict, f)
        f.close()

    @classmethod
    def load(cls):
        f = open(cls.items_dict_path, 'rb')
        items_dict = pickle.load(f)
        f.close()
        return items_dict

LFM类

class LFM:
    '''

         好吧,  看了一遍代码,算是完全的明白了,这里是目的是构建一个兴趣矩阵, 这个兴趣矩阵的形状为(len(self.user_ids),len(self.item_ids))

         然而这样一个非常大的兴趣矩阵是无法直接保存创建的,所以这里的方法是 利用矩阵分解的方式,使用(len(self.user_ids),假定类目分类数量class_count) * (假定类目分类数量class_count,len(self.user_ids))来代表
         通过这样矩阵分解的方式,我们要维护的矩阵就可以小的多的多,但是对于以上两个矩阵该怎么确定,矩阵内的参数呢,这个时候就需要使用梯度下降的方式了。

         所以这里其实训练的就是上面两个矩阵的参数,利用两个矩阵来计算还原 用户对商品的兴趣度。

         而对矩阵参数训练的方式则是使用梯度下降的方式,具体训练的样本来说的话,可以主要看下Corpus函数,这里是采取与用户产生过交互的样本
         为正例样本,以整个样本集中没有产生过的样本为负例样本。   这意思就是 对应相当于标识看过的为兴趣度高 1,没看过的兴趣度低 0。

         具体应用可以参照下_predict  函数,是找潜在的用户还没看过的电影里面,逐一根据特征 利用得到q和p矩阵,代入计算得到兴趣值。

         针对每个用户,利用兴趣值进行排序, 找到前n个作为推荐吧。



        *  明白了吧,真的是一看代码就完全理解了, 这里目的就是得到用户 对 商品大的 兴趣值矩阵,  只不过是使用假定的类目数 class_num下的
        p和q矩阵来模拟计算, 通过计算最大兴趣度的  矩阵进行推荐即可。           这里的训练是训练矩阵参数,样本是按照交互记录进行的假定吧(是个二分类,用其他项计算的是概率)。
    '''

    def __init__(self):
        self.class_count = 5
        self.iter_count = 5
        self.lr = 0.02
        self.lam = 0.01
        self._init_model()

    def _init_model(self):
        """
        Get corpus and initialize model params.
        """
        file_path = '../data/ratings.csv'
        self.frame = pd.read_csv(file_path)
        self.user_ids = set(self.frame['UserID'].values)
        self.item_ids = set(self.frame['MovieID'].values)
        self.items_dict = Corpus.load()

        array_p = np.random.randn(len(self.user_ids), self.class_count)
        array_q = np.random.randn(len(self.item_ids), self.class_count)
        self.p = pd.DataFrame(array_p, columns=range(0, self.class_count), index=list(self.user_ids))
        self.q = pd.DataFrame(array_q, columns=range(0, self.class_count), index=list(self.item_ids))

    def _predict(self, user_id, item_id):
        """
        Calculate interest between user_id and item_id.
        p is the look-up-table for user's interest of each class.
        q means the probability of each item being classified as each class.
        """
        p = np.mat(self.p.ix[user_id].values)
        q = np.mat(self.q.ix[item_id].values).T
        r = (p * q).sum()
        logit = 1.0 / (1 + exp(-r))
        return logit

    def _loss(self, user_id, item_id, y, step):
        """
        Loss Function define as MSE, the code write here not that formula you think.
        """
        e = y - self._predict(user_id, item_id)
        print('Step: {}, user_id: {}, item_id: {}, y: {}, loss: {}'.
              format(step, user_id, item_id, y, e))
        return e

    def _optimize(self, user_id, item_id, e):
        """
        Use SGD as optimizer, with L2 p, q square regular.
        e.g: E = 1/2 * (y - predict)^2, predict = matrix_p * matrix_q
             derivation(E, p) = -matrix_q*(y - predict), derivation(E, q) = -matrix_p*(y - predict),
             derivation(l2_square,p) = lam * p, derivation(l2_square, q) = lam * q
             delta_p = lr * (derivation(E, p) + derivation(l2_square,p))
             delta_q = lr * (derivation(E, q) + derivation(l2_square, q))
        """
        gradient_p = -e * self.q.ix[item_id].values
        l2_p = self.lam * self.p.ix[user_id].values
        delta_p = self.lr * (gradient_p + l2_p)

        gradient_q = -e * self.p.ix[user_id].values
        l2_q = self.lam * self.q.ix[item_id].values
        delta_q = self.lr * (gradient_q + l2_q)

        self.p.loc[user_id] -= delta_p
        self.q.loc[item_id] -= delta_q

    def train(self):
        """
        Train model.
        """
        for step in range(0, self.iter_count):
            for user_id, item_dict in self.items_dict.items():
                item_ids = list(item_dict.keys())
                random.shuffle(item_ids)
                for item_id in item_ids:
                    e = self._loss(user_id, item_id, item_dict[item_id], step)
                    self._optimize(user_id, item_id, e)
            self.lr *= 0.9
        self.save()

    def predict(self, user_id, top_n=10):
        """
        Calculate all item user have not meet before and return the top n interest items.
        """
        self.load()
        user_item_ids = set(self.frame[self.frame['UserID'] == user_id]['MovieID'])
        other_item_ids = self.item_ids ^ user_item_ids
        interest_list = [self._predict(user_id, item_id) for item_id in other_item_ids]
        candidates = sorted(zip(list(other_item_ids), interest_list), key=lambda x: x[1], reverse=True)
        return candidates[:top_n]

    def save(self):
        """
        Save model params.
        """
        f = open('../data/lfm.model', 'wb')
        pickle.dump((self.p, self.q), f)
        f.close()

    def load(self):
        """
        Load model params.
        """
        f = open('../data/lfm.model', 'rb')
        self.p, self.q = pickle.load(f)
        f.close()

主函数部分

# -*- coding: utf-8 -*-
import time
import os
from model.lfm import LFM, Corpus

print('Start..')
start = time.time()
if not os.path.exists('../data/lfm_items.dict'):
    Corpus.pre_process()
if not os.path.exists('../data/lfm.model'):
    LFM().train()
movies = LFM().predict(user_id=1)
for movie in movies:
    print(movie)
print('Cost time: %f' % (time.time() - start))

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值