Fixed AdaCosのすすめ:固定スケールで使おう!

    はじめに

    皆さん、損失関数は何を使ってらっしゃいますか?
    不良品検出、顔認証などopen set recognition problemが絡むタスクでは、ArcFaceがよく使われている印象を受けます。
    でも、ArcFaceにはスケールファクターとマージンのハイパーパラメーターがあり、煩雑です。
    AdaCosの論文に載っている固定スケールファクターを使えば、少なくともスケーリングファクターを調整する必要はなくなります。
    しかし、Fixed AdaCosの精度と処理時間は、どんな感じなんでしょう。
    そこで実際に実装して、試してみました。

    結論

    AdaCosを固定スケールで使うと、同等の処理時間でArcFaceよりも高い精度が出ました。

    Loss FunctionTraining Accuracy (%)Test Accuracy (%)Test Loss
    ArcFace98.6185.22N/A
    AdaCos99.4290.02N/A
    CrossEntropy94.5789.440.022136613415579192
    • dataset
    • 16 directories, 2602 files

    背景

    ハイパーパラメーター

    経験と勘がすべてを支配する「ハイパーパラメーター指定」。これは、機械学習において、避けて通りたい作業です。
    ハイパーパラメーターの調整は、勘を外せばモデルの訓練が不安定になり、精度にも悪影響がでます。
    自動調整するライブラリもあるにはありますが、時間もかかりますし、パラメーター範囲の指定も必要です。

    ハイパーパラメーターフリー、かつ高性能ときいて

    AdaCosはハイパーパラメーターフリーで、訓練過程で自動的にスケールパラメーターを調整できるだけでなく、高い顔認識精度を達成することが可能」、とききました。

    そんな美味しい話があるんですね。

    そこで早速、AdaCosを実装してtrainを回してみました。
    しかし、なかなか精度が上がりません。どうしてだ。

    そんな時は、論文を読むしかありません。
    下のグラフを見てください。

    このグラフを見ると、AdaCosにおいて、同じクラスのコサイン類似度は大きく、逆に異なるクラスのコサイン類似度がグラフ中もっとも小さいことがわかります。

    注目すべきは横軸です。
    ArcFaceよりAdaCosが優れた値を示すのは、2万epoch以降です
    いや、横軸がepochとは書いてません。ミニバッチかもしれない。それでもエグい数字です。

    これだったらArcFaceの方がお手軽ではないかと思いました。

    というわけで解散!…でもいいんですけど、AdaCosの論文には「固定スケーリングファクター」なるものも紹介されています。それを試してみてからでも遅くはありません。

    疑問

    • 固定スケールAdaCosFixed AdaCos)の、イテレーションにおける縦軸がどこにも書いてない。
    • 固定スケールファクターならば、ArcFaceと同じepoch数でいけるんじゃないか?

    Fixed AdaCos

    論文より。
    $$\tilde{s}_f = \sqrt{2} \cdot \log(C – 1)$$

    クラス数が $16$ だとすると、 $約3.829$ 。

    実装

    2パターンを用意しました。

    • loss_test.py
    • dataset
      • MNIST
      • 手書き数字の10クラス分類問題。やさしい。
    • model
      • 独自実装
      • 10 epoch
    • 損失関数比較
      • ArcFace: regularizers.CenterInvariantRegularizer()で正規化
      • Simple_ArcFace: ArcFaceのみ
      • Fixed AdaCoss: スケーリングファクターを固定
      • Cross Entropy: クロスエントロピーロス
    • loss_test_face_recognition.py
    • dataset
      • 16 directories, 2602 face image files
      • 16クラスの顔認証問題。比較的難しい。
    • model
      • EfficientNetV2-b0
      • 10 epoch
    • 損失関数比較
      • ArcFace: regularizers.RegularFaceRegularizer()で正規化
      • Simple_ArcFace: ArcFaceのみ
      • Fixed AdaCoss: スケーリングファクターを固定
      • Cross Entropy: クロスエントロピーロス
    Script NameDataset DescriptionModelEpochsLoss Function Description
    loss_test.pyMNIST (10-class, Easy)Custom10ArcFace with regularizers.CenterInvariantRegularizer(), Simple_ArcFace, Fixed AdaCos, Cross Entropy
    loss_test_face_recognition.py16 dirs, 2602 face images (16-class, Hard)EfficientNetV2-b010ArcFace with regularizers.RegularFaceRegularizer(), Simple_ArcFace, Fixed AdaCos, Cross Entropy

    loss_test.py

    import math
    
    import numpy as np
    import torch
    import torch.nn as nn
    import torch.nn.functional as F
    import torch.optim as optim
    from pytorch_metric_learning import losses, regularizers, samplers
    from sklearn.neighbors import KNeighborsClassifier
    from torch import nn
    from torch.utils.data import DataLoader
    from torchvision import datasets, transforms
    
    # ハイパーパラメータの設定
    epoch = 10
    arcface_s = 64.0
    arcface_m = 28.6
    num_classes = 10
    batch_size = 128
    embedding_size = 128
    lr = 0.01
    
    class SimpleCNN(nn.Module):
        def __init__(self):
            super(SimpleCNN, self).__init__()
            self.conv1 = nn.Conv2d(1, 32, kernel_size=3)  # 畳み込み層1
            self.conv2 = nn.Conv2d(32, 64, kernel_size=3)  # 畳み込み層2
            self.fc1 = nn.Linear(64 * 12 * 12, 128)  # 全結合層1
            self.fc2 = nn.Linear(128, embedding_size)  # 全結合層2(出力層)
    
        def forward(self, x):
            x = self.conv1(x)
            x = F.relu(x)
            x = self.conv2(x)
            x = F.relu(x)
            x = nn.functional.max_pool2d(x, 2)
            x = nn.functional.dropout(x, 0.25, training=self.training)
            x = x.view(-1, 64 * 12 * 12)
            x = self.fc1(x)
            x = nn.functional.relu(x)
            x = nn.functional.dropout(x, 0.5, training=self.training)
            x = self.fc2(x)
            return x
    
    class AdaCosLoss(nn.Module):
        def __init__(self, num_features, num_classes=num_classes, m=arcface_m):
            super(AdaCosLoss, self).__init__()
            self.num_classes = num_classes  # クラス数
            self.s = math.sqrt(2) * math.log(num_classes - 1)  # 固定値のスケーリングファクター
            self.m = m  # マージンmを設定
            self.ce_loss = nn.CrossEntropyLoss()  # 交差エントロピー損失
            device = 'cuda' if torch.cuda.is_available() else 'cpu'  # デバイスを確認
            self.W = nn.Parameter(torch.FloatTensor(num_classes, num_features).to(device))  # self.Wを適切なデバイスに配置
            nn.init.xavier_normal_(self.W)  # 重みの初期化
    
        def forward(self, logits, labels):
            # logitsをL2正規化
            logits = F.normalize(logits, p=2, dim=-1)  # L2正規化
            # 重みベクトルをL2正規化
            W = F.normalize(self.W, p=2, dim=-1)  # L2正規化
            # コサイン類似度の計算
            cos_theta = logits @ W.T  # 内積でコサイン類似度を計算
            # マージンmを適用
            phi = cos_theta + self.m  # マージンmを適用
            # print(f"AdaCos cos_theta: {cos_theta}, phi: {phi}")  # デバッグ情報を出力
            return self.ce_loss(self.s * phi, labels)  # スケーリングファクターとマージンを適用して損失を計算
    
    class Simple_ArcFaceLoss(nn.Module):
        def __init__(self, num_features, num_classes=num_classes, s=arcface_s, m=arcface_m):
            super(Simple_ArcFaceLoss, self).__init__()
            self.s = s
            self.m = m
            device = 'cuda' if torch.cuda.is_available() else 'cpu'  # デバイスを確認
            self.W = nn.Parameter(torch.FloatTensor(num_classes, num_features).to(device))  # self.Wを適切なデバイスに配置
            nn.init.xavier_normal_(self.W)
    
        def forward(self, logits, labels):
            logits = F.normalize(logits, p=2, dim=-1)
            W = F.normalize(self.W, p=2, dim=-1)
            cos_theta = logits @ W.T
            device = 'cuda' if torch.cuda.is_available() else 'cpu'  # デバイスを確認
            one_hot = torch.zeros(cos_theta.size(), device=device)  # one_hotを適切なデバイスに配置
            labels = labels.to(device)  # labelsも適切なデバイスに配置
            one_hot.scatter_(1, labels.view(-1, 1).long(), 1)
            output = cos_theta * 1.0
            output -= one_hot * self.m
            # print(f"ArcFace cos_theta: {cos_theta}, phi: {phi}")  # デバッグ情報を出力
            return F.cross_entropy(self.s * output, labels)
    
    def train(model, loss_fn, train_loader, test_loader, epochs=epoch):
        model = model.to('cuda')  # モデルをGPUに移動
        optimizer = optim.Adam(model.parameters(), lr=lr)
        for epoch in range(epochs):
            model.train()
            for batch_idx, (data, target) in enumerate(train_loader):
                data, target = data.to('cuda'), target.to('cuda')  # データをGPUに移動
                optimizer.zero_grad()
                output = model(data)
                loss = loss_fn(output, target)
                loss.backward()
                optimizer.step()
    
    def evaluate_with_cross_entropy(model, train_loader, test_loader, loss_fn):
        model.eval()  # 評価モード
        model = model.to('cuda')
        # 訓練データでの性能評価
        correct_train = 0
        with torch.no_grad():
            for data, target in train_loader:
                data, target = data.to('cuda'), target.to('cuda')
                output = model(data)
                pred = output.argmax(dim=1, keepdim=True)
                correct_train += pred.eq(target.view_as(pred)).sum().item()
        print(f"Training accuracy: {100. * correct_train / len(train_loader.dataset)}%")
        # テストデータでの性能評価
        test_loss = 0
        correct = 0
        with torch.no_grad():
            for data, target in test_loader:
                data, target = data.to('cuda'), target.to('cuda')  # データをGPUに移動
                output = model(data)
                test_loss += loss_fn(output, target).item()
                pred = output.argmax(dim=1, keepdim=True)
                correct += pred.eq(target.view_as(pred)).sum().item()
        test_loss /= len(test_loader.dataset)
        print(f"Test loss: {test_loss}, Test accuracy: {100. * correct / len(test_loader.dataset)}%")
    
    def evaluate_with_margin(loss_name, model, train_loader, test_loader):
        model.eval()  # 評価モード
        model = model.to('cuda')
        # 訓練データで特徴量を抽出
        train_features = []
        train_labels = []
        for data, target in train_loader:
            data, target = data.to('cuda'), target.to('cuda')
            with torch.no_grad():
                output = model(data)
            train_features.append(output.cpu().numpy())
            train_labels.append(target.cpu().numpy())
        train_features = np.vstack(train_features)
        train_labels = np.concatenate(train_labels)
        # k-NNモデルの訓練
        knn = KNeighborsClassifier(n_neighbors=1)
        knn.fit(train_features, train_labels)
        # 訓練データでの評価
        correct_train = 0
        total_train = 0
        for data, target in train_loader:
            data, target = data.to('cuda'), target.to('cuda')
            with torch.no_grad():
                output = model(data)
            output = output.cpu().numpy()
            target = target.cpu().numpy()
            pred_train = knn.predict(output)
            correct_train += np.sum(pred_train == target)
            total_train += target.shape[0]
        train_accuracy = 100. * correct_train / total_train
        print(f"{loss_name}: Training accuracy: {train_accuracy}%")
        # テストデータでの評価
        correct = 0
        total = 0
        for data, target in test_loader:
            data, target = data.to('cuda'), target.to('cuda')
            with torch.no_grad():
                output = model(data)
            output = output.cpu().numpy()
            target = target.cpu().numpy()
            pred = knn.predict(output)
            correct += np.sum(pred == target)
            total += target.shape[0]
        test_accuracy = 100. * correct / total
        print(f"{loss_name}: Test accuracy: {test_accuracy}%")
    
    transform = transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize((0.5,), (0.5,))  # MNISTの平均と標準偏差
    ])
    
    train_loader = DataLoader(datasets.MNIST('assets/MNIST', train=True, download=True, transform=transform), batch_size=batch_size, shuffle=True)
    test_loader = DataLoader(datasets.MNIST('assets/MNIST', train=False, download=True, transform=transform), batch_size=batch_size)
    
    # ArcFaceでの訓練
    print("Training with ArcFace")
    model = SimpleCNN()
    R = regularizers.CenterInvariantRegularizer()
    arcface_loss = losses.ArcFaceLoss(num_classes=num_classes, embedding_size=embedding_size, margin=arcface_m, scale=arcface_s, weight_regularizer=R)  # ArcFaceの損失関数
    train(model, arcface_loss, train_loader, test_loader, epochs=epoch)
    # ArcFaceでの評価
    evaluate_with_margin('arcface', model, train_loader, test_loader)
    
    # Simple_ArcFaceでの訓練
    print("Training with Simple_ArcFace")
    model = SimpleCNN()
    train(model, Simple_ArcFaceLoss(num_features=embedding_size, s=arcface_s, m=arcface_m), train_loader, test_loader, epochs=epoch)
    # Simple_ArcFaceでの評価
    evaluate_with_margin('Simple_arcface', model, train_loader, test_loader)
    
    # AdaCosでの訓練
    print("Training with AdaCos")
    model = SimpleCNN()
    train(model, AdaCosLoss(num_features=embedding_size), train_loader, test_loader, epochs=epoch)
    # ArcFaceでの評価
    evaluate_with_margin('adacos', model, train_loader, test_loader)
    
    # CrossEntropyでの訓練
    print("Training with CrossEntropy")
    model = SimpleCNN()
    cross_entropy_loss = nn.CrossEntropyLoss()
    train(model, cross_entropy_loss, train_loader, test_loader, epochs=epoch)
    # CrossEntropyでの評価
    evaluate_with_cross_entropy(model, train_loader, test_loader, cross_entropy_loss)
    
    """
    Training with ArcFace
    arcface: Training accuracy: 99.90166666666667%
    arcface: Test accuracy: 97.36%
    Training with Simple_ArcFace
    Simple_arcface: Training accuracy: 98.71833333333333%
    Simple_arcface: Test accuracy: 95.78%
    Training with AdaCos
    adacos: Training accuracy: 99.94833333333334%
    adacos: Test accuracy: 98.09%
    Training with CrossEntropy
    Training accuracy: 98.31833333333333%
    Test loss: 0.0004521963403734844, Test accuracy: 98.17%
    
    | Loss Function  | Training Accuracy (%) | Test Accuracy (%) | Test Loss               |
    |----------------|-----------------------|-------------------|-------------------------|
    | ArcFace        | 99.90                 | 97.36             | N/A                     |
    | Simple_ArcFace | 98.72                 | 95.78             | N/A                     |
    | AdaCos         | 99.95                 | 98.09             | N/A                     |
    | CrossEntropy   | 98.32                 | 98.17             | 0.0004521963403734844   |
    
    """

    出力結果

    Loss FunctionTraining Accuracy (%)Test Accuracy (%)Test Loss
    ArcFace99.9097.36N/A
    Simple_ArcFace98.7295.78N/A
    AdaCos99.9598.09N/A
    CrossEntropy98.3298.170.0004521963403734844

    簡単な分類問題を、単純なネットワークで解決した場合、CrossEntropy Lossでも良い結果を残しています。
    しかし、Fixed AdaCosも良いスコアです。

    loss_test_face_recognition.py

    import math
    
    import matplotlib.pyplot as plt
    import numpy as np
    import timm
    import torch
    import torch.nn as nn
    import torch.nn.functional as F
    import torch.optim as optim
    from pytorch_metric_learning import losses, regularizers, samplers
    from sklearn.neighbors import KNeighborsClassifier
    from torch import nn
    from torch.utils.data import DataLoader, Subset, random_split
    from torchvision import datasets, transforms
    from torchvision.datasets import ImageFolder
    from tqdm import tqdm
    
    # ハイパーパラメータの設定
    epoch = 10
    num_classes = 16
    arcface_s = 64.0
    arcface_m = 28.6
    batch_size = 16
    lr = 0.01
    resolution = 224
    embedding_size = 512
    
    # EfficientNetを読み込む
    class EfficientNet(nn.Module):
        def __init__(self):
            super(EfficientNet, self).__init__()
            # self.model = timm.create_model("efficientnet_b0", pretrained=True)  # EfficientNet
            self.model = timm.create_model("efficientnetv2_rw_s", pretrained=True)  # EfficientNetV2
            num_features = self.model.classifier.in_features  # `_fc`属性を使用する
            # modelの最終層にembedding_size次元のembedder層を追加
            self.model.classifier = nn.Linear(num_features, embedding_size)
    
        def forward(self, x):
            x = self.model(x)
            return x
    
    def train(model, loss_fn, train_loader, test_loader, epochs=epoch):
        model = model.to('cuda')  # モデルをGPUに移動
        optimizer = optim.Adam(model.parameters(), lr=lr)
        for epoch in tqdm(range(epochs), desc="Epochs"):
            model.train()
            correct_train = 0  # 追加; 正解数を初期化
            for batch_idx, (data, target) in enumerate(train_loader):
                data, target = data.to('cuda'), target.to('cuda')  # データをGPUに移動
                optimizer.zero_grad()
                output = model(data)
                loss = loss_fn(output, target)
                loss.backward()
                optimizer.step()
    
    class CustomSubset(Subset):
        def __init__(self, subset, transform=None):
            super(CustomSubset, self).__init__(subset.dataset, subset.indices)
            self.transform = transform
    
        def __getitem__(self, idx):
            sample, target = self.dataset[self.indices[idx]]
            if self.transform:
                sample = self.transform(sample)
            return sample, target
    
    # 画像の平均値と標準偏差
    mean_value = [0.485, 0.456, 0.406]
    std_value = [0.229, 0.224, 0.225]
    
    # 画像の前処理設定(訓練用)
    train_transform=transforms.Compose([
            transforms.Resize((resolution, resolution)),  # 解像度にリサイズ
            transforms.RandomHorizontalFlip(p=0.5),  # 水平方向にランダムに反転
            transforms.ToTensor(),  # テンソルに変換
            transforms.Normalize(mean=mean_value, std=std_value)  # 正規化
        ])
    
    # 画像の前処理設定(テスト用)
    test_transform=transforms.Compose([
            transforms.Resize((resolution, resolution)),  # 解像度にリサイズ
            transforms.ToTensor(),  # テンソルに変換
            transforms.Normalize(mean=mean_value, std=std_value)  # 正規化
        ])
    
    # DataLoaderの設定
    # 元のデータセットを読み込む
    dataset = ImageFolder(root='assets/face_data')
    # データセットのサイズを取得
    dataset_size = len(dataset)
    # 訓練データとテストデータの割合を設定(8:2)
    train_size = int(0.8 * dataset_size)
    test_size = dataset_size - train_size
    # データセットを訓練用とテスト用に分割
    train_subset, test_subset = random_split(dataset, [train_size, test_size])
    # transformを適用
    train_dataset = CustomSubset(train_subset, transform=train_transform)
    test_dataset = CustomSubset(test_subset, transform=test_transform)
    # DataLoaderの設定
    train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)  # 訓練用
    test_loader = DataLoader(test_dataset, batch_size=batch_size)  # テスト用
    
    class AdaCosLoss(nn.Module):
        def __init__(self, num_features, num_classes=num_classes, m=arcface_m):
            super(AdaCosLoss, self).__init__()
            self.num_classes = num_classes  # クラス数
            self.s = math.sqrt(2) * math.log(num_classes - 1)  # 固定値のスケーリングファクター
            self.m = m  # マージンmを設定
            self.ce_loss = nn.CrossEntropyLoss()  # 交差エントロピー損失
            device = 'cuda' if torch.cuda.is_available() else 'cpu'  # デバイスを確認
            self.W = nn.Parameter(torch.FloatTensor(num_classes, num_features).to(device))  # self.Wを適切なデバイスに配置
            nn.init.xavier_normal_(self.W)  # 重みの初期化
    
        def forward(self, logits, labels):
            # logitsをL2正規化
            logits = F.normalize(logits, p=2, dim=-1)  # L2正規化
            # 重みベクトルをL2正規化
            W = F.normalize(self.W, p=2, dim=-1)  # L2正規化
            # コサイン類似度の計算
            cos_theta = logits @ W.T  # 内積でコサイン類似度を計算
            # マージンmを適用
            phi = cos_theta + self.m  # マージンmを適用
            # print(f"AdaCos cos_theta: {cos_theta}, phi: {phi}")  # デバッグ情報を出力
            return self.ce_loss(self.s * phi, labels)  # スケーリングファクターとマージンを適用して損失を計算
    
    # # DEBUG
    # # ラベルとクラス名の対応関係を表示
    # print("Label to class mapping:", dataset.class_to_idx)
    # # DataLoaderからいくつかのサンプルを取得
    # dataiter = iter(train_loader)
    # images, labels = next(dataiter)
    # # 画像とラベルを表示
    # for i in range(4):
    #     plt.subplot(2,2,i+1)
    #     img = images[i].permute(1, 2, 0).numpy()  # C,H,W -> H,W,Cに変換
    #     img = img * std_value + mean_value  # 正規化を元に戻す
    #     img = img.clip(0, 1)  # 0~1の範囲にクリッピング
    #     plt.imshow(img)
    #     plt.title(f'Label: {labels[i]}')
    # plt.show()
    
    def evaluate_with_cross_entropy(model, train_loader, test_loader, loss_fn):
        model.eval()  # 評価モード
        model = model.to('cuda')
        # 訓練データでの性能評価
        correct_train = 0
        with torch.no_grad():
            for data, target in train_loader:
                data, target = data.to('cuda'), target.to('cuda')
                output = model(data)
                pred = output.argmax(dim=1, keepdim=True)
                correct_train += pred.eq(target.view_as(pred)).sum().item()
        print(f"Training accuracy: {100. * correct_train / len(train_loader.dataset)}%")
        # テストデータでの性能評価
        test_loss = 0
        correct = 0
        with torch.no_grad():
            for data, target in test_loader:
                data, target = data.to('cuda'), target.to('cuda')  # データをGPUに移動
                output = model(data)
                test_loss += loss_fn(output, target).item()
                pred = output.argmax(dim=1, keepdim=True)
                correct += pred.eq(target.view_as(pred)).sum().item()
        test_loss /= len(test_loader.dataset)
        print(f"Test loss: {test_loss}, Test accuracy: {100. * correct / len(test_loader.dataset)}%")
    
    def evaluate_with_margin(loss_name, model, train_loader, test_loader):
        model.eval()  # 評価モード
        model = model.to('cuda')
        # 訓練データで特徴量を抽出
        train_features = []
        train_labels = []
        for data, target in train_loader:
            data, target = data.to('cuda'), target.to('cuda')
            with torch.no_grad():
                output = model(data)
            train_features.append(output.cpu().numpy())
            train_labels.append(target.cpu().numpy())
        train_features = np.vstack(train_features)
        train_labels = np.concatenate(train_labels)
        # k-NNモデルの訓練
        knn = KNeighborsClassifier(n_neighbors=1)
        knn.fit(train_features, train_labels)
        # 訓練データでの評価
        correct_train = 0
        total_train = 0
        for data, target in train_loader:
            data, target = data.to('cuda'), target.to('cuda')
            with torch.no_grad():
                output = model(data)
            output = output.cpu().numpy()
            target = target.cpu().numpy()
            pred_train = knn.predict(output)
            correct_train += np.sum(pred_train == target)
            total_train += target.shape[0]
        train_accuracy = 100. * correct_train / total_train
        print(f"{loss_name}: Training accuracy: {train_accuracy}%")
        # テストデータでの評価
        correct = 0
        total = 0
        for data, target in test_loader:
            data, target = data.to('cuda'), target.to('cuda')
            with torch.no_grad():
                output = model(data)
            output = output.cpu().numpy()
            target = target.cpu().numpy()
            pred = knn.predict(output)
            correct += np.sum(pred == target)
            total += target.shape[0]
        test_accuracy = 100. * correct / total
        print(f"{loss_name}: Test accuracy: {test_accuracy}%")
    
    # ArcFaceでの訓練と評価
    print("Training with ArcFace")
    model = EfficientNet()
    R = regularizers.RegularFaceRegularizer()
    arcface_loss = losses.ArcFaceLoss(num_classes=num_classes, embedding_size=embedding_size, margin=arcface_m, scale=arcface_s, weight_regularizer=R)
    train(model, arcface_loss, train_loader, test_loader, epochs=epoch)
    # ArcFaceでの評価
    evaluate_with_margin('arcface', model, train_loader, test_loader)
    
    # AdaCosでの訓練と評価
    print("Training with AdaCos")
    model = EfficientNet()
    adacos_loss = AdaCosLoss(num_features=embedding_size, num_classes=num_classes, m=arcface_m)
    train(model, adacos_loss, train_loader, test_loader, epochs=epoch)
    # ArcFaceでの評価
    evaluate_with_margin('adacos', model, train_loader, test_loader)
    
    # CrossEntropyでの訓練
    print("Training with CrossEntropy")
    model = EfficientNet()
    cross_entropy_loss = nn.CrossEntropyLoss()
    train(model, cross_entropy_loss, train_loader, test_loader, epochs=epoch)
    # CrossEntropyでの評価
    evaluate_with_cross_entropy(model, train_loader, test_loader, cross_entropy_loss)
    
    """
    Training with ArcFace
    arcface: Training accuracy: 98.60643921191735%
    arcface: Test accuracy: 85.22072936660268%
    Training with AdaCos
    adacos: Training accuracy: 99.42335415665545%
    adacos: Test accuracy: 90.01919385796545%
    Training with CrossEntropy
    Training accuracy: 94.56991830850552%
    Test loss: 0.022136613415579192, Test accuracy: 89.44337811900192%
    
    | Loss Function  | Training Accuracy (%) | Test Accuracy (%) | Test Loss               |
    |----------------|-----------------------|-------------------|-------------------------|
    | ArcFace        | 98.61                 | 85.22             | N/A                     |
    | AdaCos         | 99.42                 | 90.02             | N/A                     |
    | CrossEntropy   | 94.57                 | 89.44             | 0.022136613415579192    |
    
    """

    出力結果

    Loss FunctionTraining Accuracy (%)Test Accuracy (%)Test Loss
    ArcFace98.6185.22N/A
    AdaCos99.4290.02N/A
    CrossEntropy94.5789.440.022136613415579192

    open set recognition problemの場合、損失の正則化を行ったArcFaceよりも、単純な実装のFixed AdaCosのスコアが勝りました。

    まとめ

    AdaCosの固定スケーリングファクターは使えます。
    今回は10 epoch程度の小規模な実験でしたが、実務にも役立つ知見を得られました。

    以上です。ありがとうございました。

    参考文献

    AdaCos: Adaptively Scaling Cosine Logits for Effectively Learning Deep Face Representations