无法使用两层多层感知器(MLP)学习XOR表示

4
使用PyTorch的nn.Sequential模型,我无法学习所有四个XOR布尔值的表示。
import numpy as np

import torch
from torch import nn
from torch.autograd import Variable
from torch import FloatTensor
from torch import optim

use_cuda = torch.cuda.is_available()

X = xor_input = np.array([[0,0], [0,1], [1,0], [1,1]])
Y = xor_output = np.array([[0,1,1,0]]).T

# Converting the X to PyTorch-able data structure.
X_pt = Variable(FloatTensor(X))
X_pt = X_pt.cuda() if use_cuda else X_pt
# Converting the Y to PyTorch-able data structure.
Y_pt = Variable(FloatTensor(Y), requires_grad=False)
Y_pt = Y_pt.cuda() if use_cuda else Y_pt

hidden_dim = 5

model = nn.Sequential(nn.Linear(input_dim, hidden_dim),
                      nn.Linear(hidden_dim, output_dim),
                      nn.Sigmoid())
criterion = nn.L1Loss()
learning_rate = 0.03
optimizer = optim.SGD(model.parameters(), lr=learning_rate)
num_epochs = 10000

for _ in range(num_epochs):
    predictions = model(X_pt)
    loss_this_epoch = criterion(predictions, Y_pt)
    loss_this_epoch.backward()
    optimizer.step()
    print([int(_pred > 0.5) for _pred in predictions], list(map(int, Y_pt)), loss_this_epoch.data[0])

学习后:
for _x, _y in zip(X_pt, Y_pt):
    prediction = model(_x)
    print('Input:\t', list(map(int, _x)))
    print('Pred:\t', int(prediction))
    print('Ouput:\t', int(_y))
    print('######')

[输出]:

Input:   [0, 0]
Pred:    0
Ouput:   0
######
Input:   [0, 1]
Pred:    1
Ouput:   1
######
Input:   [1, 0]
Pred:    0
Ouput:   1
######
Input:   [1, 1]
Pred:    0
Ouput:   0
######

我已经尝试使用几个随机种子运行相同的代码,但它无法学习XOR表示。如果没有PyTorch,我可以轻松地训练一个具有自定义导数函数的模型,并手动执行反向传播,请参见https://www.kaggle.io/svf/2342536/635025ecf1de59b71ea4fa03eb84f9f9/results.html#After-some-enlightenment。为什么使用PyTorch的2层MLP无法学习XOR表示?

PyTorch中的模型如何:

hidden_dim = 5

model = nn.Sequential(nn.Linear(input_dim, hidden_dim),
                      nn.Linear(hidden_dim, output_dim),
                      nn.Sigmoid())

这个与手写的使用导数和手动编写反向传播和优化器步骤的内容https://www.kaggle.com/alvations/xor-with-mlp不同吗?

它们是相同的隐藏层感知器网络吗?


已更新

奇怪的是,在nn.Linear层之间添加nn.Sigmoid()并没有起作用:

hidden_dim = 5

model = nn.Sequential(nn.Linear(input_dim, hidden_dim),
                      nn.Sigmoid(),
                      nn.Linear(hidden_dim, output_dim),
                      nn.Sigmoid())
criterion = nn.L1Loss()
learning_rate = 0.03
optimizer = optim.SGD(model.parameters(), lr=learning_rate)
num_epochs = 10000

for _ in range(num_epochs):
    predictions = model(X_pt)
    loss_this_epoch = criterion(predictions, Y_pt)
    loss_this_epoch.backward()
    optimizer.step()

for _x, _y in zip(X_pt, Y_pt):
    prediction = model(_x)
    print('Input:\t', list(map(int, _x)))
    print('Pred:\t', int(prediction))
    print('Ouput:\t', int(_y))
    print('######')

[out]:
Input:   [0, 0]
Pred:    0
Ouput:   0
######
Input:   [0, 1]
Pred:    1
Ouput:   1
######
Input:   [1, 0]
Pred:    1
Ouput:   1
######
Input:   [1, 1]
Pred:    1
Ouput:   0
######

但是添加nn.ReLU()会:
model = nn.Sequential(nn.Linear(input_dim, hidden_dim),
                      nn.ReLU(), 
                      nn.Linear(hidden_dim, output_dim),
                      nn.Sigmoid())

...
for _x, _y in zip(X_pt, Y_pt):
prediction = model(_x)
print('Input:\t', list(map(int, _x)))
print('Pred:\t', int(prediction))
print('Ouput:\t', int(_y))
print('######')

[输出]:

Input:   [0, 0]
Pred:    0
Ouput:   0
######
Input:   [0, 1]
Pred:    1
Ouput:   1
######
Input:   [1, 0]
Pred:    1
Ouput:   1
######
Input:   [1, 1]
Pred:    1
Ouput:   0
######

对于非线性激活函数,sigmoid不够吗?

我知道ReLU适用于布尔输出的任务,但是Sigmoid函数难道不能产生相同/类似的效果吗?


更新2

运行相同的训练100次:

from collections import Counter 
import random
random.seed(100)

import torch
from torch import nn
from torch.autograd import Variable
from torch import FloatTensor
from torch import optim
use_cuda = torch.cuda.is_available()


all_results=[]

for _ in range(100):
    hidden_dim = 2

    model = nn.Sequential(nn.Linear(input_dim, hidden_dim),
                          nn.ReLU(), # Does the sigmoid has a build in biased? 
                          nn.Linear(hidden_dim, output_dim),
                          nn.Sigmoid())

    criterion = nn.MSELoss()
    learning_rate = 0.03
    optimizer = optim.SGD(model.parameters(), lr=learning_rate)
    num_epochs = 3000

    for _ in range(num_epochs):
        predictions = model(X_pt)
        loss_this_epoch = criterion(predictions, Y_pt)
        loss_this_epoch.backward()
        optimizer.step()
        ##print([float(_pred) for _pred in predictions], list(map(int, Y_pt)), loss_this_epoch.data[0])

    x_pred = [int(model(_x)) for _x in X_pt]
    y_truth = list([int(_y[0]) for _y in Y_pt])
    all_results.append([x_pred == y_truth, x_pred, loss_this_epoch.data[0]])


tf, outputsss, losses__ = zip(*all_results)
print(Counter(tf))

它只在100次中的18次中成功学习了XOR表示... -_-|||

3
我以前没有使用过PyTorch,但是有一件事情引起了我的注意,那就是你的MLP的架构。你在隐藏层中使用线性激活函数。然而,XOR问题无法通过线性方式解决。你可以尝试将隐藏层切换为ReLU、Sigmoid或其他非线性激活函数。 - Scratch'N'Purr
1
nn.Linear 之间是否有一个“预设”的 sigmoid 函数? - alvas
此外,在PyTorch论坛上也有相关讨论 https://discuss.pytorch.org/t/unable-to-learn-xor-representation-using-2-layers-of-multi-layered-perceptron-mlp/13287/6 - alvas
我明白了,那么请再添加一个线性到Sigmoid的组合,伪代码如下:model = Sequential(Linear, Sigmoid, Linear, Sigmoid, Linear, Sigmoid)。我看到你设置了hidden_dim = 2。对于Sigmoid,我会增加它,因为Sigmoid函数与ReLU的行为不同。 - Scratch'N'Purr
增加隐藏层的动机是什么?我认为增加隐藏层或层数并不能帮助解决问题。我怀疑问题出在LogLoss上,或者是PyTorch的sigmoid或linear函数中存在某种偏差。我无法弄清楚…… - alvas
显示剩余5条评论
4个回答

5

原因是nn.Linear没有内置激活函数,所以你的模型实际上是一个线性分类器。而XOR问题是无法使用线性分类器解决的典型例子。

修改为:

model = nn.Sequential(nn.Linear(input_dim, hidden_dim),
                      nn.Linear(hidden_dim, output_dim),
                      nn.Sigmoid())

转化为:

model = nn.Sequential(nn.Linear(input_dim, hidden_dim),
                      nn.Sigmoid(),
                      nn.Linear(hidden_dim, output_dim),
                      nn.Sigmoid())

只有这样,你的模型才会与链接的 Kaggle 笔记本中的模型相等。

1
啊,看起来PyTorch出现了一些bug。在线性层之间使用sigmoid函数无法成功训练网络,但是使用ReLU函数却可以。 - alvas
链接的 Kaggle 笔记本是不正确的。问题出在最后一个线性层之后使用了 sigmoid 函数。 - osipov

2
你离第二次更新已经很接近了。这里有一个带有可工作解决方案的笔记本:https://colab.research.google.com/github/osipov/edu/blob/master/misc/xor.ipynb 你的错误在于在最后一个线性层之后使用sigmoid,这使得优化器难以收敛到训练数据集中期望的0和1值。请记住,sigmoid在负无穷和正无穷时分别接近于0和1。
因此,你的实现(假设使用PyTorch 1.7)应该是:
import torch as pt
from torch.nn.functional import mse_loss
pt.manual_seed(33);

model = pt.nn.Sequential(
    pt.nn.Linear(2, 5),
    pt.nn.ReLU(),
    pt.nn.Linear(5, 1)
)

X = pt.tensor([[0, 0],
               [0, 1],
               [1, 0],
               [1, 1]], dtype=pt.float32)

y = pt.tensor([0, 1, 1, 0], dtype=pt.float32).reshape(X.shape[0], 1)

EPOCHS = 100

optimizer = pt.optim.Adam(model.parameters(), lr = 0.03)

for epoch in range(EPOCHS):
  #forward
  y_est = model(X)
  
  #compute mean squared error loss
  loss = mse_loss(y_est, y)

  #backprop the loss gradients
  loss.backward()

  #update the model weights using the gradients
  optimizer.step()

  #empty the gradients for the next iteration
  optimizer.zero_grad()

执行后训练模型,以便

model(X).round().abs()

返回
tensor([[0.],
        [1.],
        [1.],
        [0.]], grad_fn=<AbsBackward>)

哪一个是正确的输出。


1
这是我找到的唯一一个解释这种行为的帖子。我已经把头撞墙两天了,试图在我对这个例子的numpy实现中找到一个bug,而这最终解决了我的问题! - Vincent Scharf

-1

使用在网络层之间和最后一层的Sigmoid函数时,最重要的是以纯随机方式更新权重,即在每个样本之后进行更新,并在每次迭代中随机选择一个样本。

如果遵守这一点,并使用较大的学习率(约为1.0),我观察到使用标准的2层PyTorch实现(2-2-1层大小)进行XOR计算时,模型通常可以学习得很好,而且不需要正则化。


-1

这里有几个简单的代码更改,应该可以帮助您走上更好的道路。我在内部使用了ReLU激活函数,但如果正确使用,则Sigmoid也可以起作用。此外,如果您想尝试使用SGD优化器,您可能需要将学习率降低一个数量级左右。

model = nn.Sequential(nn.Linear(input_dim, hidden_dim),    
                      nn.ReLU(),       
                      nn.Linear(hidden_dim, output_dim),
                      nn.Sigmoid())
if use_cuda:
  model.cuda()

criterion = nn.BCELoss()
#criterion = nn.L1Loss()
#learning_rate = 0.03
#optimizer = optim.SGD(model.parameters(), lr=learning_rate)
optimizer = optim.Adam(model.parameters())
num_epochs = 10000


for epoch in range(num_epochs):
    predictions = model(X_pt)
    loss_this_epoch = criterion(predictions, Y_pt)
    model.zero_grad()
    loss_this_epoch.backward()
    optimizer.step()
    if epoch%1000 == 0: 
      print([float(_pred) for _pred in predictions], list(map(int, Y_pt)), loss_this_epoch.data[0])

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接