數據挖掘競賽 - 猜你喜歡

datacastle上的一道推薦算法競賽(這裏是地址和數據),由於最近想整理和彙總最常用的推薦算法。因此乾脆就把這個競賽拿出來實戰一下。


1. 賽題 & 數據解析

本次比賽是一個名副其實的推薦算法大賽,在本次比賽中,我們提供了一個商品網站中大約16萬名用戶在四年內對商品的評分數據,每條評分記錄都有時間戳(隱匿了具體時間,只保證順序不變)。評分分爲5級,1分最低,5分最高。

這是訓練集,其中四列分別代表用戶編號,物品編號,評分,時間。



這是測試集,只有兩列用戶編號,物品編號,需要預測分數:



2. 基於人工規則模型

基於人工規則的模型可以設計出很多,其特點是簡單,魯棒:

# -*- coding: utf-8 -*-
""" 基於各種指標對用戶, 物品分類, 然後取均值進行預測

1. 基於用戶對物品評分的均值來分類, 然後預測;
    1.1 根據用戶對train物品的評分來分類train, test中的用戶;
    1.2 由於對用戶進行了分類, 那麼對於同一種物品, 在不同的簇下得分就不同;
    1.3 從而就可以確定[物品, 簇]的得分;

"""
import numpy as np
import pandas as pd
import data

train = data.load_data("train")
test = data.load_data("test")

# 求平均分
score_mean = train.groupby(["uid"], as_index=False)["score"].agg({"score_mean": "mean"})

score_mean["cluster"] = np.array(score_mean["score_mean"] * 2, np.int)
score_mean = score_mean[["uid", "cluster"]]

# 基於用戶, 對train, test添加cluster列
train = pd.merge(train, score_mean, on="uid", how="left")
test = pd.merge(test, score_mean, on="uid", how="left")

# 基於cluster對物品打分
item_mean_score = train.groupby(["iid", "cluster"], as_index=False).mean()
result = pd.merge(test, item_mean_score[["iid", "cluster", "score"]], on=["iid", "cluster"], how="left")
result = result.fillna(3)

# 保存結果
data.save_data(result, "result_rule.csv")

3. 基於協同過濾模型

主要是基於物品的協同過濾算法,這個算法複雜度比較高,需要計算的東西很多。因此一般實際應用都是離線計算。主要注意兩點:

1. 多使用字典,利於後期查詢;

2. 對於兩件不同的物品,可能對某個人而言,只評價過一種物品,另一種物品爲空。此時計算距離,認爲他們之間的距離爲0,而不是兩者之差。

這裏給出一個之前實現的協同過濾推薦算法:http://blog.csdn.net/zk_j1994/article/details/77062091

# -*- coding: utf-8 -*-
""" 基於物品的協同過濾算法, 使用SVD分解對人進行降維
1. 有一個細節, 計算相似度時, 一個位置爲空, 另一個位置有評價分數, 此時相似度視爲0;

2. 算法複雜度很高, 需要離線計算;

3. 這裏使用一個小數據集來進行示例;

4. 之所以使用SVD來對樣本降維, 是由於推薦系統矩陣的稀疏性導致的, 事實上使用SVD不但可以減少
    計算量, 還能提高推薦性能;

"""
import numpy as np

def load_data():
    data = [[0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 5],
            [0, 0, 0, 3, 0, 4, 0, 0, 0, 0, 3],
            [0, 0, 0, 0, 4, 0, 0, 1, 0, 4, 0],
            [3, 3, 4, 0, 0, 0, 0, 2, 2, 0, 0],
            [5, 4, 5, 0, 0, 0, 0, 5, 5, 0, 0],
            [0, 0, 0, 0, 5, 0, 1, 0, 0, 5, 0],
            [4, 3, 4, 0, 0, 0, 0, 5, 5, 0, 1],
            [0, 0, 0, 4, 0, 4, 0, 0, 0, 0, 4],
            [0, 0, 0, 2, 0, 2, 5, 0, 0, 1, 2],
            [0, 0, 0, 0, 5, 0, 0, 0, 0, 4, 0],
            [1, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0]]
    return np.array(data)

if __name__ == "__main__":
    data = load_data()
    
    u, sigma, v = np.linalg.svd(data)
    
    sigma_4 = np.mat(np.eye(6) * sigma[0:6])
    
    # 約簡後的矩陣
    simpler_data = np.dot(u[0:6, :], data)
                    
    """
    由於行被壓縮, 因此計算物品相似度的時候, 計算複雜度大大降低了;
    
    同時, 矩陣越稀疏, 這種先SVD降維, 再計算, 效果就越好
    
    """

4. 基於矩陣分解的模型

使用矩陣分解來對原矩陣進行恢復,但是這個數據量稍微大了點(用戶 - 物品矩陣就有9G),我的本本跑不動。這裏是一個例子:

# -*- coding: utf-8 -*-  
""" 
基於矩陣分解的推薦算法 
 
1. 使用梯度下降進行迭代更新; 
 
"""  
import numpy as np  
import matplotlib.pyplot as plt  
  
np.random.seed(1)  
  
def load_data():  
    data = [[0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 5],  
            [0, 0, 0, 3, 0, 4, 0, 0, 0, 0, 3],  
            [0, 0, 0, 0, 4, 0, 0, 1, 0, 4, 0],  
            [3, 3, 4, 0, 0, 0, 0, 2, 2, 0, 0],  
            [5, 4, 5, 0, 0, 0, 0, 5, 5, 0, 0],  
            [0, 0, 0, 0, 5, 0, 1, 0, 0, 5, 0],  
            [4, 3, 4, 0, 0, 0, 0, 5, 5, 0, 1],  
            [0, 0, 0, 4, 0, 4, 0, 0, 0, 0, 4],  
            [0, 0, 0, 2, 0, 2, 5, 0, 0, 1, 2],  
            [0, 0, 0, 0, 5, 0, 0, 0, 0, 4, 0],  
            [1, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0]]  
    return np.array(data)  
  
def gradAscent(data, K, max_iter, alpha, beta):  
    """ 梯度下降更新P, Q矩陣的元素, 使均方誤差最小 """  
    if not isinstance(data, np.matrix):  
        data = np.mat(data)  
          
    # 初始化P, Q矩陣  
    n, m = data.shape  
    P = np.mat(np.random.random((n, K)))  
    Q = np.mat(np.random.random((K, m)))  
    print("\nP = \n{0}".format(P))  
    print("\nQ = \n{0}".format(Q))  
      
    loss_list = []  
    _iter = 0  
    while _iter < max_iter:  
        # 更新P, Q中的每一個元素  
        for i in range(n):  
            for j in range(m):  
                if data[i, j] > 0:  
                    error = (data[i, j] - P[i, :] * Q[:, j])[0, 0]     # (i, j)處的誤差  
                    for k in range(K):  
                        P[i, k] = P[i, k] + alpha * (2 * error * Q[k, j] - beta * P[i, k])  
                        Q[k, j] = Q[k, j] + alpha * (2 * error * P[i, k] - beta * Q[k, j])  
          
        # 計算原矩陣和恢復矩陣之間的誤差  
        loss = 0  
        for i in range(n):  
            for j in range(m):  
                if data[i, j]> 0:  
                    for k in range(K):  
                        loss += P[i, k] * Q[k, j]  
                    loss = np.sum(abs(data[i, j] - loss))  
          
        loss_list.append(loss)  
        if loss <= 1e-3:  
            break  
        _iter += 1  
    return P, Q, loss_list  
  
def draw_loss(loss):  
    plt.plot(range(len(loss)), loss)  
    plt.show()  
          
if __name__ == "__main__":  
    data = load_data()  
      
    P, Q, loss = gradAscent(data, 5, 20000, 0.0002, 0.02)  
      
    print("\n恢復矩陣 = \n{0}".format(P * Q))  
    draw_loss(loss)  


參考文獻

http://blog.csdn.net/nihaoxiaocui/article/details/51974194

http://blog.csdn.net/datacastle/article/details/52190423


發佈了139 篇原創文章 · 獲贊 141 · 訪問量 45萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章