一、矩阵分解
(一)基于矩阵分解的推荐系统
推荐值矩阵R里有一些值是已知的,如果我们将R分解为两个矩阵U和P,U的每一行代表一个用户画像向量,P的每一行代表一个物品的画像向量。如果U和P能够分别准备表达用户和画像向量,那么U和的乘积
中的值就应该是推荐值。假设用户对物品 的喜好的值是取决于几个(假设是k个)因素,我们不知道这些因素是什么,所以我们命名他们为隐性因子。
评分(Rating)矩阵包含了m个用户(user)对n个物品的评分。假设评分矩阵可以分解为用户矩阵
和
,使得
。其中,k是矩阵分解的秩,决定了U,P的维度。
。
损失函数为:
采用梯度下降法求解最小化问题,设定k的值,设定学习步长(learning rate),初始化U和P,重复以下步骤知道均方差满意为止:
从原理上讲,k是我们直觉上认为有多少个隐含的因素决定了一个用户为什么喜好某个物品。在现实中,我们无法直接推导出合适的k值,我们一般采用交叉验证(cross vaildation),通过实验,测试不同的k值产生的平均绝对离差来找到合适的k值。
(二)实战:短视频推荐系统
# 导入 nunpy 和 surprise 辅助库
import numpy as np
import surprise
Surprise库本身没有提供纯粹的矩阵分解的算法,在这里我们自己实现了基于Alternating Least Squares的矩阵分解,使用梯度下降法优化。矩阵分解类MatrixFactorization继承了surprise.AlgoBase,方便我们使用surpise库提供的其它功能。
class MatrixFactorization(surprise.AlgoBase):
'''基于矩阵分解的推荐.'''
def __init__(self, learning_rate, n_epochs, n_factors, lmd):
self.lr = learning_rate # 梯度下降法的学习率
self.n_epochs = n_epochs # 梯度下降法的迭代次数
self.n_factors = n_factors # 分解的矩阵的秩(rank)
self.lmd = lmd # 防止过拟合的正则化的强度
def fit(self, trainset):
'''通过梯度下降法训练, 得到所有 u_i 和 p_j 的值'''
print('Fitting data with SGD...')
# 随机初始化 user 和 item 矩阵.
u = np.random.normal(0, .1, (trainset.n_users, self.n_factors))
p = np.random.normal(0, .1, (trainset.n_items, self.n_factors))
# 梯度下降法
for _ in range(self.n_epochs):
for i, j, r_ij in trainset.all_ratings():
err = r_ij - np.dot(u[i], p[j])
# 利用梯度调整 u_i 和 p_j
u[i] -= -self.lr * err * p[j] + self.lr * self.lmd * u[i]
p[j] -= -self.lr * err * u[i] + self.lr * self.lmd * p[j]
# 注意: 修正 p_j 时, 按照严格定义, 我们应该使用 u_i 修正之前的值, 但是实际上差别微乎其微
self.u, self.p = u, p
self.trainset = trainset
def estimate(self, i, j):
'''预测 user i 对 item j 的评分.'''
# 如果用户 i 和物品 j 是已知的值, 返回 u_i 和 p_j 的点积
# 否则使用全局平均评分rating值(cold start 冷启动问题)
if self.trainset.knows_user(i) and self.trainset.knows_item(j):
return np.dot(self.u[i], self.p[j])
else:
return self.trainset.global_mean
from surprise import BaselineOnly
from surprise import Dataset
from surprise import Reader
from surprise import accuracy
from surprise.model_selection import cross_validate
from surprise.model_selection import train_test_split
import os
# 数据文件
file_path = os.path.expanduser('./ml-100k/u.data')
# 数据文件的格式如下:
# 'user item rating timestamp', 使用制表符 '\t' 分割, rating值在1-5之间.
reader = Reader(line_format='user item rating timestamp', sep='\t', rating_scale=(1, 5))
data = Dataset.load_from_file(file_path, reader=reader)
# 将数据随机分为训练和测试数据集
trainset, testset = train_test_split(data, test_size=.25)
# 初始化以上定义的矩阵分解类.
algo = MatrixFactorization(learning_rate=.005, n_epochs=60, n_factors=2, lmd = 0.2)
# 训练
algo.fit(trainset)
# 预测
predictions = algo.test(testset)
# 计算平均绝对误差
accuracy.mae(predictions)
# 使用 surpise 内建的基于最近邻的方法做比较
algo = surprise.KNNBasic()
algo.fit(trainset)
predictions = algo.test(testset)
accuracy.mae(predictions)
# 使用 surpise 内建的基于 SVD 的方法做比较
algo = surprise.SVD()
algo.fit(trainset)
predictions = algo.test(testset)
accuracy.mae(predictions)
(三)作业:书籍商品推荐系统
1.利用梯度调整 u_i 和 p_j。
注意: 修正 p_j 时, 安装严格定义, 我们应该使用 u_i 修正之前的值, 但是实际上差别微乎其微
u[i] -= -self.lr * err * p[j] + self.lr * self.lmd * u[i]
p[j] -= -self.lr * err * u[i] + self.lr * self.lmd * p[j]
2.返回 u_i 和 p_j 的点击。
return np.dot(self.u[i], self.p[j])
3.返回 全局平均评分rating值
return self.trainset.global_mean
4.创建 reader 对象
reader = Reader(line_format='user item rating', sep='\t', rating_scale=(1, 10), skip_lines=1)
5.初始化、训练、预测、评估
# TODO
# 初始化以上定义的矩阵分解类.
algo = MatrixFactorization(learning_rate=.005, n_epochs=60, n_factors=2, lmd=0.2)
# TODO
# 训练
algo.fit(trainset)
# TODO
# 预测
predictions = algo.test(testset)
# TODO
# 计算平均绝对误差
accuracy.mae(predictions)
二、Boosting
(一)xgboost算法介绍
(二)结构分
(三)分解节点
现在的问题是:如何得到最佳的树的结构,从而使得目标函数全局最小。
3.1 贪心算法
3.2 近似算法
(四)xgboost贷款用户逾期预测
import os
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
import xgboost as xgb
df.isnull().any() # 检查 null
df = df.iloc[:,2:111] # 删掉很多空的列
empty_cols = [i for i in range(45,72)] # 删除更多的列
df = df.drop(df.columns[empty_cols],axis=1)
df = df[(df['loan_status']=="Fully Paid") | (df['loan_status']=="Charged Off")]
df['loan_status'] = df['loan_status'].map({'Fully Paid':0, 'Charged Off':1})
df=df.dropna(axis=1) #340000 is minimum number of non-NA values
df_grade = df['grade'].str.get_dummies().add_prefix('grade: ')
# 把类型独热编码
df_subgrad = df['sub_grade'].apply(str).str.get_dummies().add_prefix('sub_grade: ')
df_home = df['home_ownership'].apply(str).str.get_dummies().add_prefix('home_ownership: ')
df_addr = df['addr_state'].apply(str).str.get_dummies().add_prefix('addr_state: ')
df_term = df['term'].apply(str).str.get_dummies().add_prefix('term: ')
# 添加独热编码数据列
df = pd.concat([df, df_grade, df_subgrad, df_home, df_addr, df_term], axis=1)
# 去除独热编码对应的原始列
df = df.drop(['grade', 'sub_grade', 'home_ownership', 'addr_state', 'int_rate', 'term', 'zip_code','purpose','initial_list_status','initial_list_status','pymnt_plan','issue_d','earliest_cr_line','verification_status'], axis=1)
# 准备数据
X = df.drop('loan_status', axis=1)
y = df['loan_status']
print (X.shape, y.shape)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=123)
参数:
params {eta, gamma, max_depth, min_child_weight, max_delta_step, subsample, colsample_bytree,
colsample_bylevel, colsample_bynode, lambda, alpha, tree_method string, sketch_eps, scale_pos_weight, updater,
refresh_leaf, process_type, grow_policy, max_leaves, max_bin, predictor, num_parallel_tree}
xgboost.train (params, dtrain, num_boost_round=10, evals=(), obj=None, feval=None, maximize=False,
early_stopping_rounds=None, evals_result=None, verbose_eval=True, xgb_model=None, callbacks=None,
learning_rates=None)
在运行XGboost之前, 我们必须设置三种类型的参数: 通用参数(general parameters),Booster 参数(booster parameters)和学习目标参数(task parameters)。
• 通用参数决定了那种Booster被我们选择用于Boosting, 通常是线性和树型模型,也就是用于宏观函数控制。
• Booster参数取决于选择的Booster类型,用于控制每一步的booster。
• 学习任务参数决定学习策略。例如,回归任务可以使用不同的参数和排序任务。
1.General Parameters
(1)booster [缺省值=gbtree]
决定那个使用那个booster,可以是gbtree,gblinear或者dart。 gbtree和dart使用基于树的模型,而gblinear 使用线性函数.
(2)silent [缺省值=0]
设置为0:打印运行信息;设置为1:静默模式,不打印。
(3)nthread [缺省值=设置为最大可能的线程数]
并行运行xgboost的线程数,输入的参数应该<=系统的CPU核心数,若是没有设置算法会检测将其设置为CPU的全部核心数
下面的两个参数不需要设置,使用默认的就好了
(4)num_pbuffer [xgboost自动设置,不需要用户设置]
预测结果缓存大小,通常设置为训练实例的个数。该缓存用于保存最后boosting操作的预测结果。
(5)num_feature [xgboost自动设置,不需要用户设置]
在boosting中使用特征的维度,设置为特征的最大维度
2.Parameters for Tree Booster
(1)eta [缺省值=0.3,别名:learning_rate]
更新中减少的步长来防止过拟合。在每次boosting之后,可以直接获得新的特征权值,这样可以使得boosting更加鲁棒。
范围: [0,1]
(2)gamma [缺省值=0,别名: min_split_loss](分裂最小loss)
在节点分裂时,只有分裂后损失函数的值下降了,才会分裂这个节点。Gamma指定了节点分裂所需的最小损失函数下降值。 这个参数的值越大,算法越保守。这个参数的值和损失函数息息相关,所以是需要调整的。 范围: [0,∞]
(3)max_depth [缺省值=6]
这个值为树的最大深度。 这个值也是用来避免过拟合的。max_depth越大,模型会学到更具体更局部的样本。设置为0代表没有限制 。范围: [0,∞]
(4)min_child_weight [缺省值=1]
决定最小叶子节点样本权重和。XGBoost的这个参数是最小样本权重的和,而GBM参数是最小样本总数。 这个参数用于避免过拟合。当它的值较大时,可以避免模型学习到局部的特殊样本。 但是如果这个值过高,会导致欠拟合。这个参数需要使用CV来调整。范围: [0,∞]
(5)subsample [缺省值=1]
这个参数控制对于每棵树,随机采样的比例。 减小这个参数的值,算法会更加保守,避免过拟合。但是,如果这个值设置得过小,它可能会导致欠拟合。 典型值:0.5-1,0.5代表平均采样,防止过拟合. 范围: (0,1]
(6)colsample_bytree [缺省值=1]
用来控制每棵随机采样的列数的占比(每一列是一个特征)。 典型值:0.5-1。范围: (0,1]
(7)colsample_bylevel [缺省值=1]
用来控制树的每一级的每一次分裂,对列数的采样的占比。 我个人一般不太用这个参数,因为subsample参数和colsample_bytree参数可以起到相同的作用。但是如果感兴趣,可以挖掘这个参数更多的用处。范围: (0,1]
(8)lambda [缺省值=1,别名: reg_lambda]
权重的L2正则化项。(和Ridge regression类似)。 这个参数是用来控制XGBoost的正则化部分的。虽然大部分数据科学家很少用到这个参数,但是这个参数在减少过拟合上还是可以挖掘出更多用处的。.
(9)alpha [缺省值=0,别名: reg_alpha]
权重的L1正则化项。(和Lasso regression类似)。 可以应用在很高维度的情况下,使得算法的速度更
(10)scale_pos_weight[缺省值=1]
在各类别样本十分不平衡时,把这个参数设定为一个正值,可以使算法更快收敛。通常可以将其设置为负样本的数目与正样本数目的比值。
3.Parameters for Linear Booster
(1)lambda [缺省值=0,别称: reg_lambda]
L2正则化惩罚系数,增加该值会使得模型更加保守。
(2)alpha [缺省值=0,别称: reg_alpha]
L2正则化惩罚系数,增加该值会使得模型更加保守。
(3)lambda_bias [缺省值=0,别称: reg_lambda_bias]
偏置上的L2正则化(没有在L1上加偏置,因为并不重要)
4.Learning Task Parameters
(1)objective [缺省值=reg:linear]
“reg:linear” – 线性回归
“reg:logistic” – 逻辑回归
“binary:logistic” – 二分类逻辑回归,输出为概率
“binary:logitraw” – 二分类逻辑回归,输出的结果为wTx
“count:poisson” – 计数问题的poisson回归,输出结果为poisson分布。在poisson回归中,max_delta_step的缺省值为0.7 (used to safeguard optimization)
“multi:softmax” – 设置 XGBoost 使用softmax目标函数做多分类,需要设置参数num_class(类别个数)
“multi:softprob” – 如同softmax,但是输出结果为ndata*nclass的向量,其中的值是每个数据分为每个类的概率。
(2)eval_metric [缺省值=通过目标函数选择]
可供选择的如下所示:
“rmse”: 均方根误差
“mae”: 平均绝对值误差
“logloss”: negative log-likelihood
“error”: 二分类错误率。其值通过错误分类数目与全部分类数目比值得到。对于预测,预测值大于0.5被认为是正嘞,其它归为负类。
“error@t”: 不同的划分阈值可以通过 ‘t’进行设置
“merror”: 多分类错误率,计算公式为(wrong cases)/(all cases)
“mlogloss”: 多分类log损失
“auc”: 曲线下的面积
“ndcg”:Normalized Discounted Cumulative Gain
“map”: 平均正确率
(3)seed [缺省值=0]
随机数的种子 设置它可以复现随机数据的结果,也可以用于调整参数。
# boosting 迭代数量
num_boost_round = 10
param = {'eta' : 0.1, #学习率
'max_depth': 5, # 每棵子树的最大深度
'colsample_bytree' : 0.3, #构建子树时,对特征的采样比例
'tree_method': 'gpu_hist', # 使用GPU加速
'alpha' : 10, #L1 正则化系数
'objective' : 'binary:logistic' # 指定任务类型
}
dtrain = xgb.DMatrix(X_train, label=y_train)
dtest = xgb.DMatrix(X_test, label=y_test)
import time
gpu_res = {} # Store accuracy result
tmp = time.time()
# Train model
param['tree_method'] = 'gpu_hist'
xgb.train(param, dtrain, num_boost_round, evals=[(dtest, 'test')], evals_result=gpu_res)
print("GPU Training Time: %s seconds" % (str(time.time() - tmp)))
[0] test-error:0.00289
[1] test-error:0.00289
[2] test-error:0.00289
[3] test-error:0.002011
[4] test-error:0.002011
[5] test-error:0.00289
[6] test-error:0.002011
[7] test-error:0.002388
[8] test-error:0.002388
[9] test-error:0.002388
GPU Training Time: 4.031000137329102 seconds