目录
前言
本项目采用了矩阵分解算法,用于对玩家已游玩的数据进行深入分析。它的目标是从众多游戏中筛选出最适合该玩家的游戏,以实现一种相对精准的游戏推荐系统。
首先,项目会收集并分析玩家已经玩过的游戏数据,包括游戏名称、游戏时长、游戏评分等信息。这些数据构成了一个大型的用户-游戏交互矩阵,其中每一行代表一个玩家,每一列代表一个游戏,矩阵中的值表示玩家与游戏之间的交互情况。
接下来,项目运用矩阵分解算法,将用户-游戏这稀疏矩阵用两个小矩阵——特征-游戏矩阵和用户-特征矩阵,进行近似替代。这个分解过程会将玩家和游戏映射到一个潜在的特征空间,从而能够推断出玩家与游戏之间的潜在关系。
一旦模型训练完成,系统可以根据玩家的游戏历史,预测他们可能喜欢的游戏。这种预测是基于玩家与其他玩家的相似性以及游戏与其他游戏的相似性来实现的。因此,系统可以为每个玩家提供个性化的游戏推荐,考虑到他们的游戏偏好和历史行为。
总的来说,本项目的目标是通过矩阵分解和潜在因子模型,提供一种更为精准的游戏推荐系统。这种个性化推荐可以提高玩家的游戏体验,同时也有助于游戏平台提供更好的游戏推广和增加用户黏性。
总体设计
本部分包括系统整体结构图和系统流程图。
系统整体结构图
系统整体结构如图所示。
系统流程图
系统流程如图所示。
运行环境
本部分包括 Python 环境、TensorFlow环境、 PyQt5环境。
详见博客:https://blog.csdn.net/qq_31136513/article/details/133148686#_38
模块实现
本项目包括4个模块:数据预处理、模型构建、模型训练及保存、模型测试,下面分别给出各模块的功能介绍及相关代码。
1. 数据预处理
数据集来源于Kaggle,链接地址为https://www.kaggle.com/tamber/steam-video-games,此数据集包含了用户的ID、游戏名称、是否购买或游玩、游戏时长,其中:共包含12393名用户,涉及游戏数量5155款。将数据集置于Jupyter工作路径下的steam-video-games
文件夹中。
详见博客:https://blog.csdn.net/qq_31136513/article/details/133148686#1__97
2. 模型构建
数据加载进模型之后,需要定义模型结构,并优化损失函数。
1)定义模型结构
使用矩阵分解算法,将用户-游戏这稀疏矩阵用两个小矩阵——特征-游戏矩阵和用户-特征矩阵,进行近似替代。
详见博客:https://blog.csdn.net/qq_31136513/article/details/133151049#1_54
2)优化损失函数
L2范数常用于矩阵分解算法的损失函数中。因此,本项目的损失函数也引入了L2范数以避免过拟合现象。使用Adagrad优化器优化模型参数。
详见博客:https://blog.csdn.net/qq_31136513/article/details/133151049#2_91
3. 模型训练及保存
由于本项目使用的数据集中,将游戏的DLC (Downloadable Content,后续可下载内容)单独作为另一款游戏列举,因此,在计算准确率时,DLC和游戏本体判定为同一款游戏,同系列的游戏也可以判定为同一款。
详见博客:https://blog.csdn.net/qq_31136513/article/details/133151049#3__105
1)模型训练
详见博客:https://blog.csdn.net/qq_31136513/article/details/133151049#1_148
2)模型保存
为方便使用模型,需要将训练得到的结果使用Joblib进行保存。
详见博客:https://blog.csdn.net/qq_31136513/article/details/133151049#2_187
4. 模型应用
一是制作页面的布局,获取并检查输入的数据;二是将获取的数据-与之前保存的模型进行匹配达到应用效果。
1)制作页面
相关操作如下:
(1)使用代码绘制页面的基础布局,创建Recommandation
类。
class Recommandation(QWidget):
#初始化
def __init__(self):
super().__init__()
self.initUI()
#初始化布局
def initUI(self):
#设置界面的初始位置和大小
self.setGeometry(600,200,450,550)
#窗口名
self.setWindowTitle('steam游戏推荐')
#设置组件,以下为标签
self.lb1 = QLabel('请输入游戏名:',self)
#这是所在位置
self.lb1.move(20,20)
self.lb2 = QLabel('请输入游戏名:',self)
self.lb2.move(20,80)
self.lb3 = QLabel('请输入游戏名:',self)
self.lb3.move(20,140)
self.lb4 = QLabel('请输入游戏名:',self)
self.lb4.move(20,200)
self.lb5 = QLabel('请输入游戏名:',self)
self.lb5.move(20,260)
#以下为下拉输入框的创建
self.combobox1 = QComboBox(self, minimumWidth=200)
self.combobox1.move(100,20)
self.combobox1.setEditable(True)
self.combobox2 = QComboBox(self, minimumWidth=200)
self.combobox2.move(100,80)
self.combobox2.setEditable(True)
self.combobox3 = QComboBox(self, minimumWidth=200)
self.combobox3.move(100,140)
self.combobox3.setEditable(True)
self.combobox4 = QComboBox(self, minimumWidth=200)
self.combobox4.move(100,200)
self.combobox4.setEditable(True)
self.combobox5 = QComboBox(self, minimumWidth=200)
self.combobox5.move(100,260)
self.combobox5.setEditable(True)
#以下为输入的按键设置
self.bt1 = QPushButton('请输入游戏时间',self)
self.bt1.move(330,20)
self.bt2 = QPushButton('请输入游戏时间',self)
self.bt2.move(330,80)
self.bt3 = QPushButton('请输入游戏时间',self)
self.bt3.move(330,140)
self.bt4 = QPushButton('请输入游戏时间',self)
self.bt4.move(330,200)
self.bt5 = QPushButton('请输入游戏时间',self)
self.bt5.move(330,260)
#推荐按钮
self.bt=QPushButton('推荐开始',self)
self.bt.move(20,400)
#初始化下拉输入框
self.init_combobox()
#连接按键与槽
self.bt1.clicked.connect(self.timeDialog)
self.bt2.clicked.connect(self.timeDialog)
self.bt3.clicked.connect(self.timeDialog)
self.bt4.clicked.connect(self.timeDialog)
self.bt5.clicked.connect(self.timeDialog)
#连接推荐
self.bt.clicked.connect(self.recommand)
connect()
是Qt特有的信号与槽机制,槽接收到信号进行处理。在这里使用了clicked 作为信号,单击按键会发出信号。
(2)初始化下拉输入框,将gamelist输入进下拉框的菜单,以及添加自动补全机能。
#初始化下拉输入框
def init_combobox(self):
#增加选项元素
for i in range(len(gamelist)):
self.combobox1.addItem(gamelist[i])
self.combobox2.addItem(gamelist[i])
self.combobox3.addItem(gamelist[i])
self.combobox4.addItem(gamelist[i])
self.combobox5.addItem(gamelist[i])
self.combobox1.setCurrentIndex(-1)
self.combobox2.setCurrentIndex(-1)
self.combobox3.setCurrentIndex(-1)
self.combobox4.setCurrentIndex(-1)
self.combobox5.setCurrentIndex(-1)
#增加自动补全
self.completer = QCompleter(gamelist)
#补全方式
self.completer.setFilterMode(Qt.MatchStartsWith)
self.completer.setCompletionMode(QCompleter.PopupCompletion)
self.combobox1.setCompleter(self.completer)
self.combobox2.setCompleter(self.completer)
self.combobox3.setCompleter(self.completer)
self.combobox4.setCompleter(self.completer)
self.combobox5.setCompleter(self.completer)
(3)设置槽,同时存储数据
相关操作如下:
def timeDialog(self):
#获取信号
sender = self.sender()
if sender == self.bt1:
#获取下拉输入框1输入的游戏名
gamename = self.combobox1.currentText()
#通过字典game2idx查询获得的游戏名所对应的序列号
gameid = game2idx.get(gamename)
#没有序列号的情况,可以理解为未输入正确的游戏名,或者输入为空
if gameid == None:
#这种情况下生成一个MessageBox报错
reply = QMessageBox.information(self,'Error','请输入正确的游戏名!', QMessageBox.Close)
else:
#输入正确的情况,将游戏名字、ID,分别记录到一个字典里,方便保存与更改
gamedict[1] = gamename
idxdict[1] = gameid
#弹出一个文本输入框,要求输入对应游戏时长
text, ok = QInputDialog.getDouble(self, '游戏时间', '请输入游戏时间:', min = 0.1)
#如果输入正确,将时长记录到一个字典中,方便保存与更改
if ok:
timedict[1] = text
elif sender == self.bt2:
gamename = self.combobox2.currentText()
gameid = game2idx.get(gamename)
if gameid == None:
reply = QMessageBox.information(self,'Error','请输入正确的游戏名!', QMessageBox.Close)
else:
gamedict[2] = gamename
idxdict[2] = gameid
text, ok = QInputDialog.getDouble(self, '游戏时间', '请输入游戏时间:', min = 0.1)
if ok:
timedict[2] = text
elif sender == self.bt3:
gamename = self.combobox3.currentText()
gameid = game2idx.get(gamename)
if gameid == None:
reply = QMessageBox.information(self,'Error','请输入正确的游戏名!', QMessageBox.Close)
else:
gamedict[3] = gamename
idxdict[3] = gameid
text, ok = QInputDialog.getDouble(self, '游戏时间', '请输入游戏时间:', min = 0.1)
if ok:
timedict[3] = text
elif sender == self.bt4:
gamename = self.combobox4.currentText()
gameid = game2idx.get(gamename)
if gameid == None:
reply = QMessageBox.information(self,'Error','请输入正确的游戏名!', QMessageBox.Close)
else:
gamedict[4] = gamename
idxdict[4] = gameid
text, ok = QInputDialog.getDouble(self, '游戏时间', '请输入游戏时间:', min = 0.1)
if ok:
timedict[4] = text
elif sender == self.bt5:
gamename = self.combobox5.currentText()
gameid = game2idx.get(gamename)
if gameid == None:
reply = QMessageBox.information(self,'Error','请输入正确的游戏名!', QMessageBox.Close)
else:
gamedict[5] = gamename
idxdict[5] = gameid
text, ok = QInputDialog.getDouble(self, '游戏时间', '请输入游戏时间:', min = 0.1)
if ok:
timedict[5] = text
(4) 验证数据是否输入完毕,以及准备调用模型
def recommand(self):
#验证是否存在没有写入的数据
c = 0
for i in range(1,6):
if gamedict[i] == "NULL":
c+=1
if idxdict[i] == "NULL":
c+=1
if timedict[i] == "NULL":
c+=1
#全部写完的情况
if c == 0:
#将字典转化为列表
usertime = list(timedict.values())
useridx = list(idxdict.values())
#调用模型
allrecidx = UserSimilarity(useridx,usertime)
#降序排列数据
rr = np.argsort(-allrecidx)
#获取排行前五的游戏ID
top_k = rr[:5]
#将ID对应的游戏名字输入数组
for i in top_k:
recgame.append(idx2game[i])
#将数组转化为字符串并输出
reclist = ','.join(recgame)
reply = QMessageBox.information(self,'推荐的游戏','给您推荐的游戏是'+reclist, QMessageBox.Close)
#存在没有写完的数据,要求重新写入
else:
reply = QMessageBox.information(self,'Error','请输入全部数据!', QMessageBox.Close)
2)模型导入及调用
相关操作如下:
(1)加载当前文件夹下的Save_data模型
game2idx = joblib.load('./Save_data/game2idx.pkl')
idx2game = joblib.load('./Save_data/idx2game.pkl')
rec = joblib.load('./Save_data/rec.pkl')
hours = joblib.load('./Save_data/hours.pkl')
buy = joblib.load('./Save_data/buy.pkl')
users = joblib.load('./Save_data/buyers.pkl')
(2)创建一个用户相似度函数,用于刻画Qt里收集到的数据与训练出的用户相似度最高的数据作为输出
def UserSimilarity(games, game_hours):
similarity = np.zeros(len(users)) # 用户相似度矩阵
for i in range(len(users)):
#计算用户输入的游戏与数据集中每个用户购买游戏的重合度
coincidence = 0 #重合度,每重合一个游戏加1
positions = [] #重合游戏在games中的位置
#获取数据集中的第i个玩家与用户输入的重合情况
for ii in range(len(games)):
if games[ii] in np.where(buy[users[i], :] == 1)[0]:
coincidence += 1
positions.append(ii)
#如果没有重合,则相似度为0,跳过
if coincidence == 0:
continue
simi = []
#将重合的游戏,根据时长和相同游戏的时长差取绝对值,根据e^-x计算出相似度
for position in positions:
game = games[position]
hour = abs(game_hours[position] - hours[users[i], game])
simi.append(math.exp(-hour))
#对所有相似度取均值,得到用户与数据集中第i个玩家的相似度similarity[i]
similarity[i] = sum(simi) / coincidence
#相似度与玩家—游戏矩阵每一行相乘
for i in range(len(users)):
user = users[i]
rec[user] = rec[user] * similarity[i]
new_rec = np.zeros(len(rec[0])) # 1*n_games矩阵
#将玩家—游戏矩阵按列相加,得到用户对每个游戏的喜好程度,即new_rec矩阵
for i in range(len(new_rec)):
for user in users:
new_rec[i] += rec[user][int(i)]
return new_rec
3)模型应用代码
相关代码如下:
import joblib
import numpy as np
import pandas as pd
import math
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
#读取数据
game2idx = joblib.load('./Save_data/game2idx.pkl')
idx2game = joblib.load('./Save_data/idx2game.pkl')
rec = joblib.load('./Save_data/rec.pkl')
hours = joblib.load('./Save_data/hours.pkl')
buy = joblib.load('./Save_data/buy.pkl')
users = joblib.load('./Save_data/buyers.pkl')
#游戏名称列表
gamelist = list(game2idx)
#游戏数
n_game = len(gamelist)
#传入字典
gamedict = {1:"NULL",2:"NULL",3:"NULL",4:"NULL",5:"NULL"}
timedict = {1:"NULL",2:"NULL",3:"NULL",4:"NULL",5:"NULL"}
idxdict = {1:"NULL",2:"NULL",3:"NULL",4:"NULL",5:"NULL"}
#下面两个是要传递的
usertime=[]
useridx=[]
#下面是返回的推荐游戏
recgame=[]
#相似度推荐
def UserSimilarity(games, game_hours):
similarity = np.zeros(len(users)) #用户相似度矩阵
for i in range(len(users)):
#计算用户输入的游戏与数据集中每个用户购买游戏的重合度
coincidence = 0 #重合度
positions = [] #重合游戏在games中的位置
for ii in range(len(games)):
if games[ii] in np.where(buy[users[i], :] == 1)[0]:
coincidence += 1
positions.append(ii)
if coincidence == 0:
continue
simi = []
for position in positions:
game = games[position]
hour = abs(game_hours[position] - hours[users[i], game])
simi.append(math.exp(-hour))
similarity[i] = sum(simi) / coincidence
#相似度与玩家—游戏矩阵每一行相乘
for i in range(len(users)):
user = users[i]
rec[user] = rec[user] * similarity[i]
new_rec = np.zeros(len(rec[0])) #1*n_games矩阵
for i in range(len(new_rec)):
for user in users:
new_rec[i] += rec[user][int(i)]
return new_rec
class Recommandation(QWidget):
#初始化
def __init__(self):
super().__init__()
self.initUI()
#初始化布局
def initUI(self):
#设置界面的初始位置和大小
self.setGeometry(600,200,450,550)
#窗口名
self.setWindowTitle('steam游戏推荐')
#设置组件,以下为标签
self.lb1 = QLabel('请输入游戏名:',self)
#这是所在位置
self.lb1.move(20,20)
self.lb2 = QLabel('请输入游戏名:',self)
self.lb2.move(20,80)
self.lb3 = QLabel('请输入游戏名:',self)
self.lb3.move(20,140)
self.lb4 = QLabel('请输入游戏名:',self)
self.lb4.move(20,200)
self.lb5 = QLabel('请输入游戏名:',self)
self.lb5.move(20,260)
#以下为下拉输入框的创建
self.combobox1 = QComboBox(self, minimumWidth=200)
self.combobox1.move(100,20)
self.combobox1.setEditable(True)
self.combobox2 = QComboBox(self, minimumWidth=200)
self.combobox2.move(100,80)
self.combobox2.setEditable(True)
self.combobox3 = QComboBox(self, minimumWidth=200)
self.combobox3.move(100,140)
self.combobox3.setEditable(True)
self.combobox4 = QComboBox(self, minimumWidth=200)
self.combobox4.move(100,200)
self.combobox4.setEditable(True)
self.combobox5 = QComboBox(self, minimumWidth=200)
self.combobox5.move(100,260)
self.combobox5.setEditable(True)
#以下为输入的按键设置
self.bt1 = QPushButton('请输入游戏时间',self)
self.bt1.move(330,20)
self.bt2 = QPushButton('请输入游戏时间',self)
self.bt2.move(330,80)
self.bt3 = QPushButton('请输入游戏时间',self)
self.bt3.move(330,140)
self.bt4 = QPushButton('请输入游戏时间',self)
self.bt4.move(330,200)
self.bt5 = QPushButton('请输入游戏时间',self)
self.bt5.move(330,260)
#推荐按钮
self.bt=QPushButton('推荐开始',self)
self.bt.move(20,400)
#初始化下拉输入框
self.init_combobox()
#连接按键与槽
self.bt1.clicked.connect(self.timeDialog)
self.bt2.clicked.connect(self.timeDialog)
self.bt3.clicked.connect(self.timeDialog)
self.bt4.clicked.connect(self.timeDialog)
self.bt5.clicked.connect(self.timeDialog)
#连接推荐
self.bt.clicked.connect(self.recommand)
#初始化下拉输入框
def init_combobox(self):
#增加选项元素
for i in range(len(gamelist)):
self.combobox1.addItem(gamelist[i])
self.combobox2.addItem(gamelist[i])
self.combobox3.addItem(gamelist[i])
self.combobox4.addItem(gamelist[i])
self.combobox5.addItem(gamelist[i])
self.combobox1.setCurrentIndex(-1)
self.combobox2.setCurrentIndex(-1)
self.combobox3.setCurrentIndex(-1)
self.combobox4.setCurrentIndex(-1)
self.combobox5.setCurrentIndex(-1)
#增加自动补全
self.completer = QCompleter(gamelist)
#补全方式
self.completer.setFilterMode(Qt.MatchStartsWith)
self.completer.setCompletionMode(QCompleter.PopupCompletion)
self.combobox1.setCompleter(self.completer)
self.combobox2.setCompleter(self.completer)
self.combobox3.setCompleter(self.completer)
self.combobox4.setCompleter(self.completer)
self.combobox5.setCompleter(self.completer)
def timeDialog(self):
#获取信号
sender = self.sender()
if sender == self.bt1:
#获取下拉输入框1输入的游戏名
gamename = self.combobox1.currentText()
#通过字典game2idx查询获得的游戏名所对应的序列号
gameid = game2idx.get(gamename)
#没有序列号的情况,可以理解为未输入正确的游戏名,或者输入为空
if gameid == None:
#这种情况下生成一个MessageBox报错
reply = QMessageBox.information(self,'Error','请输入正确的游戏名!', QMessageBox.Close)
else:
#输入正确的情况,将游戏名字、ID,分别记录到一个字典里,方便保存与更改
gamedict[1] = gamename
idxdict[1] = gameid
#弹出一个文本输入框,要求输入对应的游戏时长
text, ok = QInputDialog.getDouble(self, '游戏时间', '请输入游戏时间:', min = 0.1)
#如果输入正确,将时长记录到一个字典中,方便保存与更改
if ok:
timedict[1] = text
elif sender == self.bt2:
gamename = self.combobox2.currentText()
gameid = game2idx.get(gamename)
if gameid == None:
reply = QMessageBox.information(self,'Error','请输入正确的游戏名!', QMessageBox.Close)
else:
gamedict[2] = gamename
idxdict[2] = gameid
text, ok = QInputDialog.getDouble(self, '游戏时间', '请输入游戏时间:', min = 0.1)
if ok:
timedict[2] = text
elif sender == self.bt3:
gamename = self.combobox3.currentText()
gameid = game2idx.get(gamename)
if gameid == None:
reply = QMessageBox.information(self,'Error','请输入正确的游戏名!', QMessageBox.Close)
else:
gamedict[3] = gamename
idxdict[3] = gameid
text, ok = QInputDialog.getDouble(self, '游戏时间', '请输入游戏时间:', min = 0.1)
if ok:
timedict[3] = text
elif sender == self.bt4:
gamename = self.combobox4.currentText()
gameid = game2idx.get(gamename)
if gameid == None:
reply = QMessageBox.information(self,'Error','请输入正确的游戏名!', QMessageBox.Close)
else:
gamedict[4] = gamename
idxdict[4] = gameid
text, ok = QInputDialog.getDouble(self, '游戏时间', '请输入游戏时间:', min = 0.1)
if ok:
timedict[4] = text
elif sender == self.bt5:
gamename = self.combobox5.currentText()
gameid = game2idx.get(gamename)
if gameid == None:
reply = QMessageBox.information(self,'Error','请输入正确的游戏名!', QMessageBox.Close)
else:
gamedict[5] = gamename
idxdict[5] = gameid
text, ok = QInputDialog.getDouble(self, '游戏时间', '请输入游戏时间:', min = 0.1)
if ok:
timedict[5] = text
def recommand(self):
#验证是否存在没有写入的数据
c = 0
for i in range(1,6):
if gamedict[i] == "NULL":
c+=1
if idxdict[i] == "NULL":
c+=1
if timedict[i] == "NULL":
c+=1
#全部写完的情况
if c == 0:
#将字典转化为列表
usertime = list(timedict.values())
useridx = list(idxdict.values())
#调用模型
allrecidx = UserSimilarity(useridx,usertime)
#降序排列数据
rr = np.argsort(-allrecidx)
#获取排行前五的游戏ID
top_k = rr[:5]
#将ID对应的游戏名字输入数组
for i in top_k:
recgame.append(idx2game[i])
#将数组转化为字符串并输出
reclist = ','.join(recgame)
reply = QMessageBox.information(self,'推荐的游戏','给您推荐的游戏是'+reclist, QMessageBox.Close)
#存在没有写完的数据,要求重新写入
else:
reply = QMessageBox.information(self,'Error','请输入全部数据!', QMessageBox.Close)
#主函数
if __name__ == "__main__":
app = QApplication(sys.argv)
w = Recommandation()
w.show()
sys.exit(app.exec_())
相关其它博客
基于矩阵分解算法的智能Steam游戏AI推荐系统——深度学习算法应用(含python、ipynb工程源码)+数据集(一)
基于矩阵分解算法的智能Steam游戏AI推荐系统——深度学习算法应用(含python、ipynb工程源码)+数据集(二)
基于矩阵分解算法的智能Steam游戏AI推荐系统——深度学习算法应用(含python、ipynb工程源码)+数据集(四)
工程源代码下载
其它资料下载
如果大家想继续了解人工智能相关学习路线和知识体系,欢迎大家翻阅我的另外一篇博客《重磅 | 完备的人工智能AI 学习——基础知识学习路线,所有资料免关注免套路直接网盘下载》
这篇博客参考了Github知名开源平台,AI技术平台以及相关领域专家:Datawhale,ApacheCN,AI有道和黄海广博士等约有近100G相关资料,希望能帮助到所有小伙伴们。