我想使用PyTorch进行一些神经网络实验,所以我尝试了一个简单的练习作为热身,但结果让我有些困惑。
这个练习试图根据问题的各种统计数据(如变量数量、最大子句长度等)来预测1000个TPTP问题的评级。数据文件https://github.com/russellw/ml/blob/master/test.csv非常简单,有1000行,最后一列是评级,初始有几十列输入数据,所有数字都缩放到0-1的范围内,我逐步删除特征以查看结果是否仍然有效,确实如此,一直减少到只剩一列输入;其他版本可以在Git历史中找到。
我开始时使用了独立的训练和测试集,但目前暂时搁置了测试集,因为关于训练性能是否能推广到测试的问题,只有在首先获得训练性能后才会出现。
对这个数据集进行的简单线性回归的均方误差约为0.14。
我实现了一个简单的前馈神经网络,代码在https://github.com/russellw/ml/blob/master/test_nn.py,并复制如下,经过几百个训练轮次后,均方误差也为0.14。
所以我尝试将隐藏层的数量从1改为2再改为3,使用了不同的优化器,调整了学习率,将激活函数从relu改为tanh再改为两者的混合,增加了轮次到5000,增加了隐藏单元的数量到1000。此时,它应该已经有能力完全记住整个数据集。(此时我并不担心过拟合。我只是想让训练数据的均方误差变成0.14以外的其他值。)没有任何改变。仍然是0.14。我会说它一定是陷入了局部最优,但这不应该发生在你有几百万权重的时候;所有参数同时处于局部最优应该是几乎不可能的。而且每次运行时我确实得到了稍微不同的数字序列。但它总是收敛到0.14。
显而易见的结论是,对于这个问题,0.14已经是最好的了,除了即使网络有足够的记忆来记住所有数据,它仍然保持不变。但关键是,我还尝试了随机森林,https://github.com/russellw/ml/blob/master/test_rf.py
… 而随机森林在原始数据集上的均方误差为0.01,随着特征的删除性能逐渐下降,在仅剩一个特征的数据上仍然为0.05。
在机器学习的传说中,从未听说过“随机森林大大优于神经网络”,所以我显然是做错了什么,但我看不出是什么。也许只是简单地错过了一个标志或在PyTorch中需要设置的某些东西。如果有人能看一看,我将不胜感激。
import numpy as npimport pandas as pdimport torchimport torch.nn as nn# datadf = pd.read_csv("test.csv")print(df)print()# separate the output columny_name = df.columns[-1]y_df = df[y_name]X_df = df.drop(y_name, axis=1)# numpy arraysX_ar = np.array(X_df, dtype=np.float32)y_ar = np.array(y_df, dtype=np.float32)# torch tensorsX_tensor = torch.from_numpy(X_ar)y_tensor = torch.from_numpy(y_ar)# hyperparametersin_features = X_ar.shape[1]hidden_size = 100out_features = 1epochs = 500# modelclass Net(nn.Module): def __init__(self, hidden_size): super(Net, self).__init__() self.L0 = nn.Linear(in_features, hidden_size) self.N0 = nn.ReLU() self.L1 = nn.Linear(hidden_size, hidden_size) self.N1 = nn.Tanh() self.L2 = nn.Linear(hidden_size, hidden_size) self.N2 = nn.ReLU() self.L3 = nn.Linear(hidden_size, 1) def forward(self, x): x = self.L0(x) x = self.N0(x) x = self.L1(x) x = self.N1(x) x = self.L2(x) x = self.N2(x) x = self.L3(x) return xmodel = Net(hidden_size)criterion = nn.MSELoss()optimizer = torch.optim.Adam(model.parameters(), lr=0.1)# trainprint("training")for epoch in range(1, epochs + 1): # forward output = model(X_tensor) cost = criterion(output, y_tensor) # backward optimizer.zero_grad() cost.backward() optimizer.step() # print progress if epoch % (epochs // 10) == 0: print(f"{epoch:6d} {cost.item():10f}")print()output = model(X_tensor)cost = criterion(output, y_tensor)print("mean squared error:", cost.item())
回答:
你能打印出输入的形状吗?我建议你首先检查以下几点:
- 确保你的目标y的形状是
(-1, 1)
,我不知道PyTorch在这情况下是否会抛出错误。你可以使用y.reshape(-1, 1)
如果它不是二维的 - 你的学习率过高。通常使用Adam时,默认值就足够好,或者你可以尝试简单地降低学习率。0.1作为起始学习率是一个较高的值
- 将optimizer.zero_grad()放在for循环的第一行
- 标准化/归一化你的数据(这通常对神经网络有好处)
- 移除数据中的异常值(我的意见:我认为这对随机森林的影响不大,但对神经网络的影响可能很大)
- 使用交叉验证(也许skorch可以在这里帮助你。它是一个PyTorch的scikit-learn包装器,使用起来很简单,如果你熟悉keras的话)
请注意,随机森林回归器或任何其他回归器在某些情况下可能优于神经网络。有些领域神经网络是英雄,如图像分类或自然语言处理,但你需要意识到一个简单的回归算法可能优于它们。通常当你的数据不够大时会发生这种情况。