中文自然语言处理--基于 Keras 的 LSTM中文文本分类

长短时记忆网络(Long Short Term Memory Network, LSTM),是一种改进之后的循环神经网络,可以解决RNN无法处理长距离的依赖的问题,目前比较流行。LSTM 通过三个“门”结构来控制不同时刻的状态和输出,分别为:遗忘门,输入门,输出门,结构图如下:
在这里插入图片描述
遗忘门(forget gate):它决定了上一时刻的单元状态c_t-1有多少保留到当前时刻c_t

输入门(input gate):它决定了当前时刻网络的输入x_t有多少保存到单元状态c_t

输出门(output gate):控制单元状态c_t有多少输出到 LSTM 的当前输出值h_t

import random
import jieba
import pandas as pd
import numpy as np
# 引入需要的模块
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
from keras.utils import to_categorical
from keras.layers import Dense, Input, Flatten, Dropout
from keras.layers import LSTM, Embedding, GRU
from keras.models import Sequential

# 整个过程包括:语料加载,分词和去停用词,数据预处理
# 使用 LSTM 分类,使用 GRU 分类

# 加载停用词
stopwords = pd.read_csv('./NB_SVM/stopwords.txt', index_col=False, quoting=3, sep="\t", names=['stopword'], encoding='utf-8')
print("stopwords:\n", stopwords.head())
stopwords = stopwords['stopword'].values

# 加载语料,语料是4个已经分好类的 csv 文件
laogong_df = pd.read_csv('./NB_SVM/beilaogongda.csv', encoding='utf-8', sep=',', index_col=[0])
laopo_df = pd.read_csv('./NB_SVM/beilaopoda.csv', encoding='utf-8', sep=',', index_col=[0])
erzi_df = pd.read_csv('./NB_SVM/beierzida.csv', encoding='utf-8', sep=',', index_col=[0])
nver_df = pd.read_csv('./NB_SVM/beinverda.csv', encoding='utf-8', sep=',', index_col=[0])
# 删除语料的nan行
laogong_df.dropna(inplace=True)
laopo_df.dropna(inplace=True)
erzi_df.dropna(inplace=True)
nver_df.dropna(inplace=True)
print("laogong_df:\n", laogong_df.head())
print("laopo_df:\n", laopo_df.head())
print("erzi_df:\n", erzi_df.head())
print("nver_df:\n", nver_df.head())
# 转换
laogong = laogong_df.segment.values.tolist()
laopo = laopo_df.segment.values.tolist()
erzi = erzi_df.segment.values.tolist()
nver = nver_df.segment.values.tolist()

# 定义分词和打标签函数preprocess_text
# 参数content_lines即为上面转换的list
# 参数sentences是定义的空list,用来储存打标签之后的数据
# 参数category 是类型标签
jieba.add_word("报警人")
jieba.add_word("防护装备")
jieba.add_word("防护设备")
jieba.suggest_freq(("人", "称"), tune=True)
def preprocess_text(content_lines, sentences, category):
    for line in content_lines:
        try:
            segs = jieba.lcut(line)
            segs = [v for v in segs if not str(v).isdigit()]  # 去数字
            segs = list(filter(lambda x: x.strip(), segs))  # 去左右空格
            segs = list(filter(lambda x: len(x) > 1, segs))  # 长度为1的字符
            segs = list(filter(lambda x: x not in stopwords, segs))  # 去掉停用词
            sentences.append((" ".join(segs), category))  # 打标签
        except Exception:
            print(line)
            continue

# 调用函数、生成训练数据
sentences = []
preprocess_text(laogong, sentences,0)
preprocess_text(laopo, sentences, 1)
preprocess_text(erzi, sentences, 2)
preprocess_text(nver, sentences, 3)
# 打散数据,生成更可靠的训练集
random.shuffle(sentences)
# 控制台输出前10条数据,观察一下
for sentence in sentences[:10]:
    print(sentence[0], sentence[1])
# 所有特征和对应标签
all_texts = [sentence[0] for sentence in sentences]
all_labels = [sentence[1] for sentence in sentences]

# 使用 LSTM 对数据进行分类
# 预定义变量
MAX_SEQUENCE_LENGTH = 100  # 最大序列长度
EMBEDDING_DIM = 200  # embdding 维度
VALIDATION_SPLIT = 0.16  # 验证集比例
TEST_SPLIT = 0.2  # 测试集比例

# keras的sequence模块文本序列填充
'''
Tokenizer是一个将文本向量化,转换成序列的类。用来文本处理的分词、嵌入
keras.preprocessing.text.Tokenizer(num_words=None,
                                   filters='!"#$%&()*+,-./:;<=>?@[\\]^_`{|}~\t\n',
                                   lower=True,
                                   split=' ',
                                   char_level=False,
                                   oov_token=None,
                                   document_count=0)
参数说明:
    num_words: 默认是None处理所有字词,但是如果设置成一个整数,那么最后返回的是最常见的、出现频率最高的num_words个字词。一共保留 num_words-1 个词。
    filters: 过滤一些特殊字符,默认上文的写法就可以了。
    lower: 是否全部转为小写。
    split: 分词的分隔符字符串,默认为空格。因为英文分词分隔符就是空格。
    char_level: 分字。
    oov_token: if given, it will be added to word_index and used to replace out-of-vocabulary words during text_to_sequence calls
相关的类方法:
方法    参数    返回值
fit_on_texts(texts)    texts:要用以训练的文本列表    -
texts_to_sequences(texts)    texts:待转为序列的文本列表    序列的列表,列表中每个序列对应于一段输入文本
属性:
word_index: 字典,将单词(字符串)映射为它们的排名或者索引。仅在调用fit_on_texts之后设置。
'''
tokenizer = Tokenizer()
tokenizer.fit_on_texts(all_texts)
sequences = tokenizer.texts_to_sequences(all_texts)
word_index = tokenizer.word_index
print('Found %s unique tokens.' % len(word_index))
data = pad_sequences(sequences, maxlen=MAX_SEQUENCE_LENGTH)
labels = to_categorical(np.asarray(all_labels))
print('Shape of data tensor:', data.shape)
print('Shape of label tensor:', labels.shape)
print("data:", data)
print("labels:", labels)

# 数据切分
p1 = int(len(data) * (1 - VALIDATION_SPLIT - TEST_SPLIT))
p2 = int(len(data) * (1 - TEST_SPLIT))
x_train = data[:p1]
y_train = labels[:p1]
x_val = data[p1:p2]
y_val = labels[p1:p2]
x_test = data[p2:]
y_test = labels[p2:]

# LSTM训练模型
model = Sequential()
'''
嵌入层Embedding将正整数(下标)转换为具有固定大小的向量,Embedding层只能作为模型的第一层
输入shape
形如(samples,sequence_length)的2D张量
输出shape
形如(samples, sequence_length, output_dim)的3D张量
参数
    input_dim:大或等于0的整数,字典长度,即输入数据最大下标+1
    output_dim:大于0的整数,代表全连接嵌入的维度
    input_length:当输入序列的长度固定时,该值为其长度。如果要在该层后接Flatten层,然后接Dense层,则必须指定该参数,否则Dense层的输出维度无法自动推断。
'''
model.add(Embedding(len(word_index) + 1, EMBEDDING_DIM, input_length=MAX_SEQUENCE_LENGTH))
# recurrent_dropout是给递归状态 C 设置的Dropout参数
model.add(LSTM(200, dropout=0.2, recurrent_dropout=0.2))
model.add(Dropout(0.2))
model.add(Dense(64, activation='relu'))
model.add(Dense(labels.shape[1], activation='softmax'))
model.summary()
# 模型编译
# 进一步优化损失函数在更新中存在摆动幅度过大的问题,并且进一步加快函数的收敛速度,
# RMSProp算法对权重 W 和偏置 b 的梯度使用了微分平方加权平均数。
# 这种做法有利于消除了摆动幅度大的方向,用来修正摆动幅度,使得各个维度的摆动幅度都较小。另一方面也使得网络函数收敛更快。
model.compile(loss='categorical_crossentropy',
              optimizer='rmsprop',
              metrics=['acc'])
# 训练
model.fit(x_train, y_train, validation_data=(x_val, y_val), epochs=9, batch_size=128)
model.save('./lstm.h5')
# 模型评估
# 属性model.metrics_names将提供显示标签
print("显示标签:", model.metrics_names)
print(model.evaluate(x_test, y_test))

原文:
https://soyoger.blog.csdn.net/article/details/108729405

猜你喜欢

转载自blog.csdn.net/fgg1234567890/article/details/114955458