课程内容
在之前的内容中我们使用的训练数据x只是简单的实数即一维数据,但在实际情况中只用一个变量就能描述的对象少之又少,通常我们需要去处理多维的数据。那么为了应付多维的数据我们将模型进行如下修改:
最终我们将式子变为了矩阵运算的形式,除了在形式上更加精练对于计算机来说向量计算起来也更快。
代码片:
import torch
class Model(torch.nn.Module):
def __init__(self):
super(Model,self).__init__()
self.linear=torch.nn.Linear(8,1)
self.sigmoid=torch.nn.Sigmoid()
def foward(self,x):
x=self.sigmoid(slelf.linear(x))
return x
model=Model()
相较于上一节课的代码只改动了线性层的输入维度。注意此处的sigmoid不再是上一次从nn.function中调用的函数,这次是从nn中调用的模块,这让sigmoid也作为我们网络中的一个计算模块
这里实际上就是一个单层的神经网络,不过这个模型还是相当简单的,我们接着深入将sigmoid函数的输出作为另一个输入层的输入在做一次相同操作以此提高模型的复杂度,增加模型的学习能力。
神经网络的本质就是寻找一个非线性的空间变换函数
模型设计
本次课使用的数据集会放在文后
这就是本次课所使用的模型结构,代码如下:
import numpy as np
import torch
import matplotlib.pyplot as plt
xy=np.loadtxt('diabetes.csv.gz',delimiter=',',dtype=np.float32)
x_data=torch.from_numpy(xy[:,:-1])
y_data=torch.from_numpy(xy[:,[-1]])
class Model(torch.nn.Module):
def __init__(self):
super(Model,self).__init__()
self.linear1=torch.nn.Linear(8,6)
self.linear2=torch.nn.Linear(6,4)
self.linear3=torch.nn.Linear(4,1)
self.sigmoid=torch.nn.Sigmoid()
def forward(self,x):
x=self.sigmoid(self.linear1(x))
x=self.sigmoid(self.linear2(x))
x=self.sigmoid(self.linear3(x))
return x
model=Model()
criterion=torch.nn.BCELoss(reduction='mean')
optimizer=torch.optim.SGD(model.parameters(),lr=0.1)
epochs=[]
losses=[]
for epoch in range(100):
#Forward
y_pred=model(x_data)
loss=criterion(y_pred,y_data)
print(epoch,loss.item())
epochs.append(epoch)
losses.append(loss.item())
#Backward
optimizer.zero_grad()
loss.backward()
#Update
optimizer.step()
plt.plot(epochs,losses)
plt.xlabel('epoch')
plt.ylabel('loss')
plt.title('Sigmoid')
plt.grid()
plt.show()
课后作业
将所有层的激活函数改为ReLU,代码如下:
import numpy as np
import torch
import matplotlib.pyplot as plt
xy=np.loadtxt('diabetes.csv.gz',delimiter=',',dtype=np.float32)
x_data=torch.from_numpy(xy[:,:-1])
y_data=torch.from_numpy(xy[:,[-1]])
class Model(torch.nn.Module):
def __init__(self):
super(Model,self).__init__()
self.linear1=torch.nn.Linear(8,6)
self.linear2=torch.nn.Linear(6,4)
self.linear3=torch.nn.Linear(4,1)
self.activate=torch.nn.ReLU()
def forward(self,x):
x=self.activate(self.linear1(x))
x=self.activate(self.linear2(x))
x=self.activate(self.linear3(x))
return x
model=Model()
criterion=torch.nn.BCELoss(reduction='mean')
optimizer=torch.optim.SGD(model.parameters(),lr=0.01)
epochs=[]
losses=[]
for epoch in range(100):
#Forward
y_pred=model(x_data)
loss=criterion(y_pred,y_data)
print(epoch,y_pred,loss.item())
epochs.append(epoch)
losses.append(loss.item())
#Backward
optimizer.zero_grad()
loss.backward()
#print
#Update
optimizer.step()
y_pred=model(x_data)
y_label=torch.where(y_pred>0.5,1.,0.)
acc = torch.eq(y_label, y_data).sum().item() / y_data.size(0)
print(acc)
plt.plot(epochs,losses)
plt.xlabel('epoch')
plt.ylabel('loss')
plt.title('ReLU')
plt.grid()
plt.show()
模型不优化
如果各位这么做了那么在运气好不报错的情况下很可能会出现下面这一张图:
一条漂亮的直线只可惜loss值极高,也就是说模型完全没有进行优化。造成这个问题是由于两方面的因素。首先就是由于ReLU函数的性质,先看一下他长什么样子:
其表达式为
R
e
L
U
(
x
)
=
m
a
x
(
0
,
x
)
ReLU(x)=max(0,x)
ReLU(x)=max(0,x)通俗点讲就是过滤负值输出非负值。相较于Sigmoid那些函数它有着很明显的优点就是简单,不论是图形还是计算之后的求导,而且由于将负值置零的操作能滤掉很多神经元输入可以加快计算效率。总之有着诸多益处,更多关于ReLU的说明可以看这里.
我相信很多人已经注意到问题出在何处,就是负值置零。为了验证猜想我们回到编写的模型看一看各层的输出:
1层: tensor([[0.0000, 0.1483, 0.2052, 0.0132, 0.0281, 0.0000],
[0.0000, 0.3429, 0.0878, 0.2378, 0.0000, 0.0000],
[0.0514, 0.4474, 0.1079, 0.0113, 0.0000, 0.0000],
…,
[0.0000, 0.4425, 0.0000, 0.4695, 0.0000, 0.0000],
[0.0000, 0.2160, 0.1389, 0.0000, 0.0078, 0.0000],
[0.0000, 0.3910, 0.0596, 0.2785, 0.0000, 0.0000]],
grad_fn=< ReluBackward0>)
2层: tensor([[0.2595, 0.4549, 0.0000, 0.1405],
[0.1036, 0.4247, 0.0000, 0.1097],
[0.1606, 0.4634, 0.0000, 0.1627],
…,
[0.0000, 0.3838, 0.0000, 0.0659],
[0.2296, 0.4450, 0.0000, 0.1529],
[0.0729, 0.4159, 0.0000, 0.1051]], grad_fn=< ReluBackward0>)
3层: tensor([[0.],
[0.],
[0.],
[0.],
…
[0.],
[0.]], grad_fn=< ReluBackward0>)
能够看到3层(即最后一层)的输出全都是0,这就导致了优化器的梯度下降都是0,自然也就不会优化了。
那么解决了第一个问题,接下来就是在激活函数之前为什么会出现负值。这个问题则是由torch.nn.Linear这个类的定义造成的。我们看一下官方文档对Linear这个类的介绍:
torch.nn.Linear(in_features, out_features, bias=True, device=None, dtype=None)
对输入的数据进行线性变换: y = x A T + b y=xA^T+b y=xAT+b
模组支持TensorFloat32
在某些ROCm设备上,使用float16输入时,该模组将的后向传播将使用不同的精度。
参数:
in_features (int) – 输入样本的维度
out_features (int) – 输出样本的维度
bias (bool) – 如果设定为False该线性层不会学习额外的偏置, 默认为 True
形状:
Input: (*, Hin)其中*代表任意维度(包括none),Hin=in_feature
Output: (*, Hout)其中*代表任意维度(但最后一维必须和input一致),Hout=out_feature
变量:
weight (torch.Tensor) – 形状为(out_features,in_features)的模组可学习权重.
其变量初始化的范围为 ( − k , k ) (-\sqrt k,\sqrt k) (−k,k),其中 k = 1 i n _ f e a t u r e s k=\frac{1}{in\\\_features} k=in_features1
bias-形状为(out_features)的模组可学习偏置.如果bias为True.其变量初始化的范围为 ( − k , k ) (-\sqrt k,\sqrt k) (−k,k),其中 k = 1 i n _ f e a t u r e s k=\frac{1}{in\\\_features} k=in_features1
那么谜底已经很明显了,是由于线性层的权重和偏置的初始化导致最后的线性层输出为负导致最后一层的输出为0。再让我们回过头来看看最后一层的w和b:
w: Parameter containing:
tensor([[-0.4306, 0.2701, -0.4593, 0.4169]], requires_grad=True)
b: -0.19946664571762085
拿去和上面2层的输出进行运算能够简单的验证输出是否为0.
程序报错
回到开头我说过在运气好不报错的情况下能绘制出图,现在来看一下运气不好报错的情况:
all elements of input should be between 0 and 1
这个错误很好理解毕竟ReLU不是一个饱和函数最后的输出也可能超过1,而我们使用的误差函数是交叉熵,低于0或者高于1都是不行的。
不过我想说的重点不在这里,而是在建立模型最开始的目标。这个模型我们的目标是进行分类,那么最后一层的输出应该就是完成最后分类任务的输出,但从ReLU的形状和性质上明显就能看出它并不是能够作为分类的函数。
那么在最后给出修改后的代码(实际就是改了最后一层的激活函数):
import numpy as np
import torch
import matplotlib.pyplot as plt
xy=np.loadtxt('diabetes.csv.gz',delimiter=',',dtype=np.float32)
x_data=torch.from_numpy(xy[:,:-1])
y_data=torch.from_numpy(xy[:,[-1]])
class Model(torch.nn.Module):
def __init__(self):
super(Model,self).__init__()
self.linear1=torch.nn.Linear(8,6)
self.linear2=torch.nn.Linear(6,4)
self.linear3=torch.nn.Linear(4,1)
self.activate=torch.nn.ReLU()
def forward(self,x):
x=self.activate(self.linear1(x))
x=self.activate(self.linear2(x))
x=torch.nn.functional.sigmoid(self.linear3(x))
return x
model=Model()
criterion=torch.nn.BCELoss(reduction='mean')
optimizer=torch.optim.SGD(model.parameters(),lr=0.01)
epochs=[]
losses=[]
for epoch in range(100):
#Forward
y_pred=model(x_data)
loss=criterion(y_pred,y_data)
print(epoch,y_pred,loss.item())
epochs.append(epoch)
losses.append(loss.item())
#Backward
optimizer.zero_grad()
loss.backward()
#print
#Update
optimizer.step()
y_pred=model(x_data)
y_label=torch.where(y_pred>0.5,1.,0.)
acc = torch.eq(y_label, y_data).sum().item() / y_data.size(0)
print(acc)
plt.plot(epochs,losses)
plt.xlabel('epoch')
plt.ylabel('loss')
plt.title('ReLU')
plt.grid()
plt.show()