普段画像データを使って機械学習をすることがあまりないんですが、色々あって最近ちょくちょく触っています。
そんなわけで、画像認識で使うニューラルネットワークの勉強をしていて、最近では画像認識のベースラインとして使用されることもあるResNetについて勉強してみたので、そのメモです。
ResNet is なに?
2015年にMicrosoftから提案されたImage Classsificationに関する非常に有名なCNNの手法です。
特徴
特徴をざっくり言うと、
- 従来(当時)から、CNNのネットワークの層を増加させることでモデルの精度が向上する傾向は確認されていた
- しかし、ネットワークの層を増やすにつれて学習が不安定になり、層を増やしすぎると性能が逆に低下することもわかっていた
- そこで、従来のCNNのモデルとは異なり、Residual(残差)に関して学習を行うようにネットワークの構造を変形することで、層を増やしても学習が安定化させた
- すると、従来では考えられないくらいの層を持つネットワークを構成しても学習が安定し、精度が飛躍的に向上した
って感じです。
6年も前の有名論文だと、色んな方々が論文の解説記事を書いてくださっているので、論文の詳細な解説は下記の記事などをご参照ください。
動かしてみる
さて、本題です。ざっくり理屈はわかったところで、実際に使ってみます。 とは言っても、ResNetの層が深いと、当然学習時間もとんでもないことになるので、今回は学習済みのモデルを使用して、そちらをfine tuningして使うときのメモを残していきます。
データセット
今回はThe Oxford-IIIT Pet Datasetを使いたいと思います。
wget https://www.robots.ox.ac.uk/~vgg/data/pets/data/images.tar.gz tar zxvf images.tar.gz
環境
今回は簡単のため、あとGPUを使いたいため、Google Colabを使用したいと思います。
コードの確認
ちょっとだけ、備忘のためにコードの説明を付けておきます。
Datasetクラス
MyDatasetクラスを作ります。 lenメソッドでデータの数を、getitemのメソッドでインデックスに応じたデータを返すように記述します。 その他の事前の処理なんかはコンストラクタに記述しています。
今回は、画像のサイズがまちまちだったので、transforms.Composeの中で、224×224のサイズに強引に押し込めてます。 (サイズは適当に決めました)
class MyDataSet(Dataset): def __init__(self): l = glob.glob('images/*.jpg') self.train_df = pd.DataFrame() self.images = [] self.labels = [] self.le = preprocessing.LabelEncoder() for path in l: self.images.append(path) self.labels.append(re.split('[/_.]', path)[1]) self.le.fit(self.labels) self.labels_id = self.le.transform(self.labels) self.transform = transforms.Compose([transforms.Resize((224, 224)), transforms.ToTensor()]) def __len__(self): return len(self.images) def __getitem__(self, idx): image = Image.open(self.images[idx]) image = image.convert('RGB') label = self.labels_id[idx] return self.transform(image), int(label)
データセットを学習・テストで分離
余り覚えてなかったんですが、Datasetを作ってから、データの長さを取得して、学習:テストの割合を決めて別々のデータローダーに突っ込んでます。
dataset = MyDataSet() n_samples = len(dataset) train_size = int(len(dataset) * 0.7) val_size = n_samples - train_size train_dataset, val_dataset = torch.utils.data.random_split(dataset, [train_size, val_size]) train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=32, shuffle=True) val_loader = torch.utils.data.DataLoader(val_dataset, batch_size=32, shuffle=True)
ネットワークの調整
37クラス分類になるはずなので、最終層だけちょっといじってます。
model = resnet34(pretrained=True) model.fc = nn.Linear(512,37)
結果
最終的に分類の結果としてはこんな感じになりました。
precision recall f1-score support 0 0.86 0.80 0.83 64 1 0.78 0.77 0.78 52 2 0.77 0.74 0.75 54 3 0.86 0.96 0.91 70 4 0.90 0.79 0.84 57 5 0.73 0.94 0.82 48 6 0.82 0.79 0.81 63 7 0.84 0.82 0.83 60 8 0.49 0.85 0.63 48 9 0.81 0.81 0.81 67 10 0.89 0.79 0.84 62 11 0.84 0.84 0.84 56 12 0.89 0.54 0.67 114 13 0.98 0.58 0.73 72 14 0.52 0.91 0.67 47 15 0.87 0.71 0.78 58 16 0.78 0.85 0.82 60 17 0.82 0.86 0.84 117 18 0.82 0.89 0.85 71 19 0.83 0.93 0.88 58 20 0.85 0.84 0.84 67 21 0.93 0.87 0.90 60 22 0.97 1.00 0.98 62 23 0.98 0.77 0.86 64 24 0.88 0.65 0.75 66 25 0.75 0.96 0.84 46 26 0.81 0.93 0.87 60 27 0.93 0.89 0.91 61 28 0.75 0.85 0.80 62 29 0.89 0.88 0.89 66 30 0.78 0.95 0.86 61 31 0.89 0.93 0.91 54 32 0.78 0.68 0.73 66 33 0.80 0.92 0.85 61 34 0.98 0.75 0.85 63 accuracy 0.82 2217 macro avg 0.83 0.83 0.82 2217 weighted avg 0.84 0.82 0.82 2217
accuracy 82%なんで、そこそこ画像認識はできてそうですね。 軽くfine tuningしただけでもこれくらい行っちゃうもんなんですかね。
37クラスのはずなのに35種類しかテストデータに含まれてないのは多分何かがおかしいんですが、 力尽きたので今回はここまで。覚えてたらいつか直します。
1/31 追記
データの中身を調べたら、35種類しかデータがはいってなかったです。 そのため、上記の37クラスにしているところは、正しくは35クラスが正しく、出力されている35クラスの分類が正しいことになりそうです。
書いたコード
google colabで動かせば動く気がします。多分。
参考文献
この記事を書くにあたり、下記の記事を参考にさせていただきました。
感想
非常に基本的なResNetすらぱぱっと使えなかったので、調べてみた記録でした。まだまだ勉強不足ですね。 まあ、普段から触っているわけでは無いですし、ちゃんと勉強してこうやって動かせるようになったんで良しとしましょう。