Keras:如何让Embedding层适配多维时间步输入?
解决Keras Embedding层适配二维字符序列的问题
首先,我完全理解你的场景:每个样本是51个时间步、每个时间步包含2个字符的二维序列,而Keras的Embedding层默认只接受一维整数序列输入。下面我会给出两种保留时序语义的解决方案,同时适配你的原有网络结构:
第一步:先把字符转成整数索引
Embedding层只能处理整数类型的输入(每个字符对应唯一的整数ID),所以首先需要把字符串样本转换成整数数组:
import numpy as np # 构建字符到索引的映射(确保词汇表大小为你设定的74) all_chars = set() # 遍历所有训练样本,收集所有出现过的字符 for sample in your_training_data: for char_pair in sample: all_chars.update(char_pair) char_to_idx = {char: idx for idx, char in enumerate(sorted(all_chars))} assert len(char_to_idx) == 74, "词汇表大小必须和设定的74一致" # 把单个字符串样本转换成整数数组 sample_str = np.array([['0', ' '], [' ', 'l'], ...]) # 你的原始样本 sample_int = np.vectorize(char_to_idx.get)(sample_str) # 此时sample_int的形状为 (51, 2),可直接作为模型输入
方案一:对每个特征列单独做Embedding后拼接
这种方法保留每个时间步两个字符的独立语义,再将它们的向量表示合并,适合需要区分两个字符位置含义的场景:
import keras from keras.layers import Input, Embedding, Concatenate, LSTM, Dense, Dropout num_classes = 5 # 从你的one-hot标签维度可知是5分类任务 vocab_size = 74 emb_dim = 74 timesteps = 51 num_char_per_step = 2 # 每个时间步的字符数量 # 定义输入层,接受(51, 2)形状的样本 input_layer = Input(shape=(timesteps, num_char_per_step)) # 共享一个Embedding层(也可使用两个独立层,按需调整) embedding_layer = Embedding(input_dim=vocab_size, output_dim=emb_dim) # 拆分每个时间步的两个字符特征 char1_seq = input_layer[:, :, 0] # 形状:(None, 51) char2_seq = input_layer[:, :, 1] # 形状:(None, 51) # 分别做Embedding转换 emb1 = embedding_layer(char1_seq) # 形状:(None, 51, 74) emb2 = embedding_layer(char2_seq) # 形状:(None, 51, 74) # 在特征维度拼接两个Embedding结果 combined_emb = Concatenate(axis=-1)([emb1, emb2]) # 形状:(None, 51, 148) # 沿用你原有的网络结构 x = Dropout(0.2)(combined_emb) x = LSTM(64, dropout=0.5, recurrent_dropout=0.5, return_sequences=True)(x) x = LSTM(64, dropout=0.5, recurrent_dropout=0.5)(x) output = Dense(num_classes, activation='sigmoid')(x) # 构建并编译模型 model = keras.Model(inputs=input_layer, outputs=output) model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) model.summary()
方案二:用TimeDistributed包装Embedding层
这种方法更简洁,通过TimeDistributed让Embedding层批量处理每个时间步的多个字符,再合并特征维度:
import keras from keras.layers import Input, Embedding, TimeDistributed, Reshape, LSTM, Dense, Dropout num_classes = 5 vocab_size = 74 emb_dim = 74 timesteps = 51 num_char_per_step = 2 # 定义输入层 input_layer = Input(shape=(timesteps, num_char_per_step)) # 用TimeDistributed让Embedding层处理每个时间步的2个字符 # 输出形状为:(None, 51, 2, 74) embedding = TimeDistributed(Embedding(input_dim=vocab_size, output_dim=emb_dim))(input_layer) # 合并最后两个维度(字符数和Embedding维度),得到(None, 51, 148) reshaped_emb = Reshape((timesteps, num_char_per_step * emb_dim))(embedding) # 后续网络结构与原代码一致 x = Dropout(0.2)(reshaped_emb) x = LSTM(64, dropout=0.5, recurrent_dropout=0.5, return_sequences=True)(x) x = LSTM(64, dropout=0.5, recurrent_dropout=0.5)(x) output = Dense(num_classes, activation='sigmoid')(x) model = keras.Model(inputs=input_layer, outputs=output) model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) model.summary()
补充说明
- 若你对字符位置的语义不敏感,也可以将二维序列直接展平为一维(比如把(51,2)变成(102,)),再调整
Embedding的input_length=102,但这种方法会丢失“每个时间步有两个并行字符”的时序信息,不推荐用于需要保留时序特征的任务。 - 原代码中
LSTM层的input_shape参数可以省略,因为前面的层已经明确了输入形状。
内容的提问来源于stack exchange,提问作者Fardin Abdi
相关产品推荐
相关产品推荐

