吴恩达机器学习 第三课 week2 推荐算法(下)

目录

01 学习目标

02 基于内容的过滤算法

03 实现“电影推荐系统”

3.1 问题描述

3.2 算法实现

04 大项目(数据很大)的推荐方法※

4.1 方法原理

4.2 实施示例

05 总结


01 学习目标

     (1)理解基于内容的过滤算法(Content-Based Filtering Algorithm)推荐原理

     (2)利用基于内容的过滤算法构建“电影推荐系统”

     (3)了解大项目(数据集很大)的推荐方法

02 基于内容的过滤算法

      基于内容的过滤算法,是采用神经网络架构提取用户特征物品特征后向用户推荐相似物品。用户特征包括:年龄、性别、国家、喜好和评分等等,物品(比如电影)特征包括:年份、类型、影评及所有评价用户的平均年龄等等。

       Content-based filtering的原理如下图所示:

      首先(准备内容),准备两组数据,一组是用户内容Xu,一组是物品的内容Xm。(需说明,Xu数组内均为工程特征,即经过处理后的内容,而Xm数组由原始数据和工程特征组合构成)

      然后(提取特征),定义神经网络模型,其架构为两个并联的模型,将Xu和Xm作为输入,可分别输出用户特征Vu和物品特征Vm。

      接着(用户喜好预测),利用多元线性回归模型,预测用户对所有物品的评分,但Content-based filtering中的回归模型是没有偏置项b的,如下:

\textbf{Y}_j^{(i)}=\textbf{V}_u^{(j)}\cdot \textbf{V}_m^{(i)}

式中,Y为预测结果(分数),i为物品的序数,j为用户的序数。

      现在(相似度计算),根据物品的特征计算物品之间的相似度,物品相似度有很多计算方法,这里根据课程采用欧氏距离平方:

distance=||\textbf{V}_m^{(k)}-\textbf{V}_m^{(i)}||^2=\sum_{l=1}^{n}(v_{ml}^{(k)}-v_{ml}^{(i)})^2

        最后(生成推荐),有了用户对每个物品的预测分数,也有了物品相似度,就可以向用户推荐他喜爱之物的相似物品了。

03 实现“电影推荐系统”

3.1 问题描述

       这次,我们依然采用从GroupLens网站的 MovieLens 栏目下载电影数据集来构建“电影推荐系统”。我们的任务是:根据一位用户的行为,预测其喜好,并为其推荐电影。

3.2 算法实现

    (1)导入模块

# 导包
import pickle
import csv
import numpy as np
import numpy.ma as ma
from numpy import genfromtxt
from collections import defaultdict
import pandas as pd
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.models import Model
from sklearn.preprocessing import StandardScaler, MinMaxScaler
from sklearn.model_selection import train_test_split
import tabulate
pd.set_option("display.precision", 1)
# Import Lambda layer at the beginning if not already imported
from tensorflow.keras.layers import Lambda

      (2)读取数据

       ① 由于数据较多,定义函数读取:

# 定义函数,读取数据
def load_data():item_train = genfromtxt('./data/content_item_train.csv', delimiter=',')user_train = genfromtxt('./data/content_user_train.csv', delimiter=',')y_train    = genfromtxt('./data/content_y_train.csv', delimiter=',')with open('./data/content_item_train_header.txt', newline='') as f:item_features = list(csv.reader(f))[0]with open('./data/content_user_train_header.txt', newline='') as f:user_features = list(csv.reader(f))[0]item_vecs = genfromtxt('./data/content_item_vecs.csv', delimiter=',')movie_dict = defaultdict(dict)count = 0with open('./data/content_movie_list.csv', newline='') as csvfile:reader = csv.reader(csvfile, delimiter=',', quotechar='"')for line in reader:if count == 0: count +=1  #skip headerelse:count +=1movie_id = int(line[0])  movie_dict[movie_id]["title"] = line[1]  movie_dict[movie_id]["genres"] =line[2]  with open('./data/content_user_to_genre.pickle', 'rb') as f:user_to_genre = pickle.load(f)return(item_train, user_train, y_train, item_features, user_features, item_vecs, movie_dict, user_to_genre)

       ② 读取数据:

# 导入原始数据
item_train, user_train, y_train, item_features, user_features, item_vecs, movie_dict, user_to_genre = load_data()num_user_features = user_train.shape[1] - 3  # 原始数据移除用户ID、评论总数、平均评分三列
num_item_features = item_train.shape[1] - 1  # 原始数据移除电影ID列
uvs = 3  # 用户特征向量开始索引
ivs = 3  # 物品特征向量开始索引
u_s = 3  # 训练集中用户内容开始列索引
i_s = 1  # 训练集中物品内容开始列索引
scaledata = True  # 应用标准化尺度缩放

     (3)数据处理、拆分

       ① 数据标准化:

# 原始数据标准化:均值为0、标准差为1
if scaledata:item_train_save = item_trainuser_train_save = user_trainscalerItem = StandardScaler()scalerItem.fit(item_train)item_train = scalerItem.transform(item_train)scalerUser = StandardScaler()scalerUser.fit(user_train)user_train = scalerUser.transform(user_train)

       ② 数据拆分为训练集和测试集:

# 数据集拆分,训练集:测试集=8:2
item_train, item_test = train_test_split(item_train, train_size=0.80, shuffle=True, random_state=1)
user_train, user_test = train_test_split(user_train, train_size=0.80, shuffle=True, random_state=1)
y_train, y_test       = train_test_split(y_train,    train_size=0.80, shuffle=True, random_state=1)

       ③ 数据集的数据归一化:

# 训练集、测试集数据归一化
scaler = MinMaxScaler((-1, 1))
scaler.fit(y_train.reshape(-1, 1))
ynorm_train = scaler.transform(y_train.reshape(-1, 1))
ynorm_test = scaler.transform(y_test.reshape(-1, 1))

      (4)人工神经网络模型提取特征

       ① 定义模型架构:

# 定义神经网络模型
num_outputs = 32
tf.random.set_seed(1)
user_NN = tf.keras.models.Sequential([  tf.keras.layers.Dense(256, activation='relu'),tf.keras.layers.Dense(128, activation='relu'),tf.keras.layers.Dense(num_outputs) 
])item_NN = tf.keras.models.Sequential([     tf.keras.layers.Dense(256, activation='relu'),tf.keras.layers.Dense(128, activation='relu'),tf.keras.layers.Dense(num_outputs)  
])# 创建用户输入并指向基础网络
input_user = tf.keras.layers.Input(shape=(num_user_features,))
vu = user_NN(input_user)# 创建物品输入并指向基础网络
input_item = tf.keras.layers.Input(shape=(num_item_features,))
vm = item_NN(input_item)# 使用 Keras Lambda layer计算L2范数标准化向量
norm_user = Lambda(lambda x: tf.linalg.l2_normalize(x, axis=1))(vu)
norm_item = Lambda(lambda x: tf.linalg.l2_normalize(x, axis=1))(vm)# 点乘计算两个标准化向量
output = tf.keras.layers.Dot(axes=1)([norm_user, norm_item])# 指定模型的输入和输出
model = Model([input_user, input_item], output)model.summary()

       运行以上代码,结果如下(warning不用理会)

       ② 模型编译及训练:

# 模型编译、训练
tf.random.set_seed(1)
cost_fn = tf.keras.losses.MeanSquaredError()
opt = keras.optimizers.Adam(learning_rate=0.01)
model.compile(optimizer=opt,loss=cost_fn)
model.fit([user_train[:, u_s:], item_train[:, i_s:]], ynorm_train, epochs=20)

        运行以上代码,结果如下:

      

      ③ 模型评估:

# 模型评估
model.evaluate([user_test[:, u_s:], item_test[:, i_s:]], ynorm_test)

         运行以上代码,结果如下:

      

       (5)用户喜好预测

        ① 定义函数,提取指定用户的信息内容:

def get_user_vecs(user_id, user_train, item_vecs, user_to_genre):""" 输入user_id, user_train, item_vecs, user_to_genre,返回:user_vecs:用户特征向量,与电影特征向量item_vecs同尺度y vector: 用户对所有电影的评分 """# 判断id是否存在if user_id not in user_to_genre:print("error: unknown user id")return(None)else:user_vec_found = Falsefor i in range(len(user_train)):if user_train[i, 0] == user_id:user_vec = user_train[i]user_vec_found = Truebreakif not user_vec_found:print("error in get_user_vecs, did not find uid in user_train")num_items = len(item_vecs)user_vecs = np.tile(user_vec, (num_items, 1))y = np.zeros(num_items)for i in range(num_items): movie_id = item_vecs[i, 0]if movie_id in user_to_genre[user_id]['movies']:rating = user_to_genre[user_id]['movies'][movie_id]else:rating = 0y[i] = ratingreturn(user_vecs, y)

         ② 定义函数,根据用户内容(利用model)预测并对预测结果排序:

# 对预测结果排序
def predict_uservec(user_vecs, item_vecs, model, u_s, i_s, scaler, ScalerUser, ScalerItem, scaledata=False):""" given a user vector, does the prediction on all movies in item_vecs returnsan array predictions sorted by predicted rating,arrays of user and item, sorted by predicted rating sorting index"""if scaledata:scaled_user_vecs = ScalerUser.transform(user_vecs)scaled_item_vecs = ScalerItem.transform(item_vecs)y_p = model.predict([scaled_user_vecs[:, u_s:], scaled_item_vecs[:, i_s:]])else:y_p = model.predict([user_vecs[:, u_s:], item_vecs[:, i_s:]])y_pu = scaler.inverse_transform(y_p)if np.any(y_pu < 0) : print("Error, expected all positive predictions")sorted_index = np.argsort(-y_pu,axis=0).reshape(-1).tolist()  #negate to get largest rating firstsorted_ypu   = y_pu[sorted_index]sorted_items = item_vecs[sorted_index]sorted_user  = user_vecs[sorted_index]return(sorted_index, sorted_ypu, sorted_items, sorted_user)

        ③ 定义函数,展示(预测的)用户喜好:

def print_existing_user(y_p, y, user, items, item_features, ivs, uvs, movie_dict, maxcount=10):""" print results of prediction a user who was in the datatbase. inputs are expected to be in sorted order, unscaled. """count = 0movies_listed = defaultdict(int)disp = [["y_p", "y", "user", "user genre ave", "movie rating ave", "title", "genres"]]listed = []count = 0for i in range(0, y.shape[0]):if y[i, 0] != 0:if count == maxcount:breakcount += 1movie_id = items[i, 0].astype(int)offset = np.where(items[i, ivs:] == 1)[0][0]genre_rating = user[i, uvs + offset]genre = item_features[ivs + offset]disp.append([y_p[i, 0], y[i, 0],user[i, 0].astype(int),      # useridgenre_rating.astype(float),items[i, 2].astype(float),    # movie average ratingmovie_dict[movie_id]['title'], genre])table = tabulate.tabulate(disp, tablefmt='html', headers="firstrow", floatfmt=[".1f", ".1f", ".0f", ".2f", ".2f"])return(table)

       ④ 开始为ID=18的用户预测其电影喜好:

# 从原始数据中指定一位用户ID进行推荐
uid =  18 
# 处理原始数据,形成一组用户内容向量
user_vecs, y_vecs = get_user_vecs(uid, scalerUser.inverse_transform(user_train), item_vecs, user_to_genre)# 缩放内容向量,并预测该用户对所有电影的评分
sorted_index, sorted_ypu, sorted_items, sorted_user = predict_uservec(user_vecs, item_vecs, model, u_s, i_s, scaler, scalerUser, scalerItem, scaledata=scaledata)
sorted_y = y_vecs[sorted_index]   # 返回按评级排序的结果# 生成预测排序结果
print_existing_user(sorted_ypu, sorted_y.reshape(-1,1), sorted_user, sorted_items, item_features, ivs, uvs, movie_dict, maxcount = 10)

      运行以上代码,结果如下:

      (6)为用户推荐电影

        ① 定义函数,计算电影相似度:

def sq_dist(a,b):"""Returns the squared distance between two vectorsArgs:a (ndarray (n,)): vector with n featuresb (ndarray (n,)): vector with n featuresReturns:d (float) : distance"""   d = np.sum((a - b) ** 2)    return (d)

        ②  新建模型计算电影特征向量:

# 利用已训练参数新建模型计算物品向量
input_item_m = tf.keras.layers.Input(shape=(num_item_features,))    # input layer
vm_m = item_NN(input_item_m)                                        # use the trained item_NN
# 使用item_NN的输出在Lambda层中定义L2归一化
normalized_output = Lambda(lambda x: tf.linalg.l2_normalize(x, axis=1))(vm_m)# 使用修正后的逻辑重新定义model_m,确保输入和输出正确关联
model_m = Model(inputs=input_item_m, outputs=normalized_output)model_m.summary()# 标准化处理特征向量,并使用model_m预测
scaled_item_vecs = scalerItem.transform(item_vecs)
vms = model_m.predict(scaled_item_vecs[:,i_s:])

       运行以上代码,结果如下:

     

       (新建一个模型,利用已经训练好的参数进行预测,好处是易于分离职责和维护、效率更高、便于调试和优化) 

       ③ 向用户(ID=18)推荐:

# 向用户推荐
count = 10  # 推荐数量
liked_movie_ids = item_vecs[sorted_index[:10], 0].astype(int)  # 实际应用中应根据用户评分数据动态获取# 使用训练好的模型和标准化的电影特征向量计算喜欢的电影的特征向量
liked_movie_vectors = []
for movie_id in liked_movie_ids:try:# 尝试在item_vecs中找到movie_idmovie_index = np.where(item_vecs[:, 0] == movie_id)[0]if len(movie_index) > 0:# 如果找到了,进行后续操作scaled_movie_vec = scalerItem.transform(item_vecs[movie_index, :])[:, i_s:]liked_movie_vectors.append(model_m.predict(scaled_movie_vec).flatten())else:print(f"Warning: Movie ID {movie_id} not found in item vectors despite attempt.")except Exception as e:print(f"An unexpected error occurred while processing movie ID {movie_id}: {e}")# 初始化相似度计算和推荐列表
distances = []
recommendations = []# 对于每部喜欢的电影,找到与其最相似的未看过的电影
for liked_vector in liked_movie_vectors:liked_vector_normalized = liked_vector[:32] / np.linalg.norm(liked_vector[:32])  # 取前32个元素并正则化#for i, v in enumerate(vms):v_normalized = v / np.linalg.norm(v)  # 正则化处理当前比较的电影向量sim_score = np.dot(liked_vector_normalized, v_normalized)  # 使用点积计算相似度distances.append((sim_score, i))  # 记录电影索引及其相似度# 对所有相似度进行排序并选择前N部电影(排除已喜欢的)
top_movies_indices = [idx for _, idx in sorted(distances, reverse=True)[:count]]
unique_top_movies = list(set(top_movies_indices))# 构建推荐列表
disp = [["Movie Title", "Genres"]]
for movie_idx in unique_top_movies:movie_id = int(item_vecs[movie_idx, 0])title, genres = movie_dict[movie_id]['title'], movie_dict[movie_id]['genres']disp.append([title, genres])table = tabulate.tabulate(disp, tablefmt='html', headers="firstrow")
table

       运行以上代码,结果如下:

     

        至此,完成了“基于内容过滤的电影推荐系统”搭建任务,上述系统尽量复现了吴恩达机器学习course3(week2)中的课后代码(因为原代码跑不通略作修改),推荐的结果可采纳度较高,预测用户(ID=18)的电影喜好类型为悬疑片(mystery)和恐怖片(thriller),最终推荐的电影也以恐怖片为主。

04 大项目(数据很大)的推荐方法※

4.1 方法原理

      针对数据量特别大的推荐系统,课程中给出一种提升推荐效率的方法:检索 + 排序

4.2 实施示例

       假设一个电影视频网站有数万部电影,即使仅为1位用户作推荐,每次从数万部电影中寻找适合的电影也很耗时。

      “检索 + 排序”的步骤为:

     (1)检索

       首先,根据用户最近观看的10部电影,作近似度计算,各取前3;

       然后,用户历史记录中选出3类电影类型,找出各类型榜单前10部;

       最后,找出该用户所在国家榜单的前10部;

       还可以采用其他筛选标准……

     (2)排序

        假设以上检索出100部电影,将这100部电影的特征输入神经网络模型进行计算,然后采用基于内容的过滤算法向用户推荐。

05 总结

     (1)基于内容的过滤算法,其推荐分5个步骤:准备内容>神经网络模型提取特征>用户喜好预测>近似度计算>向用户推荐。

     (2)推荐算法的原理易于理解而实际操作较为复杂,根本原因为数据量大,所以各类变量较多,因此数据处理是重点工作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/38719.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

嵌入式问题分析思路

BUG解决总体思路: 1.1 定位bug范围及性质 要有效解决问题&#xff0c;首先要缩小范围&#xff0c;集中关注最近的代码变化。这有助于迅速定位可能引入问题的部分&#xff0c;避免无谓的时间浪费。检查最近的代码提交记录和修改日志&#xff0c;找出可能影响现有功能的变更。然…

Java 位运算详解

位运算是一种直接在二进制位上进行操作的方式。位运算符包括按位与 (&)、按位或 (|)、按位异或 (^)、按位非 (~)、左移 (<<)、右移 (>>) 和无符号右移 (>>>)。这些操作符用于操作整型数据类型&#xff0c;如 int 和 long。 一、按位与 (&) 按位…

如果使用Outlook 2024出现问题

大家好&#xff0c;才是真的好。 很多企业使用Domino服务器当作POP/IMAP邮箱服务器来使用&#xff0c;虽然这不能发挥Domino最佳效能&#xff0c;但也不失为一种简单用法。 另一种企业则使用Domino仅作为应用app平台&#xff0c;邮箱早已迁移至O365或其他平台&#xff0c;他们…

报销又乱又慢,财务如何解决报销困局?

费用报销是企业频繁发生的业务场景&#xff0c;不同的企业在费用报销的流程、标准、制度、管理上各有不同。作为一些公司日常运作中的薄弱环节&#xff0c;费用报销环节存在着较大的内控风险&#xff0c;如&#xff1a;费用报销滞后&#xff0c;造成会计信息的失真&#xff0c;…

【YOLOv5/v7改进系列】更换损失函数为CIOU、GIOU、SIOU、DIOU、EIOU、WIOUv1/v2/v3、Focal C/G/S/D/EIOU等

一、导言 在目标检测任务中&#xff0c;损失函数的主要作用是衡量模型预测的边界框&#xff08;bounding boxes&#xff09;与真实边界框之间的匹配程度&#xff0c;并指导模型学习如何更精确地定位和分类目标。损失函数通常由两部分构成&#xff1a;分类损失&#xff08;用于…

我的世界服务器-高版本服务器-MC服务器-生存服务器-RPG服务器-幻世星辰

生存为主&#xff0c;RPG乐趣为辅&#xff0c;重视每位玩家的建议&#xff0c;一起打造心目中的服务器&#xff0c;与小伙伴一起探险我的世界&#xff01; 服务器版本: 1.18.2 ~ 1.20.4 Q群&#xff1a; 338238381 服务器官网: 星辰毛毛雨-Minecraft高版本生存服务器我的世界…

springboot是否可以代替spring

Spring Boot不能直接代替Spring&#xff0c;但它是Spring框架的一个扩展和增强&#xff0c;提供了更加便捷和高效的开发体验。以下是关于Spring Boot和Spring关系的详细解释&#xff1a; Spring框架&#xff1a; Spring是一个广泛应用的开源Java框架&#xff0c;提供了一系列模…

EDI是什么?与ERP有何关系

EDI的发展过程 电子数据交换&#xff08;Electronic Data Interchange&#xff0c;EDI&#xff09;是一种通过电子方式传输商业文件的技术。EDI的历史可以追溯到20世纪60年代&#xff0c;当时企业开始使用计算机进行数据处理。最早的EDI系统是为解决大型企业间的信息交换问题而…

nccl 04 nvidia 官方小程序

1&#xff0c;代码重新编辑 为了地毯式地检查结果的正确性&#xff0c;这里修改了代码 主要步骤为 step1: data_p指向的空间中&#xff0c;分别生成随机数&#xff1b; step2: 分别拷贝到gpu的sendbuff的显存中&#xff1b; step3: 通过nccl_all_reduce sum&#xff1b;…

上海市计算机学会竞赛平台2023年6月月赛丙组选取子段(二)

题目描述 给定一个长度为&#x1d45b;n的序列 &#x1d44e;1,&#x1d44e;2,...,&#x1d44e;&#x1d45b;a1​,a2​,...,an​ &#xff0c;请问多少种方案&#xff0c;能够从中选取一个连续段&#xff0c;使得该子段内所有元素的值都相同&#xff1f; 输入格式 输入共…

掌握 Python 中 isinstance 的正确用法

&#x1f44b; 简介 isinstance() 函数用于判断一个对象是否是一个特定类型或者在继承链中是否是特定类型的实例。它常用于确保函数接收到的参数类型是预期的。 &#x1f4d6; 正文 1 语法 isinstance(object, classinfo) object参数是要检查的对象&#xff1b;classinfo参数…

fifio中wr_ack信号及其用途

Vivado中FIFO IP核的wr_ack信号及其用途。 wr_ack&#xff08;写确认&#xff09;信号的作用&#xff1a; 功能&#xff1a; wr_ack是一个输出信号&#xff0c;用于指示写操作已被FIFO成功接受。当FIFO成功接收并存储了一个数据项时&#xff0c;它会激活wr_ack信号一个时钟周期…

【SpringBoot循环依赖】解决循环依赖

我的项目中&#xff0c;报错&#xff1a; Description:The dependencies of some of the beans in the application context form a cycle:frontIndexController ┌─────┐ | systemConfigService └─────┘Action:Relying upon circular references is discourage…

ingress-nginx部署-helm方式

helm 安装ingress-nginx Ingress-Nginx Controller 支持多种方式安装&#xff1a; 使用heml安装chart使用kubectl apply&#xff0c;使用YAML文件&#xff1b; 详情可参考&#xff1a;https://kubernetes.github.io/ingress-nginx/deploy/ 本文实践使用helm安装ingress-ngi…

【工具推荐】ONLYOFFICE8.1版本编辑器测评——时下的办公利器

文章目录 一、产品介绍1. ONLYOFFICE 8.1简介2. 多元化多功能的编辑器 二、产品体验1. 云端协作空间2. 桌面编辑器本地版 三、产品界面设计1. 本地版本2. 云端版本 四、产品文档处理1. 文本文档&#xff08;Word)2. 电子表格&#xff08;Excel&#xff09;3. PDF表单&#xff0…

如何学习MyBatis 源码

MyBatis 源码分析是一个深入的话题&#xff0c;涉及到框架的内部实现细节。下面是一些基本介绍和指导&#xff1a; 整体认识 MyBatis 源码结构 核心模块&#xff1a;MyBatis 的核心模块包括 Configuration、Executor、StatementHandler、ParameterHandler、ResultSetHandler 等…

【C++ | 继承】|概念、方式、特性、作用域、6类默认函数

继承 1.继承的概念与定义2.继承的方式2.1继承基本特性2.2继承的作用域2.2.1隐藏赋值兼容 派生类的创建和销毁构造函数拷贝构造赋值重载 1.继承的概念与定义 继承是面向对象编程中的一个重要概念。它的由来可以追溯到软件开发中的模块化设计和代码复用的需求。 在软件开发过程…

mq消息堆积

通常情况下&#xff0c;出现消息积压的原因有 mq消费者挂了mq生产者生产消息的速度&#xff0c;大于mq消费者消费消息的速度 当数据量不大时&#xff0c;优化消费者处理逻辑 通过在代码中增加了一些日志&#xff0c;把mq消费者中各个关键节点的耗时都打印出来&#xff0c;发现有…

从零搭建Java酒店预订系统:实战指南_02

第四步,用户注册和登录 创建用户服务接口 在src/main/java目录下创建com.example.hotelbookingsystem.service包,并在该包下创建UserService接口: package com.example.hotelbookingsystem.service;import com.example.hotelbookingsystem.entity.User;public int…