【Tensorflow 2.12 电影推荐系统之召回模型】

学习笔记

Tensorflow 2.12 智能电影推荐系统搭建学习笔记~

Tensorflow是谷歌开源的机器学习框架,可以帮助我们轻松地构建和部署机器学习模型。这里记录学习使用tensorflow-recommenders来构建一个电影推荐召回模型。
版本:python3.1.0、tensorflow2.12.0~

导入相关模块

# 导入os模块,主要提供系统相关的函数,如文件操作等,这里主要用于保存模型
import os
# pretty print,适合打印复杂的数据结构对象
import pprint
# 用于创建临时文件和目录
import tempfile
# 字典以及文本处理模块
from typing import Dict, Text
# 一个用于进行科学计算的Python库,它提供了高性能的多维数组对象(ndarray)以及用于处理这些数组的各种函数和工具
import numpy as np
# 导入TensorFlow
import tensorflow as tf
import tensorflow_recommenders as tfrs
# TensorFlow示例数据加载模块
import tensorflow_datasets as tfds

准备数据

加载数据

Movielens数据集是明尼苏达大学的GroupLens研究小组的经典数据集。它包含了一组用户对电影的评分,是推荐系统研究的重要数据集。

# 加载用户-电影评分数据
ratings = tfds.load("movielens/100k-ratings", split="train")
# 加载电影特征数据
movies = tfds.load("movielens/100k-movies", split="train")
# 打印
for x in ratings.take(1).as_numpy_iterator():
  pprint.pprint(x)
# 数据:{'bucketized_user_age': 45.0,'movie_genres': array([7], dtype=int64),'movie_id': b'357','movie_title': b"One Flew Over the Cuckoo's Nest (1975)",'raw_user_age': 46.0,'timestamp': 879024327,'user_gender': True,'user_id': b'138','user_occupation_label': 4,'user_occupation_text': b'doctor','user_rating': 4.0,'user_zip_code': b'53211'}
for x in movies.take(1).as_numpy_iterator():
  pprint.pprint(x)
# 数据:{'movie_genres': array([4], dtype=int64),'movie_id': b'1681','movie_title': b'You So Crazy (1994)'}

数据预处理

先对数据进行处理,方便后续进行模型构建、训练、验证等。

# 数据处理,保留使用到特征,方便训练(这里保留用户Id以及用户观看过的电影标题)
ratings = ratings.map(lambda x: {
    "movie_title": x["movie_title"],
    "user_id": x["user_id"],
})
movies = movies.map(lambda x: x["movie_title"])
# 设置随机数
tf.random.set_seed(42)
# 对数据集中的样本进行随机排序,以打乱样本的顺序(通过对数据集进行洗牌,可以确保模型在训练过程中能够更好地学习样本之间的关联性,提高模型的泛化能力)
shuffled = ratings.shuffle(100_000, seed=42, reshuffle_each_iteration=False)
# 切分数据集,训练数据以及测试数据
train = shuffled.take(80_000)
test = shuffled.skip(80_000).take(20_000)

生成词汇表

获取用户Id以及电影标题的词汇表,后续以词汇表将原始特征值映射到连续范围内的整数,方便训练时在嵌入表中查找相应的嵌入向量。

# 将数据集中的样本按照指定的大小分成批次,提高训练的效率
movie_titles = movies.batch(1_000)
user_ids = ratings.batch(1_000_000).map(lambda x: x["user_id"])
# 生成唯一用户Id词汇表,以及唯一电影标题词汇表
unique_movie_titles = np.unique(np.concatenate(list(movie_titles)))
unique_user_ids = np.unique(np.concatenate(list(user_ids)))
unique_movie_titles[:10]

构建模型

构建模型,这里构建双塔召回模型:将用户特征和电影特征分别通过独立的深度神经网络进行训练得到用户embedding以及电影embedding,并计算两个embedding之间的匹配程度,从而进行推荐。(有点类似矩阵分解模型)

嵌入维度

嵌入,把离散的特征映射为低维的向量表示,方便模型学习特征之间的关联关系。下面先定义嵌入向量的维度,较低的维度可能会导致信息损失;较高的维度可能构建更准确的模型,但相对拟合速度更慢,也更容易过拟合,同时也会增加计算和存储成本;嵌入维度是需要开发者根据具体任务和数据集的需求进行设置的超参数。

# 设置嵌入维度,嵌入维度是指用于表示离散特征的连续向量的维度大小。
embedding_dimension = 32

查询塔

双塔召回模型包含一个查询塔,以及一个候选条目塔,这里先实现查询塔,输出用户embedding。

# Sequential是一个用于构建序列模型的类。
# 序列模型是一种基本的神经网络架构,其中各层按顺序连接,数据从输入层经过一系列隐藏层,最终到达输出层;允许我们按顺序添加各种层,例如全连接层、卷积层、池化层等,以构建深度神经网络模型。
user_model = tf.keras.Sequential([
  # StringLookup是一个Keras预处理层,可以将原始特征值映射到连续范围内的整数,从而达到使用整数表示原始特征值(方便使用、查找,你可以想象为索引)
  # 这里将用户Id词汇表做为输入参数,通过处理将用户ID字符串转换为整数表示
  tf.keras.layers.StringLookup(vocabulary=unique_user_ids, mask_token=None),
  # Embedding是一个Keras嵌入层,用于将整数序列转换为密集向量表示。它可以将离散的整数输入(如用户ID)映射到连续的低维向量空间中。
  # Embedding层在自然语言处理(NLP)任务中非常常见,用于将单词映射到向量表示,以便在神经网络中进行处理。
  # + 1 的含义:添加一个额外的嵌入向量,用来代替未知的词汇,这样模型就可以在一定程度上处理未知词汇,从而提高模型的泛化能力
  tf.keras.layers.Embedding(len(unique_user_ids) + 1, embedding_dimension)
])

候选条目塔

接下来是实现候选条目塔,构建跟前面查询塔差不多,最后输出电影embedding。

movie_model = tf.keras.Sequential([
  # 将电影标题词汇表做为输入参数,通过处理将电影标题符串转换为整数表示
  tf.keras.layers.StringLookup(vocabulary=unique_movie_titles, mask_token=None),
  # 添加电影标题嵌入层
  tf.keras.layers.Embedding(len(unique_movie_titles) + 1, embedding_dimension)
])

模型指标

在训练数据中,都是正向的(用户,电影)样本对(即用户观看了哪些电影,做为隐式正向反馈、电影推荐的依据)。为了确定训练完模型的准确度,需要将模型计算出的评分与所有其他可能的候选条目的评分进行比较:如果正向对的分数高于所有其他候选条目,那么模型的准确度就非常高。
这里使用FactorizedTopK指标,是Tensorflow中封装的用于评估推荐模型的指标。FactorizedTopK会衡量模型对于给定用户的推荐结果中,排名前K的推荐条目中是否包含了用户真实感兴趣的条目。

# 将电影标题数据集做为参数(用于评估的候选数据集,这些候选数据集被用作隐式负样本)
metrics = tfrs.metrics.FactorizedTopK(
  candidates=movies.batch(128).map(movie_model)
)

损失函数

Retrieval类:它是一个wapper类,将损失函数和指标计算封装在一起,开发者只需要进行简单的声明、调用就可以了。这里使用的损失函数是:tf.keras.losses.CategoricalCrossentropy。

tf.keras.losses.CategoricalCrossentropy 是一个分类交叉熵损失函数,用于多分类任务中的损失计算。它计算分类问题中真实标签与预测标签之间的交叉熵损失,可以用于衡量模型预测的准确性。在多分类任务中,每个样本都有一个真实标签和一组预测标签,CategoricalCrossentropy损失函数将真实标签表示为一个one-hot编码的向量,并将预测标签表示为一个概率分布向量,它通过比较这两个向量之间的交叉熵来计算损失。该损失函数通常用于多分类神经网络中,可以与softmax激活函数一起使用。在训练过程中,优化器将尝试最小化该损失函数,以提高模型的准确性。

task = tfrs.tasks.Retrieval(
  metrics=metrics
)

定义双塔召回模型

定义完整模型:将查询塔以及候选条目塔组合在一起,实现双塔召回模型。
tfrs.Model是tensorflow-recommenders提供的模型基类,它简化了模型的构建过程:开发者只需要在init方法中设置组件,并实现 compute_loss 方法,该方法接收原始特征并返回损失值。最后实例化该模型,基础模型将负责创建适当的训练循环来拟合模型。

class MovielensModel(tfrs.Model):

  def __init__(self, user_model, movie_model):
    super().__init__()
    self.movie_model: tf.keras.Model = movie_model
    self.user_model: tf.keras.Model = user_model
    self.task: tf.keras.layers.Layer = task

  def compute_loss(self, features: Dict[Text, tf.Tensor], training=False) -> tf.Tensor:
    # 获取用户特征,并传递给用户模型,得到用户embedding
    user_embeddings = self.user_model(features["user_id"])
    # 获取电影特征,并传递给电影模型,得到电影embedding
    positive_movie_embeddings = self.movie_model(features["movie_title"])
    # 计算损失和指标并返回
    return self.task(user_embeddings, positive_movie_embeddings)

训练和评估

创建模型实例

创建模型,然后进行编译。编译时需要设置优化器(学习过程中的优化算法),这里使用自适应梯度优化算法(Adagrad),设置学习速率(learning_rate)为0.1(学习速率是一个重要的超参数,用来控制权重更新的幅度,它影响模型的训练速度和收敛速度。在训练模型的过程中,优化算法会根据样本数据和损失函数来计算出权重的更新方向和大小,学习速率就是用来控制这个更新大小的参数。需要注意的是,如果学习速率过大,容易错过最优值;如果过小,则收敛速度会很慢。因此,设置适当的学习速率非常重要,需要开发者根据实际情况进行尝试和调整)。

# 创建召回模型实例
model = MovielensModel(user_model, movie_model)
# 编译,设置优化算法:Adagrad(自适应梯度优化算法,是梯度下降优化算法的拓展,可以使学习过程中参数的更新幅度自适应)
model.compile(optimizer=tf.keras.optimizers.Adagrad(learning_rate=0.1))

缓存数据

将数据集的元素加载到内存或磁盘缓存中,提高训练过程中数据访问的效率,提高训练速度。

# 分批次加载到缓存中
cached_train = train.shuffle(100_000).batch(8192).cache()
cached_test = test.batch(4096).cache()

训练

进行模型训练,训练三个迭代,每个迭代包含多个批次。

model.fit(cached_train, epochs=3)

训练过程控制台输出如下:

Epoch 1/3
10/10 [==============================] - 13s 1s/step - factorized_top_k/top_1_categorical_accuracy: 0.0014 - factorized_top_k/top_5_categorical_accuracy: 0.0103 - factorized_top_k/top_10_categorical_accuracy: 0.0223 - factorized_top_k/top_50_categorical_accuracy: 0.1057 - factorized_top_k/top_100_categorical_accuracy: 0.1817 - loss: 69805.5433 - regularization_loss: 0.0000e+00 - total_loss: 69805.5433
Epoch 2/3
10/10 [==============================] - 11s 1s/step - factorized_top_k/top_1_categorical_accuracy: 0.0031 - factorized_top_k/top_5_categorical_accuracy: 0.0199 - factorized_top_k/top_10_categorical_accuracy: 0.0402 - factorized_top_k/top_50_categorical_accuracy: 0.1719 - factorized_top_k/top_100_categorical_accuracy: 0.2969 - loss: 67450.2784 - regularization_loss: 0.0000e+00 - total_loss: 67450.2784
Epoch 3/3
10/10 [==============================] - 10s 1s/step - factorized_top_k/top_1_categorical_accuracy: 0.0035 - factorized_top_k/top_5_categorical_accuracy: 0.0231 - factorized_top_k/top_10_categorical_accuracy: 0.0454 - factorized_top_k/top_50_categorical_accuracy: 0.1895 - factorized_top_k/top_100_categorical_accuracy: 0.3184 - loss: 66309.1023 - regularization_loss: 0.0000e+00 - total_loss: 66309.1023

备注:
top_50_categorical_accuracy: 0.1895,表示用户真实看过的电影在推荐的前50条电影的概率,这里是18.95%;
top_100_categorical_accuracy: 0.3184,表示用户真实看过的电影在推荐的前100条电影的概率,这里是31.84%;

评估

模型评估,计算模型在测试数据上的损失值和指定的评估指标,返回并打印在控制台,作为模型准确度的一个参考。

model.evaluate(cached_test, return_dict=True)

评估过程日志如下:

5/5 [==============================] - 3s 346ms/step - factorized_top_k/top_1_categorical_accuracy: 7.5000e-04 - factorized_top_k/top_5_categorical_accuracy: 0.0087 - factorized_top_k/top_10_categorical_accuracy: 0.0213 - factorized_top_k/top_50_categorical_accuracy: 0.1266 - factorized_top_k/top_100_categorical_accuracy: 0.2398 - loss: 31082.9040 - regularization_loss: 0.0000e+00 - total_loss: 31082.9040

可以看到训练完的模型推荐准确度还不是那么高,真实的候选条目出现在前100条推荐的候选条目的概率只有23.98%,还需要继续调整模型、超参数等来进行优化、训练,从而提高模型推荐的准确度。

预测

训练完模型后,可以使用BruteForce类将模型包装成基于暴力搜索的Top-K推荐层,并进行调用来计算Top-K推荐结果。

# 使用BruteForce进行包装
index = tfrs.layers.factorized_top_k.BruteForce(model.user_model)
index.index_from_dataset(tf.data.Dataset.zip((movies.batch(100),movies.batch(100).map(model.movie_model))))
# 获取用户Id:42的电影推荐结果,返回推荐的电影以及对应的评分
scores, titles = index(tf.constant(["42"]))
print(f"Recommendations for user 42: {titles[0, :3]}")

导出和加载模型

最后,把训练完的模型导出到文件保存起来,后续直接加载该模型就可以进行预测,不用重新进行训练。

在TensorFlow 2.0中,可以使用SavedModel格式来导出模型结构。SavedModel是一种用于保存和加载TensorFlow模型的通用格式,它包含了模型的结构、权重和计算图等信息。

# 创建临时目录保存模型
with tempfile.TemporaryDirectory() as tmp:
  path = os.path.join(tmp, "model")
  # 保存模型
  tf.saved_model.save(index, path)
  # 重新加载模型
  loaded = tf.saved_model.load(path)
  # 输入用户Id进行推荐
  scores, titles = loaded(["42"])
  # 打印推荐的前三个电影
  print(f"Recommendations: {titles[0][:3]}")

结尾

学习构建一个简单的推荐召回模型到这里就结束了,后续学习是构建一个简单的排序模型~
官网:https://tensorflow.google.cn/recommenders/examples/basic_retrieval

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值