20. 深度学习 - 多层神经网络

在这里插入图片描述

Hi,你好。我是茶桁。

之前两节课的内容,我们讲了一下相关性、显著特征、机器学习是什么,KNN模型以及随机迭代的方式取获取K和B,然后定义了一个损失函数(loss函数),然后我们进行梯度下降。

可以说是又帮大家回顾了一下深度学习的相关知识,但是由于要保证整个内容的连续性,所以这也没办法。

那么接下来的课程里,咱们要来看一下神经网络,怎么样去拟合更加复杂的函数,什么是激活函数,什么是神经网络,什么是深度学习。

然后我们还要来学习一下反向传播,以及如何实现自动的反向传播,什么是错误排序以及怎么样自动的去计算元素的gradients。梯度怎么样自动求导。

从简单的线性回归函数到复杂的神经网络,从人工实现的求导到自动求导。那我们现在来跟大家一起来看一下。

上一节课结束的时候我们说过,现实生活中绝大多数事情的关系都不是线性的。

比方说,我工作的特别努力,然后就可以升职加薪了。但是其实有可能工作的努力程度和升职加薪程度之间的关系可能并不是一条直线的函数关系。

可能一开始不管怎么努力,薪水都没有什么大的变化,可是忽然有了一个机会,薪水涨的幅度很大,但是似乎没怎么努力。再之后,又趋于一条平行横轴的线,不管怎么努力都无法往上有提升。这是不是咱们这些社畜的真实写照?

在现实生活中有挺多这样的问题,这样的对应关系。比如艾宾浩斯曲线,再比如细菌生长曲线,很多很多。

经过刚刚的分析我们知道了除了线性函数(kx+b),还有一种常见的函数关系式,是一种s型的一种函数,这种s形的函数在我们整个计算机科学里我们称呼它为sigmoid函数:

S i g m o i d : f ( x ) = σ ( x ) = 1 1 + e − x \begin{align*} Sigmoid: f(x) = \sigma (x) = \frac{1}{1+e^{-x}} \end{align*} Sigmoid:f(x)=σ(x)=1+ex1

这是一个非常常见的函数。我们可以节用NumPy库来用代码将它实现出来:

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

把它的图像描述出来:

sub_x = np.linspace(-10, 10)
plt.plot(sub_x, sigmoid(sub_x))

Alt text

然后我们来利用一下这个函数,我们定义一个随机线性函数, 然后和sigmoid函数一起应用画5根不同的线:

def random_linear(x):
    k, b = random.random(), random.random()
    return k*x + b

for _ in range(5):
    plt.plot(sub_x, random_linear(sigmoid(random_linear(sub_x))))    

Alt text

这个里面起变化的就是k和b这两个参数,那我们来调节k和b的画,就可以变化这条曲线的样式。

除了以上这些函数,我们生活中还会遇到更复杂的函数,甚至很有可能是一个复杂的三维图像。

那这个时候,我们该如何去拟合这么多复杂的函数呢?

一个比较直接的方法,当然就是我们人为的去观察,比如这个类似于sin(x):

Alt text

还有这个k*sin(kx+b)+b:

Alt text

当然,这样理论上是可以的,每当我们遇到一个场景之后就自己去提出来一个函数模型,然后把函数模型让机器去拟合出来。

但是这样就会有一个问题,大家就会发现假如你是那个工作者,那你熊猫眼会很严重,因为我们要看到很多这样的场景。现实生活中的问题实在太多了,每一天我们都可能会遇到新的函数。

如果我们每观察一个情况就要去考察,去思考它的这个函数模型是什么,你就会发现你的工作量无穷无尽。而且你会发现一个问题:现在函数能够可视化的,但是如果在现实生活中有很多场景的函数是无法可视化的。

那么这个时候我们就需要其他的一些方法能够拟合更加复杂的函数,这个时候我们怎么样不通过去观察它就能够拟合出复杂的函数呢?其实很简单。

有一个老头子叫做Hinton,他是2018年图灵奖的获得者。

Alt text

他在一九八几年的时候发了一篇文章,经过他多年的研究,发现人的脑能够做出非常复杂的一些行为,其实我们这个神经元的类型都是很有限的,并没有很多奇怪的东西,就是有很多不同的节点,其实就那么几种。人类就能够进行复杂行为,背后其实就是一些基本的神经元的一些组合。

只不过这些基本的在组合还有一种形式,就是输入进来的会经过一个叫做activate neurons,就是激活单元,去做一个非线性变化。然后经过不断的这种非线性变化,最后就拟合出来非常复杂的信号。

那非线性变化的这些函数其实都是一样的,就他们背后的逻辑都是一样。只不过有的时候非线性变化的多,有的时候非线性变化的少。

讲了这么多不直观的东西,我们来看点实际的。

for _ in range(5):
    i = random.choice(range(len(sub_x)))
    output_1 = np.concatenate((random_linear(sub_x[:i]), random_linear(sub_x[i:])))
    plt.plot(sub_x, output_1)

Alt text

然后我们来做两件事,第一个是将k,b随机方式改成normalvariate(), 第二个在上面的基础上再做一次拆分:

def random_linear(x):
    k, b = random.normalvariate(0,1), random.normalvariate(0,1)
    return k * x + b

for _ in range(5):
    i = random.choice(range(len(sub_x)))
    linear_output = np.concatenate((random_linear(sub_x[:i]), random_linear(sub_x[i:])))
    i_2 = random.choice(range(len(linear_output)))
    output = np.concatenate((sigmoid(linear_output[:i_2]), sigmoid(linear_output[i_2:])))

    plt.plot(sub_x, output)   

Alt text

我们来看,这个时候你会发现他生成的这个图像比较奇怪。它生成了很多奇怪的函数,每一次根据不同的参数就形成了不同的函数图像。

迄今为止就两个函数,一个sigmoid,一个random linear,但是它生成了很多奇怪的函数。

面对这种函数,这么多层出不穷列举都列举不完的函数,我们怎么样能够每次遇到一个问题就得去提出它这个函数到底是什么样。而且关键是有可能函数维度高了之后都观察不到它是什么关系。

所以我们就会去考虑,怎么样能够让机器自动的去拟合出来更复杂的函数呢?

我们可以用基本模块经过组合拼接,然后就能够形成复杂函数。而在组合拼接的过程中,我们只需要让机器自动的去把K1、K2、B1、B2等等这些参数给它拟合出来就行。

也就是说我们可以通过参数的变化来拟合出来各种各样的函数。

这其实就是深度神经网络的一个核心思想。就是用基本的模块像大家玩积木一样,并不会有很多积木类型给你,只有一些基本的东西,但是通过这些基本的可以造出来特别多复杂的东西。

这个就是背后的原理,通过函数的复合和叠加。这种变化的引起都是由一个线性函数加上一个非线性函数。

其实很大程度上由我们大脑里边这种简单东西可以构成复杂东西得到了启示。只不过人的大脑里边,在脑神经科学里面把这种非线性变化呢叫做activate neurons,叫做激活神经元。在程序里,我们把这种非线性函数叫activation function,激活函数。

激活函数的作用就是为了让我们的程序能够拟合非线性关系。如果没有激活函数,咱们的程序永远只能拟合最简单的线性关系,而现实生活中绝大多数关系并不是并不是线性关系。

让机器来拟合更加复杂函数的这种方法,和我们的神经网络很像,就是咱们现在做的这个事情,我们就把它命了个名叫做神经网络.

早些年的时候科学家们有一个理论,人们把一组线性和非线性变化叫做一层。在以前的时候科学家们发现这个层数不能多于三层,就是神经网络的层数不能多于三层。

为什么不能多于3层?其实最主要的不是计算量太大的问题,最核心的原因是什么?

假设我们有一个f(x)和一个x组成一个平面坐标系,在其中有无数的点,当我们在做拟合的时候,发现了一条直线可以拟合,但是实际上呢,当我们将数据量继续放大的时候,才发现我们的拟合的直线偏离的非常厉害。

我们之前在机器学习的课程里说过,我们要有高精度,就需要有足够的数据量。如果这个时候变成一个三维问题,就需要更多的数据量。没有更多的数据的话,就好比有一个平板在空中,它会摇来摇去,你以为拟合了一个正确的平板,但其实完全不对。

那这个时候,每当我们所需要拟合的参数多一个,多少数据量认为是足够的?这个不一定。这个和整个问题的复杂程度有关系。

后来科学家们发现一个规律,在相似的问题下,我们需要拟合的参数多一个,需要的数据就要多一个数量级。

当变成三层的时候,会发现参数就更多了,而参数变得特别多就会需要特别多的数据量。而早在一九八几年、一九九几年的时候并没有那么多的数据量,就会产生数据量不够的情况,所以模型在现实生活中没法用。

但是随着到二零零几年,再到二零一几年之后,产生了大量的数据。就给我们做函数拟合提供了数据资源,所以数据量是最重要的,数据量决定了这个东西能不能做。而其他的一些,比方说计算、GPU啊等等,它是加速这个过程的,是让它更方便。

那么后来我们把层数超过3层的就叫深度神经网络,机器学习就简称深度学习。这是为什么深度学习在二零一几年的时候才开始火起来。

那现在我们把上一节课的这个问题再拿过来,现在来想想,如果我们把房价的函数关系也写成类似的,linear和sigmoid之间的关系,那会怎么样呢?

首先,我们的k和b就会多加一组:

for t in range(total_times):
    k1 = k1 + (-1) * loss对k1的偏导
    b1 = b1 + (-1) * loss对b1的偏导
    k2 = k2 + (-1) * loss对k2的偏导
    b2 = b2 + (-1) * loss对b2的偏导

    loss_ = loss(y, model(X_rm, k1, b1, k2, b2))
    ...

然后我们的model也会多接受了一组参数,并且我们要将其内部函数关系做一个叠加:

def model(x, k1, k2, b1, b2):
    linear1_output = k1 * x + b1
    sigmoid_output = sigmoid(linear1_output)
    linear2_output = k2 * sigmoid_output + b2

    return linear2_output

在这个时候我们要求解的时候就会发现有个问题,我们现在的loss是这样的:

l o s s = 1 N ( y i − y ^ ) 2 = 1 N [ y 1 − ( k 2 1 1 + e − ( k 1 x + b 1 ) + b 2 ) ] 2 \begin{align*} loss & = \frac{1}{N}(y_i - \hat y)^2 \\ & = \frac{1}{N} \begin{bmatrix} y_1 - (k_2 \frac{1}{1+e^{-(k_1x+b_1)}}+b_2) \end{bmatrix} ^2 \end{align*} loss=N1(yiy^)2=N1[y1(k21+e(k1x+b1)1+b2)]2

似乎变得有点过于复杂。当前情况下,我们是可以复杂的去求导,但是当函数继续复杂下去的时候,怎么把这个导数求出来呢?

函数还可以继续叠加, 层数还可以写的越来越多。那么怎么样才能给它求出它的导数呢?

我们再将上面的式子做个变化:

1 N [ l 2 ( σ ( l 1 ( x ) ) ) − y 1 ] 2 \frac{1}{N}[l_2(\sigma(l_1(x))) -y_1]^2 N1[l2(σ(l1(x)))y1]2

这样我们就可以将问题进行简化,我们上面代码里loss对k1的偏导就可以写成:

∂ l o s s ∂ l 2 ⋅ ∂ l 2 ∂ σ ⋅ ∂ σ ∂ l 1 ⋅ ∂ l 1 ∂ k 1 \frac{\partial loss}{\partial l_2} \cdot \frac{\partial l_2}{\partial \sigma} \cdot \frac{\partial \sigma}{\partial l_1} \cdot \frac{\partial l_1}{\partial k_1} l2lossσl2l1σk1l1

同理,loss对b1的偏导就是:

∂ l o s s ∂ l 2 ⋅ ∂ l 2 ∂ σ ⋅ ∂ σ ∂ l 1 ⋅ ∂ l 1 ∂ b 1 \frac{\partial loss}{\partial l_2} \cdot \frac{\partial l_2}{\partial \sigma} \cdot \frac{\partial \sigma}{\partial l_1} \cdot \frac{\partial l_1}{\partial b_1} l2lossσl2l1σb1l1

这个时候,问题就变成一个可解决的了。

∂ l o s s ∂ l 2 \frac{\partial loss}{\partial l_2} l2loss其实就等于 2 N ( l 2 − y 1 ) \frac{2}{N}(l_2 - y_1) N2(l2y1)

我们继续往后看第二部分,那么这个时候我们可以得到 l 2 = k 2 ⋅ σ + b 2 l_2 = k_2 \cdot \sigma + b_2 l2=k2σ+b2,那 ∂ l 2 ∂ σ \frac{\partial l_2}{\partial \sigma} σl2就等于 k 2 k_2 k2

再来看第三部分, σ ′ ( x ) = σ ( x ) ⋅ ( 1 − σ ( x ) \sigma'(x) = \sigma(x) \cdot (1- \sigma(x) σ(x)=σ(x)(1σ(x), 所以 ∂ σ ∂ l 1 = σ ⋅ ( 1 − σ ) \frac{\partial \sigma}{\partial l_1} = \sigma \cdot (1 - \sigma) l1σ=σ(1σ)

最后第四部分, ∂ l 1 ∂ k 1 = x \frac{\partial l_1}{\partial k_1} = x k1l1=x

这样,我们整个式子就应该变成这样:

2 N ( l 2 − y 1 ) ⋅ k 2 ⋅ σ ⋅ ( 1 − σ ) ⋅ x \begin{align*} \frac{2}{N}(l_2 - y_1) \cdot k_2 \cdot \sigma \cdot (1 - \sigma) \cdot x \end{align*} N2(l2y1)k2σ(1σ)x

这样的话,我们就把loss对于K1的偏导就求出来了,这里算是一个突破。本来看起来是很复杂的的一个问题,我们将其拆分成了这样的一种形式。那这种形式,我们把它称作「链式求导」。

但是现在其实还有个问题,这整个一串链式求导的东西是我们通过眼睛求出来的,但是现在怎么样让机器自动的把这一串东西写出来?就是机器怎么知道是这些数字乘到一起?

换句话说,我们现在把这个问题再形式化一下,定义一个问题。

给定一个模型定义,这个模型里边包含参数{k1, k2, b1, b2},我们要构建一个程序,让它能够求解出k1,k2,b1,b2的偏导是多少。

如果我们想解决这个问题,我们首先要思考一下, k 1 , k 2 , b 1 , b 2 , l 1 , l 2 , σ , y t r u e , l o s s k_1, k_2, b_1, b_2, l_1, l_2, \sigma, y_{true}, loss k1,k2,b1,b2,l1,l2,σ,ytrue,loss, 它们之间是一种什么样的关系。

观察一下我们会发现它们之间的关系是这样的:

{ k 1 , b 1 , x } → l 1 → σ , { σ , k 2 , b 2 } → l 2 , { l 2 , y t r u e } → l o s s → 表示的 是 ′ 输出 到 ′ 的关系。 \begin{align*} & \{k_1, b_1, x\} \to l_1 \to \sigma, \\ & \{\sigma, k_2, b_2\} \to l_2, \\ & \{l_2, y_{true}\} \to loss \\ & \to 表示的是'输出到'的关系。 \end{align*} {k1,b1,x}l1σ,{σ,k2,b2}l2,{l2,ytrue}loss表示的输出的关系。

要用计算机去表示这种关系,是典型的一个数据结构问题,怎么样让计算机合理的去存储它,你会发现这个是一个图案。

这种节点和节点之间通过关系连接起来的就把它叫做图,我们把它先表示成图的样子。

computing_graph = {
    'k1': ['L1'],
    'b1': ['L1'],
    'x': ['L1'],
    'L1':['sigmoid'],
    'sigmoid': ['L2'],
    'k2': ['L2'],
    'b2': ['L2'],
    'L2': ['loss'],
    'y': ['loss']
}

nx.draw(nx.DiGraph(computing_graph), with_labels = True)

Alt text

这个就是我们要表达的一个关系, 我们把这个变成图。

现在我们将给定的一个model,这样一个函数变成了这样一张图。计算机里有现成的各种各样的图算法,我们就可以来计算这个图之间的关系了。

现在我们就要根据这个图的表示来思考我们如何求loss对K1的偏导。那其实,我们可以发现k1在末尾出,一直在向前输入直到loss。换句话说,我们可以通过k1一直往图的终点去寻找来找到它求导的这个过程。

也就是说,只要我们的能把模型变成一个图,然后我们就可以根据这些点去找到它们之间节点的对应关系,我们就可以通过这个节点关系来获得它的求导过程了。

那下一节课呢,我们就继续来看一下,如何将这个图的关系,变成一个自动求导的过程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/138731.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ubuntu上安装edge浏览器

1下载edge浏览器 官网下载 edge浏览器的linux版本可在上面的官网中寻找。 我选择的是Linux(.deb)。 2 安装 可在终端的edge安装包所在的路径下输入下面命令安装。 sudo dpkg -i edge安装包的名称.deb3 安装可能存在的问题 1dpkg:依赖关系问题使得edge-stable的配置工作不…

【NLP】理解 Llama2:KV 缓存、分组查询注意力、旋转嵌入等

LLaMA 2.0是 Meta AI 的开创性作品,作为首批高性能开源预训练语言模型之一闯入了 AI 场景。值得注意的是,LLaMA-13B 的性能优于巨大的 GPT-3(175B),尽管其尺寸只是其一小部分。您无疑听说过 LLaMA 令人印象深刻的性能,但您是否想知…

idea 插件推荐第二期

文章目录 便捷开发CodeGlance Pro (代码缩略图)GenerateAllSetter(快速生成对象所有set方法)GsonFormatPlus:json转实体RestfulToolkitX(找到controller快捷请求接口) 美化activate-power-mode-x (敲击计数、动效)Nyan…

nginx -s reload, 提示 [emerg] duplicate location “/“

问题描述: 执行nginx -s reload, 提示 duplicate location / 问题原因: 是因为location / 有两个,匹配重复了 解决方案: 把其中一个location / 注释掉,即可

Swift--基本运算符与程序流程控制

系列文章目录 第一章:量值与基本数据类型 第二章:字符、字符串与集合类型 文章目录 系列文章目录空合并运算符区间运算符循环结构while与repeat-while条件循环结构 空合并运算符 可选值类型是Swift语言的一个独特之处,空合并运算符就是针对…

「Verilog学习笔记」优先编码器电路①

专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点,刷题网站用的是牛客网 timescale 1ns/1ns//优先编码器电路1 //电路的优先顺序是,从9到1,高级到低级 //9个输入端:I端,4个输出端:Y端&am…

U盘怎么加密?U盘数据该怎么加密?

在使用U盘的过程中,我们会将很多重要数据存放在电脑中,但为了保护文件的安全性,我们需要使用加密来进行保护。那么,U盘数据该怎么加密呢? U盘数据加密方法 想要将普通U盘变成加密U盘,我们需要使用专业的U盘…

【JVM】类加载器 Bootstrap、Extension、Application、User Define 以及 双亲委派

以下环境为 jdk1.8 两大类 分类成员语言继承关系引导类加载器bootstrap 引导类加载器C/C无自定义类加载器extension 拓展类加载器、application 系统/应用类加载器、user define 用户自定义类加载器Java继承于 java.lang.ClassLoader 四小类 Bootstrap 引导类加载器 负责加…

js编辑只取修改后的对象的属性和值

需求:在el-table的列表编辑操作后, 第一步:获取当前行数据,为对象:{}, 第二步:数据回填 第三步:编辑 第四步:请求后端接口 本文章操作就是在编辑完成后,只取编…

npm install导致的OOM解决方案

文章目录 问题记录解决方法Linux重启排查方法 如何排查Linux自动重启的原因 问题记录 我在华为云服务器配置npm开发环境的时候, SSH远程连接一直掉线,无奈提了工单,被告知是NPM install导致的OOM问题。无语了,破NPM还有这个问题呢…

项目二:家庭记账系统

目录 一:目标 二:需求说明 2.1:基本资金和收支明细的记录 2.2:键盘访问的实现 三:实现步骤 3.1:实现主程序结构 3.2:实现收入和支出登记处理 3.3:工具类的使用 四:代…

跨境电商的数据安全:隐私保护的前沿

在数字时代,跨境电商已经成为了国际贸易的重要组成部分,为企业和消费者提供了便捷的购物和销售途径。然而,这一快速增长的领域也伴随着数据安全和隐私保护的挑战。本文将深入探讨跨境电商的数据安全问题,以及行业在维护用户隐私权…

第七章 块为结构建模 P5|系统建模语言SysML实用指南学习

仅供个人学习记录 应用泛化对分类层级建模 继承inherit更通用分类器的公共特性,并包含其他特有的附加特性。通用分类器与特殊分类器之间的关系称为泛化generalization 泛化由两个分类器之间的线条表示,父类端带有空心三角形箭头 块的分类与结构化特性…

Haskell添加HTTP爬虫ip编写的爬虫程序

下面是一个简单的使用Haskell编写的爬虫程序示例,它使用了HTTP爬虫IP,以爬取百度图片。请注意,这个程序只是一个基本的示例,实际的爬虫程序可能需要处理更多的细节,例如错误处理、数据清洗等。 import Network.HTTP.Cl…

想要轻松上手Docker?Portainer帮你实现零基础快速部署!

Portainer简介 前面我们已经讲解了安装docekr和使用docker run 部署容器了,但是这些可能需要使用CLI(命令行参数)才能快乐的玩耍,因此可以使用一个工具在web界面进行配置管理容器与镜像,方便大家使用。 Portainer是一款Docker容器管理系统&am…

《QT从基础到进阶·二十一》QGraphicsView、QGraphicsScene和QGraphicsItem坐标关系和应用

前言: 我们需要先由一个 QGraphicsView,这个是UI显示的地方,也就是装满可见原色的Scene,然后需要一个QGraphicsScene 用来管理所有可见的界面元素,要实现UI功能,我们需要用各种从QGraphicsItem拼装成UI控件…

sqlmap requires ‘python-pymysql‘ third-party library

使用sqlmap进行udf提权报错: [14:06:04] [CRITICAL] sqlmap requires python-pymysql third-party library in order to directly connect to the DBMS MySQL. You can download it from https://github.com/PyMySQL/PyMySQL. Alternative is to use a package pyt…

高频SQL50题(基础班)-4

文章目录 主要内容一.SQL练习题1.1789-员工的直属部门代码如下(示例): 2.610-判断三角形代码如下(示例): 3.180-连续出现的数字代码如下(示例): 4.1164-指定日期的产品价格代码如下(示例&#x…

【深度学习】pytorch——常用工具模块

笔记为自我总结整理的学习笔记,若有错误欢迎指出哟~ 深度学习专栏链接: http://t.csdnimg.cn/dscW7 pytorch——常用工具模块 数据处理 torch.utils.data模块DatasetDataLoadersamplertorch.utils.data的使用 计算机视觉工具包 torchvisiontorchvision.d…

在Android上使用Jetpack Compose定制下拉刷新

在Android上使用Jetpack Compose定制下拉刷新 在Jetpack Compose中向LazyList添加下拉刷新非常简单。说真的,只需几行代码。然而,默认的外观和感觉并不是那么令人满意。我们希望做得更好一些,类似于iOS版本:当用户向下拉动列表时…