始めに
この記事の結論は次の通りです。
- MATLABで分類用CNNを使うとき、入力画像サイズを合わせたくなければactivations関数を使いましょう
(2024/05/08追記)R2024a時点でactivations関数,classify関数は非推奨となりました。代わりにminibatchpredict関数を使ってください。また、ネットワークの読込みにはimagePretrainedNetwork関数を使ってください。minibatchpredict関数の場合も、画像のサイズを合わせずに使えます。使い方は下のコードに併記します。
この記事の動機
MATLABを使うときの話ですが、GoogleNetなどの事前学習済みの分類用CNNは入力画像サイズが固定(正方形)になっています。なので普通は手持ちの画像を使うとき、リサイズが必要になります。たいていの画像は横長か縦長なので、リサイズするとアスペクト比が変わってしまってなんか気持ちが悪いのは私だけでしょうか。テクニカルにも高解像度画像をリサイズすることでせっかくの細部がつぶれてしまって、分類結果に影響するんじゃないかとか、いろいろ考えてしまいます。それじゃということで、ネットワークの定義に合わせたサイズに手持ち画像を細切れにしてそれぞれ分類するという方法もありますけど、なんとも手間だしエレガントではないです。
そこでこの記事ではリサイズ抜きで分類できる小技を紹介したいと思います。意外と知らない人多いんじゃないかと思って。
まずは状況確認
Googlenetを例に挙げて進めます。通常通りネットワークを読み込み、入力画像のサイズをCNNの定義に合わせない場合と合わせた場合での、通常通りの分類コマンドの挙動の違いを確認します。
net = googlenet;
% R2024a以降は次のコマンド: [net,classes] = imagePretrainedNetwork('googlenet');
img = imread('peppers.png');
imshow(img)
size(img) % 画像のサイズ
ans = 1x3
384 512 3
imSize = net.Layers(1).InputSize % 入力レイヤに定義されている画像サイズの確認
imSize = 1x3
224 224 3
[pred, prob] = classify(net, img);
エラー: DAGNetwork/calculatePredict>predictBatch (line 151)
不正な入力サイズです。入力イメージのサイズは [224 224 3] でなければなりません。
エラー: DAGNetwork/calculatePredict (line 17)
Y = predictBatch( ...
エラー: DAGNetwork/classify (line 134)
scores = this.calculatePredict( ...
入力画像のサイズが、ネットワークに定義されたものと一致していないため、エラーになってしまいますね。
それでは次にリサイズした画像を入力して正常に分類できることを確認します。
imgResized = imresize(img, imSize(1:2));
[pred, prob] = classify(net, imgResized);
pred
pred =
bell pepper
max(prob)
ans = 0.9551
リサイズすることで正常に分類ができました。
入力画像サイズが異なっていてもactivations関数を使えば分類できる
実は、入力画像サイズがネットワークの入力レイヤに定義されたものと異なっていても、GoogleNet、ResNetをはじめとする比較的新しいネットワークのように、全結合層の前にglobal poolingレイヤがある場合、画像サイズは関係なくなるため、特別な処理なしに通常通り分類が可能です。この場合は、activations関数を使います。
念のため確認しましょう。GoogleNetのネットワークの最後の方は下記のようになっています。
全結合層の前にglobal average poolingレイヤがあるので、理屈としては入力画像のサイズは関係なく使えます。この場合はactivations関数を使うとエラーを吐き出さずに画像をネットワークに通してくれます。なぜそんな仕様になっているかは知りません…。
probs = activations(net, img, net.OutputNames{:});
% R2024a以降は次のコマンド: probs = minibatchpredict(net,img); predClass = scores2label(probs, classes);
classes = net.Layers(end).Classes;
[prob, ind] = max(probs);
if prob >= 0.5
predClass = classes(ind);
fprintf('Predicted Class: %s, probability: %.2f', predClass, prob);
else
disp('No class is predicted!!!')
end
Predicted Class: bell pepper, probability: 0.65
元のサイズにのままのせいか、確率(と言っていいのかわかりませんが)は0.95から0.65まで落ちていますが、分類はできています。
おわり
この記事ではactivations関数を使うことで、入力画像サイズがネットワークの定義と合っていなくても分類できますってことを紹介しました。もちろん、高解像度過ぎると学習した際の状況と変わりすぎるので誤分類が増えると思います。その場合はやはりリサイズは必要になるでしょう。ただし、アスペクト比は変えなくてもこの方法ならOKです。
次の記事ではこの続きで別のやり方をやってみたので、そちらを紹介したいと思います。この方法ではAlexNetとかVGGのように、全結合層の前にglobal poolingが無くても使えます。
なんか変なこと言ってたら教えてくださると幸いです。