目录
1.理论部分
范数
MP神经元
万有逼近定理
神经网络中每一层的作用
2.线性回归的从零开始实现
生成数据集
读取数据集
初始化模型参数
定义模型
定义损失函数
定义优化算法
训练
3.螺旋数据分类(spiral classification)
构建螺旋数据集
构建线性模型分类
激活函数
常见的几种激活函数
在网络中加入激活函数
4.小结
1.理论部分
范数
范数定义了向量空间里的距离,它的出现使得向量之间的比较成为了可能。
简单理解:范数可以把一组实数列表,映射成一个实数。
L1范数,也叫曼哈顿距离:是一个向量中所有元素的绝对值之和。
L2范数,也叫欧几里得范数:是一个向量中所有元素取平方和,然后再开平方。pytorch中的norm()为L2范数。
在深度学习中,我们经常试图解决优化问题:最⼤化分配给观测数据的概率; 最⼩化预测和真实观测之间的 距离。⽤向量表⽰物品(如单词、产品或新闻⽂章),以便最⼩化相似项⽬之间的距离,最⼤化不同项⽬之间 的距离。⽬标,或许是深度学习算法最重要的组成部分(除了数据),通常被表达为范数。
MP神经元
万有逼近定理
如果一个隐层包含足够多的神经元,三层前馈神经网络(输入-隐层-输出)能以任意精度逼近任意预定的连续函数。
当隐层足够宽时,双隐层感知器(输入-隐层1-隐层2-输出)可以逼近任意非连续函数:可以解决任何复杂的分类问题。
神经网络中每一层的作用
2.线性回归的从零开始实现
%matplotlib inline
#使用%matplotlib命令可以将matplotlib的图表直接嵌入到Notebook之中
#或者使用指定的界面库显示图表,它有一个参数指定matplotlib图表的显示方式
#inline表示将图表嵌入到Notebook中。
import random
import torch
from d2l import torch as d2l
生成数据集
为了简单起见,我们将根据带有噪声的线性模型构造一个人造数据集。 我们的任务是使用这个有限样本的数据集来恢复这个模型的参数。 我们将使用低维数据,这样可以很容易地将其可视化。 在下面的代码中,我们生成一个包含1000个样本的数据集, 每个样本包含从标准正态分布中采样的2个特征。 我们的合成数据集是一个矩阵 。
我们使用线性模型参数 和噪声项 生成数据集及其标签:
你可以将ϵ视为模型预测和标签时的潜在观测误差。 在这里我们认为标准假设成立,即ϵ服从均值为0的正态分布。 为了简化问题,我们将标准差设为0.01。 下面的代码生成合成数据集。
def synthetic_data(w, b, num_examples):
"""生成y=Xw+b+噪声"""
#
X = torch.normal(0, 1, (num_examples, len(w)))
# y = X * w + b
y = torch.matmul(X, w) + b
# 再补上一个满足标准差为0.01,均值为0的正态分布的噪声
y += torch.normal(0, 0.01, y.shape)
#若原来的向量的y.shape为(a,b)
#reshape(-1,m)中, 改变维度为d行m列,其中-1表示行数自动计算,行数 = a*b/m
return X, y.reshape((-1, 1))
#真实值w
true_w = torch.tensor([2, -3.4])
#真实值b
true_b = 4.2
#生成1000组样本 其中features对应X , labels对应y
#features中的每一行都包含一个二维数据样本,
#labels中的每一行都包含一维标签值(一个标量)
features, labels = synthetic_data(true_w, true_b, 1000)
读取数据集
def data_iter(batch_size, features, labels):
num_examples = len(features)
indices = list(range(num_examples))
# 这些样本是随机读取的,没有特定的顺序
random.shuffle(indices)
for i in range(0, num_examples, batch_size):
batch_indices = torch.tensor(
indices[i: min(i + batch_size, num_examples)])
yield features[batch_indices], labels[batch_indices]
初始化模型参数
在下面的代码中,我们通过从均值为0、标准差为0.01的正态分布中采样随机数来初始化权重, 并将偏置初始化为0。
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True) b = torch.zeros(1, requires_grad=True)
在初始化参数之后,我们的任务是更新这些参数,直到这些参数足够拟合我们的数据。 每次更新都需要计算损失函数关于模型参数的梯度。 有了这个梯度,我们就可以向减小损失的方向更新每个参数。 因为手动计算梯度很枯燥而且容易出错,所以没有人会手动计算梯度。 我们使用自动微分来计算梯度。
定义模型
def linreg(X, w, b):
#线性回归模型
return torch.matmul(X, w) + b
定义损失函数
def squared_loss(y_hat, y):
#均⽅损失
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
定义优化算法
def sgd(params, lr, batch_size):
#⼩批量随机梯度下降
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size
param.grad.zero_()#梯度清零
训练
def squared_loss(y_hat, y):
#均⽅损失
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
定义优化算法
def sgd(params, lr, batch_size):
#⼩批量随机梯度下降
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size
param.grad.zero_()#梯度清零
训练
读取训练样本,根据模型获得预测,计算完损失之后反向传播计算每个参数的梯度,调用优化算法sgd来更新模型参数,这里的迭代周期个数num_epochs和学习率lr(learning rate)都是超参数
这些可以调整但不在训练过程中更新的参数称为超参数(hyperparameter)。调参(hyperparameter tuning)是选择超参数的过程。
lr = 0.03 #learning rate
num_epochs = 3 #训练次数
net = linreg #网络
loss = squared_loss #损失函数
for epoch in range(num_epochs):
for X, y in data_iter(batch_size, features, labels):
l = loss(net(X, w, b), y) # X和y的小批量损失
# 因为l形状是(batch_size,1),而不是一个标量。l中的所有元素被加到一起,
# 并以此计算关于[w,b]的梯度
l.sum().backward()
sgd([w, b], lr, batch_size) # 使用参数的梯度更新参数
with torch.no_grad():
train_l = loss(net(features, w, b), labels)
print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
epoch 1, loss 0.045372 epoch 2, loss 0.000179 epoch 3, loss 0.000047
3.螺旋数据分类(spiral classification)
构建螺旋数据集
!wget https://raw.githubusercontent.com/Atcold/pytorch-Deep-Learning/master/res/plot_lib.py
import random
import torch
from torch import nn, optim
import math
from IPython import display
from plot_lib import plot_data, plot_model, set_default
# 因为colab是支持GPU的,torch 将在 GPU 上运行
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print('device: ', device)
# 初始化随机数种子。神经网络的参数都是随机初始化的,
# 不同的初始化参数往往会导致不同的结果,当得到比较好的结果时我们通常希望这个结果是可以复现的,
# 因此,在pytorch中,通过设置随机数种子也可以达到这个目的
seed = 12345
random.seed(seed)
torch.manual_seed(seed)
N = 1000 # 每类样本的数量
D = 2 # 每个样本的特征维度
C = 3 # 样本的类别
H = 100 # 神经网络里隐层单元的数量
X = torch.zeros(N * C, D).to(device)
Y = torch.zeros(N * C, dtype=torch.long).to(device)
for c in range(C):
index = 0
t = torch.linspace(0, 1, N) # 在[0,1]间均匀的取10000个数,赋给t
# 下面的代码不用理解太多,总之是根据公式计算出三类样本(可以构成螺旋形)
# torch.randn(N) 是得到 N 个均值为0,方差为 1 的一组随机数,注意要和 rand 区分开
inner_var = torch.linspace( (2*math.pi/C)*c, (2*math.pi/C)*(2+c), N) + torch.randn(N) * 0.2
# 每个样本的(x,y)坐标都保存在 X 里
# Y 里存储的是样本的类别,分别为 [0, 1, 2]
for ix in range(N * c, N * (c + 1)):
X[ix] = t[index] * torch.FloatTensor((math.sin(inner_var[index]), math.cos(inner_var[index])))
Y[ix] = c
index += 1
print("Shapes:")
print("X:", X.size())
print("Y:", Y.size())
# visualise the data
plot_data(X, Y)
!wget https://raw.githubusercontent.com/Atcold/pytorch-Deep-Learning/master/res/plot_lib.py
import random
import torch
from torch import nn, optim
import math
from IPython import display
from plot_lib import plot_data, plot_model, set_default
# 因为colab是支持GPU的,torch 将在 GPU 上运行
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print('device: ', device)
# 初始化随机数种子。神经网络的参数都是随机初始化的,
# 不同的初始化参数往往会导致不同的结果,当得到比较好的结果时我们通常希望这个结果是可以复现的,
# 因此,在pytorch中,通过设置随机数种子也可以达到这个目的
seed = 12345
random.seed(seed)
torch.manual_seed(seed)
N = 1000 # 每类样本的数量
D = 2 # 每个样本的特征维度
C = 3 # 样本的类别
H = 100 # 神经网络里隐层单元的数量
X = torch.zeros(N * C, D).to(device)
Y = torch.zeros(N * C, dtype=torch.long).to(device)
for c in range(C):
index = 0
t = torch.linspace(0, 1, N) # 在[0,1]间均匀的取10000个数,赋给t
# 下面的代码不用理解太多,总之是根据公式计算出三类样本(可以构成螺旋形)
# torch.randn(N) 是得到 N 个均值为0,方差为 1 的一组随机数,注意要和 rand 区分开
inner_var = torch.linspace( (2*math.pi/C)*c, (2*math.pi/C)*(2+c), N) + torch.randn(N) * 0.2
# 每个样本的(x,y)坐标都保存在 X 里
# Y 里存储的是样本的类别,分别为 [0, 1, 2]
for ix in range(N * c, N * (c + 1)):
X[ix] = t[index] * torch.FloatTensor((math.sin(inner_var[index]), math.cos(inner_var[index])))
Y[ix] = c
index += 1
print("Shapes:")
print("X:", X.size())
print("Y:", Y.size())
# visualise the data
plot_data(X, Y)
构建线性模型分类
learning_rate = 1e-3
lambda_l2 = 1e-5
# nn 包用来创建线性模型
# 每一个线性模型都包含 weight 和 bias
model = nn.Sequential(
nn.Linear(D, H),
nn.Linear(H, C)
)
model.to(device) # 把模型放到GPU上
# nn 包含多种不同的损失函数,这里使用的是交叉熵(cross entropy loss)损失函数
criterion = torch.nn.CrossEntropyLoss()
# 这里使用 optim 包进行随机梯度下降(stochastic gradient descent)优化
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, weight_decay=lambda_l2)
# 开始训练
for t in range(1000):
# 把数据输入模型,得到预测结果
y_pred = model(X)
# 计算损失和准确率
loss = criterion(y_pred, Y)
score, predicted = torch.max(y_pred, 1)
acc = (Y == predicted).sum().float() / len(Y)
print('[EPOCH]: %i, [LOSS]: %.6f, [ACCURACY]: %.3f' % (t, loss.item(), acc))
display.clear_output(wait=True)
# 反向传播前把梯度置 0
optimizer.zero_grad()
# 反向传播优化
loss.backward()
# 更新全部参数
optimizer.step()
# Plot trained model
print(model)
plot_model(X, Y, model)
# Plot trained model print(model) plot_model(X, Y, model)
从上面图示可以看出,线性模型的准确率最高只能达到 50% 左右,对于这样复杂的一个数据分布,线性模型难以实现准确分类。
激活函数
在引入激活函数之前,先了解一下激活函数是什么。
在构建神经网络时,神经网络中的每个神经元节点接受上一层神经元的输出值作为本神经元的输入值,并将输入值传递给下一层,输入层神经元节点会将输入属性值直接传递给下一层(隐藏层或者输出层),在多层神经网络中,上层节点的输出和下层节点的输入之间具有一个函数关系,这个函数就被称为激活函数。
简单来说,激活函数是指如何把“激活的神经元特征”通过函数把特征保留并映射出来,负责将神经元的输入映射到输出端。
如果不用激活函数,在这种情况下每一层输出都是上层输入的线性函数。容易验证,无论神经网络有多少层,输出都是输入的线性组合,与没有隐藏层效果相当,这种情况就是最原始的感知机。加入激活函数之后,给神经元引入非线性因素,神经网络就可以任意逼近任何非线性函数,这样神经网络就可以应用到非线性模型中。
常见的几种激活函数
在网络中加入激活函数
| 激活函数 | model | output |
| ReLu() | [EPOCH]: 999, [LOSS]: 0.185814, [ACCURACY]: 0.945
| |
| Tanh() | [EPOCH]: 999, [LOSS]: 0.301596, [ACCURACY]: 0.848 | |
| Sigmoid() | [EPOCH]: 999, [LOSS]: 0.757004, [ACCURACY]: 0.516 | |
| LeakyReLu() | [EPOCH]: 999, [LOSS]: 0.167929, [ACCURACY]: 0.956 |
可以看到在神经网络中加入激活函数后,分类的准确率得到了显著提高,但不是所有的激活函数都适合当前的模型,我们发现Sigmoid激活函数效果非常不明显,猜想Sigmoid涉及指数运算,计算效率较低,在修改训练次数后发现仍能较为精确的拟合。
4.小结
机器学习模型中关键因素是训练数据,损失函数,优化算法,还有模型本身,大致流程如下。
optimizer.zero_grad() ## 梯度清零 preds = model(inputs) ## 将数据导入模型得到预测值preds loss = criterion(preds, targets) ## 求解loss损失函数 loss.backward() ## 反向传播求解梯度 optimizer.step() ## 更新权重参数
神经网络学习如何利用矩阵的线性变换加激活函数的非线性变换,将原始输入空间投影到线性可分的空间去分类/回归。
增加节点数:增加维度,即增加线性转换能力。
增加层数:增加激活函数的次数,即增加非线性转换次数。
参考资料:
《动手学深度学习》 — 动手学深度学习 2.0.0-beta0 documentation
colab_demo/02_Pytorch_Basic.ipynb at master · OUCTheoryGroup/colab_demo · GitHub
https://github.com/OUCTheoryGroup/colab_demo/blob/master/03_Spiral_Classification.ipynb
Artificial neural networks (ANNs) · Deep Learning



