“你什麼意思”之基於RNN的語義槽填充(Pytorch實現)

来源:https://www.cnblogs.com/llhthinker/archive/2018/05/01/8978029.html
-Advertisement-
Play Games

1. 概況 1.1 任務 口語理解(Spoken Language Understanding, SLU) 作為語音識別與自然語言處理之間的一個新興領域,其目的是為了讓電腦從用戶的講話中理解他們的意圖。SLU是口語對話系統( "Spoken Dialog Systems" )的一個非常關鍵的環節。 ...


1. 概況

1.1 任務

口語理解(Spoken Language Understanding, SLU)作為語音識別與自然語言處理之間的一個新興領域,其目的是為了讓電腦從用戶的講話中理解他們的意圖。SLU是口語對話系統(Spoken Dialog Systems)的一個非常關鍵的環節。下圖展示了口語對話系統的主要流程。

img

SLU主要通過如下三個子任務來理解用戶的語言:

  1. 領域識別(Domain Detection)
  2. 用戶意圖檢測(User Intent Determination)
  3. 語義槽填充(Semantic Slot Filling)

例如,用戶輸入“播放周傑倫的稻香”,首先通過領域識別模塊識別為"music"領域,再通過用戶意圖檢測模塊識別出用戶意圖為"play_music"(而不是"find_lyrics" ),最後通過槽填充對將每個詞填充到對應的槽中:"播放[O] / 周傑倫[B-singer] / 的[O] / 稻香[B-song]"。

從上述例子可以看出,通常把領域識別和用戶意圖檢測當做文本分類問題,而把槽填充當做序列標註(Sequence Tagging)問題,也就是把連續序列中每個詞賦予相應的語義類別標簽。本次實驗的任務就是基於ATIS 數據集進行語義槽填充。(完整代碼地址https://github.com/llhthinker/slot-filling)

1.2 數據集

本次實驗基於ATIS(Airline Travel Information Systems )數據集。顧名思義,ATIS數據集的領域為"Airline Travel"。ATIS數據集採取流行的"in/out/begin(IOB)標註法": "I-xxx"表示該詞屬於槽xxx,但不是槽xxx中第一個詞;"O"表示該詞不屬於任何語義槽;"B-xxx"表示該詞屬於槽xxx,並且位於槽xxx的首位。部分ATIS訓練數據集如下:

what    O
is  O
the O
arrival B-flight_time
time    I-flight_time
in  O
san B-fromloc.city_name
francisco   I-fromloc.city_name
for O
the O
DIGITDIGITDIGIT B-depart_time.time
am  I-depart_time.time
flight  O
leaving O
washington  B-fromloc.city_name

ATIS數據集一共有83種語義槽,因此序列標註的標簽類別一共有\(83+83+1=167\)個。ATIS數據集分為訓練集和測試集,數據規模如下表:

訓練集 測試集
句子總數 4978個 893個
詞語總數 56590個 9198個
句子平均詞數 11.4個 10.3個

2. 模型

上文中提到,通常把槽填充當做序列標註問題。很多機器學習演算法都能夠解決序列標註問題,包括HMM/CFG,hidden vector state(HVS)等生成式模型,以及CRF, SVM等判別式模型。本次實驗主要參考論文《Using Recurrent Neural Networks for Slot Filling in Spoken Language Understanding 》 ,基於RNN來實現語義槽填充。

RNN可以分為簡單RNN(Simple RNN)和門控機制RNN(Gated RNN),前者的RNN單元完全接收上個時刻的輸入;後者基於門控機制,通過學習到的參數自行決定上個時刻的輸入量和當前狀態的保留量。下麵將介紹Elman-RNN, Jordan-RNN, Hybrid-RNN(Elman和Jordan結合)這三種簡單RNN,以及經典的門控機制RNN:LSTM。

2.1 Elman-RNN

Elman-RNN將當前時刻的輸入\(x_t\)和上個時刻的隱狀態輸出\(h_{(t-1)}\)作為輸入,具體如下:

\[\begin{split}\begin{array}{ll}h_t = \sigma(W_{ih} x_t + b_{ih} + W_{hh} h_{(t-1)} + b_{hh}) \end{array}\end{split}\]

img

需要說明的是,Pytorch預設的RNN即為Elman-RNN,但是它只支持\(\tanh\)和ReLU兩種激活函數。本次實驗按照論文設置,激活函數均採取sigmoid函數,使用Pytorch具體實現如下:

class ElmanRNNCell(nn.Module):
    def __init__(self, input_size, hidden_size):
        super(ElmanRNNCell, self).__init__()
        self.hidden_size = hidden_size
        self.i2h_fc1 = nn.Linear(input_size, hidden_size)
        self.i2h_fc2 = nn.Linear(hidden_size, hidden_size)
        self.h2o_fc = nn.Linear(hidden_size, hidden_size)

    def forward(self, input, hidden):
        hidden = F.sigmoid(self.i2h_fc1(input) + self.i2h_fc2(hidden))
        output = F.sigmoid(self.h2o_fc(hidden))
        return output, hidden

2.2 Jordan-RNN

Jordan-RNN將當前時刻的輸入\(x_t\)和上個時刻的輸出層輸出\(y_{(t-1)}\)作為輸入,具體如下:

\[\begin{split}\begin{array}{ll}h_t = \sigma(W_{ih} x_t + b_{ih} + W_{yh} y_{(t-1)} + b_{yh}) \end{array}\end{split}\]

img

使用Pytorch具體實現如下,其中\(y_0\)初始化為可訓練的參數:

class JordanRNNCell(nn.Module):
    def __init__(self, input_size, hidden_size):
        super(JordanRNNCell, self).__init__()
        self.hidden_size = hidden_size
        self.i2h_fc1 = nn.Linear(input_size, hidden_size) 
        self.i2h_fc2 = nn.Linear(hidden_size, hidden_size)
        self.h2o_fc = nn.Linear(hidden_size, hidden_size)
        self.y_0 = nn.Parameter(nn.init.xavier_uniform(torch.Tensor(1, hidden_size)), requires_grad=True)

    def forward(self, input, hidden=None):
        if hidden is None:
            hidden = self.y_0
        hidden = F.sigmoid(self.i2h_fc1(input) + self.i2h_fc2(hidden))
        output = F.sigmoid(self.h2o_fc(hidden))
        return output, output

2.4 Hybrid-RNN

Hybrid-RNN將當前時刻的輸入\(x_t​\),上個時刻的隱狀態\(h_{(t-1)}​\) 以及上個時刻輸出層輸出\(y_{(t-1)}​\)作為輸入,具體如下:

\[\begin{split}\begin{array}{ll}h_t = \sigma(W_{ih} x_t + b_{ih} + W_{hh} h_{(t-1)} + b_{hh} + W_{yh} y_{(t-1)} + b_{yh}) \end{array}\end{split}\] ,並且\(y_0\)初始化為可訓練的參數。使用Pytorch具體實現如下:

class HybridRNNCell(nn.Module):
    def __init__(self, input_size, hidden_size):
        super(HybridRNNCell, self).__init__()
        self.hidden_size = hidden_size
        self.i2h_fc1 = nn.Linear(input_size, hidden_size)
        self.i2h_fc2 = nn.Linear(hidden_size, hidden_size)
        self.i2h_fc3 = nn.Linear(hidden_size, hidden_size)
        self.h2o_fc = nn.Linear(hidden_size, hidden_size)
        self.y_0 = nn.Parameter(nn.init.xavier_uniform(torch.Tensor(1, hidden_size)), requires_grad=True)

    def forward(self, input, hidden, output=None):
        if output is None:
            output = self.y_0    
        hidden = F.sigmoid(self.i2h_fc1(input)+self.i2h_fc2(hidden)+self.i2h_fc3(output))
        output = F.sigmoid(self.h2o_fc(hidden)) 
        return output, hidden

2.5 LSTM

LSTM引入了記憶單元\(c_t\)和3種控制門,包括輸入門(input gate)\(i_t\)遺忘門(forget gate)\(f_t\)輸出門(output gate)\(o_t\), 首先,輸入層接受當前時刻輸入\(x_t\)和上個時刻隱狀態輸出\(h_{(t-1)}\),通過\(\tanh\)激活函數得到記憶單元的輸入\(g_t\); 然後遺忘門\(f_t\)決定上個時刻記憶單元\(c_{(t-1)}\)的保留比例,輸入門\(i_t\)決定當前時刻記憶單元的輸入\(g_t\)的保留比例,兩者相加得到當前的記憶單元\(c_t\); 最後記憶單元\(c_t\)通過\(\tanh\)激活函數得到的值在輸出門\(o_t\)的控制下得到最終的當前時刻隱狀態\(h_t\), 具體如下:

\[\begin{split}\begin{array}{ll}i_t = \sigma(W_{ii} x_t + b_{ii} + W_{hi} h_{(t-1)} + b_{hi}) \\f_t = \sigma(W_{if} x_t + b_{if} + W_{hf} h_{(t-1)} + b_{hf}) \\g_t = \tanh(W_{ig} x_t + b_{ig} + W_{hg} h_{(t-1)} + b_{hg}) \\o_t = \sigma(W_{io} x_t + b_{io} + W_{ho} h_{(t-1)} + b_{ho}) \\c_t = f_t c_{(t-1)} + i_t g_t \\h_t = o_t \tanh(c_t)\end{array}\end{split}\]

Pytorch已經實現了LSTM, 只需要調用相應的API即可,調用的代碼片段如下:

self.rnn = nn.LSTM(input_size=embedding_dim,
                   hidden_size=hidden_size,
                   bidirectional=bidirectional,
                   batch_first=True)

3. 實驗

3.1 實驗設置

實驗基於Python 3.6Pytorch 0.4.0,為進行對照實驗,下列設置針對所有RNN模型:

  • 所有RNN模型均只使用單層;
  • 詞向量維度設置為100維,並且隨機初始化,在訓練過程中進行調整;
  • 隱狀態維度設置為75維;
  • 採用帶動量的隨機梯度下降(SGD),batch size為1,學習率(learning rate)為0.1,動量(momentum)為0.9並保持不變;
  • epoch=10;
  • 每種RNN模型都實現單向(Single)和雙向(Bi-Directional),並分別訓練。

3.2 實驗結果

在使用CPU的情況下,不同模型在測試集的\(F_1\)得分以及平均一個epoch訓練時長的結果如下:

\(F_1(\%) / T(s)\) Elman Jordan Hybrid LSTM
Single 87.26 / 438 87.90 / 487 88.46 / 494 92.16 / 3721
Bi-Directional 92.88 / 565 90.31 / 580 91.85 / 613 93.75 / 4357

從上表中可以看出:

  • 基於門控機制的LSTM由於其參數和運算步驟的增加,一個epoch的訓練時長是另外三種Simple RNN的9倍左右,而\(F_1\)得分也比Simple RNN高;
  • 雙向(Bi-Directional)RNN的\(F_1\)得分普遍比單向(Single)RNN高,而運行時間也多一些。

在使用同一塊GPU的情況下,不同模型在測試集的\(F_1\)得分以及平均一個epoch訓練時長的結果如下:

\(F_1(\%) / T(s)\) Elman Jordan Hybrid LSTM
Single 88.89 / 35.2 88.36 / 41.3 89.65 / 43.5 92.44 / 16.8
Bi-Directional 91.78 / 68.0 89.82 / 72.2 93.61 / 81.6 94.26 / 18.7

從上表中可以看出,即使是隨機梯度下降(batch_size=1),GPU的加速效果仍然相當明顯。值得指出的是,雖然LSTM的運算步驟比其他三種Simple-RNN多,但是用時卻是最少的,這可能是由於LSTM是直接調用Pytorch的API,針對GPU有優化,而另外三種的都是自己實現的,GPU加速效果沒有Pytorch好。

4. 總結與展望

總的來說,將槽填充問題當做序列標註問題是一種有效的做法,而RNN能夠較好的對序列進行建模,提取相關的上下文特征。雙向RNN的表現優於單向RNN,而LSTM的表現優於Simple RNN。對於Simple RNN而言,Elman的表現不比Jordan差(甚至更好),而用時更少並且實現更簡單,這可能是主流深度學習框架(TensorFlow / Pytorch等)的simple RNN是基於Elman的原因。而Hybrid作為Elman和Jordan的混合體,其訓練時間都多餘Elman和Jordan,\(F_1\)得分略有提升,但不是特別明顯(使用CPU時的雙向Elman表現比雙向Hybrid好),需要更多實驗進行驗證。

從實驗設置可以看出,本次實驗沒有過多的調參。如果想取得更好的結果,可以進行更細緻的調參,包括 :

  • 改變詞向量維度和隱狀態維度;
  • 考慮採用預訓練詞向量,然後固定或者進行微調;
  • 採用正則化技術,包括L1/L2, Dropout, Batch Normalization, Layer Normalization等;
  • 嘗試使用不同的優化器(如Adam),使用mini-batch,調整學習率;
  • 增加epoch次數。

此外,可以考慮在輸入時融入詞性標註和命名實體識別等信息,在輸出時使用Viterbi演算法進行解碼,也可以嘗試不同形式的門控RNN(如GRU,LSTM變體等)以及採用多層RNN,並考慮是否使用殘差連接等。

參考資料

Mesnil G, Dauphin Y, Yao K, et al. Using recurrent neural networks for slot filling in spoken language understanding[J]. IEEE/ACM Transactions on Audio, Speech, and Language Processing, 2015, 23(3): 530-539.

Wikipedia. Recurrent neural network. https://en.wikipedia.org/wiki/Recurrent_neural_network

PyTorch documentation. Recurrent layers. http://pytorch.org/docs/stable/nn.html#recurrent-layers

Hung-yi Lee. Machine Learning (2017,Spring). http://speech.ee.ntu.edu.tw/~tlkagk/courses/ML_2017/Lecture/RNN.pdf

YUN-NUNG (VIVIAN) CHEN. Spring 105 - Intelligent Conversational Bot. https://www.csie.ntu.edu.tw/~yvchen/s105-icb/doc/170321_LU.pdf


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...