我用简单的反向传播训练实现了任意前馈神经网络的第一个实现。
class NeuralNet(object):
learning_rate = .1
# Error function
@staticmethod
def J(guess, target):
return 0.5 * np.linalg.norm(guess - target, 2) ** 2
def __init__(self, structure=tuple(), activation_functions=tuple(), activation_derivatives=tuple()):
self.w = []
self.b = []
self.f = list(activation_functions)
self.df = list(activation_derivatives)
li = len(structure) - 1
while li > 0:
self.w.insert(0, np.random.uniform(low=-1.0, high=1.0, size=(structure[li - 1], structure[li])))
self.b.insert(0, np.random.uniform(low=-1.0, high=1.0, size=structure[li]))
li -= 1
def forward(self, x):
z = [0] * len(self.w)
a = [x]
for i, (wi, bi, fi) in enumerate(zip(self.w, self.b, self.f)):
z[i] = a[i] @ wi + bi
a.append(fi(z[i]))
return a[-1]
def train(self, training_data, labels):
# Get a permutation vector for shuffling the inputs and labels in each epoch:
permutation = np.random.permutation(len(inputs))
# Keeping track of all MSE values:
errors = []
# Training loop:
for epoch in range(10000):
# Shuffling the inputs and labels for each epoch:
X = training_data[permutation]
Y = labels[permutation]
# n
# Keeping track of the error: MSE = 1/n * SUM ||Activation - Target|| ** 2
# i=1
error = 0.0
for xi, yi in zip(X, Y):
# Forward pass:
z = [0] * len(self.w)
a = [xi]
for i, (wi, bi, fi) in enumerate(zip(self.w, self.b, self.f)):
z[i] = a[i] @ wi + bi
a.append(fi(z[i]))
# Calculate error for (xi, yi) according to 0.5 * ||xi - yi|| ** 2
error += self.J(a[-1], yi)
# Backwards pass:
# - Calculate deltas:
layer_delta = []
iter_zi = iter(zip(reversed(range(len(z))), reversed(z)))
layer_delta.insert(0, (a[-1] - yi) * self.df[1](next(iter_zi)[1]))
for i, zi in iter_zi:
delta_i = np.multiply(layer_delta[0] @ self.w[i+1].T, self.df[i](z[i]))
layer_delta.insert(0, delta_i)
# - Calculate weight deltas:
weight_delta = []
for i, di in zip(reversed(range(len(layer_delta))), reversed(layer_delta)):
delta_wi = a[i].reshape(-1, 1) * layer_delta[i]
weight_delta.insert(0, delta_wi)
# w[i](new) := w[i](old) - LR * dJ/dw[i]
# b[i](new) := b[i](old) - LR * dJ/db[i]
for i in range(len(self.w)):
self.w[i] = self.w[i] - self.learning_rate * weight_delta[i]
self.b[i] = self.b[i] - self.learning_rate * layer_delta[i]
errors.append(error / len(X))
# Convergence testing, according to the last N errors:
error_delta = sum(reversed(errors[-5:]))
if error_delta < 1.0e-6:
print("Error delta reached, ", epoch, " exiting.")
break
return errors这似乎工作得很好,至少它成功地学习了XOR问题:
# Inputs and their respective labels:
inputs = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
labels = np.array([[0], [1], [1], [0]])
nn = NeuralNet(structure=(2, 2, 1),
activation_functions=(lambda x: np.tanh(x),
lambda x: .25 * x if x < 0 else x),
activation_derivatives=(lambda x: 1.0 - np.tanh(x) ** 2,
lambda x: .25 if x < 0 else 1))
nn.learning_rate = 0.1
print("Before training:")
print(nn.forward(np.array([0, 0])))
print(nn.forward(np.array([0, 1])))
print(nn.forward(np.array([1, 0])))
print(nn.forward(np.array([1, 1])))
errors = nn.train(inputs, labels)
print("After training:")
print(nn.forward(np.array([0, 0])))
print(nn.forward(np.array([0, 1])))
print(nn.forward(np.array([1, 0])))
print(nn.forward(np.array([1, 1])))
plt.plot(errors)
plt.show()layer_deltas的计算似乎特别复杂,但每当我试图简化它时,我就会破坏一些东西。任何我可以简化的提示,还是上面代码的任何提示?
发布于 2019-05-02 16:18:05
你最初的说法是你的网络是“武断的”。据我所见,我倾向于说,这并不像人们想象的那样武断。
任意:
不是任意的:
0.1)10000)error_delta < 1.0e-6)这些都是(或多或少)重要的方面,不需要"猴补“就可以调到原始类中(如果可能的话)。这些参数中的一些参数可以很容易地在构造函数中设置,或者在调用train时(针对划期数等)设置。错误函数也可以很容易地在初始化时设置,特别是因为它已经是一个不依赖于类内部的@staticmethod。
我假设一个灵活的优化器将超出您想要做的范围(并且有一个丰满 的 框架可以这样做)。
网络权重和偏差的初始化使用while循环,这是不必要的复杂。这里不需要while循环,因为您知道需要执行多少次迭代。因此,使用for循环代替,这将导致您到:
for i in range(len(structure)-1):
self.w.append(np.random.uniform(low=-1.0, high=1.0, size=(structure[i], structure[i+1])))
self.b.append(np.random.uniform(low=-1.0, high=1.0, size=(structure[i+1])))除了不需要继续跟踪索引变量之外,您还不需要使用带有负数索引的索引技巧,而且您可以使用.append(...)而不是插入前面。
哦,当我们处于初始化阶段时:没有必要将函数/派生元组转换为列表。您可以使用与over相同的方式来遍历/索引元组。
说到迭代,这个
layer_delta = []
iter_zi = iter(zip(reversed(range(len(z))), reversed(z)))
layer_delta.insert(0, (a[-1] - yi) * self.df[1](next(iter_zi)[1]))
for i, zi in iter_zi:
delta_i = np.multiply(layer_delta[0] @ self.w[i+1].T, self.df[i](z[i]))
layer_delta.insert(0, delta_i)是疯狂的™IMHO (而且也可能有™的错误)。如果您真的认为需要,请参阅这是如此的帖子关于如何反向枚举Python列表的内容。我不认为您需要这样做,因为您只使用了实际的list值一次,在所有其他情况下都使用了索引。会把那个怪物弄成这样
layer_delta = [(a[-1] - yi) * self.df[-1](z[-1])]
for i in reversed(range(len(z)-1)):
delta_i = np.multiply(layer_delta[0] @ self.w[i+1].T, self.df[i](z[i]))
layer_delta.insert(0, delta_i)你可以,也应该调整你的其他形式的怪物,以及上述。在对代码的这些部分进行重构时,我不能保证在解码代码时没有犯错误。
前传的实现也有点复杂的IMHO。因为您只通过网络一次,所以不需要存储所有层的激活和输出。你只需要最后一个。
您还在它自己的forward函数和train中实现了两次前传。如果您坚持原来的实现,请考虑是否还想返回其他值。这将只剩下一段代码可以修复,如果有问题。如果您担心可用性,因为应用程序阶段现在看到了所有的内部值,那么您可以实现一个内部函数,例如_forward,它做繁重的工作,让forward只返回最终的输出。
的注释和变量名
# Convergence testing, according to the last N errors:
error_delta = sum(reversed(errors[-5:]))
if error_delta < 1.0e-6:
print("Error delta reached, ", epoch, " exiting.")
break也有点离谱。终止准则似乎确实检查最后一个N(其中N硬编码为5)的和是否低于您(任意选择的)阈值。此外,自和不关心订单以来,没有必要在这里反转列表。
由于您使用的是Python 3,所以还可以使用新的f-string语法进行输出格式化:
print(f"Error delta reached in {epoch} exiting.")说到注释,您的类的方法都缺乏用户可见的文档。为此,Python程序员通常在他们的方法/类/函数上使用所谓的"""doc strings"""。例如:
def train(self, training_data, labels):
"""Train the neural network with all the available data
The training continues until the maximum number of epochs is reached or
the termination criterion is hit.
"""
# your code here ...用这种格式编写的文档将由所有主要的Python以及Python的内置help(...)函数来获取。
我知道在神经网络社区中有一些常见的命名约定,当你实现它的时候,你应该尽可能严格地遵守它们,这是你通常所做的。<#>但请记住,不要牺牲代码的清晰度和可读性。例如,z可以变成activation,a也可以被命名为layer_output (复数可能适用于用作列表的地方)。
https://codereview.stackexchange.com/questions/219583
复制相似问题