这个项目是一位朋友向我推荐的入门学习用的,是在Github上下载的其他学生的作业,由于找不到作业要求,只有一个程序,所以每一个作业的目的,以及如何去理解这个程序都是我自己逆推出来的,加之这是一份学习笔记,我也是初学此道,是以如果出现错误以及缺漏之处,还望诸位不吝赐教。
这位老哥的程序是我找了几个做对比后,相对而言readme写的较为详尽,且程序注释较多的一个。
另外,这是我第一次写博客分享学习笔记,也不太清楚是否会构成对这个项目亦或者这个老哥的程序的侵权什么的,如果有我会立刻修改。
本次作业的目的在于通过建立一个前馈神经网络模型实现对与门、或门、非门、异或门的结果输出。
# test.py from logic_gate import AND from logic_gate import XOR # Initialize 2 types of Gates And = AND() Xor = XOR() # Test cases for AND print("\nDemonstrating AND Gate functionality using FeedForward Neural Network") print("And(False, False) = %r" % And(False, False)) print("And(False, True) = %r" % And(False, True)) print("And(True, False) = %r" % And(True, False)) print("And(True, True) = %r" % And(True, True)) # Test cases for XOR print("\nDemonstrating XOR Gate functionality using FeedForward Neural Network") print("XOR(False, False) = %r" % Xor(False, False)) print("XOR(False, True) = %r" % Xor(False, True)) print("XOR(True, False) = %r" % Xor(True, False)) print("XOR(True, True) = %r" % Xor(True, True))
# logic_gate.py from neural_network import NeuralNetwork import torch import numpy as np class AND: def __init__(self): # 与门的输入层为两个感知器(perception) 且不需要隐含层 故初始化一个两层的ANN self.and_gate = NeuralNetwork([2, 1]) # 人为指定权重 self.weights = self.and_gate.getLayer(0) self.weights += torch.DoubleTensor([[-30], [20], [20]]) def __call__(self, x: bool, y: bool): # x和y为接收到的输入 self.x = x self.y = y # 调用前馈神经网络来计算 output = self.and_gate.feedforward(torch.DoubleTensor([[self.x], [self.y]])) return bool(np.around(output.numpy())) class XOR: def __init__(self): # 异或门的输入层为两个感知器(perception) 需要一层含两个感知器的隐含层 故初始化一个三层的ANN self.xor_gate = NeuralNetwork([2, 2, 1]) # 为输入层和隐含层人为指定权重 self.weights_1 = self.xor_gate.getLayer(0) self.weights_2 = self.xor_gate.getLayer(1) self.weights_1 += torch.DoubleTensor([[-50, -50], [60, -60], [-60, 60]]) self.weights_2 += torch.DoubleTensor([[-50], [60], [60]]) def __call__(self, x: bool, y: bool): # x和y为接收到的输入 self.x = x self.y = y # 调用前馈神经网络来计算 output = self.xor_gate.feedforward(torch.DoubleTensor([[self.x], [self.y]])) return bool(np.around(output.numpy()))
# neural_network.py import numpy as np import torch import math class NeuralNetwork: def __init__(self, layers_list: list): self.layers_list = layers_list # 建立一个字典 用以存放权重weights # eg: and_gate.weights = {'(layer0-layer1)': tensor([[-30.], # [ 20.], # [ 20.]], dtype=torch.float64)} self.weights = {} self.key = ["" for x in range(len(self.layers_list) - 1)] # 设置字典的键值 for i in range(len(self.layers_list) - 1): self.key[i] = "(layer" + str(i) + "-layer" + str(i + 1) + ")" # 初始化一个大小符合需求的张量Tensor 将其值全部初始化为double类型的0 # 所需张量的第零维(第一个维度)为本层的感知器的个数再加一个bias 故为self.layers_list[i] + 1 for i in range(len(self.layers_list) - 1): self.weights[self.key[i]] = torch.zeros((self.layers_list[i] + 1, self.layers_list[i + 1]), dtype=torch.double) def getLayer(self, layer: int): # 将所需层的权重矩阵传递出去 self.layer = layer return self.weights[self.key[layer]] def feedforward(self, input: torch.DoubleTensor): # 激活函数 Sigmoid # 若输入的为一个二维张量 则计算过程是分别单独计算后再作为一个二维张量来输出 def sigmoid(inp: torch.DoubleTensor): product = inp.numpy() sig = 1 / (1 + np.exp(-product)) return torch.from_numpy(sig) self.input = input # 新建一个初始值为1的一个一维张量来做bias 并将其修改为double型 bias = torch.ones((1, 1)) bias = bias.type(torch.DoubleTensor) # sig_prod存储了输入层的感知器的值 sig_prod = self.input # 对于and_gate这种两层的ANN来说循环并不需要 # 对于xor_gate来说 其需要先通过输入层来计算出隐含层的值 再用此结果来计算最后的输出层结果 for i in range(len(self.layers_list) - 1): # 将bias和感知器的值拼接成一个张量 cat_input = torch.cat((bias, sig_prod), 0) # 将权重weights张量取转置 方便下一步的矩阵乘法 weights_trans = torch.t(self.weights[self.key[i]]) # 矩阵乘法 prod = torch.mm(weights_trans, cat_input) # 将矩阵乘法计算出的张量送入激活函数中进行计算 其结果送回到sig_prod中 以便输出或作为下一次循环的输入 sig_prod = sigmoid(prod) return sig_prod
结果输出:
对于人工神经网络ANN的提出背景此处就不再多赘述了,这里简单放一个逻辑结构。
即,一个输入层,多个隐藏层,以及一个输出层共同组成。
我们定义上图中每个圆圈代表一个神经元模型,称其为感知器(perceptron)。
那么我们的目的就在于,对于输入的数据通过一定的处理之后,得到最后我们需要的结果。
同时,由于输入的数据对于结果的影响程度或许不同,因此我们需要对其加上一定的权重来控制其对最终结果的影响。
那么我们将权重放在不同层间的感知器的连线上,那么这样,我们就构建出了一个我们需要的神经网络模型。
而一旦我们指定了确定的权重,以及给定每一层所需的偏置值bias,那么我们就可以通过输入数据从而得到我们所需的输出数据,这一过程便被称之为前馈神经网络。
这个时候就会又出现一个新的问题,按照我们上述的表示方法来说,每一层到下一层的输出都是一个线性输出,如果按照这样的思路的话,完全可以取消掉中间所有的隐含层,只保留输入层和输出层,直接通过对输入层的线性表示来将输出层表示出来。
那么如果是这样的话,显然与我们一开始的建模产生了冲突,另外,若仅仅只能表示线性的话,对于占据了更多数情况的非线性情况来说,显然我们的模型是远不能满足需要的。
因此,事实上在每一层向下一层的输出时,都要通过一个名为【激活函数】的方法,将其变为非线性的情况,从而使得最后的模型能够满足非线性情况下的需要。