基于字符嵌入的LSTM句子分类:向量转换与模型构建咨询
嘿,我来帮你拆解基于字符嵌入的LSTM句子分类实现——其实核心是把「词-字符」的层级结构转成LSTM能接受的三维张量,再通过层级模型或者池化把字符信息聚合到句子层面。下面一步步给你讲清楚:
和词嵌入直接把句子转成词索引序列不同,字符嵌入需要先拆分到字符级,再处理成三维输入格式(样本数 × 句子最大词数 × 每个词的最大字符数)。结合你的示例来看:
给定:
sentence_list = ['this is a dog', 'the cat and the mouse']char_dict = {'t':1, 'h':2, 'i':3, 's':4, 'a':5, 'd':6, 'o':7, 'g':8}
我们需要先定义两个关键参数:
max_word_len = 4:每个词最多保留4个字符(你的示例里最长词是"this",4个字符)max_sent_len = 5:每个句子最多保留5个词(第二个句子刚好5个词)
具体处理流程:
拆分句子到词-字符层级
把每个句子拆成单词,再把每个单词拆成字符:- 第一句:
["this", "is", "a", "dog"]→[['t','h','i','s'], ['i','s'], ['a'], ['d','o','g']] - 第二句:
["the", "cat", "and", "the", "mouse"]→[['t','h','e'], ['c','a','t'], ['a','n','d'], ['t','h','e'], ['m','o','u','s','e']]
- 第一句:
字符转索引+统一词长度
用char_dict把字符转成索引,对长度不足max_word_len的词补0,超过的截断:第一句的词处理后:
"this" → [1,2,3,4] "is" → [3,4,0,0] "a" → [5,0,0,0] "dog" → [6,7,8,0]再补一个全0的词,凑够
max_sent_len=5:[[1,2,3,4], [3,4,0,0], [5,0,0,0], [6,7,8,0], [0,0,0,0]]第二句的词处理(注意字典里没有的字符用0表示未知):
"the" → [1,2,0,0]('e'不在字典,用0) "cat" → [0,5,1,0]('c'不在字典,用0) "and" → [5,0,6,0]('n'不在字典,用0) "the" → [1,2,0,0] "mouse" → [0,7,0,4](截断到4个字符,'m'/'u'不在字典用0)最终:
[[1,2,0,0], [0,5,1,0], [5,0,6,0], [1,2,0,0], [0,7,0,4]]
最终输入格式
把两个句子的处理结果组合起来,得到形状为(2, 5, 4)的三维张量,这就是LSTM可以接受的输入(格式:(batch_size, timesteps, features),这里timesteps是句子的词数,features是每个词的字符索引序列)。
LSTM要完成句子分类,需要把字符层面的信息逐步聚合到句子层面,常见的有两种实现方式:
1. 双层LSTM(层级LSTM)
这是最常用的方案,通过两层LSTM分别处理字符→词、词→句子的聚合:
- 第一层字符LSTM:输入每个词的字符序列,输出固定长度的词向量
- 第二层句子LSTM:输入句子的词向量序列,输出整个句子的向量,再接分类层
用Keras实现的示例代码:
from tensorflow.keras.models import Model from tensorflow.keras.layers import Input, Embedding, LSTM, Dense, TimeDistributed # 定义字符级LSTM:把字符序列转成词向量 max_word_len = 4 max_sent_len = 5 char_vocab_size = len(char_dict) + 1 # +1是给未知字符留位置 char_input = Input(shape=(max_word_len,)) char_emb = Embedding(input_dim=char_vocab_size, output_dim=20)(char_input) # 字符嵌入维度设为20 char_lstm = LSTM(32)(char_emb) # 每个词的向量维度设为32 char_model = Model(char_input, char_lstm) # 定义句子级LSTM:把词向量序列转成句子向量,做分类 sent_input = Input(shape=(max_sent_len, max_word_len)) # 用TimeDistributed把字符模型应用到每个词的字符序列上 word_vectors = TimeDistributed(char_model)(sent_input) sent_lstm = LSTM(64)(word_vectors) # 句子向量维度设为64 output = Dense(1, activation='sigmoid')(sent_lstm) # 二分类任务 model = Model(sent_input, output) model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
2. 字符嵌入+池化得到词向量
如果你的场景不需要捕捉字符的顺序信息(或者字符序列很短),可以用池化替代字符LSTM,简化模型:
- 对每个词的字符嵌入向量做均值/最大值池化,得到固定长度的词向量
- 再把词向量序列输入LSTM得到句子向量
示例代码:
from tensorflow.keras.layers import GlobalAveragePooling1D # 字符模型:用均值池化得到词向量 char_input = Input(shape=(max_word_len,)) char_emb = Embedding(input_dim=char_vocab_size, output_dim=20)(char_input) word_vector = GlobalAveragePooling1D()(char_emb) # 对字符嵌入做均值池化 char_model = Model(char_input, word_vector) # 句子级模型和之前一致 sent_input = Input(shape=(max_sent_len, max_word_len)) word_vectors = TimeDistributed(char_model)(sent_input) sent_lstm = LSTM(64)(word_vectors) output = Dense(1, activation='sigmoid')(sent_lstm) model = Model(sent_input, output)
- 未知字符处理:建议在
char_dict里加入<UNK>标记(比如索引设为0),所有不在字典里的字符都用这个索引代替 - 填充/截断:必须统一每个词的字符长度和句子的词长度,否则LSTM无法处理变长输入(如果想保留变长,可以配合
Masking层,但统一长度更简单) - 预训练字符嵌入:如果数据量不大,可以用预训练的字符嵌入(比如FastText的字符级嵌入)初始化Embedding层,提升效果
内容的提问来源于stack exchange,提问作者jxn




