一、异或门问题
import torch
X = torch.tensor([[1,0,0],[1,1,0],[1,0,1],[1,1,1]], dtype = torch.float32)
andgate = torch.tensor([0,0,0,1], dtype = torch.float32)
#定义w
w = torch.tensor([-0.2,0.15,0.15], dtype = torch.float32)
def LogisticR(X,w):
zhat = torch.mv(X,w) #首先执行线性回归的过程,依然是mv函数,让矩阵与向量相乘得到z
sigma = torch.sigmoid(zhat) #执行sigmoid函数,你可以调用torch中的sigmoid函数,也可
以自己用torch.exp来写
andhat = torch.tensor([int(x) for x in sigma >= 0.5], dtype = torch.float32)
#设置阈值为0.5, 使用列表推导式将值转化为0和1
return sigma, andhat
sigma, andhat = LogisticR(X,w)
sigma
andhat
andgate
andgate == andhat
考虑到与门的数据只有两维,我们可以通过python中的matplotlib代码将数据可视化,其中,特征为横坐标,特征为纵坐标,紫色点代表了类别0,红色点代表类别1。
import torch
X = torch.tensor([[1,0,0],[1,1,0],[1,0,1],[1,1,1]], dtype = torch.float32)
andgate = torch.tensor([0,0,0,1], dtype = torch.float32)
def AND(X):
w = torch.tensor([-0.2,0.15, 0.15], dtype = torch.float32)
zhat = torch.mv(X,w)
andhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32)
return andhat
andhat = AND(X)
andgate
#定义数据
X = torch.tensor([[1,0,0],[1,1,0],[1,0,1],[1,1,1]], dtype = torch.float32)
#或门,或门的图像
#定义或门的标签
orgate = torch.tensor([0,1,1,1], dtype = torch.float32)
#或门的函数(基于阶跃函数)
def OR(X):
w = torch.tensor([-0.08,0.15,0.15], dtype = torch.float32) #在这里我修改了b的数
值
zhat = torch.mv(X,w)
yhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32)
return yhat
OR(X)
#绘制直线划分散点的图像
x = np.arange(-1,3,0.5)
plt.figure(figsize=(5,3))
plt.title("OR GATE",fontsize=16)
plt.scatter(X[:,1],X[:,2],c=orgate,cmap="rainbow")
plt.plot(x,(0.08-0.15*x)/0.15,color="k",linestyle="--")
plt.xlim(-1,3)
plt.ylim(-1,3)
plt.grid(alpha=.4,axis="y")
plt.gca().spines["top"].set_alpha(.0)
plt.gca().spines["right"].set_alpha(.0)
#非与门、非与门的图像
nandgate = torch.tensor([1,1,1,0], dtype = torch.float32)
def NAND(X):
w = torch.tensor([0.23,-0.15,-0.15], dtype = torch.float32) #和与门、或门都不同
的权重
zhat = torch.mv(X,w)
yhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32)
return yhat
NAND(X)
#图像
x = np.arange(-1,3,0.5)
plt.figure(figsize=(5,3))
plt.title("NAND GATE",fontsize=16)
plt.scatter(X[:,1],X[:,2],c=nandgate,cmap="rainbow")
plt.plot(x,(0.23-0.15*x)/0.15,color="k",linestyle="--")
plt.xlim(-1,3)
plt.ylim(-1,3)
plt.grid(alpha=.4,axis="y")
plt.gca().spines["top"].set_alpha(.0)
plt.gca().spines["right"].set_alpha(.0)
def XOR(X):
#输入值:
input_1 = X
#中间层:
sigma_nand = NAND(input_1)
sigma_or = OR(input_1)
x0 = torch.tensor([[1],[1],[1],[1]],dtype=torch.float32)
#输出层:
input_2 = torch.cat((x0,sigma_nand.view(4,1),sigma_or.view(4,1)),dim=1)
y_and = AND(input_2)
#print("NANE:",y_nand)
#print("OR:",y_or)
return y_and
可以看到,最终输出的结果和异或门要求的结果是一致的。可见,拥有更多的”层“帮助我们解决了单层神经网络无法处理的非线性问题。叠加了多层的神经网络也被称为”多层神经网络“。多层神经网络是神经网络在深度学习中的基本形态,接下来,我们就来认识多层神经网络。
二、黑箱:深层神经网络的不可解释性
首先从结构上来看,多层神经网络比单层神经网络多出了“中间层”。中间层常常被称为隐藏层(hidden layer),理论上来说可以有无限层,所以在图像表示中经常被省略。层数越多,神经网络的模型复杂度越高,一般也认为更深的神经网络可以解决更加复杂的问题。在学习中,通常我们最多只会设置3~5个隐藏层,但在实际工业场景中会更多。还记得这张图吗?当数据量够大时,现代神经网络层数越深,效果越好。
三、探索多层神经网络:层 vs h(z)
#回忆一下XOR数据的真实标签
xorgate = torch.tensor([0,1,1,0],dtype=torch.float32)
def AND(X):
w = torch.tensor([-0.2,0.15, 0.15], dtype = torch.float32)
zhat = torch.mv(X,w)
#下面这一行就是阶跃函数的表达式,注意AND函数是在输出层,所以保留输出层的阶跃函数g(z)
andhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32)
return andhat
def OR(X):
w = torch.tensor([-0.08,0.15,0.15], dtype = torch.float32) #在这里我修改了b的数
值
zhat = torch.mv(X,w)
#注释掉阶跃函数,相当于h(z)是恒等函数
#yhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32)
return zhat
def NAND(X):
w = torch.tensor([0.23,-0.15,-0.15], dtype = torch.float32)
zhat = torch.mv(X,w)
#注释掉阶跃函数,相当于h(z)是恒等函数
#yhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32)
return zhat
def XOR(X):
#输入值:
input_1 = X
#中间层:
sigma_nand = NAND(input_1)
sigma_or = OR(input_1)
x0 = torch.tensor([[1],[1],[1],[1]],dtype=torch.float32)
#输出层:
input_2 = torch.cat((x0,sigma_nand.view(4,1),sigma_or.view(4,1)),dim=1)
y_and = AND(input_2)
#print("NANE:",y_nand)
#print("OR:",y_or)
return y_and
XOR(X)
def AND(X):
w = torch.tensor([-0.2,0.15, 0.15], dtype = torch.float32)
zhat = torch.mv(X,w)
#下面这一行就是阶跃函数的表达式,注意AND函数是在输出层,所以保留输出层的阶跃函数g(z)
andhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32)
return andhat
def OR(X):
w = torch.tensor([-0.08,0.15,0.15], dtype = torch.float32) #在这里我修改了b的数
值
zhat = torch.mv(X,w)
#h(z), 使用sigmoid函数
sigma = torch.sigmoid(zhat)
return sigma
def NAND(X):
w = torch.tensor([0.23,-0.15,-0.15], dtype = torch.float32)
zhat = torch.mv(X,w)
#h(z), 使用sigmoid函数
sigma = torch.sigmoid(zhat)
return sigma
def XOR(X):
#输入值:
input_1 = X
#中间层:
sigma_nand = NAND(input_1)
sigma_or = OR(input_1)
x0 = torch.tensor([[1],[1],[1],[1]],dtype=torch.float32)
#输出层:
input_2 = torch.cat((x0,sigma_nand.view(4,1),sigma_or.view(4,1)),dim=1)
y_and = AND(input_2)
#print("NANE:",y_nand)
#print("OR:",y_or)
return y_and
XOR(X)
#如果g(z)是sigmoid函数,而h(z)是阶跃函数
#输出层,以0.5为sigmoid的阈值
def AND(X):
w = torch.tensor([-0.2,0.15, 0.15], dtype = torch.float32)
zhat = torch.mv(X,w)
sigma = torch.sigmoid(zhat)
andhat = torch.tensor([int(x) for x in sigma >= 0.5],dtype=torch.float32)
return andhat
#隐藏层,OR与NAND都使用阶跃函数作为h(z)
def OR(X):
w = torch.tensor([-0.08,0.15,0.15], dtype = torch.float32) #在这里我修改了b的数
值
zhat = torch.mv(X,w)
yhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32)
return yhat
def NAND(X):
w = torch.tensor([0.23,-0.15,-0.15], dtype = torch.float32)
zhat = torch.mv(X,w)
yhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32)
return yhat
def XOR(X):
#输入值:
input_1 = X
#中间层:
sigma_nand = NAND(input_1)
sigma_or = OR(input_1)
x0 = torch.tensor([[1],[1],[1],[1]],dtype=torch.float32)
#输出层:
input_2 = torch.cat((x0,sigma_nand.view(4,1),sigma_or.view(4,1)),dim=1)
y_and = AND(input_2)
#print("NANE:",y_nand)
#print("OR:",y_or)
return y_and
XOR(X)
五、从0实现深度神经网络的正向传播
学到这里,我们已经学完了一个普通深度神经网络全部的基本元素——用来构筑神经网络的结构的层与激活函数,输入神经网络的数据(特征、权重、截距),并且我们了解从左向右的过程是神经网络的正向传播(也叫做前向传播,或者向前传播)。还记得我们的架构图吗?在过去的课程中我们所学习的内容都是在torch.nn这个模块下,现在我们就使用封装好的torch.nn模块来实现一个完整、多层的神经网络的正向传播。
假设我们有500条数据,20个特征,标签为3分类。我们现在要实现一个三层神经网络,这个神经网络的架构如下:第一层有13个神经元,第二层有8个神经元,第三层是输出层。其中,第一层的激活函数是relu,第二层是sigmoid。我们要如何实现它呢?来看代码:
#继承nn.Module类完成正向传播
import torch
import torch.nn as nn
from torch.nn import functional as F
#确定数据
torch.manual_seed(420)
X = torch.rand((500,20),dtype=torch.float32)
y = torch.randint(low=0,high=3,size=(500,1),dtype=torch.float32)
#继承nn.Modules类来定义神经网路的架构
class Model(nn.Module):
#init:定义类本身,__init__函数是在类被实例化的瞬间就会执行的函数
def __init__(self,in_features,out_features):
super(Model,self).__init__() #super(请查找这个类的父类,请使用找到的父类替换现在
的类)
self.linear1 = nn.Linear(in_features,13,bias=True) #输入层不用写,这里是隐藏
层的第一层
self.linear2 = nn.Linear(13,8,bias=True)
self.output = nn.Linear(8,out_features,bias=True)
#__init__之外的函数,是在__init__被执行完毕后,就可以被调用的函数
def forward(self, x):
z1 = self.linear1(x)
sigma1 = torch.relu(z1)
z2 = self.linear2(sigma1)
sigma2 = torch.sigmoid(z2)
z3 = self.output(sigma2)
sigma3 = F.softmax(z3,dim=1)
return sigma3
input_ = X.shape[1] #特征的数目
output_ = len(y.unique()) #分类的数目
#实例化神经网络类
torch.manual_seed(420)
net = Model(in_features=input_, out_features=output_)
#在这一瞬间,所有的层就已经被实例化了,所有随机的w和b也都被建立好了
#前向传播
net(X)
net.forward(X)
#查看输出的标签
sigma = net.forward(X)
sigma.max(axis=1)
#查看每一层上的权重w和截距b
net.linear1.weight
net.linear1.bias
如果你是初次接触“类”,那这段代码中新内容可能会有点多。但如果你对python基础比较熟悉,你就会发现这个类其实非常简单。在神经网络的类中,我们以线性的顺序从左向右描绘神经网络的计算过程,并且无需考虑在这之间 的结构是如何,矩阵之间如何进行相互运算。所以只要你对自己要建立的神经网络的架构是熟悉的,pytorch代码就非常容易。在这里,特别需要强调一下的可能是super函数的用法。
super函数用于调用父类的一个函数,在这里我们使用super函数来帮助子类(我们建立的神经网络模型)继承一些通过类名调用无法被继承的属性和方法。谨防小伙伴们不熟悉super的用法,在这里我们来说明一下:
#建立一个父类
class FooParent(object):
def __init__(self):
self.parent = 'PARENT!!'
print ('Running __init, I am parent')
def bar(self,message):
self.bar = "This is bar"
print ("%s from Parent" % message)
FooParent() #父类实例化的瞬间,运行自己的__init__
FooParent().parent #父类运行自己的__init__中定义的属性
#建立一个子类,并通过类名调用让子类继承父类的方法与属性
class FooChild(FooParent):
def __init__(self):
print ('Running __init, I am child')
#查看子类是否继承了方法
FooChild().bar("HAHAHA")
FooChild().parent #子类没有继承到父类的__init__中定义的属性
#为了让子类能够继承到父类的__init__函数中的内容,我们使用super函数
#新建一个子类,并使用super函数
class FooChild(FooParent):
def __init__(self):
super(FooChild,self).__init__()
print ('Child')
print ("I am running the __init__")
#再次调用parent属性
FooChild() #执行自己的init功能的同时,也执行了父类的init函数定义的功能
FooChild().parent
通过使用super函数,我们的神经网络模型从nn.Module那里继承了哪些有用的属性和方法呢?首先,如果不加super函数,神经网络的向前传播是无法运行的: