首页 > 其他分享 >神经网络的学习--深度学习

神经网络的学习--深度学习

时间:2024-09-13 18:24:51浏览次数:3  
标签:函数 -- 学习 神经网络 参数 np 数据

本章的主题是神经网络的学习。这里所说的“学习”是指从训练数据中
自动获取最优权重参数的过程。本章中,为了使神经网络能进行学习,将导
入损失函数这一指标。而学习的目的就是以该损失函数为基准,找出能使它
的值达到最小的权重参数。为了找出尽可能小的损失函数的值,本章我们将
介绍利用了函数斜率的梯度法。

神经网络的特征就是可以从数据中学习。所谓“从数据中学习”,是指
可以由数据自动决定权重参数的值。这是非常了不起的事情!因为如果所有
的参数都需要人工决定的话,工作量就太大了。在第 2 章介绍的感知机的例
子中,我们对照着真值表,人工设定了参数的值,但是那时的参数只有 3 个。
而在实际的神经网络中,参数的数量成千上万,在层数更深的深度学习中,
参数的数量甚至可以上亿,想要人工决定这些参数的值是不可能的。本章将
介绍神经网络的学习,即利用数据决定参数值的方法,并用 Python 实现对
MNIST 手写数字数据集的学习。

对于线性可分问题,第 2 章的感知机是可以利用数据自动学习的。
根据“感知机收敛定理”,通过有限次数的学习,线性可分问题是可
解的。但是,非线性可分问题则无法通过(自动)学习来解决。

4.1.1 数据驱动
数据是机器学习的命根子。从数据中寻找答案、从数据中发现模式、根
据数据讲故事......这些机器学习所做的事情,如果没有数据的话,就无从谈
起。因此,数据是机器学习的核心。这种数据驱动的方法,也可以说脱离了
过往以人为中心的方法。
通常要解决某个问题,特别是需要发现某种模式时,人们一般会综合考
虑各种因素后再给出回答。“这个问题好像有这样的规律性?”“不对,可能
原因在别的地方。”——类似这样,人们以自己的经验和直觉为线索,通过反
复试验推进工作。而机器学习的方法则极力避免人为介入,尝试从收集到的
数据中发现答案(模式)。神经网络或深度学习则比以往的机器学习方法更能
避免人为介入。
现在我们来思考一个具体的问题,比如如何实现数字“5”的识别。数字
5 是图 4-1 所示的手写图像,我们的目标是实现能区别是否是 5 的程序。这个
问题看起来很简单,大家能想到什么样的算法呢?


图 4-1 手写数字 5 的例子:写法因人而异,五花八门
如果让我们自己来设计一个能将 5 正确分类的程序,就会意外地发现这
是一个很难的问题。人可以简单地识别出 5,但却很难明确说出是基于何种
规律而识别出了 5。此外,从图 4-1 中也可以看到,每个人都有不同的写字习惯,
要发现其中的规律是一件非常难的工作。
因此,与其绞尽脑汁,从零开始想出一个可以识别 5 的算法,不如考虑
通过有效利用数据来解决这个问题。一种方案是,先从图像中提取特征量,

再用机器学习技术学习这些特征量的模式。这里所说的“特征量”是指可以
从输入数据(输入图像)中准确地提取本质数据(重要的数据)的转换器。图
像的特征量通常表示为向量的形式。在计算机视觉领域,常用的特征量包括
SIFT、SURF 和 HOG 等。使用这些特征量将图像数据转换为向量,然后对
转换后的向量使用机器学习中的 SVM、KNN 等分类器进行学习。
机器学习的方法中,由机器从收集到的数据中找出规律性。与从零开始
想出算法相比,这种方法可以更高效地解决问题,也能减轻人的负担。但是
需要注意的是,将图像转换为向量时使用的特征量仍是由人设计的。对于不
同的问题,必须使用合适的特征量(必须设计专门的特征量),才能得到好的
结果。比如,为了区分狗的脸部,人们需要考虑与用于识别 5 的特征量不同
的其他特征量。也就是说,即使使用特征量和机器学习的方法,也需要针对
不同的问题人工考虑合适的特征量。
到这里,我们介绍了两种针对机器学习任务的方法。将这两种方法用图
来表示,如图 4-2 所示。图中还展示了神经网络(深度学习)的方法,可以看
出该方法不存在人为介入。
如图 4-2 所示,神经网络直接学习图像本身。在第 2 个方法,即利用特
征量和机器学习的方法中,特征量仍是由人工设计的,而在神经网络中,连
图像中包含的重要特征量也都是由机器来学习的。

这个地方我们不难去发现

正常我的人的思维就是由大脑对一个事物进行相关的思考去寻找出相关的答案

机器学习是要对于人想到的相关的数据和特征进行学习,这样就使得我们在机器学习中寻找到答案

而深度学习是依靠于神经网络进行相关的学习的

从人工设计规则转变为由机器从数据中学习:没有人为介入的方块用灰色表示
人想到的算法 答案
答案
答案
人想到的特征量
(SIFT、HOG等)
神经网络
(深度学习)

深 度 学 习 有 时 也 称 为 端 到 端 机 器 学 习(end-to-end machine
learning)。这里所说的端到端是指从一端到另一端的意思,也就是
从原始数据(输入)中获得目标结果(输出)的意思。

神经网络的优点是对所有的问题都可以用同样的流程来解决。比如,不
管要求解的问题是识别 5,还是识别狗,抑或是识别人脸,神经网络都是通
过不断地学习所提供的数据,尝试发现待求解的问题的模式。也就是说,与
待处理的问题无关,神经网络可以将数据直接作为原始数据,进行“端对端”
的学习。

4.1.2 训练数据和测试数据
本章主要介绍神经网络的学习,不过在这之前,我们先来介绍一下机器
学习中有关数据处理的一些注意事项。
机器学习中,一般将数据分为训练数据和测试数据两部分来进行学习和
实验等。首先,使用训练数据进行学习,寻找最优的参数;然后,使用测试
数据评价训练得到的模型的实际能力。为什么需要将数据分为训练数据和测
试数据呢?因为我们追求的是模型的泛化能力。为了正确评价模型的泛化能
力,就必须划分训练数据和测试数据。另外,训练数据也可以称为监督数据。
泛化能力是指处理未被观察过的数据(不包含在训练数据中的数据)的
能力。获得泛化能力是机器学习的最终目标。比如,在识别手写数字的问题
中,泛化能力可能会被用在自动读取明信片的邮政编码的系统上。此时,手
写数字识别就必须具备较高的识别“某个人”写的字的能力。注意这里不是“特
定的某个人写的特定的文字”,而是“任意一个人写的任意文字”。如果系统
只能正确识别已有的训练数据,那有可能是只学习到了训练数据中的个人的
习惯写法。
因此,仅仅用一个数据集去学习和评价参数,是无法进行正确评价的。
这样会导致可以顺利地处理某个数据集,但无法处理其他数据集的情况。顺
便说一下,只对某个数据集过度拟合的状态称为过拟合(over fitting)。避免
过拟合也是机器学习的一个重要课题。

这个问题在我之前的博客中是所提及到的

https://blog.csdn.net/Darling912/article/details/141719430?spm=1001.2014.3001.5501

可以去看一下这篇文章会更加的详细

4.2 损失函数
如果有人问你现在有多幸福,你会如何回答呢?一般的人可能会给出诸
如“还可以吧”或者“不是那么幸福”等笼统的回答。如果有人回答“我现在
的幸福指数是 10.23”的话,可能会把人吓一跳吧。因为他用一个数值指标来
评判自己的幸福程度。
这里的幸福指数只是打个比方,实际上神经网络的学习也在做同样的事
情。神经网络的学习通过某个指标表示现在的状态。然后,以这个指标为基
准,寻找最优权重参数。和刚刚那位以幸福指数为指引寻找“最优人生”的
人一样,神经网络以某个指标为线索寻找最优权重参数。神经网络的学习中
所用的指标称为损失函数(loss function)。这个损失函数可以使用任意函数,
但一般用均方误差和交叉熵误差等。
损失函数是表示神经网络性能的“恶劣程度”的指标,即当前的
神经网络对监督数据在多大程度上不拟合,在多大程度上不一致。
以“性能的恶劣程度”为指标可能会使人感到不太自然,但是如
果给损失函数乘上一个负值,就可以解释为“在多大程度上不坏”,
即“性能有多好”。并且,“使性能的恶劣程度达到最小”和“使性
能的优良程度达到最大”是等价的,不管是用“恶劣程度”还是“优
良程度”,做的事情本质上都是一样的。
4.2.1 均方误差
可以用作损失函数的函数有很多,其中最有名的是均方误差(mean squared
error)。均方误差如下式所示。
(4.1)
这里,y k 是表示神经网络的输出,t k 表示监督数据,k 表示数据的维数。

损失函数是表示神经网络性能的“恶劣程度”的指标,即当前的
神经网络对监督数据在多大程度上不拟合,在多大程度上不一致。
以“性能的恶劣程度”为指标可能会使人感到不太自然,但是如
果给损失函数乘上一个负值,就可以解释为“在多大程度上不坏”,
即“性能有多好”。并且,“使性能的恶劣程度达到最小”和“使性
能的优良程度达到最大”是等价的,不管是用“恶劣程度”还是“优
良程度”,做的事情本质上都是一样的。

均方误差
可以用作损失函数的函数有很多,其中最有名的是均方误差(mean squared
error)。均方误差如下式所示。

这里,y k 是表示神经网络的输出,t k 表示监督数据,k 表示数据的维数。

比如,在 3.6 节手写数字识别的例子中,y k、tk 是由如下 10 个元素构成的数据。
>>> y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
>>> t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
数组元素的索引从第一个开始依次对应数字“0”“1”“2”...... 这里,神
经网络的输出 y 是 softmax 函数的输出。由于 softmax 函数的输出可以理解为
概率,因此上例表示“0”的概率是 0.1,“1”的概率是 0.05,“2”的概率是 0.6
等。t 是监督数据,将正确解标签设为 1,其他均设为 0。这里,标签“2”为 1,
表示正确解是“2”。将正确解标签表示为 1,其他标签表示为 0 的表示方法称
为 one-hot 表示。
如式(4.1)所示,均方误差会计算神经网络的输出和正确解监督数据的
各个元素之差的平方,再求总和。现在,我们用 Python 来实现这个均方误差,
实现方式如下所示。

数组元素的索引从第一个开始依次对应数字“0”“1”“2”...... 这里,神
经网络的输出 y 是 softmax 函数的输出。由于 softmax 函数的输出可以理解为
概率,因此上例表示“0”的概率是 0.1,“1”的概率是 0.05,“2”的概率是 0.6
等。t 是监督数据,将正确解标签设为 1,其他均设为 0。这里,标签“2”为 1,
表示正确解是“2”。将正确解标签表示为 1,其他标签表示为 0 的表示方法称
为 one-hot 表示。
如式(4.1)所示,均方误差会计算神经网络的输出和正确解监督数据的
各个元素之差的平方,再求总和。现在,我们用 Python 来实现这个均方误差,
实现方式如下所示。
def mean_squared_error(y, t):
return 0.5 * np.sum((y-t)**2)
这里,参数 y 和 t 是 NumPy 数组。代码实现完全遵照式(4.1),因此不
再具体说明。现在,我们使用这个函数,来实际地计算一下。
>>> # 设 “2” 为正确解
>>> t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
>>>
>>> # 例 1:“2” 的概率最高的情况(0.6)
>>> y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
>>> mean_squared_error(np.array(y), np.array(t))
0.097500000000000031
>>>
>>> # 例 2:“7” 的概率最高的情况(0.6)
>>> y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]
>>> mean_squared_error(np.array(y), np.array(t))
0.59750000000000003
这里举了两个例子。第一个例子中,正确解是“2”,神经网络的输出的最大
值是“2”;第二个例子中,正确解是“2”,神经网络的输出的最大值是“7”。如
实验结果所示,我们发现第一个例子的损失函数的值更小,和监督数据之间的
误差较小。也就是说,均方误差显示第一个例子的输出结果与监督数据更加吻合。

4.2.2 交叉熵误差
除了均方误差之外,交叉熵误差(cross entropy error)也经常被用作损
失函数。交叉熵误差如下式所示。

这里,log 表示以 e 为底数的自然对数(log e)。yk 是神经网络的输出,tk 是
正确解标签。并且,tk 中只有正确解标签的索引为 1,其他均为 0(one-hot 表示)。
因此,式(4.2)实际上只计算对应正确解标签的输出的自然对数。比如,假设
正确解标签的索引是“2”,与之对应的神经网络的输出是 0.6,则交叉熵误差
是 −log 0.6 = 0.51;若“2”对应的输出是 0.1,则交叉熵误差为 −log 0.1 = 2.30。
也就是说,交叉熵误差的值是由正确解标签所对应的输出结果决定的。
自然对数的图像如图 4-3 所示。

如图 4-3 所示,x 等于 1 时,y 为 0;随着 x 向 0 靠近,y 逐渐变小。因此,
正确解标签对应的输出越大,式(4.2)的值越接近 0;当输出为 1 时,交叉熵
误差为 0。此外,如果正确解标签对应的输出较小,则式(4.2)的值较大。
下面,我们来用代码实现交叉熵误差。
def cross_entropy_error(y, t):
delta = 1e-7
return -np.sum(t * np.log(y + delta))
这里,参数 y 和 t 是 NumPy 数组。函数内部在计算 np.log 时,加上了一
个微小值 delta。这是因为,当出现 np.log(0) 时,np.log(0) 会变为负无限大
的 -inf,这样一来就会导致后续计算无法进行。作为保护性对策,添加一个
微小值可以防止负无限大的发生。下面,我们使用 cross_entropy_error(y, t)
进行一些简单的计算。
>>> t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
>>> y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
>>> cross_entropy_error(np.array(y), np.array(t))
0.51082545709933802
>>>
>>> y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]
>>> cross_entropy_error(np.array(y), np.array(t))
2.3025840929945458
第一个例子中,正确解标签对应的输出为 0.6,此时的交叉熵误差大约
为 0.51。第二个例子中,正确解标签对应的输出为 0.1 的低值,此时的交叉
熵误差大约为 2.3。由此可以看出,这些结果与我们前面讨论的内容是一致的。

4.2.3 mini-batch 学习
机器学习使用训练数据进行学习。使用训练数据进行学习,严格来说,
就是针对训练数据计算损失函数的值,找出使该值尽可能小的参数。因此,
计算损失函数时必须将所有的训练数据作为对象。也就是说,如果训练数据
有 100 个的话,我们就要把这 100 个损失函数的总和作为学习的指标。
前面介绍的损失函数的例子中考虑的都是针对单个数据的损失函数。如

果要求所有训练数据的损失函数的总和,以交叉熵误差为例,可以写成下面
的式(4.3)。

这里 , 假设数据有 N 个,tnk 表示第 n 个数据的第 k 个元素的值(ynk 是神
经网络的输出,t nk 是监督数据)。式子虽然看起来有一些复杂,其实只是把
求单个数据的损失函数的式(4.2)扩大到了 N 份数据,不过最后还要除以 N
进行正规化。通过除以 N,可以求单个数据的“平均损失函数”。通过这样的
平均化,可以获得和训练数据的数量无关的统一指标。比如,即便训练数据
有 1000 个或 10000 个,也可以求得单个数据的平均损失函数。
另外,MNIST 数据集的训练数据有 60000 个,如果以全部数据为对象
求损失函数的和,则计算过程需要花费较长的时间。再者,如果遇到大数据,
数据量会有几百万、几千万之多,这种情况下以全部数据为对象计算损失函
数是不现实的。因此,我们从全部数据中选出一部分,作为全部数据的“近
似”。神经网络的学习也是从训练数据中选出一批数据(称为 mini-batch, 小
批量),然后对每个 mini-batch 进行学习。比如,从 60000 个训练数据中随机
选择 100 笔,再用这 100 笔数据进行学习。这种学习方式称为 mini-batch 学习。
下面我们来编写从训练数据中随机选择指定个数的数据的代码,以进行
mini-batch 学习。在这之前,先来看一下用于读入 MNIST 数据集的代码。
import sys, os
sys.path.append(os.pardir)
import numpy as np
from dataset.mnist import load_mnist
(x_train, t_train), (x_test, t_test) = \
load_mnist(normalize=True, one_hot_label=True)
print(x_train.shape) # (60000, 784)
print(t_train.shape) # (60000, 10)
第 3 章介绍过,load_mnist 函数是用于读入 MNIST 数据集的函数。这个
函数在本书提供的脚本 dataset/mnist.py 中,它会读入训练数据和测试数据。

读入数据时,通过设定参数 one_hot_label=True,可以得到 one-hot 表示(即
仅正确解标签为 1,其余为 0 的数据结构)。
读入上面的 MNIST 数据后,训练数据有 60000 个,输入数据是 784 维
(28 × 28)的图像数据,监督数据是 10 维的数据。因此,上面的 x_train、t_
train 的形状分别是 (60000, 784) 和 (60000, 10)。
那么,如何从这个训练数据中随机抽取 10 笔数据呢?我们可以使用
NumPy 的 np.random.choice(),写成如下形式。
train_size = x_train.shape[0]
batch_size = 10
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
使用 np.random.choice() 可以从指定的数字中随机选择想要的数字。比如,
np.random.choice(60000, 10) 会从 0 到 59999 之间随机选择 10 个数字。如下
面的实际代码所示,我们可以得到一个包含被选数据的索引的数组。
>>> np.random.choice(60000, 10)
array([ 8013, 14666, 58210, 23832, 52091, 10153, 8107, 19410, 27260,
21411])
之后,我们只需指定这些随机选出的索引,取出 mini-batch,然后使用
这个 mini-batch 计算损失函数即可。
计算电视收视率时,并不会统计所有家庭的电视机,而是仅以那些
被选中的家庭为统计对象。比如,通过从关东地区随机选择 1000 个
家庭计算收视率,可以近似地求得关东地区整体的收视率。这 1000
个家庭的收视率,虽然严格上不等于整体的收视率,但可以作为整
体的一个近似值。和收视率一样,mini-batch 的损失函数也是利用
一部分样本数据来近似地计算整体。也就是说,用随机选择的小批
量数据(mini-batch)作为全体训练数据的近似值。

mini-batch 版交叉熵误差的实现
如何实现对应 mini-batch 的交叉熵误差呢?只要改良一下之前实现的对
应单个数据的交叉熵误差就可以了。这里,我们来实现一个可以同时处理单
个数据和批量数据(数据作为 batch 集中输入)两种情况的函数。

这里,y 是神经网络的输出,t 是监督数据。y 的维度为 1 时,即求单个
数据的交叉熵误差时,需要改变数据的形状。并且,当输入为 mini-batch 时,
要用 batch 的个数进行正规化,计算单个数据的平均交叉熵误差。
此外,当监督数据是标签形式(非 one-hot 表示,而是像“2”“7”这样的
标签)时,交叉熵误差可通过如下代码实现。

实现的要点是,由于 one-hot 表示中 t 为 0 的元素的交叉熵误差也为 0,因
此针对这些元素的计算可以忽略。换言之,如果可以获得神经网络在正确
解标签处的输出,就可以计算交叉熵误差。因此,t 为 one-hot 表示时通过
t * np.log(y) 计 算 的 地 方,在 t 为 标 签 形 式 时,可 用 np.log( y[np.arange
(batch_size), t] ) 实现相同的处理(为了便于观察,这里省略了微小值 1e-7)。
作为参考,简单介绍一下np.log( y[np.arange(batch_size), t] )。np.arange
(batch_size) 会生成一个从 0 到 batch_size-1 的数组。比如当 batch_size 为 5
时,np.arange(batch_size) 会生成一个 NumPy 数组 [0, 1, 2, 3, 4]。因为

t 中标签是以 [2, 7, 0, 9, 4] 的形式存储的,所以 y[np.arange(batch_size),
t] 能抽出各个数据的正确解标签对应的神经网络的输出(在这个例子中,
y[np.arange(batch_size), t] 会 生 成 NumPy 数 组 [y[0,2], y[1,7], y[2,0],
y[3,9], y[4,4]])。
4.2.5 为何要设定损失函数
上面我们讨论了损失函数,可能有人要问:“为什么要导入损失函数呢?”
以数字识别任务为例,我们想获得的是能提高识别精度的参数,特意再导入
一个损失函数不是有些重复劳动吗?也就是说,既然我们的目标是获得使识
别精度尽可能高的神经网络,那不是应该把识别精度作为指标吗?
对于这一疑问,我们可以根据“导数”在神经网络学习中的作用来回答。
下一节中会详细说到,在神经网络的学习中,寻找最优参数(权重和偏置)时,
要寻找使损失函数的值尽可能小的参数。为了找到使损失函数的值尽可能小
的地方,需要计算参数的导数(确切地讲是梯度),然后以这个导数为指引,
逐步更新参数的值。
假设有一个神经网络,现在我们来关注这个神经网络中的某一个权重参
数。此时,对该权重参数的损失函数求导,表示的是“如果稍微改变这个权
重参数的值,损失函数的值会如何变化”。如果导数的值为负,通过使该权
重参数向正方向改变,可以减小损失函数的值;反过来,如果导数的值为正,
则通过使该权重参数向负方向改变,可以减小损失函数的值。不过,当导数
的值为 0 时,无论权重参数向哪个方向变化,损失函数的值都不会改变,此
时该权重参数的更新会停在此处。
之所以不能用识别精度作为指标,是因为这样一来绝大多数地方的导数
都会变为 0,导致参数无法更新。话说得有点多了,我们来总结一下上面的内容。
在进行神经网络的学习时,不能将识别精度作为指标。因为如果以
识别精度为指标,则参数的导数在绝大多数地方都会变为 0。
为什么用识别精度作为指标时,参数的导数在绝大多数地方都会变成 0

为了回答这个问题,我们来思考另一个具体例子。假设某个神经网络正
确识别出了 100 笔训练数据中的 32 笔,此时识别精度为 32 %。如果以识别精
度为指标,即使稍微改变权重参数的值,识别精度也仍将保持在 32 %,不会
出现变化。也就是说,仅仅微调参数,是无法改善识别精度的。即便识别精
度有所改善,它的值也不会像 32.0123 . . . % 这样连续变化,而是变为 33 %、
34 % 这样的不连续的、离散的值。而如果把损失函数作为指标,则当前损
失函数的值可以表示为 0.92543 . . . 这样的值。并且,如果稍微改变一下参数
的值,对应的损失函数也会像 0.93432 . . . 这样发生连续性的变化。
识别精度对微小的参数变化基本上没有什么反应,即便有反应,它的值
也是不连续地、突然地变化。作为激活函数的阶跃函数也有同样的情况。出
于相同的原因,如果使用阶跃函数作为激活函数,神经网络的学习将无法进行。
如图 4-4 所示,阶跃函数的导数在绝大多数地方(除了 0 以外的地方)均为 0。
也就是说,如果使用了阶跃函数,那么即便将损失函数作为指标,参数的微
小变化也会被阶跃函数抹杀,导致损失函数的值不会产生任何变化。
阶跃函数就像“竹筒敲石”一样,只在某个瞬间产生变化。而 sigmoid 函数,
如图 4-4 所示,不仅函数的输出(竖轴的值)是连续变化的,曲线的斜率(导数)
也是连续变化的。也就是说,sigmoid 函数的导数在任何地方都不为 0。这对
神经网络的学习非常重要。得益于这个斜率不会为 0 的性质,神经网络的学
习得以正确进行。

4.3 数值微分
梯度法使用梯度的信息决定前进的方向。本节将介绍梯度是什么、有什
么性质等内容。在这之前,我们先来介绍一下导数。
4.3.1 导数
假如你是全程马拉松选手,在开始的 10 分钟内跑了 2 千米。如果要计算
此时的奔跑速度,则为 2/10 = 0.2[千米 / 分]。也就是说,你以 1 分钟前进 0.2
千米的速度(变化)奔跑。
在这个马拉松的例子中,我们计算了“奔跑的距离”相对于“时间”发生
了多大变化。不过,这个 10 分钟跑 2 千米的计算方式,严格地讲,计算的是
10 分钟内的平均速度。而导数表示的是某个瞬间的变化量。因此,将 10 分
钟这一时间段尽可能地缩短,比如计算前 1 分钟奔跑的距离、前 1 秒钟奔跑
的距离、前 0.1 秒钟奔跑的距离......这样就可以获得某个瞬间的变化量(某个
瞬时速度)。
综上,导数就是表示某个瞬间的变化量。它可以定义成下面的式子。
(4.4)
式(4.4)表示的是函数的导数。左边的符号 表示 f(x)关于 x 的导
数,即 f(x)相对于 x 的变化程度。式(4.4)表示的导数的含义是,x 的“微小
变化”将导致函数 f(x)的值在多大程度上发生变化。其中,表示微小变化的
h 无限趋近 0,表示为 。
接下来,我们参考式(4.4),来实现求函数的导数的程序。如果直接实
现式(4.4)的话,向 h 中赋入一个微小值,就可以计算出来了。比如,下面
的实现如何?
# 不好的实现示例
def numerical_diff(f, x):

= 10e-50
return (f(x+h) - f(x)) / h
函数 numerical_diff(f, x) 的名称来源于数值微分 A 的英文 numerical
differentiation。这个函数有两个参数,即“函数 f”和“传给函数 f 的参数 x”。
乍一看这个实现没有问题,但是实际上这段代码有两处需要改进的地方。
在上面的实现中,因为想把尽可能小的值赋给 h(可以话,想让 h 无限接
近 0),所以 h 使用了 10e-50(有 50 个连续的 0 的“0.00 . . . 1”)这个微小值。但
是,这样反而产生了舍入误差(rounding error)。所谓舍入误差,是指因省
略小数的精细部分的数值(比如,小数点第 8 位以后的数值)而造成最终的计
算结果上的误差。比如,在 Python 中,舍入误差可如下表示。
>>> np.float32(1e-50)
0.0
如上所示,如果用 float32 类型(32 位的浮点数)来表示 1e-50,就会变成
0.0,无法正确表示出来。也就是说,使用过小的值会造成计算机出现计算
上的问题。这是第一个需要改进的地方,即将微小值 h 改为 10−4。使用 10−4
就可以得到正确的结果。
第二个需要改进的地方与函数 f 的差分有关。虽然上述实现中计算了函
数 f 在 x+h 和 x 之间的差分,但是必须注意到,这个计算从一开始就有误差。
如图 4-5 所示,“真的导数”对应函数在 x 处的斜率(称为切线),但上述实现
中计算的导数对应的是 (x + h) 和 x 之间的斜率。因此,真的导数(真的切线)
和上述实现中得到的导数的值在严格意义上并不一致。这个差异的出现是因
为 h 不可能无限接近 0。
如图 4-5 所示,数值微分含有误差。为了减小这个误差,我们可以计算
函数 f 在 (x + h) 和 (x − h) 之间的差分。因为这种计算方法以 x 为中心,计
算它左右两边的差分,所以也称为中心差分(而 (x + h) 和 x 之间的差分称为
前向差分)。下面,我们基于上述两个要改进的点来实现数值微分(数值梯度)。
A 所谓数值微分就是用数值方法近似求解函数的导数的过程。——译者注

如上所示,利用微小的差分求导数的过程称为数值微分(numerical
differentiation)。而基于数学式的推导求导数的过程,则用“解析
性”(analytic)一词,称为“解析性求解”或者“解析性求导”。比如,
y = x2 的导数,可以通过 解析性地求解出来。因此,当 x = 2 时,
y 的导数为 4。解析性求导得到的导数是不含误差的“真的导数”。

4.3.2 数值微分的例子
现在我们试着用上述的数值微分对简单函数进行求导。先来看一个由下
式表示的 2 次函数。
y = 0.01x2 + 0.1x (4.5)

这里计算的导数是 f(x) 相对于 x 的变化量,对应函数的斜率。另外,
f(x) = 0.01x2 + 0.1x 的 解 析 解 是 。因 此,在 x = 5 和
x = 10 处,“真的导数”分别为 0.2 和 0.3。和上面的结果相比,我们发现虽然
严格意义上它们并不一致,但误差非常小。实际上,误差小到基本上可以认
为它们是相等的。
现在,我们用上面的数值微分的值作为斜率,画一条直线。结果如图 4-7
所示,可以确认这些直线确实对应函数的切线(源代码在 ch04/gradient_1d.
py 中)。
图 4-7 x = 5、x = 10 处的切线:直线的斜率使用数值微分的值

这里,我们假定向参数输入了一个 NumPy 数组。函数的内部实现比较
简单,先计算 NumPy 数组中各个元素的平方,再求它们的和(np.sum(x**2)
也可以实现同样的处理)。我们来画一下这个函数的图像。结果如图 4-8 所示,
是一个三维图像。

在这些问题中,我们定义了一个只有一个变量的函数,并对这个函数进
行了求导。例如,问题 1 中,我们定义了一个固定 x1 = 4 的新函数,然后对
只有变量 x0 的函数应用了求数值微分的函数。从上面的计算结果可知,问题
1 的答案是 6.00000000000378,问题 2 的答案是 7.999999999999119,和解析
解的导数基本一致。
像这样,偏导数和单变量的导数一样,都是求某个地方的斜率。不过,
偏导数需要将多个变量中的某一个变量定为目标变量,并将其他变量固定为
某个值。在上例的代码中,为了将目标变量以外的变量固定到某些特定的值
上,我们定义了新函数。然后,对新定义的函数应用了之前的求数值微分的
函数,得到偏导数。
4.4 梯度
在刚才的例子中,我们按变量分别计算了 x0 和 x1 的偏导数。现在,我
们希望一起计算 x0 和 x1 的偏导数。比如,我们来考虑求 x0 = 3, x1 = 4 时 (x0, x1)
的偏导数 。另外,像 这样的由全部变量的偏导数汇总
而成的向量称为梯度(gradient)。梯度可以像下面这样来实现。

def numerical_gradient(f, x):
h = 1e-4 # 0.0001
grad = np.zeros_like(x) # 生成和 x 形状相同的数组
for idx in range(x.size):
tmp_val = x[idx]
# f(x+h) 的计算
x[idx] = tmp_val + h
fxh1 = f(x)
# f(x-h) 的计算
x[idx] = tmp_val - h
fxh2 = f(x)
grad[idx] = (fxh1 - fxh2) / (2*h)
x[idx] = tmp_val # 还原值
return grad
函数 numerical_gradient(f, x) 的实现看上去有些复杂,但它执行的处
理和求单变量的数值微分基本没有区别。需要补充说明一下的是,np.zeros_
like(x) 会生成一个形状和 x 相同、所有元素都为 0 的数组。
函数 numerical_gradient(f, x) 中,参数 f 为函数,x 为 NumPy 数组,该
函数对 NumPy 数组 x 的各个元素求数值微分。现在,我们用这个函数实际
计算一下梯度。这里我们求点 (3, 4)、(0, 2)、(3, 0) 处的梯度。
>>> numerical_gradient(function_2, np.array([3.0, 4.0]))
array([ 6., 8.])A
>>> numerical_gradient(function_2, np.array([0.0, 2.0]))
array([ 0., 4.])
>>> numerical_gradient(function_2, np.array([3.0, 0.0]))
array([ 6., 0.])
像这样,我们可以计算 (x0, x1) 在各点处的梯度。上例中,点 (3, 4) 处的
梯度是 (6, 8)、点 (0, 2) 处的梯度是 (0, 4)、点 (3, 0) 处的梯度是 (6, 0)。这个
梯度意味着什么呢?为了更好地理解,我们把 的梯度
画在图上。不过,这里我们画的是元素值为负梯度 B 的向量(源代码在 ch04/
gradient_2d.py 中)。

函数的极小值、最小值以及被称为鞍点(saddle point)的地方,
梯度为 0。极小值是局部最小值,也就是限定在某个范围内的最
小值。鞍点是从某个方向上看是极大值,从另一个方向上看则是
极小值的点。虽然梯度法是要寻找梯度为 0 的地方,但是那个地
方不一定就是最小值(也有可能是极小值或者鞍点)。此外,当函
数很复杂且呈扁平状时,学习可能会进入一个(几乎)平坦的地区,
陷入被称为“学习高原”的无法前进的停滞期。

根据目的是寻找最小值还是最大值,梯度法的叫法有所不同。严格地讲,
寻找最小值的梯度法称为梯度下降法(gradient descent method),
寻找最大值的梯度法称为梯度上升法(gradient ascent method)。但
是通过反转损失函数的符号,求最小值的问题和求最大值的问题会
变成相同的问题,因此“下降”还是“上升”的差异本质上并不重要。
一般来说,神经网络(深度学习)中,梯度法主要是指梯度下降法。

式(4.7)的 η 表示更新量,在神经网络的学习中,称为学习率(learning
rate)。学习率决定在一次学习中,应该学习多少,以及在多大程度上更新参数。
式(4.7)是表示更新一次的式子,这个步骤会反复执行。也就是说,每
一步都按式(4.7)更新变量的值,通过反复执行此步骤,逐渐减小函数值。
虽然这里只展示了有两个变量时的更新过程,但是即便增加变量的数量,也
可以通过类似的式子(各个变量的偏导数)进行更新。
学习率需要事先确定为某个值,比如 0.01 或 0.001。一般而言,这个值
过大或过小,都无法抵达一个“好的位置”。在神经网络的学习中,一般会
一边改变学习率的值,一边确认学习是否正确进行了。
下面,我们用 Python 来实现梯度下降法。如下所示,这个实现很简单。

像学习率这样的参数称为超参数。这是一种和神经网络的参数(权重
和偏置)性质不同的参数。相对于神经网络的权重参数是通过训练
数据和学习算法自动获得的,学习率这样的超参数则是人工设定的。
一般来说,超参数需要尝试多个值,以便找到一种可以使学习顺利
进行的设定。

素 表示当 w11 稍微变化时,损失函数 L 会发生多大变化。这里的重点是,
的形状和 W 相同。实际上,式(4.8)中的 W 和 都是 2 × 3 的形状。
下面,我们以一个简单的神经网络为例,来实现求梯度的代码。为此,
我们要实现一个名为 simpleNet 的类(源代码在 ch04/gradient_simplenet.py
中)。
import sys, os
sys.path.append(os.pardir)
import numpy as np
from common.functions import softmax, cross_entropy_error
from common.gradient import numerical_gradient
class simpleNet:
def __init__(self):
self.W = np.random.randn(2,3) # 用高斯分布进行初始化
def predict(self, x):
return np.dot(x, self.W)
def loss(self, x, t):
z = self.predict(x)
y = softmax(z)
loss = cross_entropy_error(y, t)
return loss
这 里 使 用 了 common/functions.py 中 的 softmax 和 cross_entropy_error 方
法,以及 common/gradient.py 中的 numerical_gradient 方法。simpleNet 类只有
一个实例变量,即形状为 2×3 的权重参数。它有两个方法,一个是用于预
测的 predict(x),另一个是用于求损失函数值的 loss(x,t)。这里参数 x 接收
输入数据,t 接收正确解标签。现在我们来试着用一下这个 simpleNet。
>>> net = simpleNet()
>>> print(net.W) # 权重参数
[[ 0.47355232 0.9977393 0.84668094],
[ 0.85557411 0.03563661 0.69422093]])
>>>
>>> x = np.array([0.6, 0.9])
>>> p = net.predict(x)
>>> print(p)

为了对应形状为多维数组的权重参数 W,这里使用的 numerical_
gradient() 和之前的实现稍有不同。不过,改动只是为了对应多维
数组,所以改动并不大。这里省略了对代码的说明,想知道细节的
读者请参考源代码(common/gradient.py)。

4.5 学习算法的实现
关 于 神 经 网 络 学 习 的 基 础 知 识,到 这 里 就 全 部 介 绍 完 了。“损 失 函
数”“mini-batch”“梯度”“梯度下降法”等关键词已经陆续登场,这里我们
来确认一下神经网络的学习步骤,顺便复习一下这些内容。神经网络的学习
步骤如下所示。
前提
神经网络存在合适的权重和偏置,调整权重和偏置以便拟合训练数据的
过程称为“学习”。神经网络的学习分成下面 4 个步骤。
步骤 1(mini-batch)
从训练数据中随机选出一部分数据,这部分数据称为 mini-batch。我们
的目标是减小 mini-batch 的损失函数的值。
步骤 2(计算梯度)
为了减小 mini-batch 的损失函数的值,需要求出各个权重参数的梯度。
梯度表示损失函数的值减小最多的方向。
步骤 3(更新参数)
将权重参数沿梯度方向进行微小更新。

 

步骤 4(重复)
重复步骤 1、步骤 2、步骤 3。
神经网络的学习按照上面 4 个步骤进行。这个方法通过梯度下降法更新
参数,不过因为这里使用的数据是随机选择的 mini batch 数据,所以又称为
随机梯度下降法(stochastic gradient descent)。“随机”指的是“随机选择的”
的意思,因此,随机梯度下降法是“对随机选择的数据进行的梯度下降法”。
深度学习的很多框架中,随机梯度下降法一般由一个名为 SGD 的函数来实现。
SGD 来源于随机梯度下降法的英文名称的首字母。
下面,我们来实现手写数字识别的神经网络。这里以 2 层神经网络(隐
藏层为 1 层的网络)为对象,使用 MNIST 数据集进行学习。
4.5.1 2 层神经网络的类
首先,我们将这个 2 层神经网络实现为一个名为 TwoLayerNet 的类,实现
过程如下所示 A 。源代码在 ch04/two_layer_net.py 中。
import sys, os
sys.path.append(os.pardir)
from common.functions import *
from common.gradient import numerical_gradient
class TwoLayerNet:
def __init__(self, input_size, hidden_size, output_size,
weight_init_std=0.01):
# 初始化权重
self.params = {}
self.params['W1'] = weight_init_std * \
np.random.randn(input_size, hidden_size)
self.params['b1'] = np.zeros(hidden_size)
self.params['W2'] = weight_init_std * \
np.random.randn(hidden_size, output_size)
self.params['b2'] = np.zeros(output_size)
A TwoLayerNet 的实现参考了斯坦福大学 CS231n 课程提供的 Python 源代码。

4.5 学习算法的实现 111
def predict(self, x):
W1, W2 = self.params['W1'], self.params['W2']
b1, b2 = self.params['b1'], self.params['b2']
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
y = softmax(a2)
return y
# x: 输入数据 , t: 监督数据
def loss(self, x, t):
y = self.predict(x)
return cross_entropy_error(y, t)
def accuracy(self, x, t):
y = self.predict(x)
y = np.argmax(y, axis=1)
t = np.argmax(t, axis=1)
accuracy = np.sum(y == t) / float(x.shape[0])
return accuracy
# x: 输入数据 , t: 监督数据
def numerical_gradient(self, x, t):
loss_W = lambda W: self.loss(x, t)
grads = {}
grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
return grads
虽然这个类的实现稍微有点长,但是因为和上一章的神经网络的前向处
理的实现有许多共通之处,所以并没有太多新东西。我们先把这个类中用到
的变量和方法整理一下。表 4-1 中只罗列了重要的变量,表 4-2 中则罗列了所
有的方法。

TwoLayerNet 类有 params 和 grads 两个字典型实例变量。params 变量中保存
了权重参数,比如 params['W1'] 以 NumPy 数组的形式保存了第 1 层的权重参
数。此外,第 1 层的偏置可以通过 param['b1'] 进行访问。这里来看一个例子。
net = TwoLayerNet(input_size=784, hidden_size=100, output_size=10)
net.params['W1'].shape # (784, 100)
net.params['b1'].shape # (100,)
net.params['W2'].shape # (100, 10)
net.params['b2'].shape # (10,)

如上所示,params 变量中保存了该神经网络所需的全部参数。并且,
params 变量中保存的权重参数会用在推理处理(前向处理)中。顺便说一下,
推理处理的实现如下所示。
x = np.random.rand(100, 784) # 伪输入数据(100 笔)
y = net.predict(x)
此外,与 params 变量对应,grads 变量中保存了各个参数的梯度。如下所示,
使用 numerical_gradient() 方法计算梯度后,梯度的信息将保存在 grads 变
量中。
x = np.random.rand(100, 784) # 伪输入数据(100 笔)
t = np.random.rand(100, 10) # 伪正确解标签(100 笔)
grads = net.numerical_gradient(x, t) # 计算梯度
grads['W1'].shape # (784, 100)
grads['b1'].shape # (100,)
grads['W2'].shape # (100, 10)
grads['b2'].shape # (10,)
接着,我们来看一下 TwoLayerNet 的方法的实现。首先是 __init__(self,
input_size, hidden_size, output_size) 方法,它是类的初始化方法(所谓初
始化方法,就是生成 TwoLayerNet 实例时被调用的方法)。从第 1 个参数开始,
依次表示输入层的神经元数、隐藏层的神经元数、输出层的神经元数。另外,
因为进行手写数字识别时,输入图像的大小是 784(28 × 28),输出为 10 个类别,
所以指定参数 input_size=784、output_size=10,将隐藏层的个数 hidden_size
设置为一个合适的值即可。
此外,这个初始化方法会对权重参数进行初始化。如何设置权重参数
的初始值这个问题是关系到神经网络能否成功学习的重要问题。后面我
们会详细讨论权重参数的初始化,这里只需要知道,权重使用符合高斯
分 布 的 随 机 数 进 行 初 始 化,偏 置 使 用 0 进 行 初 始 化。predict(self, x) 和
accuracy(self, x, t) 的实现和上一章的神经网络的推理处理基本一样。如
果仍有不明白的地方,请再回顾一下上一章的内容。另外,loss(self, x, t)

接着,我们来看一下 TwoLayerNet 的方法的实现。首先是 __init__(self,
input_size, hidden_size, output_size) 方法,它是类的初始化方法(所谓初
始化方法,就是生成 TwoLayerNet 实例时被调用的方法)。从第 1 个参数开始,
依次表示输入层的神经元数、隐藏层的神经元数、输出层的神经元数。另外,
因为进行手写数字识别时,输入图像的大小是 784(28 × 28),输出为 10 个类别,
所以指定参数 input_size=784、output_size=10,将隐藏层的个数 hidden_size
设置为一个合适的值即可。
此外,这个初始化方法会对权重参数进行初始化。如何设置权重参数
的初始值这个问题是关系到神经网络能否成功学习的重要问题。后面我
们会详细讨论权重参数的初始化,这里只需要知道,权重使用符合高斯
分 布 的 随 机 数 进 行 初 始 化,偏 置 使 用 0 进 行 初 始 化。predict(self, x) 和
accuracy(self, x, t) 的实现和上一章的神经网络的推理处理基本一样。如
果仍有不明白的地方,请再回顾一下上一章的内容。另外,loss(self, x, t)

是计算损失函数值的方法。这个方法会基于 predict() 的结果和正确解标签,
计算交叉熵误差。
剩下的 numerical_gradient(self, x, t) 方法会计算各个参数的梯度。根
据数值微分,计算各个参数相对于损失函数的梯度。另外,gradient(self, x, t)
是下一章要实现的方法,该方法使用误差反向传播法高效地计算梯度。
numerical_gradient(self, x, t) 基于数值微分计算参数的梯度。下
一章,我们会介绍一个高速计算梯度的方法,称为误差反向传播法。
用误差反向传播法求到的梯度和数值微分的结果基本一致,但可以
高速地进行处理。使用误差反向传播法计算梯度的 gradient(self,
x, t) 方法会在下一章实现,不过考虑到神经网络的学习比较花时间,
想节约学习时间的读者可以替换掉这里的 numerical_gradient(self,
x, t),抢先使用 gradient(self, x, t) !

根据图 4-11 呈现的结果,我们确认了通过反复学习可以使损失函数的值
逐渐减小这一事实。不过这个损失函数的值,严格地讲是“对训练数据的某
个 mini-batch 的损失函数”的值。训练数据的损失函数值减小,虽说是神经
网络的学习正常进行的一个信号,但光看这个结果还不能说明该神经网络在
其他数据集上也一定能有同等程度的表现。
神经网络的学习中,必须确认是否能够正确识别训练数据以外的其他数
据,即确认是否会发生过拟合。过拟合是指,虽然训练数据中的数字图像能
被正确辨别,但是不在训练数据中的数字图像却无法被识别的现象。
神经网络学习的最初目标是掌握泛化能力,因此,要评价神经网络的泛
化能力,就必须使用不包含在训练数据中的数据。下面的代码在进行学习的
过程中,会定期地对训练数据和测试数据记录识别精度。这里,每经过一个
epoch,我们都会记录下训练数据和测试数据的识别精度。A
epoch 是一个单位。一个 epoch 表示学习中所有训练数据均被使用过
一次时的更新次数。比如,对于 10000 笔训练数据,用大小为 100
笔数据的 mini-batch 进行学习时,重复随机梯度下降法 100 次,所
有的训练数据就都被“看过”了 A。此时,100 次就是一个 epoch。
为了正确进行评价,我们来稍稍修改一下前面的代码。与前面的代码不
同的地方,我们用粗体来表示。

• 机器学习中使用的数据集分为训练数据和测试数据。
• 神经网络用训练数据进行学习,并用测试数据评价学习到的模型的
泛化能力。
• 神经网络的学习以损失函数为指标,更新权重参数,以使损失函数
的值减小。
• 利用某个给定的微小值的差分求导数的过程,称为数值微分。
• 利用数值微分,可以计算权重参数的梯度。
• 数值微分虽然费时间,但是实现起来很简单。下一章中要实现的稍
微复杂一些的误差反向传播法可以高速地计算梯度。

标签:函数,--,学习,神经网络,参数,np,数据
From: https://blog.csdn.net/Darling912/article/details/142162071

相关文章

  • 手工转测试开发轻松实现薪资 50%涨幅的逆袭之路
    Hello大家好,见字如面,我是深圳线下周末2期的学员。我之前一直在某公司以手工测试为主,虽然公司不错团队氛围也很好,但为了追求更广阔的技术发展,并且围墙内的人也总因为新鲜感更想去看看外面的世界。而我深知现在的招聘环境愈加严峻,自己掌握的技能也不足以支撑我NextLevel。很长一段时......
  • 望繁信科技与华恒生物正式签约,共同开启流程数字化转型新篇章
    近日,上海望繁信科技有限公司(简称“望繁信科技”)与安徽华恒生物科技股份有限公司(简称“华恒生物”)成功举行了战略合作签约仪式。作为全球领先的合成生物制造企业,华恒生物将引入望繁信科技的流程智能管理平台——数字北极星,为其全面提升业务流程管理与数字化转型助力。此次合作不仅标......
  • Python中如何实现列表的排序
    在Python中,实现列表(List)的排序是一项基础且常用的操作。Python提供了多种方式来对列表进行排序,包括使用内置函数、方法以及自定义排序逻辑。下面将详细探讨Python中实现列表排序的多种方法,包括sort()方法、sorted()函数、以及利用lambda函数和functools.cmp_to_key()函数来自定......
  • 来云栖大会!探展云上开发,沉浸式体验云原生 + AI 新奇玩法
    2024云栖大会来了!本届云栖大会将于9月19日至9月21日在杭州云栖小镇召开汇集全球最新云计算、AI硬科技云栖大会的主论坛将设置三个对话环节,大模型、自动驾驶和机器人领域的领军技术人和明星创业者将分享AI前沿趋势和应用进展。2024云栖大会主论坛议程发布!揭秘最全议程!20......
  • MySQL基于GTID同步模式搭建主从复制
    系列文章目录rpmbuild构建mysql5.7.42版本的rpm包文章目录系列文章目录一、mysql-5.7.42RPM包构建二、同步模式分类介绍1.异步同步模式2.半同步模式2.1.实现半同步操作流程2.2.半同步问题总结2.3.半同步一致性2.4.异步与半同步对比3.GTID同步三、GTID同步介绍1.gtid......
  • 来云栖大会!探展云上开发,沉浸式体验云原生 + AI 新奇玩法
    2024云栖大会来了!本届云栖大会将于9月19日至9月21日在杭州云栖小镇召开汇集全球最新云计算、AI硬科技云栖大会的主论坛将设置三个对话环节,大模型、自动驾驶和机器人领域的领军技术人和明星创业者将分享AI前沿趋势和应用进展。2024云栖大会主论坛议程发布!揭秘最全议程!20......
  • fsajakslfkaslf
    点击查看代码#include<bits/stdc++.h>#include<bits/extc++.h>//usingnamespace__gnu_pbds;//usingnamespace__gnu_cxx;usingnamespacestd;#defineinfile(x)freopen(x,"r",stdin)#defineoutfile(x)freopen(x,"w",stdout)#defin......
  • Python中如何动态地执行代码
    在Python中,动态执行代码是一种强大的功能,它允许程序在运行时构建并执行字符串形式的代码。这种能力在多种场景下非常有用,比如开发交互式应用程序、构建代码模板、动态生成和执行函数等。Python提供了几种不同的方式来动态执行代码,包括使用exec()、eval()、compile()函数,以及通......
  • 事件委托机制
    事件委托机制(EventDelegation)是一种高效处理大量类似事件的方法,通过将事件监听器添加到一个共同的父元素上,而不是为每个子元素都绑定事件监听器。这种方式不仅可以减少内存消耗,还能简化事件处理代码,提升性能。目录事件委托的工作原理常见使用场景实现事件委托的步骤3.1基......
  • 联网对话功能上线,CodeGeeX智能编程助手再度升级!
    CodeGeeX上线联网获取信息的新功能!进一步提升在编程场景中的实用性和智能水平。值得一提的是,联网对话这一创新功能,目前在编程工具的同类型产品中,是独一无二的。下面我们就来一起看看这一创新为开发者带来的价值,以及如何运用它来提升编程效率和问题解决的能力。首先,打开联网获取信息......