自然言語処理100本ノック2020を解く(8章-前半)

あなたは何?

都市圏でITエンジニア(の研修)をやっているものです.
ありがたいことに,研修中にたくさんインプットができているので,少しずつ
アウトプットしていけたらと考えています.

本題

自然言語処理100本ノックの2020年版の解説があまり出回っていない気がしたので,需要
があるかと思い書いています.クオリティは担保しません.
今回は,8章の前半の解き方を載せます.合っているかはわかりません.
間違っていたらご指摘いただくと非常に非常に助かります.
 

70.単語ベクトルの和による特徴量

この章では,ニューラルネットによる学習をします.
入力が300次元の特徴ベクトル,出力が4次元のカテゴリ,というイメージです.

本問題では,入力の特徴ベクトルを作ります.特徴ベクトルの数式は以下.

 x_i=\displaystyle \frac{1}{T_i} \sum_{t=1}^{T_i}emb(w_{i,t})

 T_iは記事見出しに含まれる単語数で, emb(w_{i,t})はそれぞれの単語のベクトルです.
つまり,記事見出し中の単語ベクトルの平均を計算すれば良いです.
次に,正解ラベルについてですが,これはカテゴリ毎に何か番号を振れば良いです.


コード

import re
import numpy
from gensim.models import KeyedVectors
news_path = '../data/GoogleNews-vectors-negative300.bin'
words = KeyedVectors.load_word2vec_format(news_path, binary = True)#単語ベクトルの読み込み

def vec2sum(x_data, dim):
    pattern = re.compile(r'[a-z|A-Z]+', re.MULTILINE + re.VERBOSE)  
    vector_sum = np.zeros((len(vector_data), dim))
    for i, s in enumerate(vector_data):
        vector = np.zeros(dim) 
        cnt = 0
        ss = pattern.findall(s)
        for noun in ss:
            try :
                cnt += 1
                vector += words[noun]
            except KeyError as error:
                continue
        if cnt == 0:
            continue
        vector_sum[i] = (vector / cnt)

    return vector_sum

dim = words['US'].shape[0]
print(len(x_train))
x_train = vec2sum(x_train, dim)
print(x_train.shape)


実行結果

8708
(8708, 300)


ベクトル辞書に含まれていない単語があるので,その場合は文字数をカウントせずにループを続けます.
とりあえず,記事数8708の300次元の特徴ベクトルができました.

71.単層ニューラルネットワークによる予測

次に,上記の特徴ベクトルとランダムな重み行列 Wを掛け合わせ,それをソフトマックス関数の入力として計算します.
ソフトマックス関数は以下.

y_k = \displaystyle \frac{exp(x_k)}{\sum_{i=1} ^ {n}exp(x_i)}

これをそのまま実装しても良いですが,今回はpytorchを用いて楽をします.


コード

import torch
import torch.nn as nn
import torch.nn.functional as func
import torch.optim as optim
from torch.autograd import Variable

w = torch.randn(dim, 4, requires_grad = True)
x_feature = torch.tensor(x_feature, requires_grad = True)

# 単体事例
y = func.softmax(torch.mm(x_feature[:1].float(), w))
print(y)

# 複数事例
Y = func.softmax(torch.mm(x_feature[:4].float(), w), dim = 1)
print(Y)


実行結果

tensor([[0.0795, 0.1212, 0.1688, 0.6305]], grad_fn=<SoftmaxBackward>)
tensor([[0.0645, 0.4789, 0.1243, 0.3324],
        [0.3788, 0.2161, 0.1569, 0.2482],
        [0.2830, 0.0544, 0.2407, 0.4219],
        [0.1088, 0.0486, 0.3351, 0.5075]], grad_fn=<SoftmaxBackward>)

72.損失と勾配の計算

クロスエントロピー誤差と重み Wに対する勾配を計算します.
クロスエントロピー誤差の算出は以下.tを正解ラベルとします.

 loss(x, t) = \displaystyle \frac{exp(x_t)}{\sum_{i=1} ^ {n}exp(x_i)}

これにsoftmaxの結果を放り込めばOK.集合に対してはその平均を返す.
ここもpytorchで楽をします.pytorchでは,CrossEntropyLoss
y_trainは正解ラベルです.


コード

from math import exp, log

criterion = nn.CrossEntropyLoss()
y_train = torch.tensor(y_train.astype(float), requires_grad = True)

# 単体事例
y_ = criterion(torch.mm(x_feature[:1].float(), w), y_train[:1].long())
print(y_)
y_.backward()
print(w.grad)

# 複数事例
Y_ = criterion(torch.mm(x_feature[:4].float(), w), y_train[:4].long())
print(Y_)
Y_.backward()
print(w.grad)

# 検算
ans = []
for yy, i in zip(Y, y_train[:4].long()):
    ans.append(-log(yy[i]))
print(sum(ans) / len(ans))


実行結果

tensor(2.0852, grad_fn=<NllLossBackward>)
tensor([[-0.0011, -0.0080,  0.0147, -0.0056],
        [ 0.0072,  0.0533, -0.0976,  0.0370],
        [-0.0008, -0.0062,  0.0113, -0.0043],
        ...,
        [-0.0055, -0.0412,  0.0753, -0.0286],
        [-0.0021, -0.0154,  0.0282, -0.0107],
        [-0.0010, -0.0071,  0.0129, -0.0049]])
tensor(1.2895, grad_fn=<NllLossBackward>)
tensor([[-9.5167e-03, -1.6568e-03,  4.1909e-03,  6.9826e-03],
        [-5.0837e-03,  7.4656e-02, -1.3673e-01,  6.7157e-02],
        [ 7.1513e-03, -1.1068e-02,  1.4147e-02, -1.0230e-02],
        ...,
        [-9.3671e-03, -4.7332e-02,  9.5406e-02, -3.8707e-02],
        [ 9.5714e-05, -1.8119e-02,  3.1322e-02, -1.3299e-02],
        [ 7.7742e-03, -1.2784e-02,  3.0687e-02, -2.5677e-02]])
1.2895281521241457


出ましたね.検算を一応してみましたが,合っていました.
勾配に関しては,backward()で微分をしてくれるので,とても楽ができます.



今回は一旦ここまでとします.後半はひとまとまりにできるので...
では...!



次回:
pongyun.hatenablog.com


出典:
自然言語処理100本ノック
nlp100.github.io