用 Python 从零开始创建神经网络(五):丧失函数(Loss Functions)盘算网络毛病

[复制链接]
发表于 2025-12-27 07:28:33 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
弁言


在随机初始化的模子中,大概纵然是接纳更复杂方法初始化的模子中,我们的目标是随着时间的推移培训或教诲一个模子。为了训练一个模子,我们调解权重和毛病以进步模子的精确性和置信度。为此,我们须要盘算模子的错误量。丧失函数,也被称为资本函数,是量化模子错误程度的算法。丧失是这一指标的权衡。由于丧失是模子的错误,我们抱负情况下盼望它为0。
你大概会想知道为什么我们不根据 argmax 精确度来盘算模子的错误。追念我们之前的置信度示例:[0.22, 0.6, 0.18] 对比 [0.32, 0.36, 0.32]。假如精确的类确实是中心的那一个(索引1),那么两个例子之间的模子精确性将是雷同的。但是这两个例子真的像相互那样精确吗?它们不是,由于精确性只是简单地应用一个 argmax 到输出上,以找到最大值的索引。神经网络的输出实际上是置信度,对精确答案的更多置信度是更好的。因此,我们积极增长精确的置信度并淘汰错误放置的置信度。

1. 分类交错熵丧失(Categorical Cross-Entropy Loss)

假如你认识线性回归,那么你已经知道了用于神经网络举行回归的一个丧失函数:平方毛病(或神经网络中的均方毛病)。
在这个例子中,我们不是在举行回归,我们是在举行分类,以是我们须要一个差异的丧失函数。模子的输出层利用softmax激活函数,这意味着它输出一个概率分布。分类交错熵专门用于比力一个“真实”概率(                               y                          y               y 或“目标”)和某个猜测分布(                               y                      −                      h                      a                      t                          y-hat               y−hat 或“猜测”),因此在这里利用交错熵是故意义的。它也是输出层上利用softmax激活时最常用的丧失函数之一。
盘算                               y                          y               y (实际/盼望分布)和                               y                      −                      h                      a                      t                          y-hat               y−hat 帽(猜测分布)的分类交错熵的公式是:

此中                                        L                         i                                  L_i               Li​ 表现样本的丧失值,                              i                          i               i 是聚集中的第                               i                          i               i 个样本,                              j                          j               j 是标签/输出索引,                              y                          y               y 表现目标值,而                                        y                         ^                                  \hat{y}               y^​ 表现猜测值。
一旦我们开始编写办理方案,我们会进一步简化它为                               −                      log                      ⁡                      (                               c                         o                         r                         r                         e                         c                         t                         _                         c                         l                         a                         s                         s                         _                         c                         o                         n                         f                         i                         d                         e                         n                         c                         e                              )                          -\log({correct\_class\_confidence})               −log(correct_class_confidence),其公式为:

此中                                        L                         i                                  L_i               Li​ 表现样本的丧失值,                              i                          i               i 是数据会合第                               i                          i               i 个样本,                              k                          k               k 是目标标签的索引(真实标签),                              y                          y               y 表现目标值,                                       y                         ^                                  \hat{y}               y^​ 表现猜测值。
你大概会问,为什么我们称这为交错熵而不是对数丧失,后者也是一种丧失范例。假如你不知道什么是对数丧失,你大概会想知道为什么如许一个看似非常根本的形貌会有如许一个看似复杂的公式。
通常情况下,对数丧失毛病函数是我们应用于二元逻辑回归模子输出的函数(我们将在第十六章形貌)——分布中只有两个种别,每个种别对应一个单一输出(神经元),该输出被标记为0或1。在我们的案例中,我们有一个分类模子,它返回全部输出的概率分布。交错熵比力两个概率分布。在我们的案例中,我们有一个softmax输出,比如说是:
  1. softmax_output = [0.7, 0.1, 0.2]
复制代码
我们计划将这个概率分布与哪一个举行比力?在上述输出中,我们有3个类的置信度,假设盼望的猜测是第一类(索引0,当前为0.7)。假如这是预期的猜测,那么盼望的概率分布是 [1, 0, 0]。交错熵也可以应用于像 [0.2, 0.5, 0.3] 如许的概率分布;它们不必看起来像上面那样。也就是说,盼望的概率将包罗在盼望种别中为1,在别的不盼望的种别中为0。如许的数组或向量被称为one-hot,意味着此中一个值是“热”(开),值为1,别的的是“冷”(关),值为0。当利用交错熵将模子的效果与one-hot向量举行比力时,方程的其他部门将归零,目标概率的对数丧失乘以1,使得交错熵盘算相对简单。这也是交错熵盘算的一个特殊情况,称为分类交错熵。举个例子 — 假如我们取一个softmax输出 [0.7, 0.1, 0.2] 和目标 [1, 0, 0],我们可以按以下方式举行盘算:

   假如照旧不明白的话,我的明白是背下来就好。你只须要知道的变乱是,我们须要一个Loss函数来更新神经网络使得                                   y                              y                  y和                                             y                            ^                                       \hat{y}                  y^​尽大概靠近。至于利用哪个Loss函数,大概Loss函数是什么样的,直接上网查就行。
  让我们看看相干的 Python 代码:
  1. import math# An example output from the output layer of the neural networksoftmax_output = [0.7, 0.1, 0.2]
  2. # Ground truthtarget_output = [1, 0, 0]loss = -(math.log(softmax_output[0])*target_output[0] +         math.log(softmax_output[1])*target_output[1] +         math.log(softmax_output[2])*target_output[2])print(loss)
复制代码
  1. >>>
  2. 0.35667494393873245
复制代码
这就是完备的分类交错熵盘算,但鉴于one-hot目标向量,我们可以做一些假设。起首,这种情况下 target_output[1] 和 target_output[2] 的值是多少?它们都是0,任何数乘以0都是0。因此,我们不须要盘算这些索引。接下来,这种情况下 target_output[0] 的值是多少?它是1。以是这也可以省略,由于任何数乘以1仍旧是雷同的。那么,在这个例子中,雷同的输出可以用以下方式盘算:
  1. import math# An example output from the output layer of the neural networksoftmax_output = [0.7, 0.1, 0.2]
  2. loss = -math.log(softmax_output[0])print(loss)
复制代码
  1. >>>
  2. 0.35667494393873245
复制代码
正如你可以看到的,对于代表目标的one-hot向量或标量值,我们可以做一些简单的假设并利用更根本的盘算方法——曾经涉及的公式简化为目标种别置信度得分的负对数——这是本章开头先容的第二个公式。
正如我们已经讨论过的,示例置信程度大概看起来像 [0.22, 0.6, 0.18] 或 [0.32, 0.36, 0.32]。在这两种情况下,这些向量的 argmax 都会返回第二个种别作为猜测,但模子对这些猜测的置信度只对此中一个是高的。分类交错熵丧失思量到这一点,而且输出的丧失值会随着置信度的低落而增大:
  1. import math
  2. print(math.log(1.))
  3. print(math.log(0.95))
  4. print(math.log(0.9))
  5. print(math.log(0.8))
  6. print('...')
  7. print(math.log(0.2))
  8. print(math.log(0.1))
  9. print(math.log(0.05))
  10. print(math.log(0.01))
复制代码
  1. >>>
  2. 0.0
  3. -0.05129329438755058
  4. -0.10536051565782628
  5. -0.2231435513142097
  6. ...
  7. -1.6094379124341003
  8. -2.3025850929940455
  9. -2.995732273553991
  10. -4.605170185988091
复制代码
我们为一些示例置信度打印了差异的对数值。当置信水同即是1,意味着模子对其猜测100%“确定”时,这个样本的丧失值即是0。随着置信程度的进步,丧失值趋近于0。你大概还会想知道为什么我们没有打印 log(0) 的效果——我们将很快表明这一点。
到现在为止,我们已经将 log() 应用于 softmax 输出,但还没有表明“log”是什么,以及为什么利用它。我们将在下一章讨论“为什么”,那一章将涵盖导数、梯度和优化;可以说,log 函数具有一些抱负的属性。Log 是对数(logarithm)的简称,被界说为方程                                        a                         x                              =                      b                          a^x = b               ax=b 中                               x                          x               x 项的解。比方,                              1                               0                         x                              =                      100                          10^x = 100               10x=100 可以用对数办理:                                                 log                            ⁡                                  10                              (                      100                      )                          \log_{10}(100)               log10​(100),效果为2。当基数利用                               e                          e               e(欧拉数或约2.71828)时,log 函数的这个属性尤其有利(在示例中基数为10)。以                               e                          e               e 为基数的对数称为自然对数,简称自然 log 或 log ——你也大概看到如许写:                              ln                      ⁡                      (                      x                      )                      =                      log                      ⁡                      (                      x                      )                      =                                         log                            ⁡                                  e                              (                      x                      )                          \ln(x) = \log(x) = \log_e(x)               ln(x)=log(x)=loge​(x)。各种约定大概会使这变得紊乱,因此为了简化事物,提到的任何 log 都将始终是自然对数。自然对数表现方程                                        e                         x                              =                      b                          e^x = b               ex=b 中                               x                          x               x 项的解;比方,                                       e                         x                              =                      5.2                          e^x = 5.2               ex=5.2 由                               log                      ⁡                      (                      5.2                      )                          \log(5.2)               log(5.2) 办理。
在 Python 代码中:
  1. import numpy as np
  2. b = 5.2
  3. print(np.log(b))
复制代码
  1. >>>
  2. 1.6486586255873816
复制代码
我们可以通过对效果举行指数化来证实这一点:
  1. import math
  2. print(math.e ** 1.6486586255873816)
复制代码
  1. >>>
  2. 5.199999999999999
复制代码
这种微小的差异是由于 Python 中浮点数精度引起的。回到丧失盘算,我们须要以两种额外的方式修改我们的输出。起首,我们将更新我们的过程,使其可以大概处置惩罚批量的 softmax 输出分布;其次,使负对数盘算动态对应到目标索引(目标索引到现在为止已经被硬编码)。

思量一个场景,神经网络在三个种别之间举行分类,而且神经网络以三批的方式举行分类。在通过 softmax 激活函数处置惩罚一批3个样本和3个种别之后,网络的输出层产生如下:
  1. # Probabilities for 3 samples
  2. softmax_outputs = np.array([[0.7, 0.1, 0.2],
  3.                                                         [0.1, 0.5, 0.4],
  4.                                                         [0.02, 0.9, 0.08]])
复制代码
我们须要一种动态盘算分类交错熵的方法,如今我们知道这是一个负对数盘算。要确定从 softmax 输出中盘算负对数的哪个值,我们只须要知道我们的目标值。在这个例子中,有3个种别;假设我们试图将某物分类为“狗”、“猫”或“人”。狗是第0类(索引0),猫是第1类(索引1),人是第2类(索引2)。假设这批三个样本输入到这个神经网络被映射到一只狗、一只猫和一只猫的目标值。以是目标(作为目标索引列表)将是 [0, 1, 1]。
  1. softmax_outputs = [[0.7, 0.1, 0.2],
  2.                                    [0.1, 0.5, 0.4],
  3.                                [0.02, 0.9, 0.08]]
  4.                                
  5. class_targets = [0, 1, 1] # dog, cat, cat
复制代码
种别目标中的第一个值0意味着第一个softmax输出分布的预期猜测是 [0.7, 0.1, 0.2] 中索引0处的值;模子对这个观察是狗有0.7的置信度。这种情况在整个批次中一连,第二个softmax分布 [0.1, 0.5, 0.4] 的预期目标在索引1处;模子对这是一只猫只有0.5的置信度——模子对这个观察不太确定。在末了一个样本中,它也是来自softmax分布的第二个索引,在这种情况下是0.9的值——相称高的置信度。
有了一系列softmax输出及其预期目标,我们可以映射这些索引以从softmax分布中检索值:
  1. softmax_outputs = [[0.7, 0.1, 0.2],
  2.                    [0.1, 0.5, 0.4],
  3.                    [0.02, 0.9, 0.08]]
  4. class_targets = [0, 1, 1]
  5. for targ_idx, distribution in zip(class_targets, softmax_outputs):
  6.     print(targ_idx, distribution)
  7.     print(distribution[targ_idx])
  8.     print("====================")
复制代码
  1. >>>
  2. 0 [0.7, 0.1, 0.2]
  3. 0.7
  4. ====================
  5. 1 [0.1, 0.5, 0.4]
  6. 0.5
  7. ====================
  8. 1 [0.02, 0.9, 0.08]
  9. 0.9
  10. ====================
复制代码
同样,zip() 函数可以让我们在 Python 中同时遍历多个迭代表。利用 NumPy 可以进一步简化这一过程(这次我们将创建一个包罗 Softmax 输出的 NumPy 数组):
  1. import numpy as np
  2. softmax_outputs = np.array([[0.7, 0.1, 0.2],
  3.                             [0.1, 0.5, 0.4],
  4.                             [0.02, 0.9, 0.08]])
  5. class_targets = [0, 1, 1]
  6. class_targets_value = softmax_outputs[[0, 1, 2], class_targets]
  7. print(class_targets_value)
复制代码
  1. >>>
  2. [0.7 0.5 0.9]
复制代码
0、1 和 2 这些值是什么意思?NumPy 允许我们以多种方式索引数组。此中一种方式是利用一个添补了索引的列表,这对我们来说很方便——我们可以利用 class_targets 来实现这一目标,由于它已经包罗了我们感爱好的索引列表。题目是这必须过滤数组中的数据行——第二维。为了实行这一操纵,我们还须要明白地在其第一维过滤这个数组。这一维包罗猜测,我们固然盼望生存它们全部。我们可以通过利用一个包罗从0到全部索引的数字的列表来实现这一点。我们知道我们将有和我们整个批次中的分布一样多的索引,以是我们可以利用 range() 而不是本身输入每个值:
  1. import numpy as np
  2. softmax_outputs = np.array([[0.7, 0.1, 0.2],
  3.                             [0.1, 0.5, 0.4],
  4.                             [0.02, 0.9, 0.08]])
  5. class_targets = [0, 1, 1]
  6. class_targets_value = softmax_outputs[range(len(softmax_outputs)), class_targets]
  7. print(class_targets_value )
复制代码
  1. >>>
  2. [0.7 0.5 0.9]
复制代码
这会返回每个样本在目标指数处的信度列表。如今我们对该列表应用负对数:
  1. import numpy as np
  2. softmax_outputs = np.array([[0.7, 0.1, 0.2],
  3.                             [0.1, 0.5, 0.4],
  4.                             [0.02, 0.9, 0.08]])
  5. class_targets = [0, 1, 1]
  6. neg_log= -np.log(softmax_outputs[range(len(softmax_outputs)), class_targets])
  7. print(neg_log)
复制代码
  1. >>>
  2. [0.35667494 0.69314718 0.10536052]
复制代码
末了,我们想要得到每批的均匀丧失值,以便相识我们的模子在训练期间的表现。在Python中有很多盘算均匀值的方法;最根本的均匀值情势是算术均匀值:                              s                      u                      m                      (                      i                      t                      e                      r                      a                      b                      l                      e                      )                      /                      l                      e                      n                      (                      i                      t                      e                      r                      a                      b                      l                      e                      )                          sum(iterable) / len(iterable)               sum(iterable)/len(iterable)。NumPy有一个方法可以在数组上盘算这个均匀值,以是我们将利用它。我们在代码中添加NumPy的均匀盘算:
  1. import numpy as np
  2. softmax_outputs = np.array([[0.7, 0.1, 0.2],
  3.                             [0.1, 0.5, 0.4],
  4.                             [0.02, 0.9, 0.08]])
  5. class_targets = [0, 1, 1]
  6. neg_log = -np.log(softmax_outputs[range(len(softmax_outputs)), class_targets])
  7. average_loss = np.mean(neg_log)
  8. print(average_loss)
复制代码
  1. >>>
  2. 0.38506088005216804
复制代码

我们已经相识到目标可以举行一位有用编码(one-hot encoding),此中除了一个值是1,别的都是0,而且精确标签的位置添补为1。它们也可以是希罕的,这意味着它们包罗的数字是精确的种别编号——我们通过 spiral_data() 函数以这种方式天生它们,而且我们可以允许丧失盘算继承任何这些情势。
由于我们实现了这一点以实用于希罕标签(如我们的训练数据),假如它们是一位有用编码,我们必须举行查抄并在这种新情况下以差异方式处置惩罚。可以通过计数维度来实行查抄——假如目标是单维的(如列表),它们是希罕的;但假如有两个维度(如列表的列表),则是一组一位有用编码的向量。在这第二种情况下,我们将利用本章的第一个方程实现一个办理方案,而不是过滤目标标签处的置信度。我们必须将置信度与目标相乘,除了精确标签处的值外,将全部值归零,并沿行轴(轴1)举行求和。我们必须在刚刚编写的代码中添加一个对维数的测试,将对数值的盘算移到这个新的 if 语句之外,并按照第一个方程实现一位有用编码标签的办理方案:
  1. import numpy as np
  2. softmax_outputs = np.array([[0.7, 0.1, 0.2],
  3.                             [0.1, 0.5, 0.4],
  4.                             [0.02, 0.9, 0.08]])
  5. class_targets = np.array([[1, 0, 0],
  6.                           [0, 1, 0],
  7.                           [0, 1, 0]])
  8. # class_targets = np.array([0, 1, 1])
  9. # Probabilities for target values -
  10. # only if categorical labels
  11. if len(class_targets.shape) == 1:
  12.     correct_confidences = softmax_outputs[range(len(softmax_outputs)), class_targets]
  13. # Mask values - only for one-hot encoded labels
  14. elif len(class_targets.shape) == 2:
  15.     correct_confidences = np.sum(
  16.         softmax_outputs * class_targets,
  17.         axis=1
  18.         )
  19.    
  20. # Losses
  21. neg_log = -np.log(correct_confidences)
  22. average_loss = np.mean(neg_log)
  23. print(average_loss)
复制代码
  1. >>>
  2. 0.38506088005216804
复制代码
  在NumPy中,axis参数界说了数组沿哪个维度举行操纵。多维数组中,axis的值决定了函数是如那里理数组元素的:
  

  • axis=0:沿着第一个轴(“纵向”,“列方向”,“深度方向”),即将操纵应用于跨行的同一列的全部元素
  • axis=1:沿着第二个轴(“横向”,“行方向”),即将操纵应用于同一行的全部元素
  • axis=2:假如有的话,这通常是沿着第三维度(比方,在三维数组中),即针对每个"页"或"深度层"的同一位置的元素。
    在Python的NumPy库中,当你利用 * 操纵符对两个数组举行运算时,它实行的是元素级别的乘法(也称为Hadamard乘积或点乘)。这意味着两个数组的相应元素相乘,效果数组的每个位置                                    i                         ,                         j                              i, j                  i,j 是原来两个数组在该位置的元素相乘的效果。
  

  • 第一行:[0.7 × 1, 0.1 × 0, 0.2 × 0] = [0.7, 0, 0]
  • 第二行:[0.1 × 0, 0.5 × 1, 0.4 × 0] = [0, 0.5, 0]
  • 第三行:[0.02 × 0, 0.9 × 1, 0.08 × 0] = [0, 0.9, 0]
  1. [[0.7, 0, 0],
  2. [0, 0.5, 0],
  3. [0, 0.9, 0]]
复制代码

在我们继承之前,尚有一个题目须要办理。Softmax 输出,也是这个丧失函数的输入,由0到1范围内的数字构成——一系列置信度。模子大概会对某个标签有完全的信心,使得全部剩余的置信度为零。同样,模子也大概将完全的信心分配给一个非目标值。假如我们实验盘算这个置信度为0的丧失:
  1. import numpy as np
  2. -np.log(0)
复制代码
  1. >>>
  2. 163: RuntimeWarning: divide by zero encountered in log
  3.   -np.log(0)
复制代码
在我们表明这个题目之前,我们须要讨论一下                               log                      ⁡                      (                      0                      )                          \log(0)               log(0)。从数学的角度看,                              log                      ⁡                      (                      0                      )                          \log(0)               log(0) 是未界说的。我们已经知道以下的依靠关系:假如                               y                      =                      log                      ⁡                      (                      x                      )                          y = \log(x)               y=log(x),那么                                        e                         y                              =                      x                          e^y = x               ey=x。在                               y                      =                      log                      ⁡                      (                      0                      )                          y = \log(0)               y=log(0) 中所得到的                               y                          y               y 是什么,与在                                        e                         y                              =                      0                          e^y = 0               ey=0 中的                               y                          y               y 是什么一样。简单来说,常数                               e                          e               e 的任何幂都是一个正数,而且没有任何                               y                          y               y 可以使                                        e                         y                              =                      0                          e^y = 0               ey=0。这意味着                               log                      ⁡                      (                      0                      )                          \log(0)               log(0) 是未界说的。我们须要知道                               log                      ⁡                      (                      0                      )                          \log(0)               log(0) 是什么,而“未界说”并不意味着我们对它一无所知。既然                               log                      ⁡                      (                      0                      )                          \log(0)               log(0) 是未界说的,那么靠近0的值的效果是什么呢?我们可以盘算函数的极限。怎样精确盘算超出了本书的范围,但办理方案是:

我们将其读作自然对数                               log                      ⁡                      (                      x                      )                          \log(x)               log(x) 的极限,此中                               x                          x               x 从正值靠近0(盘算负值的自然对数是不大概的),即是负无穷。这意味着,对于无穷小的                               x                          x               x,其极限是负无穷,此中                               x                          x               x 永世不会到达0。
在编程语言中情况略有差异。这里没有极限,只有一个函数,给定一个参数,返回某个值。在Python中利用NumPy,0的负自然对数即是一个无穷大的数,而不是未界说,而且会打印一个关于0除错误的告诫(这是这种盘算方式的效果)。假如                               −                      log                      ⁡                      (                      0                      )                          -\log(0)               −log(0) 即是无穷大,用Python盘算负无穷大的                               e                          e               e 次幂大概吗?
  1. import numpy as np
  2. result = np.e**(-np.inf)
  3. print(result)
复制代码
  1. >>>
  2. 0.0
复制代码
在编程中,未界说的事物越少越好。稍后,我们将看到雷同的简化,比方盘算绝对值函数的导数,该导数在输入为0时不存在,我们将不得不做出一些决定来办理这个题目。
回到                               −                      log                      ⁡                      (                      0                      )                          -\log(0)               −log(0) 效果为无穷大的情况——只管这在数学上是故意义的,由于模子完全错误,但这将为我们进一步举行盘算带来题目。稍后,在举行优化时,我们还会在盘算梯度时遇到题目,从全部样本丧失的均匀值开始,由于列表中单个无穷大的值会导致该列表的均匀值也是无穷大:
  1. import numpy as np
  2. result = np.mean([1, 2, 3, -np.log(0)])
  3. print(result)
复制代码
  1. >>>
  2. 177: RuntimeWarning: divide by zero encountered in log
  3.   result = np.mean([1, 2, 3, -np.log(0)])
复制代码
我们可以为置信度添加一个很小的值,以防止它为零,比方1e-7:
  1. import numpy as np
  2. result = -np.log(1e-7)
  3. print(result)
复制代码
  1. >>>
  2. 16.11809565095832
复制代码
在置信度的最边沿添加一个很小的值,即十万分之一,对效果的影响微乎其微,但这种方法会产生别的两个题目。起首,在置信度值为1 :
  1. import numpy as np
  2. result = -np.log(1+1e-7)
  3. print(result)
复制代码
  1. >>>
  2. -9.999999505838704e-08
复制代码
当模子在猜测中完全精确,并将全部置信度放在精确的标签上时,丧失会酿成负值,而不是0。这里的另一个题目是置信度向1移动,纵然黑白常小的值。为了防止这两个题目,最好从两头同时剪切值,我们的案例中是1e-7。这意味着大概的最低值将变为1e-7(就像我们刚才演示的那样),但大概的最高值,而不是1+1e-7,将变为1-1e-7(稍微低于1):
  1. import numpy as np
  2. result = -np.log(1-1e-7)
  3. print(result)
复制代码
  1. >>>
  2. 1.0000000494736474e-07
复制代码
这将使丧失不会恰好为0,而是一个非常小的值,但不会使其成为负值,也不会使团体丧失方向1。 在我们的代码中,利用 numpy,我们将利用 np.clip() 方法来实现这一点:
  1. import numpy as np
  2. y_pred = np.array([0.1, 0.2, 0.3, np.inf, -np.inf])
  3. y_pred_clipped = np.clip(y_pred, 1e-7, 1-1e-7)
  4. print(y_pred_clipped)
复制代码
  1. >>>
  2. [1.000000e-01 2.000000e-01 3.000000e-01 9.999999e-01 1.000000e-07]
复制代码
该方法可以对数值数组实行剪切,因此我们可以直接将其应用于猜测值,并将其生存为一个单独的数组,稍后我们将利用该数组。

2. 分类交错熵丧失类(The Categorical Cross-Entropy Loss Class)

在后续章节中,我们将添加更多的丧失函数,我们将实行的一些操尴尬刁难全部这些丧失函数都是通用的。此中一个操纵是我们怎样盘算总丧失——无论我们利用哪种丧失函数,总丧失始终是全部样本丧失的均匀值。让我们创建一个包罗 calculate 方法的 Loss 类,该方法将调用我们的丧失对象的 forward 方法,并盘算返回的样本丧失的均匀值:
  1. import numpy as np
  2. # Common loss class
  3. class Loss:
  4.     # Calculates the data and regularization losses
  5.     # given model output and ground truth values
  6.     def calculate(self, output, y):
  7.         # Calculate sample losses
  8.         sample_losses = self.forward(output, y)
  9.         # Calculate mean loss
  10.         data_loss = np.mean(sample_losses)
  11.         # Return loss
  12.         return data_loss
复制代码
在反面的章节中,我们将为该类添加更多代码,它存在的来由也将变得更加清楚。如今,我们只需利用它这一个目标。
让我们把丧失代码转换成一个类,以方便下一步操纵:
  1. # Cross-entropy loss
  2. class Loss_CategoricalCrossentropy(Loss):
  3.     # Forward pass
  4.     def forward(self, y_pred, y_true):
  5.         # Number of samples in a batch
  6.         samples = len(y_pred)
  7.         # Clip data to prevent division by 0
  8.         # Clip both sides to not drag mean towards any value
  9.         y_pred_clipped = np.clip(y_pred, 1e-7, 1 - 1e-7)
  10.         # Probabilities for target values -
  11.         # only if categorical labels
  12.         if len(y_true.shape) == 1:
  13.             correct_confidences = y_pred_clipped[range(samples), y_true]
  14.         # Mask values - only for one-hot encoded labels
  15.         elif len(y_true.shape) == 2:
  16.             correct_confidences = np.sum(y_pred_clipped * y_true, axis=1)
  17.         # Losses
  18.         negative_log_likelihoods = -np.log(correct_confidences)
  19.         return negative_log_likelihoods
复制代码
该类继承于 Loss 类,可实行本章衍生的全部毛病盘算,并可作为对象利用。比方,利用手动创建的输出和目标:
  1. import numpy as np
  2. # Common loss class
  3. class Loss:
  4.     # Calculates the data and regularization losses
  5.     # given model output and ground truth values
  6.     def calculate(self, output, y):
  7.         # Calculate sample losses
  8.         sample_losses = self.forward(output, y)
  9.         # Calculate mean loss
  10.         data_loss = np.mean(sample_losses)
  11.         # Return loss
  12.         return data_loss
  13.         # Cross-entropy loss
  14. class Loss_CategoricalCrossentropy(Loss):
  15.     # Forward pass
  16.     def forward(self, y_pred, y_true):
  17.         # Number of samples in a batch
  18.         samples = len(y_pred)
  19.         # Clip data to prevent division by 0
  20.         # Clip both sides to not drag mean towards any value
  21.         y_pred_clipped = np.clip(y_pred, 1e-7, 1 - 1e-7)
  22.         # Probabilities for target values -
  23.         # only if categorical labels
  24.         if len(y_true.shape) == 1:
  25.             correct_confidences = y_pred_clipped[range(samples), y_true]
  26.         # Mask values - only for one-hot encoded labels
  27.         elif len(y_true.shape) == 2:
  28.             correct_confidences = np.sum(y_pred_clipped * y_true, axis=1)
  29.         # Losses
  30.         negative_log_likelihoods = -np.log(correct_confidences)
  31.         return negative_log_likelihoods
  32. softmax_outputs = np.array([[0.7, 0.1, 0.2],                            [0.1, 0.5, 0.4],                            [0.02, 0.9, 0.08]])class_targets = np.array([0, 1, 1])# class_targets = np.array([[1, 0, 0],#                           [0, 1, 0],#                           [0, 1, 0]])loss_function = Loss_CategoricalCrossentropy()loss = loss_function.calculate(softmax_outputs, class_targets)print(loss)
复制代码

展示到现在为止的全部代码

  1. import numpy as npimport nnfsfrom nnfs.datasets import spiral_datannfs.init()# Dense layerclass Layer_Dense:    # Layer initialization    def __init__(self, n_inputs, n_neurons):        # Initialize weights and biases        self.weights = 0.01 * np.random.randn(n_inputs, n_neurons)        self.biases = np.zeros((1, n_neurons))            # Forward pass    def forward(self, inputs):        # Calculate output values from inputs, weights and biases        self.output = np.dot(inputs, self.weights) + self.biases        # ReLU activationclass Activation_ReLU:    # Forward pass    def forward(self, inputs):        # Calculate output values from input        self.output = np.maximum(0, inputs)                # Softmax activationclass Activation_Softmax:    # Forward pass    def forward(self, inputs):        # Get unnormalized probabilities        exp_values = np.exp(inputs - np.max(inputs, axis=1,                                             keepdims=True))        # Normalize them for each sample        probabilities = exp_values / np.sum(exp_values, axis=1,                                             keepdims=True)        self.output = probabilities                # Common loss classclass Loss:    # Calculates the data and regularization losses    # given model output and ground truth values    def calculate(self, output, y):        # Calculate sample losses        sample_losses = self.forward(output, y)        # Calculate mean loss        data_loss = np.mean(sample_losses)        # Return loss        return data_loss        # Cross-entropy loss
  2. class Loss_CategoricalCrossentropy(Loss):
  3.     # Forward pass
  4.     def forward(self, y_pred, y_true):
  5.         # Number of samples in a batch
  6.         samples = len(y_pred)
  7.         # Clip data to prevent division by 0
  8.         # Clip both sides to not drag mean towards any value
  9.         y_pred_clipped = np.clip(y_pred, 1e-7, 1 - 1e-7)
  10.         # Probabilities for target values -
  11.         # only if categorical labels
  12.         if len(y_true.shape) == 1:
  13.             correct_confidences = y_pred_clipped[range(samples), y_true]
  14.         # Mask values - only for one-hot encoded labels
  15.         elif len(y_true.shape) == 2:
  16.             correct_confidences = np.sum(y_pred_clipped * y_true, axis=1)
  17.         # Losses
  18.         negative_log_likelihoods = -np.log(correct_confidences)
  19.         return negative_log_likelihoods
  20.         # Create datasetX, y = spiral_data(samples=100, classes=3)# Create Dense layer with 2 input features and 3 output valuesdense1 = Layer_Dense(2, 3)# Create ReLU activation (to be used with Dense layer):activation1 = Activation_ReLU()# Create second Dense layer with 3 input features (as we take output# of previous layer here) and 3 output valuesdense2 = Layer_Dense(3, 3)# Create Softmax activation (to be used with Dense layer):activation2 = Activation_Softmax()# Create loss functionloss_function = Loss_CategoricalCrossentropy()# Make a forward pass of our training data through this layerdense1.forward(X)# Make a forward pass through activation function# it takes the output of first dense layer hereactivation1.forward(dense1.output)# Make a forward pass through second Dense layer# it takes outputs of activation function of first layer as inputsdense2.forward(activation1.output)# Make a forward pass through activation function# it takes the output of second dense layer hereactivation2.forward(dense2.output)# Let's see output of the first few samples:print(activation2.output[:5])    # Perform a forward pass through loss function# it takes the output of second dense layer here and returns lossloss = loss_function.calculate(activation2.output, y)        # Print loss valueprint('loss:', loss)   
复制代码
  1. >>>
  2. [[0.33333334 0.33333334 0.33333334]
  3. [0.3333332  0.3333332  0.33333364]
  4. [0.3333329  0.33333293 0.3333342 ]
  5. [0.3333326  0.33333263 0.33333477]
  6. [0.33333233 0.3333324  0.33333528]]
  7. loss: 1.0986104
复制代码

3. 精确率盘算(Accuracy Calculation)

只管丧失是优化模子的一个有用指标,但在实践中常用的与丧失一起利用的指标是精确率,它形貌了最大置信度是精确种别的频率,以分数情势表现。方便地,我们可以重用现有的变量界说来盘算精确率指标。我们将利用 softmax 输出的 argmax 值,然后将这些值与目标举行比力。这就像做以下操纵一样简单(留意我们稍微修改了 softmax_outputs 以顺应这个例子):
  1. import numpy as np
  2. # Probabilities of 3 samples
  3. softmax_outputs = np.array([[0.7, 0.2, 0.1],
  4.                             [0.5, 0.1, 0.4],
  5.                             [0.02, 0.9, 0.08]])
  6. # Target (ground-truth) labels for 3 samples
  7. class_targets = np.array([0, 1, 1])
  8. # class_targets = np.array([[1, 0, 0], # one-hot encoded
  9. #                           [0, 1, 0],
  10. #                           [0, 1, 0]])
  11. print("class_targets:", class_targets)
  12. # Calculate values along second axis (axis of index 1)
  13. predictions = np.argmax(softmax_outputs, axis=1)
  14. print("predictions:", predictions)
  15. # If targets are one-hot encoded - convert them
  16. if len(class_targets.shape) == 2:
  17.     class_targets = np.argmax(class_targets, axis=1)
  18.     print("class_targets:", class_targets)
  19.    
  20. # True evaluates to 1; False to 0
  21. accuracy = np.mean(predictions==class_targets)
  22. print('acc:', accuracy)
复制代码
  1. >>>
  2. class_targets: [0 1 1]
  3. predictions: [0 0 1]
  4. acc: 0.6666666666666666
复制代码
我们还利用 np.argmax() 将目标转换为希罕值,从而处置惩罚单击编码目标。
我们可以在上述完备脚本的末端添加以下内容,以盘算其精确性:
  1. # Calculate accuracy from output of activation2 and targets
  2. # calculate values along first axis
  3. predictions = np.argmax(activation2.output, axis=1)
  4. if len(y.shape) == 2:
  5.     y = np.argmax(y, axis=1)
  6.    
  7. accuracy = np.mean(predictions==y)
  8. # Print accuracy
  9. print('acc:', accuracy)
复制代码
  1. >>>
  2. acc: 0.34
复制代码
如今,您已经学会了怎样对我们的网络举行前向通报,并盘算出模子是否表现不佳的指标信号,我们将在下一章开始举行优化!
   本章的章节代码、更多资源和勘误表:https://nnfs.io/ch5
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表