import numpy as np
# 基于交叉熵的loss函数计算和梯度
class CrossEntropy():
# 正向计算
def forward(self, input, y):
'''
正向计算成本函数的残熵结果值,注意input和y都是概率值在[0, 1]之间,
:param input: 预测的分类结果,是个向量,存放的是各分类结果的概率,
例如:[0.5, 0.8, 1, 0.3, 0.1]
:param y: 实际分类结果,例如:[0,1,0,0,0]
:return: 返回一条训练数据的交叉熵成本值
'''
# 公式 ce = -sum(y*log(p) + (1 - y)*log(1 - p))
# 为了规避是0 为了规避log出现负数,先计算log然后代入公式
m = [np.log(x) if x >0 else 0 for x in input]
n = [np.log(1 - x) if 1 - x > 0 else 0 for x in input]
result = - np.sum(y * m + (1 - y) * n)
return result
pass
def backward(self, input, y):
'''
# 反向计算损失函数的梯度
:param input: 预测的分类结果,是个向量,存放的是各分类结果的概率,
例如:[0.5, 0.8, 1, 0.3, 0.1]
:param y: 实际分类结果,例如:[0,1,0,0,0]
:return: 返回的是成本函数计算出来的梯度值
'''
# input是神经网络的分类结果
return (input - y)/(input * (1 - y))
pass
pass
# sigmoid激活函数和梯度计算
class Sigmoid():
def forward(self, input):
'''
正向计算求值
:param input: 输入是个向量,一次计算多个输入
:return:
'''
return 1.0 / (1.0 + np.exp(-input))
pass
# 基于sigmoid函数的梯度公式:g'(x) = g(x)*(1 - g(x)),g是sigmoid函数g(x) = 1/(1+e^(-x))
def backward(self, output):
'''
反向计算求梯度
:param output: 是正向计算的结果,output其实是forward的计算结果
:return:
'''
return output * (1 - output)
pass
pass
# 定义了神经网络层的正向计算和梯度计算
class Dense():
def __init__(self, inputSize=0, outputSize=0, activator=Sigmoid()):
'''
对于一个神经网络层来说,需要知道输入的神经元个数和输出的神经元个数
:param inputSize: 输入神经元的个数
:param outputSize: 输出神经元的个数
:param activator: 当前层的激活函数
:return:
'''
self.inputSize = inputSize
self.outputSize = outputSize
self.activator = activator
# 定义和初始化权重矩阵 基于公式 f(Wx) = W.dot(x) + b W是系数矩阵 x是输入的神经元,
# b是截距项 x和b都是列向量
self.W = np.random.uniform(-0.1, 0.1, (outputSize, inputSize))
# 定义截距项数组,初始化为全0
self.b = np.zeros((outputSize,))
# 定义输出数组
self.output = np.zeros((outputSize,))
pass
def forward(self, inputArray):
'''
定义当前层的正向计算
:param inputArray: 输入是一个列向量
:return:
'''
# 当前层的神经元
self.input = inputArray
# 基于公式 f(Wx) = W.dot(x) + b 然后使用激活 h(f(Wx)) = h(W.dot(x) + b)
# 计算出的是下一个层的神经元
self.output = self.activator.forward(self.W.dot(self.input) + self.b)
# 输出其实就是下一层的输入
return self.output
pass
def backward(self, inputDelta):
'''
反向计算梯度
:param inputDelta:是反向传播时的上一层,其实是当前层的后一层的delta值
:return:
'''
# 计算当前层的delta,detla其实计算的是神经元的梯度
self.delta = self.W.T.dot(inputDelta) * self.activator.backward(self.input)
# 通过神经元的梯度计算出当前层权重的梯度
# 梯度计算注意:inputDelta 和 self.input 是两个向量,
# 例如假设:inputDelta=[1,2,3,4] input=[1,3,2,4]
# inputDelta通过nputDelta[:,np.newaxis]后,扩展了列变为二维:[[1],[2],[3],[4]]
# input通过self.input[np.newaxis,:]后,扩展了行变为:[[1,3,2,4]]
# 这样就是一个4*1 的矩阵和 一个1*4的矩阵点积运算,得到一个4*4的矩阵
self.WGrad = np.dot(inputDelta[:,np.newaxis], self.input[np.newaxis,:])
# 因为b值是做加法,所以他的梯度就是后一次计算的梯度Delta
self.bGrad = inputDelta
pass
def update(self, learningRate):
'''
根据学习速率和已经获得的梯度,更新权重系数和截距项的梯度
:param learningRate:
:return:
'''
self.W -= self.WGrad * learningRate
self.b -= self.bGrad * learningRate
pass
def verbose(self, verbose=0):
'''
:param verbose: 0不显示梯度变化信息, 1显示
:return:
'''
if verbose:
print("Grandient update now:")
print("W:", self.W)
print("b:", self.b)
pass
pass
class Model(object):
def __init__(self):
self.layers = [] # 保存层对象,一个三层网络,只需要来个层对象
self.loss = None
pass
def predict(self, input):
'''
正向计算,预测结果
:param input: 是一个向量
:return:
'''
output = input
for layer in self.layers:
output = layer.forward(output)
pass
return output
pass
def add(self, layer):
'''
向神经网络添加层
:return:
'''
self.layers.append(layer)
pass
def compile(self):
'''
构建神经网络模型,初始化模型相关的参数
:return:
'''
pass
def optimizor(self, y, loss=None, learningRate=0.005):
'''
优化器,计算梯度和更新梯度
:return:
'''
# 当使用交叉熵作为损失函数的时候
if loss == None or loss == 'CrossEntropy':
# 最后的delta计算其实是基于交叉熵成本函数公式求导的梯度
# ce = -sum(y*log(p) + (1 - y)*log(1 - p))
# self.layers[-1].activator.backward(self.layers[-1].output)得到的是激活函数的梯度
# 对于sigmoid其实就是output*(1 - output)
delta = self.layers[-1].activator.backward(self.layers[-1].output) * \
(self.layers[-1].output - y) / (self.layers[-1].output * (1 - self.layers[-1].output))
# 计算其他层的delta和梯度
for layer in self.layers[:: -1]:
layer.backward(delta)
delta = layer.delta
pass
# 梯度更新,每一条数据都要产生梯度影响
for layer in self.layers:
# print(layer.W)
layer.update(learningRate)
pass
pass
def trainOneSample(self, x, y, loss=None, learningRate=0.005):
'''
注意本例子使用的每次计算一条样本
:param x: 一条样本数据
:param y: 样本的实际结果
:return:
'''
# 正向计算得到结果
output = self.predict(x)
self.optimizor(y, loss, learningRate)
pass
def fit(self, X, Y, loss=None, epochs=1000, learningRate=0.1):
'''
训练模型
:param X: 样本
:param Y: 标签
:param loss: 成本函数
:return:
'''
for i in range(epochs):
# epochs
print("epochs:", i)
for x, y in zip(X, Y):
self.trainOneSample(x, y, learningRate=0.01)
pass
pass
pass
pass
if __name__ == "__main__":
# 定义模型
model = Model()
# 一个三层的网络input->hidden->output,只需要两个计算层
model.add(Dense(inputSize=784, outputSize=64, activator=Sigmoid()))
model.add(Dense(inputSize=64, outputSize=10, activator=Sigmoid()))
# 加载数据
# 5.1 读取训练数据,训练的是处理好的mnist数据集,就是手写数字图片
trainData = np.loadtxt('digits_training.csv', delimiter=',', skiprows=1)
# 将样本数据X和y
xTrain = trainData[:, 1:]
yTrain = trainData[:, 0]
# 正则化
# xTrain = (xTrain - np.mean(xTrain, axis=0))/np.std(xTrain, axis=0)
xTrain = xTrain / np.max(xTrain)
# 优化出10组w,第一组w识别是0,还是不是0,第二组w识别是1还是不是1
# 处理yTrain,变为二维数组oneHot编码
y = np.zeros(shape=(len(yTrain), 10))
# print(y)
for m, row in zip(yTrain, y):
row[int(m)] = 1
pass
# print(xTrain)
# print(y)
model.fit(xTrain, y, epochs=100)
print("预测和原始值:")
print("原始结果:", np.argmax(y[0]), '预测结果:', np.argmax(model.predict(xTrain[0])), '预测值:',model.predict(xTrain[0]))
print("原始结果:", np.argmax(y[1]), '预测结果:', np.argmax(model.predict(xTrain[1])), '预测值:',model.predict(xTrain[1]))
print("原始结果:", np.argmax(y[2]), '预测结果:', np.argmax(model.predict(xTrain[2])), '预测值:',model.predict(xTrain[2]))
pass
人工智能神经网络:200行代码手写了一个全连接神经网络(NN),基于单条数据计算更新梯度,速度比较慢,计划改为批量计算,代码详细注释