基於Python和Tensorflow的電影推薦演算法

Kervin_Chan發表於2018-05-16

第一步:收集和清洗資料

資料連結:https://grouplens.org/datasets/movielens/

下載檔案:ml-latest-small

基於Python和Tensorflow的電影推薦演算法

import pandas as pd
import numpy as np
import tensorflow as tf
複製程式碼

匯入ratings.csv檔案

ratings_df = pd.read_csv('./ml-latest-small/ratings.csv')
ratings_df.tail()
#tail命令用於輸入檔案中的尾部內容。tail命令預設在螢幕上顯示指定檔案的末尾5行。
複製程式碼

結果:

userId movieId rating timestamp
99999 671 6268 2.5 1065579370
100000 671 6269 4.0 1065149201
100001 671 6365 4.0 1070940363
100002 671 6385 2.5 1070979663
100003 671 6565 3.5 1074784724

匯入movies.csv檔案

movies_df = pd.read_csv('./ml-latest-small/movies.csv')
movies_df.tail()
複製程式碼

結果:

movieId title genres
9120 162672 Mohenjo Daro (2016) Adventure|Drama|Romance
9121 163056 Shin Godzilla (2016) Action|Adventure|Fantasy|Sci-Fi
9122 163949 The Beatles: Eight Days a Week - The Touring Y... Documentary
9123 164977 The Gay Desperado (1936) Comedy
9124 164979 Women of '69, Unboxed Documentary

將movies_df中的movieId替換為行號

movies_df['movieRow'] = movies_df.index
#生成一列‘movieRow’,等於索引值index
movies_df.tail()
複製程式碼

結果:

movieId title genres movieRow
9120 162672 Mohenjo Daro (2016) Adventure|Drama|Romance 9120
9121 163056 Shin Godzilla (2016) Action|Adventure|Fantasy|Sci-Fi 9121
9122 163949 The Beatles: Eight Days a Week - The Touring Y... Documentary 9122
9123 164977 The Gay Desperado (1936) Comedy 9123
9124 164979 Women of '69, Unboxed Documentary 9124

篩選movies_df中的特徵

movies_df = movies_df[['movieRow','movieId','title']]
#篩選三列出來
movies_df.to_csv('./ml-latest-small/moviesProcessed.csv', index=False, header=True, encoding='utf-8')
#生成一個新的檔案moviesProcessed.csv
movies_df.tail()
複製程式碼

結果:

movieRow movieId title
9120 9120 162672 Mohenjo Daro (2016)
9121 9121 163056 Shin Godzilla (2016)
9122 9122 163949 The Beatles: Eight Days a Week - The Touring Y...
9123 9123 164977 The Gay Desperado (1936)
9124 9124 164979 Women of '69, Unboxed

根據movieId,合併rating_df和movie_df

ratings_df = pd.merge(ratings_df, movies_df, on='movieId')
ratings_df.head()
複製程式碼

結果:

userId movieId rating timestamp movieRow title
0 1 31 2.5 1260759144 30 Dangerous Minds (1995)
1 7 31 3.0 851868750 30 Dangerous Minds (1995)
2 31 31 4.0 1273541953 30 Dangerous Minds (1995)
3 32 31 4.0 834828440 30 Dangerous Minds (1995)
4 36 31 3.0 847057202 30 Dangerous Minds (1995)

篩選ratings_df中的特徵

ratings_df = ratings_df[['userId','movieRow','rating']]
#篩選出三列
ratings_df.to_csv('./ml-latest-small/ratingsProcessed.csv', index=False, header=True, encoding='utf-8')
#匯出一個新的檔案ratingsProcessed.csv
ratings_df.head()
複製程式碼

結果:

userId movieRow rating
0 1 30 2.5
1 7 30 3.0
2 31 30 4.0
3 32 30 4.0
4 36 30 3.0

第二步:建立電影評分矩陣rating和評分紀錄矩陣record

userNo = ratings_df['userId'].max() + 1
#userNo的最大值
movieNo = ratings_df['movieRow'].max() + 1
#movieNo的最大值
複製程式碼
rating = np.zeros((movieNo,userNo))
#建立一個值都是0的資料
flag = 0
ratings_df_length = np.shape(ratings_df)[0]
#檢視矩陣ratings_df的第一維度是多少
for index,row in ratings_df.iterrows():
    #interrows(),對錶格ratings_df進行遍歷
    rating[int(row['movieRow']),int(row['userId'])] = row['rating']
    #將ratings_df表裡的'movieRow'和'userId'列,填上row的‘評分’
    flag += 1
複製程式碼
record = rating > 0
record
record = np.array(record, dtype = int)
#更改資料型別,0表示使用者沒有對電影評分,1表示使用者已經對電影評分
record
複製程式碼

結果:

array([[0, 0, 0, ..., 0, 1, 1],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       ...,
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0]])
複製程式碼

第三步:構建模型

def normalizeRatings(rating, record):
    m, n =rating.shape
    #m代表電影數量,n代表使用者數量
    rating_mean = np.zeros((m,1))
    #每部電影的平均得分
    rating_norm = np.zeros((m,n))
    #處理過的評分
    for i in range(m):
        idx = record[i,:] !=0
        #每部電影的評分,[i,:]表示每一行的所有列
        rating_mean[i] = np.mean(rating[i,idx])
        #第i行,評過份idx的使用者的平均得分;
        #np.mean() 對所有元素求均值
        rating_norm[i,idx] -= rating_mean[i]
        #rating_norm = 原始得分-平均得分
    return rating_norm, rating_mean
複製程式碼
rating_norm, rating_mean = normalizeRatings(rating, record)
複製程式碼

結果:

/root/anaconda2/envs/python3/lib/python3.6/site-packages/numpy/core/fromnumeric.py:2957: RuntimeWarning: Mean of empty slice.
  out=out, **kwargs)
/root/anaconda2/envs/python3/lib/python3.6/site-packages/numpy/core/_methods.py:80: RuntimeWarning: invalid value encountered in double_scalars
  ret = ret.dtype.type(ret / rcount)
複製程式碼

注:如果資料出現較多的NaNN,對後面的運算影響較大

rating_norm =np.nan_to_num(rating_norm)
#對值為NaNN進行處理,改成數值0
rating_norm
複製程式碼

結果:

array([[ 0.        ,  0.        ,  0.        , ...,  0.        ,
        -3.87246964, -3.87246964],
       [ 0.        ,  0.        ,  0.        , ...,  0.        ,
         0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.        , ...,  0.        ,
         0.        ,  0.        ],
       ...,
       [ 0.        ,  0.        ,  0.        , ...,  0.        ,
         0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.        , ...,  0.        ,
         0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.        , ...,  0.        ,
         0.        ,  0.        ]])
複製程式碼

rating_mean =np.nan_to_num(rating_mean)
#對值為NaNN進行處理,改成數值0
rating_mean
複製程式碼

結果:

array([[3.87246964],
       [3.40186916],
       [3.16101695],
       ...,
       [3.        ],
       [0.        ],
       [5.        ]])
複製程式碼

構建模型

num_features = 10
X_parameters = tf.Variable(tf.random_normal([movieNo, num_features],stddev = 0.35))
Theta_parameters = tf.Variable(tf.random_normal([userNo, num_features],stddev = 0.35))
#tf.Variables()初始化變數
#tf.random_normal()函式用於從服從指定正太分佈的數值中取出指定個數的值,mean: 正態分佈的均值。stddev: 正態分佈的標準差。dtype: 輸出的型別
複製程式碼
loss = 1/2 * tf.reduce_sum(((tf.matmul(X_parameters, Theta_parameters, transpose_b = True) - rating_norm) * record) ** 2) + 1/2 * (tf.reduce_sum(X_parameters ** 2) + tf.reduce_sum(Theta_parameters ** 2))
#基於內容的推薦演算法模型
複製程式碼

基於Python和Tensorflow的電影推薦演算法

# 函式解釋:
# reduce_sum() 就是求和,reduce_sum( input_tensor, axis=None,  keep_dims=False, name=None, reduction_indices=None)
# reduce_sum() 引數解釋:
# 1) input_tensor:輸入的張量。
# 2) axis:沿著哪個維度求和。對於二維的input_tensor張量,0表示按列求和,1表示按行求和,[0, 1]表示先按列求和再按行求和。
# 3) keep_dims:預設值為Flase,表示預設要降維。若設為True,則不降維。
# 4) name:名字。
# 5) reduction_indices:預設值是None,即把input_tensor降到 0維,也就是一個數。對於2維input_tensor,reduction_indices=0時,按列;reduction_indices=1時,按行。
# 6) 注意,reduction_indices與axis不能同時設定。

# tf.matmul(a,b),將矩陣 a 乘以矩陣 b,生成a * b
# tf.matmul(a,b)引數解釋:
# 1) a:型別為 float16,float32,float64,int32,complex64,complex128 和 rank > 1的張量。
# 2) b:與 a 具有相同型別和 rank。
# 3) transpose_a:如果 True,a 在乘法之前轉置。
# 4) transpose_b:如果 True,b 在乘法之前轉置。
# 5) adjoint_a:如果 True,a 在乘法之前共軛和轉置。
# 6) adjoint_b:如果 True,b 在乘法之前共軛和轉置。
# 7) a_is_sparse:如果 True,a 被視為稀疏矩陣。
# 8) b_is_sparse:如果 True,b 被視為稀疏矩陣。
# 9) name:操作名稱(可選)
複製程式碼

優化演算法

optimizer = tf.train.AdamOptimizer(1e-4)
# https://blog.csdn.net/lenbow/article/details/52218551
train = optimizer.minimize(loss)
# Optimizer.minimize對一個損失變數基本上做兩件事
# 它計算相對於模型引數的損失梯度。
# 然後應用計算出的梯度來更新變數。
複製程式碼

第四步:訓練模型

# tf.summary的用法 https://www.cnblogs.com/lyc-seu/p/8647792.html
tf.summary.scalar('loss',loss)
#用來顯示標量資訊
複製程式碼

結果: <tf.Tensor 'loss_1:0' shape=() dtype=string>

summaryMerged = tf.summary.merge_all()
#merge_all 可以將所有summary全部儲存到磁碟,以便tensorboard顯示。
filename = './movie_tensorborad'
writer = tf.summary.FileWriter(filename)
#指定一個檔案用來儲存圖。
sess = tf.Session()
#https://www.cnblogs.com/wuzhitj/p/6648610.html
init = tf.global_variables_initializer()
sess.run(init)
#執行
複製程式碼
for i in range(5000):
    _, movie_summary = sess.run([train, summaryMerged])
    # 把訓練的結果summaryMerged存在movie裡
    writer.add_summary(movie_summary, i)
    # 把訓練的結果儲存下來
複製程式碼

檢視訓練結果: 在終端輸入 tensorboard --logir=./

基於Python和Tensorflow的電影推薦演算法

基於Python和Tensorflow的電影推薦演算法

第五步:評估模型

Current_X_parameters, Current_Theta_parameters = sess.run([X_parameters, Theta_parameters])
# Current_X_parameters為使用者內容矩陣,Current_Theta_parameters使用者喜好矩陣
predicts = np.dot(Current_X_parameters,Current_Theta_parameters.T) + rating_mean
# dot函式是np中的矩陣乘法,np.dot(x,y) 等價於 x.dot(y)
errors = np.sqrt(np.sum((predicts - rating)**2))
# sqrt(arr) ,計算各元素的平方根
errors
複製程式碼

結果:

4037.9002717628305
複製程式碼

第六步:構建完整的電影推薦系統

user_id = input('您要想哪位使用者進行推薦?請輸入使用者編號:')
sortedResult = predicts[:, int(user_id)].argsort()[::-1]
# argsort()函式返回的是陣列值從小到大的索引值; argsort()[::-1] 返回的是陣列值從大到小的索引值
idx = 0
print('為該使用者推薦的評分最高的20部電影是:'.center(80,'='))
# center() 返回一個原字串居中,並使用空格填充至長度 width 的新字串。預設填充字元為空格。
for i in sortedResult:
    print('評分: %.2f, 電影名: %s' % (predicts[i,int(user_id)],movies_df.iloc[i]['title']))
    # .iloc的用法:https://www.cnblogs.com/harvey888/p/6006200.html
    idx += 1
    if idx == 20:break
複製程式碼

結果:

您要想哪位使用者進行推薦?請輸入使用者編號:123
==============================為該使用者推薦的評分最高的20部電影是:===============================
評分: 5.03, 電影名: Fireworks Wednesday (Chaharshanbe-soori) (2006)
評分: 4.88, 電影名: Woman on the Beach (Haebyeonui yeoin) (2006)
評分: 4.73, 電影名: Mummy's Ghost, The (1944)
評分: 4.66, 電影名: Maborosi (Maboroshi no hikari) (1995)
評分: 4.63, 電影名: Boiling Point (1993)
評分: 4.60, 電影名: Mala Noche (1985)
評分: 4.49, 電影名: All-Star Superman (2011)
評分: 4.47, 電影名: Bill Hicks: Relentless (1992)
評分: 4.45, 電影名: Something Borrowed (2011)
評分: 4.37, 電影名: Box of Moon Light (1996)
評分: 4.37, 電影名: Kwaidan (Kaidan) (1964)
評分: 4.35, 電影名: Sacrifice, The (Offret - Sacraficatio) (1986)
評分: 4.29, 電影名: Hotel de Love (1996)
評分: 4.27, 電影名: Aria (1987)
評分: 4.23, 電影名: Querelle (1982)
評分: 4.22, 電影名: Rocky VI (1986) 
評分: 4.21, 電影名: Little Lord Fauntleroy (1936)
評分: 4.19, 電影名: Hardcore (1979)
評分: 4.16, 電影名: Three of Hearts (1993)
評分: 4.15, 電影名: White Stripes Under Great White Northern Lights, The (2009)
複製程式碼

本文章內容來自慕課網,僅供學習,如有侵權,聯絡刪除

相關文章