为何我的PyTorch神经网络无法学习含噪声的XOR问题?
解决PyTorch中含噪声XOR问题模型无法学习的问题
你的代码存在几个关键问题,导致模型无法更新权重、无法学习,以下是具体问题和修复方案:
核心问题分析
- 训练轮数过少:仅50轮训练不足以让多层网络学会XOR模式,这类简单非线性问题通常需要数千轮训练才能收敛。
- Sigmoid激活的梯度消失:Sigmoid函数在输入绝对值较大时梯度趋近于0,初始的线性层输出很容易落在这个饱和区域,导致反向传播时梯度无法有效传递到前层权重,权重几乎不更新。
- 学习率设置不合理:SGD优化器搭配0.01的学习率过小,再加上Sigmoid的梯度衰减,权重更新幅度微乎其微。
- 权重初始化默认值的局限:PyTorch默认的Linear层初始化可能让Sigmoid一开始就进入饱和状态,进一步加剧梯度消失问题。
修复后的代码
import numpy as np import matplotlib.pyplot as plt from sklearn.datasets import make_blobs from sklearn.model_selection import train_test_split import torch # 生成含噪声的XOR数据 X, y = make_blobs(n_samples=200, n_features=2, cluster_std=.1, centers= [(1,1), (1,0), (0,0),(0,1)]) y[y==2] = 0 y[y==3] = 1 x_train, x_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=19) # 转换为PyTorch张量,同时调整标签维度匹配模型输出 x_train = torch.FloatTensor(x_train) x_test = torch.FloatTensor(x_test) y_train = torch.FloatTensor(y_train).unsqueeze(1) y_test = torch.FloatTensor(y_test).unsqueeze(1) class XOR(torch.nn.Module): def __init__(self): super(XOR, self).__init__() self.layer1 = torch.nn.Linear(2, 4) # 增加神经元数量提升拟合能力 self.layer2 = torch.nn.Linear(4, 1) self.non_linear = torch.nn.ReLU() # 替换Sigmoid为ReLU,避免梯度消失 # 手动初始化权重,避免激活函数初始饱和 torch.nn.init.xavier_uniform_(self.layer1.weight) torch.nn.init.xavier_uniform_(self.layer2.weight) def forward(self, x): output = self.layer1(x) output = self.non_linear(output) output = self.layer2(output) output = torch.nn.Sigmoid()(output) # 仅输出层用Sigmoid适配BCELoss return output model = XOR() criterion = torch.nn.BCELoss() optimizer = torch.optim.SGD(model.parameters(), lr=0.1) # 调大学习率 # 也可替换为Adam优化器:optimizer = torch.optim.Adam(model.parameters(), lr=0.001) model.train() epoch = 5000 # 大幅增加训练轮数 for e in range(epoch): y_pred = model(x_train) loss = criterion(y_pred, y_train) optimizer.zero_grad() loss.backward() optimizer.step() # 每500轮打印一次loss,避免输出冗余 if (e + 1) % 500 == 0: print(f'Epoch {e+1}: train loss: {loss.item():.4f}') model.eval() with torch.no_grad(): # 评估阶段关闭梯度计算,节省资源 # 训练集准确率计算 y_pred = model(x_train) y_pred = (y_pred > 0.5).int() train_acc = torch.sum(y_pred == y_train.int()) / y_train.shape[0] print(f"train ACC: {train_acc.float():.4f}") # 测试集准确率计算 y_pred = model(x_test) y_pred = (y_pred > 0.5).int() test_acc = torch.sum(y_pred == y_test.int()) / y_test.shape[0] print(f"test ACC: {test_acc.float():.4f}")
关键修改点说明
- 替换激活函数:用ReLU替换隐藏层的Sigmoid,避免梯度消失问题;仅在输出层保留Sigmoid,适配BCELoss的输入要求。
- 增加训练轮数:从50轮改为5000轮,给模型足够的学习时间。
- 调优学习率:将SGD的学习率从0.01提升到0.1,确保权重有足够的更新幅度;也可以直接使用Adam优化器,它的自适应学习率对新手更友好。
- 优化权重初始化:使用Xavier初始化方法,让线性层的输出更适合激活函数,避免初始状态就进入饱和区。
- 调整标签维度:将y_train和y_test转为二维张量,和模型输出的维度保持一致,避免计算loss时的维度不匹配问题。
- 评估阶段优化:使用
torch.no_grad()关闭梯度计算,节省内存和计算资源。
内容的提问来源于stack exchange,提问作者fehctap
相关产品推荐
相关产品推荐

