【推荐系统】基于协同过滤的图书推荐系统
推薦系統一直讓我的思緒占據了一段時間,由于我傾向于閱讀書籍,因此探索Book Crossing數據集非常吸引人。
在線推薦系統是許多電子商務網站的事情。推薦系統廣泛地向最適合其口味和特征的顧客推薦產品。有關推薦系統的更多詳細信息,請閱讀我關于推薦系統的介紹性文章以及使用Python的一些插圖。
當我遇到Book Crossing數據集時,我開始構建圖書推薦系統。該數據集由Cai-Nicolas Ziegler于2004年編制,包含三個用戶,書籍和評級表。顯式評級以1-10的等級表示(較高的值表示較高的升值),隱式評級以0表示。
在構建任何機器學習模型之前,了解數據是什么以及我們要實現的目標至關重要。數據探索揭示了隱藏的趨勢和見解,數據預處理使數據可供ML算法使用。
目錄
開始
圖書
用戶數據集
評級數據集
基于簡單流行度的推薦系統
基于協同過濾的推薦系統
基于用戶的協同過濾
基于項目的協同過濾
代碼
?
?
開始
導入支持庫
import pandas as pd import matplotlib.pyplot as plt import sklearn.metrics as metrics import numpy as np from sklearn.neighbors import NearestNeighbors from scipy.spatial.distance import correlation from sklearn.metrics.pairwise import pairwise_distances import ipywidgets as widgets from IPython.display import display, clear_output from contextlib import contextmanager import warnings warnings.filterwarnings('ignore') import numpy as np import os, sys import re import seaborn as sns?
首先,我們加載數據集并檢查書籍,用戶和評級數據集的形狀,如下所示:
books = pd.read_csv('dataset/BX-Books.csv', sep=';', error_bad_lines=False, encoding="latin-1") books.columns = ['ISBN', 'bookTitle', 'bookAuthor', 'yearOfPublication', 'publisher', 'imageUrlS', 'imageUrlM', 'imageUrlL'] users = pd.read_csv('dataset/BX-Users.csv', sep=';', error_bad_lines=False, encoding="latin-1") users.columns = ['userID', 'Location', 'Age'] ratings = pd.read_csv('dataset/BX-Book-Ratings.csv', sep=';', error_bad_lines=False, encoding="latin-1") ratings.columns = ['userID', 'ISBN', 'bookRating'] print (books.shape) print (users.shape) print (ratings.shape) (271360, 8) (278858, 3) (1149780, 3)?
圖書
逐個探索每個數據集并從書籍數據集開始,我們可以看到圖像URL列似乎不需要進行分析,因此可以刪除這些列。
books.head() books.drop(['imageUrlS', 'imageUrlM', 'imageUrlL'],axis=1,inplace=True) books.head()我們現在檢查每個列的數據類型,并更正缺失和不一致的條目。我也在調整列寬以顯示列的全文。
books.dtypes ISBN object bookTitle object bookAuthor object yearOfPublication object publisher object dtype: object?
yearOfPublication
現在我們檢查此屬性的唯一值。
books.yearOfPublication.unique() array([2002, 2001, 1991, 1999, 2000, 1993, 1996, 1988, 2004, 1998, 1994,2003, 1997, 1983, 1979, 1995, 1982, 1985, 1992, 1986, 1978, 1980,1952, 1987, 1990, 1981, 1989, 1984, 0, 1968, 1961, 1958, 1974,1976, 1971, 1977, 1975, 1965, 1941, 1970, 1962, 1973, 1972, 1960,1966, 1920, 1956, 1959, 1953, 1951, 1942, 1963, 1964, 1969, 1954,1950, 1967, 2005, 1957, 1940, 1937, 1955, 1946, 1936, 1930, 2011,1925, 1948, 1943, 1947, 1945, 1923, 2020, 1939, 1926, 1938, 2030,1911, 1904, 1949, 1932, 1928, 1929, 1927, 1931, 1914, 2050, 1934,1910, 1933, 1902, 1924, 1921, 1900, 2038, 2026, 1944, 1917, 1901,2010, 1908, 1906, 1935, 1806, 2021, '2000', '1995', '1999', '2004','2003', '1990', '1994', '1986', '1989', '2002', '1981', '1993','1983', '1982', '1976', '1991', '1977', '1998', '1992', '1996','0', '1997', '2001', '1974', '1968', '1987', '1984', '1988','1963', '1956', '1970', '1985', '1978', '1973', '1980', '1979','1975', '1969', '1961', '1965', '1939', '1958', '1950', '1953','1966', '1971', '1959', '1972', '1955', '1957', '1945', '1960','1967', '1932', '1924', '1964', '2012', '1911', '1927', '1948','1962', '2006', '1952', '1940', '1951', '1931', '1954', '2005','1930', '1941', '1944', 'DK Publishing Inc', '1943', '1938','1900', '1942', '1923', '1920', '1933', 'Gallimard', '1909','1946', '2008', '1378', '2030', '1936', '1947', '2011', '2020','1919', '1949', '1922', '1897', '2024', '1376', '1926', '2037'],dtype=object)yearOfPublication中有一些不正確的條目。
由于csv文件中的一些錯誤,發布商名稱'DK Publishing Inc'和'Gallimard'在數據集中被錯誤地加載為yearOfPublication。
此外,某些值是字符串,并且在某些地方已將相同年份作為數字輸入。
我們將對這些行進行必要的更正,并將yearOfPublication的數據類型設置為int。
books.loc[books.yearOfPublication == 'DK Publishing Inc',:]從上面可以看出,bookAuthor錯誤地裝載了bookTitle,因此需要進行修正。
# ISBN '0789466953' books.loc[books.ISBN == '0789466953','yearOfPublication'] = 2000 books.loc[books.ISBN == '0789466953','bookAuthor'] = "James Buckley" books.loc[books.ISBN == '0789466953','publisher'] = "DK Publishing Inc" books.loc[books.ISBN == '0789466953','bookTitle'] = "DK Readers: Creating the X-Men, How Comic Books Come to Life (Level 4: Proficient Readers)" #ISBN '078946697X' books.loc[books.ISBN == '078946697X','yearOfPublication'] = 2000 books.loc[books.ISBN == '078946697X','bookAuthor'] = "Michael Teitelbaum" books.loc[books.ISBN == '078946697X','publisher'] = "DK Publishing Inc" books.loc[books.ISBN == '078946697X','bookTitle'] = "DK Readers: Creating the X-Men, How It All Began (Level 4: Proficient Readers)" books.loc[(books.ISBN == '0789466953') | (books.ISBN == '078946697X'),:]繼續糾正出版年鑒的類型
books.yearOfPublication=pd.to_numeric(books.yearOfPublication, errors='coerce') sorted(books['yearOfPublication'].unique()) [0, 1376, 1378, 1806, 1897, 1900, 1901, 1902, 1904, 1906, 1908, 1909, 1910, 1911, 1914, 1917, 1919, 1920, 1921, 1922, 1923, 1924, 1925, 1926, 1927, 1928, 1929, 1930, 1931, 1932, 1933, 1934, 1935, 1936, 1937, 1938, 1939, 1940, 1941, 1942, 1943, 1944, 1945, 1946, 1947, 1948, 1949, 1950, 1951, 1952, 1953, 1954, 1955, 1956, 1957, 1958, 1959, 1960, 1961, 1962, 1963, 1964, 1965, 1966, 1967, 1968, 1969, 1970, 1971, 1972, 1973, 1974, 1975, 1976, 1977, 1978, 1979, 1980, 1981, 1982, 1983, 1984, 1985, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2008, 2010, 2011, 2012, 2020, 2021, 2024, 2026, 2030, 2037, 2038, 2050]現在可以看出yearOfPublication的類型為int,其值范圍為0-2050。
由于該數據集建于2004年,我假設2006年之后的所有年份都無效,保留兩年的保證金,以防數據集可能已更新。
對于所有無效條目(包括0),我將這些條目轉換為NaN,然后??用剩余年份的平均值替換它們。
books.loc[(books.yearOfPublication > 2006) | (books.yearOfPublication == 0),'yearOfPublication'] = np.NAN用年出版的平均價值代替NaNs在案例數據集被更新的情況下保留一定的空白
books.yearOfPublication.fillna(round(books.yearOfPublication.mean()), inplace=True) books.yearOfPublication.isnull().sum() 0將dtype重置為int32
books.yearOfPublication = books.yearOfPublication.astype(np.int32)?
publisher
來到“publisher”專欄,我已經處理了兩個NaN值,將其替換為“其他”,因為在進行一些調查后無法推斷出版商名稱(檢查jupyter notebook embed)。
books.loc[books.publisher.isnull(),:]調查有NaNs的行
以“Tyrant Moon”的書名來查看是否能得到任何線索
books.loc[(books.bookTitle == 'Tyrant Moon'),:]檢查行是否有書簽作為查找器,看看我們是否能得到任何線索
與不同的出版商和圖書作者的所有行
books.loc[(books.bookTitle == 'Finders Keepers'),:]由圖書作者檢查以找到模式
都有不同的出版商。這里沒有線索
books.loc[(books.bookAuthor == 'Elaine Corvidae'),:]由圖書作者檢查以找到模式
books.loc[(books.bookAuthor == 'Linnea Sinclair'),:]?
因為沒有什么共同的東西可以推斷出NaNs的發布者,將它們替換為“other”
books.loc[(books.ISBN == '193169656X'),'publisher'] = 'other' books.loc[(books.ISBN == '1931696993'),'publisher'] = 'other'?
用戶數據集
現在我們探索用戶數據集,首先檢查其形狀,前幾列和數據類型。
print (users.shape) users.head() users.dtypes userID int64 Location object Age float64 dtype: objectuserID
users.userID.values array([ 1, 2, 3, ..., 278856, 278857, 278858], dtype=int64)?
Age
檢查唯一值后,userID看起來正確。但是,Age列具有NaN和一些非常高的值。在我看來,5歲以下和90歲以上的年齡沒有多大意義,因此,這些正在被NaN取代。然后將所有NaN替換為Age的平均值,并將其數據類型設置為int。
sorted(users.Age.unique()) [nan, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0, 32.0, 33.0, 34.0, 35.0, 36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0, 43.0, 44.0, 45.0, 46.0, 47.0, 48.0, 49.0, 50.0, 51.0, 52.0, 53.0, 54.0, 55.0, 56.0, 57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 63.0, 64.0, 65.0, 66.0, 67.0, 68.0, 69.0, 70.0, 71.0, 72.0, 73.0, 74.0, 75.0, 76.0, 77.0, 78.0, 79.0, 80.0, 81.0, 82.0, 83.0, 84.0, 85.0, 86.0, 87.0, 88.0, 89.0, 90.0, 91.0, 92.0, 93.0, 94.0, 95.0, 96.0, 97.0, 98.0, 99.0, 100.0, 101.0, 102.0, 103.0, 104.0, 105.0, 106.0, 107.0, 108.0, 109.0, 110.0, 111.0, 113.0, 114.0, 115.0, 116.0, 118.0, 119.0, 123.0, 124.0, 127.0, 128.0, 132.0, 133.0, 136.0, 137.0, 138.0, 140.0, 141.0, 143.0, 146.0, 147.0, 148.0, 151.0, 152.0, 156.0, 157.0, 159.0, 162.0, 168.0, 172.0, 175.0, 183.0, 186.0, 189.0, 199.0, 200.0, 201.0, 204.0, 207.0, 208.0, 209.0, 210.0, 212.0, 219.0, 220.0, 223.0, 226.0, 228.0, 229.0, 230.0, 231.0, 237.0, 239.0, 244.0]年齡欄有一些無效的條目,比如nan,0和非常高的值,比如100和以上
在我看來,低于5和90以上的值對我們的圖書評級案例沒有多大意義。因此,用NaNs替換這些
users.loc[(users.Age > 90) | (users.Age < 5), 'Age'] = np.nan用平均值代替NaN
users.Age = users.Age.fillna(users.Age.mean())將數據類型設置為int
users.Age = users.Age.astype(np.int32) sorted(users.Age.unique()) [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90]我這里沒有對Location列進行任何處理。但是,如果您希望可以進一步將其拆分為城市,州,國家,并使用文本處理模型進行一些處理。
?
評級數據集
我們檢查評級數據集的形狀和前幾行。它揭示了我們的用戶手冊評級矩陣將非常稀疏,因為與評級矩陣的大小(用戶數量×書籍數量)相比,實際評級非常低。
ratings.shape (1149780, 3)如果每個用戶對每個條目進行評級,那么評級數據集將有nusers * nbooks條目,這表明數據集非常稀疏。
n_users = users.shape[0] n_books = books.shape[0] print (n_users * n_books) 75670906880 ratings.head(5) ratings.bookRating.unique() array([ 0, 5, 3, 6, 8, 7, 10, 9, 4, 1, 2], dtype=int64)除非將新書添加到圖書數據集中,否則評級數據集應該只存在于我們的圖書數據集里的書籍。
ratings_new = ratings[ratings.ISBN.isin(books.ISBN)] print (ratings.shape) print (ratings_new.shape) (1149780, 3) (1031136, 3)可以看到,有許多行,有圖書ISBN,而不是書籍數據集的一部分被刪除了
除非新用戶被添加到用戶數據集,否則評級數據集應該有來自用戶數據集的用戶的評級。
ratings = ratings[ratings.userID.isin(users.userID)] print (ratings.shape) print (ratings_new.shape) (1149780, 3) (1031136, 3)沒有新用戶添加,因此我們將使用高于數據集的新用戶(1031136,3)
print ("number of users: " + str(n_users)) print ("number of books: " + str(n_books)) number of users: 278858 number of books: 271360很明顯,用戶已經評價了一些書籍,這些書籍不是原始書籍數據集的一部分。數據集的稀疏度可以如下計算:
sparsity=1.0-len(ratings_new)/float(n_users*n_books) print ('圖書交叉數據集的稀疏級別是 ' + str(sparsity*100) + ' %') 圖書交叉數據集的稀疏級別是 99.99863734155898 %由1-10表示的顯式評級和由0表示的隱含評級現在必須分開。我們將僅使用明確的評級來構建我們的圖書推薦系統。同樣,用戶也被分為明確評級的人和記錄其隱性行為的人。
ratings.bookRating.unique() array([ 0, 5, 3, 6, 8, 7, 10, 9, 4, 1, 2], dtype=int64)因此,對隱式和顯式的評級數據集進行了劃分
ratings_explicit = ratings_new[ratings_new.bookRating != 0] ratings_implicit = ratings_new[ratings_new.bookRating == 0] print (ratings_new.shape) print( ratings_explicit.shape) print (ratings_implicit.shape) (1031136, 3) (383842, 3) (647294, 3)統計
bookRating的計數圖表示更高的評級在用戶中更常見,評級8的評級最高。
sns.countplot(data=ratings_explicit , x='bookRating') plt.show()?
基于簡單流行度的推薦系統
此時,可以基于不同書籍的用戶評級的計數來構建基于簡單流行度的推薦系統。很明顯,?J. K. Rowling撰寫的書很受歡迎。
ratings_count = pd.DataFrame(ratings_explicit.groupby(['ISBN'])['bookRating'].sum()) top10 = ratings_count.sort_values('bookRating', ascending = False).head(10) print ("推薦下列書籍") top10.merge(books, left_index = True, right_on = 'ISBN')類似地隔離那些在1-10中給出明確評分的用戶以及那些隱含行為被跟蹤的用戶
users_exp_ratings = users[users.userID.isin(ratings_explicit.userID)] users_imp_ratings = users[users.userID.isin(ratings_implicit.userID)] print (users.shape) print (users_exp_ratings.shape) print (users_imp_ratings.shape) (278858, 3) (68091, 3) (52451, 3)?
基于協同過濾的推薦系統
為了應對我的機器具有的計算能力并減少數據集大小,我正在考慮已經評定至少100本書籍和至少有100個評級的書籍的用戶。
counts1 = ratings_explicit['userID'].value_counts() ratings_explicit = ratings_explicit[ratings_explicit['userID'].isin(counts1[counts1 >= 100].index)] counts = ratings_explicit['bookRating'].value_counts() ratings_explicit = ratings_explicit[ratings_explicit['bookRating'].isin(counts[counts >= 100].index)]從顯式的評級表生成評級矩陣
構建基于CF的推薦系統的下一個關鍵步驟是從評級表生成用戶項目評級矩陣。
ratings_matrix = ratings_explicit.pivot(index='userID', columns='ISBN', values='bookRating') userID = ratings_matrix.index ISBN = ratings_matrix.columns print(ratings_matrix.shape) ratings_matrix.head()?
n_users = ratings_matrix.shape[0] #只考慮那些給出明確評級的用戶 n_books = ratings_matrix.shape[1] print (n_users, n_books) 449 66574因為NaN不能通過訓練算法來處理,將它們替換為0,這表示沒有評級
設置數據類型
ratings_matrix.fillna(0, inplace = True) ratings_matrix = ratings_matrix.astype(np.int32) ratings_matrix.head(5)復查稀疏
sparsity=1.0-len(ratings_explicit)/float(users_exp_ratings.shape[0]*n_books) print ('圖書交叉數據集的稀疏級別是 ' + str(sparsity*100) + ' %') 圖書交叉數據集的稀疏級別是 99.99772184106935 %?
基于用戶的協同過濾
我將重用我的基于CF的推薦系統示例的功能。函數findksimilarusers輸入userID和rating矩陣并返回k個類似用戶的相似性和索引。(閱讀我之前的故事,了解基于用戶/項目的CF方法的概念和公式)
這個函數找到k個相似的用戶,給定用戶id和評級矩陣
這些相似點是通過使用配對距離獲得的
def findksimilarusers(user_id, ratings, metric = metric, k=k):similarities=[]indices=[]model_knn = NearestNeighbors(metric = metric, algorithm = 'brute') model_knn.fit(ratings)loc = ratings.index.get_loc(user_id)distances, indices = model_knn.kneighbors(ratings.iloc[loc, :].values.reshape(1, -1), n_neighbors = k+1)similarities = 1-distances.flatten()return similarities,indices函數predict_userbased基于基于用戶的方法預測指定用戶 - 項目組合的評級。
def predict_userbased(user_id, item_id, ratings, metric = metric, k=k):prediction=0user_loc = ratings.index.get_loc(user_id)item_loc = ratings.columns.get_loc(item_id)similarities, indices=findksimilarusers(user_id, ratings,metric, k) #similar users based on cosine similaritymean_rating = ratings.iloc[user_loc,:].mean() #to adjust for zero based indexingsum_wt = np.sum(similarities)-1product=1wtd_sum = 0 for i in range(0, len(indices.flatten())):if indices.flatten()[i] == user_loc:continue;else: ratings_diff = ratings.iloc[indices.flatten()[i],item_loc]-np.mean(ratings.iloc[indices.flatten()[i],:])product = ratings_diff * (similarities[i])wtd_sum = wtd_sum + product#在非常稀疏的數據集的情況下,使用基于協作的方法的相關度量可能會給出負面的評價#在這里的處理如下if prediction <= 0:prediction = 1 elif prediction >10:prediction = 10prediction = int(round(mean_rating + (wtd_sum/sum_wt)))print ('用戶預測等級 {0} -> item {1}: {2}'.format(user_id,item_id,prediction))return prediction測試
predict_userbased(11676,'0001056107',ratings_matrix) 用戶預測等級 11676 -> item 0001056107: 2功能recommendedItem使用上述功能來推薦基于用戶或基于項目的方法的書籍(基于所選方法和度量組合)。如果圖書的預測評級大于或等于6,并且圖書尚未評級,則會提出建議。您可以在調用此函數時選擇相似性度量(余弦/相關)。
而且Voila !!!?根據基于用戶的CF方法,檢查用戶4385的前10本書籍建議。
?
基于項目的協同過濾
已經為基于項目的CF編寫了類似的函數來查找類似的書籍并預測用戶對每本書的評級。相同的功能recommendedItem可用于根據基于項目的方法和選定的指標推薦書籍。如果圖書的預測評級大于或等于6,并且圖書尚未評級,則會提出建議。
def findksimilaritems(item_id, ratings, metric=metric, k=k):similarities=[]indices=[]ratings=ratings.Tloc = ratings.index.get_loc(item_id)model_knn = NearestNeighbors(metric = metric, algorithm = 'brute')model_knn.fit(ratings)distances, indices = model_knn.kneighbors(ratings.iloc[loc, :].values.reshape(1, -1), n_neighbors = k+1)similarities = 1-distances.flatten()return similarities,indices def predict_itembased(user_id, item_id, ratings, metric = metric, k=k):prediction= wtd_sum =0user_loc = ratings.index.get_loc(user_id)item_loc = ratings.columns.get_loc(item_id)similarities, indices=findksimilaritems(item_id, ratings) #similar users based on correlation coefficientssum_wt = np.sum(similarities)-1product=1for i in range(0, len(indices.flatten())):if indices.flatten()[i] == item_loc:continue;else:product = ratings.iloc[user_loc,indices.flatten()[i]] * (similarities[i])wtd_sum = wtd_sum + product prediction = int(round(wtd_sum/sum_wt))#在非常稀疏的數據集的情況下,使用基于協作的方法的相關度量可能會給出負面的評價#在這里處理的是下面的//代碼,沒有下面的代碼片段,下面的代碼片段是為了避免負面影響#在使用相關度規時,可能會出現非常稀疏的數據集的預測if prediction <= 0:prediction = 1 elif prediction >10:prediction = 10print ('用戶預測等級 {0} -> item {1}: {2}'.format(user_id,item_id,prediction) ) return prediction測試
prediction = predict_itembased(11676,'0001056107',ratings_matrix) 用戶預測等級 11676 -> item 0001056107: 1哇!!!根據基于項目的CF方法檢查用戶4385的前10本書籍建議。這些與基于用戶的方法建議的顯著不同。
?
代碼
原文代碼:
在這篇文章中,交叉驗證,測試列車拆分和推薦系統評估等領域尚未涉及,這些領域值得探討。這段代碼的Jupyter筆記本:https://github.com/csaluja/JupyterNotebooks-Medium/blob/master/Book%20Recommendation%20System.ipynb
本人翻譯的代碼:
https://github.com/935048000/BookRecommendationSystem
?
?
原文:https://towardsdatascience.com/my-journey-to-building-book-recommendation-system-5ec959c41847
?
總結
以上是生活随笔為你收集整理的【推荐系统】基于协同过滤的图书推荐系统的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: RISC-V详细介绍
- 下一篇: Ubuntu下搭建hadoop出现Per