Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

言語モデルとRNN

言語モデル

embeddingを直接推定するのではなく、単語予測モデル(言語モデル)を構築して副次的にembeddingを取得することになる。

言語モデルとは、尤もらしい文章を生成できるような確率分布を習得するために、文章の確率を推定するモデルのこと。

ある文章SSをトークン化したのを(w1,w2,,wn)(w_1, w_2, \cdots, w_n)と表記するならば、

P(S)=P(w1,w2,,wn)P(S)=P(w_1, w_2, \cdots, w_n)

を求めたいということになる。これは条件付き確率の積として表せる

P(w1,w2,,wn)=P(w1)×P(w2w1)×P(w3w1,w2)×=i=1np(wici)\begin{align} P(w_1, w_2, \cdots, w_n) &= P(w_1) \times P(w_2|w_1) \times P(w_3|w_1, w_2) \times \cdots\\ &= \prod_{i=1}^n p(w_i|\boldsymbol{c}_i) \end{align}

ここでci\boldsymbol{c}_iwiw_iより前のトークン列ci=(w1,w2,,wi1)\boldsymbol{c}_i=(w_1,w_2,\cdots,w_{i-1})で、文脈(context)と呼ばれる

言語モデルは文脈をもとに次の単語を予測する。例えば

Alice is reading a book in the room. Bob comes into the room and says hi to ?

の?に入る語を予測する

語順の問題

Word2Vecに使われたcontinuous bag-of words (CBOW) のようなFeed-Forward Networkによる言語モデルでは、コンテキストの語順が考慮されない

RNN型ニューラル言語モデル(Mikolov + 2010)

Mikolov et al. (2010). Recurrent neural network based language model.

RNN

RNNは前のトークンまでの情報を次のトークンの出力に渡すパスが存在する。 トークンの系列を時系列モデルになぞらえて時刻と表現すると、時刻ttの出力ht\boldsymbol{h}_t

ht=tanh(Whht1+Wxxt+b)\newcommand{\b}[1]{\boldsymbol{#1}} \b{h}_t = \text{tanh}( \b{W}_h \b{h}_{t-1} + \b{W}_x \b{x}_t + \b{b} )

となる。ここでxt\boldsymbol{x}_tは入力で、ht1\boldsymbol{h}_{t-1}は1時刻前の出力、\b{W}_h, \b{W}_xは重みで\b{b}はバイアスである。

なお、出力\b{h}隠れ状態(hidden state)と呼ばれる事が多い

Truncated BPTT

RNNの誤差逆伝播法は、時間方向への逆伝播法ということで**Backproagation Through Time(BPTT)**と呼ばれる。

しかし長い文章を扱う場合、すべてのトークンを使うと学習の際にメモリに乗り切らない問題や勾配が不安定になる問題がある。 そこで、逆伝播のときはトークン系列を分割して学習する(順伝播は全部つながるようにする)方法があり、これをTruncated BPTTという。

実装(PyTorch)

ht=tanh(xtWihT+bih+ht1WhhT+bhh)h_t = \tanh(x_t W_{ih}^T + b_{ih} + h_{t-1}W_{hh}^T + b_{hh})
# 最小構成
import torch
from torch import nn

n_input = 1
n_hidden = 3
n_layer = 2
rnn = nn.RNN(n_input, n_hidden, n_layer)
x = torch.randn(5, 3, n_input)
h0 = torch.randn(2, 3, n_hidden)
output, hn = rnn(x, h0)
output
tensor([[[ 0.4427, 0.9229, 0.5728], [ 0.1982, -0.0123, 0.3031], [ 0.3001, 0.3558, 0.2073]], [[-0.4958, 0.5230, -0.5726], [ 0.2084, 0.5869, 0.0893], [-0.0264, 0.5945, -0.3003]], [[ 0.3149, 0.3611, 0.0945], [-0.0669, 0.5235, -0.2458], [ 0.0957, 0.5043, -0.2213]], [[-0.2301, 0.3040, -0.3211], [-0.1218, 0.2889, -0.3467], [-0.2557, 0.2502, -0.5259]], [[ 0.3510, 0.5464, 0.0433], [ 0.1098, 0.4022, -0.2408], [ 0.4425, 0.5785, 0.0343]]], grad_fn=<StackBackward0>)

実装(Python)

参考:ゼロから作るDeep Learning 2

import numpy as np
h = 2
w = 3
Wh = np.random.normal(size = (h, w))
import numpy as np

class RNN:
    def __init__(self, Wx, Wh, b):
        self.params = [Wx, Wh, b]
        self.grads = [np.zeros_like(Wx),
                      np.zeros_like(Wh),
                      np.zeros_like(b)]
        self.chache = None

    def forward(self, x, h_prev):
        Wx, Wh, b = self.params
        t = Wh @ h_prev + Wx @ x + b
        h_nrex = np.tanh(t)
        self.cache = (x, h_prev, h_next)
        return h_next

    def backword(self, dh_next):
        Wx, Wh, b = self.params
        x, h_prev, h_next = self.cache
        dt = dh_next * (1 - h_next ** 2)
        db = np.sum(dt, axis=0)
        dWh = h_prev.T @ dt
        dh_prev = Wh @ dt
        dWx = x @ dt
        dx = Wx @ dt
        self.grads[0][...] = dWx
        self.grads[1][...] = dWh
        self.grads[2][...] = db
        return dx, dh_prev