谷歌云平台机器学习实用指南(四)
原文:
annas-archive.org/md5/0c231e89c375d109731af45617713934译者:飞龙
第十五章:强化学习
现在,大多数计算机都是基于符号化处理。问题是首先编码在一系列变量中,然后使用一个显式算法进行处理,该算法为问题的每个可能输入提供适当的输出。然而,有些问题通过显式算法的解决既低效甚至不自然,例如,语音识别器;用经典方法处理这类问题是不高效的。这类问题和其他类似问题,如机器人的自主导航或操作中的语音助手,是可以通过基于强化学习的解决方案直接解决的一个非常多样化的问题集。
强化学习基于心理学理论,该理论是在对动物进行一系列实验之后详细阐述的。定义一个要实现的目标,强化学习试图最大化执行动作或动作集以使我们达到指定目标所获得的奖励。强化学习是机器学习的一个非常激动人心的领域,它被用于从自动驾驶汽车到视频游戏的各种应用中。它的目标是创建能够学习和适应环境变化的算法。
本章涵盖的主题包括:
-
强化学习
-
马尔可夫决策过程(MDP)
-
Q 学习
-
时间差分(TD)学习
-
深度 Q 学习网络
在本章结束时,你将全面了解强化学习的力量,并学习这种技术的不同方法。将涵盖几种强化学习方法。
强化学习简介
强化学习的目标是创建能够学习和适应环境变化的算法。这种编程技术基于根据算法选择接收外部刺激的概念。正确的选择将涉及奖励,而错误的选择将导致惩罚。当然,系统的目标是实现最佳可能的结果。
在监督学习中,有一个教师告诉系统哪个是正确的输出(有教师的学习)。这并不总是可能的。通常我们只有定性信息(有时是二元的,对/错,或成功/失败)。可用的信息被称为强化信号。但系统不会提供任何关于如何更新代理行为(即权重)的信息。你不能定义一个成本函数或梯度。系统的目标是创建能够从经验中学习的智能代理。
下图是一个流程图,显示了强化学习与环境之间的交互:
科学文献对强化学习的分类持不确定的态度,将其视为一种范例。事实上,在最初阶段,它被视为监督学习的特殊情况,然后被完全提升为机器学习算法的第三范式。它在监督学习效率低下的不同环境中得到应用;与环境交互的问题是很明显的例子。
以下流程显示了正确应用强化学习算法的步骤:
-
代理的准备
-
监测环境
-
选择最佳策略
-
执行动作
-
计算相应的奖励(或惩罚)
-
更新策略的开发(如果需要)
-
重复步骤 2-5,直到代理学会最佳策略
强化学习基于一些心理学理论,这些理论是在一系列对动物进行的实验之后详细阐述的。特别是,美国心理学家爱德华·桑代克指出,如果一只猫在执行被认为正确的行为后立即得到奖励,那么这种行为再次发生的概率会增加。而面对不受欢迎的行为时,应用惩罚会降低错误重复的概率。
在此理论的基础上,并定义了一个要实现的目标,强化学习试图最大化执行动作或动作集以实现指定目标的奖励。
代理-环境接口
强化学习可以看作是达到目标交互问题的特殊情况。必须达到目标的是被称为代理的实体。与代理必须交互的实体被称为环境,它对应于代理外部的一切。
到目前为止,我们更多地关注了“代理”这个术语,但它究竟代表了什么?代理是一种代表其他程序执行服务的软件实体,通常自动且无形。这些软件也被称为智能代理。
以下是一个代理最重要的特征:
-
它可以在连续集和离散集之间选择对环境采取的行动
-
行动取决于情况。情况总结在系统状态中
-
代理持续监控环境(输入)并持续改变状态
-
行动选择并非易事,需要一定程度的智能
-
代理具有智能记忆
代理具有目标导向的行为,但在一个事先未知或部分已知的不确定环境中行动。代理通过与环境的交互来学习。在通过代理自身进行的测量中了解环境的同时,可以制定计划。策略接近试错理论。
尝试和错误是解决问题的基本方法。它以重复、变化的尝试为特征,直到成功,或者直到智能体停止尝试。
智能体-环境交互是连续的;智能体选择一个要采取的动作,然后环境改变状态,呈现一个新的情况来面对。
在强化学习的特定情况下,环境为智能体提供奖励;确保奖励来源是环境,以避免在智能体内部形成会损害学习的个人强化机制。
奖励的价值与动作在达到目标时产生的影响成比例;因此,在正确动作的情况下是正的或高的,在错误动作的情况下是负的或低的。
以下是一些现实生活中智能体和环境之间交互以解决问题的例子:
-
国际象棋选手,对于每一个移动,都有关于可以创造和对手可能的反击动作的棋子配置的信息
-
一只小长颈鹿在几小时内学会以 50 公里/小时的速度站起来和奔跑
-
一个真正自主的机器人学会在房间里移动以走出房间
-
炼油厂的参数(如油压、流量等)在实时设置,以便获得最大产量或最大质量
我们所分析的所有例子都具有以下共同特征:
-
与环境的交互
-
智能体的目标
-
环境的不确定性或部分知识
从这些例子的分析中,可以得出以下观察:
-
智能体从自己的经验中学习。
-
当动作改变状态(情况)时,未来选择的可能性会改变(延迟奖励)。
-
动作的效果不能完全预测。
-
智能体对其行为的整体评估。
-
它必须利用这些信息来改善其选择。选择随着经验而提高。
-
问题可以有一个有限或无限的时间范围。
马尔可夫决策过程
为了避免负载问题和计算困难,智能体-环境交互被视为一个 MDP。MDP 是一个离散时间随机控制过程。在每一个时间步,过程处于一个状态 s,决策者可以选择在状态 s 中可用的任何动作 a。在下一个时间步,过程通过随机移动到新的状态 s' 并给决策者一个相应的奖励,r(s,s') 来响应。
在这些假设下,智能体-环境交互可以概括如下:
-
智能体和环境在时间上的离散间隔内进行交互,t = 0, 1, 2, … n。
-
在每个间隔,智能体接收环境状态 st 的表示。
-
S 集合中每个元素 s[t],其中 S 是可能状态集合。
-
一旦识别出状态,智能体必须采取 A(s[t]) 中的行动 a[t],其中 A(s[t]) 是状态 s[t] 中的可能行动集合。
-
要采取的行动的选择取决于要实现的目标,并通过带有符号 π(折现累积奖励)的策略来映射,该策略将行动与 A(s) 中的 a[t] 相关联,对于每个状态 s。术语 πt 表示在状态 s 中执行行动 a 的概率。
-
在下一个时间间隔 t + 1 中,作为行动后果的一部分,智能体收到一个数值奖励 r[t] + 1 R,对应于之前采取的行动 a[t]。
-
行动的后果代表的是新的状态 s[t]。在这个时候,智能体必须再次编码状态并选择行动。
-
这种迭代会一直重复,直到智能体达到目标。
状态 s[t] + 1 的定义取决于之前的状态和采取的行动 MDP,即:
s[t] + 1 = δ (s[t],a[t])
这里 δ 代表状态函数。
总结来说:
-
在一个 MDP 中,智能体可以感知到他所处的状态 s S,并且有一个 A 集合的行动可供选择。
-
在每个离散的时间间隔 t 中,智能体检测到当前状态 st 并决定在 A 实施行动。
-
环境通过提供奖励(强化)r[t] = r (s[t], a[t]) 并移动到状态 s[t] + 1 = δ (s[t], a[t]) 来做出反应。
-
r 和 δ 函数是环境的一部分;它们只依赖于当前状态和行动(不是之前的),并且不一定为智能体所知。
-
强化学习的目标是学习一个策略,对于系统所处的每个状态 s,指示智能体采取一个行动以最大化在整个行动序列中收到的总强化。
让我们深入探讨一些使用的术语:
-
奖励函数定义了强化学习问题中的目标。它将环境检测到的状态映射到一个单一的数字,从而定义了一个奖励。如前所述,唯一的目标是在长期内最大化它所收到的总奖励。奖励函数定义了对于智能体来说什么是好事和坏事。奖励函数需要是正确的,并且可以用作改变策略的基础。例如,如果策略选择的行动导致低奖励,策略可以在下一步改变以选择其他行动。
-
策略定义了学习智能体在给定时间的行为。它将环境检测到的状态和在这些状态下采取的行动进行映射。对应于心理学中所谓的规则集或刺激反应的关联。策略是强化学习智能体的基本部分,因为它单独就足以确定行为。
-
价值函数表示一个状态对智能体有多好。它等于智能体从状态 s 预期得到的总奖励。价值函数取决于智能体选择执行的动作的策略。
折扣累积奖励
在上一节中,我们提到这一点:强化学习的目标是学习一个策略,对于系统所处的每个状态 s,指示智能体一个动作,以在整个动作序列中最大化收到的总强化。但如何最大化整个动作序列中收到的总强化呢?
根据策略计算得到的总强化如下:
在这里,r[T] 表示将环境驱动到终端状态 s[T] 的动作的奖励。
解决这个问题的可能方法是将提供最高奖励的动作与每个个体状态关联起来;也就是说,我们必须确定一个最优策略,使得之前的数量最大化。
对于在有限步数内无法达到目标或终端状态的问题(持续任务),R[t] 趋向于无穷大。
在这些情况下,想要最大化的奖励总和在无穷大处发散,因此这种方法不适用。那么,就需要开发一种替代的强化技术。
适合强化学习范式的技术最终证明是折扣累积奖励,它试图最大化以下数量:
这里,γ 被称为折扣因子,它表示对未来奖励的重要性。此参数可以取值 0 ≤ γ ≤ 1,具有以下含义:
-
如果 γ < 1,序列 r[t] 将收敛到一个有限值。
-
如果 γ = 0,智能体将不会对未来的奖励感兴趣,但会尝试仅最大化当前状态的奖励。
-
如果 γ = 1,智能体将试图增加未来的奖励,即使是以牺牲当前的奖励为代价。
折扣因子可以在学习过程中修改,以突出或忽略特定的动作或状态。一个最优策略可以使执行单个动作获得的强化甚至很低(或甚至为负),只要这总体上导致更大的强化。
探索与利用
理想情况下,智能体必须将每个动作与相应的奖励 r 关联起来,然后选择最被奖励的行为以实现目标。因此,这种方法对于复杂问题来说不切实际,在这些问题中,状态的数量特别高,因此可能的关联呈指数增长。
这个问题被称为探索-利用困境。理想情况下,智能体必须探索每个状态的所有可能动作,找到实际最被奖励的动作,以利用它来实现其目标。
因此,决策涉及一个基本的选择:
-
利用:根据当前信息做出最佳决策
-
探索:收集更多信息
在这个过程中,最佳长期策略可能会在短期内带来相当大的牺牲。因此,有必要收集足够的信息来做出最佳决策。
这里有一些采用这种技术解决现实生活案例的例子:
选择商店:
-
利用:前往你最喜欢的商店
-
探索:尝试一个新的商店
选择路线:
-
利用:选择迄今为止的最佳路线
-
探索:尝试一条新路线
在实践中,对于非常复杂的问题,收敛到非常好的策略会太慢。
解决这个问题的好方法是在探索和利用之间找到平衡:
-
一个仅限于探索的代理将始终在每个状态下以随意的方式行事,显然收敛到最优策略是不可能的
-
如果一个代理很少探索,它将始终使用常规动作,这些动作可能不是最优的
强化学习技术
正如我们在前几节中看到的,强化学习是一种编程哲学,旨在开发能够学习和适应环境变化的算法。这种编程技术基于能够根据算法的选择从外部接收刺激的假设。因此,正确的选择将带来奖励,而错误的选择将导致系统的惩罚。系统的目标是实现尽可能高的奖励,从而获得最佳结果。与强化学习相关的技术分为两大类:
-
连续学习算法:这些技术从假设有一个简单的机制来评估算法的选择,然后根据结果奖励或惩罚算法。这些技术也可以适应环境中的重大变化。例如,语音识别程序或 OCR 程序在使用过程中提高其性能。
-
预防性训练算法:这些算法从观察出发,认为不断评估算法的行为可能是一个无法自动化或非常昂贵的流程。在这种情况下,应用一个第一阶段,在这个阶段中,算法被教授;当系统被认为可靠时,它被固定下来,就不再可编辑。许多电子组件在其内部使用神经网络,这些网络的突触权重是不可变的,因为它们在电路构建过程中是固定的。
应该注意的是,前面提到的类别是实现选择而不是算法概念上的差异。因此,一个算法可以根据设计者的实现方式经常位于第一类或第二类。
Q 学习
Q-learning 是最常用的强化学习算法之一。这得益于它能够在不要求环境模型的情况下比较可用动作的预期效用。
通过这项技术,我们可以找到在完成 MDP 中每个给定状态的优化动作。
强化学习问题的一般解决方案是,通过学习过程估计一个评估函数。这个函数必须能够通过奖励的总和评估特定策略的便利性或不利性。实际上,Q-learning 试图最大化 Q 函数(动作值函数)的值,它代表我们在状态 s 执行动作 a 时的最大折现未来奖励,如下所示:
Q(S[t],a[t]) = max(R[t+1])
知道 Q 函数,状态 s 中的最佳动作 a 是具有最高 Q 值的动作。在这个点上,我们可以定义一个策略 π(s),它为我们提供任何状态下的最佳动作。回忆一下,策略 π 将 (s; a) 对与在状态 s 中执行动作的概率 (s; a) 相关联,我们可以写出以下内容:
问题被简化为评估 Q 函数。然后我们可以通过递归过程,用下一个点的 Q 函数来估计过渡点的 Q 函数。以下是在过程的单步中使用的方程。这个方程被称为 贝尔曼方程,代表了 Q-learning 的转换规则:
术语定义如下:
-
Q(s[t],a[t]) 是从状态 s 出发的动作 a 的当前策略。
-
r 是动作的奖励。
-
maxt+1) 定义了最大未来奖励。我们执行了 a[t] 动作,从状态 s[t] 到达状态 s[t+1]。从这里,我们可能有多个动作,每个动作对应一些奖励。计算这些奖励中的最大值。
-
γ 是折扣因子。γ 的值在 0 到 1 之间变化;如果值接近 0,则优先考虑即时奖励。如果它接近 1,则未来奖励的重要性增加,直到 1,此时它被认为与即时奖励相等。
在前面的方程的基础上,评估函数 Q 是即时奖励和从下一个状态开始可获得的最高奖励的总和。
应用前面的公式,我们试图将延迟奖励转化为即时奖励。我们之前提到,Q 函数的评估代表一个递归过程。然后我们可以将这个过程中获得的价值输入到一个表格中,当然,我们将这个表格称为 Q 表。在这个表格中,行是状态,列是动作。作为一个起始的 Q 表,我们可以使用一个包含所有零的矩阵(我们已经初始化了 Q 表),如下面的图所示:
此表 Q 的元素(单元格)是在给定行状态和列动作的情况下获得的奖励。在任何状态下采取的最佳动作是具有最高奖励的动作。我们的任务是使用新值更新此表。为此,我们可以采用以下算法:
-
解码状态 s[t]
-
选择动作 a[t]
-
执行动作 a[t] 并获得奖励 r
-
表 Q(s[t]; a[t]) 的元素根据 Bellman 方程提供的训练规则进行更新。
-
执行动作 a 将环境状态移动到 s[t+1]
-
将下一个状态设置为当前状态 (s[t] = s[t+1])
-
从点 1 重新开始并重复该过程,直到达到终端状态。
在更复杂和高效的公式中,可以用神经网络代替迭代效率仍然不高的表,学习过程将改变突触连接的权重。
时间差分学习
TD 学习算法基于减少代理在不同时间做出的估计之间的差异。在前一节中看到的 Q-learning 是 TD 算法,但它基于相邻瞬间状态之间的差异。TD 更通用,可能考虑更远的时间和状态。
它是蒙特卡洛(MC)方法和动态规划(DP)思想的结合。
MC 方法允许基于获得结果的平均值解决强化学习问题。
DP 代表一组算法,可以在给定环境完美模型(以 MDP 形式)的情况下计算最优策略。
TD 算法可以直接从原始数据中学习,而无需环境动态模型(如 MC)。该算法基于先前学习的估计部分更新估计,而不需要等待最终结果(如 DP 中的自举)。它适用于没有动态环境模型的场景。如果时间步长足够小,或者随着时间的推移而减少,则使用固定策略收敛。
如前节所述,Q-learning 根据以下公式计算其值:
通过采用一步前瞻。
前瞻是尝试预测选择一个分支变量以评估其值的影响的通用术语。前瞻的两个主要目标是选择下一个要评估的变量以及分配给它的值的顺序。
很明显,也可以使用两步公式,如下所示:
更一般地,使用 n 步前瞻,我们得到以下公式:
动态规划
DP 代表一组算法,可以用来在给定环境完美模型(以 MDP 形式)的情况下计算最优策略。DP 的基本思想,以及一般强化学习,是使用状态值和动作来寻找好的策略。
DP 方法通过迭代两个称为政策评估和政策改进的过程来接近解决马尔可夫决策过程。
-
政策评估算法在于将迭代方法应用于 Bellman 方程的求解。由于只有当k → ∞时我们才能保证收敛,我们必须满足于通过设置停止条件来获得良好的近似值。
-
政策改进算法基于当前值来改进策略。
政策迭代算法的一个缺点是我们必须在每一步评估策略。这涉及一个迭代过程,其收敛时间我们事先不知道。这将取决于许多其他因素,包括起始策略的选择。
克服这种缺点的一种方法是在特定步骤中断策略的评估。这种操作不会改变收敛到最优值的保证。一种特殊情况是,通过状态(也称为sweep)的步骤阻止策略评估,定义了值迭代算法。在值迭代算法中,在策略改进的每一步之间执行一次值的计算迭代。
因此,DP 算法基本上基于两个并行发生的过程:政策评估和政策改进。这两个过程的重复执行使整个过程收敛到最优解。在策略迭代算法中,这两个阶段交替进行,每个阶段都在另一个开始之前结束。
DP 方法在整个环境可能假设的状态集中操作,在每个迭代中对每个状态进行完全备份。备份操作中执行的每次更新操作都是基于所有可能的后继状态值,这些值根据它们发生的概率加权,并受到选择策略和环境动态的影响。完全备份与 Bellman 方程密切相关;它们不过是将方程转换为赋值指令。
当完整的备份迭代不会对状态值带来任何变化时,就达到了收敛;因此,最终状态值完全满足 Bellman 方程。DP 方法仅在存在交替器的完美模型时适用,该模型必须等同于 MDP。
正是因为这个原因,动态规划算法在强化学习中用处不大,这不仅是因为它们假设了一个完美的环境模型,还因为计算成本高且昂贵。但仍然有必要提及它们,因为它们代表了强化学习的理论基础。实际上,所有强化学习方法都试图实现动态规划方法相同的目标,只是计算成本更低,且不假设一个完美的环境模型。
动态规划方法相对于状态数量 n 和动作数量 m 的多项式操作次数收敛到最优解,而基于直接搜索的方法则需要指数操作次数 mn*。
动态规划方法基于后续状态的值估计来更新状态的值估计;或者基于过去的估计来更新。这代表了一种特殊属性,称为自助法。几种强化学习方法执行自助法,甚至是不需要像动态规划方法那样要求完美环境模型的方法。
蒙特卡洛方法
用于估计值函数和发现优秀策略的蒙特卡洛方法不需要环境模型的存在。它们能够仅通过代理的经验或从代理与环境交互中获得的状态序列、动作和奖励的样本来学习。经验可以通过代理在符合学习过程的情况下获得,或者通过预先填充的数据集来模拟。在学习过程中获得经验(在线学习)的可能性很有趣,因为它即使在缺乏对环境动力学先验知识的情况下,也能获得优秀的行为。即使通过已经填充的经验数据集进行学习也可能很有趣,因为如果与在线学习相结合,它使得通过他人的经验引起的自动策略改进成为可能。
为了解决强化学习问题,蒙特卡洛方法基于过去回合中获得的总奖励的平均值来估计值函数。这假设经验被划分为回合,并且所有回合都由有限数量的转换组成。这是因为,在蒙特卡洛方法中,只有当回合完成后,才会进行新值的估计和策略的修改。蒙特卡洛方法迭代地估计策略和值函数。然而,在这种情况下,每个迭代周期相当于完成一个回合——策略和值函数的新估计是逐个回合发生的。
通常,MC 术语用于估计方法,其中涉及随机成分的操作;在这种情况下,MC 指的是基于总奖励平均值的强化学习方法。与计算每个状态值的 DP 方法不同,MC 方法计算每个状态-动作对的值,因为在没有模型的情况下,只有状态值不足以决定在某个状态下执行哪种动作最好。
深度 Q 网络
深度 Q 网络(DQN)算法结合了强化学习和深度学习的方法。DQN 通过自我学习,以经验方式学习,而不需要针对特定目标(如赢得棋类游戏)的严格编程。
DQN 代表了一种使用深度学习来近似评估函数的 Q 学习的应用。DQN 是由 Mnih 等人于 2015 年 2 月 26 日在《自然》杂志上发表的文章中提出的。因此,许多研究机构加入了这个领域,因为深度神经网络可以使强化学习算法能够直接处理高维状态。
深度神经网络的使用是由于研究人员注意到了以下事实:使用神经网络来近似强化学习算法中的Q 评估函数使得系统不稳定或发散。实际上,可以注意到对Q的小更新可以显著改变策略、数据分布以及Q和目标值之间的相关性。这些相关性存在于观察序列中,是算法不稳定的原因。
要将一个普通的 Q 网络转换为 DQN,需要采取以下预防措施:
-
用多层卷积网络替换单层神经网络来近似 Q 函数评估
-
实现经验回放
-
在更新期间使用第二个网络来计算目标 Q 值
“经验回放”这个术语是什么意思?这意味着,不是在模拟或实际经验中发生时运行 Q 学习在状态/动作对上,系统会存储发现的数据,通常在一个大表中。这样,我们的网络可以使用存储的经验记忆来自我训练。
OpenAI Gym
OpenAI Gym是一个帮助我们实现基于强化学习算法的库。它包括一个不断增长的基准问题集合,这些问题提供了一个公共接口,以及一个网站,人们可以在那里分享他们的结果并比较算法性能。
OpenAI Gym 专注于强化学习的场景设置。换句话说,代理的经验被划分为一系列的场景。代理的初始状态由一个分布随机采样,直到环境达到终端状态,交互过程才继续。这个程序为每个场景重复进行,目的是最大化每个场景的总奖励期望,并在尽可能少的场景中实现高水平的表现。
Gym 是开发比较强化学习算法的工具包。它支持教代理从走路到玩像 Pong 或弹球这样的游戏的一切。该库可在以下 URL 获取:
下图显示了 OpenAI Gym 项目网站的首页:
OpenAI Gym 是更大胆的项目——OpenAI 项目的一部分。OpenAI 是由埃隆·马斯克和山姆·奥特曼共同创立的人工智能研究公司。这是一个非营利项目,旨在以造福全人类的方式促进和开发友好的人工智能。该组织旨在通过使他们的专利和研究对公众开放,自由地与其他机构和研究人员合作。创始人决定承担这个项目,因为他们对来自人工智能无差别使用的存在风险感到担忧。
OpenAI Gym 是一个程序库,允许你开发人工智能,衡量它们的智力能力,并增强它们的学习能力。简而言之,这是一个以算法形式存在的 Gym,它训练当前的数字大脑,并将它们投射到 OpenAI Gym 项目中。
但还有一个目标。OpenAI 希望通过资助那些即使在经济上没有回报也能让人类进步的项目来刺激人工智能领域的研究。另一方面,Gym 旨在标准化人工智能的测量,以便研究人员可以在平等的基础上竞争,了解他们的同事已经取得了哪些进展,但最重要的是,关注真正对所有人都有用的结果。
可用的工具很多。从玩像 Pong 这样的老式视频游戏到在围棋中战斗,再到控制机器人,我们只需将我们的算法输入这个数字空间,看看它是如何工作的。第二步是将获得的基准与其他基准进行比较,看看我们与其他人的差距,也许我们可以与他们合作,实现互利共赢。
OpenAI Gym 对我们的代理结构没有假设,并且与任何数值计算库兼容,例如 TensorFlow 或 Theano。Gym 库是我们可以使用来测试我们的强化学习算法的测试问题——环境。这些环境有一个共享的接口,允许你编写通用算法。
要安装 OpenAI Gym,请确保您之前已安装了 Python 3.5+ 版本;然后只需输入以下命令:
pip install gym
完成此操作后,我们将能够以简单直接的方式插入库提供的工具。
Cart-Pole 系统
Cart-Pole 系统 是强化学习的一个经典问题。该系统由一个杆(类似于倒立摆)通过一个关节连接到车上,如图所示:
该系统通过向车上施加 +1 或 -1 的力来控制。可以控制施加到车上的力,目标是使杆向上摆动并稳定它。这必须在不让车掉到地面上完成。在每一步,智能体可以选择将车向左或向右移动,并且每当杆平衡时,它都会收到 1 的奖励。如果杆偏离垂直方向超过 15 度,则程序结束。
要使用 OpenAI Gym 库运行 Cart-Pole 示例,只需输入以下代码:
import gym
env = gym.make('CartPole-v0')
env.reset()
for i in range(1000):
env.render()
env.step(env.action_space.sample())
和往常一样,我们将详细解释每一行代码的含义。第一行用于导入 gym 库:
import gym
然后我们继续通过调用 make 方法创建环境:
env = gym.make('CartPole-v0')
此方法创建我们的智能体将运行的虚拟环境。环境是一个具有最小接口的问题,智能体可以与之交互。OpenAI Gym 中的环境设计是为了允许对智能体能力的客观测试和基准测试。Gym 库附带了一系列从简单到困难、涉及多种不同类型数据的环境。
要获取可用环境的列表,请参阅以下链接:
最常用的环境列在这里:
-
经典控制和玩具文本:完成小规模任务,主要来自强化学习文献。它们在这里是为了让您开始。
-
算法:执行加多位数和反转序列等计算。
-
Atari:玩经典 Atari 游戏。
-
2D 和 3D 机器人:在模拟中控制机器人。
在我们的案例中,我们已将 CartPole-v0 环境命名为。make 方法返回一个 env 对象,我们将使用它来与游戏交互。但让我们回到分析代码。现在我们必须使用 reset() 方法初始化系统:
env.reset()
此方法将环境置于其初始状态,返回描述它的数组。在此阶段,我们将使用 for 循环运行 CartPole-v0 环境的实例 1000 次时间步,并在每一步渲染环境:
for i in range(1000):
env.render()
env.step(env.action_space.sample())
调用 render() 方法将可视化显示当前状态,而后续对 env.step() 的调用将允许我们与环境交互,并返回对调用它的动作的响应的新状态。
这样,我们在每一步都采用了随机动作。在这个时候,了解我们对环境所采取的动作以决定未来的动作是非常有用的。step() 方法正是返回这个信息。实际上,这个方法返回以下四个值:
-
observation (object): 代表你对环境观察的环境特定对象。 -
reward (float): 上一个动作获得的奖励量。这个量在环境中变化,但目标始终是增加你的总奖励。 -
done (boolean): 是否是时候重新设置环境了。大多数(但不是所有)任务被划分为定义良好的剧集,done为True表示剧集已结束。 -
info (dict): 用于调试的诊断信息。有时它对学习很有用。
要运行这个简单的示例,将代码保存到名为 cart.py 的文件中,并在 bash 窗口中输入以下命令:
python cart.py
这样,就会显示一个包含我们系统(不稳定且很快会超出屏幕)的窗口。这是因为对小车施加的推力是随机的,没有考虑到杆的位置。
为了解决问题,即平衡杆,因此必须将推力设置在杆倾斜的相反方向。所以,我们只需要设置两种动作,-1 或 +1,将小车推向左边或右边。但为了做到这一点,我们需要随时了解来自环境观察的数据。正如我们之前所说的,这些数据是由 step() 方法返回的,特别是它们包含在观察对象中。
此对象包含以下参数:
-
小车位置
-
小车速度
-
杆的角度
-
杆尖的速度
这四个值成为我们问题的输入。正如我们之前预料的,通过向小车施加推力来平衡系统。有两种可能的选择:
-
向左推小车(0)
-
向右推(1)
很明显,这是一个二元分类问题:四个输入和一个单一的二元输出。
让我们首先考虑如何提取作为输入的值。为了提取这些参数,我们只需更改前面提出的代码:
import gym
env = gym.make('CartPole-v0')
observation = env.reset()
for i in range(1000):
env.render()
print(observation)
observation, reward, done, info = env.step(env.action_space.sample())
通过运行代码,我们可以看到观察对象中包含的值现在被打印在屏幕上。所有这些很快就会变得有用。
使用从环境观察返回的值,智能体必须决定采取两种可能动作之一:将小车向左或向右移动。
学习阶段
现在,我们必须面对最具挑战性的阶段,即我们系统的训练。在前一节中,我们提到 Gym 库专注于强化学习的周期性设置。智能体的经验被划分为一系列的周期。智能体的初始状态由一个分布随机采样,交互过程一直进行到环境达到终端状态。这个程序为每个周期重复进行,目的是最大化每个周期的总奖励期望值,并在尽可能少的周期内达到高水平的表现。
在学习阶段,我们必须估计一个评估函数。这个函数必须能够通过奖励的总和来评估特定策略的便利性或其他方面。换句话说,我们必须近似评估函数。我们如何做?一个解决方案是使用人工神经网络作为函数近似器。
回想一下,神经网络训练的目的是识别神经元之间连接的权重。在这种情况下,我们将为每个周期选择随机的权重值。最后,我们将选择收集到最大奖励的权重组合。
在某一时刻的系统状态由观察对象返回给我们。为了从实际状态中选择一个动作,我们可以使用权重和观察的线性组合。这是函数近似的最重要特殊情况之一,其中近似函数是权重向量 w 的线性函数。对于每个状态 s,都有一个与 w 具有相同数量的分量的实值向量 x(s)。线性方法通过 w 和 x(s) 的内积来近似状态值函数。
以这种方式,我们已经指定了我们打算采用的方法来解决问题。现在,为了使整个训练阶段易于理解,我们报告整个代码块,然后逐行详细注释:
import gym
import numpy as np
env = gym.make('CartPole-v0')
HighReward = 0
BestWeights = None
for i in range(200):
observation = env.reset()
Weights = np.random.uniform(-1,1,4)
SumReward = 0
for j in range(1000):
env.render()
action = 0 if np.matmul(Weights,observation) < 0 else 1
observation, reward, done, info = env.step(action)
SumReward += reward
print( i, j, Weights, observation, action, SumReward, BestWeights)
if SumReward > HighReward:
HighReward = SumReward
BestWeights = Weights
代码的第一部分处理导入库:
import gym
import numpy as np
然后我们继续通过调用 make 方法创建环境:
env = gym.make('CartPole-v0')
这种方法创建了我们的智能体将运行的 环境。现在让我们初始化我们将使用的参数:
HighReward = 0
BestWeights = None
HighReward 将包含到目前为止获得的最高奖励;这个值将用作比较值。BestWeights 将包含将记录最高奖励的权重序列。我们现在可以通过对每个周期的迭代过程实现最佳权重序列搜索:
for i in range(200):
我们决定执行该过程 200 次,因此我们使用 reset() 方法初始化系统:
observation = env.reset()
在每个周期中,我们使用与环境的观察数量相等的权重序列,正如之前所说的,这是四个(小车位置、小车速度、杆角度和杆尖端速度):
Weights = np.random.uniform(-1,1,4)
为了固定权重,我们使用了np.random.uniform()函数。此函数从均匀分布中抽取样本。样本在半开区间(低和高)上均匀分布。它包括低但不包括高。
换句话说,在给定的区间内,任何值都有可能被均匀分布抽取。已经传递了三个参数:输出区间的下界,其上界,以及输出形状。在我们的情况下,我们请求在区间(-1,1)内生成四个随机值。完成此操作后,我们初始化奖励的总和:
SumReward = 0
在这一点上,我们实现另一个迭代周期,以确定使用这些权重可以获得的最大奖励:
for j in range(1000):
调用render()方法将可视化显示当前状态:
env.render()
现在,我们必须决定action:
action = 0 if np.matmul(Weights,observation) < 0 else 1
正如我们所说的,为了决定动作,我们使用了两个向量的线性组合:weights和observation。为了执行线性组合,我们使用了np.matmul()函数;它实现了两个数组的矩阵乘积。因此,如果这个乘积是<0,则action是 0(向左移动);否则,action是 1(向右移动)。
应该注意的是,负乘积意味着杆倾斜到左边,因此为了平衡这种趋势,有必要将小车推向左边。正乘积意味着杆倾斜到右边,因此为了平衡这种趋势,有必要将小车推向右边。
现在我们使用step()方法来返回我们调用它时采取的动作对应的新状态。显然,我们传递给方法的动作是我们刚刚决定的:
observation, reward, done, info = env.step(action)
正如我们所说的,此方法返回以下四个值:
-
observation(对象):一个特定于环境的对象,代表你对环境的观察。 -
reward(浮点数):前一个动作获得的奖励量。在不同的环境中,其比例不同,但目标始终是增加你的总奖励。 -
done(布尔值):是否是时候再次重置环境了。大多数(但不是所有)任务被划分为定义良好的剧集,done为True表示剧集已结束。 -
info(字典):用于调试的诊断信息。有时它对学习很有用。
然后,我们可以使用刚刚获得的奖励更新奖励的总和。记住,对于每次我们保持杆直立的时间步,我们都会获得+1 的reward:
SumReward += reward
我们只需打印出在此步骤中获得的价值:
print( i, j, Weights, observation, action, SumReward, BestWeights)
在当前迭代的末尾,我们可以进行比较,以检查获得的总奖励是否是迄今为止获得的最高的:
if SumReward > HighReward:
如果这是迄今为止获得的最大奖励,则使用此值更新HighReward参数:
HighReward = SumReward
一旦完成,将当前步骤的Weights序列固定为最佳序列:
BestWeights = Weights
通过这条指令,训练阶段结束,这将给我们提供最佳逼近评估函数的权重序列。我们现在可以测试系统。
测试阶段
当训练阶段完成时,在实践中这意味着我们已经找到了最佳逼近该函数的权重序列,即返回了可实现的最佳奖励的那个。现在我们必须用这些值测试系统,以检查杆是否能在至少100个时间步内保持平衡。
现在,因为我们已经完成了训练阶段,为了使整个测试阶段易于理解,我们报告整个代码块,然后逐行详细注释:
observation = env.reset()
for j in range(100):
env.render()
action = 0 if np.matmul(BestWeights,observation) < 0 else 1
observation, reward, done, info = env.step(action)
print( j, action)
首先,我们必须再次使用reset()方法初始化系统:
observation = env.reset()
然后,我们必须运行一个迭代周期来应用训练阶段获得的结果:
for j in range(100):
对于每一步,我们将调用render()方法来可视化显示当前状态:
env.render()
现在,我们必须根据训练阶段获得的最佳权重和当前状态下的观察结果来决定对系统执行的动作:
action = 0 if np.matmul(BestWeights,observation) < 0 else 1
现在我们使用返回对所调用动作的新状态的step()方法。传递给方法的行为是我们刚刚决定的:
observation, reward, done, info = env.step(action)
最后,我们打印出步数和决定执行的动作,以便进行流程的可视化控制。
通过运行提出的代码,我们可以验证在训练阶段之后,系统能够在 100 个时间步内保持杆的平衡。
摘要
强化学习旨在创建能够学习和适应环境变化的算法。这种编程技术基于根据算法选择接收外部刺激的概念。正确的选择将涉及奖励,而错误的选择将导致惩罚。系统的目标是实现最佳可能的结果。在本章中,我们讨论了强化学习的基础。
首先,我们了解到,强化学习的目标是创建能够从经验中学习的智能代理。因此,我们分析了正确应用强化学习算法的步骤。后来,我们探讨了代理-环境接口。必须实现目标的是被称为代理的实体。代理必须与之交互的实体被称为环境,它对应于代理之外的一切。
为了避免加载问题和计算困难,将代理-环境交互视为马尔可夫决策过程(MDP)。MDP 是一个随机控制过程。然后引入了折扣因子概念。折扣因子可以在学习过程中修改,以突出或忽略特定的动作或状态。一个最优策略可以使执行单个动作获得的强化甚至很低(或负值),只要这总体上导致更大的强化。
在本章的核心部分,我们专注于分析最常用的强化学习技术。涵盖了 Q 学习、TD 学习和深度 Q 学习网络。最后,我们探讨了 OpenAI Gym 库,并分析了强化学习的一个实际案例。
第十六章:生成神经网络
近年来,神经网络已被用作生成模型:能够复制输入数据的分布,然后能够从这个分布中生成新值的算法。通常,分析图像数据集,并尝试学习与图像像素相关的分布,以产生与原始图像相似的形状。正在进行大量工作,以使神经网络能够创建小说、文章、艺术和音乐。
人工智能(AI)研究人员对生成模型感兴趣,因为它们代表了一个跳板,可以构建能够使用世界原始数据并自动提取知识的 AI 系统。这些模型似乎是一种训练计算机理解概念的方法,无需研究人员事先教授这些概念。与当前系统相比,这将是一个巨大的进步,因为当前系统只能从由有能力的自然人准确标记的训练数据中学习。
在本章中,我们将触及生成模型中最激动人心的研究途径之一。首先,我们将介绍无监督学习算法;然后提出生成模型的概述。我们还将发现最常见的生成模型,并展示如何实现一些示例。最后,我们将向读者介绍 Nsynth 数据集和 Google Magenta 项目。
涵盖的主题是:
-
无监督学习
-
生成模型介绍
-
受限玻尔兹曼机
-
深度玻尔兹曼机
-
自动编码器
-
变分自动编码器
-
生成对抗网络
-
对抗性自动编码器
在本章结束时,读者将学习如何从神经网络中提取不同类型的内容生成的内容。
无监督学习
无监督学习是一种机器学习技术,它从一系列输入(系统经验)开始,能够根据共同特征重新分类和组织,以尝试对后续输入进行预测。与监督学习不同,在学习过程中,只向学习者提供未标记的示例,因为类别不是事先已知的,而是必须自动学习。
下面的图表显示了从原始数据中标记的三个组:
从这张图中,我们可以注意到系统基于相似性识别了三个组,在这个例子中,这种相似性是由于邻近性。一般来说,无监督学习试图识别数据的内部结构以重现它。
这些算法的典型例子是搜索引擎。这些程序,给定一个或多个关键词,能够创建一个链接列表,这些链接指向搜索算法认为与所进行的研究相关的页面。这些算法的有效性取决于它们可以从数据库中提取的信息的有用性。
无监督学习技术通过比较数据和寻找相似性或差异来工作。众所周知,机器学习算法试图模仿动物神经系统的功能。为此,我们可以假设神经过程是由优化他们追求的未知目标的机制所引导的。每个过程都从与刺激相关联的初始情况发展到终端,其中有一个答案,这是过程本身的结果。直观地讲,在这个过程中,存在信息传递。事实上,刺激提供了获得所需响应所需的信息。因此,在过程完成之前,尽可能忠实地传输这些信息是很重要的。因此,解释神经系统发生的过程的合理标准是,将它们视为信息传递,同时最大限度地保留相同的信息。
无监督学习算法基于这些概念。这是一个使用学习理论技术来衡量在传输过程中发生的信息损失的问题。考虑的过程被视为通过通信领域开发出的已知技术来传输信号的噪声信道。然而,也可以遵循基于过程几何表示的不同方法。实际上,刺激和响应都由适当数量的组件表征,这些组件在空间中对应于一个点。因此,这个过程可以解释为输入空间到输出空间的几何变换。输出空间的大小小于输入空间,因为刺激包含了激活许多同时进行的过程所需的信息。与只有一个相比,它是冗余的。这意味着在考虑的变换中始终存在冗余减少操作。
在输入和输出空间中,形成了典型的区域,信息与之相关联。因此,控制信息传输的自然机制必须以某种方式识别考虑过程中的这些重要区域,并确保它们在变换中相对应。因此,在所讨论的过程中存在数据分组操作;这个操作可以与经验的获得相等同。前两个分组和冗余减少操作是典型信号处理中的操作,有生物学证据表明它们存在于神经系统的功能中。值得注意的是,这两种操作在基于实验原则的非监督学习中是自动实现的,例如竞争学习。
生成模型
生成模型旨在生成现象的所有值,包括可观察到的(输入)和可以从观察到的值中计算出的(目标)。我们试图通过提出生成模型和判别模型之间的第一个区别来理解这种模型如何实现这一目标。
在机器学习中,我们通常需要根据输入 x 向量的值预测目标向量 y 的值。从概率的角度来看,目标是找到条件概率分布 p(y|x)。
事件 y 关于事件 x 的条件概率是在已知 x 已验证的情况下 y 发生的概率。这个概率,用 p(y|x) 表示,表达了由 x 的观察所决定的 y 的期望修正。
解决这个问题的最常见方法是用参数模型表示条件分布,然后使用由包含输入变量的值和相应输出的相对向量的对 (xn, yn) 组成的训练集来确定参数。得到的条件分布可以用来对新输入值 (x) 的目标 (y) 进行预测。这被称为判别方法,因为条件分布直接区分了 y 的不同值。
作为这种方法的替代方案,我们可以寻找联合概率分布 p(x∩ y),然后使用这个联合分布来评估条件概率 p(y | x) 以便对新值 x 的 y 进行预测。这被称为生成方法,因为通过从联合分布中采样,可以生成特征向量 x 的合成示例。
联合概率分布 p(x, y) 是一个概率分布,它给出了 x 和 y 向量中的每一个落在为该变量指定的任何特定范围或离散值集中的概率。
生成方法,无论数据类型和使用的理论模型如何,都分为两个基本步骤:
-
第一步涉及生成模型的构建。输入数据被处理,目的是推导它们的分布。为此,输入数据可以简单地重新组织成不同的结构,或者它可以代表从输入数据中提取的新信息,这些信息来自特定的算法。生成模型构建的结果是根据其近似分布呈现数据。
-
一旦在输入数据上构建了生成模型,这允许采样,从而导致形成与输入数据具有相同分布的新数据。
生成模型的构建允许突出显示初始数据中隐含的特征和属性。然后,根据对数据进行解释以说明这些特征的类型以及因此获得的近似数据分布的变量类型,区分不同的方法。
为什么人工智能研究人员对生成模型如此兴奋?让我们举一个简单的例子:假设我们向系统提供一系列猫的图片。假设在这些图片看过之后,计算机能够以完全独立的方式生成新的猫的照片。如果计算机能够做到这一点,并且产生的图像具有正确的腿、尾巴、耳朵等数量,那么很容易证明计算机知道哪些部分构成了猫,即使没有人向它解释过猫的解剖结构。因此,从某种意义上说,一个好的生成模型是计算机对概念基本知识的证明。
这就是为什么研究人员对构建生成模型如此热情的原因。这些模型似乎是一种训练计算机理解概念的方法,无需研究人员事先教授它们概念。
限制性玻尔兹曼机
玻尔兹曼机是一种概率图模型,可以解释为随机神经网络。玻尔兹曼机首次由杰弗里·辛顿和特里·谢诺夫斯基于 1985 年提出。"随机"一词源于神经元的行为;在它们内部,在激活函数中,它们将具有一个概率值,这将影响神经元的激活。
在实践中,玻尔兹曼机是一个模型(包括一定数量的参数),当应用于数据分布时,能够提供一种表示。该模型可以用来从目标分布(目标分布)的样本中提取未知分布的重要方面。玻尔兹曼机所引用的数据样本也称为训练数据。以下图显示了玻尔兹曼机的架构:
训练玻尔兹曼机意味着调整其参数,以便它所表示的概率分布尽可能好地插值训练数据。从计算角度来看,玻尔兹曼机的训练是一项相当繁重的工作。然而,通过在工作网络的拓扑结构上施加限制,可以简化这个问题;这定义了限制性玻尔兹曼机(RBM)。
在玻尔兹曼机中,有两种类型的单元:
-
可见单元(或神经元,因为正如我们所说,玻尔兹曼机可以解释为神经网络)
-
隐藏单元(或神经元)
即使在 RBMs 中,也存在这两种类型的单元,我们可以想象它们被安排在两个层面上:
-
可见单元是观察的组成部分(例如,如果我们的数据由图像组成,我们可以将一个可见单元与每个像素关联)
-
隐藏单元为我们提供了一个关于观察(例如,图像像素之间的依赖关系)的组件之间存在的依赖关系的模型
因此,隐藏单元可以被视为数据特征的检测器。在 RBM 图中,每个神经元都与另一层的所有神经元相连,而同一层的神经元之间没有连接;正是这种限制使得 RBM 得名,如下面的图所示:
在成功训练后,RBM 提供了对训练数据下分布的非常好的表示。它是一个生成模型,允许从学习到的分布中采样新的数据;例如,可以从研究过的图像生成新的图像结构。拥有生成模型使得有用的应用成为可能。例如,你可以考虑整合一些对应于部分观察的可见单元(即,固定观察变量的值并认为它们是常数)然后产生剩余的可见单元以完成观察;在图像分析示例中,这可以用于图像补全任务。
作为生成模型,RBM 也可以用作分类器。考虑这种类型的应用:
-
RBM 被训练来学习输入数据(解释变量)和相应的标签(响应/输出变量)的联合概率分布,这两个变量都在网络图中表示,从 RBM 的可见单元中学习。
-
随后,可以链接一个新的输入模式,这次没有标签,到可见变量。相应的标签可以通过直接从霍尔兹曼机采样来预测。
霍尔兹曼机能够完成可见单元上的部分数据模式。如果我们把可见单元分为输入单元和输出单元,给定输入模式,霍尔兹曼机通过产生输出(分类)来完成它。否则,它作为关联记忆工作,返回学习到的模式中最相似的模式到(部分)数据。
霍尔兹曼机架构
霍尔兹曼机架构基于输入、输出和隐藏节点。连接权重是对称的:
基于这个假设,霍尔兹曼机高度递归,这种递归消除了输入节点和输出节点之间的任何基本差异,在需要时可以被视为输入或输出。霍尔兹曼机是一个为整个网络定义了能量的单元网络。其单元产生二元结果((1,0)值)。输出是概率性地计算的,并依赖于温度变量T。
玻尔兹曼机的共识函数由以下公式给出:
在前面的公式中,项的定义如下:
-
*S[i]是单元i(1,0)*的状态
-
w[ij]是单元j和单元i之间的连接强度
-
u[j]是单元j的输出
计算在机器中以随机方式进行,以便增加一致性。因此,如果w[ij]是正的,那么单元i和j同时激活或同时失活的趋势会增加,而如果权重是负的,那么它们具有不同激活(一个激活,另一个不激活)的趋势。当一个权重是正的时,它被称为兴奋性;否则,它被称为抑制性。
每个二元单元都会随机决定是 1(概率p[i])或 0(概率1- p[i])。这个概率由以下公式给出:
在网络的平衡状态下,似然被定义为指数化的负能量,称为玻尔兹曼分布。你可以想象,通过施加能量,你可以使系统摆脱局部最小值。这必须缓慢进行,因为剧烈的冲击可能会使系统远离全局最小值。最佳方法是先施加能量,然后缓慢减少。这个概念在冶金学中得到了应用,首先通过熔化获得金属的有序状态,然后缓慢降低温度。在过程进行中的温度降低被称为模拟退火。
这种方法可以通过向 Hopfield 网络添加概率更新规则来重现(参见第十三章,超越前馈网络 - CNN 和 RNN);重现它的网络被称为玻尔兹曼机。将有一个参数会变化:温度。因此,在高温T下,跃迁到更高能量的概率远大于在低温下。
当温度下降时,假设正确最小能量状态的概率接近 1,网络达到热平衡。网络中的每个单元都会根据以下公式进行能量跃迁:
系统根据以下概率规则(转换函数)转变为更低能量的状态:
可以看到,在高温T下,向更高能量状态的转换概率高于低温T。网络可以根据以下玻尔兹曼分布假设稳定状态配置:
即,它取决于状态的能量和系统的温度。低能量状态更可能;事实上,如果 E[a] < E[b],则 P[a]/P[b] > 1,因此 P[a]>P[b]。所以系统倾向于向最低能量状态转变。
玻尔兹曼机缺点
基于玻尔兹曼机的算法在使用过程中出现了许多问题。以下是一些遇到的问题:
-
权重调整
-
收集统计信息以计算概率所需的时间,
-
一次改变多少权重
-
如何在模拟退火过程中调整温度
-
如何决定网络何时达到平衡温度。
主要缺点是玻尔兹曼学习比反向传播慢得多。
深度玻尔兹曼机
另一种类型的玻尔兹曼机是深度玻尔兹曼机(DBM)。这是一个类似于 RBM 的神经网络,但它不仅仅只有一个隐藏层节点,DBM 有很多。每个神经层只与相邻层(立即前一个和立即后一个)连接;在这里,同一层的神经元也不相互连接。这种结构使得每个层都能产生特定的统计信息,从而能够捕捉新的数据特征。以下图显示了具有一个可见层和两个隐藏层的 DBM 模型:
如我们所见,连接仅存在于相邻层之间的单元之间。像 RBM 和 DBM 只包含二进制单元。
DBMs 模型为可见向量 v 分配以下概率:
在前面的公式中,术语定义如下:
-
v 是可见向量
-
θ = (W(1),W(2)) 是模型参数,代表可见到隐藏和隐藏到隐藏的对称交互项
-
h^((1)) 和 h^((2)) 是隐藏的随机二进制变量
-
Z(θ) 是配分函数
在识别对象或单词的情况下,DBMs 特别有用。这是由于使用少量标记输入数据学习复杂和抽象的内部表示的强大能力,而不是利用大量未标记的输入数据。然而,与深度卷积神经网络不同,DBMs 在双向推理和训练过程中都采用,以更好地检测输入结构的表示。
自编码器
自编码器是一种神经网络,其目的是将输入编码成小维度,并得到的结果能够重建输入本身。自编码器由以下两个子网组成:
- 编码器,它计算以下函数:
z = ϕ(x)
给定一个输入 x,编码器将其编码到变量 z 中,也称为潜在变量。z通常比 x 的维度小得多。
- 解码器,它计算以下函数:
x' = ψ(z)
由于 z 是编码器产生的 x 的代码,解码器必须将其解码,以便 x' 与 x 相似。
自编码器的训练旨在最小化输入和结果之间的均方误差。
均方误差(MSE)是输出和目标之间的平均平方差。较低的值表示更好的结果。零表示没有错误。
对于 n 个观测值,MSE 由以下公式给出:
最后,我们可以总结说,编码器将输入编码为压缩表示,解码器从它返回输入的重建,如下面的图所示:
让我们定义以下术语:
-
W: 输入 → 隐藏权重
-
V: 隐藏 → 输出权重
之前的公式变为:
z = ϕ(W x)*
并且它们也变为:
x' = ψ(VW1* x)*
最后,自编码器的训练旨在最小化以下量:
自编码器的目的不仅仅是执行一种对输入的压缩或寻找恒等函数的近似。有一些技术可以从一个降低维度的隐藏层开始,指导模型给予某些数据属性更大的重要性,从而基于相同的数据产生不同的表示。
变分自编码器
变分自编码器(VAE)受自编码器概念的影响:由两个称为编码器和解码器的神经网络组成的模型。正如我们所见,编码器网络试图以压缩的形式编码其输入,而解码器网络则试图从编码器返回的代码开始重建初始输入。
然而,变分自编码器(VAE)的功能与简单的自编码器非常不同。VAE 不仅允许对输入进行编码/解码,还可以生成新的数据。为此,它们将代码 z 和重建/生成 x' 视为属于某个概率分布的一部分。特别是,VAE 是深度学习和贝叶斯推理相结合的结果,因为它们由一个使用称为重参数化技术的反向传播算法修改后的神经网络训练而成。虽然深度学习已被证明在复杂函数逼近方面非常有效,但贝叶斯统计允许以概率的形式管理随机生成的不确定性。
VAE 使用与训练集相似的相同结构来生成新的图像。在这种情况下,编码器不会直接为给定的输入生成一个代码,而是计算正态分布的均值和方差。从这个分布中取一个值,然后由解码器进行解码。训练包括修改编码器和解码器参数,以便解码的结果尽可能接近起始图像。训练结束时,我们有从编码器产生的均值和方差的正态分布开始;解码器将能够生成与训练集相似的图像。
让我们定义以下术语:
-
X: 输入数据向量
-
z: 潜在变量
-
P(X): 数据的概率分布
-
P(z): 潜在变量的概率分布
-
P(X|z): 后验概率,即给定潜在变量生成数据的分布
后验概率 P(X|z) 是在证据 z 下 X 的概率。
我们的目标是根据潜在变量中包含的特征生成数据,因此我们想要找到 P(X)。为此,我们可以使用以下公式的全概率定律:
为了理解我们是如何得到这个公式的,我们逐步进行推理。在定义模型的第一项任务是从观察数据开始推断潜在变量的良好值,或者计算后验 p(z|X)。为此,我们可以使用贝叶斯定理:
在前面的公式中,出现了 P(X) 项。在贝叶斯统计的背景下,它也可能被称为证据或模型证据。证据可以通过对潜在变量进行边缘化来计算。这使我们回到了起始公式:
这个积分的计算估计需要指数级的时间,因为它必须在所有潜在变量的配置上进行评估。为了降低计算成本,我们被迫对后验概率的估计进行近似。
在变分自编码器(VAE)中,正如其名所示,我们使用一种称为变分推断(VI)的方法来推断 p(z | X)。VI 是贝叶斯推理中最常用的方法之一。这种技术将推断视为一个优化问题。在这样做的时候,我们使用一个简单且易于评估的分布(例如高斯分布),并使用 Kullback-Leibler 散度度量来最小化这两个分布之间的差异。
Kullback-Leibler 散度度量是两个概率分布 P 和 Q 之间非对称差异的度量。特别地,从 P 到 Q 的 Kullback-Leibler 散度,记为 DKL (P ||Q),是当使用 Q 来近似 P 时损失的信息的度量。
对于离散概率分布P和Q,从Q到P的 Kullback-Leibler 散度定义为以下:
分析公式可以清楚地看出,Kullback-Leibler 散度是概率P和Q之间对数差异的期望,这里的期望是使用概率P来计算的。
生成对抗网络
生成对抗网络(GAN)是由两个共同训练的网络组成的生成模型,称为生成器和判别器。
这两个网络之间的动力学类似于伪造者和调查者之间的关系。伪造者试图制作对真实艺术作品的忠实模仿,而调查者则试图区分赝品和真品。在这个类比中,伪造者代表生成器,调查者代表判别器。生成器接受属于固定分布的输入值,并试图生成与数据集相似的图像。判别器试图区分生成器创建的数据与属于数据集的数据。这两个网络是共同训练的:
-
如果输入属于数据集,判别器试图返回输出=1,如果其输入是由生成器生成的,则返回 0
-
生成器则试图最大化判别器犯错的概率
生成器获取随机输入噪声并试图创建数据样本,而判别器从现实世界的示例或生成器获取输入,如下面的图所示:
为了简单起见,这两个对抗网络是多层感知器类型;然而,可以使用深度网络来模拟相同的结构。例如,为了生成新的图像,而不是从复杂的分布中采样数据,这些网络中使用的方法是从属于简单分布的值或从随机值开始。随后,它们通过将在训练过程中学习的第二个分布进行映射。
在这样的系统中,训练会导致生成器和判别器之间持续的竞争。在这些条件下,优化过程可以在双方独立进行。将生成器命名为G(z),判别器命名为D(x),模型的训练目标是最大化判别器将 1 分配给来自训练集的值的概率,而不是将 0 分配给生成器产生的值。另一方面,我们希望教会生成器最小化以下量:
训练是通过将梯度下降技术应用于以下表达式来进行的:
这种方法源于博弈论,特别是称为两人最小-最大博弈的方法。这类算法采用最小化玩家选择可能造成的最大损失的策略。在训练过程中,判别器可能无法对由真实数据生成的示例进行分类。
对抗自编码器
对抗自编码器(AAE)是 VAE 和 GAN 结合产生的生成模型。为了解释该模型,我们首先定义以下术语:
-
x:自编码器输入
-
z:从 x 生成的代码,
-
p(z):我们想要施加的分布
-
q(z|x):从编码器学习到的分布
-
p(x|z):从解码器学习到的分布
-
pdata:数据的分布
-
p(x):模型的分布
我们将函数 q(z|x) 视为 q(z) 的后验分布,其定义如下:
我们试图将等式 q(z)=p(z) 强加于模型。与 VAE 的不同之处在于,驱动 q (z) 向 p(z) 靠近的是对抗网络。VAE 的编码器被视为 GAN 的生成器,对于该生成器可以使用判别器。这试图区分属于 q(z) 的数据与来自 p(z) 的数据。以下图显示了 AAE 架构:
对抗网络和自编码器的训练是联合进行的,使用随机梯度下降。
使用 RBM 进行特征提取
最近,几种类型的人工神经网络(ANNs)已被应用于对特定数据集进行分类。然而,这些模型中的大多数只使用有限数量的特征作为输入,在这种情况下,由于起始数据集的复杂性,可能没有足够的信息来做出预测。如果你有更多特征,训练的运行时间会增加,并且由于维度诅咒,泛化性能会下降。在这些情况下,一个用于提取特征的工具会特别有用。RBM 是一种具有强大表示能力的机器学习工具,通常用作各种分类问题中的特征提取器。
乳腺癌数据集
乳房由一组腺体和脂肪组织组成,位于皮肤和胸壁之间。实际上,它不是一个单一的腺体,而是一组腺体结构,称为小叶,它们联合起来形成一个叶。在乳房中,有 15 到 20 个小叶。牛奶通过称为乳管的小管道从小叶流向乳头。
乳腺癌如果未被发现和治疗,可能是一种严重的疾病。它是由乳腺中某些细胞不受控制地增殖并转化为恶性细胞引起的。这意味着它们具有从产生它们的组织中脱离并侵犯周围组织以及最终侵犯身体其他器官的能力。理论上,癌症可以由所有类型的乳腺组织形成,但最常见的是由腺细胞或形成导管壁的细胞形成。
本例的目标是识别多个良性或恶性类别中的每一个。为此,我们将使用名为“乳腺癌”(威斯康星乳腺癌数据库)的数据集中的数据。这些数据是从 UCI 机器学习数据库仓库中获取的,因为 DNA 样本定期到达,正如 Wolberg 博士报告他的临床病例一样。因此,数据库反映了这种数据的按时间顺序分组。这种分组信息立即出现,因为已经被从数据本身中移除。除了第一个变量之外,每个变量都被转换成了 11 个原始数值属性,其值从零到十不等。
为了获取数据,我们借鉴了以下链接中 UCI 机器学习仓库的大量数据集:archive.ics.uci.edu/ml。
为了加载数据集,我们将使用sklearn.datasets模块。它包括用于加载数据集的实用工具,包括加载和检索流行参考数据集的方法。它还提供了一些人工数据生成器。
乳腺癌数据集是一个经典且非常容易的二分类数据集。以下表格提供了一些关于数据集的信息:
| 类别 | 2 |
|---|---|
| 每类样本数 | 212(M), 357(B) |
| 样本总数 | 569 |
| 维度 | 30 |
| 特征 | 实数和正数 |
数据准备
在介绍了乳腺癌数据集之后,我们可以分析代码,这将使我们能够逐行对输入数据进行分类。在代码的第一部分,我们导入稍后将要使用的库:
from sklearn import linear_model, datasets, preprocessing
from sklearn.cross_validation import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.neural_network import BernoulliRBM
from pandas_ml import ConfusionMatrix
import numpy as np
import pandas as pd
目前,让我们仅限于导入;我们将在使用时进一步深入研究。首先,我们必须导入数据集;我们将使用sklearn.datasets包来完成:
BC = datasets.load_breast_cancer()
此命令加载并返回乳腺癌wisconsin数据集。sklearn.datasets包嵌入了一些小型玩具数据集。为了评估数据集的规模(n_samples和n_features)对数据统计属性(通常是特征的相关性和信息性)的影响,同时生成合成数据也是可能的。此包还提供了一些辅助工具,用于获取机器学习社区常用的大型数据集,以在来自真实世界的数据上基准测试算法。数据集是一个类似字典的对象,包含所有数据和有关数据的元数据。这些数据存储在数据成员中,它是一个n_samples和n_features数组。在监督问题的情况下,一个或多个响应变量存储在目标成员中。
数据以Bunch对象的形式返回,这是一个类似字典的对象,包含以下属性:
-
data: 要学习的数据 -
target: 分类标签 -
target_names: 标签的含义 -
feature_names: 特征的含义 -
DESCR: 数据集的完整描述
为了确认数据的内容,让我们提取维度:
print(BC.data.shape)
print(BC.target.shape)
结果如下所示:
(569, 30)
(569,)
为了更好地理解操作,我们将这些数据分为X(预测变量)和Y(目标):
X = BC.data
Y = BC.target
在这一点上,我们使用提供给我们pandas库的工具从预测变量中提取一系列统计信息。
pandas是一个开源的、BSD 许可的库,为 Python 编程语言提供高性能、易于使用的数据结构和数据分析工具。
要使用此功能,我们必须将输入数据从numpy.darray转换为pandas数据框:
Xdata=pd.DataFrame(X)
print(Xdata.describe())
结果如下所示:
0 1 2 3 4 \
count 569.000000 569.000000 569.000000 569.000000 569.000000
mean 14.127292 19.289649 91.969033 654.889104 0.096360
std 3.524049 4.301036 24.298981 351.914129 0.014064
min 6.981000 9.710000 43.790000 143.500000 0.052630
25% 11.700000 16.170000 75.170000 420.300000 0.086370
50% 13.370000 18.840000 86.240000 551.100000 0.095870
75% 15.780000 21.800000 104.100000 782.700000 0.105300
max 28.110000 39.280000 188.500000 2501.000000 0.163400
由于空间限制,我们只报告了前五个预测变量的结果。正如我们所看到的,变量有不同的范围。当预测变量有不同的范围时,具有较大数值范围的特征的响应变量的影响可能比具有较小数值范围的特征的影响更大,这反过来又可能影响预测精度。我们的目标是提高预测精度,不允许某个特征由于较大的数值范围而影响预测。因此,我们可能需要将不同特征下的值缩放到一个共同的范围内。通过这个统计过程,可以比较属于不同分布的相同变量,也可以比较不同的变量或以不同单位表示的变量。
记住,在训练机器学习算法之前对数据进行缩放是一种良好的实践。通过缩放,消除了数据单位,使得您能够轻松地比较来自不同位置的数据。
在这个例子中,我们将使用最小-最大方法(通常称为特征缩放)来获取所有缩放数据在范围(0,1)内。实现此目的的公式是:
以下命令执行特征缩放:
X = (X - np.min(X, 0)) / (np.max(X, 0) - np.min(X, 0))
numpy.min() 和 numpy.max() 用于计算每个数据库列的最小值和最大值。
现在让我们将数据分割成训练和测试模型。训练和测试模型是进一步使用模型进行预测分析的基础。给定一个包含预测变量和响应变量的 100 行数据集,我们将数据集分割成方便的比例(比如说 80:20),并分配 80 行用于训练,20 行用于测试。行是随机选择的,以减少偏差。一旦有了训练数据,数据就被输入到机器学习算法中,以获得大规模的通用函数。为了分割数据集,我们将使用 sklearn.model_selection.train_test_split() 函数:
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2, random_state=1)
train_test_split() 函数将数组或矩阵分割成随机的训练和测试子集。前两个参数是 X(预测变量)和 Y(目标)numpy 数组。允许的输入包括列表、numpy 数组、scipy 稀疏矩阵或 pandas 数据框。然后添加了两个选项:
-
test_size:这应该在 0.0 和 1.0 之间,并代表要包含在测试分割中的数据集比例 -
random_state:这是随机数生成器使用的种子
模型拟合
我们之前提到 RBM 经常被用作各种分类问题中的特征。现在是时候看看如何做了。首先要做的是使用 sklearn.neural_network 模块的 BernoulliRBM 函数。
sklearn 是一个用于 Python 编程语言的免费机器学习库。它具有各种分类、回归和聚类算法,包括支持向量机、随机森林、梯度提升、k-means 和 DBSCAN。它旨在与 Python 数值和科学库 NumPy 和 SciPy 交互操作。
在 sklearn 库中,sklearn.neural_network 模块包括基于神经网络的模型。在这个模块中,BernoulliRBM 函数拟合一个伯努利 RBM。返回一个具有二元可见单元和二元隐藏单元的 RBM。参数使用随机最大似然(SML),也称为持久对比散度(PCD)进行估计。首先,我们将设置模型的架构:
RbmModel = BernoulliRBM(random_state=0, verbose=True)
然后,我们将使用训练数据拟合模型:
FitRbmModel = RbmModel.fit_transform(X_train, Y_train)
fit_transform 方法将转换器拟合到 X_train 和 Y_train 上,并可选地使用参数 fit_params,然后返回 X_train 的转换版本。在这种情况下,没有使用可选参数。
如果你还记得,我们的目的是使用Rbm模型提取特征,然后这些特征将被逻辑回归模型用于分类数据。所以,第一部分已经完成——我们已经在FitRbmModel变量中提取了特征。现在是时候创建逻辑回归模型了。为此,我们将使用sklearn.linear_model模块中的LogisticRegression函数,如下所示:
LogModel = linear_model.LogisticRegression()
现在我们将决策函数中特征系数设置为从rbm模型中提取的特征:
LogModel.coef_ = FitRbmModel
现在我们可以构建分类器了。为此,我们将使用sklearn.pipeline模块中的Pipeline函数:
Classifier = Pipeline(steps=[('RbmModel', RbmModel), ('LogModel', LogModel)])
pipeline的目的在于组装可以一起进行交叉验证的多个步骤,同时设置不同的参数。为此,它允许使用步骤的名称来设置各个步骤的参数,就像之前的代码中那样。可以通过将参数名称设置为另一个估计器来完全替换一个步骤的估计器,或者通过将其设置为None来移除一个转换器。现在分类器已经准备好了;我们只需要对其进行训练:
LogModel.fit(X_train, Y_train)
Classifier.fit(X_train, Y_train)
首先,训练逻辑回归模型,然后训练分类器。我们只需要进行预测。回想一下,为了做到这一点,我们有一个未使用的数据集可用:X_test和Y_test。为了检查分类器的性能,我们将预测与真实数据进行比较:
print ("The RBM model:")
print ("Predict: ", Classifier.predict(X_test))
print ("Real: ", Y_test)
下面的截图显示了返回的结果:
最后,为了更好地理解模型性能,我们将计算混淆矩阵。在混淆矩阵中,我们的分类结果与真实数据进行比较。混淆矩阵的优势在于它不仅识别了分类错误的性质,还识别了它们的数量。在这个矩阵中,对角线单元格显示了正确分类的案例数量;所有其他单元格显示了错误分类的案例。要计算混淆矩阵,我们可以使用 pandas 库中包含的ConfusionMatrix()函数,如下所示:
CM = ConfusionMatrix(Y_test, Classifier.predict(X_test))
CM.print_stats()
在下面的代码中,展示了ConfusionMatrix()函数返回的结果:
population: 114
P: 72
N: 42
PositiveTest: 87
NegativeTest: 27
TP: 71
TN: 26
FP: 16
FN: 1
TPR: 0.9861111111111112
TNR: 0.6190476190476191
PPV: 0.8160919540229885
NPV: 0.9629629629629629
FPR: 0.38095238095238093
FDR: 0.1839080459770115
FNR: 0.013888888888888888
ACC: 0.8508771929824561
F1_score: 0.8930817610062893
MCC: 0.6866235389841608
informedness: 0.6051587301587302
markedness: 0.7790549169859515
prevalence: 0.631578947368421
LRP: 2.588541666666667
LRN: 0.022435897435897433
DOR: 115.37500000000003
FOR: 0.037037037037037035
返回了一些信息;特别是,我们可以注意到模型的准确率为 0.85。
Keras 自编码器
正如我们之前所说的,自编码器是一种神经网络,其目的是将输入编码成小维度,并能够重建输入本身。自编码器由以下两个子网络的并集组成:编码器和解码器。此外,还有一个损失函数,它是数据压缩表示和分解表示之间信息损失量的距离。编码器和解码器将与距离函数可微分,因此编码/解码函数的参数可以通过梯度随机优化来最小化重建损失。
加载数据
这是一个包含 60,000 个 28 x 28 灰度图像的手写数字数据库,这些图像是 10 个数字的,还有一个包含 10,000 个图像的测试集。这个数据集已经在 Keras 库中可用。以下图表显示了 MNIST 数据集中 0-8 的图像样本:
如往常一样,我们将逐行分析代码。在代码的第一部分,我们导入稍后将要使用的库:
from keras.layers import Input, Dense
from keras.models import Model
此代码导入了以下函数:
-
使用 Input 函数来实例化一个 Keras 张量。Keras 张量是从底层后端(Theano、TensorFlow 或 CNTK)的张量对象。我们通过添加某些属性来增强它,这些属性允许我们仅通过知道模型的输入和输出就构建一个 Keras 模型。
-
使用 Dense 函数实例化一个常规密集连接神经网络层。
-
使用 Model 函数来定义模型。模型是你可以总结、拟合、评估并用于做出预测的东西。Keras 提供了一个
Model类,你可以用它从创建的层中创建模型。它只需要你指定输入和输出层。
要导入数据集,只需使用此代码:
from keras.datasets import mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
以下元组被返回:
-
x_train, x_test:一个uint8数组,包含灰度图像数据,形状为(num_samples, 28, 28) -
y_train, y_test:一个uint8数组,包含数字标签(范围在 0-9 的整数),形状为(num_samples)
现在我们必须将所有值归一化到 0 到 1 之间。Mnist 图像以像素格式存储,其中每个像素(总共 28 x 28)存储为一个 8 位整数,其值范围从 0 到 255。通常,0 被认为是黑色,255 被认为是白色。介于两者之间的值构成了不同的灰色阴影。现在,为了将所有值归一化到 0 到 1 之间,只需将每个值除以 255。因此,包含值 255 的像素将变为 1,包含 0 的像素将保持原样;介于两者之间的是所有其他值:
x_train = x_train.astype('float32') / 255
x_test = x_test.astype('float32') / 255
通过使用astype()函数,我们已经将输入数据从float32(单精度浮点数:符号位,8 位指数,23 位尾数)转换过来。正如我们所说的,每个样本(图像)由一个 28 x 28 的矩阵组成。为了降低维度,我们将 28 x 28 的图像展平成大小为 784 的向量:
x_train = x_train.reshape((len(x_train), np.prod(x_train.shape[1:])))
x_test = x_test.reshape((len(x_test), np.prod(x_test.shape[1:])))
reshape()函数给数组赋予一个新形状,而不改变其数据。新形状应与原始形状兼容。新形状的第一个维度是len()函数返回的观测数(len(x_train)和len(x_test))。第二个维度代表起始数据的最后两个维度的乘积(28 x 28 = 784)。为了更好地理解这种转换,我们首先打印起始数据集的形状,然后打印转换后数据集的形状:
print (x_train.shape)
print (x_test.shape)
以下是数据集重塑前后的结果:
(60000, 28, 28)
(10000, 28, 28)
(60000, 784)
(10000, 784)
Keras 模型概述
Keras 中有两种类型的模型可用:
-
顺序模型
-
Keras 功能 API
让我们在以下各节中详细查看每个部分。
顺序模型
Sequential 模型是层的线性堆叠。我们可以通过将层实例列表传递给构造函数来创建一个 Sequential 模型,如下所示:
from keras.models import Sequential
from keras.layers import Dense, Activation
model = Sequential([
Dense(32, input_shape=(784,)),
Activation('relu'),
Dense(10),
Activation('softmax'),
])
我们也可以通过 .add() 方法简单地添加层:
model = Sequential()
model.add(Dense(32, input_dim=784))
model.add(Activation('relu'))
此类模型需要知道它应该期望什么输入形状。因此,Sequential 模型的第一个层需要接收有关其输入形状的信息。有几种可能的方法可以实现这一点:
-
向第一个层传递
input_shape参数 -
通过
input_dim和input_length参数指定它们的输入形状 -
向一个层传递
batch_size参数
Keras 功能 API
定义模型的另一种方式是 Keras 功能 API。对于定义复杂模型,如多输出模型、有向无环图或具有共享层的模型,Keras 功能 API 是最佳选择。例如,要定义一个密集连接网络,只需输入以下代码:
from keras.layers import Input, Dense
from keras.models import Model
inputs = Input(shape=(784,))
x = Dense(64, activation='relu')(inputs)
x = Dense(64, activation='relu')(x)
predictions = Dense(10, activation='softmax')(x)
model = Model(inputs=inputs, outputs=predictions)
model.compile(optimizer='rmsprop',
loss='categorical_crossentropy',
metrics=['accuracy'])
model.fit(data, labels)
在下一节中,我们将通过将其应用于我们的示例来深入探讨此类模型。
定义模型架构
现在我们将使用 Keras 功能 API 来构建模型。正如我们之前看到的,首先我们必须定义输入:
InputModel = Input(shape=(784,))
这将返回一个表示我们输入占位符的张量。稍后,我们将使用这个占位符来定义一个 Model。在这个阶段,我们可以向我们的模型架构中添加层:
EncodedLayer = Dense(32, activation='relu')(InputModel)
Dense 类用于定义一个全连接层。我们指定层的神经元数量作为第一个参数(32),使用激活参数(relu)指定激活函数,最后指定层的输入张量(InputModel)。
记住,给定一个输入 x,编码器将其编码到一个变量 z 中,也称为潜在变量。z 通常比 x 的维度小得多;在我们的例子中,我们从 784 压缩到 32,压缩因子为 24.5。
现在,让我们添加解码层:
DecodedLayer = Dense(784, activation='sigmoid')(EncodedLayer)
此层是对输入的损失重建。又一次,我们使用了具有 784 个神经元(输出空间的维度)的 Dense 类,sigmoid 激活函数,以及 EncodedLayer 输出作为输入。现在我们必须如下实例化一个模型:
AutoencoderModel = Model(InputModel, DecodedLayer)
此模型将包括在给定 InputModel(输入)的情况下计算 DecodedLayer(输出)所需的全部层。以下是 Model 类的一些有用属性:
-
model.layers是包含模型图的层的扁平化列表 -
model.inputs是输入张量的列表 -
model.outputs是输出张量的列表
因此,我们必须为训练配置模型。为此,我们将使用 compile 方法,如下所示:
AutoencoderModel.compile(optimizer='adadelta', loss='binary_crossentropy')
此方法配置模型以进行训练。仅使用两个参数:
-
optimizer:字符串(优化器名称)或优化器实例。 -
loss: 字符串(目标函数的名称)或目标函数。如果模型有多个输出,可以通过传递字典或损失列表来使用不同的损失函数。然后,模型将最小化的损失值将是所有单个损失的加和。
我们使用了 adadelta 优化器。这种方法随时间动态调整,仅使用一阶信息,并且计算开销最小,超出了传统的随机梯度下降。该方法不需要手动调整学习率,并且对噪声梯度信息、不同的模型架构选择、各种数据模态和超参数的选择具有鲁棒性。
此外,我们使用了binary_crossentropy作为loss函数。损失函数是计算上可行的函数,表示在分类问题中对预测不准确性的代价。
在这一点上,我们可以训练模型:
history = AutoencoderModel.fit(x_train, x_train,
batch_size=256,
epochs=100,
shuffle=True,
validation_data=(x_test, x_test))
fit方法用于在固定数量的 epochs(在数据集上的迭代)上训练模型。以下是对传递的参数的解释,以便更好地理解其含义:
-
x: 如果模型有一个输入,则为训练数据的 Numpy 数组,或者如果有多个输入,则为 Numpy 数组的列表。如果模型中的输入层有名称,也可以通过将输入名称映射到 Numpy 数组来传递字典。如果从框架原生张量(例如,TensorFlow 数据张量)中提供,则x可以是None(默认)。 -
y: 如果模型有一个输出,则为目标(标签)数据的 Numpy 数组,或者如果有多个输出,则为 Numpy 数组的列表。如果模型中的输出层有名称,也可以通过将输出名称映射到 Numpy 数组来传递字典。如果从框架原生张量(例如,TensorFlow 数据张量)中提供,则y可以是None(默认)。 -
batch_size:整数或None。这是每次梯度更新时的样本数量。如果没有指定,batch_size将默认为32。 -
epochs: 一个整数。这是训练模型的 epoch 数量。一个 epoch 是对整个x和y数据的迭代。注意,与initial_epoch结合使用时,epochs应理解为最终 epoch。模型不是根据 epoch 的数量进行多次迭代训练,而是仅仅训练到 epoch 索引为 epochs 的那个 epoch。 -
shuffle: 一个布尔值,用于决定在每个 epoch 之前是否对训练数据进行洗牌,或者str(用于batch)。batch是处理 HDF5 数据限制的特殊选项;它以批大小块进行洗牌。当steps_per_epoch不是None时,它没有效果。 -
validation_data: 一个元组(x_val和y_val)或元组(x_val,y_val,和val_sample_weights),用于在每个 epoch 结束时评估损失和任何模型度量。模型不会在此数据上训练。validation_data将覆盖validation_split。
返回一个History对象。其history.history属性是记录在连续的 epoch 中训练损失值和指标值,以及验证损失值和验证指标值(如果适用)。
我们的模式现在已准备就绪,因此我们可以使用它来自动重建手写数字。为此,我们将使用predict方法:
DecodedDigits = AutoencoderModel.predict(x_test)
此方法为输入样本(x_test)生成输出预测。运行此示例,你应该会看到每个 100 个 epoch 的消息,打印每个 epoch 的损失和准确率,然后是对训练数据集上训练模型的最终评估。如下所示:
要了解loss函数在 epoch 中的变化情况,可以创建一个在训练和验证数据集上训练 epoch 的损失图。为此,我们将使用以下Matplotlib库:
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('Autoencoder Model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train', 'test'], loc='upper left')
plt.show()
在以下图表中显示了训练和验证数据集上训练 epoch 的损失图:
我们的工作已经完成;我们只需验证获得的结果。我们可以在屏幕上打印出原始的手写数字和从我们的模型重建的数字。当然,我们只会对数据集中包含的 60000 个数字中的某些进行操作;实际上,我们将仅显示前五个。在这种情况下,我们也将使用Matplotlib库:
n=5
plt.figure(figsize=(20, 4))
for i in range(n):
ax = plt.subplot(2, n, i + 1)
plt.imshow(x_test[i].reshape(28, 28))
plt.gray()
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
ax = plt.subplot(2, n, i + 1 + n)
plt.imshow(DecodedDigits[i].reshape(28, 28))
plt.gray()
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
plt.show()
结果如下所示:
如您所见,结果非常接近原始数据,这意味着模型运行良好。
Magenta
2016 年 6 月 1 日,谷歌推出了 Magenta 项目,这是一个旨在通过使用人工智能以自主方式创作艺术和音乐的研究项目。基于 TensorFlow 平台,Magenta 旨在以开源模式在 GitHub 上发布代码,以便开发者能够实现越来越引人注目和先进的结果。
该项目是谷歌大脑团队的一个创意,谷歌大脑团队是谷歌的一个深度学习人工智能研究团队。它将开放式的机器学习研究、系统工程和谷歌规模的计算资源相结合。
Magenta 项目为自己设定了两个雄心勃勃的目标:开发艺术和音乐机器学习,并建立一个对这一主题感兴趣的人们的社区。机器学习长期以来在不同的环境中被使用,特别是在语音识别和语言翻译方面。Magenta 的创建是为了集中活动在之前未探索的领域,如广义上的艺术生成。为此,Magenta 希望创建一个物理场所,所有由相同兴趣(即艺术生成)联合的人可以交流思想和产品。换句话说,一个由艺术家、程序员和机器学习研究者组成的社区。
更多信息,请参考以下 URL 的项目官方网站:magenta.tensorflow.org/.
NSynth 数据集
通过阅读前面的章节,我们现在已经了解到,为了正确训练机器学习算法,需要有一个包含重要数量观察的数据集。最近,由于高质量图像数据集的可用性,生成模型在图像上的使用增加,因此这对应着一个重要的数据集。考虑到这一点,Google Brain 团队推出了 NSynth。这是一个大规模、高质量的音符集,比可比的公共数据集大一个数量级。目标是拥有一个重要的音频数据集,以便开发性能更好的生成模型。
NSynth 数据集由 Jesse Engel 等人介绍在名为《使用 WaveNet 自动编码器进行神经音频合成音符》的文章中。
NSynth 是一个包含 305,979 个音乐音符的音频数据集,每个音符都有独特的音调、音色和包络。对于来自商业样本库的 1,006 个工具,Google Brain 团队生成了 4 秒的 16 kHz 单声道音频片段,称为音符,跨越标准 MIDI 钢琴的每个步骤(21-108)和五种不同的速度(25、50、75、100 和 127)。音符保持前 3 秒,并在最后 1 秒允许其衰减。
Google Brain 团队还根据人类评估和启发式算法的组合,为每个音符标注了三条额外信息:
-
来源:音符乐器的声音产生方法。这可以是声学或电子,对于分别从声学或电子乐器录制乐器的乐器,或合成,对于合成乐器。
-
家族:音符所属的高级别家族。每个乐器恰好属于一个家族。
-
品质:音符的音质。每个音符都标注了零个或多个品质。
NSynth 数据集可以以下两种格式下载:
-
TensorFlow 示例协议缓冲区的序列化 TFRecord 文件,每个 note 包含一个 Example proto
-
包含非音频特征和 16 位 PCM WAV 音频文件的 JSON 文件
整个数据集分为三个集合:
-
训练:包含 289,205 个示例的训练集。乐器与有效集或测试集不重叠。
-
有效:包含 12,678 个示例的验证集。乐器与训练集不重叠。
-
测试:包含 4,096 个示例的测试集。乐器与训练集不重叠。
更多信息和下载数据集,请参考以下 URL 的项目官方网站:magenta.tensorflow.org/datasets/nsynth.
摘要
在本章中,我们探讨了使用神经网络建模的最有趣的研究领域之一。首先,我们看到了无监督学习算法的介绍。无监督学习是一种机器学习技术,它从一系列输入(系统经验)开始,将能够根据共同特征重新分类和组织,以尝试对后续输入进行预测。与监督学习不同,在学习过程中,只向学习者提供未标记的示例,因为类别不是事先已知的,而是必须自动学习。
因此,我们分析了不同类型的生成模型。玻尔兹曼机是一种概率图模型,可以解释为随机神经网络。在实践中,玻尔兹曼机是一个模型(包括一定数量的参数),当应用于数据分布时,能够提供一种表示。该模型可以用来从目标分布(未知分布)的样本中提取重要方面。
自动编码器是一种神经网络,其目的是将输入编码成小维度,并能够重建输入本身。自动编码器的目的不仅仅是执行一种输入的压缩或寻找身份函数的近似;但有一些技术可以让我们指导模型(从减少维度的隐藏层开始)给予某些数据属性更大的重要性。因此,它们基于相同的数据产生不同的表示。
GAN 是由两个联合训练的网络组成的生成模型,称为 生成器 和 判别器。这两个网络之间的动态类似于伪造者和调查者。伪造者试图制作忠实于原作的仿制品,而调查者则试图区分赝品和真品。
然后,我们展示了如何实现一些示例:使用 RBM 进行特征提取和 Keras 的自动编码器。最后,我们介绍了 Nsynth 数据集和 Google Magenta 项目。
第十七章:聊天机器人
聊天机器人的时代已经到来,这是一种新的技术现象,它帮助产生了与机器互动的新方式,从而创造了新的商业机会。聊天机器人是能够通过聊天与用户互动的机器人,能够通过执行极其有限的任务来帮助他们:提供当前账户信息、购票、接收天气预报等。
聊天机器人处理用户呈现的文本,然后根据一组复杂的算法进行响应,这些算法解释和识别用户说了什么。在扣除用户所需的内容后,它根据从上下文中提取的信息确定一组适当的响应。一些聊天机器人提供了一种非常真实的对话体验,其中很难确定代理是机器人还是人类。
聊天机器人也是人工智能带来的最令人兴奋的创新之一。在本章中,在介绍所有这些技术所基于的主要概念之后,我们将介绍构建上下文聊天机器人的方法,并在 GCP 上实现一个简单的聊天机器人端到端应用程序。
涵盖的主题:
-
聊天机器人基础
-
聊天机器人设计技术
-
自然语言处理
-
Google Cloud Dialogflow
-
在 GCP 上构建和实施聊天机器人
在本章结束时,读者将完成对聊天机器人的实战介绍,并学习如何在实施过程中训练上下文聊天机器人。
聊天机器人基础
聊天机器人,或称为 chatbots,是能够通过聊天与人类互动的程序,模拟人类的行为。随后,人类与机器人之间建立对话。自从计算机科学开始发展以来,学者们与其他学科合作,试图通过机器来重现典型的人类认知过程。它们通常用于简单且重复的活动,这些活动可能需要花费大量时间,或者不值得分配人力资源。
考虑到它们的复杂性,显然在这种情况下,我们不能谈论对人类自身行为的令人满意的模拟,但仍然可以开始提及人工智能的概念。
机器人可以执行方案并展示其操作。机器人可以做任何事情,从自动回复消息到允许在线购物。它们可以接收任何类型的新闻,发布天气状况,或展示音乐视频,所有这些都可以通过聊天完成。
有几个平台实现了使用机器人的能力。这些包括:Telegram、Skype、Messenger、Slack、SMS 和电子邮件。机器人允许您使用这些平台——用户已知并用于其他功能(如消息)的应用程序——在它们内部执行最不同的功能,从而节省用户在设备上使用和安装额外应用程序的努力。
聊天机器人历史
聊天机器人的历史比你想象的要早得多。我们回到了 20 世纪中叶的英国,当时艾伦·图灵提出了“机器能思考吗?”这个问题,并提出了一种将智能与进行对话的能力联系起来的测试。从那时起,创建能够以越来越准确的方式模拟人类语言的软件的挑战从未停止。
模仿游戏
在 20 世纪 50 年代,艾伦·图灵撰写了一篇名为计算机与智能的文章,其中讨论了确定机器是否能够思考的标准问题。这个标准基于模仿游戏,其中有一个计算机A,一个人B,以及另一个人类C(审问者)。人类C必须确定A和B的身份。审问者向他们提问,A和B以书面形式回答。当C在判断A的身份时出错,认为它是人类时,计算机A赢得游戏。以下图显示了模仿游戏的方案:
图灵的游戏——尽管有众多批评认为这一标准不足以确定机器是否能够思考——在过去几十年中引发了一场挑战,导致了以越来越准确的方式模拟人类语言的软件的创建。
1990 年,一项名为洛布纳奖的竞赛成立,基于图灵测试,以奖励行为最接近人类思维的计算机。这项竞赛每年举行一次。以下是竞赛的官方网站:www.aisb.org.uk/events/loebner-prize。
Eliza
1966 年,约瑟夫·魏岑鲍姆创建了 Eliza 程序,旨在模仿治疗师和患者在他们初始阶段之间的对话。在程序中,发生了一系列简单的问题交换,这些问题不需要深厚的情感深度。以下截图展示了一个简单的 Eliza 聊天机器人:
Eliza 依赖于使用正则表达式将用户输入转换为输出,以便使对话保持连续性以及明显的连贯性。在其他时候,它不会产生任何上下文观察,而是产生与心理治疗师态度完全一致的内容,例如非常有趣、请、继续或can you elaborate on that?*
帕里
帕里继续留在医疗领域,模拟了患有偏执型精神分裂症的人的行为。它于 1972 年由精神病学家肯尼斯·科比开发,作为他在处理真实患者之前为学生提供的一种实践工具。
在图灵测试的一个轻微变化中,精神科医生被要求阅读 Parry 的记录和真实患者的记录,以区分这两者来自哪里。这在当时是一个令人难以置信的结果;精神科医生只成功地在 48%的时间里识别出行为上的差异。
1973 年 1 月,在国际计算机会议期间,计算机科学先驱 Vint Cerf 决定将机器人带到它们的逻辑结论。使用 ARPANET,他设置了 Eliza 和 Parry 之间的对话。这是一次计算机思维的跨海岸会议;Eliza 位于麻省理工学院,Parry 位于斯坦福大学。以下是从两个机器人之间的对话中摘录的一段简短对话:
-
Eliza:晚上好。告诉我你的问题。请用句号或问号结束输入。
-
Parry:有时候人们让我感到烦躁。
-
Eliza:我不确定我是否完全理解你。
-
Parry:你应该更加注意。
-
Eliza:也许你应该更加注意。
-
Parry:你有权持有自己的观点。
-
Eliza:是什么让你认为我有权持有自己的观点?
-
Parry:仅仅和普通人在一起就让我感到烦恼。
-
Eliza:请继续说。
-
Parry:我之前去过 Bay Meadows 的跑道。
-
Eliza:这对你有什么启示?
-
Parry:我去过赛马。
Jabberwacky
Jabberwacky 是由程序员 Rollo Carpenter 于 1988 年创造的。这个机器人的目标是通过图灵测试。Jabberwacky 能够以幽默的方式模拟人类的声音。目前,该机器人的开发仍在继续,旨在将系统应用于机器人或会说话的宠物,基于声音学习。
这是一个基于机器学习的机器人;实际上,为了与我们互动,它只使用学到的材料,并借用我们的一些智慧,同时增加它的知识。没有硬编码的规则,它完全基于反馈的原则。
Cleverbot 是 Jabberwacky 在 1997 年发布的一个变体,取得了显著成果;2011 年,它参加了印度 IIT Guwahati 的图灵测试,有 59.3%的概率被认为是人类。
Dr. Sbaitso
Dr. Sbaitso 也被设计成模拟能够解决用户情感问题的心理学家的行为,并且可以在使用 MS-DOS 操作系统的个人电脑上使用。它由 Creative Labs 于 1992 年开发,旨在展示声卡生成合成声音的能力。以下截图显示了 Dr. Sbaitso 中的欢迎信息所在的 MS-DOS 窗口:
大部分问题是WHY DO YOU FEEL THAT WAY?。因此避免了更复杂的互动。当他收到他无法理解的句子时,他通常会回答THAT'S NOT MY PROBLEM。
ALICE
人工语言互联网计算机实体 (ALICE) 是基于 自然语言处理 (NLP) 的开源软件,由科学家理查德·S·华莱士于 1995 年设计。Alice 的解释系统基于最小化方法。句子的意义通过特定的关键词或术语(根)来阐述,避免了深入和复杂的分析。Alice 三次赢得了 Loebner 奖:2000 年、2001 年和 2004 年。
SmarterChild
SmarterChild 是一个非常成功的聊天机器人,可在 AOL 即时消息和 MSN 即时消息上使用。由 ActiveBuddy Inc. 于 2001 年开发,被超过 3000 万用户使用。从 SmarterChild 的快速成功中衍生出了面向市场营销的机器人,如 Radiohead、Austin Powers、Intel、Keebler、体育新闻等。
IBM Watson
Watson 是 IBM 在 2006 年开发的人工智能系统,能够回答以自然语言表达的问题。最初,Watson 是为了参加一档名为 Jeopardy! 的美国电视智力竞赛而创建的。然而,在第一次参赛时,它只能回答 35% 的问题。在 IBM 团队进行多次改进后,Watson 于 2011 年再次尝试,这次它成功地击败了智力竞赛的人类冠军。
在游戏中,Watson 在没有连接到互联网的情况下工作,利用了 4 个 terabytes 的磁盘空间。后来,它被用于许多其他完全不同的环境中,例如在纪念斯隆-凯特琳癌症中心管理肺癌治疗决策。
构建机器人
在早期的聊天机器人中,使用了相当简单的算法来分析输入消息并返回输出响应;这些算法旨在通过提供一致的输出响应来模拟计算机对输入中提出的内容的理解。随着时间的推移和技术的发展,越来越多的复杂人工智能方法被创造出来,使得聊天机器人能够建立越来越接近人类真实对话的对话。为了正确设计聊天机器人,从哪些基本点开始是必要的?机器人构建的基本主题包括意图、实体和上下文。在下一节中,我们将分析它们,以了解如何有效地使用它们。
意图
用户的意图是他们的目的,最终目标。例如,订购某物、想要激活用户窗口上的某物、寻找节目,或者只是说再见。聊天机器人应该能够根据从用户消息中检测到的意图执行一些操作。
假设我们想要创建一个为销售 IT 相关产品的商店设计的聊天机器人。作为一个初步程序,有必要考虑聊天机器人在被用户请求后能够执行哪些操作。例如,当用户请求查看商店销售的产品时,聊天机器人需要向用户提供适当的信息,比如:“我想买一个鼠标”。同样,当用户发送消息,如“在罗马找一个商店”时,聊天机器人应该能够定位到该特定位置附近的商店。为了执行这些操作中的每一个,聊天机器人必须能够区分用户的两种意图:搜索产品或销售点。在以下图中,用户表达了两种可能的意图:
从用户消息中检测意图是机器学习领域的一个非常普遍的问题。这是称为文本分类的技术,其中程序的目标是将文档/短语分类到几个类别中,这些类别代表了用户的意图。理解用户的请求是什么是聊天机器人的智能部分,因为自然语言中表达请求的方式有很多。聊天机器人将通过识别最接近的意图来尝试解释用户的请求。当然,这种关联并不总是精确的;事实上,会返回可能的解释的排名。但从这个角度来看,可以通过提供更多相同请求的替代示例来改进答案。
实体
实体是用户消息中包含的相关主题,例如一个物体、一种颜色或一个日期。如果意图是在网页上激活某个功能,用户也可能指明是什么,比如一个按钮或一个窗口。因此,实体是聊天机器人可以识别的关键词。多亏了这些实体,聊天机器人能够识别对话的主题,从而提供有针对性的信息作为输出。在以下图中,聊天机器人识别出了两个可能的实体:
假设我们有一个以下的消息作为输入:我想买一个显示器。很明显,用户想要购买一个 IT 产品,但如果聊天机器人无法识别用户试图购买的产品类型,返回的信息将关于所有类型的 IT 产品,其中许多对用户来说并不感兴趣。如果聊天机器人能够检测到用户试图购买显示器,那么它将只返回关于这种 IT 产品的信息,从而减少可用的选项。这对用户是有益的。
上下文
聊天机器人是为了简化并自动化流程而创建的。因此,如果一个聊天机器人使人类以前简单管理的过程变得复杂,那么它就失败了。例如,假设你搜索天气信息。如果你用手机请求信息,服务提供商可能会使用你的电话号码查找你的账户信息,就像你的地址一样。同样的程序应该适用于聊天机器人。如果你旨在创建一个与使用手机相当或更有效率或更无效的机器人,那么聊天机器人的上下文就极其重要。
当我们谈论聊天机器人的上下文时,我们指的是机器人能够识别它已经知道的信息,并且能够只查找它需要提供适当解决方案的未知信息。在这种情况下,它必须提供未来几天该地区的天气信息。如果这个天气信息聊天机器人适当地使用上下文,那么它就不应该询问它已经知道的信息。由于上下文的维护,使用机器人已经持有的信息,使得返回信息的程序变得更快。
一旦意图和实体被放入系统中,对话的逻辑流程就创建了,因为区分对话和简单的常见问题解答(FAQs)的是上下文。多亏了上下文,我们才能将用户的当前输入与之前提到的输入联系起来。
上下文在聊天机器人和用户之间来回传递。维护对话从一回合到下一回合的上下文是聊天机器人的责任。上下文包括与每个用户对话的唯一标识符,以及每次对话回合增加的计数器。如果我们不保留上下文,每一轮输入似乎都是一次新对话的开始。
聊天机器人
我们不需要从头开始设计聊天机器人。事实上,我们可以利用程序员在开发我们之前章节中分析的应用程序过程中积累的经验。收集到的所有信息代表了一种知识,我们可以从中提取对我们应用程序有用的线索。
必要要求
首先,我们可以看看一个优秀的聊天机器人必须满足哪些要求,以确保它提供服务的成功。以下列表提到了我们必须记住的聊天机器人设计的一些关键要素:
-
保证用户最小手动努力:这是聊天机器人设计中的起点。为了服务的成功,必须通过最小化手动干预来陪伴用户的选择。这是通过大幅减少帮助机器人确定最佳解决方案所需的触摸、按键或鼠标点击次数来实现的。为此,你需要确保大多数选项都由同一个聊天机器人提供,用户只需选择正确的选项。这样,在用户与聊天机器人的交互中可以节省大量时间。
-
预测正确选项:为了确保系统只显示与该上下文相关的选项,必须通过一系列选择提供正确的选项。为了实现这一点,系统必须能够识别用户的需求。用户需求必须通过最少的提问和用户的手动努力来识别。
-
聊天机器人的定制:这是根据服务用户的特点构建不同用户聊天机器人交互的可能性。例如,系统可以记住用户档案、之前的交互、系统中其他用户的交互、当前上下文和环境知识。理解用户及其当前可能需要什么,必须将这些属性与其他属性一起理解。
文本的重要性
在应用任何文本解释策略之前,必须进行一系列的加工。特别是,以下阶段很重要:
-
文本清理:文本被清理掉所有可能改变后续分析结果的因素(例如,消息开头和结尾的空格)
-
验证文本字符:检查文本是否包含等同于其他可能使后续分析无效的字符
-
文本规范化:将大写字母转换为小写字母,这样用大写字母而不是小写字母写的同一个单词将被以相同的方式解释(这种方法并不总是最优的,因为有时大写字母可能具有歧视性价值)
单词转换
这种技术已被 Eliza 类型的聊天机器人广泛使用。它包括重新表述输入消息以生成相应的输出。例如,如果用户写道 you are a chatbot,聊天机器人的回答将是 so you think I'm a chatbot。
使用这种技术进行的替换主要涉及人称代词(you → me)和动词(you are → I am),因此将所有第一人称形式转换为第二人称形式,反之亦然。
检查值与模式的一致性
任何使用过文字处理程序的人都必须面对在括号内搜索文本字符串的问题。也许我们并不知道,我们遇到了模式匹配问题。模式匹配是一种检查标记序列是否具有某种模式的过程,即一组遵循特定模式的字符。
词汇标记,或简称为标记,是一个具有分配和因此识别的意义的字符串。它由一个标记名称和一个可选的标记值组成的对构成。标记名称是词汇单元的类别。
至于聊天机器人对输入的解释,模式匹配对于识别某些消息集是有用的。例如,通过模式匹配,你可以对包含单词hello或hi的所有消息回答Hello!;或者你可以通过检查最后一个标记是否为*?*来识别消息是问题类型。
正则表达式是模式识别的一个非常有用的工具,它提供了一种识别字符串集合的符号系统。
维护上下文
存储上下文是一种策略,用于跟踪之前说过的话,并能够将其用于对话。当聊天机器人的响应不能仅基于用户发送的最后一条消息,而必须从某些之前的消息中获取信息时,这变得必要。
为了更好地理解上下文管理的作用,让我们举一个例子:
-
用户:我的名字是 Giuseppe.
-
聊天机器人:好的,Giuseppe。
-
用户:我的名字是什么?
-
聊天机器人:你之前在叫你 Giuseppe 之前告诉过我。
如果用户尚未声明他的名字,聊天机器人的回答将不得不类似于:
-
用户:我的名字是什么?
-
聊天机器人:你仍然没有告诉我你的名字。
因此,理解如何管理上下文以形成答案是很简单的。这对于使聊天机器人看起来不那么机械而更有人性化至关重要。
记住之前的消息对于检测用户重复发送消息是有用的,或者它可以通过在选择下一个之前检查最后一条消息的值来防止相同的聊天机器人发送相同的消息。
聊天机器人架构
聊天机器人的主要模块是对话管理器。该模块控制人机交互的流程。它接收用户的请求作为输入,并决定系统的响应应该是什么。它将以某种形式记住对话上下文,例如通过键值对,来管理用户和系统之间多步骤的对话。
为了使对话管理器能够为用户提出的请求选择正确的答案,有必要理解用户意图。在最能理解人类语言的先进聊天机器人中,用户的表达将被转换为包含用户意图和实体的语义表示。这项操作将由自然语言理解模块执行。该模块必须事先经过训练,以理解开发者先前识别的一系列用户意图。该模块基于自然语言理解(NLU)组件。
在语音输入的情况下,系统还必须配备一个可以将输入转换为文本的语音识别模块,然后再将其传递到自然语言理解模块。在操作结束时,系统响应(输出)必须首先通过语音合成器模块处理,该模块将系统的文本响应转换为语音。
当用户输入被理解时,对话管理器会采取行动。为了执行操作或生成响应,对话管理器从数据源检索所需信息。之后,响应生成组件生成响应消息并发送回用户。以下是一个聊天机器人架构方案图:
为了跟踪上下文,对话管理器保持对话状态,以了解请求是否与之前的对话相关,或者是否将新主题引入对话。
自然语言处理
自然语言处理(NLP)是计算语言学的一个领域,它处理计算机与自然语言之间的交互。
计算语言学通过计算机方法处理自然语言的分析和处理。它侧重于发展自然语言功能的描述性形式化,以便它们可以被转换成计算机可以执行的程序。
传统上,计算机要求你通过编程语言与之交互,因此它应该是一种精确、无歧义且高度结构化的通信方式,使用有限数量的已知命令。相反,人类语言并不精确;它通常是模糊的,语言结构可能取决于许多不同的变量,例如方言和各种社会环境。
因此,NLP 是一个极其重要的领域,因为它研究和试图解决计算机在解释或分析人类语言时遇到的全部困难。人类语言的众多歧义使得算法理解人类语言特别困难。要理解一个话语,有必要对现实和周围世界有更广泛的知识。实际上,仅仅了解每个单词的含义并不足以正确解释句子的信息;相反,它可能导致矛盾和无意义的交流。
自然语言的研究是通过一系列精确顺序的步骤进行的,这些步骤以不断增长的语义价值为特征,如下所述:
-
发音并解码一种语言的声音,使我们能够识别声音和字母。
-
了解一种语言中的单词,它们的结构(复数/单数)以及它们的组织(名词、动词和形容词)。词汇分析识别构成该语言的词汇并从词典中找到定义。形态分析识别复数/单数结构、动词方式和动词时间;并为每个单词分配其自身的形态类别,即形容词、名词和动词。
-
在复杂成分(词性)中组合单词。句法识别词性作为主语、谓语、补语或具有单一意义的单词组,如热狗,或具有完整句法树派生的名词和动词部分。
-
将意义赋予简单和复杂的语言表达。语义学试图根据上下文识别单词的意义。
-
在适合交流目的的语境、情境和方式中使用句子。实用主义者观察语言是如何以及为什么目的被使用的,区分它是叙述、对话、隐喻等问题。
然后将获得的结果应用于 NLP 的两个主要类别:
-
自然语言生成(NLG),它涉及将数据库中的信息转换为人类可读语言
-
NLU,它将人类语言转换为程序易于操作的形式
NLP 面临许多问题:
-
语音分割:将语音轨道转换为具有完整意义的字符和单词
-
文本分割:识别用汉字而不是字母书写的文本中的单个单词(如中文、日语、泰语等)
-
词性标注:识别句子中的语法元素,如名词、形容词、动词、代词
-
词义消歧:从上下文中推断出通常用于表示多个概念的术语的意义
-
不完整或不规则输入:识别和纠正任何地方口音、打字错误或由光学字符识别工具产生的错误
语言领域阐述的困难也可以通过考虑自然语言本身最明显的特征来解释:
-
灵活性,因为它使用不同的方式来确认同一个事实
-
含糊性,因为同一个陈述可以有多个含义
-
动态性,由于新词的不断创造
正是因为这些特殊性,自然语言的理解通常被认为是一个 AI 完全问题,即一个其解决方案相当于创建 AI 本身的问题。实际上,理解文本需要理解与之相关的概念,因此需要广泛的知识和对现实的深刻理解,以及强大的操纵能力。
AI 完全被定义为最困难的问题;也就是说,它们呈现的计算问题等同于解决 AI 本身的问题——使计算机像人一样智能。因此,AI 完全这个术语表明了一个不会通过简单的特定算法解决的问题。
对于人类来说,语言理解是心理过程的结果,这个过程在机器中无法复制;此外,语言是人与人之间沟通和互动的一种形式,它反映了意义的表面,并使人们能够相互理解。然而,无论计算机的软件多么复杂,它仍然基于先验确定的程序。
自然语言理解
NLU 包括阅读用自然语言表达的文字;通过赋予其中存在的术语、句子和段落以意义来确定其含义,并通过对这些元素进行推理来揭示它们的显性或隐性属性。特别是,在建模文本表示时最突出的问题之一是捕捉概念之间的语义关系。为了解决这个任务,文献中提出了几种方法,其中一些提到了访问外部知识库。另一些方法则构建语义分布空间,分析文本集合的内容,而不使用先验知识。
在 NLU 的定义下,计算机应用的范围非常广泛,从简单的操作,如给机器人的简短命令,到复杂的操作,如对文本的全面理解。在现实世界中,现在广泛使用基于 NLU 的算法;例如,对电子邮件中的文本进行分类以分配标签,不需要对文本有彻底的理解,但它需要处理许多词汇项。
文本输入的拆解和解析过程非常复杂,因为输入中出现了未知和意外的特征,并且在输出语言时需要确定适用于它的适当句法和语义方案(这些因素是预先确定的)。在下面的图中,你可以看到 NLU 过程的流程图:
NLU(自然语言理解)帮助我们分析输入文本的语义特征并从内容中提取元数据,例如类别、概念、情感、实体、关键词、元数据、关系和语义角色。通过 NLU,开发者将文本翻译成机器可读的正式表示,使其内容的相关方面明确化。
Google Cloud Dialogflow
从聊天机器人到物联网设备,广泛使用的对话式虚拟助手应用能够以最自然的方式与人类语言互动,这表明需要创建更多引人入胜的个人交互。挑战是双重的:不仅需要识别和传输优化的基本信息,还需要涉及用户并帮助他们实现目标。这要求自动系统尽可能适应其目标用户的语言,这得益于数据分析和机器学习及人工智能技术的力量。
Dialogflow 概述
Google 通过 Dialogflow 应对这一挑战,Dialogflow 是一个基于机器学习的创建语音和文本对话应用的平台。它支持 14 种语言,并且可以通过服务 API 与主要的聊天平台集成,如 Google Assistant、Facebook Messenger、Slack、Skype、Telegram 以及其自身的应用。
最近,鉴于开发者对在标准版本中添加商业功能的大量需求,Google 宣布推出 Dialogflow 企业版,目前处于测试阶段。
以下是一些 Dialogflow 提供的功能:
-
基于机器学习的对话交互:Dialogflow 使用自然语言处理(NLP)来创建更快的对话体验并快速迭代。只需提供一些用户可能说的话的例子,Dialogflow 就会创建一个特定的模型,该模型可以学习激活哪些动作以及提取哪些数据以提供最相关和最准确的答案。
-
一次创建,处处部署:使用 Dialogflow 创建对话应用,并将其部署到您的网站、应用或包括 Google Assistant 和其他流行消息服务在内的 32 个不同平台。Dialogflow 还包括多语言支持和多语言体验,以覆盖全球各地的用户。
-
高级满足选项:满足意味着对用户所说内容的相应动作,例如处理食品订单或激活对用户问题的正确答案。为此,Dialogflow 允许您连接到任何 Webhook,无论它托管在公共云还是本地。Dialogflow 集成的代码编辑器允许您直接在 Dialogflow 控制台中编码、测试和实施这些动作。
-
语音识别与语音识别:Dialogflow 使对话应用能够响应命令或语音对话。它可以通过一个 API 调用实现,该调用结合了语音识别和自然语言理解。
除了理解自然语言之外,Dialogflow 的灵活性也允许开发者超越决策结构和功能,例如与云函数的深度集成,可以直接将其界面中的基本无服务器脚本写入其中,这使得 Dialogflow 与一些竞争对手区分开来。Dialogflow 还简化了与其他应用程序的连接,无论它们托管在哪里。如果你想要将你的对话应用与你的订单和发货系统集成,这将是必需的。
Dialogflow 基本元素
在详细分析构建聊天机器人的实际案例之前,建议详细分析 Dialogflow 的基本元素。我们现在将介绍在聊天机器人构建中最常用的元素。
代理
代理是一个响应特定任务的程序。它可以是一个负责酒店房间预订的接待员。或者它可能是一个了解所有产品和价格表的在线商店的商业专家。或者它可能是一个为我们购买的家用电器提供合格技术支持的合格技术支持人员。或者它可能是汽车的机载计算机。
重要的是,一个智能代理有一个特定的目的和有限的知识库;我们并不感兴趣与那个负责航班预订的机器人下棋,而且无论如何它也无法做到这一点。
使用 Dialogflow,创建代理非常简单;只需访问服务的初始页面,点击创建代理按钮,给它起一个名字,如下面的截图所示:
要访问 Dialogflow,只需使用 URL dialogflow.com/。你可以注册或使用 Google 账户并登录。
意图
意图是最终用户可以向代理提出的问题。预订酒店房间是一个意图;另一个意图是咨询午餐时间,或者取消已经做出的预订。
理解用户的请求是什么是代理的智能部分,因为请求可以用自然语言以多种方式表达,即我们人类所谈论的内容。
代理将通过识别最接近的意图来尝试解释用户的请求。自然地,这种关联并不总是精确的;事实上,会返回可能的解释的排名。但是,从这个角度来看,我们可以通过提供更多相同请求的替代示例来改进答案。此外,还可以应用机器学习算法来从之前的答案中学习。
实体
如果一个意图与请求匹配,实体对应于细节。在预订酒店房间时,您需要知道确切的日期、将入住的人或用户的请求。从代理的设计角度来看,像酒店房间这样的实体被定义,并包含所有必要的细节。
Dialogflow 有一系列已创建的系统实体,这些实体有助于管理更简单的概念(例如,日期)。开发者可以定义一系列实体(开发实体)以概括代理的行为。最后,最终用户为每个请求创建一个实体。开发实体可以具有由列表确定的值、执行映射或由其他实体组成,等等。
动作
到目前为止,我们处理的是对用户请求的解释;现在则是回答的问题。实际上,这个概念更为广泛;一旦我们理解了一个请求,我们就可以满足它,例如,预订房间、开票或与餐厅沟通。但如果我们在制作聊天机器人,答案可能将对应于动作。
一个动作对应于当用户的输入触发特定意图时,应用程序将采取的步骤。动作的名称及其参数都在意图的动作部分定义。
上下文
客人何时到达酒店?你吃了什么?这些短语在没有上下文的情况下是没有意义的,但在更广泛的对话上下文中变得可以理解;这就是上下文的作用。从上下文中收集参数的语法非常简单:
# context_name.parameter_name
使用 Dialogflow,上下文保持时间为 10 分钟或五次请求。
使用 Dialogflow 构建聊天机器人
在分析了 Dialogflow 的主要组件之后,现在是时候关注实际应用了。实际上,我们将创建一个简单的聊天机器人,帮助用户检索关于世界上最美丽城市的天气信息。
首先要做的是创建代理,即包含您想要向用户提供的意图、实体和答案的项目。意图是收集用户请求(使用实体)并指导代理相应响应的机制。对于不包含对话外收集信息的简单答案,您可以直接在意图中定义答案。您可以使用自己的逻辑和 Webhook 来执行更复杂的响应。
Webhook 是调用执行要执行的动作的代码的 URL。与其它环境不同,Dialogflow 还允许您使用 HTTP 协议(而不仅仅是 HTTPS 协议)。
代理创建
要创建代理,请执行以下步骤:
-
如果您还没有 Dialogflow 账户,请注册。如果您已有账户,请登录。
-
点击左侧导航中的“创建代理”并填写字段。以下窗口将被打开:
在此窗口中,我们需要设置一些参数:
-
-
代理的名称。
-
代理的主要语言。
-
时区设置。日期和时间使用此时区进行解析。
-
Google 项目。这使 Cloud functions、Google 动作和权限管理成为可能。
-
已有一系列代理(预构建代理)可用于某些类型的请求,可以根据您的需求进行定制和丰富。可用的代理数量取决于语言;在英语中,有超过 30 个不同的代理可用。
- 点击保存按钮。
意图定义
如我们之前所说,用户的意图是他们的目的,最终目标。例如,订购某物、想要在用户窗口上激活某物、寻找节目,或者只是说再见。聊天机器人应该能够根据从用户消息中检测到的意图执行一些操作。
要创建一个意图,请点击“意图”旁边的加号图标;以下窗口将被打开:
在此窗口中,我们需要设置一些参数:
-
意图名称:意图的名称。
-
上下文:这是用于管理对话流程的。
-
事件:这是一个允许您通过事件名称而不是用户查询来调用意图的功能。事件可以被外部服务用来触发 Dialogflow 意图,例如,Google Assistant 的内置意图。
-
训练短语:必须以自然语言报告识别意图的短语。您可以使用示例(示例模式由“图标”识别)和模板(模板模式由“@”图标识别)。还提供了其他句子的示例,越多,代理将越智能,或者他将能够更好地识别用户请求并解决歧义。
-
动作和参数:这指定了要执行的可能动作及其可以从对话中提取的参数。
-
响应:当识别到意图时,必须报告返回给用户的答案。为了使其更人性化,您可以输入相同答案的不同变体。答案也可以参数化,并且根据所使用的集成,它们可以由丰富的消息组成。
-
履行:调用网络服务以连接您的后端。将意图、参数和上下文发送到您的云函数或网络服务。执行必要的逻辑并以书面、口语或视觉响应进行响应。
当插入示例句子时,它会自动标记,识别收集为实体的部分。一个代理总是包含一个默认的回退意图,收集所有没有识别到其他意图的情况。
我们想要插入的第一个意图的目的是指定与我们的天气预报相关的位置。我们说意图代表用户的意图,因此我们需要思考用户可能会提出哪些问题以获取天气预报。我们需要不同的意图,因为询问相同内容的方式有很多。识别意图的过程是将用户可以用来表达意图的所有可能方式映射出来。我们可能期望从用户那里得到的要求应在训练短语部分中指定。为了开始,让我们插入以下短语:
-
罗马的天气怎么样 -
罗马的天气怎么样 -
罗马的天气 -
罗马天气预报
要插入单个短语,只需在训练短语文本字段中添加用户表达式,然后按Enter键添加另一个短语。我们会注意到句子将被添加到我们的意图声明中。特别是,我们可以看到单词 Rome 被突出显示。这意味着它被标记为分配给现有城市实体的参数,如下面的截图所示:
我们继续插入句子。在定义位置之后,还需要定义时间。很明显,用户将需要知道特定日期的预测,例如今天或明天。然后我们也包括以下短语:
-
今天天气怎么样 -
明天的天气预报 -
罗马今天的天气预报
如前所述,时间参数也被突出显示,这次颜色不同。最后一句很有趣,因为它包含了一个date参数和一个geo-city参数,如下面的截图所示:
要开始,我们保留其他字段不变,只关注聊天机器人将提供给用户的答案。到目前为止,我们还没有考虑任何外部参考来检索用户请求的信息。这意味着至少现在,我们不得不插入这样的模糊答案:
-
很抱歉,我现在没有这个信息 -
日期为$date 的预报不可用 -
在$geo-city 的日期为$date 的天气预报不可用
在最后两个短语中,我们插入了以下参考实体:$date和$geo-city。因此,当代理响应时,它会考虑收集到的参数值,并使用包含这些值的回复。
一旦完成,我们点击保存按钮。以下消息出现在窗口的右下角:
-
意图已保存
-
代理训练开始
-
代理训练完成
意义很明确。既然你的代理能够理解用户的基本请求,现在就尝试一下你到目前为止所做的一切。要尝试新创建的代理,我们可以使用右上角控制台中的相应框。为此,我们只需输入一个请求。让我们通过输入与“训练短语”部分给出的示例略有不同的请求来测试我们的代理。例如,我们询问:“今天罗马的天气怎么样”。之后,我们按下Enter键,随后返回以下窗口:
我们可以理解那些未能恢复搜索信息的用户的挫败感,但至少现在我们可以满意,因为代理已经正确理解了问题并提供了合理的答案。记住,至少目前,预报数据是不可用的,这就是代理所说的。此外,正如预期的那样,代理已经识别出两个参考实体,并重新使用它们来构建响应。
摘要
在本章中,我们发现了聊天机器人的神奇世界。聊天机器人是机器人,通过与用户进行聊天互动,能够通过执行极其有限的任务来帮助他们:提供当前账户的信息、购票、接收天气预报等。
首先,我们研究了该主题的基本概念,从 20 世纪 50 年代聊天机器人的历史开始,包括艾伦·图灵的努力以及各种随后的聊天机器人实现,它们完善了基本概念。Eliza、Parry、Jabberwacky、Dr. Sbaitso、ALICE、SmarterChild 和 IBM Watson 是最重要的例子。随着时间的推移和技术的发展,越来越多的复杂人工智能方法被创造出来。
在介绍基本概念之后,我们专注于聊天机器人的设计技术,然后转向分析聊天机器人的架构。我们探讨了自然语言处理(NLP)和自然语言理解(NLU)的有趣领域。
在本章的最后部分,我们介绍了基于机器学习的语音和文本对话应用平台 Google Cloud Dialogflow。它支持 14 种语言,可以与主要的聊天平台集成。最后,我们创建了一个简单的聊天机器人,帮助用户获取世界上最美城市——罗马的天气预报。这至少是一个心灵旅行的好机会。