この本にあるDeepLearningフレームワークDeZeroですが、使い方がPyTorchとよく似ているので、書き換えをいろいろ試してみましたが、最後のMNISTを使ったCNNに関してはもうChatGPTに聞いたらその通り出るんじゃないか?と思ってそれを試してみました。
ほぼ等価なコードがそれはそれはよく似たコードで出てきました。
DeZeroのコード
第4ステージで登場する以下のコードを題材にしてみます。
max_epoch = 5
batch_size = 100
hidden_size = 1000
train_set = dezero.datasets.MNIST(train=True) # datasetを継承したMNIST-dataset
test_set = dezero.datasets.MNIST(train=False)
train_loader = DataLoader(train_set, batch_size) # 上述のままのDataLoader
test_loader = DataLoader(test_set, batch_size, shuffle=False)
#model = MLP((hidden_size, 10))
#optimizer = optimizers.SGD().setup(model)
model = MLP((hidden_size, hidden_size, 10), activation=F.relu) # 活性化はrelu
optimizer = optimizers.Adam().setup(model) # 最適化はAdam
for epoch in range(max_epoch):
sum_loss, sum_acc = 0, 0
for x, t in train_loader: # 学習過程
y = model(x)
loss = F.softmax_cross_entropy(y, t)
acc = F.accuracy(y, t)
model.cleargrads()
loss.backward()
optimizer.update()
sum_loss += float(loss.data) * len(t)
sum_acc += float(acc.data) * len(t)
print('epoch: {}'.format(epoch+1))
print('train loss: {:.4f}, accuracy: {:.4f}'.format(
sum_loss / len(train_set), sum_acc / len(train_set)))
sum_loss, sum_acc = 0, 0
with dezero.no_grad(): # 勾配保持OFF(微分しないから)
for x, t in test_loader: # テスト過程
y = model(x)
loss = F.softmax_cross_entropy(y, t) # Loss出さなきゃいらない
acc = F.accuracy(y, t)
sum_loss += float(loss.data) * len(t)
sum_acc += float(acc.data) * len(t)
print('test loss: {:.4f}, accuracy: {:.4f}'.format(
sum_loss / len(test_set), sum_acc / len(test_set)))
このコードと等価なPyTorchのコードを吐き出させます。プロンプトは
pytorchを使ったサンプルコードが欲しいです。
MNISTを読み込んで全結合で隠れ層が1000のニューラルネットワークで
エポック数5で変数max_epoch、バッチサイズ100で変数batch_size、最適化はAdamを使ってください。
1エポック毎に正答率とLossをダンプし、テストデータでテストも行いその正答率とLossもダンプしてください。
学習データの変数はx、教師データの変数はtにしてください。
Loss関数はsoftmaxの結果をcross entropyで求めてください。
正答率はそれを求める関数をaccuracyという名前で別に作ってください。
モデルのクラスはMLPという名前にしてください。
何回か試行錯誤はしましたが、これを新規のチャットで聞いてみたところ、
import torch
...
# ハイパーパラメータ
max_epoch = 5
batch_size = 100
learning_rate = 0.001
# データのロード
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))])
train_dataset = datasets.MNIST(root="./data", train=True, download=True, transform=transform)
test_dataset = datasets.MNIST(root="./data", train=False, transform=transform)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
# MLPモデル定義
class MLP(nn.Module):
def __init__(self):
super(MLP, self).__init__()
...
# 正答率を計算する関数
def accuracy(pred, target):
...
# モデル、損失関数、最適化手法の設定
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = MLP().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
# 学習ループ
for epoch in range(max_epoch):
model.train()
total_loss, total_acc = 0, 0
for x, t in train_loader:
x, t = x.to(device), t.to(device)
optimizer.zero_grad()
y = model(x)
loss = criterion(y, t)
loss.backward()
optimizer.step()
total_loss += loss.item()
total_acc += accuracy(y, t)
print(f"Epoch {epoch+1}/{max_epoch}, Train Loss: {total_loss/len(train_loader):.4f}, Train Acc: {total_acc/len(train_loader):.4f}")
# テストデータで評価
model.eval()
test_loss, test_acc = 0, 0
with torch.no_grad():
for x, t in test_loader:
x, t = x.to(device), t.to(device)
y = model(x)
test_loss += criterion(y, t).item()
test_acc += accuracy(y, t)
print(f"Test Loss: {test_loss/len(test_loader):.4f}, Test Acc: {test_acc/len(test_loader):.4f}")
並べるために一部省略しています。dezeroで書かれたコードとほぼ同じようなコードが出てきました。それぞれ比較してみます。cudaへの対応記述はいったん無視します。
# DeZero
train_set = dezero.datasets.MNIST(train=True) # datasetを継承したMNIST-dataset
test_set = dezero.datasets.MNIST(train=False)
train_loader = DataLoader(train_set, batch_size) # 上述のままのDataLoader
test_loader = DataLoader(test_set, batch_size, shuffle=False)
# torch
train_dataset = datasets.MNIST(root="./data", train=True, download=True, transform=transform)
test_dataset = datasets.MNIST(root="./data", train=False, transform=transform)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
transformの関数を指定しているか否かだけですね。この口も用意されているので、引数や名前も同じ。
# DeZero
#model = MLP((hidden_size, 10))
#optimizer = optimizers.SGD().setup(model)
model = MLP((hidden_size, hidden_size, 10), activation=F.relu) # 活性化はrelu
optimizer = optimizers.Adam().setup(model) # 最適化はAdam
# torch
# モデル、損失関数、最適化手法の設定
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = MLP().to(device)
criterion = nn.CrossEntropyLoss() # 違い①
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
optimizerへの引数が異なります。dezeroはモデルそのものを食うのに対して、torchはパラメータだけ。とはいえLayer型はparameters()でも返ってくるのでdezero側も同じ記述で行けるような気がします。torchではまたLoss関数も外だしされています。
# DeZero
for epoch in range(max_epoch):
sum_loss, sum_acc = 0, 0
for x, t in train_loader: # 学習過程
y = model(x)
loss = F.softmax_cross_entropy(y, t)
acc = F.accuracy(y, t)
model.cleargrads()
loss.backward()
optimizer.update()
sum_loss += float(loss.data) * len(t)
sum_acc += float(acc.data) * len(t)
print('epoch: {}'.format(epoch+1))
print('train loss: {:.4f}, accuracy: {:.4f}'.format(
sum_loss / len(train_set), sum_acc / len(train_set)))
# torch
# 学習ループ
for epoch in range(max_epoch):
model.train() # 違い①
total_loss, total_acc = 0, 0
for x, t in train_loader:
x, t = x.to(device), t.to(device)
optimizer.zero_grad() # 違い②
y = model(x)
loss = criterion(y, t)
loss.backward()
optimizer.step()
total_loss += loss.item() # 違い③
total_acc += accuracy(y, t)
print(f"Epoch {epoch+1}/{max_epoch}, Train Loss: {total_loss/len(train_loader):.4f}, Train Acc: {total_acc/len(train_loader):.4f}")
for文の形はまるで同じ。
torchのモデルには.train()のモードがあります。理由は後述。それへと明示的に切り替えています。
dezeroがmodelが勾配クリアしているのに対して、torchではoptimizerがその役割を担っています。タイミングもbackwardの前にやるか、後にやるかが違います。がどっちでもいいでしょう。
torchでは、criterionの返り値に.item()というのがあるようで、そこにdataがあるのだと思われます。
# DeZero
sum_loss, sum_acc = 0, 0
with dezero.no_grad(): # 勾配保持OFF(微分しないから)
for x, t in test_loader: # テスト過程
y = model(x)
loss = F.softmax_cross_entropy(y, t) # Loss出さなきゃいらない
acc = F.accuracy(y, t)
sum_loss += float(loss.data) * len(t)
sum_acc += float(acc.data) * len(t)
print('test loss: {:.4f}, accuracy: {:.4f}'.format(
sum_loss / len(test_set), sum_acc / len(test_set)))
# torch
# テストデータで評価
model.eval() # 違い①
test_loss, test_acc = 0, 0
with torch.no_grad():
for x, t in test_loader:
x, t = x.to(device), t.to(device)
y = model(x)
test_loss += criterion(y, t).item()
test_acc += accuracy(y, t)
print(f"Test Loss: {test_loss/len(test_loader):.4f}, Test Acc: {test_acc/len(test_loader):.4f}")
torchでは明確にeval()、評価モードにモデルを切り替えています。先ほどのtrainと同じです。あとはほぼ同じ。
ちなみにevalとtrainの違いは、batch_normやDropoutを学習用、評価用で切り替えているようです。今回batch_normもDropoutも使っていないので、等価でもっとコードを似せられるかもしれません。
しかしよく似たコードです。
続けてGPTに「Chainerで書いて。」とお願いしたらやはり似たようなコードが現れました。全部は載せませんが
# モデル、損失関数、最適化手法の設定
model = MLP()
optimizer = optimizers.Adam(learning_rate)
optimizer.setup(model)
# 学習ループ
for epoch in range(max_epoch):
model.cleargrads()
total_loss, total_acc = 0, 0
for batch in train_iter:
x, t = chainer.dataset.concat_examples(batch)
y = model(x)
loss = F.softmax_cross_entropy(y, t)
loss.backward()
optimizer.update()
total_loss += loss.array
total_acc += accuracy(y, t)
optimizerへの設定方法はdezeroと同じになりました。lossの出し方も全く同じになりました。
x,tの取り出し方は微妙に変わりました。data_loaderの考え方が少し違うようです。また勾配を初期化する記述がありません。update()したらついでに消すような仕様なのかもしれません。
この書籍を読むことで、PyTorchやChainerの実装のイメージがなんとなく理解できた気になります。これまでブラックボックスで使っていたこれらの記述の意味が理解でき、とてもお勉強になりました。
コメント