我实现了一个简单的neural network。它在使用’sigmoid + 交叉熵’、’sigmoid + 二次成本’和’tanh + 二次成本’时运行良好,但使用’tanh + 交叉熵’时不起作用(效果不比随机猜测好)。请问有人能帮我找出原因吗?请看FullConnectedLayer的代码:
class FullConnectedLayer(BaseLayer): """ FullConnectedLayer
~~~~~~~~~~~~~~~~~~~~
数据成员:
sizes ---- <type list> 网络的尺寸
n_layers ---- <type int> 子层数量
activation ---- <type Activation> 神经元的激活函数
weights ---- <type list> 存储权重
biases ---- <type list> 存储偏置
neurons ---- <type list> 存储神经元的状态(输出)
zs ---- <type list> 存储加权输入到神经元
grad_w ---- <type list> 存储成本相对于权重的梯度
grad_b ---- <type list> 存储成本相对于偏置的梯度
---------------------
方法:
__init__(self, sizes, activation = Sigmoid())
size(self)
model(self)
feedforward(self, a)
backprop(self, C_p)
update(self, eta, lmbda, batch_size, n)
"""
def __init__(self, sizes, activation = Sigmoid(), normal_initialization = False):
"""
列表''sizes''包含网络各层的神经元数量。例如,sizes = [2, 3, 2]表示3层,第1层有2个神经元,第2层有3个神经元,第3层有2个神经元。
请注意,输入层可能由其他类型的层传递,当连接在该层之后,我们不为此层设置偏置。
还请注意,如果输出层连接在该层之前,则传递给其他层,在这种情况下,只需将输出分配给其输入即可。
例如,Layer1([3, 2, 4])->Layer2([4, 6, 3])->Layer3([3, 2])。只需将Layer1的输出分配给Layer2的输入,这样就安全了。
"""
BaseLayer.__init__(self, sizes, activation)
if normal_initialization:
self.weights = [np.random.randn(j, i)
for i, j in zip(sizes[:-1], sizes[1:])]
else:
self.weights = [np.random.randn(j, i) / np.sqrt(i)
for i, j in zip(sizes[:-1], sizes[1:])]
self.biases = [np.random.randn(j, 1) for j in sizes[1:]]
self.grad_w = [np.zeros(w.shape) for w in self.weights]
self.grad_b = [np.zeros(b.shape) for b in self.biases]
def feedforward(self, a):
"""
如果''a''是输入,返回网络的输出。
"""
self.neurons = [a] # 存储所有层的激活值(输出)
self.zs = []
for w, b in zip(self.weights, self.biases):
z = np.dot(w, self.neurons[-1]) + b
self.zs.append(z)
self.neurons.append(self.activation.func(z))
return self.neurons[-1]
def backprop(self, Cp_a):
"""
反向传播误差。
------------------------------
返回一个元组,其第一部分是权重和偏置的梯度列表,第二部分是反向传播的误差。
Cp_a, dC/da: 成本函数相对于a的导数,a是神经元的输出。
"""
# 最后一层
delta = Cp_a * self.activation.prime(self.zs[-1])
self.grad_b[-1] += delta
self.grad_w[-1] += np.dot(delta, self.neurons[-2].transpose())
for l in range(2, self.n_layers):
sp = self.activation.prime(self.zs[-l]) # a.prime(z)
delta = np.dot(self.weights[-l + 1].transpose(), delta) * sp
self.grad_b[-l] += delta
self.grad_w[-l] += np.dot(delta, self.neurons[-l - 1].transpose())
Cp_a_out = np.dot(self.weights[0].transpose(), delta)
return Cp_a_out
def update(self, eta, lmbda, batch_size, n):
"""
通过应用梯度下降算法更新网络的权重和偏置。
''eta''是学习率
''lmbda''是正则化参数
''n''是训练数据集的总大小
"""
self.weights = [(1 - eta * (lmbda/n)) * w - (eta/batch_size) * delta_w\
for w, delta_w in zip(self.weights, self.grad_w)]
self.biases = [ b - (eta / batch_size) * delta_b\
for b, delta_b in zip(self.biases, self.grad_b)]
# 清除''grad_w''和''grad_b'',以便它们不会被添加到下一次更新中
for dw, db in zip(self.grad_w, self.grad_b):
dw.fill(0)
db.fill(0)
这是tanh函数的代码:
class Tanh(Activation): @staticmethod def func(z): """ 功能。 """ return (np.exp(z) - np.exp(-z)) / (np.exp(z) + np.exp(-z)) @staticmethod def prime(z): """ 导数。 """ return 1. - Tanh.func(z) ** 2
这是交叉熵类的代码:
class CrossEntropyCost(Cost): @staticmethod def func(a, y): """ 返回与输出''a''和期望输出''y''相关的成本。
请注意,使用np.nan_to_num来确保数值稳定性。特别是,如果''a''和''y''在同一个位置都有1.0,
那么表达式(1-y) * np.log(1-a)会返回nan。np.nan_to_num确保将其转换为正确的值(0.0)。
"""
for ai in a: if ai < 0: print("在CrossEntropyCost.func(a, y)中...需要a_i > 0,a_i属于a。") exit(1) return np.sum(np.nan_to_num(-y * np.log(a) - (1-y) * np.log(1-a))) @staticmethod def Cp_a(a, y): """ Cp_a, dC/da: C相对于a的导数
''a''是神经元的输出
''y''是神经元的期望输出
"""
#return (a - y) # delta
return (a - y) / (a * (1 - a))
编辑:看起来问题是tanh的范围是-1到+1,这对于交叉熵来说是非法的。但是,如果我只想使用tanh激活和交叉熵成本,应该如何处理呢?
回答:
看起来你在输出层使用了tanh
,而tanh
的范围是-1, +1
,期望的输出在0, +1
范围内。这对于产生0, +1
范围输出的Sigmoid
来说无关紧要。