R-机器学习示例-二-

40 阅读1小时+

R 机器学习示例(二)

原文:annas-archive.org/md5/099c261ea8cfb1acbd5544b92a953b97

译者:飞龙

协议:CC BY-NC-SA 4.0

第四章。构建产品推荐系统

数字世界让一切都可以一键获取。随着一切转向线上,在线购物或电子商务已经成为一大趋势。从杂货到电子产品,甚至汽车,一切都可以在亚马逊、Flipkart 和 eBay 等全球平台找到。这个不断扩大的数字市场正是数据科学展示其魔力的理想场所。

电子商务的在线革命不仅赋予了顾客权力,还让他们因选择过多而感到不知所措。选择不仅限于产品或类别,还包括不同的电子商务平台。在这个高度竞争的市场中,作为一家电子商务公司确实非常困难。脱颖而出是一个挑战,而数据再次发挥了救星的作用。

正如我们在第三章中看到的,“使用市场篮子分析预测客户购物趋势”,购买模式可以提供关于购物行为的很多见解。我们利用此类数据来找到关联规则,不仅帮助客户快速找到合适的产品,还帮助零售商增加收入(参见第三章,“使用市场篮子分析预测客户购物趋势”)。对于关联规则,其粒度位于交易层面。它们使用交易作为中心实体,因此不提供特定于用户的见解。

在本章中,我们也将继续在电子商务领域进行我们的项目工作。在这里,我们将解决个性化的问题。我们将使用机器学习算法提供特定于用户的推荐。

通过本章,我们将了解:

  • 推荐系统及其类型

  • 推荐系统的问题

  • 协同过滤器

  • 基于矩阵分解从头开始构建推荐系统

  • 利用高度优化的 R 包构建一个生产就绪的推荐引擎并评估其推荐

在本章的整个过程中,我们将交替使用术语推荐引擎推荐系统

理解推荐系统

每个人都是独一无二的,我们做事的方式定义了我们独特的个性。我们吃饭、走路、说话,甚至购物都有非常独特的方式。由于本章的重点是电子商务,我们将主要关注我们的购物行为。我们将利用每位客户的独特行为来提供个性化的购物体验。

为了完成提供个性化购物体验的任务,我们需要一个系统来理解和模拟我们的客户。推荐引擎是学习客户偏好、选择等信息的系统,以推荐更接近用户可能自己购买的新产品,从而提供个性化体验。这些系统提供的选项有很大的可能性会被客户购买。

让我们尝试正式定义一个推荐系统。

推荐系统(或推荐引擎)是一类信息过滤系统,它们分析输入数据以预测用户的偏好,就像他们为自己做的那样。

与信息过滤系统不同,后者会删除或过滤信息,推荐引擎会添加或重新排列流向用户的信息,使其与当前上下文更相关。

推荐引擎并不是一个新概念。在互联网出现之前,它们就已经存在了。它们以我们的朋友和家人的形式存在,他们曾经向我们推荐购买的东西,因为他们理解我们的选择。这些就是,并且仍然是某种离线推荐引擎。网络上充满了在线推荐引擎。从 Twitter 上的关注推荐到 Netflix 上的你可能喜欢的其他电影,再到 LinkedIn 上的你可能感兴趣的工作,推荐引擎无处不在,而不仅仅是电子商务平台。

现在我们已经了解了推荐引擎是什么,让我们来看看它们的不同类型:

  • 基于用户的推荐引擎:正如其名所示,这些系统以用户为中心实体。通过分析用户的活跃度、偏好或行为来预测他们可能喜欢的东西,这取决于他们与其他类似用户的相似性。由于这些推荐引擎广泛使用协同过滤器,因此它们通常被称为基于用户的协同过滤器。

  • 基于内容的推荐引擎:正如其名所示,这些引擎以内容或项目为中心实体。这些项目被分析以提取特征;同时,也建立用户档案以将用户偏好映射到项目类型。然后,这些引擎使用这些信息来预测与用户过去喜欢的项目相似的项目。这类推荐引擎也被称为基于项目的协同过滤器,其根源在于信息检索理论。

  • 混合推荐引擎:这些系统结合了两种方法的优点,以改进预测结果。两种纯类型可以同时使用,然后合并它们的结果;它们可以通过向基于内容的系统添加协同过滤功能或甚至将两种方法统一到一个单一模型中来实现。已经进行了多项研究来证明混合方法比简单方法更好。混合推荐引擎在解决一般推荐引擎面临的难题方面也更为出色。

在我们深入探讨这些算法的复杂性之前,让我们看看影响推荐系统的问题。

推荐系统的问题

推荐引擎主要受以下两个问题的影响:

  • 稀疏性问题:推荐引擎根据用户偏好(或根据应用的不同,对不同项目的评分)来预测或推荐产品。通常评分是在某个选定的尺度上给出的,但用户可能选择不对他/她未购买或查看的项目进行评分。对于这种情况,评分是空白或零。因此,评分矩阵 R 的元素形式如下:

    推荐系统的问题

    对于任何现实世界应用,例如电子商务平台,由于平台上可用的用户和项目数量庞大,这样的评分矩阵的大小是巨大的。尽管在这样一个平台上收集了大量的用户相关信息,但评分矩阵本身可能仍然相当稀疏,即矩阵可能有大量空白(或零)元素。这个问题通常被称为稀疏性问题。稀疏性问题使得推荐引擎的预测无效,因为算法无法正确推断出由于空白或缺失评分而产生的相关性。在最坏的情况下,算法可能会将两个用户视为不相关,而实际上他们有高度相似的选择偏好。稀疏性问题通常影响协同过滤算法。

  • 冷启动问题:稀疏问题的特殊情况是冷启动问题。如前所述,当评分矩阵包含稀疏元素(或评分)时,推荐引擎无法返回有效的推荐。冷启动问题在两种特定情况下发生。首先,假设一个新用户被添加到系统中。在这种情况下,代表该用户的行将包含零(大多数情况下)。由于缺乏与用户偏好相关的信息,向此类用户推荐项目几乎是不可能的。第二种情况是当新项目被添加到系统中。由于新添加的项目不会有任何用户评分,因此推荐此类项目对推荐系统来说将是困难的。因此,这两个场景代表了所谓的冷启动问题。与稀疏问题非常相似,冷启动问题也困扰着协同过滤器。

协同过滤器

推荐系统和协同过滤器有着悠久的历史。从早期使用特定分类和硬编码结果的原始推荐引擎,到当前各种电子商务平台上的复杂推荐引擎,推荐引擎一直使用协同过滤器。它们不仅易于理解,而且同样易于实现。在我们深入了解实现细节之前,让我们利用这个机会更多地了解协同过滤器。

注意

趣味事实

推荐引擎无疑已经过时了任何已知的电子商务平台!1979 年开发的 Grundy 是一个虚拟图书管理员。它是一个向用户推荐书籍的系统。它根据某些预定义的刻板印象来模拟用户,并为每个此类类别推荐已知列表中的书籍。

核心概念和定义

协同过滤器(以下简称CF)和一般的推荐引擎使用某些术语和定义来正式定义和解决该问题。

推荐引擎的问题领域围绕着用户和他们感兴趣的项目。一个用户是任何与系统互动并在项目(例如购买或查看它)上执行某些操作的人。同样,一个评分定义了用户对考虑中的项目的偏好。通常,这个三元组表示为(用户,项目,评分)元组。由于评分量化了用户的偏好,评分可以根据应用的不同以不同的方式定义。应用将评分定义为从0-5等整数刻度,而其他可能定义实值刻度。某些应用可能使用喜欢/不喜欢购买/未购买等值的二进制刻度。因此,每个应用都使用评分尺度来满足其用户的偏好。

现在我们已经知道了涉及的关键角色,下一步是数学上表示这些核心概念。一个(用户, 项目, 评分)元组通常以稀疏矩阵的形式表示,称为评分矩阵。每一行代表一个用户,而列表示项目。这个评分矩阵的每个元素都指代用户对项目的评分或偏好。由于并非所有项目都会被每个用户评分,因此这些未评分的项目将包含空值或空白值。使用 0-5 评分尺度(未评分/缺失评分用?表示)的评分矩阵如下所示,显示了三个用户对不同笔记本电脑型号的偏好:

核心概念和定义

一个示例评分矩阵

推荐引擎被赋予执行两个主要操作的任务:预测推荐。预测操作针对给定的用户和项目,以确定用户对所考虑项目的可能偏好。对于评分矩阵(如前面所示),预测就像识别缺失值(在先前的例子中用?表示)。

推荐操作在预测完成后进行。给定一个用户,推荐操作会根据用户的偏好生成一个包含前N个项目的列表。

注意

注意,在推荐引擎的上下文中,用于预测和推荐任务的用户被称为活跃用户

协同过滤算法

协同过滤算法是一组流行的算法,在众多应用中被广泛使用。众所周知,协同过滤算法利用类似用户的行为来预测和为活跃用户推荐项目。这些算法基于一个简单的假设,即类似用户会表现出类似的行为。更正式地说,算法假设系统中其他用户的偏好或评分可以用来为活跃用户提供合理的预测。

基于邻居的协同过滤,也称为用户-用户协同过滤kNN 协同过滤,是协同过滤算法家族中最早和最广泛使用的算法之一。kNN 协同过滤算法基于用户具有类似偏好的用户之间类似行为的核心假设。该算法利用相似度度量(在第二章中讨论,让我们帮助机器学习),来预测和为活跃用户推荐项目。该算法遵循两步方法,首先计算预测,然后进行推荐。接下来讨论该算法的三个主要组成部分。

预测

kNN CF 的第一步是利用评分矩阵(通常表示为 R)来计算预测。由于我们关注的是用户-用户 CF,因此需要考虑活跃用户(考虑中的用户)的邻域,表示为 u

U 为系统中所有可用用户的集合,N 表示所需的邻域 预测。然后算法使用一个相似度度量,例如 s,来计算 u 的邻居。一旦确定了 Nu 的邻域),就会汇总相邻用户的评分来计算 u 对当前项目的偏好。最常用的汇总偏好的方法是使用 N 个相邻用户的 加权平均

从数学上讲,活跃用户 u 对项目 i 的预测偏好,表示为 p[ui],如下所示:

预测

其中:

  • 预测 是活跃用户 u 的平均评分

  • 预测 是活跃用户 u 和相邻用户 预测 之间的相似度度量

在前面的公式中,我们从相邻用户的平均评分中减去活跃用户评分的均值 预测,以消除用户评分的偏差(一些用户给出极高或极低的评分,因此他们可能会影响整体预测评分)。一个有偏差的推荐引擎可能会阻碍更好的用户-产品匹配,偏向于受欢迎的产品或反对不太受欢迎的产品。我们可以通过使用标准差来规范化用户的评分,以控制评分在均值周围的分布,从而进一步提高预测的准确性。为了简化问题,我们将使用之前提到的公式。以下图像展示了活跃用户的最近邻:

预测

最近邻(K=3

现在出现的问题是,为什么只使用加权平均来预测评分,以及最优的邻居数量(N)是多少。使用加权平均的原因是,它是有助于生成一致结果的一种度量方法。多年来,不同的系统使用了各种方法,例如 多元回归(BellCore 系统用于视频推荐)、未加权平均(Ringo 用于音乐推荐)等等,但在实践中加权平均表现相当好。

注意

更多信息,请参阅 W. Hill, L. Stead, M. Rosenstein, 和 G. Furnas 的论文,在虚拟使用社区中推荐和评估选择,发表于 ACM CHI '95,第 194–201 页,ACM Press/Addison-Wesley 出版公司,1995 年。

接下来是关于最优邻居数量的第二个问题,这非常依赖于具体的应用。我们在第二章中看到,“让我们帮助机器学习”,邻居的数量如何改变算法的结果(参见K-最近邻 (KNN)),同样,N的值也会影响推荐引擎的结果。一般来说,限制邻居用户数量有助于通过移除与活跃用户相关性低的用户来减少噪声。但再次强调,N的值依赖于具体应用,并且需要数据科学家进行适当的尽职调查。

推荐

一旦对活跃用户进行了预测,可以通过按预测排名对项目进行排序来生成推荐列表。这个推荐列表可以通过应用某些最小阈值和其他用户特定特征(如对颜色、尺寸、价格敏感度等)进一步微调。因此,这一步生成了一份可能的项目列表,用户更有可能根据个人偏好购买这些项目。我们将在下一节“构建推荐引擎”中详细介绍这一点。

相似度

相似度度量是我们基于协同过滤的推荐引擎算法的一个重要组成部分。有各种相似度度量可供使用。其中最常见的是余弦相似度度量。这种方法将每个用户表示为一个n维度的评分向量,相似度通过计算两个此类用户向量之间的余弦距离来衡量。

从数学上讲,余弦相似度表示为:

相似度

其中,相似度相似度是每个评分向量的L2欧几里得范数。

皮尔逊 相关系数斯皮尔曼秩相关系数是两种广泛使用的统计相似度度量。

现在我们已经了解了协同过滤和一般概念的基础,我们准备着手处理实现细节。让我们从构建推荐系统,一块一块地开始!

构建推荐引擎

如前所述,协同过滤是一种简单而非常有效的预测和推荐项目给用户的方法。如果我们仔细观察,算法是针对输入数据工作的,这实际上只是不同产品用户评分的矩阵表示。

从数学角度来说,矩阵分解是一种操纵矩阵并从矩阵表示的数据中识别潜在或隐藏特征的技术。基于同样的概念,让我们使用矩阵分解作为预测用户尚未评分的项目评分的基础。

矩阵分解

矩阵分解指的是识别两个或多个矩阵,当这些矩阵相乘时,我们得到原始矩阵。如前所述,矩阵分解可以用来发现两种不同实体之间的潜在特征。随着我们为电子商务平台准备推荐引擎,我们将理解和使用矩阵分解的概念。

由于我们当前项目的目标是个性化购物体验并为电子商务平台推荐产品评分,我们的输入数据包含网站上各种产品的用户评分。我们处理输入数据,将其转换为矩阵表示,以便使用矩阵分解进行分析。输入数据看起来像这样:

矩阵分解

用户评分矩阵

如您所见,输入数据是一个矩阵,其中每一行代表特定用户对不同项目(在列中表示)的评分。对于当前情况,表示项目的列是不同的手机,如 iPhone 4、iPhone 5s、Nexus 5 等。每一行包含由八个不同用户给出的这些手机的评分。评分范围从 1 到 5,1 为最低,5 为最高。评分为 0 表示未评分的项目或缺失的评分。

我们推荐引擎的任务将是预测输入矩阵中缺失的正确评分。然后我们可以使用预测的评分来推荐用户最希望得到的物品。

在这里的前提是,如果两个用户喜欢类似的产品或物品特征,他们会对该产品或物品进行相似的评分。由于我们当前的数据与不同手机的用户评分相关,人们可能会根据硬件配置、价格、操作系统等因素对手机进行评分。因此,矩阵分解试图识别这些潜在特征,以预测特定用户和特定产品的评分。

在尝试识别这些潜在特征时,我们基于一个基本假设进行操作,即这些特征的数目少于考虑中的项目总数。这个假设是有意义的,因为如果是这样的话,那么每个用户都会有一个与他自己/她自己(以及类似地对于产品)相关的特定特征。这反过来会使推荐变得毫无意义,因为没有任何用户会对其他用户评分的项目感兴趣(这通常不是情况)。

现在让我们深入了解矩阵分解和我们的推荐引擎的数学细节。

由于我们处理的是不同产品的用户评分,让我们假设U是一个表示用户偏好的矩阵,同样地,一个矩阵P表示我们对其有评分的产品。然后评分矩阵R将被定义为矩阵分解

假设这个过程帮助我们识别出 K 个潜在特征,我们的目标是找到两个矩阵 XY,使得它们的乘积(矩阵乘法)近似于 R

矩阵分解

其中,X 是与用户相关的矩阵,它表示用户和潜在特征之间的关联。另一方面,Y 是与产品相关的矩阵,它表示产品和潜在特征之间的关联。

预测用户 u[i] 对产品 p[j] 的评分 矩阵分解 的任务是通过计算对应于 p[j] 的向量(即用户向量 Y)和 u[i] 的向量(即产品向量 X)的点积来完成的。

矩阵分解

现在,为了找到矩阵 XY,我们利用一种称为 梯度下降 的技术。简单来说,梯度下降试图找到一个函数的局部最小值;它是一种优化技术。在当前上下文中,我们使用梯度下降来迭代地最小化预测评分和实际评分之间的差异。首先,我们随机初始化矩阵 XY,然后计算它们的乘积与实际评分矩阵 R 的差异。

预测值和实际值之间的差异被称为 误差。对于我们的问题,我们将考虑 平方误差,其计算方式如下:

矩阵分解

在这里,r[ij] 是用户 i 对产品 j 的实际评分,而 矩阵分解 是相同评分的预测值。

为了最小化误差,我们需要找到正确的方向或梯度来改变我们的值。为了获得变量 xy 的梯度,我们分别对它们进行微分:

矩阵分解

因此,找到 x[ik]y[kj] 的方程可以表示为:

矩阵分解

其中,α 是表示 下降率 或接近最小值的速率的常数(也称为学习率)。α 的值定义了我们在两个方向上采取的步长的大小。较大的值可能导致振荡,因为我们可能会每次都超过最小值。通常的做法是选择非常小的 α 值,大约为 10^(-4)矩阵分解矩阵分解 是梯度下降每次迭代后 x[ik]y[kj] 的更新值。

如第二章中所述,在《让我们帮助机器学习》中,机器学习算法可能会出现过拟合问题。为了避免过拟合,除了控制矩阵XY中的极端或大值外,我们引入了正则化的概念。正式来说,正则化是指引入额外信息以防止过拟合的过程。正则化会惩罚具有极端值的模型。

为了防止我们的模型过拟合,我们引入了正则化常数β。引入β后,方程更新如下:

矩阵分解

此外,

矩阵分解矩阵分解

由于我们已经有评分矩阵R,并使用它来确定我们的预测值与实际值之间的距离,矩阵分解变成了一个监督学习问题。对于这个监督问题,正如我们在第二章中看到的,《让我们帮助机器学习》,我们使用一些行作为我们的训练样本。设S为我们的训练集,其元素为形式为(u[i], p[j], r[ij])的元组。因此,我们的任务是使训练集S中每个元组(u[i], p[j], r[ij])的误差(e[ij])最小化。

总体误差(例如E)可以计算如下:

矩阵分解

实现

既然我们已经研究了矩阵分解的数学原理,让我们将算法转换为代码,并为之前讨论的移动电话评分数据集准备推荐引擎。

矩阵分解部分所示,输入数据集是一个矩阵,其中每一行代表用户对列中提到的产品的评分。评分范围从 1 到 5,0 代表缺失值。

为了将我们的算法转换为可工作的代码,我们需要完成以下任务:

  • 加载输入数据并将其转换为评分矩阵表示

  • 准备基于矩阵分解的推荐模型

  • 预测并向用户推荐产品

  • 解释和评估模型

加载并将输入数据转换为矩阵表示很简单。如前所述,R 为我们提供了易于使用的实用函数来完成这项任务。

# load raw ratings from csv
raw_ratings <- read.csv(<file_name>)

# convert columnar data to sparse ratings matrix
ratings_matrix <- data.matrix(raw_ratings)

现在我们已经将数据加载到R矩阵中,我们继续准备用户潜在特征矩阵X和项目潜在特征矩阵Y。我们使用runif函数从均匀分布中初始化这两个矩阵。

# number of rows in ratings
rows <- nrow(ratings_matrix)

# number of columns in ratings matrix
columns <- ncol(ratings_matrix)

# latent features
K <- 2

# User-Feature Matrix
X <- matrix(runif(rows*K), nrow=rows, byrow=TRUE)

# Item-Feature Matrix
Y <- matrix(runif(columns*K), nrow=columns, byrow=TRUE)

主要组成部分是矩阵分解函数本身。让我们将任务分为两部分,计算梯度以及随后计算总体误差。

梯度计算的涉及评分矩阵 R 和两个因子矩阵 XY,以及常数 αβ。由于我们处理的是矩阵操作(特别是乘法),我们在开始任何进一步的计算之前将 Y 转置。以下代码行将之前讨论的算法转换为 R 语法。所有变量都遵循与算法相似的命名约定,以便于理解。

for (i in seq(nrow(ratings_matrix))){

 for (j in seq(length(ratings_matrix[i, ]))){

 if (ratings_matrix[i, j] > 0){

 # error 
 eij = ratings_matrix[i, j] - as.numeric(X[i, ] %*% Y[, j])

 # gradient calculation 

 for (k in seq(K)){
 X[i, k] = X[i, k] + alpha * (2 * eij * Y[k, j]/
 - beta * X[i, k])

 Y[k, j] = Y[k, j] + alpha * (2 * eij * X[i, k]/
 - beta * Y[k, j])
 }
 }
 }
 }

算法的下一部分是计算整体误差;我们再次使用相似的变量名以保持一致性:

# Overall Squared Error Calculation

e = 0

for (i in seq(nrow(ratings_matrix))){

 for (j in seq(length(ratings_matrix[i, ]))){

 if (ratings_matrix[i, j] > 0){

 e = e + (ratings_matrix[i, j] - /
 as.numeric(X[i, ] %*% Y[, j]))²

 for (k in seq(K)){
 e = e + (beta/2) * (X[i, k]² + Y[k, j]²)
 }
 }
 }
}

作为最后一部分,我们多次迭代这些计算以减轻冷启动和稀疏性的风险。我们将控制多次启动的变量称为epoch。一旦整体误差下降到某个阈值以下,我们也会停止计算。

此外,由于我们从均匀分布初始化了 XY,预测值将是实数。在返回预测矩阵之前,我们将最终输出四舍五入。

注意,这是一个非常简单的实现,为了便于理解,省略了很多复杂性。因此,预测矩阵中可能包含大于 5 的值。对于当前场景,可以安全地假设超过 5 的最大刻度值等同于 5(同样适用于小于 0 的值)。我们鼓励读者调整代码以处理此类情况。

α 设置为 0.0002β 设置为 0.02K(即潜在特征)设置为 2,并将 epoch 设置为 1000,让我们看看我们的代码的一个样本运行,整体误差阈值设置为 0.001

# load raw ratings from csv
raw_ratings <- read.csv("product_ratings.csv")

# convert columnar data to sparse ratings matrix
ratings_matrix <- data.matrix(raw_ratings)

# number of rows in ratings
rows <- nrow(ratings_matrix)

# number of columns in ratings matrix
columns <- ncol(ratings_matrix)

# latent features
K <- 2

# User-Feature Matrix
X <- matrix(runif(rows*K), nrow=rows, byrow=TRUE)

# Item-Feature Matrix
Y <- matrix(runif(columns*K), nrow=columns, byrow=TRUE)

# iterations
epoch <- 10000

# rate of descent
alpha <- 0.0002

# regularization constant
beta <- 0.02

pred.matrix <- mf_based_ucf(ratings_matrix, X, Y, K, epoch = epoch)

# setting column names
colnames(pred.matrix)<-c("iPhone.4","iPhone.5s","Nexus.5","Moto.X","Moto.G","Nexus.6",/"One.Plus.One")

上述代码行利用前面解释过的函数来准备推荐模型。预测评分或输出矩阵看起来如下:

实现

预测评分矩阵

结果解释

让我们快速进行视觉检查,看看我们的预测做得有多好或有多差。以用户 1 和用户 3 作为我们的训练样本。从输入数据集中,我们可以清楚地看到用户 1 对 iPhone 给出了高评分,而用户 3 对基于 Android 的手机也做了同样的操作。以下并排比较显示了我们的算法预测的值与实际值非常接近:

结果解释

用户 1 的评分

让我们查看以下截图中的用户 3 的评分:

结果解释

用户 3 的评分

现在我们有了更新值的评分矩阵,我们准备向用户推荐产品。只显示用户尚未评分的产品是常识。正确的推荐集也将使卖家能够推销那些有较高概率被用户购买的产品。

常规做法是返回每个用户未评分产品列表中排名前N的项目列表。考虑的用户通常被称为活跃用户。让我们以用户 6 作为我们的活跃用户。这位用户只按以下顺序对 Nexus 6、One Plus One、Nexus 5 和 iPhone4 进行了评分,即 Nexus 6 评分很高,而 iPhone4 评分最低。使用我们的算法为这样的客户提供Top 2推荐手机的结果将是 Moto X 和 Moto G(确实非常正确,你明白为什么吗?)。

因此,我们构建了一个足够智能的推荐引擎,能够为安卓爱好者推荐合适的手机,并拯救了世界免于又一次灾难!

数据拯救!

使用矩阵分解的简单推荐引擎实现让我们领略了此类系统实际运作的方式。接下来,让我们通过使用推荐引擎来进入一些实际操作。

适用于生产的推荐引擎

到目前为止,我们已经详细了解了推荐引擎,甚至从头开始开发了一个(使用矩阵分解)。通过所有这些,我们可以清楚地看到此类系统的应用范围是多么广泛。

电子商务网站(或者更确切地说,任何流行的技术平台)今天都提供了大量的内容。不仅如此,用户数量也非常庞大。在这种场景下,当成千上万的用户在全球范围内同时浏览/购买商品时,为他们提供推荐本身就是一项任务。更复杂的是,良好的用户体验(例如响应时间)可以在两个竞争对手之间产生巨大差异。这些都是处理数百万客户日复一日生产的实时系统实例。

注意

有趣的事实

Amazon.com 是电子商务领域最大的名字之一,拥有 2.44 亿活跃客户。想象一下,为了向如此庞大的客户群提供推荐,需要处理多少数据!

来源:www.amazon.com/b?ie=UTF8&node=8445211011

为了在这些平台上提供无缝的使用能力,我们需要高度优化的库和硬件。对于一个推荐引擎来说,要同时处理每秒成千上万的用户,R 语言有一个强大且可靠的框架,称为recommenderlab

Recommenderlab 是一个广泛使用的 R 扩展,旨在为推荐引擎提供一个稳健的基础。该库的重点是提供高效的数据处理、标准算法的可用性和评估能力。在本节中,我们将使用 recommenderlab 处理一个相当大的数据集,为用户推荐项目。我们还将使用 recommenderlab 的评估函数来查看我们的推荐系统是好是坏。这些功能将帮助我们构建一个生产就绪的推荐系统,类似于(或者至少更接近)许多在线应用程序,如亚马逊或 Netflix 所使用的系统。

本节使用的数据集包含 100 个物品的评分,由 5000 个用户进行评分。数据已被匿名化,产品名称已被产品 ID 替换。使用的评分范围是 0 到 5,其中 1 是最差的,5 是最佳的,0 表示未评分的项目或缺失的评分。

要使用 recommenderlab 为生产就绪的系统构建推荐引擎,需要执行以下步骤:

  1. 提取、转换和分析数据。

  2. 准备推荐模型并生成推荐。

  3. 评估推荐模型。

我们将在以下子节中查看所有这些步骤。

提取、转换和分析

就像任何数据密集型(尤其是机器学习)应用程序一样,首要步骤是获取数据,理解/探索它,然后将其转换为适合当前应用程序的算法所需的格式。对于使用 recommenderlab 包的推荐引擎,我们首先从上一节中描述的 csv 文件加载数据,然后使用各种 R 函数对其进行探索。

# Load recommenderlab library
library("recommenderlab")

# Read dataset from csv file
raw_data <- read.csv("product_ratings_data.csv")

# Create rating matrix from data 
ratings_matrix<- as(raw_data, "realRatingMatrix")

#view transformed data
image(ratings_matrix[1:6,1:10])

代码的前一节加载了 recommenderlab 包,然后使用标准实用工具函数读取 product_ratings_data.csv 文件。为了探索以及进一步的步骤,我们需要将数据转换为用户-项目评分矩阵格式(如 核心概念和定义 节中所述)。

as(<data>,<type>) 实用工具将 csv 转换为所需的评分矩阵格式。

csv 文件包含以下截图所示格式的数据。每一行包含一个用户对特定产品的评分。列标题是自解释的。

提取、转换和分析

产品评分数据

realRatingMatrix 转换将数据转换为以下图像所示的矩阵。用户表示为行,而列表示产品。评分使用渐变尺度表示,其中白色表示缺失/未评分的评分,而黑色表示 5 分/最佳评分。

提取、转换和分析

我们数据评分矩阵表示

现在我们已经将数据放入我们的环境中,让我们探索一些其特征,看看我们是否能解码一些关键模式。

首先,我们从主数据集中提取一个代表性样本(参见图 产品评分数据)并对其进行分析,以了解:

  • 我们用户群体的平均评分分数

  • 用户群体中项目评分的分布/扩散

  • 每个用户评分的项目数量

以下代码行帮助我们探索数据集样本并分析之前提到的点:

# Extract a sample from ratings matrix
sample_ratings <-sample(ratings_matrix,1000)

# Get the mean product ratings as given by first user
rowMeans(sample_ratings[1,])

# Get distribution of item ratings
hist(getRatings(sample_ratings), breaks=100,/
 xlab = "Product Ratings",main = " Histogram of Product Ratings")

# Get distribution of normalized item ratings
hist(getRatings(normalize(sample_ratings)),breaks=100,/
 xlab = "Normalized Product Ratings",main = /
 " Histogram of Normalized Product Ratings")

# Number of items rated per user
hist(rowCounts(sample_ratings),breaks=50,/
 xlab = "Number of Products",main =/
 " Histogram of Product Count Distribution")

我们从数据集中提取了 1,000 个用户的样本用于探索目的。用户评分的平均值,即用户评分样本的第一行给出的2.055,告诉我们这个用户要么没有看到/评分很多产品,要么通常评分很低。为了更好地了解用户如何评分产品,我们生成了一个项目评分分布的直方图。这个分布峰值在中间,即3。直方图如下所示:

提取、转换和分析

评分分布的直方图

直方图显示评分围绕平均值正常分布,对于评分非常高或非常低的产品计数较低。

最后,我们检查用户评分产品数量的分布。我们准备了一个直方图来显示这种分布:

提取、转换和分析

评分数量的直方图

前面的直方图显示有许多用户评分了70个或更多的产品,同样也有许多用户评分了所有100个产品。

探索步骤帮助我们了解我们的数据是什么样的。我们还对用户通常如何评分产品以及有多少产品被评分有了了解。

模型准备和预测

我们的数据已经在我们转换成评分矩阵格式的 R 环境中。在本节中,我们感兴趣的是基于用户协同过滤准备推荐引擎。我们将使用与之前章节中描述的类似术语。Recommenderlab 提供了直观的实用工具来学习和准备构建推荐引擎的模型。

我们基于仅 1,000 个用户的样本来准备我们的模型。这样,我们可以使用这个模型来预测评分矩阵中其余用户的缺失评分。以下代码行利用前 1,000 行来学习模型:

# Create 'User Based collaborative filtering' model 
ubcf_recommender <- Recommender(ratings_matrix[1:1000],"UBCF")

代码中的"UBCF"代表基于用户的协同过滤。Recommenderlab 还提供了其他算法,例如IBCF基于项目的协同过滤PCA主成分分析,以及其他算法。

在准备模型之后,我们使用它来预测系统中第 1,010 个和第 1,011 个用户的评分。Recommenderlab 还要求我们提及要推荐给用户的物品数量(当然按照偏好顺序)。对于当前情况,我们提到推荐 5 个物品。

# Predict list of product which can be recommended to given users
recommendations <- predict(ubcf_recommender,/
 ratings_matrix[1010:1011], n=5)

# show recommendation in form of the list
as(recommendations, "list")

前面的代码生成了两个列表,每个列表对应一个用户。这些列表中的每个元素都是推荐的产品。模型预测,对于用户 1,010,应该推荐产品 prod_93 作为最推荐的产品,其次是 prod_79,依此类推。

# output generated by the model
[[1]]
[1] "prod_93" "prod_79" "prod_80" "prod_83" "prod_89"

[[2]]
[1] "prod_80" "prod_85" "prod_87" "prod_75" "prod_79"

Recommenderlab 是一个健壮的平台,经过优化以处理大型数据集。我们只需几行代码就能加载数据,学习模型,甚至在几乎没有任何时间的情况下向用户推荐产品。与使用矩阵分解开发的简单推荐引擎相比(与 recommenderlab 相比,代码行数很多),除了明显的性能差异之外,这也有很大的不同。

模型评估

我们已经成功准备了一个模型,并使用它来预测和向系统中的用户推荐产品。但我们对我们模型的准确性了解多少?为了评估准备好的模型,recommenderlab 提供了方便易用的工具。由于我们需要评估我们的模型,我们需要将其分为训练数据和测试数据集。此外,recommenderlab 要求我们说明用于测试的物品数量(它使用其余部分来计算误差)。

对于当前案例,我们将使用 500 个用户来准备一个评估模型。该模型将基于 90-10 的训练-测试数据集分割,其中 15 个项目用于测试集。

# Evaluation scheme
eval_scheme <- evaluationScheme(ratings_matrix[1:500],/
 method="split",train=0.9,given=15)

# View the evaluation scheme
eval_scheme

# Training model
training_recommender <- Recommender(getData(eval_scheme,/
 "train"), "UBCF")

# Preditions on the test dataset
test_rating <- predict(training_recommender,/
 getData(eval_scheme, "known"), type="ratings")

#Error 
error <- calcPredictionAccuracy(test_rating,/
 getData(eval_scheme, "unknown"))

error

我们使用基于 UBCF 算法的评估方案来训练我们的模型。从训练数据集中准备好的模型用于预测给定项目的评分。我们最终使用 calcPredictionAccuracy 方法来计算测试集中已知和未知成分预测评分之间的误差。对于我们的案例,我们得到以下输出:

模型评估

生成的输出提到了 RMSE(均方根误差)、MSE(均方误差)和 MAE(平均绝对误差)的值。特别是对于 RMSE,其值与正确值相差 1.162(请注意,由于采样、迭代等各种因素,值可能略有偏差)。当将不同 CF 算法的输出进行比较时,这种评估将更有意义。

为了评估 UBCF,我们使用 IBCF 作为比较。以下几行代码帮助我们准备一个基于 IBCF 的模型并测试评分,然后可以使用 calcPredictionAccuracy 工具进行比较:

# Training model using IBCF
training_recommender_2 <- Recommender(getData(eval_scheme,/
 "train"), "IBCF")

# Preditions on the test dataset
test_rating_2 <- predict(training_recommender_2,/
 getData(eval_scheme, "known"),/
 type="ratings")

error_compare <- rbind(calcPredictionAccuracy(test_rating,/
 getData(eval_scheme, "unknown")),/
 calcPredictionAccuracy(test_rating_2,/
 getData(eval_scheme, "unknown")))

rownames(error_compare) <- c("User Based CF","Item Based CF")

比较输出显示,UBCF 在 RMSE、MSE 和 MAE 的值方面优于 IBCF。

模型评估

同样,我们可以使用 recommenderlab 中可用的其他算法来测试/评估我们的模型。我们鼓励用户尝试更多,看看哪个算法在预测评分中的误差最小。

摘要

在本章中,我们继续追求在电子商务领域应用机器学习以提升销售和整体用户体验。上一章讨论了基于交易日志的推荐;在本章中,我们考虑了人为因素,并探讨了基于用户行为的推荐引擎。

我们首先理解了推荐系统及其分类为基于用户、基于内容和混合推荐系统。我们简要提到了与推荐引擎相关的一般性问题。然后我们深入探讨了协同过滤的具体细节,并讨论了预测和相似度度量的数学原理。在弄清楚基础知识后,我们从头开始构建自己的推荐引擎。我们使用矩阵分解,通过一个小型虚拟数据集逐步构建推荐引擎。然后我们转向使用 R 语言中流行的库 recommenderlab 来构建一个生产就绪的推荐引擎。我们使用基于用户的 CF 作为核心算法,在一个包含 5,000 个用户对 100 个产品进行评分的大数据集上构建推荐模型。我们通过使用 recommenderlab 的实用方法来评估我们的推荐模型来结束我们的讨论。

接下来的几章将从电子商务领域转向金融领域,并利用机器学习来处理一些更有趣的应用场景。

第五章. 信用风险检测与预测 – 描述性分析

在最后两章中,你看到了一些围绕零售和电子商务领域的问题。你现在知道如何从购物模式中检测和预测购物趋势,以及如何构建推荐系统。如果你还记得第一章,使用 R 和机器学习入门,机器学习的应用是多样化的,我们可以将相同的概念和技术应用于解决现实世界中广泛的各种问题。在这里,我们将解决一个全新的问题,但请记住你所学到的知识,因为你在之前学到的几个概念很快就会派上用场!

在接下来的几章中,我们将探讨与金融领域相关的新问题。我们将研究一家特定德国银行的客户,根据之前收集的一些数据,这些客户可能是银行的信用风险。我们将对数据进行描述性和探索性分析,以突出数据集中不同的潜在特征,并查看它们与信用风险的关系。在下一步,我们将使用机器学习算法和数据特征来构建预测模型,以检测和预测可能成为潜在信用风险的客户。你可能还记得,为了进行这项分析并保持不变,我们需要的是数据和算法。

你可能会惊讶地发现,风险分析是金融机构,包括银行、投资公司、保险公司和经纪公司等,最关注的领域之一。这些机构中的每一个通常都有专门的团队来解决围绕风险分析的问题。经常分析的风险例子包括信用风险、销售风险、与欺诈相关的风险等等。

在本章中,我们将重点关注以下主题:

  • 信用风险数据集的描述性分析

  • 信用风险问题的领域知识

  • 数据集特征的详细分析

  • 数据的探索性分析

  • 各种数据特征的可视化

  • 用于确定特征重要性的统计测试

总是记住,在解决任何机器学习问题之前,领域知识是必不可少的,否则我们可能会盲目地应用随机算法和技术,这可能导致结果不正确。

分析类型

在我们开始解决下一个挑战之前,了解不同类型的分析将非常有用,这些分析大致涵盖了数据科学领域。我们使用各种数据挖掘和机器学习技术来解决不同的数据问题。然而,根据技术的机制和最终结果,我们可以将分析大致分为四种不同类型,以下将进行解释:

  • 描述性分析:这是我们分析一些数据时使用的。我们从查看数据的各种属性开始,提取有意义的特征,并使用统计和可视化来了解已经发生的事情。描述性分析的主要目的是获得我们处理的数据类型的大致概念,并总结过去发生的事情。几乎超过 80%的商业分析都是描述性的。

  • 诊断分析:这有时与描述性分析结合在一起。这里的主要目标是深入数据以寻找特定的模式并回答诸如为什么发生这种情况等问题。通常,它涉及根本原因分析,以找到事情发生的原因以及在其发生过程中涉及的主要因素。有时回归建模等技术有助于实现这一点。

  • 预测分析:这是任何分析流程中的最后一步。一旦你构建了具有良好数据流且稳定的预测模型,你就可以构建利用这些模型并开始制定可能采取的行动以改善业务的系统。请记住,预测模型只能预测未来可能发生的事情,因为所有模型本质上都是概率性的,没有任何事情是 100%确定的。

  • 规范性分析:如果你处于构建了具有良好数据流且一致的预测模型,能够预测未来可能发生的事情的阶段,那么这是任何分析流程中的最后一步。然后你可以构建利用这些模型并开始制定可能采取的行动以改善业务的系统。请记住,你需要具有良好数据的运行预测模型和出色的反馈机制才能实现这一点。

大多数组织进行大量的描述性分析以及一定程度的预测分析。然而,由于不断变化的商业条件和数据流以及与之相关的问题,实施规范性分析非常困难,其中最常见的问题是数据清洗问题。在本章中,我们将先讨论描述性分析,然后再在下一章中转向预测分析,以解决与信用风险分析相关的问题。

我们接下来的挑战

在过去的几章中,我们已经处理了一些电子商务领域机器学习的有趣应用。对于接下来的两章,我们的主要挑战将是在金融领域。我们将使用数据分析与机器学习技术来分析一家德国银行的财务数据。这些数据将包含大量关于该银行客户的信息。我们将分两个阶段分析这些数据,包括描述性和预测性分析。

  • 描述性:在这里,我们将仔细研究数据和其各种属性。我们将进行描述性分析和可视化,以了解我们正在处理什么样的特征以及它们可能与信用风险有何关联。我们在这里处理的数据已经标记,我们将能够看到有多少客户是信用风险,有多少不是。我们还将仔细研究数据中的每个特征,了解其重要性,这将在下一步中很有用。

  • 预测性:在这里,我们将更多地关注用于预测建模的机器学习算法,利用我们在上一步中已经获取的数据来构建预测模型。我们将使用各种机器学习算法,并在预测客户是否可能成为潜在的信用风险时测试模型的准确性。我们将使用标记数据来训练模型,然后在几个数据实例上测试模型,将我们的预测结果与实际结果进行比较,以查看我们的模型表现如何。

预测信用风险的重要性对金融机构来说非常有用,例如经常处理客户贷款申请的银行。他们必须根据他们关于客户的信息来决定批准或拒绝贷款。如果他们有一个强大的机器学习系统已经建立,可以分析客户数据并指出哪些客户可能是信用风险,那么他们可以通过不批准这些客户的贷款来防止他们的业务遭受损失。

什么是信用风险?

我们从本章开始就一直在使用这个术语信用风险,你们中的许多人可能想知道这究竟是什么意思,即使你在阅读了上一节之后可能已经猜到了。在这里,我们将清楚地解释这个术语,以便你们在后续章节分析数据时,对数据和其特征的理解不会有任何问题。

信用风险的标准定义是因借款人未能按时偿还债务而产生的违约风险。这种风险由贷款人承担,因为贷款人将遭受本金及其利息的损失。

在我们的案例中,我们将处理一家作为金融组织向申请贷款的客户发放贷款的银行。因此,可能无法按时偿还贷款的客户将成为银行的信用风险。通过分析客户数据并在其上应用机器学习算法,银行将能够提前预测哪些客户可能是潜在的信用风险。这将有助于风险缓解,并通过不向可能成为银行信用风险的客户发放贷款来最小化损失。

获取数据

我们数据分析流程的第一步是获取数据集。我们实际上已经清理了数据,并为数据属性提供了有意义的名称,您可以通过打开german_credit_dataset.csv文件来检查这一点。您还可以从以下 URL 获取实际数据集,该数据集来自慕尼黑大学统计学系:www.statistik.lmu.de/service/datenarchiv/kredit/kredit_e.html

您可以下载数据,然后在同一目录下启动 R 并运行以下命令,以了解以下章节中我们将要处理的数据:

> # load in the data and attach the data frame
> credit.df <- read.csv("german_credit_dataset.csv", header = TRUE, sep = ",") 
> # class should be data.frame
> class(credit.df)
[1] "data.frame"
> 
> # get a quick peek at the data
> head(credit.df)

下图显示了数据的头六行。每一列表示数据集的一个属性。我们将在稍后更详细地关注每个属性。

获取数据

要获取有关数据集及其属性的详细信息,您可以使用以下代码片段:

> # get dataset detailed info
> str(credit.df)

上述代码将使您能够快速查看您正在处理的数据点的总数,这包括记录数、属性数以及每个属性的详细信息,例如属性名称、类型和一些属性值的样本,正如您可以在以下屏幕截图中所见。利用这一点,我们可以对不同的属性及其数据类型有一个很好的了解,以便我们知道对它们应用哪些转换,以及在描述性分析期间使用哪些统计方法。

获取数据

从前面的输出中,您可以看到我们的数据集共有 1000 条记录,其中每条记录处理与一位银行客户相关的数据点。每条记录都有各种数据点或属性来描述数据,我们为每条记录有 21 个属性。每个属性的数据类型和样本值也在之前的图像中显示。

注意

请注意,默认情况下,R 根据其值将int数据类型分配给变量,但我们在数据预处理阶段将根据它们的实际语义更改其中一些。

数据预处理

在本节中,我们将重点关注数据预处理,包括数据清理、转换以及如果需要的话进行归一化。基本上,我们在开始对数据进行任何分析之前执行操作以使数据准备就绪。

处理缺失值

当您处理的数据将包含缺失值时,这些缺失值通常在 R 中表示为NA。有几种方法可以检测它们,我们将在下面展示几种方法。请注意,您可以通过多种方式完成此操作。

> # check if data frame contains NA values
> sum(is.na(credit.df))
[1] 0
> 
> # check if total records reduced after removing rows with NA 
> # values
> sum(complete.cases(credit.df))
[1] 1000

is.na 函数非常有用,因为它有助于找出数据集中是否有任何元素具有 NA 值。还有另一种方法可以做到这一点,即使用 complete.cases 函数,它本质上返回一个逻辑向量,表示行是否完整以及是否有任何 NA 值。你可以检查与原始数据集相比总记录数是否减少,这样你就会知道数据集中有一些缺失值。幸运的是,在我们的案例中,我们没有缺失值。然而,在未来如果你处理缺失值,有各种方法可以处理。其中一些包括使用 complete.cases 等函数删除具有缺失值的行,或者用最频繁的值或平均值等填充它们。这也被称为缺失值插补,它取决于变量的属性和你要处理的领域。因此,我们不会在这个领域过多关注。

数据类型转换

我们之前提到,默认情况下,数据集的所有属性都已声明为 int,这是 R 中的数值类型,但在这个案例中并非如此,我们必须根据变量语义和值来更改它。如果你已经上过基础的统计学课程,你可能知道我们通常处理两种类型的变量:

  • 数值变量:这些变量的值具有某种数学意义。这意味着你可以对它们执行数学运算,例如加法、减法等。一些例子可以是人的年龄、体重等。

  • 分类变量:这些变量的值没有任何数学意义,你不能对它们执行任何数学运算。这个变量中的每个值都属于一个特定的类别或类别。一些例子可以是人的性别、工作等。

由于我们数据集中的所有变量默认都已转换为数值类型,我们只需要将分类变量从数值数据类型转换为因子,这是在 R 中表示分类变量的好方法。

我们数据集中的数值变量包括 credit.duration.monthscredit.amount 和年龄,我们不需要进行任何转换。然而,剩余的 18 个变量都是分类变量,我们将使用以下实用函数来转换它们的数据类型:

# data transformation
to.factors <- function(df, variables){
 for (variable in variables){
 df[[variable]] <- as.factor(df[[variable]])
 }
 return(df)
}

此函数将用于我们的现有数据框 credit.df,如下所示,以转换变量数据类型:

> # select variables for data transformation
> categorical.vars <- c('credit.rating', 'account.balance', 
+                       'previous.credit.payment.status',
+                       'credit.purpose', 'savings', 
+                       'employment.duration', 'installment.rate',
+                       'marital.status', 'guarantor', 
+                       'residence.duration', 'current.assets',
+                       'other.credits', 'apartment.type', 
+                       'bank.credits', 'occupation', 
+                       'dependents', 'telephone', 
+                       'foreign.worker')
> 
> # transform data types
> credit.df <- to.factors(df = credit.df, 
+                         variables=categorical.vars)
> 
> # verify transformation in data frame details
> str(credit.df)

现在,我们可以看到以下输出中转换后的数据类型所选分类变量的属性细节。你会注意到,在数据集的 21 个变量/属性中,有 18 个已经成功转换为分类变量。

数据类型转换

这标志着数据预处理步骤的结束,我们将现在深入分析我们的数据集。

注意

请注意,我们的一些数据集属性/特征有很多类别或类别,我们将在分析阶段进行更多的数据转换和特征工程,以防止我们的预测模型过度拟合,我们将在后面讨论这一点。

数据分析和转换

现在我们已经处理了我们的数据,它已准备好进行分析。我们将进行描述性和探索性分析,如前所述。我们将分析不同的数据集属性,并讨论它们的显著性、语义以及与信用风险属性的关系。我们将使用统计函数、列联表和可视化来描述所有这些。

除了这个,我们还将对我们数据集中的某些特征进行数据转换,特别是分类变量。我们将这样做是为了合并具有相似语义的类别,并通过与相似类别合并来删除比例非常小的类别。这样做的一些原因包括防止我们将在第六章中构建的预测模型过度拟合,即“信用风险检测与预测 – 预测分析”,将语义相似的类别联系起来,以及因为像逻辑回归这样的建模技术并不擅长处理具有大量类别的分类变量。我们首先将分析数据集中的每个特征/变量,然后根据需要执行任何转换。

构建分析工具

在我们开始分析之前,我们将开发一些实用函数,我们将使用这些函数来分析数据集特征。请注意,所有实用函数都定义在一个单独的.R文件中,称为descriptive_analytics_utils.R。您可以通过使用命令source('descriptive_analytics_utils.R')将所有函数加载到内存中或任何其他 R 脚本文件中,然后开始使用它们。我们现在将讨论这些实用函数。

现在,我们将讨论我们使用过的各种包。我们使用了一些包,如pastecsgmodels来获取特征的摘要统计信息以及构建列联表。gridExtraggplot2包被用于网格布局和构建可视化。如果您尚未安装它们,可以使用install.packages命令进行安装。接下来,按照以下代码片段加载这些包:

# load dependencies
library(gridExtra) # grid layouts
library(pastecs) # details summary stats
library(ggplot2) # visualizations
library(gmodels) # build contingency tables

stat.desc and summary functions for getting detailed and condensed summary statistics about the variable. The conventions for independent variables and dependent variables are denoted by indep.var and dep.var in the code segments that follow and in other functions later on.
# summary statistics
get.numeric.variable.stats <- function(indep.var, detailed=FALSE){
 options(scipen=100)
 options(digits=2)
 if (detailed){
 var.stats <- stat.desc(indep.var)
 }else{
 var.stats <- summary(indep.var)
 }

 df <- data.frame(round(as.numeric(var.stats),2))
 colnames(df) <- deparse(substitute(indep.var))
 rownames(df) <- names(var.stats)

 if (names(dev.cur()) != "null device"){
 dev.off()
 }
 grid.table(t(df))
}

接下来,我们将构建一些用于可视化数值变量的函数。我们将通过使用直方图/密度图箱线图来描述属性分布来完成这项工作。

# visualizations
# histograms\density
visualize.distribution <- function(indep.var){
 pl1 <- qplot(indep.var, geom="histogram", 
 fill=I('gray'), binwidth=5,
 col=I('black'))+ theme_bw()
 pl2 <- qplot(age, geom="density",
 fill=I('gray'), binwidth=5, 
 col=I('black'))+ theme_bw()

 grid.arrange(pl1,pl2, ncol=2)
}

# box plots
visualize.boxplot <- function(indep.var, dep.var){
 pl1 <- qplot(factor(0),indep.var, geom="boxplot", 
 xlab = deparse(substitute(indep.var)), 
 ylab="values") + theme_bw()
 pl2 <- qplot(dep.var,indep.var,geom="boxplot",
 xlab = deparse(substitute(dep.var)),
 ylab = deparse(substitute(indep.var))) + theme_bw()

 grid.arrange(pl1,pl2, ncol=2)
}

我们使用了ggplot2包中的qplot函数来构建我们将看到的可视化。现在,我们将把注意力转向分类变量。我们将从一个函数开始,该函数用于获取任何分类变量的汇总统计信息。

# summary statistics
get.categorical.variable.stats <- function(indep.var){

 feature.name = deparse(substitute(indep.var))
 df1 <- data.frame(table(indep.var))
 colnames(df1) <- c(feature.name, "Frequency")
 df2 <- data.frame(prop.table(table(indep.var)))
 colnames(df2) <- c(feature.name, "Proportion")

 df <- merge(
 df1, df2, by = feature.name
 )
 ndf <- df[order(-df$Frequency),]
 if (names(dev.cur()) != "null device"){
 dev.off()
 }
 grid.table(ndf)
}

前面的函数将总结分类变量,并讨论其中有多少类别或类别以及一些其他细节,如频率和比例。如果你还记得,我们之前提到我们还将展示分类变量与类/依赖变量credit.risk的关系。以下函数将帮助我们以列联表的形式实现相同的目标:

# generate contingency table
get.contingency.table <- function(dep.var, indep.var, 
 stat.tests=F){
 if(stat.tests == F){
 CrossTable(dep.var, indep.var, digits=1, 
 prop.r=F, prop.t=F, prop.chisq=F)
 }else{
 CrossTable(dep.var, indep.var, digits=1, 
 prop.r=F, prop.t=F, prop.chisq=F,
 chisq=T, fisher=T)
 }
}

我们还将构建一些用于展示可视化的函数。我们将使用以下函数通过条形图可视化分类变量的分布:

# visualizations
# barcharts
visualize.barchart <- function(indep.var){
 qplot(indep.var, geom="bar", 
 fill=I('gray'), col=I('black'),
 xlab = deparse(substitute(indep.var))) + theme_bw()
}

我们将使用 mosaic 图来展示之前提到的列联表的可视化,使用以下函数:

# mosaic plots
visualize.contingency.table <- function(dep.var, indep.var){
 if (names(dev.cur()) != "null device"){
 dev.off()
 }
 mosaicplot(dep.var ~ indep.var, color=T, 
 main = "Contingency table plot")
}

现在我们已经构建了所有必要的工具,接下来我们将开始分析数据,并在下一节进行详细说明。

分析数据集

在本节中,我们将分析数据集的每个特征,并在必要时以汇总统计、关系、统计检验和可视化的形式展示我们的分析。我们将用表格表示对每个变量进行的必要分析。需要记住的一个重要点是,代码中用dep.var表示的依赖特征始终是credit.rating,因为这是依赖于其他特征的变量;这些特征是独立变量,在表格和图中通常用indep.var表示。

我们将对一些重要特征进行详细分析和转换,这些特征具有很大的意义,特别是具有大量类别的数据特征,这样我们可以清楚地理解数据分布以及它们在数据转换时的变化。对于剩余的特征,我们不会过多关注汇总统计,而更强调通过转换和它们与依赖变量credit.rating的关系来进行特征工程。

现在,我们将附加数据框,以便我们可以轻松访问单个特征。你可以使用以下代码片段来完成:

> # access dataset features directly
> attach(credit.df)

现在,我们将从依赖变量credit.risk开始分析,也称为数据集中的类变量,我们将在下一章尝试预测它。

以下代码片段帮助我们获取该特征的所需汇总统计信息:

> # credit.rating stats
> get.categorical.variable.stats(credit.rating)
> # credit.rating visualizations
> visualize.barchart(credit.rating)

以下可视化告诉我们credit.rating有两个类别,10,并提供了必要的统计数据。基本上,信用评分为1的客户是可信赖的,而评分为0的客户是不可信赖的。我们还从条形图中观察到,在银行中,有信用评级的客户比例相对于其他客户来说显著较高。

分析数据集

接下来,我们将分析account.balance特征。基本上,这个属性表示客户当前账户的余额。

我们将首先使用以下代码片段获取摘要统计信息并绘制条形图。为了更好地理解,我们将一起包含输出结果。

> # account.balance stats and bar chart
> get.categorical.variable.stats(account.balance)
> visualize.barchart(account.balance)

从以下可视化中,你可以看到account.balance有四个不同的类别,并且每个类别都有一些特定的语义,我们将在下面讨论。

分析数据集

从前面的输出中,你可以看到account.balance有四个不同的类别,并且每个类别都有一些语义,如下定义。货币 DM 表示德国的旧货币名称——德国马克。

这四个类别表示以下主要语义或至少持有一年以上的支票账户:

  • 1: 没有运行中的银行账户

  • 2: 没有余额或借记

  • 3: 余额小于200 DM

  • 4: 余额>=200 DM

货币 DM 表示德国马克,德国的旧货币名称。在这里,我们将进行一些特征工程,并将类别 3 和 4 合并在一起,以表示账户中有正余额的客户。我们这样做是因为类别 3 的比例相对于其他类别来说很小,我们不希望在不必要的情况下为每个特征保留太多的类别,除非它们是关键的。我们将通过以下代码片段实现这一点。

首先,我们将加载进行此操作所需的包。如果你还没有安装该包,请使用命令install.packages("car")进行安装。

> #load dependencies
> library(car)

现在,我们将重新编码必要的类别,如下所示:

> # recode classes and update data frame
> new.account.balance <- recode(account.balance,
+                           "1=1;2=2;3=3;4=3")
> credit.df$account.balance <- new.account.balance

现在,我们将使用之前讨论的列联表来查看new.account.balancecredit.rating之间的关系,并使用以下代码片段通过 mosaic plot 进行可视化。我们还将进行一些统计测试,我将在稍后简要解释。

> # contingency table and mosaic plot 
> get.contingency.table(credit.rating, new.account.balance, 
 stat.tests=T)
> visualize.contingency.table(credit.rating, new.account.balance)

在以下图中,你现在可以看到account.balance的各个类别与credit.rating在表格和图中的分布情况。一个有趣的现象是,90%的账户有资金的客户不是潜在的信用风险,这听起来是合理的。

分析数据集

我们在这里还执行了两个统计测试:卡方测试和费舍尔测试,这两个测试都是列联表中广泛用于假设检验的相关测试。详细讨论这些测试中涉及的统计计算超出了本章的范围。我将用一种易于理解的方式来说明。通常,我们从一个零假设开始,即之前描绘的两个变量之间不存在关联或关系,以及一个备择假设,即两个变量之间存在关联或关系的可能性。如果从测试中获得 p 值小于或等于0.05,那么我们才能拒绝零假设,接受备择假设。在这种情况下,您可以清楚地看到,这两个测试都给出了< 0.05的 p 值,这肯定有利于备择假设,即credit.ratingaccount.balance之间存在某种关联。这类测试在构建统计模型时非常有用。您可以在互联网或任何统计学书籍中查找前面的测试,以深入了解 p 值的意义及其工作原理。

注意

请注意,从现在开始,我们将只展示每个特征最重要的分析结果。然而,您始终可以使用我们之前解释过的函数尝试获取各种分析技术的相关信息。对于列联表,使用get.contingency.table()函数。可以通过在get.contingency.table()函数中将stat.tests参数设置为TRUE来执行统计测试。您还可以使用visualize.contingency.table()函数来查看错综图。

现在我们将查看credit.duration.months,这表示信用期限(按月)。这是一个数值变量,分析将与其他分类变量略有不同。

> # credit.duration.months analysis
> get.numeric.variable.stats(credit.duration.months)

我们可以从以下图中看到相同的内容:

分析数据集

我们看到的值是按月计算的,我们得到这个特征的典型汇总统计量,包括平均值、中位数和四分位数。我们现在将使用histograms/density图和boxplots来可视化这个特征的值分布。

> # histogram\density plot
> visualize.distribution(credit.duration.months)

我们现在以箱线图的形式可视化相同的内容,包括显示与credit.rating相关的下一个。

> # box plot
> visualize.boxplot(credit.duration.months, credit.rating)

有趣的是,从下面的图中我们可以看到,信用评级差的客户的平均信用期限高于信用评级好的客户。如果我们假设许多信用期限长的客户违约了,这似乎是合理的。

分析数据集

接下来,我们将查看下一个变量previous.credit.payment.status,这表示客户在支付其先前信用时的状态。这是一个分类变量,我们得到如下的统计数据:

> # previous.credit.payment.status stats and bar chart
> get.categorical.variable.stats(previous.credit.payment.status)
> visualize.barchart(previous.credit.payment.status)

这给我们以下表格和条形图,展示了数据分布:

分析数据集

这些类别表示以下主要语义:

  • 0: 犹豫不决的支付

  • 1: 运行账户有问题

  • 2: 没有剩余的先前信用

  • 3: 这家银行的当前信用额度没有问题

  • 4: 在这家银行还清了之前的信用

我们将对这个功能应用以下变换,因此新的语义将是:

  • 1: 支付存在一些问题

  • 2: 所有信用已支付

  • 3: 没有问题,并且只在银行支付了信用

我们将在以下代码片段中执行变换:

> # recode classes and update data frame
> new.previous.credit.payment.status <- 
 recode(previous.credit.payment.status,
+                                           "0=1;1=1;2=2;3=3;4=3")
> credit.df$previous.credit.payment.status <- 
 new.previous.credit.payment.status

变换后特征的列联表如下获得:

> # contingency table
> get.contingency.table(credit.rating,
 new.previous.credit.payment.status)

从以下表格中我们可以观察到,拥有良好信用评级的最大人数已经没有问题地支付了之前的信用,而没有良好信用评级的人则支付上存在一些问题,这是有道理的!

分析数据集

我们接下来要分析的功能是 credit.purpose,它表示信用金额的目的。这也是一个分类变量,我们得到其摘要统计信息,并绘制以下条形图来显示其各种类别的频率:

> # credit.purpose stats and bar chart
> get.categorical.variable.stats(credit.purpose)
> visualize.barchart(credit.purpose)

这给我们以下表格和条形图,展示了数据分布:

分析数据集

我们观察到,仅针对这个功能就有惊人的 11 个类别。除此之外,我们还观察到,与前 5 个类别相比,有几个类别的比例极低,而且类别标签 7甚至没有出现在数据集中!这正是我们需要通过将一些这些类别分组在一起进行特征工程的原因,就像我们之前做的那样。

这些类别表示以下主要语义:

  • 0: 其他

  • 1: 新车

  • 2: 二手车

  • 3: 家具物品

  • 4: 收音机或电视

  • 5: 家用电器

  • 6: 维修

  • 7: 教育

  • 8: 假期

  • 9: 再培训

  • 10: 商业

我们将通过结合一些现有的类和变换后的新语义来转换这个功能,变换后的语义如下:

  • 1: 新车

  • 2: 二手车

  • 3: 与家庭相关的物品

  • 4: 其他

我们将通过以下代码片段来完成这项工作:

> # recode classes and update data frame
> new.credit.purpose <- recode(credit.purpose,"0=4;1=1;2=2;3=3;
+                                              4=3;5=3;6=3;7=4;
+                                              8=4;9=4;10=4")
> credit.df$credit.purpose <- new.credit.purpose

通过以下代码片段获得变换后特征的列联表:

> # contingency table
> get.contingency.table(credit.rating, new.credit.purpose)

根据以下表格,我们看到拥有家庭相关物品或其他物品信用目的的客户在不良信用评级类别中的比例似乎最高:

分析数据集

我们接下来要分析的功能是 credit.amount,它基本上表示客户从银行请求的信用金额。这是一个数值变量,我们使用以下代码来获取摘要统计信息:

> # credit.amount analysis
> get.numeric.variable.stats(credit.amount)

分析数据集

我们可以看到一些正常统计量,例如平均信贷金额为 3270 DM,中位数约为 3270 DM。现在我们将使用直方图和密度图来可视化前面数据的分布,如下所示:

> # histogram\density plot
> visualize.distribution(credit.amount)

这将为我们提供credit.amount的直方图和密度图,您可以在以下图中看到它是一个右偏分布:

分析数据集

接下来,我们将使用以下代码片段使用箱线图来可视化数据,以查看数据分布及其与 credit.rating 的关系:

> # box plot
> visualize.boxplot(credit.amount, credit.rating)

这生成了以下箱线图,您可以在箱线图中清楚地看到分布的右偏,由箱线中的许多点表示。我们还发现了一个有趣的见解,即对于要求更高信贷金额的客户,其信贷评分的中位数是差的,这在许多客户可能未能支付所需全部还款以偿还信贷金额的情况下似乎很合理。

分析数据集

现在您已经了解了如何对分类和数值变量进行描述性分析,从现在开始,我们将不会展示每个特征的所有不同分析技术的输出。如果您有兴趣深入了解数据,请随意使用我们之前使用的函数对剩余变量进行实验,以获取汇总统计量和可视化结果!

下一个特征是储蓄,它是一个具有以下五个类别语义的分类变量:

  • 1: 没有储蓄

  • 2: < 100 DM

  • 3: 在 [100, 499] DM 之间

  • 4: 在 [500, 999] DM 之间

  • 5: >= 1000 DM

该特征表示客户拥有的平均储蓄/股票金额。我们将将其转换为以下四个类别标签:

  • 1: 没有储蓄

  • 2: < 100 DM

  • 3: 在 [100, 999] DM 之间

  • 4: >= 1000 DM

我们将使用以下代码片段:

> # feature: savings - recode classes and update data frame
> new.savings <- recode(savings,"1=1;2=2;3=3;
+                                4=3;5=4")
> credit.df$savings <- new.savings

现在我们将使用以下代码分析储蓄与 credit.rating 之间的关系,以生成列联表:

> # contingency table
> get.contingency.table(credit.rating, new.savings)

这生成了以下列联表。观察表值后,很明显,在具有不良信贷评分的客户中,没有储蓄的人的比例最高,这并不令人惊讶!这个数字对于具有良好信贷评分的客户来说也很高,因为与不良信贷评分的总记录数相比,良好信贷评分的总记录数也较高。然而,我们还可以看到,具有 > 1000 DM 和良好信贷评分的人的比例相对于在储蓄账户中同时具有不良信贷评分和 > 1000 DM 的人的比例相当高。

分析数据集

现在,我们将查看名为 employment.duration 的特征,它是一个表示客户至今为止被雇佣时间的分类变量。该特征的五个类别语义如下:

  • 1: 失业

  • 2: < 1

  • 3: 在 [1, 4] 年之间

  • 4: 在 [4, 7] 年之间

  • 5: >= 7

我们将将其转换为以下四个类别:

  • 1: 未就业或< 1

  • 2: 在 [1,4] 年之间

  • 3: 在 [4,7] 年之间

  • 4: >= 7

我们将使用以下代码:

> # feature: employment.duration - recode classes and update data frame
> new.employment.duration <- recode(employment.duration,
+                                   "1=1;2=1;3=2;4=3;5=4")
> credit.df$employment.duration <- new.employment.duration

现在我们使用列联表分析其关系,如下所示:

> # contingency table
> get.contingency.table(credit.rating, new.employment.duration)

从以下表中我们可以观察到,没有或显著低就业年数和不良信用评级的客户比例远高于具有良好信用评级的类似客户。在employment.duration特征中,值 1 表示失业或就业时间少于1年的人。在 300 人中有 93 人具有不良信用评级,这给出了 31%的比例,与具有良好信用评级的客户相同指标相比,这一比例要高得多,后者为 700 客户中的 141 人,或 20%。

分析数据集

我们现在继续分析下一个特征,名为installment.rate,这是一个具有以下语义的分类变量:

  • 1: >=35%

  • 2: 在 [25, 35]% 之间

  • 3: 在 [20, 25]% 之间

  • 4: 四个类别的< 20%

对于这个属性的原始元数据中信息不多,因此存在一些歧义,但我们假设它表示客户工资中用于支付信用贷款月度分期付款的百分比。我们在这里不会进行任何转换,所以我们将直接进入关系分析。

> # feature: installment.rate - contingency table and statistical tests
> get.contingency.table(credit.rating, installment.rate, 
+                      stat.tests=TRUE)

> 0.05, thus ruling the null hypothesis in favor of the alternative. This tells us that these two variables do not have a significant association between them and this feature might not be one to consider when we make feature sets for our predictive models. We will look at feature selection in more detail in the next chapter.

分析数据集

我们接下来要分析的是marital.status变量,它表示客户的婚姻状况,是一个分类变量。它有四个类别,具有以下语义:

  • 1: 男性离婚

  • 2: 男性单身

  • 3: 男性已婚/丧偶

  • 4: 女性

我们将根据以下语义将其转换为三个类别:

  • 1: 男性离婚/单身

  • 2: 男性已婚/丧偶

  • 3: 女性

我们将使用以下代码:

> # feature: marital.status - recode classes and update data frame
> new.marital.status <- recode(marital.status, "1=1;2=1;3=2;4=3")
> credit.df$marital.status <- new.marital.status

我们现在通过以下代码片段构建一个列联表来观察marital.statuscredit.rating之间的关系:

> # contingency table
> get.contingency.table(credit.rating, new.marital.status)

从以下表中,我们注意到,对于信用评级良好的客户,单身男性与已婚男性的比例为1:2,而对于信用评级较差的客户,这一比例接近1:1。这意味着也许已婚男性更倾向于按时偿还信用债务?这可能是一个可能性,但请记住,相关性并不总是意味着因果关系。

分析数据集

统计测试的 p 值给出了0.01的值,这表明特征之间可能存在某种关联。

下一个特征是担保人,这表示客户是否有任何其他债务人或担保人。这是一个有三个类别的分类变量,具有以下语义:

  • 1: 无

  • 2: 共同借款人

  • 3: 担保人

我们将它们转换成以下语义的两个变量:

  • 1: 否

  • 2: 是

对于转换,我们使用以下代码片段:

> # feature: guarantor - recode classes and update data frame
> new.guarantor <- recode(guarantor, "1=1;2=2;3=2")
> credit.df$guarantor <- new.guarantor

对此结果进行统计分析得到 p 值为 1,这个值远大于 0.05,因此拒绝零假设,暗示担保人与 credit.rating 之间可能没有关联。

提示

您也可以使用直接函数而不是每次都调用 get.contingency.table(…) 函数来运行统计分析。对于费舍尔精确检验,调用 fisher.test(credit.rating, guarantor),对于皮尔逊卡方检验,调用 chisq.test(credit,rating, guarantor)。您可以将担保人替换为任何其他独立变量以执行这些测试。

下一个特征是 residence.duration,表示客户在其当前地址居住的时间。

这是一个分类变量,以下是对四个类别的语义描述:

  • 1: < 1

  • 2: 在 [1,4] 年之间

  • 3: 在 [4,7] 年之间

  • 4: >= 7

我们将不对这些数据进行转换,而是直接进行统计分析,以查看该特征是否与 credit,rating 有任何关联。根据之前的提示,使用 fisher.testchisq.test 函数都给出了 p 值为 0.9,这个值显著大于 0.05,因此它们之间没有显著关系。我们将在这里展示这两个统计测试的输出,以便您了解它们所描述的内容。

> # perform statistical tests for residence.duration
> fisher.test(credit.rating, residence.duration)
> chisq.test(credit.rating, residence.duration)

您可以从以下输出中看到,我们从之前提到的两个测试中都得到了相同的 p 值:

分析数据集

我们现在将重点转向 current.assets,这是一个分类变量,以下是对四个类别的语义描述:

  • 1: 没有资产

  • 2: 汽车/其他

  • 3: 人寿保险/储蓄合同

  • 4: 房地产/土地所有权

我们将不对这些数据进行转换,而是直接运行相同的统计分析,以检查它是否与 credit.rating 有任何关联。我们得到的 p 值为 3 x 10-5,这个值显然小于 0.05,因此我们可以得出结论,备择假设成立,即变量之间存在某种关联。

我们将要分析的下一个变量是 age。这是一个数值变量,我们将如下获取其摘要统计信息:

> # age analysis
> get.numeric.variable.stats(age)

输出

分析数据集

我们可以观察到,客户平均年龄为 35.5 岁,中位数为 33 岁。为了查看特征分布,我们将使用以下代码片段使用直方图和密度图进行可视化:

> # histogram\density plot
> visualize.distribution(age)

从以下图表中我们可以观察到,分布是右偏分布,大多数客户年龄在 25 至 45 岁之间:

分析数据集

我们现在将通过箱线图可视化来观察 agecredit.rating 之间的关系,如下所示:

> # box plot
> visualize.boxplot(age, credit.rating)

从以下图表中可以看出,右偏斜在箱线图中通过我们在极端端点看到的点簇明显可辨。从右边的图表中我们可以得出的有趣观察是,信用评级差的客户的平均年龄低于信用评级好的客户。

分析数据集

这种关联的一个原因可能是,一些年轻人由于尚未稳定就业而未能偿还他们从银行借取的信用贷款。但,再次强调,这只是一个假设,除非我们查看每个客户的完整背景,否则我们无法验证。

接下来,我们将查看特征 other.credits,它具有以下三个类别的语义:

  • 1: 在其他银行

  • 2: 在商店

  • 3: 没有更多信用

这个特征表示客户是否在其他地方有任何其他待处理的信用。我们将将其转换为两个类别,具有以下语义:

  • 1: 是

  • 2: 否

我们将使用以下代码片段:

> # feature: other.credits - recode classes and update data frame
> new.other.credits <- recode(other.credits, "1=1;2=1;3=2")
> credit.df$other.credits <- new.other.credits

对新转换的特征进行统计分析,我们得到一个 p 值为 0.0005,这 < 0.05,因此支持备择假设而非零假设,表明这个特征与 credit.rating 之间存在某种关联,假设没有其他因素的影响。

下一个特征 apartment.type 是一个具有以下语义的三个类别的类别变量:

  • 1: 免费公寓

  • 2: 租赁公寓

  • 3: 拥有并居住的公寓

这个特征基本上表示客户居住的公寓类型。我们不会对这个变量进行任何转换,并将直接进行统计分析。这两个测试都给出了 p 值 < 0.05,这表明 apartment.typecredit.rating 之间存在某种关联,假设没有其他因素影响。

现在我们将查看特征 bank.credits,这是一个具有以下语义的类别变量,对于四个类别:

  • 1: 一个

  • 2: 两个/三个

  • 3: 四个/五个

  • 4: 六个或更多

这个特征表示客户从该银行(包括当前的一个)借取的信用贷款总数。我们将将其转换为一个二元特征,具有以下两个类别的语义:

  • 1: 一个

  • 2: 超过一个

我们将使用以下代码:

> # feature: bank.credits - recode classes and update data frame
> new.bank.credits <- recode(bank.credits, "1=1;2=2;3=2;4=2")
> credit.df$bank.credits <- new.bank.credits

对这个转换后的特征进行统计分析,我们得到一个 p 值为 0.2,这比 0.05 大得多,因此我们知道零假设仍然有效,即 bank.creditscredit.rating 之间没有显著的关联。有趣的是,如果你使用 bank.credits 的未转换版本进行统计分析,你会得到一个更高的 p 值为 0.4,这表明没有显著的关联。

下一个特征是occupation,显然表示客户的当前工作。这是一个分类变量,其四个类别具有以下语义:

  • 1: 失业且无永久居留权

  • 2: 无技能且拥有永久居留权

  • 3: 技能工人/次要公务员

  • 4: 执行/自雇/高级公务员

我们不会对这个特征应用任何转换,因为每个类别在特征上都有其独特的特点。因此,我们将直接进行统计分析,这两个测试得到的 p 值为0.6,这肯定大于0.05,因此零假设成立,即两个特征之间没有显著的关系。

我们现在将查看下一个特征dependents,这是一个具有以下语义的两个类别标签的分类变量:

  • 1: 零到两个

  • 2: 三或更多

这个特征表示作为客户依赖者的总人数。我们不会对这个特征应用任何转换,因为它已经是一个二进制变量。对这个特征进行统计分析得到的 p 值为1,这告诉我们这个特征与credit.rating没有显著的关系。

接下来是特征telephone,这是一个具有两个类别的二进制分类变量,以下语义表示客户是否有电话:

  • 1: 否

  • 2: 是

由于这是一个二进制变量,我们在这里不需要进行任何进一步的转换。因此,我们继续进行统计分析,得到的 p 值为0.3,这大于0.05,因此零假设被拒绝,表明电话与credit.rating之间没有显著的关联。

数据集中的最后一个特征是foreign.worker,这是一个具有两个类别的二进制分类变量,以下语义表示客户是否是外国工人:

  • 1: 是

  • 2: 否

我们没有进行任何转换,因为这个变量已经是二进制变量,具有两个不同的类别,所以我们继续进行统计分析。这两个测试都给出了小于 0.05 的 p 值,这可能表明这个变量与credit.rating有显著的关系。

通过这一点,我们完成了对数据集的数据分析阶段。

保存转换后的数据集

我们已经对多个分类变量进行了大量的特征工程,使用数据转换,由于我们将在转换后的特征集上构建预测模型,我们需要将这个数据集单独存储到磁盘上。我们使用以下代码片段来完成同样的任务:

> ## Save the transformed dataset
> write.csv(file='credit_dataset_final.csv', x = credit.df, 
+           row.names = F)

下次开始构建预测模型时,我们可以直接将上述文件加载到 R 中,我们将在下一章中介绍这一点。

下一步

我们已经分析了我们的数据集,进行了必要的特征工程和统计测试,构建了可视化,并获得了关于信用风险分析和银行在分析客户时考虑哪些特征的大量领域知识。我们详细分析数据集中每个特征的原因是为了让你了解银行在分析客户信用评级时考虑的每个特征。这是为了让你对领域知识有良好的理解,并帮助你熟悉未来对任何数据集进行探索性和描述性分析的技术。那么接下来是什么?现在真正有趣的部分来了;从这些数据中构建特征集,并将它们输入到预测模型中,以预测哪些客户可能是潜在的信用风险,哪些不是。如前所述,这有两个步骤:数据和算法。实际上,我们将更进一步,说有特征集和算法将帮助我们实现主要目标。

特征集

数据集基本上是一个包含多个观测记录的文件,其中每个元组或记录代表一组完整的观测,而列则是该观测中特定的属性或特征,它们讨论了特定的特性。在预测分析中,通常数据集中有一个属性或特征,其类别或类别需要被预测。这个变量在我们的数据集中是credit.rating,也称为因变量。所有其他依赖这些特征的变量都是自变量。这些特征的组合形成了一个特征向量,也常被称为特征集。确定我们应该考虑哪些特征集用于预测模型有多种方法,并且你将会看到,对于任何数据集,都没有一个固定的特征集。它根据特征工程、我们构建的预测模型类型以及基于统计测试的特征的重要性而不断变化。

特征集中的每个属性被称为特征或属性,在统计学中它们也被称为独立变量或解释变量。特征可以是各种类型,正如我们在数据集中所看到的。我们可以有具有多个类别的分类特征,具有两个类别的二元特征,有序特征,它们基本上是分类特征,但具有内在的某种顺序(例如,低、中、高),以及可能是整数或实数值的数值特征。特征在构建预测模型中非常重要,而且往往数据科学家会花费大量时间构建完美的特征集,以极大地提高预测模型的准确性。这就是为什么除了了解机器学习算法之外,领域知识也非常重要。

机器学习算法

一旦我们准备好了特征集,我们就可以开始使用预测模型来使用它们,并开始根据客户特征预测客户的信用评级。要记住的一个重要的事情是,这是一个迭代过程,我们必须根据从预测模型获得的输出和反馈来不断修改我们的特征集,以进一步提高它们。在本节中简要解释了与我们场景相关的几种方法,它们属于监督机器学习算法的类别。

  • 线性分类算法:这些算法通过线性函数进行分类,通过执行特征集和与之相关的某些权重的点积为每个类别分配分数。预测的类别是得分最高的类别。特征集的最优权重以各种方式确定,并且根据所选算法而有所不同。一些算法的例子包括逻辑回归、支持向量机和感知器。

  • 决策树:在这里,我们使用决策树作为预测模型,将数据点中的各种观察结果映射到我们要预测的记录的观察类别。决策树就像一个流程图结构,其中每个内部非叶节点表示对特征的检查,每个分支代表该检查的结果,每个终端叶节点包含一个我们最终预测的类别标签。

  • 集成学习方法:这包括使用多个机器学习算法来获得更好的预测模型。一个例子是随机森林分类算法,它在模型训练阶段使用决策树的集成,并在每个阶段从决策树的集成中获取多数输出决策作为其输出。这倾向于减少过度拟合,这在使用决策树时经常发生。

  • 提升算法:这也是监督学习算法家族中的一种集成学习技术。它由训练多个弱分类模型并从它们中学习的过程组成,然后再将它们添加到一个比它们更强的最终分类器中。在添加分类器时遵循一种加权的做法,这基于它们的准确性。未来的弱分类器更多地关注先前被错误分类的记录。

  • 神经网络:这些算法受到生物神经网络的启发,生物神经网络由相互连接的神经元系统组成,它们相互交换信息。在预测建模中,我们处理人工神经网络,它由相互连接的节点组组成。每个节点包含一个函数,通常是一个数学函数(即,Sigmoid 函数),并且与它相关联的权重是自适应的,根据输入到节点的数据不断变化,并在多次迭代(也称为时期)中不断检查从输出获得的错误。

在下一章构建预测模型时,我们将介绍这些算法中的几个。

摘要

恭喜你坚持看到本章的结尾!到目前为止,你已经通过本章学习到了一些重要内容。你现在对金融领域最重要的一个领域有了概念,那就是信用风险分析。除此之外,你还获得了关于银行如何分析客户以确定其信用评级以及他们考虑的哪些属性和特征的显著领域知识。对数据集的描述性和探索性分析也让你了解了当你只有一个问题要解决和一个数据集时,如何从头开始工作!你现在知道如何进行特征工程,使用ggplot2构建精美的出版物质量可视化,以及进行统计测试以检查特征关联。最后,我们通过讨论特征集来结束我们的讨论,并对几个监督机器学习算法进行了简要介绍,这些算法将帮助我们下一步预测信用风险。最有趣的部分还在后面,所以请继续关注!

第六章:信用风险检测与预测 - 预测分析

在上一章中,我们在金融领域覆盖了很多内容,我们接受了检测和预测可能成为潜在信用风险的银行客户的挑战。现在,我们对关于信用风险分析的主要目标有了很好的了解。此外,从数据集及其特征的描述性分析中获得的大量知识将对预测分析有用,正如我们之前提到的。

在本章中,我们将穿越预测分析的世界,这是机器学习和数据科学的核心。预测分析包括几个方面,如分类算法、回归算法、领域知识和业务逻辑,它们结合在一起构建预测模型并从数据中提取有用见解。我们在上一章的末尾讨论了各种机器学习算法,这些算法将适用于解决我们的目标,当我们使用给定的数据集和这些算法构建预测模型时,我们将在本章中探索其中的一些。

对预测分析的一个有趣的观点是,它为希望在未来加强其业务和利润的组织提供了很多希望。随着大数据的出现,现在大多数组织拥有的数据比他们能分析的数据还要多!虽然这是一个大挑战,但更严峻的挑战是从这些数据中选择正确的数据点并构建能够正确预测未来结果的预测模型。然而,这种方法有几个注意事项,因为每个模型基本上是基于公式、假设和概率的数学函数。此外,在现实世界中,条件和场景不断变化和演变,因此我们必须记住,今天构建的预测模型明天可能完全过时。

许多怀疑者认为,由于环境的不断变化,计算机模仿人类预测结果非常困难,甚至人类也无法预测,因此所有统计方法仅在理想假设和条件下才有价值。虽然这在某种程度上是正确的,但有了正确的数据、正确的思维方式和应用正确的算法和技术,我们可以构建稳健的预测模型,这些模型肯定可以尝试解决传统或蛮力方法无法解决的问题。

预测建模是一个困难的任务,尽管可能存在许多挑战,结果可能总是难以获得,但我们必须带着一点盐来接受这些挑战,并记住著名统计学家乔治·E·P·博克斯的名言:“本质上所有模型都是错误的,但其中一些是有用的!”这一点在我们之前讨论的内容中得到了充分的证实。始终记住,预测模型永远不会达到 100%完美,但如果它是基于正确的原则构建的,它将非常有用!

在本章中,我们将重点关注以下主题:

  • 预测分析

  • 如何预测信用风险

  • 预测建模中的重要概念

  • 获取数据

  • 数据预处理

  • 特征选择

  • 使用逻辑回归建模

  • 使用支持向量机建模

  • 使用决策树建模

  • 使用随机森林建模

  • 使用神经网络建模

  • 模型比较和选择

预测分析

在上一章中,我们已经就预测分析进行了相当多的讨论,以向您提供一个关于其含义的概述。在本节中,我们将更详细地讨论它。预测分析可以定义为机器学习领域的一个子集,它包括基于数据科学、统计学和数学公式的广泛监督学习算法,这些算法使我们能够使用这些算法和已经收集的数据来构建预测模型。这些模型使我们能够根据过去的观察预测未来可能发生的事情。结合领域知识、专业知识和商业逻辑,分析师可以使用这些预测进行数据驱动决策,这是预测分析最终的结果。

我们在这里讨论的数据是过去已经观察到的数据,并且在一段时间内为了分析而收集的数据。这些数据通常被称为历史数据或训练数据,它们被输入到模型中。然而,在预测建模方法中,我们大多数时候并不直接输入原始数据,而是使用从数据中提取的特征,这些特征经过适当的转换。数据特征与监督学习算法结合形成一个预测模型。在当前获得的这些数据可以输入到这个模型中,以预测正在观察的结果,并测试模型在各个准确度指标方面的性能。在机器学习领域,这种数据被称为测试数据。

我们在本章中执行预测分析将遵循的分析管道是一个标准流程,以下步骤简要解释:

  1. 获取数据:在这里,我们获取构建预测模型所需的数据集。我们将对数据集进行一些基本的描述性分析,这已经在上一章中介绍过。一旦我们有了数据,我们就会进入下一步。

  2. 数据预处理:在这个步骤中,我们执行数据转换,例如更改数据类型、特征缩放和归一化,如果需要,以准备数据供模型训练。通常这个步骤是在数据集准备步骤之后执行的。然而,在这种情况下,最终结果是一样的,因此我们可以按任何顺序执行这些步骤。

  3. 数据集准备:在这个步骤中,我们使用一些比例,如 70:30 或 60:40,将数据实例从数据中分离成训练集和测试集。我们通常使用训练集来训练模型,然后使用测试集检查其性能和预测能力。通常数据按 60:20:20 的比例划分,我们除了其他两个数据集外,还有一个验证集。然而,在本章中,我们只保留两个数据集。

  4. 特征选择:这个过程是迭代的,如果需要,甚至在后续阶段也会发生。在这个步骤中的主要目标是选择一组属性或特征,从训练数据集中选择,使预测模型能够给出最佳预测,最小化错误率并最大化准确性。

  5. 预测建模:这是主要步骤,我们选择最适合解决该问题的机器学习算法,并使用算法构建预测模型,通过向其提供从训练数据集中的数据中提取的特征来构建预测模型。这个阶段的输出是一个预测模型,可以用于对未来数据实例的预测。

  6. 模型评估:在这个阶段,我们使用测试数据集从预测模型中获得预测结果,并使用各种技术和指标来衡量模型的性能。

  7. 模型调优:我们微调模型的各个参数,并在必要时再次进行特征选择。然后我们重新构建模型并重新评估它,直到我们对结果满意。

  8. 模型部署:一旦预测模型给出令人满意的表现,我们就可以通过在任何应用程序中使用 Web 服务来部署这个模型,以提供实时或近实时的预测。这个步骤更多地关注围绕部署模型进行的软件和应用开发,因此我们不会涉及这个步骤,因为它超出了范围。然而,关于围绕预测模型构建 Web 服务以实现“预测即服务”的教程有很多。

最后三个步骤是迭代的,如果需要,可以执行多次

尽管乍一看这个过程可能看起来相当复杂,但实际上它是一个非常简单且直接的过程,一旦理解,就可以用于构建任何类型的预测模型。需要记住的一个重要事情是,预测建模是一个迭代的过程,我们可能需要通过从模型预测中获得反馈并评估它们来多次分析数据和构建模型。因此,即使你的模型在第一次尝试时表现不佳,你也绝不能气馁,因为正如我们之前提到的,模型永远不可能完美,构建一个好的预测模型既是艺术也是科学!

在下一节中,我们将关注如何应用预测分析来解决我们的预测问题,以及在本章中我们将探索的机器学习算法类型。

如何预测信用风险

如果你还记得上一章的主要目标,我们当时处理的是来自德国银行的客户数据。我们将快速回顾我们的主要问题场景以刷新你的记忆。这些银行客户是潜在的候选人,他们向银行申请信用贷款,条件是他们必须每月支付一定的利息来偿还贷款金额。在理想的世界里,信用贷款会被自由发放,人们会毫无问题地偿还它们。不幸的是,我们并不生活在一个乌托邦的世界,因此将会有一些客户会违约,无法偿还贷款金额,这会给银行造成巨大的损失。因此,信用风险分析是银行关注的几个关键领域之一,他们分析与客户及其信用历史相关的详细信息。

现在回到主要问题,对于预测信用风险,我们需要分析客户相关的数据集,使用机器学习算法围绕它构建预测模型,并预测客户是否可能拖欠信用贷款,并可能被标记为潜在的信用风险。我们将遵循的过程是我们在上一节中讨论的。你已经从上一章中了解了数据及其相关的特征。我们将探索几个预测模型,了解模型工作背后的概念,然后构建这些模型以预测信用风险。一旦我们开始预测结果,我们将比较这些不同模型的性能,然后讨论业务影响以及如何从模型预测结果中得出见解。请注意,预测不是预测分析生命周期中的输出,我们从这些预测中得出的宝贵见解才是最终目标。像金融机构这样的企业只能从使用领域知识将预测结果和机器学习算法的原始数字转换为数据驱动决策中获得价值,这些决策在正确的时间执行时有助于业务增长。

对于这个场景,如果你对数据集记得很清楚,特征credit.rating是响应或类别变量,它表示客户的信用评级。我们将根据其他特征(独立变量)预测这个值,以预测其他客户的信用评级。在建模时,我们将使用属于监督学习算法家族的机器学习算法。这些算法用于预测,可以分为两大类:分类和回归。然而,它们有一些区别,我们现在将讨论。在回归的情况下,要预测的变量的值是连续值,例如根据不同的特征(如房间数量、房屋面积等)预测房屋价格。回归主要处理基于输入特征估计和预测响应值。在分类的情况下,要预测的变量的值具有离散且独特的标签,例如预测我们银行的客户信用评级,其中信用评级可以是好的,用1表示,或者坏,用0表示。分类主要处理对数据集中的每个数据元进行分类和识别组成员资格。逻辑回归等算法是回归模型的特殊情况,用于分类,其中算法将变量属于某个类别标签的概率估计为其他特征的函数。在本章中,我们将使用以下机器学习算法构建预测模型:

  • 逻辑回归

  • 支持向量机

  • 决策树

  • 随机森林

  • 神经网络

我们选择这些算法是为了展示现有的各种监督机器学习算法的多样性,这样你不仅可以了解这些模型背后的概念,还可以学习如何使用它们构建模型,并使用各种技术比较模型性能。在我们开始分析之前,我们将简要回顾本书中提到的预测建模的一些基本概念,并详细讨论其中的一些,以便你对幕后发生的事情有一个良好的了解。

预测建模中的重要概念

当我们讨论机器学习流程时,已经探讨了几个概念。在本节中,我们将探讨预测建模中常用的典型术语,并详细讨论模型构建和评估概念。

准备数据

数据准备步骤,如前所述,涉及准备用于特征选择和构建预测模型所需的数据集。在这个背景下,我们经常使用以下术语:

  • 数据集:它们通常是数据点的集合或观察结果。大多数数据集通常对应于某种形式的结构化数据,涉及二维数据结构,如数据矩阵或数据表(在 R 中通常使用数据框表示)包含各种值。例如,我们来自第五章的german_credit_dataset.csv文件,信用风险检测与预测 – 描述性分析

  • 数据观察:它们是数据集中的行,其中每行包含一组观察结果与一组属性。这些行也常被称为元组。对于我们数据集,包含有关客户信息的每行都是一个很好的例子。

  • 数据特征:它们是数据集中的列,描述数据集中的每一行。这些特征通常被称为属性或变量。例如credit.ratingaccount.balance等特征构成了我们的信用风险数据集的特征。

  • 数据转换:它指的是根据描述性分析中的观察结果,根据需要转换各种数据特征。数据类型转换、缺失值插补、缩放和归一化是最常用的技术。此外,对于分类变量,如果你的算法无法检测变量的不同级别,你需要将其转换为几个虚拟变量;这个过程被称为独热编码。

  • 训练数据:它指的是仅用于训练预测模型的数据。机器学习算法从这个数据集中提取元组,并试图从各种观察实例中找出模式和进行学习。

  • 测试数据:它指的是输入到预测模型中以获取预测结果的数据,然后我们使用该数据集中已经存在的类别标签来检查模型的准确性。我们从不使用测试数据来训练模型,因为这会偏置模型并给出不正确的评估。

构建预测模型

我们使用机器学习算法和数据特征来构建实际的预测模型,并最终在输入新的数据元组时开始给出预测。与构建预测模型相关的概念如下:

  • 模型训练:它与构建预测模型类似,其中我们使用监督机器学习算法,并将训练数据特征输入其中来构建预测模型。

  • 预测模型:它基于某种机器学习算法,本质上是一个数学模型,具有一些假设、公式和参数值。

  • 模型选择:这是一个过程,其主要目标是从多个预测模型的迭代中选择一个预测模型。选择最佳模型的标准可能因我们想要选择的指标而异,例如最大化准确性、最小化错误率或获得最大的 AUC(我们将在后面讨论)。交叉验证是运行此迭代过程的好方法。

  • 超参数优化:这基本上是尝试选择算法在模型中使用的一组超参数,使得模型的预测准确性最优。这通常通过网格搜索算法来完成。

  • 交叉验证:这是一种模型验证技术,用于估计模型以通用方式的表现。它主要用于迭代过程,其中最终目标是优化模型并确保模型不会过度拟合数据,以便模型能够很好地泛化新数据并做出良好的预测。通常,会进行多轮交叉验证。每一轮交叉验证都涉及将数据分为训练集和测试集;使用训练数据来训练模型,然后使用测试集评估其性能。最终,我们得到一个模型,它是所有模型中最好的。

评估预测模型

预测建模中最重要的部分是测试创建的模型是否真正有用。这是通过在测试数据上评估模型并使用各种指标来衡量模型性能来完成的。我们将在下面讨论一些流行的模型评估技术。为了清楚地解释这些概念,我们将考虑一个与我们的数据相关的例子。让我们假设我们有 100 名客户,其中 40 名客户的信用评级不良,类别标签为 0,剩余的 60 名客户的信用评级良好,类别标签为 1。现在假设我们的模型将 40 个不良实例中的 22 个预测为不良,其余的 18 个预测为良好。模型还将 60 名良好客户中的 40 个预测为良好,其余的 20 个预测为不良。现在我们将看到我们将如何使用不同的技术来评估模型性能:

  • 预测值:它们通常是离散值,属于特定的类别或类别,通常被称为类别标签。在我们的案例中,这是一个二元分类问题,我们处理两个类别,其中标签 1 表示信用评级良好的客户,0 表示信用评级不良。

  • 混淆矩阵:这是一种很好的方式来查看模型是如何预测不同类别的。它是一个通常有两行两列的列联表,用于我们这样的二元分类问题。它报告了每个类别中预测实例的数量与实际类别值。对于我们的先前列举的例子,混淆矩阵将是一个 2x2 的矩阵,其中两行将表示预测的类别标签,两列将表示实际的类别标签。总共有坏(0)类别标签的预测实例数,实际上具有坏标签,被称为真负TN),而剩余的错误预测为好的坏实例被称为假正FP)。相应地,总共有好(1)类别标签的预测实例数,实际上被标记为好的,被称为真正TP),而剩余的错误预测为坏的好的实例被称为假负FN)。

    我们将在以下图中展示这一点,并讨论从混淆矩阵中导出的一些重要指标,这些指标也将在同一图中展示:

    评估预测模型

在先前的图中,2x2 矩阵中突出显示的值是我们模型正确预测的值。白色中的值是模型错误预测的。因此,我们可以很容易地推断以下指标:TN 是 22,FP18TP40FN20。总N40,总P60,在我们的示例数据集中总和为 100 名客户。

特异性也称为真阴性率,可以用公式评估预测模型表示,它给出了总真阴性数被正确预测的比例,这些真阴性数是所有实际为负的实例总数。在我们的案例中,特异性为55%

灵敏度,也称为真阳性率召回率,其公式为评估预测模型,表示在所有实际为正的实例中,正确预测的总真阳性数所占的比例。我们的例子中灵敏度为67%

精确率,也称为阳性预测值,其公式为评估预测模型,表示在所有正预测中实际正实例的数量。我们的例子中精确率为69%

负预测值的公式为评估预测模型,表示在所有负预测中实际负实例的数量。我们的例子中 NPV 为52%

假阳性率,也称为误报率,基本上是特异性的倒数;其公式为评估预测模型,表示在所有负例中错误正预测的数量。我们的例子中 FPR 为45%

假阴性率,也称为漏报率,基本上是敏感度的倒数;其公式为评估预测模型,表示在所有正例中错误负预测的数量。我们的例子中 FNR 为33%

准确率基本上是衡量模型在做出预测时准确性的指标,其公式为评估预测模型。我们的预测准确率为62%

F1分数是衡量模型准确性的另一个指标。它通过计算值的调和平均值来考虑精确率和召回率,公式表示为评估预测模型。我们的模型 f1 分数为68%

接收者操作特征ROC)曲线基本上是一个图表,用于可视化模型性能,当我们改变其阈值时。ROC 图由 FPR 和 TPR 作为x轴和y轴分别定义,每个预测样本都可以拟合为 ROC 空间中的一个点。完美的图表将涉及所有数据点的 TPR 为 1 和 FPR 为 0。一个平均模型或基线模型将是一条从*(0, 0)(1, 1)*的对角线,表示两个值都是0.5。如果我们的模型 ROC 曲线在基线对角线之上,则表明其性能优于基线。以下图解展示了典型的 ROC 曲线在一般情况下的样子:

评估预测模型

曲线下面积AUC)基本上是从模型评估中获得的 ROC 曲线下的面积。AUC 是一个值,表示模型将随机选择的正实例排名高于随机选择的负实例的概率。因此,AUC 越高,越好。请查看文件performance_plot_utils.R(与章节代码包共享),其中包含一些实用函数,用于绘制和描述我们稍后评估模型时将使用的这些值。

这应该为你提供了关于预测建模中重要术语和概念足够的背景知识,现在我们将开始对数据进行预测分析!

获取数据

在第五章中,信用风险检测与预测 – 描述性分析,我们已经分析了德国银行的信用数据集并进行了几个转换。在本章中,我们将处理这个转换后的数据集。我们已经保存了转换后的数据集,你可以通过打开credit_dataset_final.csv文件来查看。我们将像往常一样在 R 中进行所有分析。要加载数据到内存中,请运行以下代码片段:

> # load the dataset into data frame
> credit.df <- read.csv("credit_dataset_final.csv", header = TRUE, sep = ",")

这将数据集加载到一个数据框中,现在可以通过credit.df变量轻松访问。接下来,我们将关注数据转换和归一化。

数据预处理

在数据预处理步骤中,我们将主要关注两个方面:数据类型转换和数据归一化。最后,我们将数据分割成训练集和测试集以进行预测建模。你可以通过打开data_preparation.R文件来访问这一部分的代码。我们将使用一些实用函数,这些函数在下面的代码片段中提到。请记住,通过在 R 控制台中运行它们来将它们加载到内存中:

## data type transformations - factoring
to.factors <- function(df, variables){
 for (variable in variables){
 df[[variable]] <- as.factor(df[[variable]])
 }
 return(df)
}

## normalizing - scaling
scale.features <- function(df, variables){
 for (variable in variables){
 df[[variable]] <- scale(df[[variable]], center=T, scale=T)
 }
 return(df)
}

前面的函数在数据框上操作以转换数据。对于数据类型转换,我们主要对分类变量进行分解,即将分类特征的类型从数值转换为因子。有几个数值变量,包括credit.amountagecredit.duration.months,它们都有各种值。如果你还记得上一章中的分布,它们都是偏斜分布。这有多重不利影响,如引起共线性、梯度受到影响以及模型收敛时间更长。因此,我们将使用 z 分数标准化,其中,例如,一个名为 E 的特征的值,可以用以下公式计算:数据预处理,其中数据预处理代表特征 E 的整体均值,数据预处理代表特征 E 的标准差。我们使用以下代码片段来对我们的数据进行这些转换:

> # normalize variables
> numeric.vars <- c("credit.duration.months", "age", 
 "credit.amount")
> credit.df <- scale.features(credit.df, numeric.vars)
> # factor variables
> categorical.vars <- c('credit.rating', 'account.balance', 
+                       'previous.credit.payment.status',
+                       'credit.purpose', 'savings', 
+                       'employment.duration', 'installment.rate',
+                       'marital.status', 'guarantor', 
+                       'residence.duration', 'current.assets',
+                       'other.credits', 'apartment.type', 
+                       'bank.credits', 'occupation', 
+                       'dependents', 'telephone', 
+                       'foreign.worker')
> credit.df <- to.factors(df=credit.df, 
 variables=categorical.vars)

预处理完成后,我们将数据集分成训练集和测试集,比例为 60:40,其中训练集将包含 600 个元组,测试集将包含 400 个元组。它们将以以下随机方式选择:

> # split data into training and test datasets in 60:40 ratio
> indexes <- sample(1:nrow(credit.df), size=0.6*nrow(credit.df))
> train.data <- credit.df[indexes,]
> test.data <- credit.df[-indexes,]

现在我们已经准备好了数据集,我们将在下一节中探讨特征的重要性和选择。

特征选择

特征选择的过程涉及通过使用它们训练预测模型并根据其重要性对变量或特征进行排名,然后试图找出哪些变量是该模型中最相关的特征。虽然每个模型通常都有自己的重要特征集,但在分类中,我们将在这里使用随机森林模型来尝试确定哪些变量可能在基于分类的预测中普遍重要。

我们进行特征选择有几个原因,包括:

  • 在不损失太多信息的情况下移除冗余或不相关的特征

  • 通过使用过多的特征防止模型过拟合

  • 减少由过多特征引起的模型方差

  • 减少模型的训练时间和收敛时间

  • 构建简单且易于理解的模型

我们将使用递归特征消除算法进行特征选择,并使用预测模型进行评估算法。在这个过程中,我们将反复构建几个具有不同特征的机器学习模型。在每次迭代中,我们都会消除无关或冗余的特征,并检查获得最大准确率和最小误差的特征子集。由于这是一个迭代过程,遵循流行的贪婪爬山算法的原则,通常不可能进行穷举搜索并得到全局最优解,而且根据起始点不同,我们可能会陷入局部最优解,得到的特征子集可能与不同运行中获得的特征子集不同。然而,如果我们多次使用交叉验证运行它,通常大多数特征在获得的子集中将是恒定的。我们将使用随机森林算法,我们将在稍后详细解释。现在,只需记住它是一个集成学习算法,在其训练过程的每个阶段都使用多个决策树。这倾向于减少方差和过拟合,同时由于我们在算法的每个阶段引入了一些随机性,模型偏差略有增加。

本节代码位于feature_selection.R文件中。我们首先加载必要的库。如果你还没有安装它们,按照我们在前几章的做法进行安装:

> library(caret)  # feature selection algorithm
> library(randomForest) # random forest algorithm

the R console to load into memory for using it later:
run.feature.selection <- function(num.iters=20, feature.vars, class.var){
 set.seed(10)
 variable.sizes <- 1:10
 control <- rfeControl(functions = rfFuncs, method = "cv", 
 verbose = FALSE, returnResamp = "all", 
 number = num.iters)
 results.rfe <- rfe(x = feature.vars, y = class.var, 
 sizes = variable.sizes, 
 rfeControl = control)
 return(results.rfe)
}

默认情况下,前面的代码使用交叉验证,将数据分为训练集和测试集。对于每次迭代,都会进行递归特征消除,并在测试集上对模型进行训练和测试,以检查准确率和误差。数据分区在每次迭代中都会随机变化,以防止模型过拟合,并最终给出一个通用的估计,即模型在一般情况下的表现。如果你观察,我们的函数默认运行 20 次迭代。记住,在我们的情况下,我们总是在训练数据上训练,该数据由函数内部分区进行交叉验证。变量feature.vars表示在训练数据集中可以使用train.data[,-1]子集访问的所有独立特征变量,要访问表示要预测的类变量的class.var,我们使用train.data[,1]进行子集操作。

注意

我们完全不接触测试数据,因为我们只打算用它来进行预测和模型评估。因此,我们不想通过使用这些数据来影响模型,因为这会导致评估结果不准确。

我们现在使用定义好的函数在训练数据上运行算法,以下代码展示了这一过程。运行可能需要一些时间,所以如果你看到 R 在返回结果时花费了一些时间,请耐心等待:

rfe.results <- run.feature.selection(feature.vars=train.data[,-1], 
 class.var=train.data[,1])
# view results
rfe.results

查看结果后,我们得到以下输出:

特征选择

从输出中,你可以看到它从 20 个特征中找到了最重要的 10 个特征,并且默认返回了前五个特征。你可以进一步玩转这个结果变量,并使用 R 控制台中的varImp(rfe.results)命令查看所有变量的重要性。由于训练和测试数据分区是随机进行的,如果你记得,所以如果你看到与截图不同的值,请不要慌张。然而,根据我们的观察,前五个特征通常会保持一致。现在,我们将开始构建预测模型,使用不同的机器学习算法进行我们分析管道的下一阶段。然而,请记住,由于训练和测试集是随机选择的,你的集合可能给出的结果与我们在这里进行实验时描述的不同。

使用逻辑回归建模

逻辑回归是一种回归模型,其中因变量或类别变量不是连续的,而是分类的,就像在我们的案例中,信用评级是具有两个类别的因变量。原则上,逻辑回归通常被视为广义线性模型家族的一个特例。该模型通过估计概率来尝试找出类别变量与其他独立特征变量之间的关系。它使用逻辑或 Sigmoid 函数来估计这些概率。逻辑回归不直接预测类别,而是预测结果的概率。对于我们的模型,由于我们处理的是一个二元分类问题,我们将处理二项逻辑回归。

首先,我们将如下加载库依赖项,并分别分离测试特征和类别变量:

library(caret) # model training and evaluation
library(ROCR) # model evaluation
source("performance_plot_utils.R") # plotting metric results
## separate feature and class variables
test.feature.vars <- test.data[,-1]
test.class.var <- test.data[,1]

现在我们将使用所有独立变量训练初始模型如下:

> formula.init <- "credit.rating ~ ."
> formula.init <- as.formula(formula.init)
> lr.model <- glm(formula=formula.init, data=train.data, family="binomial")

我们可以使用summary(lr.model)命令查看模型细节,该命令显示基于其显著性值的各个变量及其重要性。我们在以下快照中展示了这些细节的一部分:

使用逻辑回归建模

你可以看到模型自动对分类变量执行了一元编码,这基本上是在该变量中为每个类别都有一个变量。旁边带有星号的变量具有 p 值< 0.05(我们在上一章中讨论过),因此是显著的。

接下来,我们在测试数据上执行预测,并如下评估结果:

> lr.predictions <- predict(lr.model, test.data, type="response")
> lr.predictions <- round(lr.predictions)
> confusionMatrix(data=lr.predictions, reference=test.class.var, positive='1')

运行此代码后,我们得到一个混淆矩阵以及相关的指标,我们之前已经讨论过,如下所示。看到我们实现了71.75%的整体准确率,这相当不错,考虑到这个数据集大多数客户都有良好的信用评级。它对不良信用评级的预测相当准确,这从48%特异性中可以看出。灵敏度83%,相当不错,NPV58%PPV76%

使用逻辑回归建模

现在,我们将尝试使用一些选定的特征构建另一个模型,并看看它的表现如何。如果你还记得,我们在特征选择部分获得了一些对分类很重要的通用特征。我们仍然会为逻辑回归运行特征选择,以使用以下代码片段查看特征的重要性:

formula <- "credit.rating ~ ."
formula <- as.formula(formula)
control <- trainControl(method="repeatedcv", number=10, repeats=2)
model <- train(formula, data=train.data, method="glm", 
 trControl=control)
importance <- varImp(model, scale=FALSE)
plot(importance)

我们从以下图中选择了前五个变量来构建下一个模型。正如你所见,阅读这个图相当简单。重要性越大,变量就越重要。你可以自由地添加更多变量,并使用它们构建不同的模型!

使用逻辑回归建模

接下来,我们使用与之前类似的方法构建模型,并使用以下代码片段在测试数据上测试模型性能:

> formula.new <- "credit.rating ~ account.balance + credit.purpose 
 + previous.credit.payment.status + savings 
 + credit.duration.months"
> formula.new <- as.formula(formula.new)
> lr.model.new <- glm(formula=formula.new, data=train.data, family="binomial")
> lr.predictions.new <- predict(lr.model.new, test.data, type="response") 
> lr.predictions.new <- round(lr.predictions.new)
> confusionMatrix(data=lr.predictions.new, reference=test.class.var, positive='1')

我们得到了以下混淆矩阵。然而,如果你查看模型评估结果,如以下输出所示,你会发现现在准确率略有提高,达到了72.25%灵敏度大幅上升到94%,这是非常好的,但遗憾的是,这是以特异性下降为代价的,特异性下降到27%,你可以清楚地看到,更多的不良信用评级被预测为好,这在测试数据中的 130 个不良信用评级客户中有 95 个!NPV上升到69%,因为由于灵敏度更高,较少的正信用评级被错误地分类为假阴性。

使用逻辑回归建模

现在的问题是我们要选择哪个模型进行预测。这不仅仅取决于准确率,还取决于问题的领域和业务需求。如果我们预测一个客户的不良信用评级(0)为1),这意味着我们将批准该客户的信用贷款,而该客户最终可能不会偿还,这将给银行造成损失。然而,如果我们预测一个客户的良好信用评级(1)为0),这意味着我们将拒绝他的贷款,在这种情况下,银行既不会盈利也不会遭受任何损失。这比错误地将不良信用评级预测为好要安全得多。

因此,我们选择我们的第一个模型作为最佳模型,现在我们将使用以下代码片段查看一些指标评估图:

> lr.model.best <- lr.model
> lr.prediction.values <- predict(lr.model.best, test.feature.vars, type="response")
> predictions <- prediction(lr.prediction.values, test.class.var)
> par(mfrow=c(1,2))
> plot.roc.curve(predictions, title.text="LR ROC Curve")
> plot.pr.curve(predictions, title.text="LR Precision/Recall Curve")

我们从前面的代码中得到了以下图:

使用逻辑回归建模

您可以从前面的图中看到,AUC0.74,这对于一个开始来说相当不错。我们现在将使用类似的过程构建下一个预测模型,使用支持向量机,并看看它的表现如何。

使用支持向量机建模

支持向量机属于用于分类和回归的监督机器学习算法家族。考虑到我们的二分类问题,与逻辑回归不同,支持向量机算法将围绕训练数据构建模型,使得属于不同类别的训练数据点通过一个清晰的间隙分开,这个间隙被优化到最大分离距离。位于边缘的样本通常被称为支持向量。分隔两个类别的边缘中间部分称为最优分离超平面。

位于错误边缘的数据点被降低权重以减少其影响,这被称为软边缘,与我们之前讨论的硬边缘分离相比。支持向量机分类器可以是简单的线性分类器,其中数据点可以线性分离。然而,如果我们处理的是由多个特征组成的数据,而这些特征无法直接进行线性分离,那么我们就使用多个核来实现这一点,这些核形成了非线性支持向量机分类器。您将能够通过以下来自 R 中svm库官方文档的图来可视化支持向量机分类器实际的样子:

使用支持向量机建模

图片来源:cran.r-project.org/web/packages/e1071/vignettes/svmdoc.pdf

从图中,你可以清楚地看到我们可以放置多个超平面来分隔数据点。然而,选择分隔超平面的标准是两个类别的分隔距离最大,支持向量是两个类别的代表性样本,如图中边缘所示。回顾非线性分类器的问题,SVM 除了用于线性分类的常规线性核之外,还有几个核可以用来实现这一点。这些包括多项式、径向基 函数RBF)以及几个其他核。这些非线性核函数背后的主要原理是,即使在线性特征空间中无法进行线性分隔,它们也能使分隔在更高维度的变换特征空间中发生,在那里我们可以使用超平面来分隔类别。需要记住的一个重要事情是维度的诅咒;由于我们可能最终要处理更高维度的特征空间,模型的泛化误差增加,模型的预测能力降低。如果我们有足够的数据,它仍然表现合理。在我们的模型中,我们将使用 RBF 核,也称为径向基函数,为此有两个重要的参数是成本和 gamma。

我们将首先加载必要的依赖项并准备测试数据特征:

library(e1071) # svm model
library(caret) # model training\optimizations
library(kernlab) # svm model for hyperparameters
library(ROCR) # model evaluation
source("performance_plot_utils.R") # plot model metrics
## separate feature and class variables
test.feature.vars <- test.data[,-1]
test.class.var <- test.data[,1]

一旦完成这些,我们将使用训练数据和 RBF 核在所有训练集特征上构建 SVM 模型:

> formula.init <- "credit.rating ~ ."
> formula.init <- as.formula(formula.init)
> svm.model <- svm(formula=formula.init, data=train.data, 
+                  kernel="radial", cost=100, gamma=1)
> summary(svm.model)

模型的属性如下所示,来自summary函数:

使用支持向量机建模

现在我们使用测试数据对这个模型进行预测并评估结果如下:

> svm.predictions <- predict(svm.model, test.feature.vars)
> confusionMatrix(data=svm.predictions, reference=test.class.var, positive="1")

这给我们带来了如下混淆矩阵,就像我们在逻辑回归中看到的那样,模型性能的细节如下。我们观察到准确率67.5%,灵敏度100%,特异性0%,这意味着这是一个非常激进的模型,它只是预测每个客户评价为好。这个模型显然存在主要类别分类问题,我们需要改进这一点。

使用支持向量机建模

为了构建更好的模型,我们需要进行一些特征选择。我们已经在特征选择部分获得了前五个最佳特征。尽管如此,我们仍然会运行一个专门为 SVM 设计的特征选择算法来查看特征重要性,如下所示:

> formula.init <- "credit.rating ~ ."
> formula.init <- as.formula(formula.init)
> control <- trainControl(method="repeatedcv", number=10, repeats=2)
> model <- train(formula.init, data=train.data, method="svmRadial", 
+                trControl=control)
> importance <- varImp(model, scale=FALSE)
> plot(importance, cex.lab=0.5)

这给我们一个图,我们看到前五个重要变量与我们的前五个最佳特征相似,但这个算法将年龄的重要性排名高于credit.amount,所以你可以通过构建具有不同特征的几个模型来测试这一点,看看哪一个给出最好的结果。对我们来说,从随机森林中选择的特征给出了更好的结果。变量重要性图如下所示:

使用支持向量机建模

现在我们基于给我们最佳结果的五个特征构建一个新的 SVM 模型,并使用以下代码片段在测试数据上评估其性能:

> formula.new <- "credit.rating ~ account.balance + 
 credit.duration.months + savings + 
 previous.credit.payment.status + credit.amount"
> formula.new <- as.formula(formula.new)
> svm.model.new <- svm(formula=formula.new, data=train.data, 
+                  kernel="radial", cost=100, gamma=1)
> svm.predictions.new <- predict(svm.model.new, test.feature.vars)
> confusionMatrix(data=svm.predictions.new, 
 reference=test.class.var, positive="1")

1% to 66.5%. However, the most interesting part is that now our model is able to predict more bad ratings from bad, which can be seen from the confusion matrix. The specificity is now 38% compared to 0% earlier and, correspondingly, the sensitivity has gone down to 80% from 100%, which is still good because now this model is actually useful and profitable! You can see from this that feature selection can indeed be extremely powerful. The confusion matrix for the preceding observations is depicted in the following snapshot:

使用支持向量机建模

我们肯定会选择这个模型,并继续使用网格搜索算法进行模型优化,如下所示,以优化成本和 gamma 参数:

cost.weights <- c(0.1, 10, 100)
gamma.weights <- c(0.01, 0.25, 0.5, 1)
tuning.results <- tune(svm, formula.new,
 data = train.data, kernel="Radial", 
 ranges=list(cost=cost.weights, gamma=gamma.weights))
print(tuning.results)

输出结果:

使用支持向量机建模

网格搜索图可以如下查看:

> plot(tuning.results, cex.main=0.6, cex.lab=0.8,xaxs="i", yaxs="i")

输出结果:

使用支持向量机建模

最暗的区域显示了给出最佳性能的参数值。我们现在选择最佳模型并再次评估,如下所示:

> svm.model.best = tuning.results$best.model
> svm.predictions.best <- predict(svm.model.best,
 test.feature.vars)
> confusionMatrix(data=svm.predictions.best, 
 reference=test.class.var, positive="1")

观察从以下输出中获得的混淆矩阵结果(我们此后只描述我们跟踪的指标),我们看到整体准确率增加到71%,灵敏度86%,特异性41%,与之前的模型结果相比,这是非常好的:

使用支持向量机建模

你可以看到超参数优化在预测建模中的强大作用!我们还会绘制一些评估曲线,如下所示:

> svm.predictions.best <- predict(svm.model.best, test.feature.vars, decision.values = T)
> svm.prediction.values <- attributes(svm.predictions.best)$decision.values
> predictions <- prediction(svm.prediction.values, test.class.var)
> par(mfrow=c(1,2))
> plot.roc.curve(predictions, title.text="SVM ROC Curve")
> plot.pr.curve(predictions, title.text="SVM Precision/Recall Curve")

我们可以看到预测是如何在评估空间中绘制的,并且我们看到从以下 ROC 图中,AUC 在这种情况下为 0.69:

使用支持向量机建模

现在,假设我们想要根据这个 ROC 图优化模型,目标是最大化 AUC。我们现在将尝试这样做,但首先我们需要将分类变量的值编码为一些字母,因为 R 在表示只有数字的因子变量的列名时会引起一些问题。所以基本上,如果credit.rating的值为01,则它被转换为X0X1;最终我们的类别仍然是不同的,没有任何变化。我们首先使用以下代码片段转换我们的数据:

> transformed.train <- train.data
> transformed.test <- test.data
> for (variable in categorical.vars){
+   new.train.var <- make.names(train.data[[variable]])
+   transformed.train[[variable]] <- new.train.var
+   new.test.var <- make.names(test.data[[variable]])
+   transformed.test[[variable]] <- new.test.var
+ }
> transformed.train <- to.factors(df=transformed.train, variables=categorical.vars)
> transformed.test <- to.factors(df=transformed.test, variables=categorical.vars)
> transformed.test.feature.vars <- transformed.test[,-1]
> transformed.test.class.var <- transformed.test[,1]

现在我们再次使用网格搜索构建一个 AUC 优化的模型,如下所示:

> grid <- expand.grid(C=c(1,10,100), sigma=c(0.01, 0.05, 0.1, 0.5, 
 1))
> ctr <- trainControl(method='cv', number=10, classProbs=TRUE,
 summaryFunction=twoClassSummary)
> svm.roc.model <- train(formula.init, transformed.train,
+                        method='svmRadial', trControl=ctr, 
+                        tuneGrid=grid, metric="ROC")

我们下一步是对测试数据进行预测并评估混淆矩阵:

> predictions <- predict(svm.roc.model, 
 transformed.test.feature.vars)
> confusionMatrix(predictions, transformed.test.class.var, 
 positive = "X1")

这给我们以下结果:

使用支持向量机建模

我们现在看到准确率进一步提高到72%,而特异性略有下降到40%,但灵敏度增加到87%,这是好的。我们再次绘制曲线,如下所示:

> svm.predictions <- predict(svm.roc.model, transformed.test.feature.vars, type="prob")
> svm.prediction.values <- svm.predictions[,2]
> predictions <- prediction(svm.prediction.values, test.class.var)
> par(mfrow=c(1,2))
> plot.roc.curve(predictions, title.text="SVM ROC Curve")
> plot.pr.curve(predictions, title.text="SVM Precision/Recall Curve")

这给我们以下图表,与我们的早期迭代中做的一样:

使用支持向量机建模

看到 AUC 确实从之前的 0.69 增加到现在的 0.74,这非常令人满意,这意味着基于 AUC 的优化算法确实有效,因为它在所有我们跟踪的方面都给出了比之前模型更好的性能。接下来,我们将探讨如何使用决策树构建预测模型。

使用决策树进行建模

决策树是再次属于监督机器学习算法家族的算法。它们也用于分类和回归,通常称为CART,代表分类和回归树。这些在决策支持系统、商业智能和运筹学中应用广泛。

决策树主要用于做出最有用的决策,以实现某些目标并基于这些决策设计策略。在核心上,决策树只是一个包含几个节点和条件边的流程图。每个非叶节点代表对某个特征的测试条件,每条边代表测试的结果。每个叶节点代表一个类标签,其中对最终结果进行预测。从根节点到所有叶节点的路径给出了所有的分类规则。决策树易于表示、构建和理解。然而,缺点是它们非常容易过拟合,并且这些模型通常泛化能力不佳。我们将遵循之前类似的分析流程,基于决策树构建一些模型。

我们首先加载必要的依赖项和测试数据特征:

> library(rpart)# tree models 
> library(caret) # feature selection
> library(rpart.plot) # plot dtree
> library(ROCR) # model evaluation
> library(e1071) # tuning model
> source("performance_plot_utils.R") # plotting curves
> ## separate feature and class variables
> test.feature.vars <- test.data[,-1]
> test.class.var <- test.data[,1]

现在我们将使用以下所有特征构建一个初始模型:

> formula.init <- "credit.rating ~ ."
> formula.init <- as.formula(formula.init)
> dt.model <- rpart(formula=formula.init, 
 method="class",data=train.data,control = 
 rpart.control(minsplit=20, cp=0.05))

我们使用以下代码在测试数据上预测和评估模型:

> dt.predictions <- predict(dt.model, test.feature.vars, 
 type="class")
> confusionMatrix(data=dt.predictions, reference=test.class.var, 
 positive="1")

从以下输出中,我们看到模型的准确率大约为68%,灵敏度92%,这是非常好的,但特异性仅为18%,这是我们应努力改进的:

使用决策树进行建模

我们现在将尝试特征选择来改进模型。我们使用以下代码来训练模型并按其重要性对特征进行排序:

> formula.init <- "credit.rating ~ ."
> formula.init <- as.formula(formula.init)
> control <- trainControl(method="repeatedcv", number=10, repeats=2)
> model <- train(formula.init, data=train.data, method="rpart", 
+                trControl=control)
> importance <- varImp(model, scale=FALSE)
> plot(importance)

这给我们以下图表,显示了不同特征的重要性:

使用决策树进行建模

如果你仔细观察,决策树在模型构建中并没有使用所有特征,并且前五个特征与我们之前在讨论特征选择时获得的是相同的。我们现在将使用以下特征构建一个模型:

> formula.new <- "credit.rating ~ account.balance + savings +
 credit.amount + 
 credit.duration.months + 
 previous.credit.payment.status"
> formula.new <- as.formula(formula.new)
> dt.model.new <- rpart(formula=formula.new, method="class",data=train.data, 
+                   control = rpart.control(minsplit=20, cp=0.05),
+                   parms = list(prior = c(0.7, 0.3)))

我们现在对测试数据进行预测并评估,如下所示:

> dt.predictions.new <- predict(dt.model.new, test.feature.vars, 
 type="class")
> confusionMatrix(data=dt.predictions.new, 
 reference=test.class.var, positive="1")

这给我们以下混淆矩阵和其他指标:

使用决策树进行建模

你现在可以看到,整体模型的准确率略有下降,达到了62%。然而,我们在不良信用评分预测方面取得了进步,我们预测在 130 个客户中有 100 个不良信用评分客户,这非常出色!因此,特异性上升到77%,而灵敏度下降到55%,但我们仍然将大量良好信用评分的客户分类为良好。尽管这个模型有些激进,但这是一个合理的模型,因为我们虽然拒绝了更多可能违约的客户的信用贷款,但我们同时也确保了合理数量的良好客户能够获得他们的信用贷款。

我们获得这些结果的原因是因为我们使用了一个名为先验的参数来构建模型,如果你检查前面的建模部分。这个先验参数基本上使我们能够对类别变量中的不同类别应用权重。如果你记得,在我们的数据集中有700个信用评分良好的人和300个信用评分不良的人,这是一个高度倾斜的数据集,所以在训练模型时,我们可以使用先验来指定这个变量中每个类别的相对重要性,从而调整每个类别的误分类的重要性。在我们的模型中,我们给予不良信用评分客户更多的重视。

你可以通过使用参数prior = c(0.7, 0.3)来反转先验概率,并给予良好信用评分客户更多的重视,这将给出以下混淆矩阵:

使用决策树建模

你现在可以清楚地看到,由于我们给予了良好信用评分更多的重视,灵敏度上升到92%,而特异性下降到18%。你可以看到,这为你提供了很大的灵活性,取决于你想要实现的目标。

要查看模型,我们可以使用以下代码片段:

> dt.model.best <- dt.model.new
> print(dt.model.best)

输出

使用决策树建模

为了可视化前面的树,你可以使用以下代码:

> par(mfrow=c(1,1))
> prp(dt.model.best, type=1, extra=3, varlen=0, faclen=0)

这给我们以下树形图,我们可以看到,使用先验概率,现在在五个特征中只使用了account.balance这个特征,并且忽略了其他所有特征。你可以尝试通过使用e1071包中的tune.rpart函数进行超参数调整来进一步优化模型:

使用决策树建模

我们通过绘制以下指标评估曲线来完成我们的分析:

> dt.predictions.best <- predict(dt.model.best, test.feature.vars, 
 type="prob")
> dt.prediction.values <- dt.predictions.best[,2]
> predictions <- prediction(dt.prediction.values, test.class.var)
> par(mfrow=c(1,2))
> plot.roc.curve(predictions, title.text="DT ROC Curve")
> plot.pr.curve(predictions, title.text="DT Precision/Recall 
 Curve")

AUC大约为0.66,这并不是最好的,但肯定比以下图中用红线表示的基线要好:

使用决策树建模

根据我们的业务需求,这个模型相当公平。我们将在本章后面讨论模型比较。现在我们将使用随机森林来构建我们的下一组预测模型。

使用随机森林建模

随机森林,也称为随机决策森林,是一种来自集成学习算法家族的机器学习算法。它用于回归和分类任务。随机森林实际上就是决策树集合或集成,因此得名。

算法的工作原理可以简要描述如下。在任何时刻,决策树集成中的每一棵树都是从一个自助样本中构建的,这基本上是带有替换的采样。这种采样是在训练数据集上进行的。在构建决策树的过程中,之前在所有特征中选择为最佳分割的分割不再进行。现在,每次分割时总是从特征的一个随机子集中选择最佳分割。这种随机性引入到模型中略微增加了模型的偏差,但大大减少了模型的方差,这防止了模型的过拟合,这在决策树的情况下是一个严重的问题。总的来说,这产生了性能更好的泛化模型。现在我们将开始我们的分析流程,通过加载必要的依赖项:

> library(randomForest) #rf model 
> library(caret) # feature selection
> library(e1071) # model tuning
> library(ROCR) # model evaluation
> source("performance_plot_utils.R") # plot curves
> ## separate feature and class variables
> test.feature.vars <- test.data[,-1]
> test.class.var <- test.data[,1]

接下来,我们将使用所有特征构建初始训练模型,如下所示:

> formula.init <- "credit.rating ~ ."
> formula.init <- as.formula(formula.init)
> rf.model <- randomForest(formula.init, data = train.data, 
 importance=T, proximity=T)

您可以使用以下代码查看模型详情:

> print(rf.model)

输出

使用随机森林建模

这给我们提供了关于袋外误差OOBE)的信息,大约为23%,以及基于训练数据的混淆矩阵,以及它在每个分割中使用的变量数量。

接下来,我们将使用此模型在测试数据上进行预测,并评估它们:

> rf.predictions <- predict(rf.model, test.feature.vars, 
 type="class")
> confusionMatrix(data=rf.predictions, reference=test.class.var, 
 positive="1")

new model:
formula.new <- "credit.rating ~ account.balance + savings +
 credit.amount + 
 credit.duration.months + 
 previous.credit.payment.status"
formula.new <- as.formula(formula.new)
rf.model.new <- randomForest(formula.new, data = train.data, 
 importance=T, proximity=T)

我们现在使用此模型在测试数据上进行预测,并如下评估其性能:

> rf.predictions.new <- predict(rf.model.new, test.feature.vars, 
 type="class")
> confusionMatrix(data=rf.predictions.new,   reference=test.class.var, positive="1")

这给我们以下混淆矩阵作为输出,以及其他关键性能指标:

使用随机森林建模

我们得到了略微降低的准确率71%,这是显而易见的,因为我们已经消除了许多特征,但现在特异性已增加到42%,这表明它能够更准确地分类更多的坏实例。灵敏度略有下降至84%。现在我们将使用网格搜索来对此模型进行超参数调整,如下所示,以查看我们是否可以进一步提高性能。这里感兴趣的参数包括ntree,表示树的数量,nodesize,表示终端节点的最小大小,以及mtry,表示每次分割时随机采样的变量数量。

nodesize.vals <- c(2, 3, 4, 5)
ntree.vals <- c(200, 500, 1000, 2000)
tuning.results <- tune.randomForest(formula.new, 
 data = train.data,
 mtry=3, 
 nodesize=nodesize.vals,
 ntree=ntree.vals)
print(tuning.results)

输出

使用随机森林建模

我们现在从先前的网格搜索中得到最佳模型,对测试数据进行预测,并使用以下代码片段评估其性能:

> rf.model.best <- tuning.results$best.model
> rf.predictions.best <- predict(rf.model.best, test.feature.vars, 
 type="class")
> confusionMatrix(data=rf.predictions.best,
 reference=test.class.var, positive="1")

从以下输出中,我们可以得出几个观察结果。性能提高非常微不足道,因为整体准确率保持在71%特异性保持在42%灵敏度略有提高,从84%增加到85%

使用随机森林建模

我们现在为这个模型绘制一些性能曲线,如下所示:

> rf.predictions.best <- predict(rf.model.best, test.feature.vars, type="prob")
> rf.prediction.values <- rf.predictions.best[,2]
> predictions <- prediction(rf.prediction.values, test.class.var)
> par(mfrow=c(1,2))
> plot.roc.curve(predictions, title.text="RF ROC Curve")
> plot.pr.curve(predictions, title.text="RF Precision/Recall Curve")

我们观察到总AUC约为0.7,在以下图中比红色基线AUC0.5要好得多:

使用随机森林建模

我们将要探索的最后一种算法是神经网络,我们将在下一节中使用它们来构建模型。

使用神经网络建模

神经网络,或者更具体地说,在这种情况下,人工神经网络,是一系列基于生物神经网络工作原理的机器学习模型,就像我们的神经系统一样。神经网络已经存在很长时间了,但最近,人们对于使用深度学习和人工智能构建高度智能系统的兴趣激增。深度学习利用深度神经网络,这些网络在输入层和输出层之间有大量的隐藏层。一个典型的神经网络可以用以下图示来表示:

使用神经网络建模

从图中,你可以推断出这个神经网络是一个由各种节点相互连接的网络,也称为神经元。每个节点代表一个神经元,它实际上就是一个数学函数。我们不可能详细说明如何从数学上表示一个节点,但在这里我们会给出要点。这些数学函数接收一个或多个带有权重的输入,这些输入在先前的图中表示为边,然后对这些输入进行一些计算以给出输出。在这些节点中使用的各种流行函数包括阶跃函数和 sigmoid 函数,这些函数你已经在逻辑回归算法中看到过使用。一旦输入被函数加权并转换,这些函数的激活就会发送到后续的节点,直到达到输出层。节点集合形成一层,就像在先前的图中,我们有三个层。

因此,神经网络依赖于多个神经元或节点以及它们之间的互连模式,学习过程用于在每次迭代(通常称为一个 epoch)中更新连接的权重,以及节点的激活函数,这些激活函数将带有权重的节点输入转换为输出激活,该激活通过层传递,直到我们得到输出预测。我们将从以下方式开始加载必要的依赖项:

> library(caret) # nn models
> library(ROCR) # evaluate models
> source("performance_plot_utils.R") # plot curves
> # data transformation
> test.feature.vars <- test.data[,-1]
> test.class.var <- test.data[,1]

现在,我们不得不进行一些特征值编码,类似于我们在为 SVM 进行 AUC 优化时所做的那样。为了刷新你的记忆,你可以运行以下代码片段:

> transformed.train <- train.data
> transformed.test <- test.data
> for (variable in categorical.vars){
+   new.train.var <- make.names(train.data[[variable]])
+   transformed.train[[variable]] <- new.train.var
+   new.test.var <- make.names(test.data[[variable]])
+   transformed.test[[variable]] <- new.test.var
+ }
> transformed.train <- to.factors(df=transformed.train, variables=categorical.vars)
> transformed.test <- to.factors(df=transformed.test, variables=categorical.vars)
> transformed.test.feature.vars <- transformed.test[,-1]
> transformed.test.class.var <- transformed.test[,1]

一旦我们准备好数据,我们将使用所有特征构建我们的初始神经网络模型,如下所示:

> formula.init <- "credit.rating ~ ."
> formula.init <- as.formula(formula.init)
> nn.model <- train(formula.init, data = transformed.train, method="nnet")

nnet package if you do not have it installed, so just select the option when it asks you and it will install it automatically and build the model. If it fails, you can install it separately and run the code again. Remember, it is an iterative process so the model building might take some time. Once the model converges, you can view the model details using the print(nn.model) command which will show several iteration results with different size and decay options, and you will see that it does hyperparameter tuning internally itself to try and get the best model!

我们现在在测试数据上执行预测并评估模型性能如下:

> nn.predictions <- predict(nn.model, 
 transformed.test.feature.vars, type="raw")
> confusionMatrix(data=nn.predictions, 
 reference=transformed.test.class.var, 
 positive="X1")

您可以从以下输出中观察到,我们的模型具有72%准确率,这相当不错。它很好地预测了负面评价为负面,这从特异性48%)中可以看出,而且通常灵敏度在**84%**时表现良好。

使用神经网络建模

我们现在将使用以下代码片段来绘制基于神经网络的模型的重要性特征:

> formula.init <- "credit.rating ~ ."
> formula.init <- as.formula(formula.init)
> control <- trainControl(method="repeatedcv", number=10, repeats=2)
> model <- train(formula.init, data=transformed.train, method="nnet", 
 trControl=control)
> importance <- varImp(model, scale=FALSE)
> plot(importance)

这为我们提供了以下根据重要性排序的绘图排名变量:

使用神经网络建模

我们从先前的图中选择了最重要的特征,并按以下方式构建我们的下一个模型:

> formula.new <- "credit.rating ~ account.balance + credit.purpose + savings + current.assets +
foreign.worker + previous.credit.payment.status"
> formula.new <- as.formula(formula.new)
> nn.model.new <- train(formula.new, data=transformed.train, method="nnet")

我们现在在测试数据上执行预测并评估模型性能:

> nn.predictions.new <- predict(nn.model.new, 
 transformed.test.feature.vars, 
 type="raw")
> confusionMatrix(data=nn.predictions.new, 
 reference=transformed.test.class.var, 
 positive="X1")

这为我们提供了以下混淆矩阵,其中包含我们感兴趣的各个指标。从以下输出中我们可以观察到,准确率略有提高至73%,而灵敏度现在提高至87%,但以特异性为代价,它已降至43%

使用神经网络建模

您可以检查它内部进行的超参数调整,如下所示:

> plot(nn.model.new, cex.lab=0.5)

以下图显示了具有不同隐藏层节点数和权重衰减的各种模型的准确率:

使用神经网络建模

根据银行要求最小化损失的要求,我们选择最佳模型作为最初构建的初始神经网络模型,因为它具有与新模型相似的准确率,并且其特异性要高得多,这非常重要。我们现在绘制最佳模型的一些性能曲线如下:

> nn.model.best <- nn.model
> nn.predictions.best <- predict(nn.model.best, transformed.test.feature.vars, type="prob")
> nn.prediction.values <- nn.predictions.best[,2]
> predictions <- prediction(nn.prediction.values, test.class.var)
> par(mfrow=c(1,2))
> plot.roc.curve(predictions, title.text="NN ROC Curve")
> plot.pr.curve(predictions, title.text="NN Precision/Recall Curve")

从以下图中我们可以观察到,AUC0.74,这相当不错,并且比用红色表示的基线表现要好得多:

使用神经网络建模

这标志着我们的预测建模会话的结束,我们将通过模型选择和比较来总结。

模型比较和选择

我们已经探索了各种机器学习技术,并构建了几个模型来预测客户的信用评级,因此现在的问题是我们应该选择哪个模型以及模型之间是如何相互比较的。我们的测试数据有 130 个客户的不良信用评级0)和 270 个客户的良好信用评级1)。

如果您还记得,我们之前讨论过在建模后使用领域知识和业务需求来解释结果并做出决策。目前,我们的决定是选择最佳模型以最大化德国银行的利润并最小化损失。让我们考虑以下条件:

  • 如果我们错误地将信用评级差的客户预测为好,银行将损失其贷出的全部信用金额,因为他将违约支付,因此损失为 100%,可以用-1 表示,以方便计算。

  • 如果我们正确地将信用评级差的客户预测为坏,我们就正确地拒绝了他信用贷款,因此既没有损失也没有利润。

  • 如果我们正确地将信用评级好的客户预测为好,我们就正确地给他提供了信用贷款。假设银行对贷出的金额有利息,让我们假设利润是客户每月支付的利息的 30%。因此,利润表示为 30%或+0.3,以方便计算。

  • 如果我们错误地将信用评级好的客户预测为坏,我们就错误地拒绝了他信用贷款,但在此情况下既没有利润也没有损失。

考虑到这些条件,我们将为各种模型制作一个比较表,包括我们之前为每个机器学习算法的最佳模型计算的一些指标。记住,考虑到所有模型性能指标和业务需求,没有一种模型在所有模型中都是最佳的。每个模型都有其自身的良好性能点,这在以下分析中是显而易见的:

模型比较和选择

前表中突出显示的单元格显示了该特定指标的最佳性能。正如我们之前提到的,没有最佳模型,我们已经列出了针对每个指标表现最好的模型。考虑到总整体收益,决策树似乎是最优模型。然而,这是假设每个客户请求的信用贷款金额是恒定的。记住,如果每个客户请求的贷款金额不同,那么这种总收益的概念就无法比较,因为在这种情况下,一笔贷款的利润可能不同于另一笔,而损失也可能在不同贷款中不同。这种分析有些复杂,超出了本章的范围,但我们将简要说明如何计算。如果你还记得,有一个credit.amount特征,它指定了客户请求的信用金额。由于我们已经在训练数据中有了客户编号,我们可以将评级客户及其请求的金额进行汇总,并对那些产生损失和利润的客户进行汇总,然后我们将得到每种方法的银行总收益!

摘要

在本章中,我们探讨了监督学习领域中的几个重要方面。如果你从我们旅程的开始就跟随了这一章,并且勇敢地走到了最后,给自己鼓掌吧!你现在已经知道了构成预测分析的内容以及与之相关的一些重要概念。此外,我们还看到了预测模型是如何在实际中工作的,以及完整的预测分析流程。这将使你能够在未来构建自己的预测模型,并从模型预测中开始获得有价值的见解。我们还看到了如何实际使用模型进行预测,以及如何评估这些预测以测试模型性能,以便我们可以进一步优化模型,并根据指标和业务需求选择最佳模型。在我们得出结论并开始你自己的预测分析之旅之前,我想提到的是,你应该始终记住奥卡姆剃刀原理,它指出在相互竞争的假设中,应该选择假设最少的那一个,这也可以被解释为有时,最简单的解决方案是最好的一个。不要盲目地使用最新的包和技术来构建预测模型,因为首先你需要理解你正在解决的问题,然后从最简单的实现开始,这通常会带来比大多数复杂解决方案更好的结果。