赞
踩
前言:
毕业前的项目,最近终于有时间整理个博客出来。使用的keras+gensim完成,也参考了互联网很多相关资料。最终效果只有88%左右,不过优化空间很大,只用作学习demo
数据集使用的是谭松波酒店评论数据集 停用词我自己整理了一个停用词词典 分享给大家
链接:https://pan.baidu.com/s/1ZkMGAUH7VSxJALWBs41iKQ
提取码:2c1e
这一步主要是对评论文本做清洗,在这里只做简单的去停用词。
首先写一个去停用词的方法
import jieba
f = open('./stop_words.txt', encoding='utf-8') # 加载停用词
stopwords = [i.replace("\n", "") for i in f.readlines()] # 停用词表
def del_stop_words(text):
"""
删除每个文本中的停用词
:param text:
:return:
"""
word_ls = jieba.lcut(text)
word_ls = [i for i in word_ls if i not in stopwords]
return word_ls
然后读取正面评论与负面评论的语料 并进行清洗
with open("./test_data/neg.txt", "r", encoding='UTF-8') as e: # 加载负面语料
neg_data1 = e.readlines()
with open("./test_data/pos.txt", "r", encoding='UTF-8') as s: # 加载正面语料
pos_data1 = s.readlines()
neg_data = sorted(set(neg_data1), key=neg_data1.index) #列表去重 保持原来的顺序
pos_data = sorted(set(pos_data1), key=pos_data1.index)
neg_data = [del_stop_words(data.replace("\n", "")) for data in neg_data] # 处理负面语料
pos_data = [del_stop_words(data.replace("\n", "")) for data in pos_data]
all_sentences= neg_data + pos_data # 全部语料 用于训练word2vec
对于文本的向量化其实有很多方式,包括独热(one-hot),词袋模型(bag of words),逆文本特征频率(tf-idf)和word2vec等。
本项目我们使用word2vec(据说效果很好)进行词向量的提取,word2vec是使用深度学习的方式将词映射为一个多维向量。
首先顶级模型结构 并进行训练及保存
from gensim.models.word2vec import Word2Vec
from gensim.corpora.dictionary import Dictionary
import pockle
import logging
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO) # 将日志输出到控制台
model = Word2Vec(all_sentences, # 上文处理过的全部语料
size=150, # 词向量维度 默认100维
min_count=1, # 词频阈值 词出现的频率 小于这个频率的词 将不予保存
window=5 # 窗口大小 表示当前词与预测词在一个句子中的最大距离是多少
)
model.save('./models/Word2vec_v1') # 保存模型
然后加载模型 提取出词的索引与向量
def create_dictionaries(model):
"""
创建词语字典,并返回word2vec模型中词语的索引,词向量
"""
gensim_dict = Dictionary() # 创建词语词典
gensim_dict.doc2bow(model.wv.vocab.keys(), allow_update=True)
w2indx = {v: k + 1 for k, v in gensim_dict.items()} # 词语的索引,从1开始编号
w2vec = {word: model[word] for word in w2indx.keys()} # 词语的词向量
return w2indx, w2vec
model = Word2Vec.load('./models/Word2vec_v1') # 加载模型
index_dict, word_vectors= create_dictionaries(model) # 索引字典、词向量字典
使用 pickle 存储序列化数据 pickle是一个非常方便的库 可以将py的字典 列表等等程序运行过程中的对象存储为实体数据
output = open(pkl_name + ".pkl", 'wb')
pickle.dump(index_dict, output) # 索引字典
pickle.dump(word_vectors, output) # 词向量字典
output.close()
接下来 使用keas库搭建LSTM模型 来进行训练
首先我们定义几个必要参数
# 参数设置
vocab_dim = 150 # 向量维度
maxlen = 150 # 文本保留的最大长度
batch_size = 100 # 训练过程中 每次传入模型的特征数量
n_epoch = 4 # 迭代次数
加载词向量数据 并填充词向量矩阵
f = open("./model/评价语料索引及词向量2.pkl", 'rb') # 预先训练好的
index_dict = pickle.load(f) # 索引字典,{单词: 索引数字}
word_vectors = pickle.load(f) # 词向量, {单词: 词向量(100维长的数组)}
n_symbols = len(index_dict) + 1 # 索引数字的个数,因为有的词语索引为0,所以+1
embedding_weights = np.zeros((n_symbols, 150)) # 创建一个n_symbols * 100的0矩阵
for w, index in index_dict.items(): # 从索引为1的词语开始,用词向量填充矩阵
embedding_weights[index, :] = word_vectors[w] # 词向量矩阵,第一行是0向量(没有索引为0的词语,未被填充)
接下来将所有的评论数据映射成为数字
因为之前通过加载词向量 已经拥有了一个索引字典
只要将出现在的索引字典中的单词转换为其索引数字 未出现的转换为0即可
def text_to_index_array(p_new_dic, p_sen): """ 文本或列表转换为索引数字 :param p_new_dic: :param p_sen: :return: """ if type(p_sen) == list: new_sentences = [] for sen in p_sen: new_sen = [] for word in sen: try: new_sen.append(p_new_dic[word]) # 单词转索引数字 except: new_sen.append(0) # 索引字典里没有的词转为数字0 new_sentences.append(new_sen) return np.array(new_sentences) # 转numpy数组 else: new_sentences = [] sentences = [] p_sen = p_sen.split(" ") for word in p_sen: try: sentences.append(p_new_dic[word]) # 单词转索引数字 except: sentences.append(0) # 索引字典里没有的词转为数字0 new_sentences.append(sentences) return new_sentences
加载特征与标签 将特征全部映射成数字 并且分割验证集和测试集
with open("./原始语料/neg.txt", "r", encoding='UTF-8') as f: neg_data1 = f.readlines() with open("./原始语料/pos.txt", "r", encoding='UTF-8') as g: pos_data1 = g.readlines() neg_data = sorted(set(neg_data1), key=neg_data1.index) #列表去重 保持原来的顺序 pos_data = sorted(set(pos_data1), key=pos_data1.index) neg_data = [process_txt(data) for data in neg_data] pos_data = [process_txt(data) for data in pos_data] data = neg_data + pos_data # 读取语料类别标签 label_list = ([0] * len(neg_data) + [1] * len(pos_data)) # 划分训练集和测试集,此时都是list列表 X_train_l, X_test_l, y_train_l, y_test_l = train_test_split(data, label_list, test_size=0.2) # 转为数字索引形式 # token = Tokenizer(num_words=3000) #字典数量 # token.fit_on_texts(train_text) X_train = text_to_index_array(index_dict, X_train_l) X_test = text_to_index_array(index_dict, X_test_l) y_train = np.array(y_train_l) # 转numpy数组 y_test = np.array(y_test_l) print("训练集shape: ", X_train.shape) print("测试集shape: ", X_test.shape)
因为模型输入的每一个特征长度需要相同,所以我们需要定义一个最大的长度max_len。
当特征小于max_len时,根据max_len填充其余位数为0。
当特征大于max_len,则进行截断。
在本项目中,我定义的max_len为150,是一个平均长度。有时候为了保证不丢失信息,可以打印出所有特征中最大的长度,并将其设置为max_len
X_train = sequence.pad_sequences(X_train, maxlen=maxlen)
X_test = sequence.pad_sequences(X_test, maxlen=maxlen)
定义模型 训练模型 验证模型
ef train_lstm(p_n_symbols, p_embedding_weights, p_X_train, p_y_train, p_X_test, p_y_test, X_test_l): print('创建模型...') model = Sequential() model.add(Embedding(output_dim=vocab_dim, # 输出向量维度 input_dim=p_n_symbols, # 输入向量维度 mask_zero=True, # 使我们填补的0值在后续训练中不产生影响(屏蔽0值) weights=[p_embedding_weights], # 对数据加权 input_length=maxlen )) # 每个特征的长度 model.add(LSTM(output_dim=100, activation='sigmoid', inner_activation='hard_sigmoid')) model.add(Dropout(0.5)) # 每次迭代丢弃50神经元 防止过拟合 model.add(Dense(units=512, activation='relu')) model.add(Dropout(0.5)) model.add(Dense(units=1, # 输出层1个神经元 1代表正面 0代表负面 activation='sigmoid')) model.summary() print('编译模型...') model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) print("训练...") train_history = model.fit(p_X_train, p_y_train, batch_size=batch_size, nb_epoch=n_epoch, validation_data=(p_X_test, p_y_test)) print("评估...") score, acc = model.evaluate(p_X_test, p_y_test, batch_size=batch_size) label = model.predict(p_X_test) print('Test score:', score) print('Test accuracy:', acc) for (a, b, c) in zip(p_y_test, X_test_l, label): print("原文为:"+ "".join(b)) print("预测倾向为", a) print("真实倾向为", c) show_train_history(train_history, 'acc', 'val_acc') # 训练集准确率与验证集准确率 折线图 show_train_history(train_history, 'loss', 'val_loss') # 训练集误差率与验证集误差率 折线图 """保存模型""" model.save('./model/emotion_model_LSTM.h5') print("模型保存成功")
可以通过show_train_history函数打印的训练集曲线 来判断模型是否过拟合。
方便确定迭代次数 进行调参 函数如下
def show_train_history(train_history,train, velidation):
"""
可视化训练过程 对比
:param train_history:
:param train:
:param velidation:
:return:
"""
plt.plot(train_history.history[train])
plt.plot(train_history.history[velidation])
plt.title("Train History") #标题
plt.xlabel('Epoch') #x轴标题
plt.ylabel(train) #y轴标题
plt.legend(['train', 'test'], loc='upper left') #图例 左上角
plt.show()
项目github:https://github.com/sph116/lstm_emotion
没仔细检查,可能会有些小问题,望海涵,欢迎交流。
后续优化:
1.增大训练word2vec 语料数量
2.数据清洗不止进行简单的去停用词
3.增加模型结构及复杂度
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。