機械学習・自然言語処理の勉強メモ

学んだことのメモやまとめ

Pytorch:CNNで文字表現の抽出

NLPにCNNを利用したモデルはすっかり当たり前になりました。

少し前に「CNNでテキスト分類」という記事を書きましたが、
その時はPytorchでCNNを扱うための基本的なコードだけに留まっていました。
kento1109.hatenablog.com

今回はNERなどで用いられる文字情報をCNNで表現する際のコーディングについて書こうと思います。

以下は単語を文字単位に分割してその分散表現を獲得する場合のイメージです。

f:id:kento1109:20190930103525p:plain
End-to-end Sequence Labeling via Bi-directional LSTM-CNNs-CRFより引用

今回はこれをPytorchで書いてみたいと思います。

Char Embedding

簡単のため、文字列は既にpadding済であることを前提とします。

vocab_size = 100
char_emb_size = 30

char_emb = nn.Embedding(num_embeddings=vocab_size, embedding_dim=char_emb_size)

として、Embeddingクラスを定義しておきます。

ここに、入力文を渡します。

max_sent = 20
max_char = 10

input = torch.randint(0, vocab_size, (max_sent, max_char))
char_embeded = char_emb(input)
char_embeded.size()
# torch.Size([20, 10, 30])

CNN

各単語は「文字列×分散表現の次元数」の行列で表現されています。
これにCNNに渡します。
今回は、window size=3で畳み込みます。

window_size = 3
cnn = nn.Conv2d(in_channels=1, 
                out_channels=char_emb_size, 
                kernel_size=(window_size,char_emb_size), 
                stride=window_size-1,
                padding=(window_size-1,0))

stride=window_size-1は、上の図の畳み込み操作のイメージ通りで、2文字列単位でフィルターをスライドさせます。
また、文字の前後にwindow_size-1の行列をpaddingしておきます。

char_embeded = char_embeded.unsqueeze(1)
feature_map = cnn(char_embeded)
feature_map.size()
torch.Size([20, 30, 6, 1])

最初にチャネル数の次元を追加します。

出力は(N, C_{out}, H_{out}, W_{out}) の次元となります。

畳み込み操作をイメージで表すと以下のようになります。
f:id:kento1109:20190930113643p:plain

max_pooling

さて、畳み込み操作により得た特徴マップをプーリング層に渡します。

m = nn.MaxPool1d(kernel_size=feature_map.size(2))

kernel_sizeは最大値を取る範囲を指定します。
今回は各チャネルの特徴マップ全てからmax_poolingを行うので、上記のように書きます。

最後に特徴マップをプーリング層に渡します。

char_feat = m(feature_map.squeeze()).squeeze()
char_feat.size()
# torch.Size([20, 30])

これで、「単語×次元数」の分散表現を得ることが出来ました。
NER等の場合、これを後続のLSTMなどに渡してあげるとOKです。

BERTなどが主流のなか、あえて少し前のベーシックなモデルについて復習しました。