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

基于Flux.jl的Minimal U-Net训练MethodError问题排查与修复

问题:Flux.jl实现U-Net时的MethodError问题及修复方案

我有Python/PyTorch开发经验,现在想用Julia的Flux.jl实现U-Net,最终用于科学问题的神经网络训练,先以KITTI数据集做实验。转写Julia代码后未达预期,简化为合成数据的最小U-Net模型时,训练出现如下错误:

ERROR: MethodError: no method matching (::var"#17#19"{var"#loss#18"{…}, Array{…}, Array{…}})(::@NamedTuple{layers::Tuple{…}})

The function #17 exists, but no method is defined for this combination of argument types.

注:#17后的数字会随代码变化。

已完成工作

  • 基于少量卷积和转置卷积层构建最小化U-Net模型
  • 使用64×64的合成输入图像与二值掩码作为数据集
  • 尝试通过Flux.jl的gradient函数和logitcrossentropy损失搭建基础训练循环
  • 测试不同U-Net实现方案(如指定GitHub仓库)

怀疑的问题根源

  • 模型输出形状与目标标签形状不匹配
  • 损失函数未返回梯度计算所需的标量值

可复现代码

using Flux 
using Flux: Conv, ConvTranspose, relu, MaxPool, Dense, Chain, params
using Base.Iterators: partition
using Random
using Plots 

# Summary:
# This code defines a U-Net architecture for image segmentation using the Flux library in Julia.
# It creates synthetic data, prepares batches, trains the U-Net model, and tests the trained model.
# The problem is to ensure the U-Net model is correctly implemented and trained on the synthetic dataset.

# Define the U-Net architecture
function unet(input_channels::Int, output_channels::Int)
    encoder = Chain(
        Conv((3, 3), input_channels => 64, pad=1), relu, MaxPool((2, 2), stride=(2, 2)),
        Conv((3, 3), 64 => 128, pad=1), relu, MaxPool((2, 2), stride=(2, 2)),
        Conv((3, 3), 128 => 256, pad=1), relu, MaxPool((2, 2), stride=(2, 2)),
        Conv((3, 3), 256 => 512, pad=1), relu, MaxPool((2, 2), stride=(2, 2))
    )
    
    decoder = Chain(
        ConvTranspose((3, 3), 512 => 256, stride=2, pad=1), relu,
        ConvTranspose((3, 3), 256 => 128, stride=2, pad=1), relu,
        ConvTranspose((3, 3), 128 => 64, stride=2, pad=1), relu,
        ConvTranspose((3, 3), 64 => output_channels, stride=2, pad=1)
    )
    
    return Chain(encoder, decoder, x -> x[:, 1:64, 1:64, :])
end

# Create a synthetic dataset for image segmentation
function create_test_data(num_samples::Int)
    data = []
    for _ in 1:num_samples
        image = rand(Float32, 64, 64, 1)
        mask = rand(Bool, 64, 64, 1)
        push!(data, (image, mask))
    end
    return data
end

# Split the synthetic dataset into batches
function prepare_batches(data, batch_size::Int)
    batches = []
    for batch in partition(data, batch_size)
        input_batch = cat([x[1] for x in batch]..., dims=4)
        mask_batch = cat([x[2] for x in batch]..., dims=4)
        push!(batches, (input_batch, mask_batch))
    end
    return batches
end

# Implement a training loop for the U-Net model
function train_unet(model, train_data, num_epochs::Int, learning_rate::Float64)
    opt = ADAM(learning_rate)
    loss(x, y) = Flux.logitcrossentropy(model(x), float(y))
    
    for epoch in 1:num_epochs
        for (input_batch, mask_batch) in train_data
            gs = gradient(() -> loss(input_batch, mask_batch), Flux.trainable(model))
            Flux.Optimise.update!(opt, Flux.trainable(model), gs)
        end
        println("Epoch $epoch complete")
    end
end

# Test a trained U-Net model
function test_unet(model, test_image)
    prediction = model(test_image)
    plot(plot(test_image[:, :, 1, 1], title="Input Image"),
         plot(prediction[:, :, 1, 1], title="Predicted Mask"),
         layout=(1, 2))
end

# Example usage
model = unet(1, 1)
data = create_test_data(100)
batches = prepare_batches(data, 8)
train_unet(model, batches, 10, 0.001)
test_image, _ = data[1]
test_unet(model, test_image)

问题原因及修复方案

1. 核心错误原因

调用gradient时传入的Flux.trainable(model)返回的是嵌套的NamedTuple结构(因为模型是Chain,内部的encoder和decoder也是Chain,trainable会递归返回它们的参数),但gradient函数的第二个参数需要的是可迭代的参数集合(比如params(model)返回的参数列表),而非NamedTuple。这导致梯度计算时无法正确处理输入的参数结构,引发MethodError。

此外,U-Net模型的输出尺寸匹配存在隐患:当前依赖切片操作修正转置卷积的输出尺寸,而非通过调整层参数确保尺寸精准匹配。

2. 具体修复步骤

修复1:替换Flux.trainable(model)为params(model)

在训练循环中,gradient的第二个参数应使用params(model)获取所有可训练参数的集合,修改后的train_unet函数:

function train_unet(model, train_data, num_epochs::Int, learning_rate::Float64)
    opt = ADAM(learning_rate)
    loss(x, y) = Flux.logitcrossentropy(model(x), float(y))
    
    for epoch in 1:num_epochs
        for (input_batch, mask_batch) in train_data
            # 用params(model)替代Flux.trainable(model)
            gs = gradient(() -> loss(input_batch, mask_batch), params(model))
            Flux.Optimise.update!(opt, params(model), gs)
        end
        println("Epoch $epoch complete")
    end
end

修复2:修正U-Net的输出尺寸匹配(可选但更严谨)

去掉多余的切片操作,调整ConvTranspose的output_pad参数让输出尺寸精准匹配输入:

function unet(input_channels::Int, output_channels::Int)
    encoder = Chain(
        Conv((3, 3), input_channels => 64, pad=1), relu, MaxPool((2, 2), stride=(2, 2)),
        Conv((3, 3), 64 => 128, pad=1), relu, MaxPool((2, 2), stride=(2, 2)),
        Conv((3, 3), 128 => 256, pad=1), relu, MaxPool((2, 2), stride=(2, 2)),
        Conv((3, 3), 256 => 512, pad=1), relu, MaxPool((2, 2), stride=(2, 2))
    )
    
    decoder = Chain(
        ConvTranspose((3, 3), 512 => 256, stride=2, pad=1, output_pad=1), relu,
        ConvTranspose((3, 3), 256 => 128, stride=2, pad=1, output_pad=1), relu,
        ConvTranspose((3, 3), 128 => 64, stride=2, pad=1, output_pad=1), relu,
        ConvTranspose((3, 3), 64 => output_channels, stride=2, pad=1, output_pad=1)
    )
    
    # 去掉多余的切片操作
    return Chain(encoder, decoder)
end

修复3:明确损失函数的计算维度

显式指定logitcrossentropy的dims参数,确保在空间维度上计算损失并返回标量:

loss(x, y) = Flux.logitcrossentropy(model(x), float(y), dims=(1,2,3))

3. 验证修复效果

修改后重新运行代码,训练循环将正常执行,无MethodError。可通过打印model(test_image)的形状确认输出与输入尺寸一致,同时观察损失是否随训练下降。

内容的提问来源于stack exchange,提问作者Baird

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 07:04:57