nikkie-ftnextの日記

イベントレポートや読書メモを発信

ニュースを分類するMLP(keras製)をpytorchで動くように書き直そう [前編]

はじめに

頑張れば、何かがあるって、信じてる。nikkieです。
2019年12月末から自然言語処理のネタで毎週1本ブログを書いています。
初回はこちら:

今週のネタは「自然言語処理のタスクをするkeras(tensorflow)製のモデルをpytorchでも書いてみる」です。

前提:nikkieとkeras, そしてtorch

  • keras 入門+αレベル(一応読める・書ける)
  • pytorch 読み書きできるようになりたい(未入門)

業務で使っているのはtensorflowで、kerasで書かれたモデルであれば、ドキュメントに当たりながら読めるような感じです。
それに対してpytorchはこれまで触ったことがありません。

直近ではPython2を見送る前にChainerを見送ることになったり、Kagglerの方々の中でこの本が流行っているらしいとTwitterで見かけたりして、ぼんやりと「pytorchが流行っているんだなあ」という印象を持ち始めました。
また、この試みでは、今後BERTなど、まだ触ったことのないモデルも触ろうと思っているのですが、BERTはじめ新しいモデルはpytorchの方が情報が多いという印象があります。
keraspytorchもどっちも読めたら便利そう」というやや安直な考えから、今後につながる一歩目として、kerasで書いたMLPpytorchで書き直してみます。

keras製、ニュースを分類するMLP

  • データセット:ロイター通信のニューステキスト
    • tensorflow.keras.datasets.reuters
    • ニュース1つは、単語をインデックス(整数)に変換して表したリスト(インデックスが若い単語ほど頻出する)
    • トピックを表すクラスが全部で46ある。ニュースそれぞれはどれか1クラスに分類される(多クラス分類
  • モデル:2層のMLPドロップアウトしているだけ)
model = keras.Sequential(
    [
        layers.Dense(512, input_shape=(max_words,), activation=tf.nn.relu),
        layers.Dropout(drop_out),
        layers.Dense(number_of_classes, activation=tf.nn.softmax),
    ]
)

理解を深めるのを目的に、keras製モデル構築はQiitaにアウトプットしています:

それでは、pytorchで書き直します(コードにならって、以下ではtorchとします)。

動作環境

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.14.6
BuildVersion:   18G103
$ python -V  # venvモジュールによる仮想環境を利用
Python 3.7.3
$ pip list  # 主要なものを抜粋
ipython              7.11.0
matplotlib           3.1.2
numpy                1.18.0
pip                  19.3.1
scipy                1.4.1
tensorflow           2.0.0
torch                1.3.1

kerasからtorchへ

以下の方針で書いてみました:

  • ロイター通信のデータと全く同じデータはtorchにはなさそうなので、データのロードはkerasを使用
  • 2層のMLPを作るところをtorchで書き換え

以下の2点について見ていきます:

  • データの準備
  • モデル作成

データの準備(dataset)

torchで実装するMLPに渡すデータの形式はText Classification with TorchText — PyTorch Tutorials 1.3.1 documentation を参考に準備しました。

In [2]: from torchtext.datasets import text_classification                      

In [8]: train_dataset, test_dataset = text_classification.DATASETS['AG_NEWS']()  # .dataというディレクトリがないと落ちるので注意
ag_news_csv.tar.gz: 11.8MB [00:01, 11.7MB/s] 
120000lines [00:08, 14080.42lines/s] 
120000lines [00:15, 7859.85lines/s] 
7600lines [00:00, 8097.48lines/s] 
 
In [9]: len(train_dataset)                                                      
Out[9]: 120000 
 
In [10]: len(test_dataset)                                                      
Out[10]: 7600 
 
In [11]: type(train_dataset)                                                    
Out[11]: torchtext.datasets.text_classification.TextClassificationDataset 
 
In [12]: train_dataset[0]                                                       
Out[12]:  
(2, 
 tensor([    572,     564,       2,    2326,   49106,     150,      88,       3,
            1143,      14,      32,      15,      32,      16,  443749,       4,
             572,     499,      17,      10,  741769,       7,  468770,       4,
              52,    7019,    1050,     442,       2,   14341,     673,  141447,
          326092,   55044,    7887,     411,    9870,  628642,      43,      44,
             144,     145,  299709,  443750,   51274,     703,   14312,      23,
         1111134,  741770,  411508,  468771,    3779,   86384,  135944,  371666,
            4052])) 

チュートリアルで使っているAG_NEWSのデータの1つ1つは、(クラス, tensor([単語のインデックス]))という形式です。
このリストがdatasetとなっています。

チュートリアルは入力層にEmbeddingBagレイヤーを使っています。
MLPでは入力の長さを揃える必要があると考え、keras.preprocessing.text.Tokenizersequences_to_matrixで長さを揃えて0/1で表したテキストをdatasetとすることにしました。

tensorの部分をどう作るか試してみたところ、AG_NEWSのデータの一部をtorch.tensorに渡したところ、要素が整数のままでtensorを作ることができました1
そこでkerasTokenizersequences_to_matrixの結果をtensorに渡して、datasetの形式にします。

まとめると、ロイター通信のニューステキストデータをtorchで扱えるように変換する関数はこちらです:

def convert_to_torch_tensors(texts, labels):
    torch_tensors = []
    for text, label in zip(texts, labels):
        text_tensor = torch.tensor(text)
        torch_tensor = (label, text_tensor)
        torch_tensors.append(torch_tensor)
    return torch_tensors

モデル作成

torchでのMLP実装で参考にしたのはこちらのブログ:
PyTorch まずMLPを使ってみる | cedro-blog

上記ブログのMLPNetを参考にします:

イニシャライザで層を定義した後は、forwardメソッドで層の重ね方を定義します。

class MLPNet(nn.Module):
    def __init__(self, max_words, number_of_classes, drop_out):
        super(MLPNet, self).__init__()
        self.fc1 = nn.Linear(max_words, 512)
        self.fc2 = nn.Linear(512, number_of_classes)
        self.dropout1 = nn.Dropout(drop_out)

    def forward(self, x):
        x = F.relu(self.fc1(x))
        x = self.dropout1(x)
        return F.softmax(self.fc2(x), dim=1)

書き換えは学習部分に続くのですが、それは後半で扱います。
学習部分はkerastorchで全然違うのです!
後半をお楽しみに。


  1. ドキュメントのExampleに「Type inference on data」とあるため、整数のままだったようです

  2. Pytorch equivalent of Keras - PyTorch Forums などpytorchのForumに同様の質問が見つかりました