对于某个用户,首先得到他的兴趣分类,然后从分类中挑选他可能喜欢的物品。总结一下,这个基于兴趣分类的方法大概需要解决3个问题。

  1. 如何给物品进行分类?
  2. 如何确定用户对哪些类的物品感兴趣,以及感兴趣的程度?
  3. 对于一个给定的类。选择哪些属于这个类的物品推荐给用户,以及如何确定这些物品在一个类中的权重?

隐含语义分析技术采用基于用户行为统计的自动聚类,较好地解决了上面提出的问题。

隐含语义分析技术的分类来自对用户行为的统计,代表了用户对物品分类的看法。隐含语义分析技术和ItemCF在物品分类方面的思想类似,如果两个物品被很多用户同时喜欢,那么这两个物品就很有可能属于同一个类。隐含语义分析技术允许指定最终有多少个分类,这个数字越大,分类的粒度就会越细,反之分类粒度就会越粗。隐含语义分析技术会计算出物品属于每个类的权重,因此每个物品都不是硬性地被分到某一个类中。隐含语义分析技术给出的每个分类都不是同一个维度的,它是基于用户的共同兴趣计算出来的,如果用户的共同兴趣是某一个维度,那么LFM给出的类也是相同的维度。隐含语义分析技术可以通过统计用户行为决定物品在每个类中的权重,如果喜欢某个类的用户都会喜欢某个物品,那么这个物品在这个类中的权重就可能比较高。

LFM通过如下公式计算用户u物品i的兴趣:

                                                                                                         preference(u,i)=r_{ui}=p_u^Tq_i=\sum_{f=1}^Fp_{u,k}q_{i,k}

这个公式中$p_{u,k}$$q_{i,k}$是模型的参数,其中$p_{u,k}$度量了用户$u$的兴趣和第$k$个隐类的关系,而$q_{i,k}$度量了第$k$个隐类和物品$i$之间的关系。那么下面的问题就是如何计算这两个参数。

要计算这两个参数,需要一个训练集,对于每个用户$u$,训练集里都包含了用户$u$喜欢的物品和不感兴趣的物品,通过学习这个数据集,就可以获得上面的模型参数。

推荐系统的用户行为分为显性反馈和隐性反馈。LFM在显性反馈数据(也就是评分数据)上解决评分预测问题并达到了很好的精度。这里主要讨论的是隐性反馈数据集,这种数据集的特点是只有正样本(用户喜欢什么物品),而没有负样本(用户对什么物品不感兴趣)。

对负样本采样时应该遵循以下原则:

  • 对每个用户,要保证正负样本的平衡(数目相似)。
  • 对每个用户采样负样本时,要选取那些很热门,而用户却没有行为的物品。

 

reader.py  数据准备

import os


def get_user_click(rating_file):
    '''获取用户的点击'''
    if not os.path.exists(rating_file):
        return {}
    fp = open(rating_file, 'r', encoding='UTF-8')
    num = 0
    user_click = {}
    for line in fp:
        if num == 0:
            num += 1
            continue
        item = str(line).strip().split(',')
        if len(item) < 4:
            continue
        [userid, itemid, rating, tiemstamp] = item
        if float(rating) < 3.0:
            continue
        if userid not in user_click:
            user_click[userid] = []
        user_click[userid].append(itemid)

    fp.close()
    return user_click


def get_item_info(item_file):
    '''获取电影详情'''
    if not os.path.exists(item_file):
        return {}
    num = 0
    item_info = {}
    fp = open(item_file, 'r', encoding='UTF-8')
    for line in fp:
        if num == 0:
            num += 1
            continue
        item = line.strip().split(',')
        if len(item) < 3:
            continue
        if len(item) == 3:
            [itemid, title, genres] = item
        elif len(item) > 3:
            itemid = item[0]
            title = ','.join(item[1:-1])
            genres = item[-1]
        if itemid not in item_info:
            item_info[itemid] = [title, genres]
    fp.close()
    return item_info


def get_ave_score(file_name):
    '''获取电影的平均得分'''
    if not os.path.exists(file_name):
        return {}
    fp = open(file_name, 'r', encoding='UTF-8')
    num = 0
    recode_dict = {}
    score_dict = {}
    for line in fp:
        if num == 0:
            num += 1
            continue
        item = str(line).strip().split(',')
        if len(item) < 4:
            continue
        [userid, itemid, rating, tiemstamp] = item
        if itemid not in recode_dict:
            recode_dict[itemid] = [0, 0]
        recode_dict[itemid][0] += 1
        recode_dict[itemid][1] += float(rating)
    fp.close()
    for itemid in recode_dict:
        score_dict[itemid] = round(recode_dict[itemid][1] / recode_dict[itemid][0], 3)
    return score_dict


def get_train_data(file_name):
    '''用户对item的评分文件'''
    if not os.path.exists(file_name):
        return {}
    score_dict = get_ave_score('..\\data\\ratings.txt')
    fp = open(file_name, 'r', encoding='UTF-8')
    num = 0
    pos_dict = {}
    neg_dict = {}
    train_data = []
    for line in fp:
        if num == 0:
            num += 1
            continue
        item = str(line).strip().split(',')
        if len(item) < 4:
            continue
        [userid, itemid, rating, tiemstamp] = item
        if userid not in pos_dict:
            pos_dict[userid] = []
        if userid not in neg_dict:
            neg_dict[userid] = []
        if float(rating) >= 4:
            pos_dict[userid].append((itemid, 1))
        else:
            score = score_dict.get(itemid, 0)
            neg_dict[userid].append((itemid, score))
    fp.close()
    for userid in pos_dict:
        # data_num = min(len(pos_dict[userid]), len(neg_dict[userid]))
        # if data_num > 0:
        #     train_data += [(userid, zuhe[0], zuhe[1]) for zuhe in pos_dict[userid]][:data_num]
        # else:
        #     continue
        # sorted_neg_list = sorted(neg_dict[userid], key=lambda element: element[1], reverse=True)[:data_num]

        train_data += [(userid, zuhe[0], zuhe[1]) for zuhe in pos_dict[userid]]
        sorted_neg_list = sorted(neg_dict[userid], key=lambda element: element[1], reverse=True)

        train_data += [(userid, zuhe[0], 0) for zuhe in sorted_neg_list]

    return train_data


if __name__ == '__main__':
    # user_click = get_user_click('..\\data\\ratings_max.txt')
    # print(len(user_click))
    # print(user_click['1'])
    # item_info = get_item_info('..\\data\\movies.txt')
    # # print(len(item_info))
    # # print(item_info)
    # for i, j in item_info.items():
    #     print(i, j)
    item_info = get_train_data('..\\data\\ratings.txt')
    print(len(item_info))
    print('*' * 100)
    print(item_info[:1000])

latent_factir_mode.py

import numpy as np
from reader import get_train_data, get_ave_score, get_item_info
import operator, time


def lfm_train(train_data, F, alpha, beta, step):
    '''
    train_data:训练的样本
    F: user item 的长度很维度
    alpha:正则化的参数
    beta:模型的学习率
    step:模型迭代的次数
    :return:
    '''

    user_vec = {}
    item_vec = {}
    for step_index in range(step):
        for data_instance in train_data:
            userid, itemid, label = data_instance
            if userid not in user_vec:
                user_vec[userid] = init_model(F)  # init_model 模型初始化
            if itemid not in item_vec:
                item_vec[itemid] = init_model(F)  # init_model 模型初始化

        delta = label - model_predict(user_vec[userid], item_vec[itemid])  # 模型参数迭代
        for index in range(F):
            user_vec[userid][index] += beta * (
                    delta * item_vec[itemid][index] - alpha * user_vec[userid][index])  # user 偏导
            item_vec[itemid][index] += beta * (
                    delta * user_vec[userid][index] - alpha * item_vec[itemid][index])  # item 偏导

        beta = beta * 0.9
    return user_vec, item_vec


def init_model(vetor_len):
    '''参数初始化'''
    return np.random.random(vetor_len)


def model_predict(user_vector, item_vector):
    '''计算模型的预测'''
    res = np.dot(user_vector, item_vector) / (np.linalg.norm(user_vector) * np.linalg.norm(item_vector))
    return res


def give_recom_result(user_vec, item_vec, userid):
    '''等到用户的推荐结果, 并分析推荐结果
    userid: 我们想等到的用户推荐结果的
    '''
    fix_num = 50
    recom_list = []
    if userid not in user_vec:
        return {}
    recode = {}
    user_vactor = user_vec[userid]
    for itemid in item_vec:
        item_vector = item_vec[itemid]
        res = np.dot(user_vactor, item_vector) / (np.linalg.norm(user_vactor) * np.linalg.norm(item_vector))
        recode[itemid] = res
    for zube in sorted(recode.items(), key=operator.itemgetter(1), reverse=True)[:fix_num]:
        itemid = zube[0]
        score = round(zube[1], 3)
        recom_list.append((itemid, score))

    return recom_list


def ana_recom_result(train_data, userid, recome_list):
    '''分析推荐结果的好坏'''
    item_info = get_item_info('..\\data\\movies.txt')
    instance_list = []
    train_list = []
    for data_instance in train_data:
        tmp_userid, itemid, label = data_instance
        if tmp_userid == userid and label == 1:
            # print(item_info[itemid])
            # instance_list.append(itemid)
            instance_list.append(item_info[itemid])
    print('*' * 100)
    for zube in recome_list:
        # print(item_info[zube[0]])
        # train_list.append(zube[0])
        train_list.append(item_info[zube[0]])
    print(instance_list)
    print(train_list)


def model_train_process():
    start_time = time.time()
    train_data = get_train_data('..\\data\\ratings.txt')
    user_vec, item_vec = lfm_train(train_data, 50, 0.01, 0.1, 50)
    print("user_vec:{}".format(user_vec['1']))
    print('*' * 100)
    print("item_vec:{}".format(item_vec['1']))
    print('*' * 100)
    crome_list = give_recom_result(user_vec, item_vec, '24')
    ana_recom_result(train_data, '24', crome_list)
    print(len(crome_list))
    print('*' * 100)
    print(crome_list)
    print(time.time()-start_time)


if __name__ == '__main__':
    model_train_process()