如何用LSTM神经网络处理三维分类输入输出实现Keras音符预测?
我来帮你一步步搞定用LSTM预测音符序列的问题!针对你这种每个音符包含三个分类特征的场景,我们可以从数据预处理、数据集格式化、模型搭建与训练这几个核心环节入手,下面是具体的代码和思路解析:
一、先把原始数据解析成结构化格式
首先需要把你给出的文本格式数据,转换成模型能理解的结构化数组。这里写了一个简单的解析函数,你可以直接适配自己的数据集:
import numpy as np from tensorflow.keras.models import Model from tensorflow.keras.layers import Input, LSTM, Dense, Embedding, Concatenate from tensorflow.keras.utils import to_categorical from sklearn.preprocessing import LabelEncoder # 你的原始数据示例(实际使用时可以替换成文件读取逻辑) raw_data = [ "1 [A,2,#] [B,3,b] [C,4,#] [A,2,b] | [B,3,b]", "2 [B,2,#] [B,2,b] [B,5,#] [A,2,#] | [A,3,#]", "3 [C,2,#] [B,5,#] [A,4,#] [A,1,#] | [A,8,b]" ] def parse_raw_data(raw_data): sequences = [] targets = [] for line in raw_data: # 分割输入序列和目标音符 input_part, target_part = line.split("|") # 提取输入的4个音符(跳过开头的序号) input_notes = input_part.strip().split()[1:] # 解析每个音符的三个特征 parsed_input = [] for note_str in input_notes: features = note_str.strip("[]").split(",") parsed_input.append([f.strip() for f in features]) # 解析目标音符 target_note = target_part.strip().strip("[]").split(",") target_note = [f.strip() for f in target_note] sequences.append(parsed_input) targets.append(target_note) return np.array(sequences, dtype=object), np.array(targets, dtype=object) # 得到结构化的输入序列X和目标音符y X, y = parse_raw_data(raw_data)
二、对分类特征进行编码
你的三个特征(音高、八度、升降号)都是分类变量,不能直接输入神经网络,需要先转换成数字编码。这里用LabelEncoder把每个特征的字符串映射成数字,后续可以通过嵌入层或独热编码处理:
# 收集所有特征的取值,用于拟合编码器 all_pitches = [] all_octaves = [] all_accidentals = [] for seq in X: for note in seq: all_pitches.append(note[0]) all_octaves.append(note[1]) all_accidentals.append(note[2]) for target in y: all_pitches.append(target[0]) all_octaves.append(target[1]) all_accidentals.append(target[2]) # 初始化并拟合编码器 le_pitch = LabelEncoder() le_pitch.fit(all_pitches) le_octave = LabelEncoder() le_octave.fit(all_octaves) le_accidental = LabelEncoder() le_accidental.fit(all_accidentals) # 获取每个特征的类别数量 num_pitches = len(le_pitch.classes_) num_octaves = len(le_octave.classes_) num_accidentals = len(le_accidental.classes_)
三、构建LSTM所需的输入输出格式
LSTM要求输入形状为(样本数, 时间步长, 特征维度),这里时间步长是你输入的音符序列长度(4个)。推荐用多输入嵌入层的方式,对每个分类特征单独做嵌入,能更好地捕捉分类特征的语义:
# 转换输入数据:每个特征对应一个序列数组 X_pitch = le_pitch.transform(X.reshape(-1, 1)[:,0]).reshape(X.shape[0], X.shape[1]) X_octave = le_octave.transform(X.reshape(-1, 1)[:,1]).reshape(X.shape[0], X.shape[1]) X_accidental = le_accidental.transform(X.reshape(-1, 1)[:,2]).reshape(X.shape[0], X.shape[1]) # 转换目标数据:每个特征单独作为一个输出(多任务预测) y_pitch = to_categorical(le_pitch.transform(y[:,0]), num_classes=num_pitches) y_octave = to_categorical(le_octave.transform(y[:,1]), num_classes=num_octaves) y_accidental = to_categorical(le_accidental.transform(y[:,2]), num_classes=num_accidentals)
四、搭建并训练LSTM模型
因为要预测下一个音符的三个特征,模型采用多输出头结构,每个输出头对应一个特征的预测,损失函数用交叉熵,优化器选用Adam:
# 定义每个特征的输入层 input_pitch = Input(shape=(4,)) input_octave = Input(shape=(4,)) input_accidental = Input(shape=(4,)) # 嵌入层:将分类特征映射到低维向量空间 emb_pitch = Embedding(input_dim=num_pitches, output_dim=8)(input_pitch) emb_octave = Embedding(input_dim=num_octaves, output_dim=4)(input_octave) emb_accidental = Embedding(input_dim=num_accidentals, output_dim=2)(input_accidental) # 拼接三个嵌入后的特征序列 concat_features = Concatenate(axis=-1)([emb_pitch, emb_octave, emb_accidental]) # LSTM层捕捉序列依赖关系 lstm_layer = LSTM(64, return_sequences=False)(concat_features) # 三个输出头,分别预测三个特征 output_pitch = Dense(num_pitches, activation='softmax', name='pitch')(lstm_layer) output_octave = Dense(num_octaves, activation='softmax', name='octave')(lstm_layer) output_accidental = Dense(num_accidentals, activation='softmax', name='accidental')(lstm_layer) # 构建模型 model = Model(inputs=[input_pitch, input_octave, input_accidental], outputs=[output_pitch, output_octave, output_accidental]) # 编译模型 model.compile(optimizer='adam', loss={'pitch': 'categorical_crossentropy', 'octave': 'categorical_crossentropy', 'accidental': 'categorical_crossentropy'}, metrics={'pitch': 'accuracy', 'octave': 'accuracy', 'accidental': 'accuracy'}) # 查看模型结构 model.summary() # 训练模型 history = model.fit([X_pitch, X_octave, X_accidental], [y_pitch, y_octave, y_accidental], epochs=50, batch_size=1)
五、用训练好的模型做预测
训练完成后,输入一个4音符的序列,解码模型的预测结果即可得到下一个音符:
# 取第一个样本作为测试输入 test_seq = X[0] # 对测试序列进行编码 test_pitch = le_pitch.transform(test_seq[:,0]).reshape(1, 4) test_octave = le_octave.transform(test_seq[:,1]).reshape(1, 4) test_accidental = le_accidental.transform(test_seq[:,2]).reshape(1, 4) # 执行预测 preds = model.predict([test_pitch, test_octave, test_accidental]) # 解码预测结果 pred_pitch = le_pitch.inverse_transform(np.argmax(preds[0], axis=1))[0] pred_octave = le_octave.inverse_transform(np.argmax(preds[1], axis=1))[0] pred_accidental = le_accidental.inverse_transform(np.argmax(preds[2], axis=1))[0] print(f"预测的下一个音符:[{pred_pitch}, {pred_octave}, {pred_accidental}]")
一些优化建议
- 如果你的数据集很大,建议改成从文件(如CSV、TXT)批量读取数据,避免内存溢出;
- 可以调整LSTM的单元数量(比如64→128)、堆叠多层LSTM(设置
return_sequences=True)来提升模型能力; - 如果分类特征的类别较少,也可以用独热编码替代嵌入层,示例代码如下:
# 独热编码方式的输入转换 def encode_note(note): pitch_onehot = to_categorical(le_pitch.transform([note[0]]), num_pitches)[0] octave_onehot = to_categorical(le_octave.transform([note[1]]), num_octaves)[0] accidental_onehot = to_categorical(le_accidental.transform([note[2]]), num_accidentals)[0] return np.concatenate([pitch_onehot, octave_onehot, accidental_onehot]) X_onehot = np.array([[encode_note(note) for note in seq] for seq in X]) # 独热编码版本的模型 input_layer = Input(shape=(4, X_onehot.shape[2])) lstm = LSTM(64)(input_layer) output_pitch = Dense(num_pitches, activation='softmax')(lstm) output_octave = Dense(num_octaves, activation='softmax')(lstm) output_accidental = Dense(num_accidentals, activation='softmax')(lstm) model = Model(inputs=input_layer, outputs=[output_pitch, output_octave, output_accidental])
内容的提问来源于stack exchange,提问作者Someone Needing Help
相关产品推荐
相关产品推荐

