国外网站源代码,学做网站教程视频,cnzz统计代码如何添加到网站上去,杭州哪里找网站建设的兼职文章目录 简介数据集设置准备数据下载并准备数据框将电影评分数据转换为序列 定义元数据为训练和评估创建 tf.data.Dataset创建模型输入编码输入特征创建一个二叉搜索树模型运行训练和评估实验结论 描述#xff1a; 使用行为序列Transformer#xff08;BST#xff09;模型在… 文章目录 简介数据集设置准备数据下载并准备数据框将电影评分数据转换为序列 定义元数据为训练和评估创建 tf.data.Dataset创建模型输入编码输入特征创建一个二叉搜索树模型运行训练和评估实验结论 描述 使用行为序列TransformerBST模型在Movielens上进行评分预测。 简介
本示例演示了由Qiwei Chen等人使用Movielens数据集使用行为序列转换器BST模型。BST模型利用用户在观看和评分电影时的顺序行为以及用户配置文件和电影特征来预测用户对目标电影的评分。
更具体地说BST模型旨在通过接受以下输入来预测目标电影的评分
用户观看的电影的固定长度的序列其中包含movie_ids。用户观看的电影的固定长度的序列其中包含电影的ratings。用户特征的集合包括user_id、sex、occupation和age_group。输入序列和目标电影中每个电影的genres的集合。要预测评分的target_movie_id。
本示例对原始BST模型进行了以下修改
我们将电影特征genres合并到每个输入序列和目标电影的嵌入处理中而不是将它们视为转换器层外的“其他特征”。我们利用输入序列中电影的评分以及它们在序列中的位置在将它们馈送到自注意力层之前对它们进行更新。
请注意此示例应在TensorFlow 2.4或更高版本上运行。
数据集
我们使用Movielens数据集的1M版本。 该数据集包括来自6000个用户对4000部电影的大约100万个评分 还包括一些用户特征和电影类型。此外还提供了每个用户-电影评分的时间戳 这允许为每个用户创建电影评分序列正如BST模型所期望的那样。
设置
# 导入所需的库
import os # 用于操作系统相关的功能
os.environ[KERAS_BACKEND] tensorflow # 设置环境变量指定使用tensorflow作为Keras的后端import math # 用于数学计算
from zipfile import ZipFile # 用于解压缩zip文件
from urllib.request import urlretrieve # 用于从URL下载文件import keras # Keras库用于构建深度学习模型
import numpy as np # 用于处理数值数组和矩阵
import pandas as pd # 用于处理数据表格
import tensorflow as tf # TensorFlow库用于构建和训练机器学习模型
from keras import layers # Keras库中的层模块
from keras.layers import StringLookup # Keras库中的字符串查找层模块准备数据
下载并准备数据框
首先让我们下载movielens数据。
下载的文件夹将包含三个数据文件users.datmovies.dat和ratings.dat。
# 导入必要的库
from urllib.request import urlretrieve
from zipfile import ZipFile# 下载movielens数据集的zip文件
urlretrieve(http://files.grouplens.org/datasets/movielens/ml-1m.zip, movielens.zip)# 创建一个ZipFile对象用于解压缩zip文件
zip_file ZipFile(movielens.zip, r)# 解压缩zip文件中的所有内容到当前目录
zip_file.extractall()然后我们使用正确的列名将数据加载到pandas DataFrames中。
# 导入所需的库
import pandas as pd# 读取用户数据
users pd.read_csv(ml-1m/users.dat, # 用户数据文件路径sep::, # 分隔符为双冒号names[user_id, sex, age_group, occupation, zip_code], # 列名encodingISO-8859-1, # 使用ISO-8859-1编码enginepython, # 使用Python解析引擎
)# 读取评分数据
ratings pd.read_csv(ml-1m/ratings.dat, # 评分数据文件路径sep::, # 分隔符为双冒号names[user_id, movie_id, rating, unix_timestamp], # 列名encodingISO-8859-1, # 使用ISO-8859-1编码enginepython, # 使用Python解析引擎
)# 读取电影数据
movies pd.read_csv(ml-1m/movies.dat, # 电影数据文件路径sep::, # 分隔符为双冒号names[movie_id, title, genres], # 列名encodingISO-8859-1, # 使用ISO-8859-1编码enginepython, # 使用Python解析引擎
)在这里我们对列的数据类型进行一些简单的数据处理以修复数据类型。
# 给用户数据添加user_id前缀
users[user_id] users[user_id].apply(lambda x: fuser_{x})# 给用户数据添加age_group前缀
users[age_group] users[age_group].apply(lambda x: fgroup_{x})# 给用户数据添加occupation前缀
users[occupation] users[occupation].apply(lambda x: foccupation_{x})# 给电影数据添加movie_id前缀
movies[movie_id] movies[movie_id].apply(lambda x: fmovie_{x})# 给评分数据添加movie_id前缀
ratings[movie_id] ratings[movie_id].apply(lambda x: fmovie_{x})# 给评分数据添加user_id前缀
ratings[user_id] ratings[user_id].apply(lambda x: fuser_{x})# 将评分数据中的rating转换为浮点型
ratings[rating] ratings[rating].apply(lambda x: float(x))每部电影都有多个类型。我们在movies数据框中将它们拆分为单独的列。
# 定义电影类型列表
genres [Action, Adventure, Animation, Childrens, Comedy, Crime]
genres [Documentary, Drama, Fantasy, Film-Noir, Horror, Musical]
genres [Mystery, Romance, Sci-Fi, Thriller, War, Western]# 遍历电影类型列表
for genre in genres:# 对于每个电影类型将movies[genres]中的每个电影的类型字符串进行处理# 使用lambda函数将字符串转换为对应的二进制值1表示包含该类型0表示不包含该类型movies[genre] movies[genres].apply(lambda values: int(genre in values.split(|)))将电影评分数据转换为序列
首先让我们使用unix_timestamp对评分数据进行排序然后按user_id对movie_id值和rating值进行分组。
输出的DataFrame将为每个user_id记录两个有序列表按评分日期排序他们评价过的电影和他们对这些电影的评分。
# 导入必要的库
import pandas as pd# 按照unix_timestamp列对ratings数据集进行排序并按user_id分组
ratings_group ratings.sort_values(by[unix_timestamp]).groupby(user_id)# 创建一个新的数据框ratings_data包含以下列user_id, movie_ids, ratings, timestamps
ratings_data pd.DataFrame(data{user_id: list(ratings_group.groups.keys()), # 获取分组后的用户IDmovie_ids: list(ratings_group.movie_id.apply(list)), # 获取每个用户对应的电影ID列表ratings: list(ratings_group.rating.apply(list)), # 获取每个用户对应的评分列表timestamps: list(ratings_group.unix_timestamp.apply(list)), # 获取每个用户对应的时间戳列表}
)现在让我们将movie_ids列表分割成一组固定长度的序列。 我们对ratings也做同样的操作。设置sequence_length变量来改变输入序列的长度。 您还可以更改step_size来控制为每个用户生成的序列数量。
# 定义窗口大小和步长
sequence_length 4
step_size 2# 创建序列函数输入值、窗口大小和步长返回序列列表
def create_sequences(values, window_size, step_size):sequences [] # 存储序列的列表start_index 0 # 起始索引while True:end_index start_index window_size # 结束索引seq values[start_index:end_index] # 根据窗口大小切片得到序列if len(seq) window_size: # 如果序列长度小于窗口大小seq values[-window_size:] # 则取最后窗口大小长度的序列if len(seq) window_size: # 如果序列长度等于窗口大小sequences.append(seq) # 将序列添加到列表中break # 结束循环sequences.append(seq) # 将序列添加到列表中start_index step_size # 更新起始索引return sequences # 返回序列列表# 对电影ID列应用create_sequences函数将结果赋值给movie_ids列
ratings_data.movie_ids ratings_data.movie_ids.apply(lambda ids: create_sequences(ids, sequence_length, step_size)
)# 对评分列应用create_sequences函数将结果赋值给ratings列
ratings_data.ratings ratings_data.ratings.apply(lambda ids: create_sequences(ids, sequence_length, step_size)
)# 删除timestamps列
del ratings_data[timestamps]之后我们处理输出使每个序列在DataFrame中成为单独的记录。此外我们将用户特征与评分数据进行连接。
# 导入所需的库
import pandas as pd# 将ratings_data中的movie_ids列拆分成多行每行只包含一个电影ID并重置索引
ratings_data_movies ratings_data[[user_id, movie_ids]].explode(movie_ids, ignore_indexTrue)# 将ratings_data中的ratings列拆分成多行每行只包含一个评分并重置索引
ratings_data_rating ratings_data[[ratings]].explode(ratings, ignore_indexTrue)# 将拆分后的movie_ids和ratings两列合并为一个DataFrame
ratings_data_transformed pd.concat([ratings_data_movies, ratings_data_rating], axis1)# 根据user_id列将ratings_data_transformed与users进行连接
ratings_data_transformed ratings_data_transformed.join(users.set_index(user_id), onuser_id)# 将movie_ids列中的每个元素转换为字符串并用逗号分隔
ratings_data_transformed.movie_ids ratings_data_transformed.movie_ids.apply(lambda x: ,.join(x))# 将ratings列中的每个元素转换为字符串并用逗号分隔
ratings_data_transformed.ratings ratings_data_transformed.ratings.apply(lambda x: ,.join([str(v) for v in x]))# 删除ratings_data_transformed中的zip_code列
del ratings_data_transformed[zip_code]# 将列名movie_ids改为sequence_movie_ids将列名ratings改为sequence_ratings
ratings_data_transformed.rename(columns{movie_ids: sequence_movie_ids, ratings: sequence_ratings}, inplaceTrue)使用sequence_length为4和step_size为2我们最终得到498,623个序列。
最后我们将数据分割为训练集和测试集分别占总数据的85%和15%并将它们存储为CSV文件。
import numpy as np# 生成一个与ratings_data_transformed.index长度相同的随机数数组每个元素都是0到1之间的随机数
random_selection np.random.rand(len(ratings_data_transformed.index)) 0.85# 根据随机数数组选择85%的数据作为训练数据
train_data ratings_data_transformed[random_selection]# 根据随机数数组选择15%的数据作为测试数据
test_data ratings_data_transformed[~random_selection]# 将训练数据保存为CSV文件不包含索引列使用竖线作为分隔符不包含表头
train_data.to_csv(train_data.csv, indexFalse, sep|, headerFalse)# 将测试数据保存为CSV文件不包含索引列使用竖线作为分隔符不包含表头
test_data.to_csv(test_data.csv, indexFalse, sep|, headerFalse)定义元数据
# 定义CSV_HEADER为ratings_data_transformed的列名列表
CSV_HEADER list(ratings_data_transformed.columns)# 定义CATEGORICAL_FEATURES_WITH_VOCABULARY为一个字典包含了几个特征及其对应的唯一值列表
CATEGORICAL_FEATURES_WITH_VOCABULARY {user_id: list(users.user_id.unique()), # 用户ID特征对应的唯一值列表movie_id: list(movies.movie_id.unique()), # 电影ID特征对应的唯一值列表sex: list(users.sex.unique()), # 性别特征对应的唯一值列表age_group: list(users.age_group.unique()), # 年龄组特征对应的唯一值列表occupation: list(users.occupation.unique()), # 职业特征对应的唯一值列表
}# 定义USER_FEATURES为一个列表包含了用户特征
USER_FEATURES [sex, age_group, occupation]# 定义MOVIE_FEATURES为一个列表包含了电影特征
MOVIE_FEATURES [genres]为训练和评估创建 tf.data.Dataset
# 定义一个函数get_dataset_from_csv用于从csv文件中获取数据集
# 参数
# - csv_file_pathcsv文件的路径
# - shuffle是否对数据进行洗牌默认为False
# - batch_size批处理的大小默认为128def get_dataset_from_csv(csv_file_path, shuffleFalse, batch_size128):# 定义一个内部函数process用于处理特征# 参数# - features特征数据def process(features):# 从特征中获取电影ID序列的字符串movie_ids_string features[sequence_movie_ids]# 将电影ID序列字符串按逗号分割并转换为张量sequence_movie_ids tf.strings.split(movie_ids_string, ,).to_tensor()# 序列中的最后一个电影ID是目标电影features[target_movie_id] sequence_movie_ids[:, -1]# 将特征中的电影ID序列更新为除了最后一个电影ID之外的序列features[sequence_movie_ids] sequence_movie_ids[:, :-1]# 从特征中获取评分序列的字符串ratings_string features[sequence_ratings]# 将评分序列字符串按逗号分割并转换为浮点数类型的张量sequence_ratings tf.strings.to_number(tf.strings.split(ratings_string, ,), tf.dtypes.float32).to_tensor()# 序列中的最后一个评分是模型要预测的目标target sequence_ratings[:, -1]# 将特征中的评分序列更新为除了最后一个评分之外的序列features[sequence_ratings] sequence_ratings[:, :-1]return features, target# 使用tf.data.experimental.make_csv_dataset函数从csv文件中创建数据集dataset tf.data.experimental.make_csv_dataset(csv_file_path,batch_sizebatch_size,column_namesCSV_HEADER,num_epochs1,headerFalse,field_delim|,shuffleshuffle,).map(process)return dataset创建模型输入
# 定义一个函数create_model_inputs用于创建模型的输入def create_model_inputs():# 返回一个字典包含模型的输入return {user_id: keras.Input(nameuser_id, shape(1,), dtypestring), # 用户ID输入形状为(1,)数据类型为字符串sequence_movie_ids: keras.Input(namesequence_movie_ids, shape(sequence_length - 1,), dtypestring), # 电影序列ID输入形状为(sequence_length - 1,)数据类型为字符串target_movie_id: keras.Input(nametarget_movie_id, shape(1,), dtypestring), # 目标电影ID输入形状为(1,)数据类型为字符串sequence_ratings: keras.Input(namesequence_ratings, shape(sequence_length - 1,), dtypetf.float32), # 电影评分序列输入形状为(sequence_length - 1,)数据类型为浮点数sex: keras.Input(namesex, shape(1,), dtypestring), # 性别输入形状为(1,)数据类型为字符串age_group: keras.Input(nameage_group, shape(1,), dtypestring), # 年龄组输入形状为(1,)数据类型为字符串occupation: keras.Input(nameoccupation, shape(1,), dtypestring), # 职业输入形状为(1,)数据类型为字符串}编码输入特征
encode_input_features 方法的工作原理如下 使用 layers.Embedding 对每个分类用户特征进行编码其中嵌入维度等于特征的词汇量的平方根。 这些特征的嵌入被连接起来形成一个单一的输入张量。 使用 layers.Embedding 对电影序列中的每个电影和目标电影进行编码其中维度大小为电影数量的平方根。 对每个电影的多热流派向量与其嵌入向量进行连接并使用非线性 layers.Dense 处理输出相同电影嵌入维度的向量。 在序列中的每个电影嵌入中添加位置嵌入然后乘以其来自评分序列的评分。 将目标电影嵌入连接到序列电影嵌入中生成一个形状为 [batch size, sequence length, embedding size] 的张量符合变压器架构的注意力层的预期形状。 该方法返回一个由两个元素组成的元组encoded_transformer_features 和 encoded_other_features。
# 编码输入特征## 定义函数encode_input_features用于将输入特征进行编码
### 参数
- inputs包含输入特征的字典
- include_user_id是否包含用户ID默认为True
- include_user_features是否包含用户特征默认为True
- include_movie_features是否包含电影特征默认为True### 返回值
- encoded_transformer_features编码后的转换器特征
- encoded_other_features编码后的其他特征## 初始化编码后的转换器特征列表和其他特征列表
encoded_transformer_features []
encoded_other_features []## 初始化其他特征名称列表
other_feature_names []## 如果include_user_id为True则将user_id添加到其他特征名称列表中
if include_user_id:other_feature_names.append(user_id)## 如果include_user_features为True则将USER_FEATURES中的特征名称添加到其他特征名称列表中
if include_user_features:other_feature_names.extend(USER_FEATURES)## 对用户特征进行编码
for feature_name in other_feature_names:# 将字符串输入值转换为整数索引vocabulary CATEGORICAL_FEATURES_WITH_VOCABULARY[feature_name]idx StringLookup(vocabularyvocabulary, mask_tokenNone, num_oov_indices0)(inputs[feature_name])# 计算嵌入维度embedding_dims int(math.sqrt(len(vocabulary)))# 创建指定维度的嵌入层embedding_encoder layers.Embedding(input_dimlen(vocabulary),output_dimembedding_dims,namef{feature_name}_embedding,)# 将索引值转换为嵌入表示encoded_other_features.append(embedding_encoder(idx))## 创建用户特征的单个嵌入向量
if len(encoded_other_features) 1:encoded_other_features layers.concatenate(encoded_other_features)
elif len(encoded_other_features) 1:encoded_other_features encoded_other_features[0]
else:encoded_other_features None## 创建电影嵌入编码器
movie_vocabulary CATEGORICAL_FEATURES_WITH_VOCABULARY[movie_id]
movie_embedding_dims int(math.sqrt(len(movie_vocabulary)))
# 创建查找表将字符串值转换为整数索引
movie_index_lookup StringLookup(vocabularymovie_vocabulary,mask_tokenNone,num_oov_indices0,namemovie_index_lookup,
)
# 创建指定维度的嵌入层
movie_embedding_encoder layers.Embedding(input_dimlen(movie_vocabulary),output_dimmovie_embedding_dims,namefmovie_embedding,
)
# 创建电影类型的向量查找表
genre_vectors movies[genres].to_numpy()
movie_genres_lookup layers.Embedding(input_dimgenre_vectors.shape[0],output_dimgenre_vectors.shape[1],embeddings_initializerkeras.initializers.Constant(genre_vectors),trainableFalse,namegenres_vector,
)
# 创建电影类型的处理层
movie_embedding_processor layers.Dense(unitsmovie_embedding_dims,activationrelu,nameprocess_movie_embedding_with_genres,
)## 定义一个函数用于编码给定的电影ID
def encode_movie(movie_id):# 将字符串输入值转换为整数索引movie_idx movie_index_lookup(movie_id)movie_embedding movie_embedding_encoder(movie_idx)encoded_movie movie_embeddingif include_movie_features:movie_genres_vector movie_genres_lookup(movie_idx)encoded_movie movie_embedding_processor(layers.concatenate([movie_embedding, movie_genres_vector]))return encoded_movie## 编码目标电影ID
target_movie_id inputs[target_movie_id]
encoded_target_movie encode_movie(target_movie_id)## 编码序列电影ID
sequence_movies_ids inputs[sequence_movie_ids]
encoded_sequence_movies encode_movie(sequence_movies_ids)
# 创建位置嵌入
position_embedding_encoder layers.Embedding(input_dimsequence_length,output_dimmovie_embedding_dims,nameposition_embedding,
)
positions tf.range(start0, limitsequence_length - 1, delta1)
encodded_positions position_embedding_encoder(positions)
# 获取序列评分将其合并到电影编码中
sequence_ratings inputs[sequence_ratings]
sequence_ratings keras.ops.expand_dims(sequence_ratings, -1)
# 将位置编码添加到电影编码中并乘以评分
encoded_sequence_movies_with_poistion_and_rating layers.Multiply()([(encoded_sequence_movies encodded_positions), sequence_ratings]
)# 构建转换器的输入
for i in range(sequence_length - 1):feature encoded_sequence_movies_with_poistion_and_rating[:, i, ...]feature keras.ops.expand_dims(feature, 1)encoded_transformer_features.append(feature)
encoded_transformer_features.append(encoded_target_movie)encoded_transformer_features layers.concatenate(encoded_transformer_features, axis1
)return encoded_transformer_features, encoded_other_features创建一个二叉搜索树模型
# 创建模型## 设置参数include_user_id False # 是否包含用户ID特征
include_user_features False # 是否包含用户特征
include_movie_features False # 是否包含电影特征hidden_units [256, 128] # 隐藏层单元数
dropout_rate 0.1 # Dropout比例
num_heads 3 # 多头注意力机制的头数## 创建模型函数def create_model():inputs create_model_inputs() # 创建模型输入transformer_features, other_features encode_input_features(inputs, include_user_id, include_user_features, include_movie_features) # 编码输入特征# 创建多头注意力层attention_output layers.MultiHeadAttention(num_headsnum_heads, key_dimtransformer_features.shape[2], dropoutdropout_rate)(transformer_features, transformer_features)# Transformer块attention_output layers.Dropout(dropout_rate)(attention_output)x1 layers.Add()([transformer_features, attention_output])x1 layers.LayerNormalization()(x1)x2 layers.LeakyReLU()(x1)x2 layers.Dense(unitsx2.shape[-1])(x2)x2 layers.Dropout(dropout_rate)(x2)transformer_features layers.Add()([x1, x2])transformer_features layers.LayerNormalization()(transformer_features)features layers.Flatten()(transformer_features)# 添加其他特征if other_features is not None:features layers.concatenate([features, layers.Reshape([other_features.shape[-1]])(other_features)])# 全连接层for num_units in hidden_units:features layers.Dense(num_units)(features)features layers.BatchNormalization()(features)features layers.LeakyReLU()(features)features layers.Dropout(dropout_rate)(features)outputs layers.Dense(units1)(features) # 输出层model keras.Model(inputsinputs, outputsoutputs) # 创建模型return modelmodel create_model() # 创建模型运行训练和评估实验
# 编译模型
model.compile(optimizerkeras.optimizers.Adagrad(learning_rate0.01), # 使用Adagrad优化器学习率为0.01losskeras.losses.MeanSquaredError(), # 使用均方误差作为损失函数metrics[keras.metrics.MeanAbsoluteError()], # 使用平均绝对误差作为评估指标
)# 读取训练数据
train_dataset get_dataset_from_csv(train_data.csv, shuffleTrue, batch_size265)# 使用训练数据拟合模型
model.fit(train_dataset, epochs5)# 读取测试数据
test_dataset get_dataset_from_csv(test_data.csv, batch_size265)# 在测试数据上评估模型
_, rmse model.evaluate(test_dataset, verbose0)
print(fTest MAE: {round(rmse, 3)}) # 打印测试数据上的平均绝对误差你应该在测试数据上达到或接近0.7的平均绝对误差MAE。
结论
BST模型在其架构中使用Transformer层来捕捉推荐中用户行为序列的顺序信号。
您可以尝试使用不同的配置来训练该模型例如增加输入序列长度并将模型训练更多个周期。此外您还可以尝试包括其他特征如电影发布年份和客户邮编以及包括性别X类型等交叉特征。