PyTorch足球比分预测神经网络失效问题排查与优化咨询
问题
我尝试构建一个预测足球比分的模型,使用1872到2017年的国际足球比赛结果数据集。我为每支球队分配了唯一的整数作为标识,代码如下:
import csv import torch import torch.nn as nn import torch.optim as optim import numpy as np import pandas as pd import pandas as pd data = [] with open('/Volumes/Drive 2/Football nn/output.csv', 'r') as csvfile: reader = csv.reader(csvfile) for row in reader: data.append(row) # team names home_team = [str(row[1]) for row in data] away_team = [str(row[2]) for row in data] #team scores old_home_score = [str(row[3]) for row in data] old_away_score = [str(row[4]) for row in data] home_score = [] away_score = [] print(home_team[44762]) print(away_team[44762]) home_team.pop(0) away_team.pop(0) old_home_score.pop(0) old_away_score.pop(0) for item in (old_home_score): iteam = int(item) iteam /=10 home_score.append(iteam) for item in (old_away_score): iteam = int(item) iteam /=10 away_score.append(iteam) print(away_score[44761]) print(home_score[44761]) home_team = [eval(i) for i in home_team] away_team = [eval(i) for i in away_team] class SoccerNN(nn.Module): def __init__(self): super(SoccerNN, self).__init__() self.fc1 = nn.Linear(2, 15) self.fc2 = nn.Linear(15, 20) self.fc3 = nn.Linear(20, 10) self.fc4 = nn.Linear(10, 2) def forward(self, x): x = self.fc1(x) x = self.fc2(x) x = self.fc3(x) x = self.fc4(x) x = torch.sigmoid(x) return x # Convert the columns to PyTorch tensors input_data = torch.tensor(np.column_stack((home_team, away_team)), dtype=torch.int16) output_data = torch.tensor(np.column_stack((home_score, away_score)), dtype=torch.int16) model = SoccerNN() criterion = nn.MSELoss() optimizer = optim.Adam(model.parameters(), lr=0.001) # Training loop epochs = 50 # Adjust the number of epochs as needed batch_size = 10 # Adjust the batch size as needed for epoch in range(epochs): for batch_start in range(0, len(input_data), batch_size): batch_end = batch_start + batch_size batch_input = input_data[batch_start:batch_end] batch_output = output_data[batch_start:batch_end] # Convert batch_input and batch_output to the same dtype as the model's parameters batch_input = batch_input.to(model.fc1.weight.dtype) batch_output = batch_output.to(model.fc2.weight.dtype) # Zero the gradients optimizer.zero_grad() # Forward pass predictions = model(batch_input) # Calculate loss loss = criterion(predictions, batch_output) # Backpropagation loss.backward() # Update weights optimizer.step() print(f"Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}") # After training, you can use the model for predictions home_team = [16] away_team = [37] new_data = torch.tensor(np.column_stack((home_team, away_team)), dtype=torch.int16) new_data = torch.tensor(new_data, dtype=torch.float32) # Use the appropriate data type # Put the model in evaluation mode model.eval() # Make predictions with torch.no_grad(): predictions = model(new_data) # Convert predictions to numpy array predictions_np = predictions.numpy() # Print or use the predictions as needed print(predictions_np)
我将代表两支球队的整数作为输入,期望模型输出预测比分。但训练时损失显示为0,预测结果却完全错误。我猜测可能陷入局部最小值,但不确定。我已尝试调整批次大小、网络层数、训练轮数,但问题仍未解决。请问模型失效的原因是什么,该如何改进?
分析与改进方案
核心失效原因
输出数据类型错误
你把比分除以10后得到的是浮点数(比如1分变成0.1),但转换为torch.int16时,所有小数都会被截断为0。这意味着你的标签output_data全是0,模型只需要输出全0就能让MSE损失为0,完全没有学到任何有效信息,这就是训练损失为0但预测完全错误的根本原因。输入数据处理错误
球队的整数ID是类别型数据,不是数值型数据。直接把ID输入全连接层会让模型错误地认为ID数值越大的球队越强(比如ID37比ID16强),但实际上ID只是无意义的编号,这种输入方式无法让模型学习到球队的真实实力特征。激活函数选择错误
最后一层用sigmoid会把输出限制在0-1之间,但即使你修复了标签的类型问题,比分归一化后可能超过1(比如比分11的话是1.1),而且sigmoid不适合回归任务的最终输出,会限制模型的预测能力。
具体改进措施
1. 修复数据类型问题
不要将home_score和away_score转换为int16,保留浮点类型:
# 替换原来的output_data定义 output_data = torch.tensor(np.column_stack((home_score, away_score)), dtype=torch.float32)
2. 用Embedding层处理球队ID
把球队ID转换为嵌入向量,让模型学习每个球队的特征表示:
class SoccerNN(nn.Module): def __init__(self, num_teams, embed_dim=16): super(SoccerNN, self).__init__() # 球队嵌入层,num_teams是球队的总数(最大ID+1) self.team_embedding = nn.Embedding(num_teams, embed_dim) # 拼接后是2*embed_dim维的输入 self.fc1 = nn.Linear(2*embed_dim, 32) self.fc2 = nn.Linear(32, 16) self.fc3 = nn.Linear(16, 2) # 输出主客场比分 def forward(self, x): # x的形状是[batch_size, 2],分别是主场和客场球队ID home_embed = self.team_embedding(x[:, 0]) away_embed = self.team_embedding(x[:, 1]) # 拼接两个嵌入向量 x = torch.cat([home_embed, away_embed], dim=1) x = torch.relu(self.fc1(x)) x = torch.relu(self.fc2(x)) x = self.fc3(x) # 回归任务用线性激活,输出原始归一化后的比分 return x
初始化模型时需要传入球队总数:
max_team_id = max(max(home_team), max(away_team)) + 1 # 确保覆盖所有ID model = SoccerNN(max_team_id)
3. 调整激活函数与损失函数
- 去掉最后一层的
sigmoid,回归任务直接用线性激活。 - 考虑使用泊松损失(
nn.PoissonNLLLoss),因为比分是离散的非负整数,泊松分布更贴合这类数据的特性,比MSE更合适:criterion = nn.PoissonNLLLoss(log_input=False) # 如果输出是原始比分,设置log_input=False
如果你坚持用归一化后的比分,也可以继续用MSE,但要确保输出范围匹配。
4. 优化数据处理流程
用pandas读取CSV更简洁,避免手动处理表头和类型转换:
df = pd.read_csv('/Volumes/Drive 2/Football nn/output.csv') home_team = df['home_team_id'].values # 假设你的CSV里有球队ID列 away_team = df['away_team_id'].values home_score = df['home_score'].values / 10.0 away_score = df['away_score'].values / 10.0
5. 训练过程优化
- 划分训练集和测试集,用测试集验证模型的泛化能力,避免过拟合:
from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split( np.column_stack((home_team, away_team)), np.column_stack((home_score, away_score)), test_size=0.2, random_state=42 ) - 减少训练时的loss打印频率,比如每个epoch结束后打印一次平均loss,避免输出冗余。
- 可以尝试调整学习率,或者加入Dropout层防止过拟合:
self.dropout = nn.Dropout(0.2) # 在forward中加入 x = self.dropout(torch.relu(self.fc1(x)))
内容的提问来源于stack exchange,提问作者Serge Brainin

