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

U-Net音频分类模型Concatenate层形状不匹配问题求助

U-Net音频分类模型维度不匹配问题解决

问题背景

尝试用U-Net模型做音频分类,预处理后数据集形状如下:

  • X_train shape: (3806, 2809)
  • X_test shape: (952, 2809)
  • y_train shape: (3806, 1)
  • y_test shape: (952, 1)

已尝试padding和cropping操作,但始终存在维度不匹配问题,以下是U-Net架构代码及错误信息:

U-Net代码

import tensorflow as tf
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Conv2D, MaxPooling2D, UpSampling2D, Concatenate, Dropout, ZeroPadding2D

def unet(input_size=(53, 53, 1), num_classes=2):
    inputs = Input(input_size)
    
    # Encoder
    conv1 = Conv2D(64, 3, activation='relu', padding='same', kernel_initializer='he_normal')(inputs)
    conv1 = Conv2D(64, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv1)
    pool1 = MaxPooling2D(pool_size=(2, 2))(conv1)
    
    conv2 = Conv2D(128, 3, activation='relu', padding='same', kernel_initializer='he_normal')(pool1)
    conv2 = Conv2D(128, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv2)
    pool2 = MaxPooling2D(pool_size=(2, 2))(conv2)
    
    conv3 = Conv2D(256, 3, activation='relu', padding='same', kernel_initializer='he_normal')(pool2)
    conv3 = Conv2D(256, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv3)
    pool3 = MaxPooling2D(pool_size=(2, 2))(conv3)
    
    conv4 = Conv2D(512, 3, activation='relu', padding='same', kernel_initializer='he_normal')(pool3)
    conv4 = Conv2D(512, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv4)
    drop4 = Dropout(0.5)(conv4)
    pool4 = MaxPooling2D(pool_size=(2, 2))(drop4)

    conv5 = Conv2D(1024, 3, activation='relu', padding='same', kernel_initializer='he_normal')(pool4)
    conv5 = Conv2D(1024, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv5)
    drop5 = Dropout(0.5)(conv5)

    # Decoder
    up6 = UpSampling2D(size=(2, 2))(drop5)
    conv6 = Conv2D(512, 2, activation='relu', padding='same', kernel_initializer='he_normal')(up6)
    
    # Padding
    pad_top = (conv4.shape[1] - conv6.shape[1]) // 2
    pad_bottom = conv4.shape[1] - conv6.shape[1] - pad_top
    pad_left = (conv4.shape[2] - conv6.shape[2]) // 2
    pad_right = conv4.shape[2] - conv6.shape[2] - pad_left
    padded_conv4 = ZeroPadding2D(padding=((pad_top, pad_bottom), (pad_left, pad_right)))(conv4)
    
    merge6 = Concatenate(axis=3)([padded_conv4, conv6])
    conv6 = Conv2D(512, 3, activation='relu', padding='same', kernel_initializer='he_normal')(merge6)
    conv6 = Conv2D(512, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv6)

    up7 = UpSampling2D(size=(2, 2))(conv6)
    conv7 = Conv2D(256, 2, activation='relu', padding='same', kernel_initializer='he_normal')(up7)
    
    # Padding
    pad_top = (conv3.shape[1] - conv7.shape[1]) // 2
    pad_bottom = conv3.shape[1] - conv7.shape[1] - pad_top
    pad_left = (conv3.shape[2] - conv7.shape[2]) // 2
    pad_right = conv3.shape[2] - conv7.shape[2] - pad_left
    padded_conv3 = ZeroPadding2D(padding=((pad_top, pad_bottom), (pad_left, pad_right)))(conv3)
    
    merge7 = Concatenate(axis=3)([padded_conv3, conv7])
    conv7 = Conv2D(256, 3, activation='relu', padding='same', kernel_initializer='he_normal')(merge7)
    conv7 = Conv2D(256, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv7)

    up8 = UpSampling2D(size=(2, 2))(conv7)
    conv8 = Conv2D(128, 2, activation='relu', padding='same', kernel_initializer='he_normal')(up8)
    
    # Padding
    pad_top = (conv2.shape[1] - conv8.shape[1]) // 2
    pad_bottom = conv2.shape[1] - conv8.shape[1] - pad_top
    pad_left = (conv2.shape[2] - conv8.shape[2]) // 2
    pad_right = conv2.shape[2] - conv8.shape[2] - pad_left
    padded_conv2 = ZeroPadding2D(padding=((pad_top, pad_bottom), (pad_left, pad_right)))(conv2)
    
    merge8 = Concatenate(axis=3)([padded_conv2, conv8])
    conv8 = Conv2D(128, 3, activation='relu', padding='same', kernel_initializer='he_normal')(merge8)
    conv8 = Conv2D(128, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv8)

    up9 = UpSampling2D(size=(2, 2))(conv8)
    conv9 = Conv2D(64, 2, activation='relu', padding='same', kernel_initializer='he_normal')(up9)
    
    # Padding
    pad_top = (conv1.shape[1] - conv9.shape[1]) // 2
    pad_bottom = conv1.shape[1] - conv9.shape[1] - pad_top
    pad_left = (conv1.shape[2] - conv9.shape[2]) // 2
    pad_right = conv1.shape[2] - conv9.shape[2] - pad_left
    padded_conv1 = ZeroPadding2D(padding=((pad_top, pad_bottom), (pad_left, pad_right)))(conv1)
    
    merge9 = Concatenate(axis=3)([padded_conv1, conv9])
    conv9 = Conv2D(64, 3, activation='relu', padding='same', kernel_initializer='he_normal')(merge9)
    conv9 = Conv2D(64, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv9)
    
    conv10 = Conv2D(num_classes, 1, activation='sigmoid')(conv9)  # Use 'sigmoid' for binary classification

    model = Model(inputs=inputs, outputs=conv10)
    
    model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
    
    return model

错误信息

ValueError                                Traceback (most recent call last)
Cell In[73], line 1
----> 1 model = unet(input_size=(53, 53, 1))

Cell In[72], line 55, in unet(input_size, num_classes)
     52 pad_right = conv3.shape[2] - conv7.shape[2] - pad_left
     53 padded_conv3 = ZeroPadding2D(padding=((pad_top, pad_bottom), (pad_left, pad_right)))(conv3)
---> 55 merge7 = Concatenate(axis=3)([padded_conv3, conv7])
     56 conv7 = Conv2D(256, 3, activation='relu', padding='same', kernel_initializer='he_normal')(merge7)
     57 conv7 = Conv2D(256, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv7)

File ~\anaconda3\lib\site-packages\keras\src\utils\traceback_utils.py:70, in filter_traceback.<locals>.error_handler(*args, **kwargs)
     67     filtered_tb = _process_traceback_frames(e.__traceback__)
     68     # To get the full stack trace, call:
     69     # `tf.debugging.disable_traceback_filtering()`
---> 70     raise e.with_traceback(filtered_tb) from None
     71 finally:
     72     del filtered_tb

File ~\anaconda3\lib\site-packages\keras\src\layers\merging\concatenate.py:131, in Concatenate.build(self, input_shape)
    125 unique_dims = set(
    126     shape[axis]
    127     for shape in shape_set
    128     if shape[axis] is not None
    129 )
    130 if len(unique_dims) > 1:
---> 131     raise ValueError(err_msg)

ValueError: A `Concatenate` layer requires inputs with matching shapes except for the concatenation axis. Received: input_shape=[(None, 14, 14, 256), (None, 12, 12, 256)]

错误原因

错误核心是padding对象搞反了:当前代码给encoder输出的特征图(比如conv3)加padding,导致它的尺寸比decoder上采样后的特征图(conv7)更大,无法拼接。正确逻辑应该是给decoder的特征图加padding,让它和encoder对应层的尺寸一致,或者对encoder特征图做cropping。

另外,输入尺寸53x53不是2的幂次,经过多次下采样/上采样后,尺寸无法完美匹配,加剧了维度问题。

解决方案

方案1:修正padding对象

将所有padding操作从encoder特征图转移到decoder特征图,确保拼接时两者尺寸一致。修改后的Decoder部分代码如下:

# Decoder
up6 = UpSampling2D(size=(2, 2))(drop5)
conv6 = Conv2D(512, 2, activation='relu', padding='same', kernel_initializer='he_normal')(up6)

# 修正:给decoder的conv6加padding,匹配conv4尺寸
pad_top = (conv4.shape[1] - conv6.shape[1]) // 2
pad_bottom = conv4.shape[1] - conv6.shape[1] - pad_top
pad_left = (conv4.shape[2] - conv6.shape[2]) // 2
pad_right = conv4.shape[2] - conv6.shape[2] - pad_left
padded_conv6 = ZeroPadding2D(padding=((pad_top, pad_bottom), (pad_left, pad_right)))(conv6)

merge6 = Concatenate(axis=3)([conv4, padded_conv6])
conv6 = Conv2D(512, 3, activation='relu', padding='same', kernel_initializer='he_normal')(merge6)
conv6 = Conv2D(512, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv6)

up7 = UpSampling2D(size=(2, 2))(conv6)
conv7 = Conv2D(256, 2, activation='relu', padding='same', kernel_initializer='he_normal')(up7)

# 修正:给decoder的conv7加padding,匹配conv3尺寸
pad_top = (conv3.shape[1] - conv7.shape[1]) // 2
pad_bottom = conv3.shape[1] - conv7.shape[1] - pad_top
pad_left = (conv3.shape[2] - conv7.shape[2]) // 2
pad_right = conv3.shape[2] - conv7.shape[2] - pad_left
padded_conv7 = ZeroPadding2D(padding=((pad_top, pad_bottom), (pad_left, pad_right)))(conv7)

merge7 = Concatenate(axis=3)([conv3, padded_conv7])
conv7 = Conv2D(256, 3, activation='relu', padding='same', kernel_initializer='he_normal')(merge7)
conv7 = Conv2D(256, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv7)

# 后续merge8、merge9做同样修改,给conv8、conv9加padding

方案2:改用Conv2DTranspose替代UpSampling2D

Conv2DTranspose可以直接控制输出尺寸,避免手动计算padding的麻烦。示例如下:

# 替换Decoder中的UpSampling2D+Conv2D为Conv2DTranspose
up6 = Conv2DTranspose(512, (2,2), strides=(2,2), padding='same', kernel_initializer='he_normal')(drop5)
# 直接和conv4拼接,无需额外padding
merge6 = Concatenate(axis=3)([conv4, up6])
conv6 = Conv2D(512, 3, activation='relu', padding='same', kernel_initializer='he_normal')(merge6)
conv6 = Conv2D(512, 3, activation='relu', padding='same', kernel_initializer='he_normal')(conv6)

方案3:调整输入尺寸为2的幂次

将输入尺寸改为64x64(2的6次方),这样每次下采样和上采样的尺寸都是整数倍,无需额外padding/cropping:

def unet(input_size=(64, 64, 1), num_classes=2):
    # 后续代码不变

同时需要将预处理后的音频数据(2809维度)reshape为64x64,可通过补零或裁剪实现:

# 补零示例
X_train = np.pad(X_train, ((0,0), (0, 64*64 - 2809)), mode='constant')
X_train = X_train.reshape(-1, 64, 64, 1)

额外提示

音频分类任务中,通常先将一维音频转换为二维频谱图(比如Mel频谱图)再输入CNN,直接用一维数据reshape为二维可能丢失时序信息,建议先做频谱转换。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 21:57:31