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

神经网络的梯度实现

梯度

上篇文章我们按变量分别计算了x0和x1的偏导数。现在,我们希望一起计算x0和x1的偏导数。比如,我们来考虑求x0 = 3, x1 = 4时(x0, x1)的偏导数(αf/αx0,αf/αx1) 。这样的由全部变量的偏导数汇总而成的向量称为梯度(gradient)。梯度可以像下面这样来实现。

import numpy as np


def function_2(x):
    return x[0] ** 2 + x[1] ** 2


def numerical_gradient(f, x):
    h = 1e-4  # 0.0001
    grad = np.zeros_like(x)  # 生成和x形状相同的数组
    for idx in range(x.size):  # 如X[3,4],idx=0,1
        tmp_val = x[idx]
        x[idx] = tmp_val + h  # f(x+h)的计算
        fxh1 = f(x)
        x[idx] = tmp_val - h  # f(x-h)的计算
        fxh2 = f(x)
        grad[idx] = (fxh1 - fxh2) / (2 * h)  # 梯度的计算
        x[idx] = tmp_val  # 还原X[3,4]
    return grad


print(numerical_gradient(function_2, np.array([3.0, 4.0])))  # [6. 8.]

为了更好地理解,我们把f(x0,x1)=x02+x12的梯度画在图上。不过,这里我们画的是元素值为负梯度的向量,负梯度方向是梯度法中变量的更新方向。
在这里插入图片描述
可以看出,梯度会指向各点处的函数值降低的方向。更严格地讲,梯度指示的方向是各点处的函数值减小最多的方向。

梯度法

机器学习的主要任务是在学习时寻找最优参数。同样地,神经网络也必须在学习时找到最优参数(权重和偏置)。这里所说的最优参数是指损失函数。取最小值时的参数。但是,一般而言,损失函数很复杂,参数空间庞大,我们不知道它在何处能取得最小值。而通过巧妙地使用梯度来寻找函数最小值(或者尽可能小的值)的方法就是梯度法。

在梯度法中,函数的取值从当前位置沿着梯度方向前进一定距离,然后在新的地方重新求梯度,再沿着新梯度方向前进,如此反复,不断地沿梯度方向前进。像这样,通过不断地沿梯度方向前进,逐渐减小函数值的过程就是梯度法(gradient method)。梯度法是解决机器学习中最优化问题的常用方法,特别是在神经网络的学习中经常被使用。根据目的是寻找最小值还是最大值,梯度法的叫法有所不同。严格地讲,寻找最小值的梯度法称为梯度下降法(gradient descent method),寻找最大值的梯度法称为梯度上升法(gradient ascent method)。但是通过反转损失函数的符号,求最小值的问题和求最大值的问题会变成相同的问题,因此“下降”还是“上升”的差异本质上并不重要。一般来说,神经网络(深度学习)中,梯度法主要是指梯度下降法。

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

import numpy as np


def function_2(x):
    return x[0] ** 2 + x[1] ** 2


def numerical_gradient(f, x):
    h = 1e-4  # 0.0001
    grad = np.zeros_like(x)  # 生成和x形状相同的数组
    for idx in range(x.size):  # 如X[3,4],idx=0,1
        tmp_val = x[idx]
        x[idx] = tmp_val + h  # f(x+h)的计算
        fxh1 = f(x)
        x[idx] = tmp_val - h  # f(x-h)的计算
        fxh2 = f(x)
        grad[idx] = (fxh1 - fxh2) / (2 * h)  # 梯度的计算
        x[idx] = tmp_val  # 还原X[3,4]
    return grad


def gradient_descent(f, init_x, lr=0.01, step_num=200):
    x = init_x
    for i in range(step_num):
        grad = numerical_gradient(f, x)
        x -= lr * grad
    return x


print(gradient_descent(function_2, np.array([-3.0, 4.0])))  # [-0.05276384  0.07035179]
print(gradient_descent(function_2, np.array([-3.0, 4.0]), lr=10))  # [-2.58983747e+13 -1.29524862e+12]
print(gradient_descent(function_2, np.array([-3.0, 4.0]), lr=1e-4))  # [-2.88235679  3.84314238]

可以看出,学习率过大的话,会发散成一个很大的值;反过来,学习率过小的话,基本上没怎么更新就结束了。也就是说,设定合适的学习率是一个很重要的问题。
像学习率这样的参数称为超参数。这是一种和神经网络的参数(权重和偏置)性质不同的参数。相对于神经网络的权重参数是通过训练数据和学习算法自动获得的,学习率这样的超参数则是人工设定的。一般来说,超参数需要尝试多个值,以便找到一种可以使学习顺利进行的设定。

神经网络的梯度实现

神经网络的学习也要求梯度。这里所说的梯度是指损失函数关于权重参数的梯度。比如,有一个只有一个形状为2 × 3的权重W的神经网络,损失函数用L表示。此时,梯度可以用αL/αW表示。用数学式表示如下:
在这里插入图片描述
下面,我们以一个简单的神经网络为例,来实现求梯度的代码。为此,我们要实现一个名为simpleNet的类:

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

为什么要建立一个类呢?我们要求L和W的梯度,而我们的损失函数与x和t相关,t是正确解标签,是常数,所以问题在于怎样将w与x联系起来。所以建立一个类,求损失函数时,w改变x随之改变。程序完整实现如下:

import os
import sys

import numpy as np

sys.path.append(os.pardir)


def softmax(a):
    c = np.max(a)
    exp_a = np.exp(a - c)  # 溢出对策
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    return y


def cross_entropy_error(y, t):
    if y.ndim == 1:
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)
    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)  # 生成和x形状相同的数组
    for idx in range(x.size):  # 如X[3,4],idx=0,1
        tmp_val = x[idx]
        x[idx] = tmp_val + h  # f(x+h)的计算
        fxh1 = f(x)
        x[idx] = tmp_val - h  # f(x-h)的计算
        fxh2 = f(x)
        grad[idx] = (fxh1 - fxh2) / (2 * h)  # 梯度的计算
        x[idx] = tmp_val  # 还原X[3,4]
    return grad


def numerical_gradient_2d(f, x):
    if x.ndim == 1:
        return numerical_gradient(f, x)
    else:
        grad = np.zeros_like(x)

        for idx, x in enumerate(x):
            grad[idx] = numerical_gradient(f, x)
    return grad


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


net = simpleNet()
x = np.array([0.6, 0.9])
t = np.array([0, 0, 1])  # 正确解标签

f = lambda w:net.loss(x, t)
dw = numerical_gradient_2d(f,net.W)
print(dw)

相关文章:

  • 网络版本计算器(再谈“协议“)
  • C++——string的简单使用与深浅拷贝的理解(建议收藏)
  • BGP综合实验
  • Day4——两两交换链表节点、删除链表第n个绩点、链表相交、环形链表||
  • YOLOv5实现佩戴安全帽检测和识别(含佩戴安全帽数据集+训练代码)
  • H.264 入门篇 - 10 (帧间预测 - 参考帧列表修改/重排)
  • 第三章Linux环境基础开发工具使用(yum+rzsz+vim+g++和gcc+gdb+make和Makefile+进度条+git)
  • 博客系统数据库设计与实现之修改(java)
  • django-设置X-Frame-Options响应头防止点击劫持攻击
  • PostgreSQL自增主键的用法以及在mybatis中的使用
  • app逆向(10)| APP的加固与脱壳
  • 解析IEC 61850通信规约
  • 了解Selenium,看这一篇够了
  • 你就想这样一辈子躺平,还是改变这个世界?
  • Xmake基础----自定义脚本介绍
  • 【译】JS基础算法脚本:字符串结尾
  • 【跃迁之路】【519天】程序员高效学习方法论探索系列(实验阶段276-2018.07.09)...
  • - C#编程大幅提高OUTLOOK的邮件搜索能力!
  • IDEA常用插件整理
  • Java IO学习笔记一
  • Js基础知识(四) - js运行原理与机制
  • python docx文档转html页面
  • rc-form之最单纯情况
  • React系列之 Redux 架构模式
  • Vue源码解析(二)Vue的双向绑定讲解及实现
  • 将回调地狱按在地上摩擦的Promise
  • 理解 C# 泛型接口中的协变与逆变(抗变)
  • 如何正确配置 Ubuntu 14.04 服务器?
  • 使用iElevator.js模拟segmentfault的文章标题导航
  • 数据结构java版之冒泡排序及优化
  • 如何通过报表单元格右键控制报表跳转到不同链接地址 ...
  • ​ 轻量应用服务器:亚马逊云科技打造全球领先的云计算解决方案
  • ​520就是要宠粉,你的心头书我买单
  • ​MySQL主从复制一致性检测
  • #Linux(Source Insight安装及工程建立)
  • #我与Java虚拟机的故事#连载08:书读百遍其义自见
  • $redis-setphp_redis Set命令,php操作Redis Set函数介绍
  • ( 用例图)定义了系统的功能需求,它是从系统的外部看系统功能,并不描述系统内部对功能的具体实现
  • (DFS + 剪枝)【洛谷P1731】 [NOI1999] 生日蛋糕
  • (pytorch进阶之路)扩散概率模型
  • (非本人原创)史记·柴静列传(r4笔记第65天)
  • (附源码)springboot电竞专题网站 毕业设计 641314
  • (附源码)springboot高校宿舍交电费系统 毕业设计031552
  • (附源码)springboot太原学院贫困生申请管理系统 毕业设计 101517
  • (附源码)ssm基于微信小程序的疫苗管理系统 毕业设计 092354
  • (四)鸿鹄云架构一服务注册中心
  • (一)RocketMQ初步认识
  • (转)h264中avc和flv数据的解析
  • (转)socket Aio demo
  • (转)平衡树
  • *_zh_CN.properties 国际化资源文件 struts 防乱码等
  • .net mvc actionresult 返回字符串_.NET架构师知识普及
  • .NET 分布式技术比较
  • .NET和.COM和.CN域名区别
  • .NET使用HttpClient以multipart/form-data形式post上传文件及其相关参数