目录
ReLU激活函数——rectified linear unit
感知机
人工智能最早最早的一个模型
给定输入x向量,权重w向量,和偏移b标量,感知机输出:
其中<w,x>指为两个向量做内积
感知机是一个简单的二分类问题:输出 0 or 1
VS.线性回归输出为实数
VS.Softmax回归输出为概率
训练感知机
当年最早是怎么训练感知机的呢?
initialize w = 0 and b = 0
repeat
if yi[<w,xi> + b] <= 0 then // <=0意味着分类感知机把分类预知错了
w <- w + yixi and b <- b + yi // 因为分类错误,所以更新一次权重
end if
until all classified correctly
早期感知机将循环此段代码直到所有分类结果正确 ,也存在什么所谓的随机梯度下降,因为最早期的感知机就是一遍一遍的扫数据,没有随机的概念
其实这段代码等价于使用批量大小为1的梯度下降,并使用如下的损失函数:
批量大小为1意思是:每次运算完一个样本就会去进行一次参数更新
算式表明,当感知机预测正确时,y<w,x>会大于0,而max函数会使结果为0,即梯度为0。表示不会更新参数权重;如果分类错误,这时候就会存在梯度,则开始更新权重参数
为什么 yi[<w,xi> + b] <= 0 意味着分类分错了:
1、如果 <w,xi> + b >= 0 表示感知机分为正类,如果 yi 也为正类则结果一定 >0
2、如果 <w,xi> + b <= 0 表示感知机分为负类,如果 yi 也为负类则,负负得正,结果也一定 >0
3、因此当出现结果 <=0 时,则意味着感知机分类出现错误
收敛定理
- 数据在半径 r 内:假设所有数据都可以被囊括在一个半径为r的圆内
- 余量ρ分类两类:意味着要求所有类别都被分类正确并且在分界线附近是存在一定余量的
- 感知机保证在
步后收敛:r —其实代表了数据的大小,所以当我们 r 很大的时候,肯定需要走很多很多步,才能基本遍历完;ρ — 表示的是你的数据是否易于分类,ρ越大则说明两个类本身之间间隔就非常大,肯定很好分类,反之则不易分类,需要走更多步。
感知机的缺陷
XOR问题(Mincky & Papert,1969)
感知机不能拟合XOR函数,它只能产生线性分割面
直接导致了AI的第一个寒冬!!当时大家觉得,我搞这么大一个装置(因为当时一个参数就是一条数据线,直接是现实连接的)在那里,结果这么简单一个问题你都解决不了......
十年二十年之后人们发现,这个问题其实可以解决,解决办法就是——多层感知机
总结
- 感知机是一个二分类模型,是最早的AI模型之一
- 它的求解算法等价于使用批量大小为1的梯度下降
- 它不能拟合XOR函数,导致了第一次AI寒冬
多层感知机
那我们继续从XOR问题入手!
学习XOR
我们看到这张图,图中的y(蓝色的线)表示:区分 x 的正负;x(黄色的线)表示:区分 y 的正负,所以我们可以得出下图
product表示一次异或运算,相同赋值正;不同赋值负
将这一整个判断过程画成图,就如下图所示:
x、y数据依次进入我们的黄色分类器和蓝色分类器,最终进入灰色分类器。由灰色分类器就可以得到我们的正确结果
我们发现,当我们一个判断判断不了的时候,就多进入几个不同的判断,最终可以拼出我们需要的结果,这就是多层感知机的思路
单隐藏层
首先我们的输入有X1,X2,X3,X4,假设我们有五个隐藏层,那我们四个输入会依次进入五个隐藏层,得到的输出将作为输入,输入到输出层,最终由输出层输出
输入大小基本是不能改的,有多少数据就有多少输入;输出也同理,你有多少类就有多少输出;但是隐藏层的大小是一个超参数,是可以人为设置的
单分类
- 输入
- 隐藏层:假设我们有m个隐藏层,而有多少隐藏层就有多少偏置
- 输出层
-
是按元素的激活函数,o为输出标量
为什么我们需要非线性激活函数?
因为线性函数无论如何与线性函数做运算,结果始终为一个线性函数,那仍然可以视为只进行了一次判断,仍为单层感知机!这是容易犯错的一个地方
多类分类
假设我们要做 k 类分类,即我们有 k 个输出。因为分类需要拿到置信度结果,所以我们把 k 类拿到softmax里面去运算,最终得到 Y1 ~ Yk 的输出
Softmax是干啥的:
其实也没干啥,就是把所有输入拉到(0,1)之间,使得 O1+O2+...+Ok = 1
即.求得是一个概率问题
然后我们会发现...Softmax回归和多层感知机分类其实没有本质区别。唯一区别就是多层感知机比Softmax回归多了隐藏层,如果隐藏层为 0,则就是Softmax回归
那我们来进一步看多类分类的定义
会发现它和单分类定义唯一的区别就是输出层从一个向量变成了一个形状为 m x k 的矩阵 ,偏置 b 也变成了一个长为 k 的向量。
多隐藏层
那多个隐藏层的定义公式长啥样呢?下方是一个三层隐藏层的例子
一定要记得!!每层一份激活函数!!!不然少几个激活函数就少几层隐藏层 ,输出就不需要激活函数的,激活函数主要是用来避免层数的塌陷。
在多隐藏层中,我们有两个可以人为设定的超参数
- 隐藏层层数
- 每层隐藏层的大小
通过上面两个超参数,可以配置模型的隐藏层,使其更符合不同问题的需要。是有一些公认好用的隐藏层配置的:
- 一般第一个隐藏层h1可以配置的稍微大一些。
很好理解,隐藏层越大,模型就越复杂。当我问题很复杂的时候,一般有两种解决思路:
1、用单隐藏层,把每层大小设置的大一些(假如我的输入是128,那我隐藏层可以做 64 或者是 128,甚至 256)
2、我用三个隐藏层,一般配置是h1最大,h2比h1小一点点,h3比h2小一点点。因为当你数据复杂的时候,一般来说隐藏层是很大的(128 or 256),但是你的输出是很小的(5类 or 10类)。意味着你要把一个128维的数据压缩到一个5维空间里面,最好的办法就是,慢慢的压缩回去!一次压太狠很有可能损失太多东西
E.G.128 -> 64 -> 32 -> 16 -> 8 -> 你的输出
那就意味着如果我们采用第二种方法,我们的h1就可以稍微的大一些!
一般来说也没有人反过来(比如先压倒2维再扩到128),还是那句话,一口气压太狠容易损失特征,损失的特征后期是不好还原回来的!
突然觉得机器学习本身干的事情就是不断给数据做扩充,压缩,扩充,压缩哈哈哈
总结
- 多层感知机使用隐藏层和激活函数来得到非线性模型
- 超参数为隐藏层层数和各个隐藏层的大小
- 使用Softmax来处理多类分类
激活函数
由多层感知机,就可以引入到激活函数这一趴。要始终记得:激活函数的存在是为了避免层数塌陷,所以每有一层隐藏层,就需要一个激活函数!
Sigmoid激活函数
首当其冲的就是Sigmoid激活函数,最简单也是最经典的激活函数
它就是对于 x 来说,我不管你是什么值,我就是给你投到一个0~1的区间里面,一个开区间
Sigmoid激活函数相较于 (x) 而言,没有那么硬,
(x)在 0 时存在两个尖锐的直角,并不可导,而Sigmoid激活函数图像如下所示:
Tanh激活函数
将输入投影到(-1,1)
其实和Sigmoid激活函数很像,但是Tanh是将输入投射到(-1,1)之间,所以它仍然是一个soft版本的 (x)
VS.Sigmoid激活函数 -> 将输入投射到(0,1)
ReLU激活函数——rectified linear unit
它的优势在于:便宜!!!
我们回看前面几个激活函数,基本都是一个指数运算打底的计算量,在算力消耗上面指数运算时一件很贵的事情。这个激活函数直接一个max大法搞定一切!
吐槽:感觉深度学习在2024年以前提出的概念就是把一些已有的东西给你重命名一下,搞得高大上起来=。=
多层感知机的从零开始实现
这里导入了沐神的《动手学PyTorch》里面的一个d2l包
import torch
from torch import nn
from d2l import torch as d2l
batch_size = 256 # 批量大小为256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size) # 导入数据集
实现一个具有单隐藏层的多层感知机,它包含256个隐藏单元
num_inputs, num_outputs, num_hiddens = 784, 10, 256 # 256为超参,选了一个784和10之间的一个数
W1 = nn.Parameter( # 声明是torch的一个参数Parameter
torch.randn(num_inputs, num_hiddens, requires_grad = True)) # 随机生成一个784*256的矩阵
requires_grad = True:因为要更新,所以需要梯度
b1 = nn.Parameter(
torch.zero(num_hiddens, requires_grad = True)) # 偏置就是隐藏层的个数,可以初始化为0
W2 = nn.Parameter(
torch.randn(num_hiddens, num_outputs, requires_grad = True))
b2 = nn.Parameter(
torch.zeros(num_outputs, requires_grad = True))
params = [W1, b1, W2, b2] # 所有的参数
为什么参数W是随机生成而偏置却可以初始化为0呢?
因为如果参数设置为0,即梯度也为0,相当于没有梯度,导致参数不会更新,相当于隐藏层只有一个单元
实现ReLU激活函数
def relu(X):
a = torch.zeros_like(X) # 生成一个和 X 数据类型、形状都一样的 a,但元素全为 0
return torch.max(X, a) # 其实就是求 X 和 0 的最大值
实现模型
def net(X):
X = X.reshape((-1, num_inputs)) # 把数据集拉成一个二维矩阵
H = relu(X @ W1 + b1) # @:矩阵乘的简写
return (H @ W2 + b2)
loss = nn.CrossEntropyLoss() # 交叉熵损失函数,会自动执行Softmax运算
多层感知机的训练过程与softmax回归的训练过程完全相同
num_epochs, lr = 10, 0.1 # 跑10轮,每轮batch_size个数据,学习率为0.1
updater = torch.optim.SGD(params, lr = lr)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, updater)
多层感知机的简洁实现
通过高级API更简洁地实现多层感知机
import torch
from torch import nn
from d2l import torch as d2l
隐藏层包含256个隐藏单元,并使用了ReLU激活函数
net = nn.Sequential(
nn.Flatten(), # Flatten:展平,将一个三维的数据展为二维
nn.Linear(784, 256),
nn.ReLU(),
nn.Linear(256, 10))
def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std = 0.01)
net.apply(init_weights)
训练过程
batch_size, lr, num_epochs = 256, 0.1, 10
loss = nn.CrossEntropyLoss()
trainer = torch.optim.SGD(net.parameters(), lr = lr)
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
d2l.train_ch3(net, train_iter, test_iter, lr, num_epochs, batch_size)
QA
1、是因为感知机只能解决XOR问题所以人们才会使用SVM吗?
NO!可以说SVM替代了感知机,但多层感知机解决了XOR的问题之后没有流行起来是因为两个问题:
(1)需要选超参数,这是一个老中医的技能
(2)收敛也不好收敛,需要各种调整学习率啊bulabula
SVM的好处是没有这么多超参数可以选,SVM对超参数不敏感。而且SVM优化相对来说更容易一些。
其实SVM和MLP效果差不多,但是SVM的数学证明很漂亮,具有很强的可解释性。那对于学术界来讲,在实用性都差不多的情况下,当然会追求更有数学性的东西。这导致在90年代SVM一直是机器学习的主流。
但!!用MLP好改其他神经网络,只需要改一点点,优化算法什么的都不需要变,而SVM需要全改
2、XOR函数有什么应用?
没啥用,就是为了证明感知机的局限性,证明没有办法用一层感知机去拟合全世界,一个四个样本二维数据的问题都解决不了
俗称:抬杠用的....
3、 为什么神经网络要增加隐藏层数而不是神经元个数?
其实是都可以的,但是问题是神经元个数多的模型,不好训练哈哈哈。。。特别容易过拟合(毕竟没可能一口气在一层里面吃个胖子)。增加层数的模型更好训练一些,类似每一层只学一个简单的东西,这种方法更容易找到一个解,所以叫深度学习!
4、ReLU为什么管用?它在大于 0 的部分也就只是线性变化,为什么可以促进学习?激活的本质是什么?不是为模型引入非线性吗?
首先,ReLU是一个非线性模型!!不是一个线性函数 !线性函数一定是f(x) = ax + b,无论如何一定是一条线!而ReLU不是一个线性, 所以加入ReLU之后就有多层了。
确实,激活函数的本质是引入非线性,它唯一的作用就是这个。
甚至于,激活函数本身对于精度的提升没有超参数有作用。