个性化推荐系统-离线召回模型验证

背景

计划构建并优化一个覆盖前端与后端的个性化推荐系统中的离线召回模块。
此模块旨在通过高效的数据处理与分析,预先筛选出用户可能感兴趣的内容或商品,为后续的实时推荐流程提供丰富且精准的候选集。为了确保实施效果与性能,我们将设计最简前端界面以直观展示召回结果,同时构建后端服务来处理大量数据并响应前端请求。

在前端部分,将开发用户基本的交互界面,允许用户在不影响系统性能的前提下,轻松浏览和反馈召回结果,支持切换用户查询,从而辅助验证推荐算法的有效性与准确性。

后端方面,以验证模型以及算法为主,验证过程中不采用离线计算框架(
Spark、Hadoop、Hive),不设置缓存层。

具体参考
个性化推荐系统-离线召回模型框架

前端

使用vue开发

核心组件

<div class="title-with-margin">
      <h2>个性化推荐系统</h2>
    </div> 
    <!-- Top section with input and button -->
    <div class="input-and-button">
      <el-input v-model="uid" placeholder="请输入 UID(1-600)" clearable @keyup.enter="fetchUserData" />
      <el-button type="primary" @click="fetchUserData(currentPage.value)">推荐</el-button>
      <el-button type="primary" @click="gethistory(historycurrentPage.value)">历史行为</el-button>
    </div>

    <!-- Bottom section with pagination and list -->
    <el-table :data="userData" style="width: 100%; margin-top: 1rem;">
      <el-table-column prop="name" label="名称" width="220"> </el-table-column>
      <el-table-column prop="mediatype" label="类型" width="150"> </el-table-column>
      <el-table-column prop="catid" label="分类" width="100"> </el-table-column>    
       <el-table-column prop="abstract" label="简介" width="550" show-overflow-tooltip> </el-table-column>
       <!-- <el-table-column prop="content" label="内容" > </el-table-column> -->
       <el-table-column label="操作" width="150">
        <template #default="{ row }">
          <el-button size="small" type="text" @click="showreco(row)">操作</el-button>
        </template>
      </el-table-column>
    </el-table>

模拟操作

<el-dialog v-model="showDialog" title="模拟操作" width="60%">
      <div>
        <el-row :gutter="20">
        <el-col :span="6">
          <!-- icon="el-icon-thumb" -->
          <el-button :type="isLiked ? 'success' : 'primary'"  type="text" @click="onLike(currentMedia)">点赞 </el-button>
        </el-col>
        <el-col :span="6">
          <el-button :type="isPlay ? 'success' : 'primary'" type="text" @click="onView(currentMedia)">观看</el-button>
        </el-col>
        <el-col :span="6">
          <el-button :type="isColl ? 'success' : 'primary'" @click="onCollect(currentMedia)">收藏</el-button>
        </el-col>
        <el-col :span="6">
          <el-button :type="isShare ? 'success' : 'primary'" @click="onForward(currentMedia)">转发</el-button>
        </el-col>        
        </el-row>
      </div>    
       <div style="margin-top: 10px;">  
        <div >播放完整度</div>
        <div class="slider-demo-block">         
          <el-slider v-model="playrate" show-input @change="playhandleChange"/>
        </div>
       </div>       
      <h3>相关推荐</h3>
       <el-table :data="userData2" style="width: 100%; margin-top: 1rem;">
      <el-table-column prop="name" label="名称" width="200"> </el-table-column>
      <el-table-column prop="mediatype" label="类型" width="80"> </el-table-column>
      <el-table-column prop="catid" label="分类" width="120"> </el-table-column>
      <el-table-column prop="abstract" label="简介" width="300"> </el-table-column>       
      </el-table>
          <el-pagination
          background
          layout="prev, pager, next"
          :total="totalItems2"
          :page-size="pageSize"
          :current-page="currentPage2"
          @current-change="handleCurrentChange2"
          >
          </el-pagination>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="showDialog = false">关闭</el-button>
        </span>
      </template>
    </el-dialog>

用户历史行为

  <el-dialog v-model="historyshowDialog" title="用户历史操作记录" width="65%">           
     
      <el-table :data="historyuserData" style="width: 100%; margin-top: 1rem;">
      <el-table-column prop="name" label="名称" width="200"> </el-table-column>
      <el-table-column prop="mediatype" label="类型" width="80"> </el-table-column>
      <el-table-column prop="catid" label="分类" width="100"> </el-table-column>      
      <el-table-column prop="abstract" label="简介" width="300"> </el-table-column> 
      <el-table-column prop="score" label="评分" width="80"> </el-table-column>
      </el-table>
          <el-pagination
          background
          layout="prev, pager, next"
          :total="hisrorytotalItems"
          :page-size="pageSize"
          :current-page="historycurrentPage"
          @current-change="historyhandleCurrentChange"
          >
          </el-pagination>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="historyshowDialog = false">关闭</el-button>
        </span>
      </template>
    </el-dialog>

const get_history_api = async (page = historycurrentPage.value) => {
  console.log(historycurrentPage.value,page)
  try {
    const response = await axios.get('http://127.0.0.1:5000/useraction', {
      params: {
        page:  parseInt(page),
        pageSize: pageSize.value,
        uid: uid.value
      }
    });
    console.log(response.data.data)   
    historyuserData.value = response.data.data;
    hisrorytotalItems.value = response.data.total;
  } catch (error) {
    console.error('Error fetching data:', error);
  }
};

后端

导入依赖

from flask import Flask, request, jsonify
import pymysql
from flask_cors import CORS

启动服务

app = Flask(__name__)
CORS(app)
app.config['JSON_AS_ASCII'] = False

## db_config 数据库配置文件 参考之前的样例
def get_db_connection():
    return pymysql.connect(**db_config)

if __name__ == '__main__':    
    app.run(host='127.0.0.1', port=5000,debug=True)

根据uid获取推荐列表

解析参数,判断uid是否为0,分别进行不同处理

@app.route('/medialist', methods=['GET'])
def get_users():
    page = int(request.args.get('page', 1))
    per_page = int(request.args.get('pageSize', 10))
    try:
        uid = int(request.args.get('uid', '0'))
    except ValueError:
        uid = 0
    print("uid",uid)
    offset = (page - 1) * per_page
    res = "0" if page > 0 else "-1"
    total=300
    connection = get_db_connection()
    if(uid<=0):
        try:
            with connection.cursor() as cursor:
                sql = "SELECT mediaid,name,CONCAT(catid,subcatid,tag) AS catid,mediatype,abstract FROM mediainfo ORDER BY RAND() LIMIT %s OFFSET %s"
                #cursor.execute(sql, (per_page, offset))
                #results = cursor.fetchall()
                #users = [{'id': row[0], 'name': row[1]} for row in results]           
                cursor.execute(sql, (per_page, offset))
                results = cursor.fetchall()            
                resdata = { "res": res,"total": total,"data":results}
                return jsonify(resdata), 200
        finally:
            connection.close()
    else:
        total=100
        try:
            with connection.cursor() as cursor:
                sql = "SELECT  m.mediaid, m.name,CONCAT(m.catid,m.subcatid,m.tag) AS catid,m.mediatype,m.abstract FROM  uid_reco ur JOIN  mediainfo m ON ur.mediaid = m.mediaid WHERE  ur.uid = %s LIMIT %s OFFSET %s"
                #cursor.execute(sql, (per_page, offset))
                #results = cursor.fetchall()
                #users = [{'id': row[0], 'name': row[1]} for row in results]           
                cursor.execute(sql, (uid,per_page, offset))
                results = cursor.fetchall()            
                resdata = { "res": res,"total": total,"data":results}
                return jsonify(resdata), 200
        finally:
            connection.close()


相关推荐

内容相似度计算参考
相似度计算

@app.route('/relatereco', methods=['GET'])
def get_relate_reco():
    page = int(request.args.get('page', 1))
    per_page = int(request.args.get('pageSize', 10))
    try:
        mediaid = int(request.args.get('mediaid', '0'))
    except ValueError:
        mediaid = 0
    print("relatereco - mediaid",mediaid)
    offset = (page - 1) * per_page
    res = "0" if page > 0 else "-1"
    
    connection = get_db_connection()
    total=100
    try:
        with connection.cursor() as cursor:
            sql = "SELECT m.mediaid,m.name, CONCAT(m.catid, m.subcatid, m.tag) AS catid,  m.mediatype,  m.abstract FROM  media_similarity ms   JOIN mediainfo m   ON ms.mediaid_2 = m.mediaid WHERE ms.mediaid_1 = %s  LIMIT %s OFFSET %s"
            cursor.execute(sql, (mediaid,per_page, offset))
            results = cursor.fetchall()            
            resdata = { "res": res,"total": total,"data":results}
            return jsonify(resdata), 200
    finally:
        connection.close()

用户历史记录

app.route('/useraction', methods=['GET'])
def get_user_action():
    page = int(request.args.get('page', 1))
    per_page = int(request.args.get('pageSize', 10))
    try:
        uid = int(request.args.get('uid', '0'))
    except ValueError:
        uid = 0
    print("relatereco - mediaid",uid)
    offset = (page - 1) * per_page
    res = "0" if page > 0 else "-1"
    total=100
    connection = get_db_connection()   
    try:
        with connection.cursor() as cursor:
            sql = "SELECT m.mediaid,m.name,CONCAT(m.catid, m.subcatid, m.tag) AS catid,  m.mediatype,  m.abstract,u.score FROM  user_rate u  JOIN mediainfo m   ON u.mediaid= m.mediaid WHERE u.uid = %s  LIMIT %s OFFSET %s"
            cursor.execute(sql, (uid,per_page, offset))
            results = cursor.fetchall()            
            resdata = { "res": res,"total": total,"data":results}
            return jsonify(resdata), 200
    finally:
        connection.close()

用户行为数据上报

def handle_user_action():
    # 解析请求体中的 JSON 数据
    data = request.get_json()
    
    # 获取 uid, mediaid, score
    uid = data.get('uid')
    mediaid = data.get('mediaid')
    score = data.get('score')
    
    if not (uid and mediaid and score):
        return jsonify({'error': 'Missing required fields'}), 400
    connection = get_db_connection()   
    try:
        with connection.cursor() as cursor:
            sql = "insert user_rate (uid,mediaid,score)values(%s,%s,%s)"
            cursor.execute(sql, (uid,mediaid, score))
            results = cursor.fetchall()            
            resdata = { "res": results}
            return jsonify(resdata), 200
    finally:
        connection.close()
        return jsonify({'error': 'DB connection fields'}), 400

  • 10
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值