如何创建可输入Keras.fit的shape为[128,audio_length,1]的Numpy数组?
解决Numpy数组广播错误:统一音频特征形状适配Keras.fit
这个问题我之前也碰到过!核心原因是你处理的音频时长不一致,导致每个生成的logspec子数组的第二个维度(时间轴)长度不统一,而np.array()要求所有元素的形状必须完全相同才能拼成一个规整的多维数组,这就触发了无法广播的错误。下面给你两种针对性的解决方案:
方案一:统一所有音频特征的长度(推荐用于固定输入形状的模型)
如果你的模型(比如CNN)需要固定的输入形状,最直接的方法是把所有音频的特征截断或补零到同一个目标长度。具体步骤如下:
- 先确定一个目标时间轴长度(比如取所有音频特征中的最大长度,或者根据业务需求设定一个固定值,比如对应2秒音频的帧数);
- 对每个生成的
logspec进行截断/补零处理,确保形状统一为[128, target_length, 1]; - 最后就可以安全地用
np.array()转换为规整数组,输入到Keras.fit()中。
修改后的代码示例:
import numpy as np import librosa def prepare_data(df, config, data_dir, bands=128, target_audio_length=512): log_specgrams_2048 = [] for i, fname in enumerate(df.index): file_path = data_dir + fname data, _ = librosa.core.load(file_path, sr=config.sampling_rate, res_type="kaiser_fast") # 生成梅尔频谱并转对数刻度 melspec = librosa.feature.melspectrogram(data, sr=config.sampling_rate, n_mels=bands) logspec = librosa.core.power_to_db(melspec) # 形状为[128, T],T是当前音频的时间轴长度 # 统一时间轴长度:截断过长的,补零过短的 current_length = logspec.shape[1] if current_length < target_audio_length: # 在时间轴右侧补零(也可以根据需求在左侧补) pad_width = ((0, 0), (0, target_audio_length - current_length)) logspec = np.pad(logspec, pad_width, mode='constant') else: # 截断到目标长度 logspec = logspec[:, :target_audio_length] # 添加通道维度,转为[128, target_audio_length, 1] logspec = logspec[..., np.newaxis] log_specgrams_2048.append(normalize_data(logspec)) # 现在所有子数组形状一致,可以转为Numpy数组 return np.array(log_specgrams_2048)
方案二:使用可变长度输入(适用于RNN类模型)
如果你的模型是RNN(比如LSTM),支持可变长度输入,那么不需要统一长度,但不能直接用np.array()拼接。可以用TensorFlow的tf.data.Dataset来构建数据集,这样就能处理不同长度的样本:
import tensorflow as tf import numpy as np import librosa def prepare_data_as_dataset(df, config, data_dir, bands=128): def process_single_file(fname): # 转换为字符串路径 file_path = data_dir + fname.numpy().decode('utf-8') data, _ = librosa.core.load(file_path, sr=config.sampling_rate, res_type="kaiser_fast") # 生成对数梅尔频谱并添加通道维度 melspec = librosa.feature.melspectrogram(data, sr=config.sampling_rate, n_mels=bands) logspec = librosa.core.power_to_db(melspec) logspec = logspec[..., np.newaxis] return normalize_data(logspec) # 构建文件名数据集 filenames = tf.convert_to_tensor(df.index.tolist(), dtype=tf.string) dataset = tf.data.Dataset.from_tensor_slices(filenames) # 用tf.py_function包装处理函数,兼容Librosa的操作 dataset = dataset.map( lambda x: tf.py_function(process_single_file, [x], tf.float32), num_parallel_calls=tf.data.AUTOTUNE ) # 如果需要添加标签,可取消下面注释 # labels = tf.convert_to_tensor(df['label'].tolist(), dtype=tf.int32) # dataset = tf.data.Dataset.zip((dataset, tf.data.Dataset.from_tensor_slices(labels))) # 批量处理时注意:可变长度样本需要用padded_batch # dataset = dataset.padded_batch(batch_size=32, padded_shapes=([128, None, 1])) return dataset
使用这个数据集时,你可以直接传入model.fit(dataset, ...),TensorFlow会自动处理可变长度的样本(如果用批量的话记得用padded_batch)。
内容的提问来源于stack exchange,提问作者Henry Hargreaves
相关产品推荐
相关产品推荐

