You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,预测结果却完全错误。我猜测可能陷入局部最小值,但不确定。我已尝试调整批次大小、网络层数、训练轮数,但问题仍未解决。请问模型失效的原因是什么,该如何改进?


分析与改进方案

核心失效原因

  1. 输出数据类型错误
    你把比分除以10后得到的是浮点数(比如1分变成0.1),但转换为torch.int16时,所有小数都会被截断为0。这意味着你的标签output_data全是0,模型只需要输出全0就能让MSE损失为0,完全没有学到任何有效信息,这就是训练损失为0但预测完全错误的根本原因。

  2. 输入数据处理错误
    球队的整数ID是类别型数据,不是数值型数据。直接把ID输入全连接层会让模型错误地认为ID数值越大的球队越强(比如ID37比ID16强),但实际上ID只是无意义的编号,这种输入方式无法让模型学习到球队的真实实力特征。

  3. 激活函数选择错误
    最后一层用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 04:00:09