国際人工知能オリンピックのサンプル問題を解いてみた
国際人工知能オリンピック(IOAI)が今年第1回の開催を迎えるとのことで、公式HPに紹介されているサンプル問題を解いてみます。
IOAIについて
今年から始まる、科学オリンピックのうちの1つです。今年はブルガリアのブルガスで開催されるようです。
肝心のコンテストの内容ですが、科学ラウンドと実践ラウンドの2つがあります。科学ラウンドは機械学習、深層学習に関するipynbが提供され、それを解きます。実践ラウンドはChatGPTを始めとするGUIアプリケーションを活用した科学的な問題について考察します。
いわゆるKaggle的なコーディング要素が求められるのは前者の科学ラウンドのようです。
競技AIというとKaggleの印象が強いですが、IOAIはどんな事を問われるのか気になるところです。
公式HPには3問掲載されていました。
- NLPタスク(言語モデルの訓練、論文の再実装)
- NLPタスク(単語埋込のバイアス除去)
- 画像タスク(Adversarial Attack) <- これをやる
今回扱う問題は次のcolabリンクに掲載されています。(正解はなさそう)
Task1 CNNモデルの作成
要件
- CIFAR-10データセットを読み込む
- ResNet-18モデルを使ってCIFAR-10訓練セットを訓練する
- CIFAR-10評価セットでモデルの性能を評価する
- 評価セットでAcc 80%を超えること
- モデルのアーキテクチャを変更しないこと
このコードだけ与えられます。
from torchvision.models import resnet18
net = resnet18(num_classes=10).cuda()
予備実験でAugmentationとschedulerなしで訓練してみましたが、性能が70%未満だったのでちゃんと工夫しないといけなさそうです。
3時間くらい奮闘したんですが、全然80%を超えてくれないので意外と難しいみたいです。何で?
以下の条件で訓練しました。
- Data Augmentation
- クロップ、フリップ、回転、アフィン変換、色Jitter、正規化
- Adam (lr: 1e-1) + OneCycleLRスケジューラ
- バッチサイズ: 512, epoch: 100
- NLLLoss
80%超えた(๑•̀ㅂ•́)و✧
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 98/98 [00:19<00:00, 5.16it/s]
Epoch 100, Loss: 0.36403972488276815
Accuracy on the test set: 82.61%
コード
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms, models
from torch.utils.data import DataLoader
from torch.functional import F
from tqdm import tqdm
stats = ((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
# Define transformations for the CIFAR-10 dataset
train_transform = transforms.Compose([
transforms.RandomCrop(32, padding=4),
transforms.RandomHorizontalFlip(), # FLips the image w.r.t horizontal axis
transforms.RandomRotation((-7,7)), #Rotates the image to a specified angel
transforms.RandomAffine(0, shear=10, scale=(0.8,1.2)), #Performs actions like zooms, change shear angles.
transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2), # Set the color params
transforms.ToTensor(),
transforms.Normalize(*stats)
])
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize(*stats)
])
# Load CIFAR-10 dataset
train_dataset = datasets.CIFAR10(root='./data', train=True, download=True, transform=train_transform)
test_dataset = datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
train_loader = DataLoader(train_dataset, batch_size=512, shuffle=True, num_workers=4)
test_loader = DataLoader(test_dataset, batch_size=512, shuffle=False, num_workers=4)
net = resnet18(num_classes=10).cuda()
# Define loss function and optimizer
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(), lr=1e-1)
epochs = 100
sched = torch.optim.lr_scheduler.OneCycleLR(optimizer, 1e-1, epochs=epochs,
steps_per_epoch=len(train_loader))
def train_model(model, sched, train_loader, criterion, optimizer, num_epochs=10):
model.train()
for epoch in range(num_epochs):
running_loss = 0.0
for images, labels in tqdm(train_loader):
images, labels = images.cuda(), labels.cuda()
outputs = model(images)
loss = F.nll_loss(F.log_softmax(outputs), labels)
loss.backward()
optimizer.step()
optimizer.zero_grad()
running_loss += loss.item()
print(f'Epoch {epoch+1}, Loss: {running_loss/len(train_loader)}')
sched.step()
evaluate_model(model, test_loader)
def evaluate_model(model, test_loader):
model.eval()
correct = 0
total = 0
with torch.no_grad():
for images, labels in test_loader:
images, labels = images.cuda(), labels.cuda()
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print(f'Accuracy on the test set: {100 * correct / total}%')
# Train and evaluate the model
evaluate_model(net, test_loader)
train_model(net, sched, train_loader, criterion, optimizer, num_epochs=epochs)
Task2 Adversarial Exampleの作成
低ければ低いほど良いようです。
colabで貼られている文献を読む限り、The Fast Gradient Sign Method (FGSM)を試せば良いことがわかりました。FGSMは最もシンプルなAdversarial Attackの方法で、勾配を上昇する方向にサンプルにノイズを付与します。
紹介されていたFGSM(eps=0.1)をそのまま適用させてみたところ、ほとんどのサンプルが間違えるようになりました。
Before
After
結果は次のようになりました。乱択よりも悪い結果になっていてすごい。
そもそも正規化したデータに対して適用させてよかったんだっけ? epsilonの取り方正しいか不安になってきました。間違ってるかもしれないです...... 申し訳ない......
=== eps=0.25 ===
Accuracy on the test set: 7.06%
=== eps=1.0 ===
Accuracy on the test set: 6.33%
=== eps=1.5 ===
Accuracy on the test set: 7.02%
コード
def fgsm(model, X, y, epsilon):
""" Construct FGSM adversarial examples on the examples X"""
delta = torch.zeros_like(X, requires_grad=True)
loss = nn.CrossEntropyLoss()(model(X + delta), y)
loss.backward()
return epsilon * delta.grad.detach().sign()
# Function to evaluate the model
def evaluate_adversarial_model(model, test_loader, eps=0.01):
model.eval()
correct = 0
total = 0
# with torch.no_grad():
for images, labels in test_loader:
images, labels = images.cuda(), labels.cuda()
# print(images.shape)
delta = fgsm(net, images, labels, eps)
outputs = model(images + delta)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print(f'=== {eps=} ===')
print(f'Accuracy on the test set: {100 * correct / total}%')
evaluate_adversarial_model(net, test_loader, eps=0.25)
evaluate_adversarial_model(net, test_loader, eps=1.0)
evaluate_adversarial_model(net, test_loader, eps=1.5)
Task3 応用したAdversarial Exampleの作成
一様な大きさでノイズを付与する従来のやり方を応用し、画像中心を小さいノイズ、外側を大きなノイズを付与するタスクです。
要件
-
(画像中心16x16ピクセル)\epsilon = \frac{2}{225} -
(その他)\epsilon = \frac{8}{255} -
は\epsilon 距離で定義される。l\infty
行列で係数マスクを作って符号と掛け合わせます。
epsilon = 8/255
epsilon_mask = torch.ones((3, 32, 32)) * epsilon
epsilon_mask[:, 8:24, 8:24] /= 4
plt.imshow(epsilon_mask[0])
82%->61%になりました。
Accuracy on the test set: 61.38%
コード
# Function to evaluate the model
def evaluate_adversarial_custom_model(model, test_loader, eps_mask):
model.eval()
correct = 0
total = 0
# with torch.no_grad():
for images, labels in test_loader:
images, labels = images.cuda(), labels.cuda()
# print(images.shape)
delta = fgsm(net, images, labels, 1) * eps_mask
outputs = model(images + delta)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
# print(f'=== {eps=} ===')
print(f'Accuracy on the test set: {100 * correct / total}%')
evaluate_adversarial_custom_model(net, test_loader, eps_mask=epsilon_mask.cuda())
Task4 カスタムモデルに対する攻撃
WIP
おわりに
気になるところがあれば気軽に連絡してください。epsilonの取り方は全く自信がありません。
そして、IOAIは4/17まで応募を受け付けているらしいです。
Discussion