同過濾推薦系統(tǒng)實戰(zhàn):冷啟動+實時評分+后臺管理)
簡介這是一套基于協(xié)同過濾算法的電影推薦系統(tǒng)畢業(yè)設(shè)計實現(xiàn)面向Python與Web開發(fā)初學(xué)者及高校計算機(jī)專業(yè)學(xué)生解決個性化推薦系統(tǒng)從理論到工程落地的學(xué)習(xí)痛點。資源完整復(fù)現(xiàn)了基于用戶和基于物品的雙路協(xié)同過濾邏輯采用Django 2.2.1框架構(gòu)建前后端后端支持MySQL與SQLite雙數(shù)據(jù)庫前端集成Bootstrap 3響應(yīng)式界面并預(yù)置MovieLens公開數(shù)據(jù)集及配套爬蟲腳本。壓縮包共83個文件含28個核心Python模塊如userCF、itemCF、數(shù)據(jù)填充與推薦計算腳本、16個HTML模板頁、4個CSV原始數(shù)據(jù)文件、1個PDF論文、1個SQLite3數(shù)據(jù)庫及詳細(xì)README與requirements依賴說明整體大小8.41MB。已有10608人學(xué)習(xí)下載讀者可直接運行調(diào)試、查閱完整技術(shù)文檔、復(fù)用爬蟲與數(shù)據(jù)初始化腳本并參考附帶的畢業(yè)論文與目錄結(jié)構(gòu)組織方式快速掌握推薦系統(tǒng)工程化開發(fā)全流程。1. 這不是又一個“Hello World”推薦系統(tǒng)它真能跑通冷啟動實時評分后臺管理閉環(huán)專治畢設(shè)答辯前一周還在調(diào)不通 MySQL 連接的焦慮你手頭那份寫著“基于協(xié)同過濾的電影推薦系統(tǒng)”的畢設(shè)文檔是不是已經(jīng)卡在第三章——數(shù)據(jù)庫建表失敗、Django 啟動報django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module、用戶評分提交后推薦結(jié)果永遠(yuǎn)是《阿凡達(dá)》別慌。這不是理論空談而是一套完整落地過、可直接 clone、改完 settings.py 就能跑通的 Django 2.2.1 Python 3.7 MySQL 實戰(zhàn)工程。它不依賴任何云服務(wù)或第三方 API所有推薦邏輯寫在recommend/views.py里用純 SQL Pandas 做用戶-物品矩陣構(gòu)建用余弦相似度實現(xiàn)基于用戶的協(xié)同過濾User-Based CF并內(nèi)置管理員后臺——你能看到每個用戶的歷史評分、系統(tǒng)生成的 Top-N 推薦列表、甚至手動觸發(fā)“重新計算相似度矩陣”按鈕。適合正在趕畢設(shè) deadline 的本科生也適合想搞懂“協(xié)同過濾怎么從公式變成可調(diào)試代碼”的轉(zhuǎn)行者。它不炫技但每一步都踩在真實部署的坑上MySQL 字符集亂碼、Django 2.2.1 對 MySQL 5.7 的驅(qū)動兼容性、評分表外鍵約束導(dǎo)致的 INSERT 失敗……這些我都替你翻過車。2. 從零搭起推薦骨架Python 環(huán)境、Django 工程結(jié)構(gòu)與 MySQL 數(shù)據(jù)庫三件套實操2.1 Python 3.7 環(huán)境隔離與依賴精準(zhǔn)安裝為什么不用 conda 而堅持 venv很多同學(xué)用pip install django全局安裝結(jié)果發(fā)現(xiàn) Django 版本沖突、MySQL 驅(qū)動報錯。協(xié)同過濾對環(huán)境版本極其敏感Django 2.2.1 明確要求mysqlclient1.3.13,2.0而PyMySQL在該版本下無法正確處理DateTimeField的時區(qū)解析。我堅持用venv不是 conda的原因很實際畢業(yè)答辯現(xiàn)場演示時評審老師可能用的是 Windows 10 默認(rèn) CMDvenv創(chuàng)建的Scripts/activate.bat兼容性遠(yuǎn)高于 conda 的 shell 腳本。# 在項目根目錄執(zhí)行注意不要用管理員權(quán)限 python -m venv venv_rec venv_rec\Scripts\activate.bat # Windows # venv_rec/bin/activate # macOS/Linux # 安裝指定版本關(guān)鍵 pip install --upgrade pip pip install django2.2.1 pip install mysqlclient1.4.6 # 注意不是最新版1.4.6 是 Django 2.2.1 最穩(wěn)的搭檔 pip install pandas0.24.2 numpy1.16.4 scikit-learn0.20.3提示mysqlclient1.4.6必須用源碼編譯安裝。Windows 下需提前裝好 Visual Studio Build Tools2015 或 2017 版本否則會報Microsoft Visual C 14.0 is required。Mac 用戶用brew install mysql-client再pip install mysqlclientLinux 用戶先sudo apt-get install python3-dev default-libmysqlclient-dev。2.2 Django 2.2.1 工程初始化四步完成核心配置Django 2.2.1 的settings.py有兩處必須改否則連數(shù)據(jù)庫都連不上INSTALLED_APPS加入自定義 app項目含movies電影模型、ratings評分記錄、recommend推薦邏輯三個核心 app必須顯式注冊# settings.py INSTALLED_APPS [ django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, movies, # ← 新增 ratings, # ← 新增 recommend, # ← 新增 ]DATABASES配置必須用mysqlclient驅(qū)動不要抄網(wǎng)上教程用pymysqlDjango 2.2.1 會報DatabaseWrapper錯誤# settings.py DATABASES { default: { ENGINE: django.db.backends.mysql, # ← 引擎固定 NAME: movie_recommender, # ← 數(shù)據(jù)庫名需提前創(chuàng)建 USER: root, # ← MySQL 用戶名 PASSWORD: your_password, # ← 密碼明文畢設(shè)可接受 HOST: 127.0.0.1, # ← 本地連接 PORT: 3306, # ← 默認(rèn)端口 OPTIONS: { init_command: SET sql_modeSTRICT_TRANS_TABLES, charset: utf8mb4, # ← 關(guān)鍵支持 emoji 和生僻字 }, TEST: { CHARSET: utf8mb4, COLLATION: utf8mb4_unicode_ci, } } }參數(shù)說明charset: utf8mb4解決中文電影名存入亂碼init_command強(qiáng)制嚴(yán)格模式避免INSERT時因字段為空被靜默截斷TEST配置確保單元測試也能用相同字符集。2.3 MySQL 數(shù)據(jù)庫建模五張表支撐協(xié)同過濾全流程協(xié)同過濾不是只存用戶和電影它需要行為日志、關(guān)系映射、緩存中間結(jié)果。本系統(tǒng)共 5 張表全部用 Django ORM 自動生成python manage.py makemigrations python manage.py migrate但你必須理解每張表的物理意義表名字段關(guān)鍵作用協(xié)同過濾中角色movies_movieid,title,genres,year電影元數(shù)據(jù)物品集合Item Setauth_userid,username,emailDjango 自帶用戶表用戶集合User Setratings_ratinguser_id,movie_id,rating,timestamp用戶對電影的 1-5 分評分核心評分矩陣Ru,irecommend_usersimilarityuser1_id,user2_id,similarity用戶間余弦相似度預(yù)計算相似度緩存表加速推薦recommend_recommendationuser_id,movie_id,score,rank每個用戶的 Top-10 推薦結(jié)果推薦結(jié)果持久化供前端調(diào)用注意ratings_rating表的user_id和movie_id是外鍵指向auth_user.id和movies_movie.id。Django 遷移時會自動加ON DELETE CASCADE但務(wù)必在 MySQL 中確認(rèn)外鍵已啟用SHOW VARIABLES LIKE foreign_key_checks;應(yīng)為ON。3. 協(xié)同過濾核心實現(xiàn)從評分矩陣到 Top-N 推薦的四步推導(dǎo)與代碼落地3.1 構(gòu)建用戶-物品評分矩陣Pandas DataFrame 是最穩(wěn)的起點協(xié)同過濾的第一步是把ratings_rating表里的稀疏數(shù)據(jù)轉(zhuǎn)成稠密矩陣。很多人直接用scipy.sparse但在 Django 視圖里頻繁構(gòu)造稀疏矩陣容易內(nèi)存溢出。我的做法是用 Pandas 讀取全量評分用pivot_table生成 DataFrame再轉(zhuǎn)為 numpy 數(shù)組——既保留可讀性又便于后續(xù)計算。# recommend/utils.py import pandas as pd import numpy as np from ratings.models import Rating def build_user_item_matrix(): 返回 (user_ids, movie_ids, matrix) 三元組 matrix[i][j] 表示 user_ids[i] 對 movie_ids[j] 的評分NaN 表示未評 # 1. 從數(shù)據(jù)庫讀取所有評分僅需三列 qs Rating.objects.values(user_id, movie_id, rating) df pd.DataFrame(list(qs)) # 2. 生成唯一用戶ID和電影ID列表保證索引連續(xù) user_ids sorted(df[user_id].unique()) movie_ids sorted(df[movie_id].unique()) # 3. pivot_table 構(gòu)建矩陣自動填充 NaN matrix df.pivot_table( indexuser_id, columnsmovie_id, valuesrating, aggfuncfirst # 防止同一用戶對同一電影多次評分取平均 ) # 4. 重索引確保所有用戶/電影都在行列中補(bǔ) NaN matrix matrix.reindex(indexuser_ids, columnsmovie_ids) return user_ids, movie_ids, matrix.values # 返回 numpy.ndarray邏輯說明pivot_table比groupbyunstack更穩(wěn)定aggfuncfirst防止歷史數(shù)據(jù)中存在重復(fù)評分導(dǎo)致聚合錯誤reindex確保矩陣行列嚴(yán)格對應(yīng)user_ids/movie_ids列表這是后續(xù)用np.nanmean計算用戶均值的前提。3.2 用戶相似度計算余弦相似度 均值中心化拒絕 raw cosine 玄學(xué)原始余弦相似度Raw Cosine對用戶評分尺度敏感——一個習(xí)慣打 4-5 分的用戶和一個只打 1-2 分的用戶相似度會虛高。本系統(tǒng)采用 Pearson 相關(guān)系數(shù)變體先中心化減去用戶均分再算余弦。代碼封裝在recommend/algorithms.py# recommend/algorithms.py from scipy.spatial.distance import cosine import numpy as np def calculate_user_similarity(matrix, user_ids): matrix: (n_users, n_movies) numpy array, 缺失值為 np.nan 返回相似度矩陣 sim_matrix[i][j] user_ids[i] 與 user_ids[j] 的相似度 n_users len(user_ids) sim_matrix np.zeros((n_users, n_users)) # 預(yù)計算每個用戶的均分跳過 NaN user_means np.nanmean(matrix, axis1) for i in range(n_users): # 獲取用戶 i 的中心化向量只取非 NaN 位置 vec_i matrix[i] - user_means[i] mask_i ~np.isnan(vec_i) # True 表示該電影有評分 for j in range(i1, n_users): vec_j matrix[j] - user_means[j] mask_j ~np.isnan(vec_j) mask mask_i mask_j # 兩人共同評分的電影 if mask.sum() 5: # 共同評分少于 5 部視為無相似性 sim 0.0 else: # 只在共同評分位置上計算余弦 valid_i vec_i[mask] valid_j vec_j[mask] sim 1 - cosine(valid_i, valid_j) # cosine 返回距離1-距離相似度 sim_matrix[i][j] sim sim_matrix[j][i] sim # 對稱 return sim_matrix參數(shù)說明mask.sum() 5是硬性閾值——協(xié)同過濾中兩個用戶至少共同評過 5 部電影相似度才有統(tǒng)計意義cosine函數(shù)來自scipy比手寫更魯棒1 - cosine(...)直接得到 [0,1] 區(qū)間相似度。3.3 Top-N 推薦生成加權(quán)平均預(yù)測 排序截斷拒絕“猜你喜歡”黑匣子推薦不是找相似用戶然后隨機(jī)選電影而是對目標(biāo)用戶未評分的每一部電影預(yù)測其評分再按預(yù)測分排序。預(yù)測公式為$$ \hat{r}{u,i} \bar{r}u \frac{\sum{v \in N(u)} sim(u,v) \cdot (r{v,i} - \bar{r}v)}{\sum{v \in N(u)} |sim(u,v)|} $$其中 $N(u)$ 是用戶 $u$ 的 Top-K 相似用戶K20。代碼實現(xiàn)# recommend/views.py def get_top_n_recommendations(user_id, n10): 為 user_id 生成 Top-N 推薦 返回 [(movie_id, predicted_rating, rank), ...] from recommend.algorithms import calculate_user_similarity from movies.models import Movie # 1. 獲取全量矩陣和 ID 映射 user_ids, movie_ids, matrix build_user_item_matrix() try: u_idx user_ids.index(user_id) except ValueError: return [] # 用戶不存在 # 2. 計算相似度矩陣生產(chǎn)環(huán)境應(yīng)緩存 sim_matrix calculate_user_similarity(matrix, user_ids) # 3. 找出 Top-20 相似用戶排除自己 sim_scores sim_matrix[u_idx] top_sim_indices np.argsort(sim_scores)[::-1][1:21] # 跳過第0位自己 # 4. 預(yù)測未評分電影的分?jǐn)?shù) predictions [] user_mean np.nanmean(matrix[u_idx]) for m_idx, movie_id in enumerate(movie_ids): if not np.isnan(matrix[u_idx][m_idx]): # 已評分跳過 continue # 計算分子分母 numerator 0.0 denominator 0.0 for v_idx in top_sim_indices: r_vi matrix[v_idx][m_idx] if np.isnan(r_vi): continue v_mean np.nanmean(matrix[v_idx]) numerator sim_scores[v_idx] * (r_vi - v_mean) denominator abs(sim_scores[v_idx]) if denominator 0: pred user_mean # 無相似用戶貢獻(xiàn)回退到用戶均分 else: pred user_mean numerator / denominator predictions.append((movie_id, pred)) # 5. 按預(yù)測分降序取 Top-N predictions.sort(keylambda x: x[1], reverseTrue) top_n predictions[:n] # 6. 補(bǔ)充電影標(biāo)題供前端顯示 movie_titles {m.id: m.title for m in Movie.objects.filter(id__in[x[0] for x in top_n])} result [] for i, (mid, score) in enumerate(top_n): result.append({ movie_id: mid, title: movie_titles.get(mid, 未知電影), predicted_rating: round(score, 2), rank: i1 }) return result關(guān)鍵點numerator / denominator是加權(quán)平均的核心denominator 0的兜底邏輯防止除零Movie.objects.filter用id__in批量查詢比循環(huán)查單個快 10 倍以上。4. 避坑指南Django MySQL 協(xié)同過濾項目里90% 的翻車都發(fā)生在這五個地方4.1 現(xiàn)象python manage.py migrate報錯BLOB/TEXT column used in key specification without a key length原因MySQL 5.7 默認(rèn)innodb_large_prefixOFF而 Django 2.2.1 的auth_user.username字段是VARCHAR(150)建索引時超出限制。解決登錄 MySQL 執(zhí)行SET GLOBAL innodb_file_formatBarracuda; SET GLOBAL innodb_file_per_tableON; SET GLOBAL innodb_large_prefixON; ALTER TABLE auth_user MODIFY username VARCHAR(150) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;然后刪掉migrations/下除__init__.py外所有文件重新makemigrations。4.2 現(xiàn)象Django 后臺能登錄但ratings_rating表里user_id總是 0原因前端表單提交時user_id字段沒傳Django ORM 默認(rèn)用0填充外鍵。解決在ratings/forms.py中強(qiáng)制綁定當(dāng)前用戶class RatingForm(forms.ModelForm): class Meta: model Rating fields [movie, rating] # 不包含 user 字段 def save(self, commitTrue, userNone): instance super().save(commitFalse) if user: instance.user user # 從視圖傳入 request.user if commit: instance.save() return instance視圖中調(diào)用form.save(userrequest.user)。4.3 現(xiàn)象get_top_n_recommendations()執(zhí)行超時30秒Apache 直接 504原因每次請求都重新計算相似度矩陣而 1000 用戶 × 1000 用戶的矩陣計算需 O(N2×M) 時間。解決必須加緩存。在settings.py中配置 Redis或 Django 自帶的文件緩存CACHES { default: { BACKEND: django.core.cache.backends.filebased.FileBasedCache, LOCATION: /var/tmp/django_cache, # LinuxWindows 用 C:/tmp/django_cache TIMEOUT: 3600, # 緩存 1 小時 } }然后在get_top_n_recommendations開頭加cache_key fuser_sim_matrix_{len(user_ids)} sim_matrix cache.get(cache_key) if sim_matrix is None: sim_matrix calculate_user_similarity(matrix, user_ids) cache.set(cache_key, sim_matrix, 3600)4.4 現(xiàn)象中文電影名在 Django Admin 中顯示為????原因MySQL 數(shù)據(jù)庫、表、字段三級字符集不一致。CREATE DATABASE時沒指定CHARSETutf8mb4。解決-- 刪除舊庫謹(jǐn)慎先備份 DROP DATABASE movie_recommender; -- 重建時指定字符集 CREATE DATABASE movie_recommender CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 然后重新 migrate python manage.py migrate4.5 現(xiàn)象mysqlclient安裝成功但python manage.py dbshell報Cant connect to local MySQL server原因MySQL 服務(wù)未啟動或my.ini中bind-address127.0.0.1被注釋導(dǎo)致監(jiān)聽0.0.0.0而防火墻阻止了 3306 端口。解決Windows打開「服務(wù)」→ 找到MySQL→ 右鍵「啟動」檢查my.ini確保bind-address127.0.0.1且未被#注釋命令行驗證mysql -u root -p能登錄即服務(wù)正常5. 生產(chǎn)就緒技巧讓畢設(shè)系統(tǒng)扛住答辯演示流量同時保住你的 GPA5.1 推薦結(jié)果預(yù)計算 定時任務(wù)告別“點擊推薦按鈕后轉(zhuǎn)圈 10 秒”答辯現(xiàn)場最怕什么評委說“點一下推薦看看”然后你盯著加載動畫心跳加速。協(xié)同過濾的預(yù)測計算必須離線化。Django 自帶django-crontab可實現(xiàn)定時任務(wù)每天凌晨 2 點批量更新所有活躍用戶的 Top-10 推薦# 安裝 pip install django-crontab # settings.py INSTALLED_APPS [django_crontab] CRONJOBS [ (0 2 * * *, recommend.cron.update_all_recommendations), # 每天2點執(zhí)行 ]# recommend/cron.py from django.contrib.auth.models import User from recommend.views import get_top_n_recommendations from recommend.models import Recommendation def update_all_recommendations(): # 只更新近 30 天有評分的用戶減少計算量 recent_user_ids set( r.user_id for r in Rating.objects.filter( timestamp__gtetimezone.now() - timedelta(days30) ).values_list(user_id, flatTrue) ) for uid in recent_user_ids: recs get_top_n_recommendations(uid, n10) # 清空舊推薦寫入新結(jié)果 Recommendation.objects.filter(user_iduid).delete() Recommendation.objects.bulk_create([ Recommendation(user_iduid, movie_idr[movie_id], scorer[predicted_rating], rankr[rank]) for r in recs ])技巧bulk_create比循環(huán)save()快 50 倍timedelta(days30)過濾掉沉寂用戶讓計算量從 1000 降到 200Recommendation模型需加unique_together (user_id, movie_id)防止重復(fù)插入。5.2 Django Admin 深度定制讓答辯老師一眼看懂你的推薦邏輯默認(rèn) Admin 只能 CRUD但你可以把它變成“推薦系統(tǒng)監(jiān)控面板”。在recommend/admin.py中from django.contrib import admin from .models import UserSimilarity, Recommendation admin.register(UserSimilarity) class UserSimilarityAdmin(admin.ModelAdmin): list_display (user1, user2, similarity, updated_at) list_filter (updated_at,) search_fields (user1__username, user2__username) ordering (-similarity,) def user1(self, obj): return obj.user1.username def user2(self, obj): return obj.user2.username admin.register(Recommendation) class RecommendationAdmin(admin.ModelAdmin): list_display (user, movie_title, score, rank, created_at) list_filter (rank, created_at) search_fields (user__username, movie__title) ordering (user, rank) def movie_title(self, obj): return obj.movie.title[:30] ... if len(obj.movie.title) 30 else obj.movie.title效果評委點進(jìn)/admin/recommend/recommendation/能看到所有用戶的推薦列表按rank篩選“Top-1”、“Top-3”還能搜用戶名快速定位——這比口頭解釋“我們用了協(xié)同過濾”有力得多。5.3 最小化部署 checklist三步打包答辯前 1 小時搞定別等到答辯前夜才部署。按這個順序走1 小時內(nèi)必通數(shù)據(jù)庫導(dǎo)出/導(dǎo)入避免現(xiàn)場建表# 導(dǎo)出開發(fā)機(jī) mysqldump -u root -p --databases movie_recommender movie_rec.sql # 導(dǎo)入答辯機(jī)先創(chuàng)建空庫 mysql -u root -p movie_recommender movie_rec.sql靜態(tài)文件收集Django 2.2.1 必做python manage.py collectstatic --noinput # 確保 settings.py 中 STATIC_ROOT os.path.join(BASE_DIR, staticfiles)用runserver替代 Gunicorn答辯機(jī)無需高并發(fā)# 修改 settings.py DEBUG True # 答辯現(xiàn)場開 DEBUG方便看錯誤頁 ALLOWED_HOSTS [*] # 臨時允許所有 host # 啟動 python manage.py runserver 0.0.0.0:8000然后用手機(jī)訪問http://[答辯機(jī)IP]:8000確保首頁、登錄、推薦頁全通。從那以后我每次做畢設(shè)都強(qiáng)制走一遍「導(dǎo)出 SQL → 新建虛擬機(jī) → 導(dǎo)入 → collectstatic → runserver」全流程哪怕只是本地測試。因為答辯現(xiàn)場的網(wǎng)絡(luò)、系統(tǒng)環(huán)境、甚至 USB 插口松動都可能讓你的 demo 在最后一分鐘崩掉。這份協(xié)同過濾系統(tǒng)不是炫技的玩具它是你交出去的代碼答卷——而答卷的及格線從來不是“能跑”而是“在任何一臺 Windows 10 筆記本上插上 U 盤雙擊 start.bat就能讓評委點頭說‘嗯邏輯是通的’”。希望幫到你。本文還有配套的精品資源點擊獲取