Julia 数据科学(三)
原文:
annas-archive.org/md5/c00c9228e4b434716ed57438765bbb96译者:飞龙
第九章 时间序列
展示和执行决策建模与分析的能力,是一些现实应用的关键组成部分,这些应用涵盖了从重症监护病房的紧急医疗治疗到军事指挥控制系统。现有的推理方法和技术在需要权衡决策质量与计算可行性之间取得平衡的应用中并不总是有效。对于时间关键性元素的决策建模,一种成功的方法应该明确支持瞬时过程的演示,并能够应对时间敏感的情况。
在本章中,我们将涵盖:
-
什么是预测?
-
决策过程
-
什么是时间序列?
-
模型的类型
-
趋势分析
-
季节性分析
-
ARIMA
-
平滑
什么是预测?
让我们以一个公司为例,假设它需要找出未来一段时间内的库存需求,以最大化投资回报。
例如,许多库存系统适用于不确定需求。这些系统中的库存参数需要评估需求和预测误差的分布。
这些系统的两个阶段——预测和库存控制——通常是独立分析的。理解需求预测和库存控制之间的相互作用至关重要,因为这直接影响库存系统的执行效果。
预测需求包括:
-
每个决策最终都会变得具有操作性,因此应该基于对未来情况的预测来制定。
-
各种数字在整个组织中都需要,绝对不应该由一个孤立的预测小组来生成。
-
预测从不“完成”。预测是持续需要的,随着时间的推移,预测对实际执行的影响被衡量,原始预测会被更新,决策会被调整,这一过程持续循环。
决策者利用预测模型来做出决策。它们通常用于模拟过程,反思不同策略的影响。
将决策过程的组成部分分成三组是很有帮助的:
-
不可控
-
可控
-
资源(定义问题情境)
决策过程
什么是系统?框架是由各个部分以特定方式组合而成的,目的是为了实现特定的目标。各个部分之间的关系决定了系统的功能及其整体作用。因此,系统中的连接往往比单独的部分更加重要。最终,作为其他系统构建模块的系统被称为子系统。
系统的动态
不变化的系统是静态系统。许多商业系统是快速变化的系统,这意味着它们的状态会随时间变化。我们将系统随时间变化的方式称为系统的行为。而当系统的发展呈现出典型模式时,我们说系统具有行为模式。一个系统是静态还是动态,取决于它如何随时间变化。
决策过程包括以下几个部分:
-
绩效衡量(或指标):在每个组织中,制定有效的衡量标准被视为重要。绩效衡量标准提供了期望的结果水平,即决策的目标。目标在识别预测行动中至关重要:
-
战略:投资回报率、增长和创新
-
战术:成本、数量和客户满意度
-
操作性:目标设定与标准符合性
-
-
资源:资源是预测期间保持不变的恒定因素。资源是定义决策问题的变量。战略决策通常具有比战术和操作性决策更长的时间跨度。
-
预测:预测信息来自决策者的环境。必须确定或预测不可控的输入。
-
决策:决策输入是所有可能可行方法的集合。
-
互动:前述决策部分之间的关联是表示输入、资源、预测和结果之间情况和结果关系的逻辑、科学函数。当决策的结果依赖于策略时,我们会改变一个或多个风险情境的部分,目的是在其中的其他部分实现有利变化。如果我们了解问题各部分之间的联系,我们就能取得成功。
-
行动:决策包括由决策者选择的策略。我们的策略如何影响选择结果,取决于预测和不同输入之间的相互关系,以及它们如何与结果相关联。
什么是时间序列?
时间序列是一组统计数据,通常按固定时间间隔收集。时间序列数据通常在许多应用中出现:
-
经济学:例如,失业率、住院人数等的月度数据
-
财务:例如,日常汇率、股价等
-
环境:例如,日降水量、空气质量读数等
-
医学:例如,每 2 到 8 秒的心电图脑电活动
时间序列分析技术早于一般的随机过程和马尔可夫链。时间序列分析的目标是描绘和概述时间序列数据,拟合低维度模型,并做出可取的预测。
趋势、季节性、周期和残差
描述序列的一个直接策略是经典分解法。其思想是将序列分解为四个组成部分:
-
趋势(Tt):均值的长期变化
-
季节效应(It):与日历相关的周期性波动
-
周期(Ct):其他周期性波动(如商业周期)
-
残差(Et):其他随机或系统性波动
该思想是为这四个元素创建独立的模型,然后将它们合并:
-
加法形式:Xt = Tt + It + Ct + Et
-
乘法形式:Xt = Tt It Ct Et
与标准线性回归的区别
信息并非绝对独立,也不是天生地无差异地分布。时间序列的一个特点是它是一个观测的列表,其中排序非常重要。序列至关重要,因为存在依赖关系,改变顺序可能会改变数据的意义。
分析的基本目标
基本目标通常是确定一个描述时间序列模式的模型。此类模型的用途包括:
-
描述时间序列模式的重要特征
-
解释过去如何影响未来,或者两个时间序列如何“相互作用”
-
预测系列的未来值
-
作为衡量某些制造环境中产品质量的控制标准
模型类型
有两种基本类型的“时间域”模型:
-
使用时间指数作为自变量的普通回归模型:
- 有助于数据的初步描述,并为几种简单的预测方法奠定基础
-
ARIMA 模型(自回归积分滑动平均模型):
- 将序列当前值与过去的值和过去的预测误差相关联的模型
首先要考虑的重要特征
在观察时间序列时需要首先考虑的一些重要问题是:
-
是否存在趋势?
- 测量值随着时间的推移趋向增加或减少的模式。
-
季节性效应的影响?
- 是否存在与日历时间相关的高低重复模式,如季节、季度、月份、星期几等?
-
是否存在异常值?
- 在回归中,异常值距离趋势线较远。而在时间序列数据中,异常值距离其他数据较远。
-
是否有与季节性因素无关的周期?
-
是否在特定时间段内具有恒定的方差?
-
是否有任何突变发生在任一侧?
下图是时间序列中随机数的示例。时间序列图表是指将变量与时间进行绘图。类似的图表可以用于心跳、市场波动、地震图等。
图表的一些特征包括:
-
整个时间跨度内没有持续的趋势(上升或下降)。序列似乎在缓慢地上下波动。
-
存在一些明显的异常值。
-
很难判断方差是否是恒定的。
系统性模式和随机噪声
与大多数不同的分析一样,在时间序列分析中,通常认为信息由系统性模式(作为一组可识别的片段的排列)和随机噪声(误差)组成,这通常使得模式难以识别。大多数时间,序列分析系统会通过某种类型的噪声滤波,目的是使模式更加可识别。
时间序列模式的两个主要方面
大多数时候,序列模式可以通过两个基本类别的部分来描述:
-
趋势
-
规律性
趋势是一个大体上是直线的或(通常)非线性的部分,随时间推移而变化,并且在我们数据捕获的时间范围内不会重复(可能)。
规律性可能具有形式上类似的特征;然而,它会在系统性的间隔内重复。时间序列的这两类主要元素可能在实际的现实数据中共存。
例如,一个公司的销售额可能会在几年内迅速增长,但它们仍然遵循可预测的季节性模式(例如,每年 10 月的销售额占全年销售额的 30%,而 3 月仅占 10%)。
趋势分析
没有已知的“自动”方法可以识别时间序列数据中的趋势部分。然而,只要趋势的持续时间是重复的(以一致的方式增加或减少),并且部分数据分析通常并不特别困难。如果时间序列数据包含显著的误差,那么识别趋势的第一步通常是平滑。
平滑
平滑通常涉及某种形式的局部平均数据,使得个别观测值的非系统性部分相互抵消。最常见的方法是移动平均平滑。这将序列中的每个元素替换为 n 个相邻元素的简单或加权平均值,其中 n 是平滑“窗口”的宽度。
中位数可以替代均值。中位数有一些优点:
-
在平滑窗口内,结果受异常值的影响较小。
-
如果数据中存在异常值,中位数平滑通常会比移动平均更能产生平滑或更“可靠”的曲线,即使窗口宽度相同。
中值平滑的主要弱点是,在没有明显的异常值时,它可能会生成比移动平均更为曲折的曲线,并且没有考虑加权。
拟合函数
许多单调的时间序列数据可以通过线性函数来很好地近似。如果存在合理的单调非线性部分,数据应首先进行转换以消除非线性。通常可以使用对数、指数或(较少使用的)多项式函数。
季节性分析
季节性依赖性(季节性)是时间序列模型的另一个常见组成部分。例如,如果我们看到购买趋势的时间序列图,我们会发现每年十月底和十二月都会出现一个巨大的峰值。这个模式每年都会重复。
自相关图
时间序列的季节性模式可以通过自相关图进行分析。自相关图通过图形和数字方式展示自相关函数(ACF),即在预定滞后范围内,序列滞后的关系系数(及其标准误差)。
在自相关图中,通常会为每个滞后设置两个标准误差的范围,但通常来说,自相关的大小比它的可靠性更为重要。
以下是 mtcars 数据集的自相关图:
检查自相关图
在检查自相关图时,我们必须记住,连续滞后的自相关是正式相关的。例如,如果第一个成分与第二个成分紧密相关,第二个成分与第三个成分相关,那么第一个成分与第三个成分也应该有某种程度的相关性,依此类推。
偏自相关
另一种有助于检查序列依赖性的方法是观察偏自相关函数(PACF),它是自相关的扩展,消除了对中间成分(即滞后内的成分)的依赖。
去除序列依赖性
对于特定滞后 k 的序列依赖性,可以通过差分来消除,即将序列的每个第 i 个成分转化为它与 (i-k) 个成分的差异。
这种变化背后有两种解释:
-
序列中可能识别出隐藏的季节性依赖性
-
ARIMA 和其他过程要求我们使序列平稳,而这本身需要去除季节性依赖性。
ARIMA
我们已经讨论了时间序列分析过程的数值建模。在现实生活中,模式并不那么清晰,观察数据通常会有相当多的误差。
要求如下:
-
寻找隐藏模式
-
生成预测
现在让我们理解 ARIMA 模型以及它如何帮助我们获取这些信息。
常见过程
-
自回归过程:
-
大多数时间序列由彼此相关的组成部分构成,你可以评估一个系数或一组系数,描述系列中紧密相连的组成部分,这些组成部分来自特定的时间滞后(过去)部分。
-
平稳性要求。自回归过程仅在特定参数范围内稳定,这些参数会影响后续点,并且序列可能不再是平稳的。
-
-
移动平均过程。与自回归过程独立,序列中的每个组成部分也可能受到过去误差(或随机冲击)的影响,而这些误差无法通过自回归组件表示。
-
可逆性要求。移动平均过程与自回归过程之间存在“对偶”关系:
- 移动平均方程可以转换为自回归结构。无论如何,与上面描述的平稳条件相同,只有当移动平均参数满足特定条件时才能进行转换,也就是说,如果该模型是可逆的。另外,系列本身可能不会是平稳的。
ARIMA 方法
自回归移动平均模型。
Box 和 Jenkins(1976)提出的通用模型包括自回归和移动平均参数,并且在模型表述中明确包括差分操作。
具体来说,模型中的三种参数是:
-
自回归参数(p)
-
差分次数(d)
-
移动平均参数(q)
在 Box 和 Jenkins 的文档中,模型通常缩写为 ARIMA(p,d,q)。
辨识
ARIMA 的输入序列应该是平稳的。它必须在时间上具有稳定的均值、方差和自相关性。因此,通常情况下,序列需要首先进行差分,直到变为平稳(这通常也需要通过对数据进行对数变换来稳定方差)。
为了达到平稳性,系列需要进行多少次差分,体现在“d”参数中。为了确定基本的差分阶数,我们需要观察数据的图形和自相关图。
显著的水平变化(明显的上升或下降变化)通常需要一阶非季节性(滞后=1)差分:
- 显著的斜率变化通常需要二阶非季节性差分。
估计与预测
下一步是估计。在此阶段,通过使用函数最小化方法来评估参数,以最小化残差平方和。参数的评估将在最后阶段(预测)中用于计算系列的新估计值(包括输入数据集之外的部分)及其预测值的置信区间。
估计过程在变化(差分)后的数据上进行,然后生成预测数据。需要保证序列是平稳的,以便预测值与输入数据兼容。
ARIMA 模型中的常数
在标准的自回归和移动平均参数下,ARIMA 模型也可能包含常数。该常数的表示形式取决于所拟合的模型:
-
如果模型中没有自回归参数,则序列的均值就是常数的期望值
-
如果模型中有自回归参数,则截距由常数表示
识别阶段
在估计开始之前,我们必须确定(区分)要评估的特定 ARIMA 参数的数量和类型。识别阶段使用的重要工具包括:
-
排列的图示
-
自相关(ACF)的相关图
-
部分自相关(PACF)
选择并非简单,在一些非典型的情况下,需要经验以及大量的实验性时间序列模式试验(以及 ARIMA 的技术参数)。
然而,许多实验性的时间序列模式可以使用五种基本模型之一进行充分近似。这些模型基于自相关图(ACF)和部分自相关图(PACF)的形状:
-
一个自回归参数(p):
-
ACF:指数衰减
-
PACF:滞后 1 处出现尖峰
-
其他滞后无相关性
-
-
两个自回归参数(p):
-
ACF:正弦波形状模式或一组指数衰减
-
PACF:滞后 1 和 2 处出现尖峰
-
其他滞后无相关性
-
-
一个移动平均参数(q):
-
ACF:滞后 1 处出现尖峰
-
其他滞后无相关性
-
PACF:指数衰减
-
-
两个移动平均参数(q):
-
ACF:滞后 1 和 2 处出现尖峰
-
其他滞后无相关性
-
PACF:正弦波形状模式或一组指数衰减
-
-
一个自回归(p)和一个移动平均(q)参数:
-
ACF:从滞后 1 开始的指数衰减
-
PACF:从滞后 1 开始的指数衰减
-
季节性模型
一个模式在时间上按季节性重复的序列需要特殊的模型。
这与季节性模型中的简单 ARIMA 参数类似:
-
季节性自回归(ps)
-
季节性差分(ds)
-
季节性移动平均参数(qs)
例如,假设模型为(0,1,2)(0,1,1)。
这描述了一个包含以下内容的模型:
-
无自回归参数
-
两个一般的移动平均参数
-
一个常规的移动平均参数
用于季节性参数的季节性滞后通常在识别阶段确定,并应明确指出。
关于选择需要评估的参数(考虑 ACF 和 PACF)的普遍建议同样适用于季节性模型。主要区别在于,在季节性序列中,ACF 和 PACF 会在季节性滞后的倍数处显示出显著的系数。
参数估计
有几种不同的评估参数的方法。它们应该基本上会产生相同的估计值,但对于任何给定的模型来说,可能在效率上有所不同。通常,在参数估计阶段,使用一个函数最小化算法来最大化在给定参数值下观察到的序列的概率(似然)。
这需要计算残差的(条件)平方和(SS),给定单独的参数。
在实际应用中,这要求计算残差的(条件)平方和(SS),给定相应的参数。
已提出不同的方法来计算残差的 SS:
-
根据 McLeod 和 Sales(1983)的近似最大似然法
-
使用回溯法的近似最大似然法
-
根据 Melard(1984)的精确最大似然法
模型评估
-
参数估计:
-
报告估算的 t 值,基于参数的标准误差计算得出
-
如果没有显著性,则通常可以从模型中去除单独的参数,而不会显著影响模型的整体拟合度。
-
-
其他质量标准:另一个明显且常见的模型质量衡量标准是根据部分数据生成的预测的准确性,以便将预测值与已知(原始)观测值进行比较。
中断时间序列 ARIMA
我们可能希望评估一个或多个离散事件对时间序列中变量的影响。这类时间序列分析中的干扰已在 McDowall、McCleary、Meidinger 和 Hay(1980)的细节中有所描述。McDowall 等人识别出三种可能的影响类型:
-
永久性突发干扰
-
永久性渐变干扰
-
突发性短暂干扰
指数平滑
指数平滑已被证明是各种时间序列数据预测策略中极为流行的一种方法。该策略由布朗和霍尔特独立开发。布朗在二战期间为美国海军工作,他的任务是设计一个追踪系统,用于计算潜艇的位置并控制火力信息。后来,他将这一策略应用于预测备件需求(一个库存控制问题)。
简单指数平滑
对时间序列 t 的简单模型是将每个观测值视为由一个常数(b)和一个误差项(epsilon)组成,即:Xt = b + t。
常数 b 在序列的每个片段中通常保持不变;但它可能随着时间的推移而逐渐变化。如果拟合成功,那么隔离 b 的真实估值,从而隔离出序列的系统性或可预测部分的方法之一是计算某种类型的移动平均,其中当前和最近的观察值比较老的观察值被赋予更大的权重。
指数平滑正好满足这样的加权,即给较旧的观察值分配越来越小的指数权重。简单指数平滑的具体公式如下:
St = aXt + (1-a)St-1
当递归地连接到序列中的每个连续观察值时,每个新的平滑值(预测值)是当前观察值和过去平滑观察值的加权平均值。
过去的平滑观察值是通过计算过去的观察值以及之前的平滑值得出的,依此类推。因此,每个平滑值是过去观察值的加权平均值,其中权重会根据参数(alpha)的值呈指数递减。
如果它等于 1(一个),则完全忽略过去的观察值;如果它等于 0(零),则完全忽略当前观察值,平滑值完全由过去的平滑值组成(该平滑值是通过计算之前的平滑观察值得出的,依此类推;因此,所有的平滑值将等于初始平滑值 S0)。中间值将产生过渡结果:
-
如果它等于 1,则完全忽略过去的观察值。
-
如果它等于 0,则完全忽略当前观察值:
- 平滑值完全由过去的平滑值组成(该平滑值又是通过计算之前的平滑观察值得出的,依此类推;因此,所有的平滑值将等于初始平滑值 S0)。中间值将产生过渡结果。
隐含在观察到的时间序列中的过程的假设模型,简单指数平滑,通常能产生准确的预测。
拟合缺失指标(误差)
评估基于特定值的预测准确性最直接的方法是仅绘制观察值和前一步预测值的图表。此图表还可以包括残差(与右侧的 Y 轴比例),这样就能轻松识别出拟合较好或较差的区域。
这种对预测准确性的视觉检查通常是判断当前指数平滑模型是否适合数据的最有效方法:
-
平均误差:平均误差(ME)是通过计算观察值与前一步预测值之差的平均值来得出的:
- 显然,这一衡量标准的缺点是正负误差值可能会相互抵消,因此它不是一个良好的总体拟合指标。
-
平均绝对误差(MAE):平均绝对误差(MAE)值是通过计算平均绝对误差值得到的:
-
如果值为 0(零),则拟合(预测)被认为是完美的。
-
与均方误差值相比,这一拟合衡量标准会忽略异常值,因此,独特或异常的大误差值将比均方误差对 MAE 的影响更小。
-
-
平方误差和均方误差(SSE):这些值是通过计算平方误差值的总和(或均值)得到的。它是统计拟合方法中最常用的拟合缺失指标之一。
-
百分比误差(PE):所有前述的衡量标准都依赖于实际误差值。可能看起来更合理的是,使用相对于观察值的幅度,表示未来一步预测与观察值的相对偏差,而不是直接表达拟合的缺失。
- 例如,在试图预测每月可能大幅波动的销售额时,如果我们的预测"接近目标",偏差约为±10%,我们可能会感到满意。换句话说,绝对误差可能不那么重要,而更关心的是相对误差:
PEt = 100(Xt - Ft )/Xt*
这里,Xt是时刻t的观察值,Ft是预测值(平滑值)。
-
平均百分比误差(MPE):该值通过计算 PE 值的平均值得到。
-
平均绝对百分比误差(MAPE):与均值误差值的情况类似,接近 0(零)的均值百分比误差可能是由相互抵消的显著正负误差百分比产生的。因此,相对拟合优度的更优衡量标准是平均绝对百分比误差。此外,该衡量标准通常比均方误差更为重要:
- 例如,意识到正常预测偏差为±5%本身就是一个有用的结果,尽管 30.8 的均方误差并不容易直观理解。
-
自动搜索最佳参数:利用类牛顿法的函数最小化过程(与 ARIMA 中的方法相同)来最小化均方误差、平均绝对误差或平均总率误差。
-
初始平滑值 S0:我们需要一个 S0 值,以便计算时间序列中第一个观察值的平滑预测值。根据参数选择(即当它接近零时),平滑过程的初始值可能会影响某些观察值的预测质量。
在 Julia 中的实现
TimeSeries 是一个注册包。因此,像其他包一样,我们可以将其添加到您的 Julia 包中:
Pkg.update()
Pkg.add("TimeSeries")
时间序列类型 TimeArray
immutable TimeArray{T, N, D<:TimeType, A<:AbstractArray} <: AbstractTimeSeries
timestamp::Vector{D}
values::A
colnames::Vector{UTF8String}
meta::Any
function TimeArray(timestamp::Vector{D},
values::AbstractArray{T,N},
colnames::Vector{UTF8String},
meta::Any)
nrow, ncol = size(values, 1), size(values, 2)
nrow != size(timestamp, 1) ? error("values must match length of
timestamp"):
ncol != size(colnames,1) ? error("column names must match width of
array"):
timestamp != unique(timestamp) ? error("there are duplicate dates"):
~(flipdim(timestamp, 1) == sort(timestamp) || timestamp ==
sort(timestamp)) ? error("dates are mangled"):
flipdim(timestamp, 1) == sort(timestamp) ?
new(flipdim(timestamp, 1), flipdim(values, 1), colnames, meta):
new(timestamp, values, colnames, meta)
end
end
该类型有四个字段:
-
timestamp:时间戳字段由一个TimeType类型的子类型值向量组成,实际上是Date或DateTime。DateTime类型与 Date 类型相似,但它表示的是比一天更小的时间框架。为了使TimeArray的构造正常工作,这个向量需要按顺序排序。如果向量包含非连续的日期,对象构造会报错。该向量还需要从最旧到最新日期排序,但构造器可以处理此问题,不会阻止对象的创建。 -
values:值字段包含时间序列中的数据,其行数必须与时间戳数组的长度匹配。如果它们不匹配,构造器将失败。values数组中的所有值必须是相同类型的。 -
colnames:colnames字段是一个 UTF8 字符串类型的向量,包含每个列在值字段中的列名。此向量的长度必须与values数组的列数匹配,否则构造器将失败。 -
meta:meta字段默认不包含任何内容,用Void类型表示。这个默认设置旨在允许程序员忽略该字段。对于那些希望使用此字段的人,meta可以保存常见类型,如String或更复杂的用户定义类型。有人可能希望为不可变的对象分配一个名称,而不是依赖于对象类型字段外的变量绑定。
我们将使用MarketData包中可用的历史财务数据集:
Pkg.add("MarketData")
using TimeSeries
using MarketData
现在让我们来查看数据:
ohlc[1]
这将产生以下输出:
1x4 TimeSeries.TimeArray{Float64,2,Date,Array{Float64,2}} 2000-01-03 to 2000-01-03
Open High Low Close
2000-01-03 | 104.88 112.5 101.69 111.94
让我们再看一些记录和统计数据:
ohlc[[1:3;9]]
这将产生以下输出:
4x4 TimeSeries.TimeArray{Float64,2,Date,Array{Float64,2}} 2000-01-03 to 2000-01-13
Open High Low Close
2000-01-03 | 104.88 112.5 101.69 111.94
2000-01-04 | 108.25 110.62 101.19 102.5
2000-01-05 | 103.75 110.56 103.0 104.0
2000-01-13 | 94.48 98.75 92.5 96.75
我们也可以通过列名来遍历它们:
500x2 TimeSeries.TimeArray{Float64,2,Date,Array{Float64,2}} 2000-01-03 to 2001-12-31
Open Close
2000-01-03 | 104.88 111.94
2000-01-04 | 108.25 102.5
2000-01-05 | 103.75 104.0
2000-01-06 | 106.12 95.0
2001-12-26 | 21.35 21.49
2001-12-27 | 21.58 22.07
2001-12-28 | 21.97 22.43
2001-12-31 | 22.51 21.9
要使用日期访问记录,可以如下操作:
ohlc[[Date(2000,1,3),Date(2000,2,4)]]
它将给出以下输出:
2x4 TimeSeries.TimeArray{Float64,2,Date,Array{Float64,2}} 2000-01-03 to 2000-02-04
Open High Low Close
2000-01-03 | 104.88 112.5 101.69 111.94
2000-02-04 | 103.94 110.0 103.62 108.0
我们还可以列出日期范围内的记录:
ohlc[Date(2000,1,10):Date(2000,2,10)]
它将产生以下输出:
23x4 TimeSeries.TimeArray{Float64,2,Date,Array{Float64,2}} 2000-01-10 to 2000-02-10
Open High Low Close
2000-01-10 | 102.0 102.25 94.75 97.75
2000-01-11 | 95.94 99.38 90.5 92.75
2000-01-12 | 95.0 95.5 86.5 87.19
2000-01-13 | 94.48 98.75 92.5 96.75
2000-02-07 | 108.0 114.25 105.94 114.06
2000-02-08 | 114.0 116.12 111.25 114.88
2000-02-09 | 114.12 117.12 112.44 112.62
2000-02-10 | 112.88 113.88 110.0 113.5
我们还可以使用两个不同的列:
ohlc["Open"][Date(2000,1,10)]
它产生以下输出:
1x1 TimeSeries.TimeArray{Float64,1,Date,Array{Float64,1}} 2000-01-10 to 2000-01-10
Open
2000-01-10 | 102
使用时间约束
如果满足条件,某些特定方法可以按时间范围进行分段。
when
when方法允许将TimeArray中的元素聚合到特定的时间段。
例如:dayofweek或month。以下是一些日期方法及其示例:
day Jan 3, 2000 = 3
dayname Jan 3, 2000 = "Monday"
week Jan 3, 2000 = 1
month Jan 3, 2000 = 1
monthname Jan 3, 2000 = "January"
year Jan 3, 2000 = 2000
dayofweek Monday = 1
dayofweekofmonth Fourth Monday in Jan = 4
dayofyear Dec 31, 2000 = 366
quarterofyear Dec 31, 2000 = 4
dayofquarter Dec 31, 2000 = 93
来自
from(cl, Date(2001, 10, 24))
这将产生以下输出:
47x1 TimeSeries.TimeArray{Float64,1,Date,Array{Float64,1}} 2001-10-24 to 2001-12-31
Close
2001-10-24 | 18.95
2001-10-25 | 19.19
2001-10-26 | 18.67
2001-10-29 | 17.63
2001-12-26 | 21.49
2001-12-27 | 22.07
2001-12-28 | 22.43
2001-12-31 | 21.9
到
to(cl, Date(2000, 10, 24))
这段代码将生成以下输出:
206x1 TimeSeries.TimeArray{Float64,1,Date,Array{Float64,1}} 2000-01-03 to 2000-10-24
Close
2000-01-03 | 111.94
2000-01-04 | 102.5
2000-01-05 | 104.0
2000-01-06 | 95.0
2000-10-19 | 18.94
2000-10-20 | 19.5
2000-10-23 | 20.38
2000-10-24 | 18.88
findwhen
这可能是最常用且高效的方法之一。它测试一个条件并返回Date或DateTime向量:
red = findwhen(ohlc["Close"] .< ohlc["Open"]);
这将生成以下输出:
252x4 TimeSeries.TimeArray{Float64,2,Date,Array{Float64,2}} 2000-01-04 to 2001-12-31
Open High Low Close
2000-01-04 | 108.25 110.62 101.19 102.5
2000-01-06 | 106.12 107.0 95.0 95.0
2000-01-10 | 102.0 102.25 94.75 97.75
2000-01-11 | 95.94 99.38 90.5 92.75
2001-12-14 | 20.73 20.83 20.09 20.39
2001-12-20 | 21.4 21.47 20.62 20.67
2001-12-21 | 21.01 21.54 20.8 21.0
2001-12-31 | 22.51 22.66 21.83 21.9
find
find方法类似于findwhen。它测试一个条件并返回一个整数向量,表示条件为真的行号:
green = find(ohlc["Close"] .> ohlc["Open"]);
这将生成以下输出:
244x4 TimeSeries.TimeArray{Float64,2,Date,Array{Float64,2}} 2000-01-03 to 2001-12-28
Open High Low Close
2000-01-03 | 104.88 112.5 101.69 111.94
2000-01-05 | 103.75 110.56 103.0 104.0
2000-01-07 | 96.5 101.0 95.5 99.5
2000-01-13 | 94.48 98.75 92.5 96.75
2001-12-24 | 20.9 21.45 20.9 21.36
2001-12-26 | 21.35 22.3 21.14 21.49
2001-12-27 | 21.58 22.25 21.58 22.07
2001-12-28 | 21.97 23.0 21.96 22.43
数学、比较和逻辑运算符
这些方法也由 TimeSeries 包支持。
使用数学运算符:
-
- 或 .+:数学元素逐项加法
-
- 或 .-: 数学元素级减法
-
- 或 .*: 数学元素级乘法
-
./: 数学元素级除法
-
.^: 数学元素级指数运算
-
% 或 .%: 数学元素级余数
使用比较运算符:
-
.> 元素级大于比较
-
.< 元素级小于比较
-
.== 元素级等价比较
-
.>= 元素级大于或等于比较
-
.<= 元素级小于或等于比较
-
.!= 元素级不等价比较
使用逻辑运算符:
-
& 元素级逻辑与
-
| 元素级逻辑或
-
!, ~ 元素级逻辑非
-
$ 元素级逻辑异或
应用方法到时间序列
常见的时间序列数据转换包括:
-
滞后操作
-
前导
-
计算变化
-
窗口操作和聚合操作
滞后
lag 方法将昨天的值放置在今天的时间戳上:
cl[1:4]
#Output
4x1 TimeSeries.TimeArray{Float64,1,Date,Array{Float64,1}} 2000-01-03 to 2000-01-06
Close
2000-01-03 | 111.94
2000-01-04 | 102.5
2000-01-05 | 104.0
2000-01-06 | 95.0
这在此应用了滞后操作:
lag(cl[1:4])
它生成以下输出:
3x1 TimeSeries.TimeArray{Float64,1,Date,Array{Float64,1}} 2000-01-04 to 2000-01-06
Close
2000-01-04 | 111.94
2000-01-05 | 102.5
2000-01-06 | 104.0
滞后
Lead 操作与滞后操作相对:
lead(cl[1:4])
生成的输出如下:
3x1 TimeSeries.TimeArray{Float64,1,Date,Array{Float64,1}} 2000-01-03 to 2000-01-05
Close
2000-01-03 | 102.5
2000-01-04 | 104.0
2000-01-05 | 95.0
由于 cl 有 500 行长,我们可以向前推动至此。目前,我们将推动 400:
lead(cl, 400)
生成的输出如下:
100x1 TimeSeries.TimeArray{Float64,1,Date,Array{Float64,1}} 2000-01-03 to 2000-05-24
Close
2000-01-03 | 19.5
2000-01-04 | 19.13
2000-01-05 | 19.25
2000-01-06 | 18.9
2000-05-19 | 21.49
2000-05-22 | 22.07
2000-05-23 | 22.43
2000-05-24 | 21.9
百分比
最常见的时间序列操作之一是计算百分比变化:
percentchange(cl)
生成的输出如下:
499x1 TimeSeries.TimeArray{Float64,1,Date,Array{Float64,1}} 2000-01-04 to 2001-12-31
Close
2000-01-04 | -0.0843
2000-01-05 | 0.0146
2000-01-06 | -0.0865
2000-01-07 | 0.0474
2001-12-26 | 0.0061
2001-12-27 | 0.027
2001-12-28 | 0.0163
2001-12-31 | -0.0236
这显示了与上一记录的百分比变化。
将方法结合在时间序列中
两个 TimeArrays 可以合并生成有意义的数组。
合并
合并操作连接两个 TimeArrays。默认情况下,使用内连接:
merge(op[1:4], cl[2:6], :left)
生成的输出如下:
4x2 TimeSeries.TimeArray{Float64,2,Date,Array{Float64,2}} 2000-01-03 to 2000-01-06
Open Close
2000-01-03 | 104.88 NaN
2000-01-04 | 108.25 102.5
2000-01-05 | 103.75 104.0
2000-01-06 | 106.12 95.0
在前面的例子中,我们提供了要执行的连接类型。我们还可以进行右连接或外连接。
压缩
collapse 方法用于将数据压缩到更大的时间范围内。
映射
该方法用于时间序列数据的转换。此方法的第一个参数是一个二元函数(时间戳和值)。此方法返回两个值,分别是新的时间戳和新的值向量:
a = TimeArray([Date(2015, 10, 24), Date(2015, 11, 04)], [15, 16], ["Number"])
生成的输出如下:
2x1 TimeSeries.TimeArray{Int64,1,Date,Array{Int64,1}} 2015-10-24 to 2015-11-04
Number
2015-10-24 | 15
2015-11-04 | 16
您可以如下应用 map 方法:
map((timestamp, values) -> (timestamp + Dates.Year(1), values), a)
这会转换给定时间的记录:
2x1 TimeSeries.TimeArray{Int64,1,Date,Array{Int64,1}} 2016-10-24 to 2016-11-04
Number
2016-10-24 | 15
2016-11-04 | 16
总结
在本章中,我们学习了什么是预测以及为什么它在商业中是必需的。预测帮助识别需求并采取必要的措施,在其他领域,它有助于预测天气等。预测的结果对决策过程有很大的影响。时间序列是按标准间隔收集的洞察的安排。它已在医学、天气、金融市场等多个领域得到应用。
我们还学习了不同类型的模型以及如何分析时间序列中的趋势。我们还考虑了季节性对时间序列分析的影响。我们详细讨论了 ARIMA 模型,并探索了 Julia 的时间序列库。
参考
-
documents.software.dell.com/statistics/textbook/time-series-analysis -
userwww.sfsu.edu/efc/classes/biol710/timeseries/timeseries1.htm
第十章 协同过滤与推荐系统
每天,我们都面临各种决策和选择。这些选择可以从我们的衣物,到我们能看的电影,甚至是我们在线订餐时的选择。我们在商业中也需要做决策。例如,选择我们应该投资哪只股票。我们所面临的选择集合会根据我们实际做什么以及我们想要什么而有所不同。例如,在 Flipkart 或 Amazon 上购买衣物时,我们会看到成百上千的选择。Amazon 的 Kindle 商店如此庞大,以至于没有人能够在一生中读完所有书籍。为了做出这些决策,我们需要一些背景信息,也许还需要一点帮助,知道什么对我们最好。
通常,个人依赖于朋友的建议或专家的意见来做出选择和决策。他们可能会观察朋友或信任的人,做出与自己相同的决策。这些决策可能包括支付一笔较高的票价去看电影,或者点一份自己从未尝试过的披萨,或者开始阅读一本自己一无所知的书。
这些建议的产生方式是有限制的。这些建议并不依赖于用户的喜好或“口味”。可能有许多电影、披萨或书籍是某人可能喜欢的,但他们的朋友或公司可能并不喜欢,而这些人的决定通常会影响他们的选择。传统的建议或推荐方式无法照顾到用户的特定口味。
在本章中,我们将学习:
-
什么是推荐系统?
-
关联规则挖掘
-
基于内容的过滤
-
什么是协同过滤?
-
什么是基于用户和基于项目的协同过滤?
-
构建推荐引擎
什么是推荐系统?
推荐框架使用学习方法来为数据、物品或服务提供个性化的建议。这些推荐系统通常与目标用户有一定程度的互动。近年来收集到的数据以及今天产生的数据,为这些推荐系统提供了巨大的支持。
今天,许多推荐系统正在运行,并每天生成数百万条推荐:
-
电子商务网站关于书籍、衣物或商品的推荐
-
适合我们口味的广告
-
我们可能感兴趣的属性类型
-
适合我们口味和预算的旅游套餐
当前一代推荐系统能够提供有价值的推荐,并且可以扩展到数百万种产品和目标用户。即使产品或用户的数量增加,推荐系统也应该继续正常工作。但这也带来了另一个挑战,因为为了获得更好的推荐,算法将处理更多的数据,这将增加推荐所需的时间。如果限制处理的数据量,生成的推荐可能不那么有效,或者质量无法获得用户的信任。
我们需要创建平衡,并设计机制以在更短的时间内处理更多数据。
这是通过使用基于用户的协同过滤或基于物品的协同过滤来实现的。在深入了解协同过滤之前,我们还将介绍关联规则挖掘。
推荐系统框架是数据和电子商务系统中的核心部分。它是一个有效的技术,帮助客户在海量数据和商品空间中筛选信息。它还帮助用户发现他们可能未曾搜索过或未曾购买的产品,如果没有推荐系统的存在,用户可能根本不会接触到这些产品。这也有助于提高销售,因为越来越多的用户会找到他们感兴趣的正确物品。
推荐系统的改进已经进行了大量研究,并且普遍认为没有一种推荐系统能够适应所有类型的问题。
算法无法在没有用户交互或数据的情况下工作。与用户的交互是必要的:
-
了解用户
-
向用户提供生成的推荐
从用户那里收集“好”数据是一个巨大挑战,即消除或去除那些可能影响生成推荐结果的噪音数据。
一些用户通常会浏览互联网上的各种信息,而另一些用户则只关注他们感兴趣的数据。另外,一些用户非常关注隐私,不允许数据收集过程进行。
实际上,当前的推荐系统通常在提供干净且有用的数据时能够给出好的推荐。大量的工作被投入到数据收集和清洗阶段,在这个过程中我们了解哪些数据对用户实际上是有用的。
用户效用矩阵
一般来说,在推荐系统中,我们会遇到属于两个类别的实体:
-
用户
-
物品
用户可能会对特定的物品有偏好,我们需要发现这种偏好,并向他们展示符合标准的物品。
让我们以电影的用户评分矩阵为例。我们将构建一个用户-电影矩阵,其中评分将作为矩阵中的值:
| 星际大战 IV | 教父 | 指环王 1 | 指环王 2 | 指环王 3 | 恋恋笔记本 | 泰坦尼克号 | |
|---|---|---|---|---|---|---|---|
| 用户 1 | 4 | 3 | 3 | ||||
| 用户 2 | 5 | 3 | 2 | ||||
| 用户 3 | 5 | 5 | 4 | ||||
| 用户 4 | 4 | 3 | |||||
| 用户 5 | 2 | 3 | 4 | 5 |
在这个具体的例子中,我们可以看到用户 1 给《教父》打了 4 分,给《指环王 3》打了 3 分,给《恋恋笔记本》打了 3 分,但该用户没有给其他电影评分,这通常是因为用户没有观看这些电影。也有可能是用户选择不分享对这些电影的看法。
这些值的范围是从 1 到 5,1 为最低评分,5 为最高评分。这表明矩阵是稀疏的,意味着大多数条目是未知的。在现实世界中,我们遇到的数据通常更稀疏,我们需要用用户可能的评分来填充这些空白,从而提供推荐。
关联规则挖掘
关联规则挖掘是寻找频繁出现的项目集合之间的关联或模式。这也被称为市场篮分析。
它的主要目的是理解顾客的购买习惯,通过发现顾客打算购买或实际购买的商品之间的关联性和模式来实现。例如,购买计算机键盘的顾客也很可能购买计算机鼠标或优盘。
该规则由以下给出:
- 前提 → 结果 [支持度,置信度]
关联规则的衡量标准
令* A 、 B 、 C 、 D * 和* E *……表示不同的商品。
然后我们需要生成关联规则,例如:
-
{A, J} → {C}
-
{M, D, J} → {X}
第一个规则表示,当* A 和 J 一起购买时,顾客购买 C *的概率很高。
类似地,第二个规则表示,当* M 、 D 和 J 一起购买时,顾客购买 X *的概率也很高。
这些规则通过以下方式衡量:
-
支持度:支持度指的是总覆盖率。它是一起购买商品的概率,占总交易的比例:
-
支持度, X → Y: P(X,Y)
-
(同时包含 X 和 Y 的交易)/(总交易数)
-
-
置信度:置信度指的是准确性。它是购买第二件商品的概率,前提是第一件商品已经被购买:
-
置信度, X → Y: P(Y|X)
-
(包含 X 和 Y 的交易)/(仅包含 X 的交易)
-
有些商品购买得不够频繁,它们可能对算法没有那么重要。为了生成这些规则,需要丢弃这些商品。它们通过两个阈值来定义,称为:
-
最小支持度
-
最小置信度
如何生成项目集
-
满足所需最小支持度的项目集被选中。
-
如果 {X,Y} 满足最小支持度的标准,那么 X 和 Y 也满足该标准,反之则不成立。
Apriori 算法:属于频繁项目集的子集本身也是频繁的:
-
首先,基于 n 查找所有项目集:
- 例如,当 n=2 时,{{X,Y}, {Y,Z}, {X,S}, {S,Y}}
-
现在我们将这些集合合并到更高的层次:
- {{X,Y,Z}, {X,Y,S}, {Y,Z,S}, {X,Z,S}}
-
从这些合并的集合中,我们检查有多少个符合所需的最小支持度:
- 我们排除那些无法生成最小支持度的集合
-
我们不断增加层级,直到无法生成具有所需最小支持度的集合为止
如何生成规则
当项目集数量较小时,使用暴力破解方法:
-
生成所有项目集的子集。空集不包括在内。
-
计算这些子集的置信度。
-
选择具有较高置信度的规则。
基于内容的过滤
基于内容的过滤创建用户的个人资料,并利用该个人资料向用户提供相关的推荐。用户个人资料是通过用户的历史记录创建的。
例如,电子商务公司可以跟踪以下用户详细信息,以生成推荐:
-
过去的订单项
-
查看或加入购物车但未购买的项目
-
用户浏览历史记录,用于识别用户可能感兴趣的产品类型
用户可能没有手动为这些项目评分,但可以考虑各种因素来评估它们与用户的相关性。基于此,向用户推荐那些可能感兴趣的新项目。
如图所示的过程,从用户个人资料中获取属性,并将其与可用项目的属性进行匹配。当有相关项目时,这些项目被认为是用户感兴趣的,并进行推荐。
因此,推荐结果在很大程度上依赖于用户的个人资料。如果用户个人资料正确反映了用户的喜好和兴趣,那么生成的推荐将是准确的;如果个人资料没有反映用户当前的偏好,那么生成的推荐可能不准确。
基于内容的过滤的步骤
在使用基于内容的过滤生成推荐时涉及多个步骤。这些步骤如下:
-
分析项目的属性。候选推荐项目可能没有有效的信息结构。因此,第一步是以结构化的方式从项目中提取这些属性:
- 例如,对于一家电子商务公司,这些属性是他们目录中产品的属性或特征
-
生成用户的个人资料。用户个人资料是通过考虑各种因素来创建的。这个过程是使用机器学习技术完成的。可以考虑的各种因素包括:
-
订单历史
-
项目查看历史
-
用户浏览历史记录,用于识别用户可能感兴趣的产品类型
除此之外,用户的反馈也会被考虑在内。例如,如果用户在订购产品后感到满意,用户查看了产品多少次,以及在产品上花了多少时间。
-
-
推荐系统使用前面两步生成的组件,接下来生成的用户资料和提取的商品属性通过各种技术进行匹配。用户和商品的不同属性会被赋予不同的权重。然后,我们生成的推荐内容可以根据相关性进行排序。
生成用户资料是一个典型的任务,并且需要全面性,以便生成更准确的资料。
社交网络有助于建立用户资料。它是用户手动提供的信息宝库。用户提供了诸如以下内容的详细信息:
-
用户感兴趣的产品类型,例如哪些类型的书籍、音乐等
-
用户不喜欢的产品,例如某些特定的菜肴、化妆品牌等
在我们开始为用户提供推荐后,我们还可以收到反馈,这有助于推荐系统生成更好的推荐内容:
-
显式反馈:当我们在电商网站上购买商品时,通常会在初次使用后 2-3 天收到反馈表单。该表单的主要目的是帮助公司了解我们是否喜欢该产品,如果不喜欢,应该如何改进。这就是显式反馈。它使得推荐系统可以知道该产品不完全适合用户,或者可以推荐一个更合适的产品。
-
隐式反馈:用户可能不需要手动填写反馈表单,或者可能选择不填写。在这种情况下,用户的活动会被分析和监控,以了解用户对产品的反应。
反馈也可能出现在这些电商网站的产品评论区。这些评论可以被挖掘,从中提取出用户的情感。
尽管从用户那里获得直接反馈可以让系统更容易工作,但大多数用户选择忽略这种反馈。
基于内容的过滤的优点
使用基于内容的过滤方法有很多优点:
-
基于内容的过滤仅依赖于我们为其生成推荐的用户。这些推荐不依赖于其他用户的评分或资料。
-
生成的推荐内容可以向用户解释,因为它们是依赖于用户的个人资料和商品的属性。
-
由于推荐不是基于商品的评分,而是基于这些商品的属性和用户的个人资料,因此尚未购买或评分的新商品也可以被推荐。
基于内容的过滤的局限性
基于内容的过滤方法也有一些局限性:
-
由于基于内容的过滤需要用户资料,对于新用户来说,生成推荐可能会很困难。为了提供高质量的推荐,我们需要分析用户的活动,但生成的推荐可能仍然不会符合用户的喜好。
-
当项目的属性或特征不可用时,基于内容的过滤会面临提供推荐的困难。此外,还需要足够的领域知识来理解这些属性。
-
基于内容的过滤还依赖于用户提供的反馈。因此,我们需要持续分析和监控用户的反馈。在系统无法判断反馈是正面还是负面时,它可能无法提供相关的推荐。
-
CBF(基于内容的过滤)也有将推荐限制在特定集合中的倾向。它可能无法推荐用户可能感兴趣的类似或相关项目。
协同过滤
协同过滤是一种著名的算法,它基于其他用户或同伴的喜好或行为,不同于我们在前一节中研究的基于内容的过滤。
协同过滤:
-
如果用户喜欢其他用户或同伴表现出兴趣的某些项目,那么这些用户的偏好可以推荐给目标用户。
-
它被称为“最近邻推荐”
为了实现协同过滤,做出了一些假设:
-
可以考虑同伴或其他用户的喜好或行为,以了解并预测目标用户的兴趣。因此,假设目标用户与其他考虑的用户具有相似的兴趣。
-
如果用户过去是根据一组用户的评分获得推荐的,那么该用户与该组具有相似的兴趣。
协同过滤有不同的类型:
-
基于记忆的协同过滤:基于记忆的协同过滤通过用户的评分计算用户之间甚至是项目之间的相似性。这用于生成推荐:
-
它利用评分矩阵
-
使用这个评分矩阵,可以在任何给定时刻为目标用户生成推荐。
-
-
基于模型的协同过滤:基于模型的协同过滤依赖于训练数据和学习算法来创建模型。该模型用于利用实际数据生成推荐:
- 这种方法将模型拟合到提供的矩阵,以便基于该模型生成推荐。
协同过滤的基本过程包括:
-
由于协同过滤高度依赖于考虑的群体的偏好,建议找到具有相似兴趣的同伴群体。
-
我们考虑推荐的项目应该出现在该组的项目列表中,但不是用户的项目。
-
在创建矩阵后,根据各种因素为项目赋予特定的评分。
-
得分最高的项目将被推荐。
-
为了随着新项目的加入不断改进和添加推荐,前述步骤会在所需的时间间隔内重新执行。
协同过滤有一些优点和缺点。让我们先来看看优点:
-
如果用户的属性正确形成,那么就更容易理解。
-
用户和产品是简单的系统,不需要特定的理解即可构建推荐系统。
-
产生的推荐通常很好
协同过滤也有一些缺点:
-
正如前面所讨论的,协同过滤需要大量的用户反馈。此外,这些用户需要是可靠的。
-
还需要对项目的属性进行标准化。
-
假设过去的行为会影响当前的选择。
基准预测方法
基准预测方法是可以形成的最简单、最容易的预测。计算基准非常重要,因为它能帮助我们了解我们生成的模型的准确性以及我们产生的算法结果的有效性:
-
分类:分类问题的基准可以通过考虑预测结果的大多数将来自观察量最多的类别来形成。
-
回归:回归问题的基准可以通过考虑预测结果的大多数将是一个集中趋势度量(如均值或中位数)来形成。
-
优化:在进行优化问题时,领域中的随机样本是固定的。
当我们选择了基准预测方法并得到结果后,可以将其与我们生成的模型结果进行比较。
如果我们生成的模型无法超越这些基准方法,那么很可能我们需要在模型上进行改进,以提高准确性。
基于用户的协同过滤
基于用户的协同过滤利用了协同过滤的核心思想,即找到与目标用户过去评分或行为相似的用户。
这种方法也被称为 k-最近邻协同过滤。
如果我们有 n 个用户和 x 个项目,那么我们将得到一个矩阵 R -> nx*。在前面的图中,我们可以看到有一个目标用户和多个其他用户。在这些其他用户中,有两个与目标用户相似。因此,之前的评分或行为可以用于生成推荐。
除了上述矩阵外,我们还需要一个函数来计算用户之间的相似度,以便计算哪些用户的评分可以用来生成推荐。
为了找到用户(u,v)之间的相似度,我们使用 Pearson 相关系数(Pearson 的 r)来寻找最近邻:
这会计算邻居 u 的邻域 N ⊆ U:
-
完全正相关 = 1
-
完全负相关 = -1
Pearson 相关系数的缺点在于,即使两个用户之间有很少的共同评分,它也可能将他们显示为相似。为了解决这个问题,我们可以对两个用户都评分的项目应用一个阈值。
余弦相似度:这是一种找到相似用户的另一种方法。它采用了不同于 Pearson 相关系数的方法。
与使用统计方法的 Pearson 相关系数不同,余弦相似度采用的是向量空间方法。在这种方法中,用户不是矩阵的一部分,而是由|I|维向量表示。
为了衡量两个用户(向量)之间的相似度,它计算余弦距离。这是向量的点积,并将其除以它们的 L2(欧几里得)范数的乘积。
当某个项目没有评分时,点积为 0,并且它会被忽略。
现在,为了生成推荐,我们使用:
它计算邻居的加权平均值,其中相似度作为权重。这是最常用的方法。
以下是基于用户的协同过滤法的一些缺点:
-
稀疏性:所形成的矩阵通常非常稀疏,并且随着用户和项目数量的增加,稀疏性会增加。
-
找到最近邻并做出推荐并不总是那么容易。
-
它的可扩展性较差,并且随着用户和项目数量的增加,计算负担加重。
-
稀疏矩阵可能无法预测实际的志同道合的人群集合。
项目-项目协同过滤
用户协同过滤法存在一些缺点,其中之一是可扩展性问题。为了找到最近邻,我们需要计算邻居之间的相似度,这涉及大量的计算。当用户数量达到数百万时,由于计算能力需求过高,用户协同过滤法可能无法应用。
因此,为了实现所需的可扩展性,采用了项目-项目协同过滤,而不是用户协同过滤。它会寻找一些相似的模式,比如同一组用户喜欢某些项目而不喜欢其他项目,然后这些用户被视为志同道合,进而推荐项目。
仍然需要通过使用 k-最近邻或类似算法,在项目集合中找到相似的项目。
假设有一个用户给了一些项目评分。几天后,该用户重新访问这些项目并修改他们的评分。通过修改评分,用户实际上是进入了另一个邻居。
因此,不建议始终预计算矩阵或寻找最近邻。这通常在实际需要推荐时完成。
基于项目的协同过滤算法
-
对于(i=1 到 I),其中I指所有可用项目:
-
对于每个顾客x,他评分了I。
- 对于同一顾客x购买的每个项目K
-
-
保存顾客x购买了项目I和K
-
对于每个项目K
- 计算i和K之间的相似度。
-
这个特定的相似度是通过与用户协同过滤中使用的相同方法计算的:
-
基于余弦的相似度
-
基于相关性的相似度
使用加权平均值来生成推荐。
让S表示与i相似的项目集合,那么可以做出预测。定义基于项目的协同过滤的公式如下:
对于邻居数k,你可能已经评分了一些被考虑在内的项目。
构建电影推荐系统
数据集由"GroupLens 研究"维护,并且可以在grouplens.org/datasets/movielens/免费获取。
我们将处理包含 2000 万个评分的数据库(ml-20m.zip)。该数据库包含:
-
2000 万个评分
-
465,000 个标签应用被 138,000 个用户应用到 27,000 部电影。
我们将使用 ALS 推荐器,它是一种矩阵分解算法,采用加权λ正则化交替最小二乘法(ALS-WR)。
假设我们有一个包含用户u和项目i的矩阵:
*Matrix, M (ui) = { r (if item i is rated by the user, u)*
*0 (if item i is not rated by user, u) }*
这里,r表示提交的评分。
假设我们有m个用户和n个电影。
对于m个用户和n个电影,我们创建一个用户与电影的矩阵(mn*)。
推荐是为任何用户-电影对生成的,如下所示:
(i,j),rij=ui⋅mj,∀i,j
这里,*(i,j)*表示用户-电影对。
Julia 有一个名为RecSys.jl的包,由 Abhijith Chandraprabhu 创建(github.com/abhijithch)。可以按如下方式安装此包:
Pkg.update()
Pkg.clone("https://github.com/abhijithch/RecSys.jl.git")
我们将以并行模式启动 Julia:
julia -p <number of worker processes>
由于我们将处理一个庞大的数据集,建议并行启动 Julia 进程。
在示例部分有movielens.jl。我们将使用它为我们生成推荐。
将其保存在可以调用的目录中,并使用任何文本编辑器(如 Atom(Juno)、Sublime、Vim 等)打开:
using RecSys
import RecSys: train, recommend, rmse
if isless(Base.VERSION, v"0.5.0-")
using SparseVectors
end
本示例将使用RecSys包,我们正在导入方法train、recommend和rmse:
type MovieRec
movie_names::FileSpec
als::ALSWR
movie_mat::Nullable{SparseVector{AbstractString,Int64}}
function MovieRec(trainingset::FileSpec, movie_names::FileSpec)
new(movie_names, ALSWR(trainingset, ParShmem()), nothing)
end
function MovieRec(trainingset::FileSpec, movie_names::FileSpec,
thread::Bool)
new(movie_names, ALSWR(trainingset, ParThread()), nothing)
end
function MovieRec(user_item_ratings::FileSpec,
item_user_ratings::FileSpec, movie_names::FileSpec)
new(movie_names, ALSWR(user_item_ratings, item_user_ratings,
ParBlob()), nothing)
end
end
这创建了一个复合类型,它是一个由命名字段组成的集合,可以将其实例视为单个值。这是一个用户定义的数据类型。
这个用户定义的数据类型有三个字段和三个方法。字段als属于类型 ALSWR,该类型定义在 RecSys 中。
该函数使用多重调度处理不同类型的输入,用户可以提供这些输入:
-
trainingset和movie_names -
trainingset、movie_names和thread -
user_item_ratings、item_user_ratings和movie_names
function movie_names(rec::MovieRec)
if isnull(rec.movie_mat)
A = read_input(rec.movie_names)
movie_ids = convert(Array{Int}, A[:,1])
movie_names = convert(Array{AbstractString}, A[:,2])
movie_genres = convert(Array{AbstractString}, A[:,3])
movies = AbstractString[n*" - "*g for (n,g) in
zip(movie_names, movie_genres)]
M = SparseVector(maximum(movie_ids), movie_ids, movies)
rec.movie_mat = Nullable(M)
end
get(rec.movie_mat)
end
这创建了一个名为 movie_names 的函数,它与 movielens 数据集协作,处理 CSV 文件中的数据类型和缺失值,我们使用该文件作为推荐系统的输入。
现在,为了训练系统,我们将使用 train 函数:
train(als, num_iterations, num_factors, lambda)
在这种特定情况下,我们将按如下操作:
train(movierec::MovieRec, args...) = train(movierec.als, args...)
这将使用 ALS 对 movielens 数据集进行模型训练:
rmse(movierec::MovieRec, args...; kwargs...) = rmse(movierec.als, args...; kwargs...)
我们还可以启动对将产生的推荐进行测试:
rmse(als, testdataset)
要开始推荐,按如下步骤操作:
recommend(movierec::MovieRec, args...; kwargs...) = recommend(movierec.als, args...; kwargs...)
function print_recommendations(rec::MovieRec, recommended::Vector{Int}, watched::Vector{Int}, nexcl::Int)
mnames = movie_names(rec)
print_list(mnames, watched, "Already watched:")
(nexcl == 0) || println("Excluded $(nexcl) movies already watched")
print_list(mnames, recommended, "Recommended:")
nothing
end
这将在屏幕上打印推荐结果。
我收到的推荐是:
[96030] Weekend It Lives, The (Ax 'Em) (1992) - Horror
[96255] On Top of the Whale (Het dak van de Walvis) (1982) - Fantasy
[104576] Seasoning House, The (2012) - Horror|Thriller
[92948] Film About a Woman Who... (1974) - Drama
[6085] Neil Young: Human Highway (1982) - Comedy|Drama
[94146] Flower in Hell (Jiokhwa) (1958) - Crime|Drama
[92083] Zen (2009) - Drama
[110603] God's Not Dead (2014) - Drama
[105040] Dragon Day (2013) - Drama|Sci-Fi|Thriller
[80158] Cartoon All-Stars to the Rescue (1990) - Animation|Children|Comedy|Drama|Fantasy
我们将调用 test 函数生成推荐:
function test(dataset_path)
ratings_file = DlmFile(joinpath(dataset_path, "ratings.csv");
dlm=',', header=true)
movies_file = DlmFile(joinpath(dataset_path, "movies.csv");
dlm=',', header=true)
rec = MovieRec(ratings_file, movies_file)
@time train(rec, 10, 10)
err = rmse(rec)
println("rmse of the model: $err")
println("recommending existing user:")
print_recommendations(rec, recommend(rec, 100)...)
println("recommending anonymous user:")
u_idmap = RecSys.user_idmap(rec.als.inp)
i_idmap = RecSys.item_idmap(rec.als.inp)
# take user 100
actual_user = isempty(u_idmap) ? 100 : findfirst(u_idmap, 100)
rated_anon, ratings_anon = RecSys.items_and_ratings(rec.als.inp,
actual_user)
actual_movie_ids = isempty(i_idmap) ? rated_anon : i_idmap[rated_anon]
nmovies = isempty(i_idmap) ? RecSys.nitems(rec.als.inp) :
maximum(i_idmap)
sp_ratings_anon = SparseVector(nmovies, actual_movie_ids,
ratings_anon)
print_recommendations(rec, recommend(rec, sp_ratings_anon)...)
println("saving model to model.sav")
clear(rec.als)
localize!(rec.als)
save(rec, "model.sav")
nothing
end
-
这个函数将数据集路径作为参数。在这里,我们将提供提取自
ml-20m.zip的目录路径,该文件我们从 grouplens 下载。 -
它接受评分文件和电影文件,并创建一个类型为 MovieRec 的对象 "rec",这是我们之前创建的。
-
我们将对象传递给 rmse 以找出误差。
-
它调用
print_recommendations,该函数会调用推荐函数为现有用户生成推荐。 -
它会保存模型以供以后使用。
总结
在本章中,我们学习了什么是推荐引擎,以及它们对企业的重要性,此外,我们还了解了它们为客户提供的价值。我们讨论了关联规则挖掘和市场篮子分析,了解了这一简单方法在行业中的应用。接着,我们探讨了基于内容的过滤及其优缺点。随后,我们讨论了协同过滤以及协同过滤的不同类型,包括基于用户的协同过滤和基于项目的协同过滤。基于用户的协同过滤的目标是找到与目标用户过去的评分或行为相似的用户,而基于项目的协同过滤则通过分析项目评分中的模式来找到志同道合的用户并推荐项目。
第十一章 深度学习简介
创新者一直渴望创造能够思考的机器。当可编程计算机首次被设想时,人们就已经在思考它们是否能够变得聪明,这比计算机的实际诞生早了一百多年(1842 年由洛夫莱斯提出)。
今天,人工智能(AI)是一个蓬勃发展的领域,拥有众多实际应用和充满活力的研究方向。我们期望智能程序能够自动化日常工作、处理图像和音频并从中提取意义、自动化多种疾病的诊断等。
起初,随着人工智能(AI)的发展,该领域处理和解决了那些对人类来说在心理上较为困难,但对计算机来说却相对简单的问题。这些问题可以通过一套正式的、数学的原则来描述。人工智能的真正挑战变成了解决那些对人类来说容易执行,但对于计算机来说却很难正式描述的任务。这些任务我们通常是自然地解释的,例如人类理解语言(和讽刺)的能力,以及我们识别图像,尤其是面孔的能力。
这种方法是让计算机通过积累经验进行学习,并通过一系列的事实链条或树状结构来理解世界,每个事实都通过与更简单事实的关联来定义。通过理解这些事实,这种方法避免了需要人工管理者正式指定计算机所需的所有信息。
事实的渐进系统使计算机能够通过将复杂的概念构建为更简单的概念来学习复杂的思想。如果我们画出一个图表,表示这些概念是如何相互依赖的,那么这个图表将是深刻的,并且包含许多层次。因此,我们称这种方法为深度学习。
人工智能的早期成就发生在相对封闭和正式的环境中,当时计算机并不需要太多关于世界的知识。让我们来看一个例子:
- IBM 的深蓝(Deep Blue)国际象棋框架在 1997 年击败了当时的世界冠军加里·卡斯帕罗夫(Gary Kasparov)。
我们还应该考虑以下因素:
-
国际象棋显然是一个极其简单的世界。
-
它仅包含 64 个方块和 32 个元素,这些元素只能按照预定义的方式移动。
-
尽管构思一个成功的国际象棋系统是一项巨大的成就,但这个挑战并不在于如何将国际象棋元素的排列和可行的走法描述给计算机。
-
国际象棋可以完全通过一套极其简短的、完全正式的规则来描述,这些规则可以很容易地由程序员预先给出。
计算机在某些任务上表现优于人类,而在其他任务上则表现较差:
-
对人类来说,抽象任务是最具挑战性的心理工作之一,而对计算机来说却是最简单的。计算机更适合处理此类任务。
- 一个例子是执行复杂的数学任务。
-
主观和自然的任务由普通人比计算机更好地完成。
-
人类的日常生活需要大量关于世界的信息。
-
其中很多知识是主观的和自然的,因此很难以正式的方式表达。
-
计算机也需要捕捉这些信息,以便做出明智的决策。人工智能的一个关键挑战是如何将这种非正式的学习传递到计算机中。
-
一些人工智能项目曾尝试在形式化语言中对世界的知识进行硬编码。计算机可以通过在这些形式化语言中进行推理,从而使用合乎逻辑的推理规则。这被称为基于知识库的人工智能方法。然而,这些尝试并未取得显著的成功。
依赖硬编码信息的系统所面临的挑战表明,人工智能系统需要能够获取自己的知识,通过从原始数据中提取模式。这就是我们在前几章中学习过的机器学习。
这些简单的机器学习算法的性能在很大程度上依赖于它们所接收到的数据信息的表示。
例如,当逻辑回归用于预测未来天气时,人工智能系统并不会直接考虑患者:
-
专家会向系统提供一些重要的信息,例如温度变化、风向和风速、湿度等。
-
包含在天气表示中的每一项数据都被称为特征。逻辑回归会了解这些天气特征如何与不同季节或其他地区的天气相关。
-
然而,它无法以任何方式影响特征的定义。
解决这个问题的一个方法是利用机器,找出从表示到结果的映射方式以及表示本身。这种方法被称为表示学习。学习到的表示通常能带来比手工设计的表示更优的性能。它们还使得人工智能系统能够快速适应新任务,且几乎不需要人类干预。
一种表示学习算法可以在几分钟内为简单任务找到合适的特征集合,或为复杂任务找到特征集合,这可能需要数小时到数个月。为复杂任务手动设计特征需要大量的人力时间和精力,而计算机则大大减少了这一过程。
在本章中,我们将涉及多个主题,首先是基本介绍:
-
基础知识
-
机器学习与深度学习的区别
-
什么是深度学习?
-
深度前馈网络
-
单层和多层神经网络
-
卷积网络
-
实用的方法论与应用
重温线性代数
线性代数是数学中广泛使用的一个分支。线性代数是离散数学的一部分,而不是连续数学的一部分。要理解机器学习和深度学习模型,需要有良好的基础理解。我们只会复习数学对象。
标量的要点
标量仅是一个单独的数字(与线性代数中讨论的大多数对象不同,后者通常是不同数字的数组)。
向量的简要概述
向量是一个有序的数字集合或数组。我们可以通过该列表中的索引来识别每个单独的数字。例如:
x = [x1, x2, x3, x4 ..... xn]
-
向量也可以被看作是空间中点的标识。
-
每个元素代表沿不同轴的坐标值。
-
我们还可以对这些值在向量中的位置进行索引。因此,更容易访问数组的特定值。
矩阵的重要性
-
矩阵是一个二维的数字数组。
-
每个元素由两个索引标识,而不仅仅是一个。
-
例如,二维空间中的一个点可以表示为(3,4)。这意味着该点在 x 轴上是 3 个单位,在 y 轴上是 4 个单位。
-
我们也可以拥有类似[(3,4), (2,4), (1,0)]的数字数组。这样的数组称为矩阵。
什么是张量?
如果需要多于两维(矩阵),我们则使用张量。
这是一个没有定义轴数的数字数组。
这些对象的结构如下:T (x, y, z)
[(1,3,5), (11,12,23), (34,32,1)]
概率与信息理论
概率理论是一种用于表示不确定性命题的科学体系。它提供了一种评估不确定性的方法,并为推导新的不确定性陈述提供了准则。
在 AI 应用中,我们使用概率理论的方式如下:
-
概率法则定义了 AI 系统应如何推理,因此算法被设计用来计算或近似基于概率理论推导出的不同表达式。
-
概率和统计可以用来假设性地分析提议的 AI 系统的行为。
虽然概率理论允许我们提出不确定的表达式并在不确定的视野中进行推理,但数据使我们能够衡量概率分布中的不确定性程度。
为什么选择概率?
与主要依赖计算机系统确定性特性的其他计算机科学分支不同,机器学习大规模利用概率理论:
-
这是因为机器学习必须始终处理不确定的量。
-
有时候也可能需要处理随机(非确定性)量。不确定性和随机性可能来自多个来源。
所有活动都需要在不确定性的面前进行推理。实际上,通过过去的数学推理,既然它们是定义上有效的,我们很难想到任何完全有效的建议,或任何完全能够保证发生的事件。
不确定性有三种可能的来源:
-
模型框架中存在的随机性。
- 例如,在玩扑克牌游戏时,我们假设牌是以完全随机的方式洗牌的。
-
片段化的可观察性。当大部分驱动系统行为的变量无法被观察到时,即使是确定性系统也可能看起来是随机的。
- 例如,在一个带有多个选择题答案的考试中,一个选项是正确答案,而其他选项将导致错误的结果。给定挑战者的选择,结果是确定性的,但从候选人的角度看,结果是不可确定的。
-
片段化建模。当我们使用一个模型,而必须丢弃我们已经观察到的一部分数据时,丢弃的数据会导致模型预测的不稳定。
- 例如,假设我们制造了一个能够准确观察到周围每个物体位置的机器人。如果机器人在预测这些物体的未来位置时将空间离散化,那么离散化会使得机器人迅速变得不确定物体的确切位置:每个物体可能出现在它被看到的离散单元中的任何地方。
概率可以看作是将逻辑扩展到处理不确定性的方式。逻辑提供了一套形式化的规则,用于计算在假设某些其他建议为真或假时,哪些结论可以被推断为真或假。
概率理论提供了一套形式化的规则,用于根据不同建议的概率来确定某个建议为真或假的概率。
机器学习与深度学习的区别
机器学习和深度学习旨在实现相同的目标,但它们是不同的,代表着不同的思维方式。机器学习是两者中最主要的一种,科学家和数学家们已经研究它几十年了。深度学习是一个相对较新的概念。深度学习基于通过神经网络(多个层级)来学习以实现目标。理解两者之间的区别非常重要,这有助于我们知道在何种情况下应该应用深度学习,哪些问题可以通过机器学习解决。
已知通过利用仅依赖于领域和决定目标的信息,可以构建一种更强大的模式识别算法,该信息可以轻松挖掘。
例如,在图像识别中,我们积累了各种图片并在此基础上扩展算法。利用这些图片中的信息,我们的模型可以被训练来识别生物、人的外貌或其他模式。
机器学习与其他领域相关,现在它不仅仅局限于图像或字符识别。目前,它在机器人技术、金融市场、自动驾驶汽车和基因组分析等领域得到了广泛应用。我们在之前的章节中学习了机器学习,现在我们可以进一步了解它与深度学习的不同之处。
什么是深度学习?
深度学习在 2006 年开始变得流行,也被称为层次学习。它的应用广泛,极大地扩展了人工智能和机器学习的范围。社区对深度学习的兴趣巨大。
深度学习指的是一类机器学习技术,具体包括:
-
执行无监督或监督的特征提取。
-
通过利用多个非线性信息处理层,执行模式分析或分类。
它由一系列特征或因素构成。在这个层次结构中,低层特征有助于定义高层特征。人工神经网络通常用于深度学习。
-
传统的机器学习模型学习模式或聚类。深度神经网络通过极少的步骤学习计算。
-
一般来说,神经网络越深,其能力就越强大。
-
神经网络会根据新提供的数据进行更新。
-
人工神经网络具有容错性,这意味着如果网络的某些部分被破坏,可能会影响网络的性能,但网络的关键功能仍可能得以保留。
-
深度学习算法学习多层次的表示,并行执行计算,这些计算的复杂性可能不断增加。
如果我们快速推进到今天,大家普遍对现在许多人称之为深度学习的技术充满热情。最著名的深度学习模型,特别是在大规模图像识别任务中应用的,是卷积神经网络,简称 ConvNets。
深度学习强调我们需要使用的模型类型(例如深度卷积多层神经网络),以及我们可以利用数据来填补缺失的参数。
深度学习带来了巨大的责任。因为我们从一个具有高维度的世界模型开始,我们实际上需要大量的数据,也就是我们所说的“大数据”,并且需要相当大的计算能力(通用 GPU/高性能计算)。卷积在深度学习中被广泛使用(特别是在计算机视觉应用中)。
在前面的图像中,我们看到了三层:
-
输出层:在这里预测一个监督目标
-
隐藏层:中间函数的抽象表示
-
输入层:原始输入
人工模拟神经元代表了多层人工神经网络的构建模块。基本的思想是模拟人类大脑以及它如何解决复杂问题。制造神经网络的主要思想是基于这些理论和模型。
在过去几十年里,深度学习算法取得了许多重要进展。这些进展可以用来从无标签数据中提取特征指标,还可以预训练深度神经网络,这些神经网络由多个层次组成。
神经网络是学术研究中的一个有趣问题,也是在大型科技公司中至关重要的领域,例如 Facebook、Microsoft 和 Google 等公司,正在大力投资于深度学习研究。
由深度学习算法驱动的复杂神经网络被认为是解决重大问题的最先进技术。例如:
-
谷歌图像搜索:我们可以使用谷歌图像搜索工具在互联网上搜索图片。这可以通过上传图片或提供图片的 URL 来搜索。
-
谷歌翻译:这个工具可以读取图片中的文本,并理解语音,进行翻译或解释多种语言的含义。
另一个非常著名的应用是自动驾驶汽车,谷歌或特斯拉所创造的。它们由深度学习驱动,能够实时找到最佳路径,穿越交通,并执行必要的任务,像是由人类司机驾驶时一样。
深度前馈网络
深度前馈网络是最著名的深度学习模型。这些也被称为以下几种:
-
前馈神经网络。
-
多层感知器(MLPs)
前馈神经网络的目标是通过其参数进行学习,并定义一个映射到输出 y 的函数:
y = f(x, theta)
正如图中所示,前馈神经网络之所以叫做前馈网络,是因为它们的数据流向是单向的。它从 x 开始,通过函数进行中间计算,生成 y。
当这些系统还包括与上一层的连接(反馈)时,它们被称为递归神经网络。
前馈系统对机器学习专家至关重要。它们构成了许多重要商业应用的基础。例如,用于语音自然语言处理的卷积网络就是一种特定类型的前馈系统。
前馈系统是通往递归网络的合理垫脚石。这些系统在自然语言应用中有许多用途。前馈神经网络被称为网络,因为它们通过将多个不同的函数组合在一起来表示。该模型与一个有向无环图相连,描述了函数是如何组合在一起的。
例如,我们有三个函数——f(1)、f(2)和f(3)。
它们按如下方式链接或关联在一起:
f(x) = f(3)(f(2)(f(1)(x)))
这些链式结构是神经网络中最常用的结构。在这种情况下:
-
f(1) 被称为网络的第一层。
-
f(2) 被称为第二层,依此类推。
-
链的总长度决定了模型的深度。正是从这个术语中,"深度学习" 这个名称产生。
-
前馈网络的最终层被称为输出层或结果层。
在神经网络训练过程中,我们遵循以下步骤:
-
驱动 f(x) 与 f∗(x) 一致。训练数据包含噪声和不准确的数据 off ∗(x),这些数据是在不同的训练集上评估的。
-
每个* x *的示例都由标签 *y ≈ f∗(x)*关联。
-
训练案例直接决定了每个 x 点上输出层应该做什么。也就是说,它必须生成一个接近 y 的值。
理解神经网络中的隐藏层
其他层的行为并未由训练数据直接指定。学习算法必须选择如何利用这些层来生成期望的输出,但训练数据并没有说明每一层应该做什么。
相反,必须由学习算法来选择如何利用这些层以最佳方式执行估计 off ∗。由于训练数据没有显示每一层的期望输出,这些层被称为隐藏层。
神经网络的动机
-
这些系统之所以被称为神经网络,是因为它们在某种程度上受到神经科学的启发。
-
系统的每一隐藏层通常都是向量值的。
-
这些隐藏层的 y 维度决定了模型的宽度。
-
向量的每个分量可以理解为承担类似神经元的角色。
-
与其将该层视为展示单一的向量到向量函数,不如认为该层由许多单元组成,这些单元并行工作,每个单元展示一个向量到标量的函数。
-
每个单元看起来像一个神经元,因为它从许多不同的单元获取贡献并注册其自身的激活值。
-
使用多个向量值表示的层是受到神经科学的启发。
-
用来确定这些表示的函数 f(i)(x) 的选择在某种程度上是受到神经科学观察的指导,这些观察关注有机神经元处理的功能。
我们在前几章中研究了正则化。现在让我们研究为什么这对深度学习模型至关重要。
理解正则化。
机器学习中的主要问题是如何构建一个能够在训练数据和新输入上都表现良好的算法。机器学习中使用的许多技术特别旨在减少测试误差,可能以增加训练误差为代价。这些技术统称为正则化。
深度学习专家可以使用许多种正则化方法。更有效的正则化策略一直是该领域研究的重点之一。
有许多正则化策略。
-
机器学习模型的额外约束。
- 例如,包含对参数值的约束。
-
目标函数中的附加项可以视为与参数值的精细要求进行比较。
-
如果策略得当且谨慎,这些附加要求和约束可以在测试数据上带来更好的性能。
-
这些约束和限制也可以用来编码特定类型的先前学习。
-
这些约束和限制也可以导致模型的泛化。
-
集成方法也使用正则化来生成更好的结果。
关于深度学习,大多数正则化程序依赖于正则化估计器。为了调节估计器:
-
我们需要交换增加的偏差以减少方差。
-
一个有效的正则化器是能够做出有利交换的,这意味着它显著减少了方差,同时不会过度增加偏差。
在过拟合和泛化中,我们专注于训练模型时遇到的这些情况:
-
避免关于生成过程的真实信息,以考虑过拟合并引入偏差。
-
包含关于生成过程的真实信息。
-
包含关于生成过程的信息,并且额外包含关于生成过程的其他众多信息,以考虑过拟合,其中方差而非偏差主导了估计误差。
正则化的目标是将模型带入提到的第二个过程。
过于复杂的模型家族并不一定包含目标函数或真实的数据生成过程。然而,大多数深度学习算法的应用场景是,真实的数据生成过程很可能超出了模型家族的范围。深度学习算法通常与复杂的应用场景密切相关,如图像识别、语音识别、自动驾驶汽车等。
这意味着,控制模型复杂性不仅仅是找到一个适当大小且具有正确参数集的模型。
优化深度学习模型。
优化方法在设计算法中至关重要,用于从海量数据中提取所需的知识。深度学习是一个快速发展的领域,新的优化技术不断涌现。
深度学习算法在许多关联中包含优化。例如,在像 PCA 这样的模型中执行推断,涉及解决优化问题。
我们通常使用诊断优化来编写验证或配置计算。深度学习中许多优化问题中,最难的就是训练神经网络。
在许多机器上花费数天甚至数月的时间,以解决神经网络训练问题的单一案例,这种情况非常常见。由于这个问题如此关键且昂贵,已提出了一系列优化策略来改进它。
优化的案例
为了找到神经网络的参数θ,从而显著减少成本函数 J(θ),通常需要评估整个训练集的执行度量,并可能包含额外的正则化项。
用作机器学习任务训练算法的优化与纯粹的优化不同。更复杂的算法在训练过程中会调整其学习率,或影响包含在成本函数二阶导数中的数据。最后,一些优化方法是通过将基本的优化算法组合成更高级的策略而产生的。
用于训练深度学习模型的优化算法与传统优化算法在几个方面有所不同:
- 机器学习通常是间接进行的。在大多数机器学习场景中,我们考虑某个执行度量P,该度量在测试集上定义,并且可能是顽固的。因此,我们间接地优化P。我们减少另一个成本函数J(θ),期望通过这样做来改善P。
这与纯粹的优化不同,在纯粹优化中,最小化J本身就是目标。用于训练深度学习模型的优化算法通常还会针对机器学习目标函数的具体结构进行一些专门化。
在 Julia 中的实现
有许多好的、经过测试的深度学习库,适用于流行的编程语言:
-
Theano(Python)可以同时使用 CPU 和 GPU(来自蒙特利尔大学的 MILA 实验室)
-
Torch(Lua)是一个类似 Matlab 的环境(来自 Ronan Collobert、Clement Farabet 和 Koray Kavukcuoglu)
-
Tensorflow(Python)利用数据流图
-
MXNet(Python、R、Julia、C++)
-
Caffe 是最流行且广泛使用的
-
Keras(Python)基于 Theano
-
Mocha(Julia)由张启源编写
我们将主要介绍 Mocha for Julia,这是一个由麻省理工学院博士生张启源编写的令人惊叹的包。
首先,按如下方式添加包:
Pkg.update()
Pkg.add("Mocha")
网络架构
Mocha 中的网络架构指的是一组层:
data_layer = HDF5DataLayer(name="data", source="data-list.txt", batch_size=64, tops=[:data])
ip_layer = InnerProductLayer(name="ip", output_dim=500, tops=[:ip], bottoms=[:data])
-
ip_layer的输入与data_layer的输出具有相同的名称 -
相同的名称将它们连接起来
Mocha 对一组层执行拓扑排序
层的类型
-
数据层
- 这些层从源读取信息并将其传递给顶层
-
计算层
- 这些层从基础层接收输入流,进行计算,并将生成的结果反馈给顶层
-
损失层
-
这些层从基础层接收处理过的结果(以及真实标签)并计算标量损失值
-
来自网络中所有层和正则化器的损失值被纳入,以表征网络的最终损失函数
-
反向传播中的网络参数通过损失函数的帮助进行训练
-
-
统计层
-
这些层从基础层接收信息,并生成有价值的见解,如分类准确性
-
见解在多个迭代过程中收集
-
reset_statistics可用于明确重置统计汇总
-
-
工具层
神经元(激活函数)
让我们了解真正的神经网络(大脑)。神经科学是研究大脑功能的学科,并为我们提供了关于大脑如何工作的有力证据。神经元是大脑的真实信息存储单元。理解它们的连接强度,即一个神经元如何强烈地影响与其连接的神经元,也是非常重要的。
学习或任务的重复以及对新的刺激过程或环境的暴露,通常会导致大脑活动,实际上是神经元根据接收到的新数据做出反应。
神经元,因而大脑,在面对不同的刺激和环境时表现得非常不同。它们在某些情境下的反应或激动程度可能比其他情境更加明显。
理解这一点对了解人工神经网络非常重要:
-
神经元可以连接到任何层
-
每一层的神经元都会影响前向传递中的输出以及反向传播中的梯度,除非它是一个身份神经元
-
默认情况下,层具有身份神经元
让我们了解一下可以用来构建网络的各种神经元类型:
-
class Neurons.Identity- 这是一种激活函数,其输入不发生变化。
-
class Neurons.ReLU-
修正线性单元。在前向传递过程中,它将所有小于某个限制 ϵ(通常是 0)的约束限制在该值之下。
-
它逐点处理 y=max(ϵ,x)。
-
-
class Neurons.LreLU-
泄漏修正线性单元。Leaky ReLU 可以解决“死亡 ReLU”问题。
-
如果足够大的梯度改变权重,使得神经元在新信息上永远不被激活,ReLU 会“死亡”。
-
-
class Neurons.Sigmoid-
Sigmoid 是一种平滑的阶跃函数
-
它对于绝对值极大的负信息输出大约为 0,对于极大的正输入输出约为 1
-
点对点方程是 y=1/(1+e−x)y=1/(1+e−x)
-
-
class Neurons.Tanh-
Tanh 是 Sigmoid 的一种变种
-
它的取值为 ±1±1,而不是单位间隔。
-
点对点方程是 y=(1−e−2x)/(1+e−2x)
-
理解 ANN 的正则化方法
我们在前面的章节中研究了正则化方法。正则化方法包括对网络参数的附加惩罚或限制,以限制模型的复杂度。在一个流行的深度学习框架中,Caffe,它被称为衰减(decay)。
权重衰减和正则化在反向传播中是可以比较的。前向传递中的理论对比在于,当被看作权重衰减时,它们不被视为目标函数的一部分。
默认情况下,Mocha 同样会删除正则化器的前向计算,目的是减少计算量。我们使用“正则化”这个术语,而不是“权重衰减”,因为它更容易理解。
-
类
NoRegu: 无正则化 -
类
L2Regu: L2 正则化器 -
类
L1Regu: L1 正则化器
范数约束
范数限制是一种通过在每个 n 周期中明确收缩参数来直接限制模型复杂度的方法,如果参数的标准或范数超过给定阈值。
-
类
NoCons: 无约束 -
类
L2Cons: 限制参数的欧几里得范数。阈值和收缩应用于每个参数。特别是,阈值应用于卷积层的滤波器参数的每个滤波器。
在深度神经网络中使用求解器
Mocha 包含广泛实用的随机(子)梯度优化求解器。这些求解器可以用来训练深度神经网络。
求解器通过指明一个求解器参数词汇表来开发,提供了重要的配置:
-
一般设置,如停止条件
-
特定计算的参数
此外,通常建议在训练迭代之间休息片刻,以打印进度或保存快照。这些在 Mocha 中被称为“咖啡休息”。
求解器算法
-
类
SGD: 带动量的随机梯度下降。-
lr_policy: 学习率策略。 -
mom_policy: 动量策略。
-
-
类
Nesterov: 随机 Nesterov 加速梯度方法。-
lr_policy: 学习率策略。 -
mom_policy: 动量策略。
-
-
类
Adam: 随机优化方法-
lr_policy: 学习率策略。 -
beta1: 一阶矩估计的指数衰减因子。0<=beta1<1,默认 0.9 -
beta2: 二阶矩估计的指数衰减因子,0<=beta2<1,默认 0.999。 -
epsilon: 影响参数更新的缩放,用于数值条件化,默认 1e-8。
-
咖啡休息
训练可能会变成一个非常计算密集的多次迭代过程。通常建议在训练迭代之间适当休息,打印进度或保存快照。这些被称为 Mocha 中的咖啡休息。它们的执行方式如下:
# report training progress every 1000 iterations
add_coffee_break(solver, TrainingSummary(), every_n_iter=1000)
# save snapshots every 5000 iterations
add_coffee_break(solver, Snapshot(exp_dir), every_n_iter=5000)
每 1,000 次迭代打印一次训练摘要,并每 5,000 次迭代保存一次快照。
使用预训练的 Imagenet CNN 进行图像分类。
MNIST 是一个手写数字识别数据集,包含以下内容:
-
60,000 个训练样本。
-
10,000 个测试样本。
-
28 x 28 单通道灰度图像。
我们可以使用get-mnist.sh脚本来下载数据集。
它调用mnist.convert.jl将二进制数据集转换为 Mocha 可以读取的 HDF5 文件。
data/train.hdf5和data/test.hdf5将在转换完成后生成。
我们在这里使用 Mocha 的本地扩展以加速卷积:
ENV["MOCHA_USE_NATIVE_EXT"] = "true"
using Mocha
backend = CPUBackend()
init(backend)
这配置 Mocha 使用本地后台而非 GPU(CUDA)。
现在,我们将继续定义网络结构。我们将从定义一个数据层开始,该数据层将读取 HDF5 文件。这将成为网络的输入。
source包含真实数据文件的列表:
data_layer = HDF5DataLayer(name="train-data", source="data/train.txt",
batch_size=64, shuffle=true)
通过形成小批量来处理数据。随着批量大小的增加,方差减小,但会影响计算性能。
洗牌可以减少训练过程中顺序的影响。
现在我们将继续定义卷积层:
conv_layer = ConvolutionLayer(name="conv1", n_filter=20, kernel=(5,5),
bottoms=[:data], tops=[:conv1])
-
name: 用于标识层的名称。 -
n_filter: 卷积滤波器的数量。 -
kernel: 滤波器的大小。 -
bottoms: 一个数组,用于定义输入的位置。(我们定义的 HDF5 数据层。) -
tops: 卷积层的输出。
按照以下方式定义更多卷积层:
pool_layer = PoolingLayer(name="pool1", kernel=(2,2), stride=(2,2),
bottoms=[:conv1], tops=[:pool1])
conv2_layer = ConvolutionLayer(name="conv2", n_filter=50, kernel=(5,5),
bottoms=[:pool1], tops=[:conv2])
pool2_layer = PoolingLayer(name="pool2", kernel=(2,2), stride=(2,2),
bottoms=[:conv2], tops=[:pool2])
这些是卷积层和池化层后的两个全连接层。
创建该层的计算是输入与层权重之间的内积。这些也被称为InnerProductLayer。
层的权重也会被学习,因此我们还为这两个层命名:
fc1_layer = InnerProductLayer(name="ip1", output_dim=500,
neuron=Neurons.ReLU(), bottoms=[:pool2], tops=[:ip1])
fc2_layer = InnerProductLayer(name="ip2", output_dim=10,
bottoms=[:ip1], tops=[:ip2])
最后的内积层的维度为 10,表示类别的数量(数字 0~9)。
这是 LeNet 的基本结构。为了训练这个网络,我们将通过添加一个损失层来定义一个损失函数:
loss_layer = SoftmaxLossLayer(name="loss", bottoms=[:ip2,:label])
我们现在可以构建我们的网络:
common_layers = [conv_layer, pool_layer, conv2_layer, pool2_layer,
fc1_layer, fc2_layer]
net = Net("MNIST-train", backend, [data_layer, common_layers..., loss_layer])
使用随机梯度下降法训练神经网络的过程如下:
exp_dir = "snapshots"
method = SGD()
params = make_solver_parameters(method, max_iter=10000, regu_coef=0.0005,
mom_policy=MomPolicy.Fixed(0.9),
lr_policy=LRPolicy.Inv(0.01, 0.0001, 0.75),
load_from=exp_dir)
solver = Solver(method, params)
使用的参数如下:
-
max_iter: 这些是求解器将执行的最大迭代次数,用于训练网络。 -
regu_coef: 正则化系数。 -
mom_policy: 动量策略。 -
lr_policy: 学习率策略。 -
load_from: 在这里我们可以从文件或目录加载已保存的模型。
添加一些咖啡休息,如下所示:
setup_coffee_lounge(solver, save_into="$exp_dir/statistics.hdf5", every_n_iter=1000)
add_coffee_break(solver, TrainingSummary(), every_n_iter=100)
add_coffee_break(solver, Snapshot(exp_dir), every_n_iter=5000)
性能会定期在单独的验证集上进行检查,以便我们能看到进展。我们拥有的验证数据集将用作测试数据集。
为了执行评估,定义一个新的网络,采用相同的架构,但数据层不同,它将从验证集获取输入:
data_layer_test = HDF5DataLayer(name="test-data", source="data/test.txt", batch_size=100)
acc_layer = AccuracyLayer(name="test-accuracy", bottoms=[:ip2, :label])
test_net = Net("MNIST-test", backend, [data_layer_test, common_layers..., acc_layer])
添加一个咖啡休息,获取验证性能报告,具体如下:
add_coffee_break(solver, ValidationPerformance(test_net), every_n_iter=1000)
最后,开始训练,具体如下:
solve(solver, net)
destroy(net)
destroy(test_net)
shutdown(backend)
这是我们创建的两个网络:
现在我们在测试数据上运行生成的模型。我们得到了以下输出:
Correct label index: 5
Label probability vector:
Float32[5.870685e-6
0.00057068263
1.5419962e-5
8.387835e-7
0.99935246
5.5915066e-6
4.284061e-5
1.2896479e-6
4.2869314e-7
4.600691e-6]
总结
在本章中,我们学习了深度学习以及它与机器学习的不同。深度学习是指一类机器学习技术,通过利用多层非线性信息处理来执行无监督或监督的特征提取、模式分析或分类。
我们学习了深度前馈网络、正则化和优化深度学习模型。我们还学习了如何使用 Mocha 在 Julia 中创建一个神经网络来分类手写数字。