Python
DeepLearning
PyTorch

(Part2)PyTorchに学習済みのTorch7モデル(.t7)を読み込んで,画像特徴量を抽出する

本記事について

前回の記事の続きです。前回の記事では,ネットワークにランダムなテンソルを入力しましたが,今回は実際の画像を入力します。

具体的に何をするか

  • 画像の前処理。具体的には,正規化とリサイズ。
  • 画像のテンソル化➔Variable化
  • 画像をニューラルネットワークに入力する!←これが個人的にさしあたりの目標であった

画像からの特徴量抽出

下記のリンクを参考に,画像の前処理(正規化➔リサイズ➔Tensor化➔Variable化)を行いました。

PyTorch quick start: Classifying an image
http://blog.outcome.io/pytorch-quick-start-classifying-an-image/

必要なパッケージをimportします:

from PIL import Image
from torchvision import models, transforms
from torch.autograd import Variable

正規化と前処理

画像の正規化には,ネットワークが学習に用いた画像の各チャンネルのピクセル輝度値の平均と標準偏差を用います。
余談ですが,

  • $k$番目のチャンネルの$(i,j)$のピクセルを$x_{ijk}$
  • $k$番目のチャンネルの正規化後の$(i,j)$のピクセルを$z_{ijk}$
  • $k$番目のチャンネルのピクセル輝度値の平均を$\mu_k$
  • $k$番目のチャンネルのピクセル輝度値の標準偏差を$\sigma_k$

とすると,

$z_{ij}=\frac{x_{ijk}-\mu_k}{\sigma_k}$

となります。さて,正規化のために平均と標準偏差を知りたいわけですが,私の場合(つまり,'stylenet.t7'の場合)は,つぎのようにして確認できました:

input

from torch.utils.serialization import load_lua

model = load_lua("stylenet.t7")
print(model)

output

{'mean': [0.5657177752729754, 0.5381838567195789, 0.4972228365504561],
 'std': [0.29023818639817184, 0.2874722565279285, 0.2933830104791508],
 'stylenet': nn.Sequential {
   [input -> (0) -> (1) -> (2) -> (3) -> (4) -> (5) -> (6) -> (7) -> (8) -> (9) -> (10) -> (11) -> (12) -> (13) -> (14) -> (15) -> (16) -> (17) -> (18) -> (19) -> (20) -> (21) -> (22) -> (23) -> (24) -> output]
   (0): nn.SpatialConvolution(3 -> 64, 3x3, 1, 1, 1, 1)
   (1): nn.ReLU
   (2): nn.SpatialConvolution(64 -> 64, 3x3, 1, 1, 1, 1)
   (3): nn.ReLU
   (4): nn.Dropout(0.2500)
   (5): nn.SpatialMaxPooling(4x4, 4, 4)
   (6): nn.SpatialBatchNormalization
   (7): nn.SpatialConvolution(64 -> 128, 3x3, 1, 1, 1, 1)
   (8): nn.ReLU
   (9): nn.SpatialConvolution(128 -> 128, 3x3, 1, 1, 1, 1)
   (10): nn.ReLU
   (11): nn.Dropout(0.2500)
   (12): nn.SpatialMaxPooling(4x4, 4, 4)
   (13): nn.SpatialBatchNormalization
   (14): nn.SpatialConvolution(128 -> 256, 3x3, 1, 1, 1, 1)
   (15): nn.ReLU
   (16): nn.SpatialConvolution(256 -> 256, 3x3, 1, 1, 1, 1)
   (17): nn.ReLU
   (18): nn.Dropout(0.2500)
   (19): nn.SpatialMaxPooling(4x4, 4, 4)
   (20): nn.SpatialBatchNormalization
   (21): nn.SpatialConvolution(256 -> 128, 1x1)
   (22): nn.ReLU
   (23): nn.Reshape(3072)
   (24): nn.Linear(3072 -> 128)
 }}

※結局load_lua()使うんかいという感じですが(前回の記事で使わないと明言した),これ以外の方法で平均・標準偏差にアクセスするPyTorch流の方法が間違いなくあると思いますので,時間があったら調べます。

load_lua()でモデルを読み込むと,モデルがdict型みたいなものに入れられるみたいです。この辞書の最初の方にmeanstdが見えてますね。このキーにアクセスして平均と標準偏差をとってこれますが,一応参考にした文献にならって次のようにコピペして貼り付けます。

# 正規化用の変数
normalize = transforms.Normalize(
   mean=[0.5657177752729754, 0.5381838567195789, 0.4972228365504561],
   std=[0.29023818639817184, 0.2874722565279285, 0.2933830104791508]
)

前処理

つぎに,前処理用のtransforms.Composeの値を設定します。

# 前処理
preprocess = transforms.Compose([
   transforms.Scale(256),
   #transforms.CenterCrop(224),  # 画像を正方形で切り取る
   transforms.ToTensor(),
   normalize
])

ここで,transforms.Resize(256)がちょっとややこしいのですが,公式ドキュメントでは次のようになっています:

class torchvision.transforms.Resize(size, interpolation=2)

  • sizeが非intなら,もとの画像サイズが(h,w)に対し(h*size,w*size)になる。
  • sizeがintなら,短い方の辺の長さがsizeにリサイズされ,長い方の辺は、もとの画像の比を維持するように変更する。
  • size=[h,w]のようにすれば、縦横の長さが(h,w)になるようにリサイズする。

私の場合,ネットワークに入力する画像の縦横ピクセルは仕様上(384×256)となっており,また,入力する画像は3:2の縦横比になっているので,Resize(256)とすれば画像は(384×256)のピクセルサイズになります。

画像をPILで読み込んで,試しに出力してみます.PILに関しては,こちらに簡潔にまとめてあってわかりやすいです.

input

img_PIL = Image.open("fashion_style.jpg")
img_PIL

output
11005.jpg

ネットワークに画像を入力して特徴量をとっていきたいのですが,ネットワークにはVariableしか入力できません。そこで,つぎのように画像のTensor化➔Variable化の処理をします。

img_tensor = preprocess(img_PIL)
img_tensor.unsqueeze_(0)

img_tensorは(3,384,256)のテンソルです。つまり,3チャンネル,ピクセル縦横(384×256)です。.unsqueeze_(0)は,バッチ用の次元を追加します。その結果,(1,3,384,256)のテンソルが得られます。

つぎに,テンソル化➔Variable化をして,ついにネットワークに画像を入力してみます。

input

img_variable = Variable(img_tensor)   # Variable化
stylenet.train(False)   # 特徴抽出器として使う
output = stylenet.forward(img_variable)
output

output

Variable containing:

Columns 0 to 9 
 0.5769 -0.8995  1.2549  1.0596 -3.3031  1.8836 -0.7131 -0.2804  0.1950  0.2517
.
.
.
Columns 120 to 127 
 0.9533  0.9206  0.6384 -2.3381  1.6441  1.5416  0.3826 -0.0569
[torch.FloatTensor of size 1x128]

上記でstylenet.train(False)とするのは,ネットワークを特徴抽出器として使うためです。これをしないと,学習時と同じようにDropoutレイヤーが働く(つまり,層間のノード同士の結合を確率的に決める)ので,同じ画像でも毎回異なった特徴量が出力されることになります。