动手学机器学习双线性模型+习题

在数学中,双线性的含义为,二元函数固定任意一个自变量时,函数关于另一个自变量线性

矩阵分解

设想有N个用户和M部电影,构建一个用户画像库,包含每个用户更偏好哪些类型的特征,以及偏好的程度。假设特征的个数是d,那么所有电影的特征构成的矩阵是P∈R^Mxd,用户喜好构成的矩阵是Q∈R^Nxd

 最后,用这两个矩阵的乘积 R = P.T * Q 可以还原出用户对电影的评分。即使用户对某部电影并没有打分,我们也能通过矩阵乘积,根据用户喜欢的特征和该电影具有的特征,预测出用户对电影的喜好程度

实际上,我们通常能获取到的并不是P和Q,而是打分的结果R。并且由于一个用户只会对极其有限的一部分电影打分,矩阵R是非常稀疏的,绝大多数元素都是空白。因此,我们需要从R有限的元素中推测出用户的喜好P和电影的特征Q

 变成MSE形式+正则化得到:

这里的正则化不是针对整个矩阵,而是每一行,因为电影之间、用户之间是相互独立的

对p和q的梯度:

 

动手实现矩阵分解

import numpy as np
import matplotlib.pyplot as plt
from tqdm import tqdm # 进度条工具

data = np.loadtxt('movielens_100k.csv', delimiter=',', dtype=int)
print('数据集大小:', len(data))
# 用户和电影都是从1开始编号的,我们将其转化为从0开始
data[:, :2] = data[:, :2] - 1

# 计算用户和电影数量
users = set()
items = set()
for i, j, k in data:
    users.add(i)
    items.add(j)
user_num = len(users)
item_num = len(items)
print(f'用户数:{user_num},电影数:{item_num}')

# 设置随机种子,划分训练集与测试集
np.random.seed(0)

ratio = 0.8
split = int(len(data) * ratio)
np.random.shuffle(data)
train = data[:split]
test = data[split:]

# 统计训练集中每个用户和电影出现的数量,作为正则化的权重
user_cnt = np.bincount(train[:, 0], minlength=user_num)
item_cnt = np.bincount(train[:, 1], minlength=item_num)
print(user_cnt[:10])
print(item_cnt[:10])

# 用户和电影的编号要作为下标,必须保存为整数
user_train, user_test = train[:, 0], test[:, 0]
item_train, item_test = train[:, 1], test[:, 1]
y_train, y_test = train[:, 2], test[:, 2]

用set去重

user_cnt用np.bincount计算数组 train 中第一列中每个元素出现的次数

class MF:
    def __init__(self, N, M, d):
        # N是用户数量,M是电影数量,d是特征维度
        # 定义模型参数
        self.user_params = np.ones((N, d))
        self.item_params = np.ones((M, d))

    def pred(self, user_id, item_id):
        # 预测用户user_id对电影item_id的打分
        # 获得用户偏好和电影特征
        user_param = self.user_params[user_id]
        item_param = self.item_params[item_id]
        # 返回预测的评分
        rating_pred = np.sum(user_param * item_param, axis=1)
        return rating_pred

    def update(self, user_grad, item_grad, lr):
        # 根据参数的梯度更新参数
        self.user_params -= lr * user_grad
        self.item_params -= lr * item_grad

给定用户 ID 和电影 ID,计算用户参数和电影参数的乘积,并返回预测的评分

def train(model, learning_rate, lbd, max_training_step, batch_size):
    train_losses = []
    test_losses = []
    batch_num = int(np.ceil(len(user_train) / batch_size))
    with tqdm(range(max_training_step * batch_num)) as pbar:
        for epoch in range(max_training_step):
            # 随机梯度下降
            train_rmse = 0
            for i in range(batch_num):
                # 获取当前批量
                st = i * batch_size
                ed = min(len(user_train), st + batch_size)
                user_batch = user_train[st: ed]
                item_batch = item_train[st: ed]
                y_batch = y_train[st: ed]
                # 计算模型预测
                y_pred = model.pred(user_batch, item_batch)
                # 计算梯度
                P = model.user_params
                Q = model.item_params
                errs = y_batch - y_pred
                P_grad = np.zeros_like(P)
                Q_grad = np.zeros_like(Q)
                for user, item, err in zip(user_batch, item_batch, errs):
                    P_grad[user] = P_grad[user] - err * Q[item] + lbd * P[user]
                    Q_grad[item] = Q_grad[item] - err * P[user] + lbd * Q[item]
                model.update(P_grad / len(user_batch), Q_grad / len(user_batch), learning_rate)

                train_rmse += np.mean(errs ** 2)
                # 更新进度条
                pbar.set_postfix({
                    'Epoch': epoch,
                    'Train RMSE': f'{np.sqrt(train_rmse / (i + 1)):.4f}',
                    'Test RMSE': f'{test_losses[-1]:.4f}' if test_losses else None
                })
                pbar.update(1)

            # 计算测试集上的RMSE
            train_rmse = np.sqrt(train_rmse / len(user_train))
            train_losses.append(train_rmse)
            y_test_pred = model.pred(user_test, item_test)
            test_rmse = np.sqrt(np.mean((y_test - y_test_pred) ** 2))
            test_losses.append(test_rmse)

    return train_losses, test_losses

np.ceil 用于计算数组中每个元素的向上取整值

遍历训练集中的每个批量,进行随机梯度下降训练

根据梯度公式求梯度,并根据批大小调整

通过 set_postfix 方法设置进度条的附加信息,在更新完附加信息后,使用 pbar.update(1) 更新进度条,使其前进一步

pred的结果:也差不多相差1

因子分解机

FM 的应用场景与 MF 有一些区别,MF 的目标是从交互的结果中计算出用户和物品的特征;而 FM 则正好相反,希望通过物品的特征和某个用户点击这些物品的历史记录,预测该用户点击其他物品的概率,即点击率(click through rate,CTR)

由于被点击和未被点击是一个二分类问题,CTR 预估可以用逻辑斯谛回归模型来解决,然而逻辑回归的线性参数化假设中不同的特征xi与xj之间并没有运算,因此需要进一步引入双线性部分:

写成向量形式:

物体的特征向量one-hot后会过于稀疏,y(x)对w求导后得到的xixj大多地方都是0,所以难以对wij更新

从该结果中可以看出,只要xs≠0,参数的梯度vs就不为零,可以用梯度相关的算法对其更新。因此,即使特征向量非常稀疏,FM 模型也可以正常进行训练

模型还存在一个问题。双线性模型考虑不同特征之间乘积的做法,虽然提升了模型的能力,但也引入了额外的计算开销。可以对上面的公式做一些变形,改变计算顺序来降低时间复杂度

 至此,FM 的预测公式为:

 动手实现因子分解机

class FM:
    def __init__(self, feature_num, vector_dim):
        # vector_dim代表公式中的k,为向量v的维度
        self.theta0 = 0.0 # 常数项
        self.theta = np.zeros(feature_num) # 线性参数
        self.v = np.random.normal(size=(feature_num, vector_dim)) # 双线性参数
        self.eps = 1e-6 # 精度参数

    def _logistic(self, x):
        # 工具函数,用于将预测转化为概率
        return 1 / (1 + np.exp(-x))

    def pred(self, x):
        # 线性部分
        linear_term = self.theta0 + x @ self.theta
        # 双线性部分
        square_of_sum = np.square(x @ self.v)
        sum_of_square = np.square(x) @ np.square(self.v)
        # 最终预测
        y_pred = self._logistic(linear_term \
            + 0.5 * np.sum(square_of_sum - sum_of_square, axis=1))
        # 为了防止后续梯度过大,对预测值进行裁剪,将其限制在某一范围内
        y_pred = np.clip(y_pred, self.eps, 1 - self.eps)
        return y_pred

    def update(self, grad0, grad_theta, grad_v, lr):
        self.theta0 -= lr * grad0
        self.theta -= lr * grad_theta
        self.v -= lr * grad_v

np.clip将预测值限制在 [self.eps, 1 - self.eps] 的范围内,如果预测值超出了这个范围,就将其设置为边界值(因为用了sigmoid)

习题

1.B。只要有不为0的特征就能训练

2.C。C不涉及θ1和θ2之间的乘积或内积,因此不是一个双线性模型;D不是标准的双线性形式,但它可以被视为双线性的,它涉及到了θ1和θ2的乘积

3.题目中这种编码方式叫作label encoder

避免顺序假设: Label Encoder 将类别按照它们出现的顺序进行编码,这可能会给模型引入错误的假设,认为类别之间存在顺序关系

避免偏好性: Label Encoder 可能会给编码后的类别赋予不同的数值,这可能导致模型在训练过程中对数值较大的类别产生偏好

适用性广泛: One-Hot Encoder 适用于大多数机器学习模型,包括线性模型、树模型等

4.

class MF:
    def __init__(self, N, M, d):
        # N是用户数量,M是电影数量,d是特征维度
        # 定义模型参数
        self.user_params = np.ones((N, d))
        self.item_params = np.ones((M, d))
        self.global_bias = 0.0  # 全局打分偏置
        self.user_bias = np.zeros(N)  # 用户打分偏置
        self.item_bias = np.zeros(M)  # 物品打分偏置

    def pred(self, user_id, item_id):
        # 预测用户user_id对电影item_id的打分
        # 获得用户偏好和电影特征
        user_param = self.user_params[user_id]
        item_param = self.item_params[item_id]
        # 计算预测分数
        pred_score = np.sum(user_param * item_param, axis=1)
        pred_score += self.global_bias  # 添加全局打分偏置
        pred_score += self.user_bias[user_id]  # 添加用户打分偏置
        pred_score += self.item_bias[item_id]  # 添加物品打分偏置
        return pred_score

    def update(self, user_grad, item_grad, global_bias_grad, user_bias_grad, item_bias_grad, lr):
        # 根据参数的梯度更新参数
        self.user_params -= lr * user_grad
        self.item_params -= lr * item_grad
        self.global_bias -= lr * global_bias_grad
        self.user_bias -= lr * user_bias_grad
        self.item_bias -= lr * item_bias_grad

def train(model, learning_rate, lbd, max_training_step, batch_size):
    train_losses = []
    test_losses = []
    batch_num = int(np.ceil(len(user_train) / batch_size))
    with tqdm(range(max_training_step * batch_num)) as pbar:
        for epoch in range(max_training_step):
            # 随机梯度下降
            train_rmse = 0
            for i in range(batch_num):
                # 获取当前批量
                st = i * batch_size
                ed = min(len(user_train), st + batch_size)
                user_batch = user_train[st: ed]
                item_batch = item_train[st: ed]
                y_batch = y_train[st: ed]
                # 计算模型预测
                y_pred = model.pred(user_batch, item_batch)
                # 计算梯度
                P = model.user_params
                Q = model.item_params
                errs = y_batch - y_pred
                P_grad = np.zeros_like(P)
                Q_grad = np.zeros_like(Q)
                # 计算全局打分偏置、用户打分偏置和物品打分偏置的梯度
                global_bias_grad = -np.mean(errs)  # 全局打分偏置梯度
                user_bias_grad = np.zeros_like(model.user_bias)
                item_bias_grad = np.zeros_like(model.item_bias)
                for user, item, err in zip(user_batch, item_batch, errs):
                    user_bias_grad[user] += -err
                    item_bias_grad[item] += -err
                    P_grad[user] = P_grad[user] - err * Q[item] + lbd * P[user]
                    Q_grad[item] = Q_grad[item] - err * P[user] + lbd * Q[item]
                model.update(P_grad / len(user_batch), Q_grad / len(user_batch), 
                             global_bias_grad, user_bias_grad / len(user_batch), 
                             item_bias_grad / len(user_batch), learning_rate)
                
                train_rmse += np.mean(errs ** 2)
                # 更新进度条
                pbar.set_postfix({
                    'Epoch': epoch,
                    'Train RMSE': f'{np.sqrt(train_rmse / (i + 1)):.4f}',
                    'Test RMSE': f'{test_losses[-1]:.4f}' if test_losses else None
                })
                pbar.update(1)

            # 计算 RMSE 损失
            train_rmse = np.sqrt(train_rmse / len(user_train))
            train_losses.append(train_rmse)
            y_test_pred = model.pred(user_test, item_test)
            test_rmse = np.sqrt(np.mean((y_test - y_test_pred) ** 2))
            test_losses.append(test_rmse)
    
    return train_losses, test_losses

之前

加了之后可以看到loss低了

5.略

6.对不起,做不到>_<

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【数据结构与算法】二叉树的遍历及还原

树形结构 - 有向无环图 树是图的一种。 树形结构有一个根节点树形结构没有回路根节点&#xff1a;A叶子节点&#xff1a;下边没有其他节点了节点:既不是根节点,又不是叶子节点的普通节点树的度:这棵树最多叉的节点有多少叉&#xff0c;这棵树的度就为多少树的深度&#xff1a…

ROS中IMU惯性测量单元

一、IMU惯性测量单元消息包 IMU 是安装在机器人内部的一种传感器模块&#xff0c;用于测量机器人的空间姿态。 IMU的消息包定义在sensor_msgs包中的Imu中。头部是header&#xff0c;记录了消息发送的时间戳和坐标系ID。第二个是角速度。第三个是矢量加速度。三个数据成员都各…

机器学习每周挑战——旅游景点数据分析

数据的截图&#xff0c;数据的说明&#xff1a; # 字段 数据类型 # 城市 string # 名称 string # 星级 string # 评分 float # 价格 float # 销量 int # 省/市/区 string # 坐标 string # 简介 string # 是否免费 bool # 具体地址 string拿到数据…

Godot 4 教程《勇者传说》依赖注入 学习笔记(0):环境配置

文章目录 前言相关地址环境配置初始化环境配置文件夹结构代码结构代码运行 资源文件导入像素风格窗口环境设置背景设置,Tileap使用自动TileMap 人物场景动画节点添加站立节点添加移动动画添加 通过依赖注入获取Godot的全局属性项目声明 当前项目逻辑讲解角色下降添加代码位置问…

MCGS学习——水位控制

要求 插入一个水罐&#xff0c;液位最大值为37插入一个滑动输入器&#xff0c;用来调节水罐水位&#xff0c;滑动输入器最大调节为液位最大值&#xff0c;并能清楚的显示出液位情况用仪表显示水位变化情况&#xff0c;仪表最大显示设置直观清楚方便读数&#xff0c;主划线为小…

CAJViewer8.1下载地址及安装教程

CAJViewer是中国学术期刊&#xff08;CAJ&#xff09;全文数据库的专用阅读软件。CAJViewer是中国知识资源总库&#xff08;CNKI&#xff09;开发的一款软件&#xff0c;旨在方便用户在线阅读和下载CAJ数据库中的学术论文、期刊和会议论文等文献资源。 CAJViewer具有直观的界面…

Linux系统——Mysql数据库锁的拓展

目录 一、锁的概述 二、锁的分类 1.按锁粒度分类 2.按性能分类 3.按对数据库操作类型 三、全局锁 1.定义 2.操作 3.特点 四、表级锁 1.表级锁分类 2.表锁分类 2.1表共享读锁&#xff08;read lock&#xff09; 2.2表独占写锁&#xff08;write lock&#xff09; …

随便注【强网杯2019】

大佬的完整wp&#xff1a;buuctf-web-[强网杯 2019]随便注-wp_取材于某次真实环境渗透,只说一句话:开发和安全缺一不可-CSDN博客 知识点&#xff1a; 单引号字符型绕过堆叠注入 可以执行多条语句multi_query()&#xff1a;该函数可能引发堆叠注入handler用法 mysql专属&#…

计算机基础系列 —— 虚拟机代码翻译器(2)

I believe that at the end of the century the use of words and general educated opinion will have altered so much that one will be able to speak of machines thinking without expecting to be contradicted. —— Alan Turing 文中提到的所有实现都可以参考&#xf…

【MATLAB源码-第173期】基于matlab的RS编码的2FSK通信系统误码率仿真,通过AWGN信道输出误码率曲线。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 通信系统的基本框架 在现代通信系统中&#xff0c;数据的传输通常涉及四个基本步骤&#xff1a;源编码、信道编码、调制和传输。源编码主要负责压缩数据&#xff0c;减少传输的数据量。信道编码则通过添加冗余信息来提高传输…

【Blockchain】区块链浏览器 | 以太坊Etherscan比特币Blockchain门罗币Monero

区块链浏览器概述 区块链浏览器是一种软件,它使用API(应用程序编程接口)和区块链节点从区块链中提取各种数据&#xff0c;然后使用数据库来排列搜索到的数据&#xff0c;并以可搜索的格式将数据呈现给用户。 用户的输入是资源管理器上的可搜索项&#xff0c;然后通过数据库上…

【力扣hot100】128-最长连续序列、283-移动零

128. 最长连续序列 import java.util.*;public class Test {public static void main(String[] args) {int[] nums {0, 3, 7, 2, 5, 8, 4, 6, 0, 1};int res new Solution().longestConsecutive(nums);System.out.println(res);} }class Solution {public int longestConsecu…

3.31学习总结

算法 解题思路 使用dfs,对蛋糕每层可能的高度和半径进行穷举.通过观察我们可以知道第一层的圆面积是它上面所有蛋糕层的圆面积之和,所以我们只要去求每层的侧面积就行了. 因为题目要求Ri > Ri1且Hi > Hi1,所以我们可以求出每层的最小体积和侧面积,用两个数组分别储存起来…

教你一键轻松领取阿里云优惠券

随着云计算的普及&#xff0c;越来越多的企业和个人开始选择使用云服务。阿里云作为国内领先的云计算服务提供商&#xff0c;以其稳定、高效、安全的服务赢得了广大用户的信赖。为了吸引用户上云&#xff0c;阿里云推出了优惠券活动&#xff0c;本文将教大家如何一键领取阿里云…

【Linux】深入理解进程状态、优先级和调度:Linux 内核中的实现原理探析

文章目录 前言1. 进程状态1.1. 轻量进程排队这件事情——队列1.2. 进程状态的表述及其影响&#xff1a;1.3. 挂起状态及处理&#xff1a;1.4.理解 Linux 内核源代码中的状态表述&#xff1a; 2. 进程优先级Linux 为什么要调整优先级是要受限的&#xff1f; 3. Linux的调度与切换…

Typora下载激活方案

一、下载 1.在typora官网下载最新版本&#xff0c;并安装: 官网地址 2.获取激活工具 感谢Typora激活方法&#xff08;2023年最新版&#xff09; - AI小智的文章 - 知乎 https://zhuanlan.zhihu.com/p/669618741 二、激活 1.把两个.exe文件复制到typora安装目录下 2.在typor…

ubuntu下给不同串口设置别名

目录 一、绑定设备ID 1.查看设备ID 2.编写usev规则 3.重新加载usev规则 4.查看 二、绑定USB端口号 1.先插入一个串口&#xff0c;查看USB设备信息 2.查看USB转串口信息 3.编写usev规则 4.重新加载usev规则 5.查看 在Ubuntu环境下&#xff0c;有时候工控机或者arm开…

推挽输出与开漏输出

推挽输出与开漏输出 文章目录 推挽输出与开漏输出前言一、推挽输出二、开漏输出总结 前言 在使用GPIO口时&#xff0c;会遇到两种配置&#xff0c;一种叫推挽输出&#xff0c;一种叫开漏输出&#xff0c;今天就简聊一聊这两种模式的差异和选择。 一、推挽输出 如图所示&#…

Lazarus远控组件NukeSped分析

静态信息&#xff1a; 样本md5&#xff1a;9b656f5d7e679b94e7b91fc3c4f313e4 由此可见为假的Adobe Flash Player 的攻击样本 样本分析 通过五个函数&#xff0c;内部调用sub_40159D函数动态获取API函数 利用IDA python解密字符串。。 完整python代码 Python> idc.get_…

扫雷(蓝桥杯)

题目描述 小明最近迷上了一款名为《扫雷》的游戏。其中有一个关卡的任务如下&#xff0c; 在一个二维平面上放置着 n 个炸雷&#xff0c;第 i 个炸雷 (xi , yi ,ri) 表示在坐标 (xi , yi) 处存在一个炸雷&#xff0c;它的爆炸范围是以半径为 ri 的一个圆。 为了顺利通过这片土…