当前位置: 首页 > article >正文

神经网络的学习 求梯度

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


# simpleNet类
class simpleNet:
    def __init__(self):
        self.W = np.random.rand(2, 3)  # 随机形状为2*3的权重参数

    def predict(self, x):  # 方法一:预测
        return np.dot(x, self.W) #表示输入 x 与权重 W 之间的点积,这相当于神经网络的前向传播。

    def loss(self, x, t):  # 方法二:求损失函数值 x接受输入数据,t为真实值标签
        z = self.predict(x)
        y = softmax(z)  # softmax的输出总和=1,可将softmax函数输出的结果称为概率
        loss = cross_entropy_error(y, t)  # 交叉熵损失函数,值越小,表示越接近真实值
        return loss


net = simpleNet()#生成
print(net.W)  #输出权重参数
x = np.array([0.6, 0.9])   
p = net.predict(x)
print(p)

print(np.argmax(p))#最大的索引值

t=np.array([0,0,1])#正确标签为2

print(net.loss(x,t))#求损失函数值

#求梯度:
def f(W): #f(W)是为参数W可以在求权重函数中使用的兼容参数,类似于将W重构成f(W)?
    return net.loss(x,t)
dW=numerical_gradient(f,net.W)
print(dW)

common中用到的的function和gradient

import numpy as np

def softmax(x):
    if x.ndim == 2:
        x = x.T
        x = x - np.max(x, axis=0)
        y = np.exp(x) / np.sum(np.exp(x), axis=0)
        return y.T 

    x = x - np.max(x) # 溢出对策
    return np.exp(x) / np.sum(np.exp(x))

def cross_entropy_error(y, t):
    if y.ndim == 1:
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)
        
    # 监督数据是one-hot-vector的情况下,转换为正确解标签的索引
    if t.size == y.size:
        t = t.argmax(axis=1)
             
    batch_size = y.shape[0]
    return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size
def numerical_gradient(f, x):
    h = 1e-4 # 0.0001
    grad = np.zeros_like(x)
    
    it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
    while not it.finished:
        idx = it.multi_index
        tmp_val = x[idx]
        x[idx] = float(tmp_val) + h
        fxh1 = f(x) # f(x+h)
        
        x[idx] = tmp_val - h 
        fxh2 = f(x) # f(x-h)
        grad[idx] = (fxh1 - fxh2) / (2*h)
        
        x[idx] = tmp_val # 还原值
        it.iternext()   
        
    return grad

 代码详解:

第一段:simpleNet

这段代码实现了一个简单的神经网络(simpleNet 类),并展示了如何计算神经网络的预测、损失函数值以及权重的梯度。接下来,我将逐行解释代码的作用和它的核心概念。

1. 导入模块

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
  • import sys, os: 导入 sysos 模块,允许你在程序中操作系统路径、环境变量等。
  • sys.path.append(os.pardir): 将父目录添加到模块搜索路径中,以便可以访问父目录中的模块。os.pardir 是上级目录的路径。
  • import numpy as np: 导入 NumPy 库,常用于数组操作和数学计算。
  • from common.functions import softmax, cross_entropy_error: 从 common.functions 模块中导入 softmaxcross_entropy_error 函数。softmax 将网络的输出转化为概率分布,cross_entropy_error 用于计算交叉熵损失。
  • from common.gradient import numerical_gradient: 从 common.gradient 模块中导入 numerical_gradient 函数,用于计算梯度。

2. simpleNet

class simpleNet:
    def __init__(self):
        self.W = np.random.rand(2, 3)  # 随机形状为2*3的权重参数
  • class simpleNet: 定义了一个名为 simpleNet 的类,这个类是简单的神经网络模型。
  • def __init__(self): 构造函数初始化模型参数。
  • self.W = np.random.rand(2, 3): 初始化权重 W,它是一个 2×32 \times 3 的随机矩阵,表示有 2 个输入和 3 个输出神经元。

3. predict 方法

def predict(self, x):  # 方法一:预测
    return np.dot(x, self.W)  # 表示输入 x 与权重 W 之间的点积,这相当于神经网络的前向传播。
  • def predict(self, x): 定义了一个方法 predict,用于计算神经网络的输出。
  • return np.dot(x, self.W): 计算输入 x 和权重矩阵 W 的点积。点积相当于神经网络的前向传播过程,得出每个神经元的激活值。

4. loss 方法

def loss(self, x, t):  # 方法二:求损失函数值 x 接受输入数据,t 为真实标签
    z = self.predict(x)
    y = softmax(z)  # softmax的输出总和=1,可将 softmax 函数输出的结果称为概率
    loss = cross_entropy_error(y, t)  # 交叉熵损失函数,值越小,表示越接近真实值
    return loss
  • def loss(self, x, t): 定义了一个计算损失的函数。x 是输入数据,t 是真实标签。
  • z = self.predict(x): 调用 predict 方法计算输入 x 的预测值 z
  • y = softmax(z): 使用 softmax 函数将输出 z 转化为概率分布。softmax 函数将模型的原始输出转化为每个类别的概率。
  • loss = cross_entropy_error(y, t): 使用交叉熵损失函数计算预测概率 y 和真实标签 t 之间的差异。交叉熵损失值越小,表示预测越接近真实值。
  • return loss: 返回损失值。

5. 实例化并测试网络

net = simpleNet()  # 生成 simpleNet 类的实例
print(net.W)  # 输出权重参数
x = np.array([0.6, 0.9])   
p = net.predict(x)
print(p)
print(np.argmax(p))  # 打印最大值的索引
  • net = simpleNet(): 创建一个 simpleNet 类的实例,初始化网络的权重。
  • print(net.W): 打印权重 W,它是一个 2×32 \times 3 的随机矩阵。
  • x = np.array([0.6, 0.9]): 定义输入数据 x,它是一个包含两个元素的数组。
  • p = net.predict(x): 使用 predict 方法计算输入 x 的预测结果 p
  • print(p): 打印预测结果 p
  • print(np.argmax(p)): 打印 p 中最大的值的索引。np.argmax(p) 返回数组 p 中最大元素的索引,通常用于分类任务,表示预测的类别。

6. 计算损失

t = np.array([0, 0, 1])  # 正确标签为2(即第三类)
print(net.loss(x, t))  # 求损失函数值
  • t = np.array([0, 0, 1]): 定义真实标签 t,这里标签为 [0, 0, 1],表示类别 2 的独热编码。
  • print(net.loss(x, t)): 计算输入数据 x 与标签 t 之间的交叉熵损失,并打印出来。

7. 计算梯度

def f(W):  # f(W) 是一个可以在求权重函数中使用的兼容参数,类似于将 W 重构成 f(W)?
    return net.loss(x, t)

dW = numerical_gradient(f, net.W)
print(dW)
  • def f(W): 定义了一个函数 f(W),该函数用于计算给定权重 W 下的损失值。这个函数将 net.loss(x, t) 封装在其中,并接受 W 作为参数。
  • dW = numerical_gradient(f, net.W): 使用 numerical_gradient 函数计算损失函数 f(W) 对权重 W 的数值梯度。numerical_gradient 会通过数值差分方法计算梯度。
  • print(dW): 打印计算得到的梯度 dW,它表示每个权重参数对于损失函数的敏感程度。

总结

这段代码展示了一个简单的神经网络的实现,包括了:

  1. 网络的初始化(权重的随机生成)。
  2. 前向传播过程(通过点积和 softmax 函数得到预测)。
  3. 损失函数的计算(使用交叉熵损失)。
  4. 计算损失对权重的梯度(使用数值梯度)。

通过这些步骤,代码展示了如何用 Python 构建一个简单的神经网络,并计算其梯度,从而为后续的优化(如梯度下降)做好准备。

function:激活函数softmax和求交叉熵误差

这段代码实现了两个常用的函数:softmaxcross_entropy_error,它们在神经网络中用于分类任务。接下来我会逐行解释这两个函数的作用和实现细节。

1. Softmax 函数

def softmax(x):
    if x.ndim == 2:
        x = x.T  # 转置,使得每一列代表一个样本
        x = x - np.max(x, axis=0)  # 减去每列的最大值,避免溢出
        y = np.exp(x) / np.sum(np.exp(x), axis=0)  # 对每列应用 softmax
        return y.T  # 转置回原来的形状

    x = x - np.max(x)  # 溢出对策,减去最大值避免指数溢出
    return np.exp(x) / np.sum(np.exp(x))  # 计算softmax
解释:
  • softmax 函数将一个向量或者矩阵(代表每个类的分数)转换成概率分布。它常用于神经网络的输出层,将原始的网络输出(称为“logits”)转换为类的概率。

  • if x.ndim == 2::检查输入 x 的维度。如果 x 是二维数组(形状为 batch_size x class_num),即处理的是多个样本(一个小批量的数据),则执行以下代码:

    • x = x.T:转置矩阵,使得每一列代表一个样本的数据。
    • x = x - np.max(x, axis=0):减去每列的最大值,防止在计算指数时溢出。因为大数的指数值会导致计算中的溢出。
    • y = np.exp(x) / np.sum(np.exp(x), axis=0):对每列的值应用 softmax 函数,得到每个类别的概率。np.exp(x) 对每个元素求指数,np.sum(np.exp(x), axis=0) 是对每列进行求和。
    • return y.T:最后将矩阵转置回原来的形状。
  • x = x - np.max(x):如果 x 是一个一维数组(单个样本),则直接减去最大值,避免指数计算时的溢出。

  • return np.exp(x) / np.sum(np.exp(x)):计算 softmax 输出,返回每个类别的概率。

Softmax 特点
  • 输入值经过 softmax 函数后,输出的概率值总和为 1。
  • 它将每个输出值转换为一个介于 0 和 1 之间的值,表示该类的预测概率。

2. 交叉熵损失函数(Cross-Entropy Error)

def cross_entropy_error(y, t):
    if y.ndim == 1:
        t = t.reshape(1, t.size)  # 如果标签是1D,转换为2D
        y = y.reshape(1, y.size)  # 如果输出是1D,转换为2D
        
    if t.size == y.size:
        t = t.argmax(axis=1)  # 将标签转换为类索引(对于one-hot编码)

    batch_size = y.shape[0]  # 获取批次大小
    return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size  # 计算平均交叉熵损失
解释:
  • if y.ndim == 1::检查 y 是否是一维数组。如果 y 是一维数组,表示只有一个样本,接着将标签和预测的 y 重塑为二维数组,便于处理。

  • if t.size == y.size::检查 ty 的尺寸。如果标签 t 和预测概率 y 的尺寸相同,则说明标签是 one-hot 编码。例如,标签为 [0, 0, 1],表示类别 2。argmax(axis=1) 将标签从 one-hot 编码转换为类别索引。即 t.argmax(axis=1) 变为 2

  • batch_size = y.shape[0]:获取样本的批次大小,即 y 的第一维的大小。

  • return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size

    • np.arange(batch_size):生成批次大小的数组(从 0 到 batch_size-1),表示样本的索引。
    • y[np.arange(batch_size), t]:从预测概率 y 中选取对应类别 t 的概率值。t 是每个样本的类别索引,y 是一个矩阵,y[i, t[i]] 会返回样本 i 在类别 t[i] 上的预测概率。
    • np.log(y[np.arange(batch_size), t] + 1e-7):对每个样本的预测概率取对数,1e-7 是防止概率值为 0,导致对数函数计算出无穷大。
    • np.sum(...)/batch_size:求和并计算平均值,返回批次的平均交叉熵损失。
交叉熵损失

交叉熵损失函数衡量了预测概率分布与实际标签之间的差异,特别适用于分类问题。它的值越小,表示模型的预测越准确。对于二分类任务和多分类任务,交叉熵是常用的损失函数。

总结:

  • Softmax 函数将神经网络的原始输出转化为概率分布,用于分类问题。
  • Cross-Entropy Error 计算模型的输出概率与实际标签之间的差异,用于量化模型的预测误差。

这两个函数常常一起使用,尤其是在多类分类任务中,softmax 用于生成分类概率,交叉熵损失用于衡量预测与真实标签的差异。

求函数的梯度值

这段代码实现了 数值梯度 的计算。数值梯度是通过有限差分方法来近似计算梯度的,常用于验证反向传播算法的正确性。接下来我将详细解释这段代码的每一部分。

1. numerical_gradient(f, x) 函数的作用

  • 函数输入:

    • f: 目标函数。它接受一个输入 x,并返回该输入对应的损失值。
    • x: 参数 x,是我们要计算梯度的输入,通常是模型的参数(如权重和偏置)。
  • 函数输出:

    • grad: 数值梯度,表示目标函数对每个参数 x 的导数,形状与 x 相同。

2. 初始化和设置

h = 1e-4  # 设定一个小的值,用于计算有限差分
grad = np.zeros_like(x)  # 创建一个与x相同形状的零矩阵,用于存储计算出来的梯度
  • h = 1e-4: 设定一个很小的值 h,用于在计算梯度时做微小的偏移。h 是差分方法中的步长,用来近似导数。
  • grad = np.zeros_like(x): 创建一个与 x 形状相同的零矩阵 grad,用来存储计算得到的梯度。

3. 使用 np.nditer 迭代 x

it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
  • np.nditer(x) 是 NumPy 中的一个迭代器,用于遍历 x 中的每一个元素。
  • flags=['multi_index']: 允许获取每个元素的多维索引。
  • op_flags=['readwrite']: 允许对 x 中的元素进行读取和修改。

4. 计算每个元素的数值梯度

while not it.finished:
    idx = it.multi_index  # 获取当前元素的多维索引
    tmp_val = x[idx]  # 保存当前元素的值
    x[idx] = float(tmp_val) + h  # 将当前元素加上h
    fxh1 = f(x)  # 计算 f(x + h)
    
    x[idx] = tmp_val - h  # 将当前元素减去h
    fxh2 = f(x)  # 计算 f(x - h)
    
    grad[idx] = (fxh1 - fxh2) / (2 * h)  # 通过中心差分法计算梯度
    
    x[idx] = tmp_val  # 还原当前元素的值
    it.iternext()  # 移动到下一个元素
  • while not it.finished:: 这是一个循环,直到迭代器遍历完 x 中的所有元素。

  • idx = it.multi_index: 获取当前元素的索引。

  • tmp_val = x[idx]: 保存当前元素的原始值,以便在计算后将其还原。

  • x[idx] = float(tmp_val) + h: 将当前元素的值加上 h,然后调用目标函数 f(x) 计算其值 fxh1

  • fxh1 = f(x): 计算函数在 x + h 处的值。

  • x[idx] = tmp_val - h: 将当前元素的值减去 h,然后计算函数 f(x)x - h 处的值 fxh2

  • fxh2 = f(x): 计算函数在 x - h 处的值。

  • grad[idx] = (fxh1 - fxh2) / (2 * h): 使用中心差分法计算梯度。中心差分法通过 (f(x+h) - f(x-h)) / (2 * h) 近似计算导数。

  • x[idx] = tmp_val: 还原当前元素的值,以便继续计算其他元素的梯度。

  • it.iternext(): 移动到下一个元素,继续计算梯度。

5. 返回结果

return grad
  • grad 是一个与 x 形状相同的矩阵,包含了 x 中每个元素的数值梯度。

数值梯度的原理

数值梯度通过有限差分方法来近似计算。对于给定的函数 f(x),某个元素 x_i 的导数可以通过以下公式来近似:

其中:

  • x + hx - h 分别表示对 x_i 添加和减去微小偏移量 h 后的值。
  • (f(x + h) - f(x - h)) / (2h) 是使用中心差分法近似计算的梯度。

使用场景

数值梯度主要用于验证反向传播算法的正确性。在训练神经网络时,计算梯度是一个关键步骤。反向传播算法是基于链式法则计算的梯度,而数值梯度可以作为一种“手工”计算梯度的方式,帮助我们检查反向传播是否实现正确。

总结

  • 这个 numerical_gradient 函数通过对 x 中每个元素添加和减去一个小的 h 来计算数值梯度,采用了中心差分法。
  • 数值梯度对于调试和验证梯度计算的正确性非常有用,特别是在训练神经网络时。

http://www.kler.cn/a/548515.html

相关文章:

  • 【鸿蒙】从网页打开获取文件,并转成base64
  • 【微服务学习一】springboot微服务项目构建以及nacos服务注册
  • Python爬虫技术
  • 【推理llm论文精度】DeepSeek-R1:强化学习驱动LLM推理能力飞跃
  • 如何在Spring Boot中配置分布式配置中心
  • 消息队列概要讲解
  • uniapp-列表样式
  • 安卓基础(Adapter)
  • Redisson分布式锁和同步器完整篇
  • BeginInvoke和Invoke的使用时机
  • [环境配置] 环境配置 - Java - Apache Tomcat 安装与配置
  • Mac之JDK安装
  • NS新金融:区块链时代的财富新引擎
  • 【做一个微信小程序】校园地图页面实现
  • Dbeaver 下载mysql驱动失败
  • HTTP相关面试题
  • 机器学习:k均值
  • 2025 AutoCable 中国汽车线束线缆及连接技术创新峰会启动报名!
  • 国内外网络安全政策动态(2025年1月)
  • 【前端框架】vue2和vue3的区别详细介绍