LoginSignup
17
14

More than 3 years have passed since last update.

irisで試す相互情報量の最大化による教師なし学習手法IIC

Last updated at Posted at 2020-02-25

概要

実験結果のファイルをGitHubに保存しています

概要

未知ラベルの画像にノイズをのっけて、相互情報量を最大化するように学習することで画像のクラスタリングを行えるとのこと。
つまり、画像に対して事前のアノテーション(ラベリング)作業不要でクラスタリングが可能
詳細はarxiv読んでください。

MNISTはいろんなひとが実装しているので、
画像ではなく、もっとスモールなirisデータセットを使って教師なしを実装してみる。

損失関数

論文中のコードは一部間違っているはず

\sum^{C}_{c=1} \sum^{C}_{c'=1} P_{cc'} \ln{\frac{P_{cc'}}{P_{c}P_{c'}}}
= \sum^{C}_{c=1} \sum^{C}_{c'=1} P_{cc'} (\ln{P_{cc'} - \ln{P_{c}} - \ln{P_{c'}}})
import torch

class IIC(torch.nn.Module):
    def __init__(self):
      super(IIC, self).__init__()

    def IIC(self, z, zt, C):
      P = (z.unsqueeze(2) * zt.unsqueeze(1)).sum(dim=0)
      P = ((P + P.t()) / 2) / P.sum()
      EPS = torch.finfo(P.dtype).eps
      P[(P < EPS).data] = EPS
      Pi = P.sum(dim=1).view(C, 1).expand(C, C)
      Pj = P.sum(dim=0).view(1, C).expand(C, C)

      # 論文中の式は計算間違っているのでは?
      # (P * (log(Pi) + log(Pj) - log(P))).sum() (元計算)

      # 損失関数なので、最大化->最小化に切り替えるために負にする
      loss = (-1.0 * P * (torch.log(P) - torch.log(Pi) - torch.log(Pj))).sum()
      return loss

    def forward(self, Z, ZT, C=3):
      # headと呼んでいる処理のためにこのような計算になる
      # headで学習が早くなるのだが、なぜ早くなるのかは不勉強。。コメント待っています。
      return torch.sum(torch.stack([
                self.IIC(z, zt, C) for z, zt in zip(Z, ZT)
              ]))

データ

定番のirisを使う。

from torch.utils.data import Dataset
from sklearn.datasets import load_iris
import pandas as pd

class Iris_forIIC(Dataset):
    def __init__(self):
      # irisデータセットをロード
      iris = load_iris()
      self.df = pd.DataFrame(iris.data).assign(label=iris.target)

    def __len__(self):
        return len(self.df.index)

    def __getitem__(self, idx):
      # idx行目を取り出して、データとラベルにわける
      row = self.df.iloc[idx].values
      label = row[4]

      # データはTensorのfloat型じゃないといけない
      data_target = torch.from_numpy(row[:4]).float()
      # IICのキモ!
      # 今回は雑に元データにノイズをのっける。
      data_other = torch.from_numpy(row[:4]).float() * torch.normal(torch.tensor(1).float(), torch.tensor(0.1).float())

      return data_target, data_other

モデル

モデルがかなり苦労した。
通常の教師あり学習ならオーバーキルなモデルを使わないと学習が進まない。

import torch.nn as nn
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        # irisデータにあわせて4次元入力の、3次元出力。
        self.nn = nn.Sequential(
            nn.Linear(4, 100),
            # バッチ正規化を利用しないと学習が進まない
            nn.BatchNorm1d(100),
            nn.ReLU(),
            nn.Linear(100, 50),
            nn.BatchNorm1d(50),
            nn.ReLU(),
            nn.Linear(50, 3),
            nn.BatchNorm1d(3),
            nn.ReLU())

      # 普通の教師ありなら下記のようなモデルで十分
      # self.nn = nn.Sequential(
      #     nn.Linear(4, 100),
      #     nn.ReLU(),
      #     nn.Linear(100, 50),
      #     nn.ReLU(),
      #     nn.Linear(50, 3))

    def forward(self, x):
        # headと呼んでいる処理。不勉強で何しているかわからない。
        # 学習がとても早くなる。
        return [F.softmax(self.nn(x), dim=1) for _ in range(5)]
        # return F.softmax(self.nn(x), dim=1)

学習

import torch.optim as optim
import torch.nn.functional as F
from sklearn.metrics import confusion_matrix

# cpu/gpuを指定
device = torch.device('cpu')
# モデルを生成
model = Net()
model = model.to(device)
# 損失関数
criterion = IIC()
# 最適化関数
optimizer = optim.Adam(model.parameters(), lr=0.0001)

# Dataloader
kwargs = {'num_workers': 1, 'pin_memory': True}
train_set = Iris_forIIC()
train_loader = torch.utils.data.DataLoader(train_set, batch_size=len(train_set), shuffle=True, **kwargs)

EPOCH = 250
for idx in range(EPOCH):
  model.train()
  for X, XT in train_loader:
    # 元データ
    X = X.to(device)
    # ノイズ付与データ
    XT = XT.to(device)
    # XとXTについてそれぞれ分類
    Z = model(X)
    ZT = model(XT)
    # 損失=相互情報量を計算
    loss = criterion(Z, ZT)

    # 勾配計算・学習
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

# 学習済みモデルを評価
model.eval()
# irisデータを読み込み
iris = load_iris()
df_iris = pd.DataFrame(iris.data).assign(label=iris.target)
X = torch.Tensor(df_iris.drop('label', axis=1).values).to(device)
Y_predict = model.nn(X).argmax(1).cpu()
Y_actual = df_iris['label'].values
# 混同行列を表示
display(confusion_matrix(Y_actual, Y_predict))

結果

もともとのラベルと数が異なるので対角に並ぶわけではないが、
ちゃんと分類されていることがわかる。

[[ 0, 49,  1],
 [ 5,  0, 45],
 [50,  0,  0]]
17
14
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
14