【问题标题】:Multilayer Perceptron works fine with sigmoid activations, but not with hyperbolic tangent多层感知器适用于 sigmoid 激活,但不适用于双曲正切
【发布时间】:2018-09-20 03:36:37
【问题描述】:

我编写了一个单层的简单 MLP。当使用 sigmoid 激活学习 XOR 函数时,损失不断减少。但是,如果我将 XOR 数据的标签从 [0, 1] 更改为 [-1, 1] 并改用 tanh 激活,则损失不会减少。它应该仍然可以通过简单地更改标签并使用 tanh 来工作,不是吗?如果是这样,我的 tanh 实现哪里出了问题?

import sys

import numpy as np


class DataSet:

    def __init__(self, data):
        self.size = len(data)
        self.inputs, self.labels = [], []
        for i in range(len(data)):
            self.inputs.append(data[i][0])
            self.labels.append(data[i][1])
        try:
            self.numInputs = len(self.inputs[0])
        except TypeError:
            self.numInputs = 1
        try:
            self.numOutputs = len(self.labels[0])
        except TypeError:
            self.numOutputs = 1


class MLP:

    def __init__(self, numInputs, numHidden, numOutputs, activationFunction):
        # MLP architecture sizes
        self.numInputs = numInputs
        self.numHidden = numHidden
        self.numOutputs = numOutputs
        self.activationFunction = activationFunction.lower()

        # MLP weights
        self.IH_weights = np.random.rand(numInputs, numHidden)      # Input -> Hidden
        self.HO_weights = np.random.rand(numHidden, numOutputs)     # Hidden -> Output

        # Gradients corresponding to weight matrices computed during backprop
        self.IH_gradients = np.zeros_like(self.IH_weights)
        self.HO_gradients = np.zeros_like(self.HO_weights)

        # Input, hidden and output neuron values
        self.I = np.zeros(numInputs)    # Inputs
        self.L = np.zeros(numOutputs)   # Labels
        self.H = np.zeros(numHidden)    # Hidden
        self.O = np.zeros(numOutputs)   # Output

    # ADD BIAS FOR RELU ########################################################
    # ADD SOFTMAX ##############################################################
    def activation(self, x, derivative=False):
        if self.activationFunction == 'sigmoid':
            if derivative:
                return x * (1 - x)
            return 1 / (1 + np.exp(-x))

        if self.activationFunction == 'tanh':
            if derivative:
                return 1. - np.tanh(x) ** 2
            return np.tanh(x)

        # if self.activationFunction == 'softmax':
        #     if derivative:
        #         return ########
        #     return np.exp(x) / np.sum(np.exp(x))

        if self.activationFunction == 'relu':
            if derivative:
                return (x > 0).astype(float)
            return np.maximum(0, x)

        print("ERROR: Activation function not found.")
        sys.exit()

    def forward(self, inputs):
        # Ensure that inputs is a list
        try:
            len(inputs)
        except TypeError:
            inputs = [inputs]

        self.I = np.array(inputs).reshape(1, self.numInputs)
        self.H = self.I.dot(self.IH_weights)
        self.H = self.activation(self.H)
        self.O = self.H.dot(self.HO_weights)
        self.O = self.activation(self.O)
        return self.O, self.L

    def backwards(self, labels):
        # Ensure that labels is a list
        try:
            len(labels)
        except TypeError:
            labels = [labels]

        self.L = np.array(labels)
        self.O_error = self.L - self.O
        self.O_delta = self.O_error * self.activation(self.O, derivative=True)

        self.H_error = self.O_delta.dot(self.HO_weights.T)
        self.H_delta = self.H_error * self.activation(self.H, derivative=True)

        self.IH_gradients += self.I.T.dot(self.H_delta)
        self.HO_gradients += self.H.T.dot(self.O_delta)

        return np.sum(self.O_error ** 2)

    def updateWeights(self, learningRate):
        self.IH_weights += self.IH_gradients
        self.HO_weights += self.HO_gradients
        self.IH_gradients = np.zeros_like(self.IH_weights)
        self.HO_gradients = np.zeros_like(self.HO_weights)

# data = DataSet([
#     [[0, 0], 0],
#     [[0, 1], 1],
#     [[1, 0], 1],
#     [[1, 1], 0]
# ])
#
# mlp = MLP(data.numInputs, 20, data.numOutputs, 'sigmoid')

data = DataSet([
    [[0, 0], -1],
    [[0, 1], 1],
    [[1, 0], 1],
    [[1, 1], -1]
])

mlp = MLP(data.numInputs, 20, data.numOutputs, 'tanh')

numEpochs = 10000
learningRate = 0.01

losses = []
for epoch in range(numEpochs):
    errors = []
    correct = []
    for i in range(data.size):
        mlp.forward(data.inputs[i])
        errors.append(mlp.backwards(data.labels[i]))
    mlp.updateWeights(learningRate)
    epochLoss = np.mean(errors)
    losses.append(epochLoss)
    if epoch % 100 == 0 or epoch == numEpochs - 1:
        print("EPOCH:", epoch)
        print("LOSS: ", epochLoss, "\n")

【问题讨论】:

    标签: python machine-learning neural-network deep-learning


    【解决方案1】:

    您的网络无法预测的样本是 ([0,0], -1),这是由于缺乏偏差造成的。对于 I = [0,0],我们有 H = dot(Wh,I) = [0,0,..0]。由于 tanh(0) = 0,网络无法在任何层学习从零到非零的任何转换。添加偏见将解决问题;可以通过向填充任何常数的数据集添加第三列来模拟偏差的影响,从而实现一个 hacky 解决方案。

    【讨论】:

    • 不幸的是,这似乎没有任何区别。我还看到人们只使用return 1. - x ** 2,大概是出于类似的原因。这也对我不起作用。
    • 对我来说,这两种方式都会不断产生略高于 0.25 的损失(它会缓慢减少每个时期的微小量,但绝不会低于 0.25)。使用 sigmoid,损失会迅速降低到低得多的值。你有什么改变吗?
    • 给我一点时间,我会回复你
    • 我实际上刚刚发现了我的问题。我从来没有将梯度乘以 updateWeights 中的学习率,所以我认为在这种特殊情况下,它是在损失函数的山谷中“来回跳跃”。
    • 没关系,这只是让损失减少到 0.25 的速度要慢得多。也许 0.25 实际上是一个非常低的损失,但我只是期望它会更低,因为当标签和预测在使用 sigmoid 的一半范围内时,它比使用 tanh 时要低得多。我会调查的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-02-22
    • 2014-07-02
    • 2018-10-04
    • 2013-01-08
    • 1970-01-01
    • 1970-01-01
    • 2015-05-14
    相关资源
    最近更新 更多