Adagradによる最適化

Adagradによるパラメータ毎の更新過程

Adagradはよく使われる最適化のテクニックの一つで有り、おそらくSGD(Stochastic Gradient Descent)が最も使われている最適化手法かと思います。
一般的にニューラルネットワークには予測精度を向上させるため、回帰の損失を最小化するために、ネットワークで用いる重みの更新を行う必要があります。
その際に基本的にニューラルネットワークの更新規則はSGDと同じように微分値を使って最適化を行うと思いますが、その微分値に対してどれくらい大きく重みを更新するかについては学習率というものが使われ、0.001や0.01など小さい値が与えられ、少しずつ更新されていきます。
そのために学習率の選び方は効率的な学習において重要なハイパーパラメータになります。
SGDについては最初に学習率を決めると全てのパラメータに対して同じ学習率が適用されます。同じ学習率を適用するということは例えば大きく学習を進めたいパラメータとそれほどもう学習しなくて良いパラメータがあったときに、重みによっては最適な値から離れてしまったり、もう少し大きく学習を進めたいのになかなか進まないケースもあると思います。
そこでAdagradでは重みによってlossが既に小さくなっているような箇所の更新についてはそれほど大きく更新せず、lossが大きく勾配が大きくなるような箇所については大きな更新がされるように調整を行うようなアルゴリズムになっています。実装としてはGという変数を用意し、Gに勾配の二乗を毎エポック加えて、学習率をGで割ることで学習率の自動調整を行います。Adagradはいくつかの自然言語処理タスクで上手くいくことが報告されており、ほとんど現れないような単語の学習はSGDでは難しいですが、Adagradを用いることで大きく更新する必要のある低頻出語を他の語に比べて大きく更新したい場合に役に立つと言われています。

Required Libraries

  • numpy 1.21.1
  • scikit-learn 0.18.1
In [1]:
import numpy as np
from sklearn.preprocessing import LabelBinarizer
from sklearn.model_selection import train_test_split
import renom as rm
from renom.cuda.cuda import set_cuda_active
set_cuda_active(False)

Make label data

使用しているデータセットについては以下のようになっています。

ISOLET Data Set, Ron Cole and Mark Fanty. Department of Computer Science and Engineering,
Oregon Graduate Institute, Beaverton, OR 97006.
In [2]:
filename = "./isolet1+2+3+4.data"
labels = []
X = []
y = []

def make_label_idx(filename):
    labels = []
    for line in open(filename, "r"):
        line = line.rstrip()
        label = line.split(",")[-1]
        labels.append(label)
    labels = list(set(labels))
    return list(set(labels))

labels = make_label_idx(filename)
labels = sorted(labels, key=lambda d:int(d.replace(".","").replace(" ","")))

Load data from the file for training and prediction

In [3]:
for line in open(filename,"r"):
    line = line.rstrip()
    label = labels.index(line.split(",")[-1])
    features = list(map(float,line.split(",")[:-1]))
    X.append(features)
    y.append(label)

X = np.array(X)
y = np.array(y)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
print("X_train:{}, y_train:{}, X_test:{}, y_test:{}".format(X_train.shape, y_train.shape,
                                                            X_test.shape, y_test.shape))
lb = LabelBinarizer().fit(y)
labels_train = lb.transform(y_train)
labels_test = lb.transform(y_test)
print("labels_train:{}, labels_test:{}".format(labels_train.shape, labels_test.shape))
X_train:(4990, 617), y_train:(4990,), X_test:(1248, 617), y_test:(1248,)
labels_train:(4990, 26), labels_test:(1248, 26)

Network definition and initialize parameters

In [4]:
output_size = len(labels)
sequential = rm.Sequential([
    rm.Dense(100),
    rm.Relu(),
    rm.Dense(50),
    rm.Relu(),
    rm.Dense(output_size)
])

Learning loop

In [5]:
epoch = 20
batch_size = 128
N = len(X_train)
optimizer = rm.Adagrad(lr=0.01)
for i in range(epoch):
    perm = np.random.permutation(N)
    loss = 0
    for j in range(0, N//batch_size):
        train_batch = X_train[perm[j*batch_size : (j+1)*batch_size]]
        response_batch = labels_train[perm[j*batch_size : (j+1)*batch_size]]
        with sequential.train():
            l = rm.softmax_cross_entropy(sequential(train_batch), response_batch)
        grad = l.grad()
        grad.update(optimizer)
        loss += l.as_ndarray()
    train_loss = loss / (N//batch_size)
    test_loss = rm.softmax_cross_entropy(sequential(X_test), labels_test).as_ndarray()
    print("epoch:{:03d}, train_loss:{:.4f}, test_loss:{:.4f}".format(i, float(train_loss), float(test_loss)))
epoch:000, train_loss:1.7550, test_loss:0.8006
epoch:001, train_loss:0.6106, test_loss:0.5314
epoch:002, train_loss:0.4123, test_loss:0.3825
epoch:003, train_loss:0.3136, test_loss:0.3244
epoch:004, train_loss:0.2602, test_loss:0.3071
epoch:005, train_loss:0.2278, test_loss:0.2655
epoch:006, train_loss:0.1989, test_loss:0.2614
epoch:007, train_loss:0.1815, test_loss:0.2311
epoch:008, train_loss:0.1644, test_loss:0.2115
epoch:009, train_loss:0.1493, test_loss:0.2066
epoch:010, train_loss:0.1370, test_loss:0.2012
epoch:011, train_loss:0.1279, test_loss:0.2083
epoch:012, train_loss:0.1197, test_loss:0.1837
epoch:013, train_loss:0.1111, test_loss:0.1837
epoch:014, train_loss:0.1044, test_loss:0.1827
epoch:015, train_loss:0.0984, test_loss:0.1723
epoch:016, train_loss:0.0945, test_loss:0.1710
epoch:017, train_loss:0.0885, test_loss:0.1696
epoch:018, train_loss:0.0849, test_loss:0.1619
epoch:019, train_loss:0.0810, test_loss:0.1600