import numpy as np
# layers: A list of integers which represents the actual architecture of the feedforward
# network. For example, a value of [2,2,1] would imply that our first input layer has two nodes,
# our hidden layer has two nodes, and our final output layer has one node.
# alpha:这里我们可以指定神经网络的学习率。此值在权重更新阶段应用。
class NeuralNetwork:
def __init__(self, layers, alpha=0.1):
self.W = []
self.layers = layers
self.alpha = alpha
# 初始化权重矩阵
for i in np.arange(0, len(layers) - 2):
w = np.random.randn(layers[i] + 1, layers[i + 1] + 1)
self.W.append(w / np.sqrt(layers[i]))
# 上述for代码解释:例如,假设layers[i] = 2, layers[i + 1] = 2。因此,我们的权值矩阵将是2x2,以连接各层之间的所有节点集。然而,在这里我们需要小心,因为我们忘记了一个重要的组成部分——偏见项。为了解释这种偏差,我们在层数[i]和层数[i + 1]上加1——这样做会改变我们的权值矩阵w,使形状3x3给定当前层的2 + 1个节点和下一层的2 + 1个节点。我们通过除以当前层中节点数量的平方根来缩放w,从而标准化每个神经元输出[57]的方差(第19行)。
# 构造函数中需要处理的特殊情况,其中输入连接需要一个偏置项,但输出不需要
w = np.random.randn(layers[-2] + 1, layers[-1])
self.W.append(w / np.sqrt(layers[-2]))
# 定义的一个对函数调试有用处的函数,主要是得到当前网络的架构层数,以及每一层的节点数
def __repr__(self):
# construct and return a string that represents the network
# architecture
return "NeuralNetwork: {}".format("-".join(str(l) for l in self.layers))
# 定义sigmoid激活函数
def sigmoid(self, x):
return 1.0 / (1 + np.exp(-x))
# 同时我们将在反向传播过程中用到上述sigmoid函数的导数
def sigmoid_deriv(self, x):
return x * (1 - x)
# 需要注意的是无论何时需要适用反向传播函数,再次注意,无论何时执行反向传播,您都希望选择一个可微的激活函数。
# 定义一个fit函数来训练我们的神经网络
def fit(self, X, y, epochs=1000, displayUpdate=100):
X = np.c_[X, np.ones((X.shape[0]))]
for epoch in np.arange(0, epochs):
for (x, target) in zip(X, y):
self.fit_partial(x, target)
if epoch == 0 or (epoch + 1) % displayUpdate == 0:
loss = self.calculate_loss(X, y)
print("[INFO] epoch={}, loss={:.7f}".format(epoch + 1, loss))
# 反向传播函数的核心在下面的函数中
def fit_partial(self, x, y):
# 初始化一个保存每层输出的网络
A = [np.atleast_2d(x)]
# 开始向前传播
for layer in np.arange(0, len(self.W)):
# 得到每层网络的权值和网络层的数据点之间的乘积
net = A[layer].dot(self.W[layer])
# 通过激活函数得到每层网络的输出
out = self.sigmoid(net)
# 将输出追加到A中
A.append(out)
# We start looping over every layer in the network on Line 71. The net input to the current layer
# is computed by taking the dot product between the activation and the weight matrix (Line 76). The
# net output of the current layer is then computed by passing the net input through the nonlinear
# sigmoid activation function. Once we have the net output, we add it to our list of activations (Line
# 84).
# 进行反向传播
error = A[-1] - y
# 从这里开始,我们需要应用链式法则并构建我们的94 # delta ' D '列表;delta中的第一个条目是95 #,即输出层的误差乘以激活函数对输出值的导数96 #
D = [error * self.sigmoid_deriv(A[-1])]
# 向后传递的第一阶段是计算我们的错误,或者简单地计算我们的预测标签和地面真相标签之间的差值(第91行)。由于激活列表A中的最后一个条目包含了网络的输出,我们可以通过A[-1]访问输出预测。值y是输入数据点x的目标输出。
#接下来,我们需要开始应用链式法则来构建delta的列表d。delta将被用来更新我们的权值矩阵,按学习速率缩放。delta列表中的第一个条目是输出层的误差乘以输出值的sigmoid的导数(第97行)。
for layer in np.arange(len(A) - 2, 0, -1):
delta = D[-1].dot(self.W[layer].T)
delta = delta * self.sigmoid_deriv(A[layer])
D.append(delta)
# 在第103行,我们开始对网络中的每一层进行循环(忽略前面的两层,因为它们已经在第97行中被计入了),因为我们需要反向计算每一层的增量更新。当前层的delta等于前一层的delta,用当前层的权值矩阵点乘D[-1](第109行)。为了完成delta的计算,我们通过将层的激活通过我们的s形函数的导数(第110行)来乘以它。然后,我们用刚刚计算的增量更新增量D列表(第111行)。
# 看看这段代码,我们可以看到反向传播步骤是迭代的——我们只是从上一层取delta,用当前层的权值点乘它,然后乘以激活的导数。这个过程不断重复,直到我们到达网络的第一层。
# 翻转D矩阵
D = D[::-1]
for layer in np.arange(0, len(self.W)):
self.W[layer] += -self.alpha * A[layer].T.dot(D[layer])
# 定义预测函数
def predict(self, X, addBias=True):
p = np.atleast_2d(X)
if addBias:
p = np.c_[p, np.ones((p.shape[0]))]
for layer in np.arange(0, len(self.W)):
p = self.sigmoid(np.dot(p, self.W[layer]))
return p
# 定义的一个计算训练集中损失的函数
def calculate_loss(self, X, targets):
targets = np.atleast_2d(targets)
predictions = self.predict(X, addBias=False)
loss = 0.5 * np.sum((predictions - targets) ** 2)
return loss
from pyimagesearch.nn import NeuralNetwork
from sklearn.preprocessing import LabelBinarizer
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn import datasets
# 开始训练MINIST数据集
print("[INFO] loading MNIST (sample) dataset...")
digits = datasets.load_digits()
data = digits.data.astype("float") # 将数据集转换为float类型
data = (data - data.min()) / (data.max() - data.min()) # 作归一化处理
print("[INFO] samples: {}, dim: {}".format(data.shape[0], data.shape[1]))
# 将数据集进行分分类,75%作为训练集,25%作为测试集
(trainX, testX, trainY, testY) = train_test_split(data, digits.target, test_size=0.25)
# 使用one-hot编码将对应的标签进行向量化
trainY = LabelBinarizer().fit_transform(trainY)
testY = LabelBinarizer().fit_transform(testY)
# 训练网络
print("[INFO] training network...")
nn = NeuralNetwork.NeuralNetwork([trainX.shape[1], 32, 16, 10]) # 初始化神经网络
print("[INFO] {}".format(nn))
nn.fit(trainX, trainY, epochs=1000) # 对神经网络进行训练
# 测试网络,并标准化输出对应的一些指标
print("[INFO] evaluating network...")
predictions = nn.predict(testX)
predictions = predictions.argmax(axis=1)
print(classification_report(testY.argmax(axis=1), predictions))