PytorchでRNNとLSTMモデルを使って名前から国籍を推測する予想モデルを作る方法

スポンサーリンク

この記事では、RNNとLSTMモデルを構築して、各キャラクターの名前から国籍を予測するのに役立てます。

まず、私たちが持っているデータセットを理解することから始めましょう。

スポンサーリンク

データセットを理解する

データセットは、各行にカンマで区切られた人名と国籍を含むテキストファイルです。

データセットには2万人以上の名前と、ポルトガル、アイルランド、スペインなど18のユニークな国籍が含まれています。

データのスナップショットを以下に示します。

データセットはこちらからダウンロードできます。

1
2
3
4
5
6
7
8
9
10
from io import open
import os, string, random, time, math
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from sklearn.model_selection import train_test_split
import torch
import torch.nn as nn
import torch.optim as optim
from IPython.display import clear_output

Pythonで人名から国籍を予測する

さっそくコードを実装してみましょう。

まず、モジュールをインポートし、今回のデモで選んだ名前と国籍のデータセットをインポートします。

ステップ1:モジュールのインポート

モデルの構築を始める前に、必要なすべてのライブラリをプログラムにインポートする必要があります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
languages = []
data = []
X = []
y = []
 
with open("name2lang.txt", 'r') as f:
    #read the dataset
    for line in f:
        line = line.split(",")
        name = line[0].strip()
        lang = line[1].strip()
        if not lang in languages:
            languages.append(lang)
        X.append(name)
        y.append(lang)
        data.append((name, lang))
 
n_languages = len(languages)
print("Number of  Names: ", len(X))
print("Number of Languages: ",n_languages)
print("All Names: ", X)
print("All languages: ",languages)
print("Final Data: ", data)

ステップ 2: データセットの読み込み

データセットをロードするために、データの各行を調べて、名前と国籍を一緒に含むタプルのリストを作成します。

こうすることで、後のセクションでモデルがデータを理解しやすくなります。

1
2
3
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 123, stratify = y)
print("Training Data: ", len(X_train))
print("Testing Data: ", len(X_test))
Training Data:  16040
Testing Data:  4010

ステップ3:訓練とテストの分割

80%のデータをトレーニングに、残りの20%をテストに使用する80:20の割合で、データをトレーニングとテストに分割します。

1
2
3
4
5
6
7
8
9
10
all_letters = string.ascii_letters + ".,;"
print(string.ascii_letters)
n_letters = len(all_letters)
 
def name_rep(name):
  rep = torch.zeros(len(name), 1, n_letters)
  for index, letter in enumerate(name):
    pos = all_letters.find(letter)
    rep[index][0][pos] = 1
  return rep
1
2
def nat_rep(lang):
    return torch.tensor([languages.index(lang)], dtype = torch.long)

ステップ4:データのエンコード

文字エンコーディングは、生のテキストデータではなく、シーケンスモデルへの入力として使用される。

そのため、入力を暗号化し、文字レベルで識別する必要がある。

文字レベルでエンコーディングを作成したら、単語全体のエンコーディングを得るために、すべての文字レベルのエンコーディングを連結する必要があります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class RNN_net(nn.Module):
     
    def __init__(self, input_size, hidden_size, output_size):
        super(RNN_net, self).__init__()
        self.hidden_size = hidden_size
        self.i2h = nn.Linear(input_size + hidden_size, hidden_size)
        self.i2o = nn.Linear(input_size + hidden_size, output_size)
        self.softmax = nn.LogSoftmax(dim = 1)
     
    def forward(self, input_, hidden):
        combined = torch.cat((input_, hidden), 1)
        hidden = self.i2h(combined)
        output = self.i2o(combined)
        output = self.softmax(output)
        return output, hidden
     
    def init_hidden(self):
        return torch.zeros(1, self.hidden_size)

上記の関数name_repは名前の一回限りのエンコーディングを生成します。

まず、入力サイズが名前の長さに等しく、外形がリストの全文字数に等しいゼロのテンソルを宣言します。

次に、各文字を巡回して文字のインデックスを特定し、そのインデックス位置の値を1に設定し、残りの値を0にします。

1
2
3
4
5
6
7
8
9
10
11
12
def infer(net, name):
    net.eval()
    name_ohe = name_rep(name)
    hidden = net.init_hidden()
    for i in range(name_ohe.size()[0]):
        output, hidden = net(name_ohe[i], hidden)
    return output
n_hidden = 128
net = RNN_net(n_letters, n_hidden, n_languages)
output = infer(net, "Adam")
index = torch.argmax(output)
print(output, index)

国籍のエンコーディングは、名前のエンコーディングよりもはるかに単純な論理で行われます。

国籍のエンコードには、国籍のリストでその国籍が出現するインデックスを決定するだけです。

そのインデックスがエンコーディングとして割り当てられます。

ステップ5: ニューラルネットワークモデルの構築

Pytorchを使ってRNNのモデルを構築していきます。

init関数(コンストラクタ関数)は、隠れ層に関連する重みやバイアスなどのネットワーク特性を初期化するのに役立ちます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def dataloader(npoints, X_, y_):
    to_ret = []
    for i in range(npoints):
        index_ = np.random.randint(len(X_))
        name, lang = X_[index_], y_[index_]
        to_ret.append((name, lang, name_rep(name), nat_rep(lang)))
 
    return to_ret
 
def eval(net, n_points, k, X_, y_):
     data_ = dataloader(n_points, X_, y_)
     correct = 0
 
     for name, language, name_ohe, lang_rep in data_:
         output = infer(net, name)
         val, indices = output.topk(k)
         if lang_rep in indices:
             correct += 1
     accuracy = correct/n_points
     return accuracy

forward関数は、まず文字の入力と隠れ表現を連結し、それを入力としてi2h層、i2o層、softmax層を使って出力ラベルを計算します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def train(net, opt, criterion, n_points):
    opt.zero_grad()
    total_loss = 0
    data_ = dataloader(n_points, X_train, y_train)
    for name, language, name_ohe, lang_rep in data_:
        hidden = net.init_hidden()
        for i in range(name_ohe.size()[0]):
            output, hidden = net(name_ohe[i], hidden)
        loss = criterion(output, lang_rep)
        loss.backward(retain_graph=True)
        total_loss += loss 
    opt.step()      
    return total_loss/n_points
 
def train_setup(net, lr = 0.01, n_batches = 100, batch_size = 10, momentum = 0.9, display_freq = 5):
    criterion = nn.NLLLoss()
    opt = optim.SGD(net.parameters(), lr = lr, momentum = momentum)
    loss_arr = np.zeros(n_batches + 1)
    for i in range(n_batches):
        loss_arr[i + 1] = (loss_arr[i]*i + train(net, opt, criterion, batch_size))/(i + 1)
        if i%display_freq == display_freq - 1:
            clear_output(wait = True)
            print("Iteration number ", i + 1, "Top - 1 Accuracy:", round(eval(net, len(X_test), 1, X_test, y_test),4), 'Top-2 Accuracy:', round(eval(net, len(X_test), 2, X_test, y_test),4), 'Loss:', round(loss_arr[i]),4)
            plt.figure()
            plt.plot(loss_arr[1:i], "-*")
            plt.xlabel("Iteration")
            plt.ylabel("Loss")
            plt.show()
            print("

")

n_hidden = 128
net = RNN_net(n_letters, n_hidden, n_languages)
train_setup(net, lr = 0.0005, n_batches = 100, batch_size = 256)

infer関数の入力引数としてネットワークインスタンスと人名が渡される。

この関数ではネットワークを評価モードに設定し、入力された人名の One-Hot 表現を計算します。

その後、隠れサイズに応じた隠れ表現を計算し、全文字を循環させた後、計算した隠れ表現をネットワークに返す。

最後に、出力である人物の国籍を計算します。

ステップ6:RNNモデルの精度を計算する

モデルのトレーニングに移る前に、モデルの精度を計算する関数を作成しましょう。

そのために、以下を入力とする評価関数を作成します。

  1. ネットワークインスタンス
  2. データ点数
  3. kの値
  4. XとYのテストデータ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class LSTM_net(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(LSTM_net, self).__init__()
        self.hidden_size = hidden_size
        self.lstm_cell = nn.LSTM(input_size, hidden_size) #LSTM cell
        self.h2o = nn.Linear(hidden_size, output_size)
        self.softmax = nn.LogSoftmax(dim = 2)
 
    def forward(self, input_, hidden):
        out, hidden = self.lstm_cell(input_.view(1, 1, -1), hidden)
        output = self.h2o(hidden[0])
        output = self.softmax(output)
        return output.view(1, -1), hidden
 
    def init_hidden(self):
        return (torch.zeros(1, 1, self.hidden_size), torch.zeros(1, 1, self.hidden_size))
 
n_hidden = 128
net = LSTM_net(n_letters, n_hidden, n_languages)
train_setup(net, lr = 0.0005, n_batches = 100, batch_size = 256)

この関数の内部では、次のような処理を行うことになる。

  1. データローダー`を使用してデータをロードします。
    1. データローダー内に存在するすべての人物の名前を反復します。
    1. 入力に対してモデルを起動し、出力を取得します。
    1. 予測されたクラスを計算します。
  2. 正しく予測されたクラスの総数を計算する
  3. 最終的なパーセンテージを返す。

ステップ 7: RNN モデルのトレーニング

モデルを訓練するために、ネットワークを訓練するための簡単な関数をコーディングします。

Dataset Snapshot Nationality Predictor

100バッチ学習させた結果、RNNモデルでtop-1の精度は66.5%、top-2の精度は79%を達成することができました。

Load Dataset Nationality Predictor

ステップ8:LSTMモデルの学習

また、人名の国籍を分類するためのLSTMモデルの実装方法について説明します。

そのために、Pytorchを利用し、カスタムLSTMクラスを作成します。

Loss Plot Nationality Predictor

100バッチ学習した結果、LSTM Modelでtop-1の精度は52.6%、top-2の精度は66.9%を達成することができました。

Loss Plot Nationality Predictor LSTM

まとめ

Pytorchを使って国籍分類モデルを構築する方法を学びましたね。

このチュートリアルは気に入りましたか?いずれにせよ、以下のチュートリアルをご覧になることをお勧めします。

  1. Pythonで衣服の画像を分類する – 完全ガイド
  2. Pythonを使ったワイン分類 – 簡単な説明

お時間を割いていただき、ありがとうございました。

タイトルとURLをコピーしました