Pytorch の軽量ラッパー、skorch で回帰・分類を試してみた

公開:2021/02/14
更新:2021/02/14
6 min読了の目安(約6200字TECH技術記事

Pytorch の軽量ラッパー、skorch を使って教師あり学習の回帰・分類を試してみたのでまとめました。

skorch は scikit-learn のように簡単に使うことができるそうです。

公式ドキュメント

https://skorch.readthedocs.io/en/stable/index.html

参照した公式のサンプルコード

https://colab.research.google.com/github/skorch-dev/skorch/blob/master/notebooks/Basic_Usage.ipynb#scrollTo=hyntGdjqaPaG

公式のGitHub

https://github.com/skorch-dev/skorch

ブログ中ではコードの詳細は割愛しています。

コードは全体については、Google Colab はこちら、GitHub はこちら

なお、ブログ中では触れていませんが、説明変数は StandardScaler() で標準化しています。

回帰

scikit-learn のボストンの住宅の中古価格のデータセットを使用します。

https://scikit-learn.org/stable/modules/generated/sklearn.datasets.load_boston.html

データセットの中身についてはこちらの記事をご参照ください。

https://qiita.com/yut-nagase/items/6c2bc025e7eaa7493f89

ざっと目的変数の分布を確認すると、以下のとおり。

fig = plt.figure(figsize=(16, 8))
plt.plot(y, label="実績")

plt.legend()

説明変数 X、目的変数 y の型変換

説明変数 (X) は float型にする必要があるようです。
目的変数 (y) は、float型にし、reshapeで2次元の配列 (要素数, 1)に変換する必要があるようです。

X = X.astype(np.float32)

y = y.reshape(-1, 1)
y = y.astype(np.float32)

モデルの作成

回帰用のニューラルネットのクラスを作成します。

なお、ボストンの住宅価格のデータセットは、説明変数が13個なので入力層は13、回帰なので出力層は1にする必要があります。

class RegressorModule(nn.Module):
    def __init__(
            self,
            num_units=20,
            nonlin=F.relu,
    ):
        super(RegressorModule, self).__init__()
        self.num_units = num_units
        self.nonlin = nonlin

        self.dense0 = nn.Linear(13, num_units) # trainデータの説明変数の数に合わせる
        self.nonlin = nonlin
        self.dense1 = nn.Linear(num_units, 10)
        self.output = nn.Linear(10, 1)

    def forward(self, X, **kwargs):
        X = self.nonlin(self.dense0(X))
        X = F.relu(self.dense1(X))
        X = self.output(X)
        return X

TensorBoard 表示に必要な設定

Pytorch で TensorBoard を使うために必要な SummaryWriter 関数を利用します。

また、skorch には TensorBoard で使えるコールバック関数があるので利用します。

from torch.utils.tensorboard import SummaryWriter

# TensorBoard に出力するための log を格納するフォルダの指定
writer_reg = SummaryWriter(log_dir="./logs_reg")

# callbackの作成
from skorch.callbacks import TensorBoard
torch_callback_reg = TensorBoard(writer_reg, close_after_train=True)

処理スピードを上げるために、Google Colab で GPU を使用するために device='cuda' を指定しています。

GPU を使用しない、出来ない場合にはコメントアウトしましょう。

from skorch import NeuralNetRegressor

net_regr = NeuralNetRegressor(
    RegressorModule,
    max_epochs=500,
    lr=0.01,
    callbacks=[torch_callback_reg], # TensorBoard用の設定
    device='cuda'  # GPUを使用しない場合はコメントアウトが必要
)

学習

net_regr.fit(
            X_train, 
            y_train,
)

tensorboard の表示

TensorBoard で読み込むログフォルダは、先ほど SummaryWriter() で指定したものを使用します。

%load_ext tensorboard
%tensorboard --logdir ./logs_reg

こんな感じで学習の進み方を可視化できました。

予測と評価

予測データは scikit-learn の要領で作成できるのでとても簡単です。

# 予測データの作成
y_pred = net_regr.predict(X_test)

# 評価
from sklearn.metrics import r2_score
r2_score(y_test, y_pred)

適当に作ったニューラルネットでも R2スコアが80%以上と比較的いい結果になりました。

0.8332760985274763

精度が思うように出ない場合は、NeuralNetRegressor のエポック数(max_epochs)や学習率(lr)、RegressorModule クラスの層の形状を調整してみましょう。

実績と予測をグラフで比較してやると以下のような感じです。

分類

scikit-learn のアヤメのデータセットを使用します。

https://scikit-learn.org/stable/modules/generated/sklearn.datasets.load_iris.html

データセットの中身についてはこちらの記事をご参照ください。

https://qiita.com/Hirochon/items/12379d7ca6141f1fb6fa

ざっと目的変数の分布を確認すると、以下のとおり。

fig = plt.figure(figsize=(12, 8))
plt.hist(y, label="実績")

plt.legend()

説明変数 X、目的変数 y の型変換

説明変数 (X) は回帰と同様にfloat型にする必要があるようです。
目的変数 (y) は回帰と違いint型にし、次元も1次元のままでよいようです。

X = X.astype(np.float32)

y = y.astype(np.int64)

モデルの作成

分類用のニューラルネットのクラスを作成します。

なお、アヤメのデータセットは、説明変数が4個なので入力層は4、出力層は目的変数のカテゴリ数3にする必要があるようです。

class ClassifierModule(nn.Module):
    def __init__(
            self,
            num_units=20,
            nonlin=F.relu,
            dropout=0.5,
    ):
        super(ClassifierModule, self).__init__()
        self.num_units = num_units
        self.nonlin = nonlin
        self.dropout = dropout

        self.dense0 = nn.Linear(4, num_units) # trainデータの説明変数の数に合わせる
        self.nonlin = nonlin
        self.dropout = nn.Dropout(dropout)
        self.dense1 = nn.Linear(num_units, 10)
        self.output = nn.Linear(10, 3) # trainデータの被説明変数の数に合わせる

    def forward(self, X, **kwargs):
        X = self.nonlin(self.dense0(X))
        X = self.dropout(X)
        X = F.relu(self.dense1(X))
        X = F.softmax(self.output(X), dim=-1)
        return X

なお、以降TensorBoard 使用に必要な部分は回帰と同様なので割愛します。

from skorch import NeuralNetClassifier

net = NeuralNetClassifier(
    ClassifierModule,
    max_epochs=500,
    lr=0.1,
   device='cuda',  # GPUを使用しない場合はコメントアウトが必要
)

学習

net.fit(X_train, 
        y_train)

予測と評価

iris のデータセットは3項分類なので、f1スコアの引数に average='micro' を追加してやります。

# 予測データの作成
y_pred = net.predict(X_test)

# 評価
from sklearn.metrics import f1_score
f1_score(y_test, y_pred, average='micro')

1.0

これはたまたまだと思いますが、なんと完全に予測結果が一致しています。

https://hahaeatora.hateblo.jp/entry/2019/02/28/200000

https://scikit-learn.org/stable/modules/generated/sklearn.metrics.f1_score.html

実績と予測をグラフで比較してやると以下のような感じです。

その他、MNISTデータセットを使った学習や、転移学習、自然言語処理についてもチュートリアルがあるようなので、引き続き試したいと思います。

以上になります、最後までお読みいただきありがとうございました。