技術(shù)交流QQ群:1027579432揍移,歡迎你的加入绎秒!
1.概述
- 為了改善基本RNN的長期依賴問題搔涝,一種方法是引入門控機制來控制信息的累積速度,包括有選擇性地加入新的信息曾雕,并有選擇性遺忘之前累積的信息奴烙。下面主要介紹兩種基于門控的循環(huán)神經(jīng)網(wǎng)絡(luò):長短時記憶網(wǎng)絡(luò)和門控循環(huán)單元網(wǎng)絡(luò)。因為基本的RNN即切诀,每層的隱狀態(tài)都是由前一層的隱狀態(tài)經(jīng)變換和激活函數(shù)得到的,反向傳播求導(dǎo)時搔弄,最終得到的導(dǎo)數(shù)會包含每步梯度的連乘幅虑,會導(dǎo)致梯度爆炸或消失。所以顾犹,基本的RNN很難處理長期依賴問題倒庵,即無法學(xué)習(xí)到序列中蘊含的間隔時間較長的規(guī)律褒墨。
2.長短時記憶網(wǎng)絡(luò)LSTM
-
2.1長短時記憶網(wǎng)絡(luò)是基本的循環(huán)神經(jīng)網(wǎng)絡(luò)的一種變體,可以有效的解決簡單RNN的梯度爆炸或消失問題。LSTM網(wǎng)絡(luò)主要改進在下面兩個方面:
-
1.新的內(nèi)部狀態(tài):LSTM網(wǎng)絡(luò)引入一個新的內(nèi)部狀態(tài),專門進行線性的循環(huán)信息傳遞屏歹,同時輸出信息給隱藏層的外部狀態(tài).
符號說明:寨辩、、分別代表遺忘門锄奢、輸入門失晴、輸出門用來控制信息傳遞的路徑;⊙表示向量元素的點乘拘央;表示上一時刻的記憶單元涂屁;表示通過非線性函數(shù)得到的候選狀態(tài)。
在每個時刻t拆又,LSTM網(wǎng)絡(luò)的內(nèi)部狀態(tài)記錄了到當(dāng)前時刻為止的歷史信息。 -
2.門控機制:LSTM網(wǎng)絡(luò)引入了門控機制栏账,用來控制信息傳遞的路徑帖族,、挡爵、分別代表遺忘門竖般、輸入門、輸出門茶鹃。這里的門概念類似于電路中的邏輯門概念涣雕,1表示開放狀態(tài),允許信息通過闭翩;0表示關(guān)閉狀態(tài)挣郭,阻止信息通過。LSTM網(wǎng)絡(luò)中的門是一個抽象的概念疗韵,借助sigmiod函數(shù)丈屹,使得輸出值在(0,1)之間,表示以一定的比例運行信息通過伶棒。三個門的作用如下:
- 遺忘門控制上一時刻的內(nèi)部狀態(tài)需要遺忘多少信息
- 輸入門控制當(dāng)前時刻的候選狀態(tài)有多少信息需要保存
- 輸出門控制當(dāng)前時刻的內(nèi)部狀態(tài)有多少信息需要輸出給外部狀態(tài)
當(dāng)時旺垒,記憶單元將歷史信息清空,并將候選狀態(tài)向量寫入肤无。但此時記憶單元依然和上一時刻的歷史信息相關(guān)先蒋。當(dāng)時,記憶單元將復(fù)制上一時刻的內(nèi)容宛渐,不寫入新的信息竞漾。三個門的計算公式如下:
其中鳞仙,激活函數(shù)使用sigmoid函數(shù),其輸出區(qū)間是(0,1)笔时,表示當(dāng)前時刻的輸入棍好,表示上一時刻的外部狀態(tài)。
-
1.新的內(nèi)部狀態(tài):LSTM網(wǎng)絡(luò)引入一個新的內(nèi)部狀態(tài),專門進行線性的循環(huán)信息傳遞屏歹,同時輸出信息給隱藏層的外部狀態(tài).
-
2.2 LSTM網(wǎng)絡(luò)的循環(huán)單元結(jié)構(gòu)如下圖所示允耿,計算過程如下:
- a.利用上一時刻的外部狀態(tài)和當(dāng)前時刻的輸入借笙,計算出三個門,已經(jīng)候選狀態(tài)
- b.結(jié)合遺忘門和輸入門來更新記憶單元
- c.結(jié)合輸出門低散,將內(nèi)部狀態(tài)的信息傳遞給外部狀態(tài)
- a.利用上一時刻的外部狀態(tài)和當(dāng)前時刻的輸入借笙,計算出三個門,已經(jīng)候選狀態(tài)
3.門控循環(huán)單元網(wǎng)絡(luò)GRU
- GRU與LSTM的不同之處在于:GRU不引入額外的記憶單元,GRU網(wǎng)絡(luò)引入一個更新門來控制當(dāng)前狀態(tài)需要從歷史狀態(tài)中保留多少信息(不經(jīng)過非線性變換)骡楼,以及需要從候選狀態(tài)中接收多少新的信息熔号。
其中,為更新門
在GRU網(wǎng)絡(luò)中跨嘉,函數(shù)定義為:
上式中的符號說明:表示當(dāng)前時刻的候選狀態(tài)吃嘿,為重置門祠乃,用來控制候選狀態(tài)的計算是否依賴上一時刻的狀態(tài)。
當(dāng)=0時亮瓷,候選狀態(tài)只和當(dāng)前輸入相關(guān)而與歷史狀態(tài)無關(guān)降瞳。當(dāng)=1時嘱支,候選狀態(tài)和當(dāng)前輸入相關(guān)挣饥,也和歷史狀態(tài)相關(guān)除师,此時和簡單的RNN是一樣的。
綜合上述各式扔枫,GRU網(wǎng)絡(luò)的狀態(tài)更新方式為:
- 總結(jié):當(dāng)時汛聚,GRU網(wǎng)絡(luò)退化為簡單的RNN;若時短荐,當(dāng)前狀態(tài)只和當(dāng)前輸入相關(guān)倚舀,和歷史狀態(tài)無關(guān)叹哭。當(dāng)時,當(dāng)前狀態(tài)等于上一時刻狀態(tài)和當(dāng)前輸入無關(guān)痕貌。
3.實戰(zhàn):基于Keras的LSTM和GRU的文本分類
import random
import jieba
import pandas as pd
import numpy as np
stopwords = pd.read_csv(r"E:\DeepLearning\jupyter_code\dataset\corpus\03_project\stopwords.txt", index_col=False, quoting=3, sep="\t", names=["stopword"], encoding="utf-8")
stopwords = stopwords["stopword"].values
# 加載語料
laogong_df = pd.read_csv(r"E:\DeepLearning\jupyter_code\dataset\corpus\03_project\beilaogongda.csv", encoding="utf-8", sep=",")
laopo_df = pd.read_csv(r"E:\DeepLearning\jupyter_code\dataset\corpus\03_project\beilaopoda.csv", encoding="utf-8", sep=",")
erzi_df = pd.read_csv(r"E:\DeepLearning\jupyter_code\dataset\corpus\03_project\beierzida.csv", encoding="utf-8", sep=",")
nver_df = pd.read_csv(r"E:\DeepLearning\jupyter_code\dataset\corpus\03_project\beinverda.csv", encoding="utf-8", sep=",")
# 刪除語料的nan行
laogong_df.dropna(inplace=True)
laopo_df.dropna(inplace=True)
erzi_df.dropna(inplace=True)
nver_df.dropna(inplace=True)
# 轉(zhuǎn)換
laogong = laogong_df.segment.values.tolist()
laopo = laopo_df.segment.values.tolist()
erzi = erzi_df.segment.values.tolist()
nver = nver_df.segment.values.tolist()
# 分詞和去掉停用詞
## 定義分詞和打標(biāo)簽函數(shù)preprocess_text
def preprocess_text(content_lines, sentences, category):
# content_lines是上面轉(zhuǎn)換得到的list
# sentences是空的list风罩,用來存儲打上標(biāo)簽后的數(shù)據(jù)
# category是類型標(biāo)簽
for line in content_lines:
try:
segs = jieba.lcut(line)
segs = [v for v in segs if not str(v).isdigit()] # 除去數(shù)字
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)) # 打標(biāo)簽
except Exception:
print(line)
continue
# 調(diào)用上面函數(shù),生成訓(xùn)練數(shù)據(jù)
sentences = []
preprocess_text(laogong, sentences, 0)
preprocess_text(laopo, sentences, 1)
preprocess_text(erzi, sentences, 2)
preprocess_text(nver, sentences, 3)
# 先打亂數(shù)據(jù)舵稠,使得數(shù)據(jù)分布均勻超升,然后獲取特征和標(biāo)簽列表
random.shuffle(sentences) # 打亂數(shù)據(jù),生成更可靠的訓(xùn)練集
for sentence in sentences[:10]: # 輸出前10條數(shù)據(jù)柱查,觀察一下
print(sentence[0], sentence[1])
# 所有特征和對應(yīng)標(biāo)簽
all_texts = [sentence[0] for sentence in sentences]
all_labels = [sentence[1] for sentence in sentences]
# 使用LSTM對數(shù)據(jù)進行分類
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
# 預(yù)定義變量
MAX_SEQENCE_LENGTH = 100 # 最大序列長度
EMBEDDING_DIM = 200 # 詞嵌入維度
VALIDATION_SPLIT = 0.16 # 驗證集比例
TEST_SPLIT = 0.2 # 測試集比例
# 使用keras的sequence模塊文本序列填充
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_SEQENCE_LENGTH)
labels = to_categorical(np.asarray(all_labels))
print("data shape:", data.shape)
print("labels shape:", labels.shape)
# 數(shù)據(jù)切分
p1 = int(len(data) * (1 - VALIDATION_SPLIT - TEST_SPLIT))
p2 = int(len(data) * (1 - TEST_SPLIT))
# 訓(xùn)練集
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訓(xùn)練模型
model = Sequential()
model.add(Embedding(len(word_index) + 1, EMBEDDING_DIM, input_length=MAX_SEQENCE_LENGTH))
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()
# 模型編譯
model.compile(loss="categorical_crossentropy", optimizer="rmsprop", metrics=["acc"])
print(model.metrics_names)
model.fit(x_train, y_train, validation_data=(x_val, y_val), epochs=10, batch_size=128)
model.save("lstm.h5")
# 模型評估
print(model.evaluate(x_test, y_test))
# 使用GRU模型
model = Sequential()
model.add(Embedding(len(word_index) + 1, EMBEDDING_DIM, input_length=MAX_SEQENCE_LENGTH))
model.add(GRU(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()
model.compile(loss="categorical_crossentropy", optimizer="rmsprop", metrics=["acc"])
print(model.metrics_names)
model.fit(x_train, y_train, validation_data=(x_val, y_val), epochs=10, batch_size=128)
model.save("gru.h5")
print(model.evaluate(x_test, y_test))