Im2Col を用いた畳み込み計算を MATLAB で解説

  
新井仁之(早稲田大学)
    
公開日 2024年11月18日
Ver 1.4.1 2024年1月2日
  
  
CNN では多くの画像のたたみ込み積を計算する必要がある.深層学習では im2col を使って行列の掛け算にする計算が良く知られている.本ノートではこれを前回のチュートリアルで述べた MATLABで作成した Im2Col ([5])を用いて解説する.ここでは便宜上この方式によるたたみ込みを Cnv2 で表す.なお本ノートの MATLAB コードは,[3] のpython コード([2], [4] も参考)に基づいたものである.最後にMATLABの conv2 との比較をする.
clear

1.入力画像例を設定

ここでは次の簡単な入力画像例で説明していく.
一般に画像ファイルは
「チャネル数」x「画像高さ」x「画像幅」
の 3 次元配列でできている.ここでは Im2Col への入力が必要なため
入力型 = 「画像数」x「チャネル数」x「画像高さ」x「画像幅」
とするが,画像数 = 1 で議論を進める.
また,議論を単純にすることと,MATLAB の conv2 との比較をするため,チャネル数 1 と設定して話を進める.
NumImages = 1; % これは常に 1 に設定.Col2Im に入力するためのダミー次元.
ChannelImages = 1; % 入力画像数を指定.
 
% 画像サイズを指定.今回は正方形の画像を設定
Image_height = 6;
Image_width =Image_height;
 
% 画像データの枠組み
Image = zeros(NumImages,ChannelImages,Image_height,Image_width);
 
% 画像例の作成
A1 = 1:Image_height * Image_width;
A1 = reshape(A1,[Image_height Image_width])';
Image(1,1,:,:) = A1;
 
% ChannelImages が 2 以上の場合は,
% Image(1,i,:,:) (i=2,...,ChanelImages) も設定する.
 
% 画像データの表示
for i = 1:NumImages
for j = 1: ChannelImages
sq = squeeze(Image(i,j,:,:));
disp(['Image(',num2str(i),',',num2str(j),',:,:) =']);
disp(sq);
end
end
Image(1,1,:,:) =
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
% Image は後で変形するので,オリジナルを保存.後で使う.
Image_org = Image;

2.フィルタとバイアスの設定

フィルタの型は
「NumFilters」x「ChannelFilters 」x「 Filter_height」x「Filter_width」
の 4 次元配列で設定しておく.ニューラルネットでは通常 ChannelFilters = ChannelImages である.
次の例を考える.ここでは NumFilters = 1 で設定する.
NumFilters = 1;
ChannelFilters = ChannelImages;
Filter_height =3;
Filter_width = 3;
 
F_1 = [0 1 1; 1 0 2; 0 1 0]; % テストフィルタ
% F_1 = randi(3,3); %ランダムに設定
% F_1 = [1 0 0; 0 0 0 ;0 0 0]; % 単位インパルス
 
% Filter の枠組みの設定
Filter = zeros(NumFilters,ChannelFilters,Filter_height,Filter_width);
 
% フィルタの設定
for i = 1: NumFilters
for j = 1:ChannelFilters
Filter(i,j,:,:)=F_1;
end
end
 
% Bias の設定
Bias = 0; % Bias は 0 としておく.
 
% Filter の表示
for i = 1: NumFilters
for j = 1:ChannelFilters
sq = squeeze(Filter(i,j,:,:));
disp(['Filter(',num2str(i),',',num2str(j),',:,:) ='])
disp(sq)
end
end
Filter(1,1,:,:) =
0 1 1 1 0 2 0 1 0
%Filter は後で変形するので,オリジナルを保存.後で使う.
Filter_org = Filter;

3.Cnv2 の解説 (Im2Col を使った畳み込みの計算)

Stride と padding の設定をする.Padding の設定によって,MATLAB の次の型に対応する.
Padding = 0 -> 'valid' 型
Padding = 1 -> 'same' 型
Stride = 1;
 
% valid 型にする場合
Padding = 0;
 
% same 型にする場合
% Padding = 1;
Padding による画像拡張
Image = padarray(Image,[0 0 Padding Padding],0,'both');
Image データのパラメータ読み出し
[NumImages, ChannelImages,Image_height, Image_width] = size(Image)
NumImages = 1
ChannelImages = 1
Image_height = 6
Image_width = 6
Filter データのパラメータ読み出し
[NumFilters,ChannelFilters,Filter_height,Filter_width] = size(Filter)
NumFilters = 1
ChannelFilters = 1
Filter_height = 3
Filter_width = 3
たたみ込み演算による出力サイズ
Output_height = fix((Image_height - Filter_height + 2*Padding)/Stride)+1
Output_height = 4
Output_width = fix((Image_width - Filter_width + 2*Padding)/Stride)+1
Output_width = 4
たたみ込みを画像とフィルタの行列積で計算するため,画像データを Im2Col で並べ替える.Im2Col については前回の説明を参照.
ただし Im2Col は Python 仕様の配列方式に設定しておく(MATLAB仕様の配列方式,Pyhton仕様の配列方式については [5] を参照).
Col1 = Im2Col(Image,[Filter_height,Filter_width],Stride,Padding);
sz = size(Col1);
disp(['size(Col1) = [',num2str(sz),']'])
size(Col1) = [16 9]
for i=1:1
disp('Col1 =');
disp(Col1);
end
Col1 =
1 7 13 2 8 14 3 9 15 7 13 19 8 14 20 9 15 21 13 19 25 14 20 26 15 21 27 19 25 31 20 26 32 21 27 33 2 8 14 3 9 15 4 10 16 8 14 20 9 15 21 10 16 22 14 20 26 15 21 27 16 22 28 20 26 32 21 27 33 22 28 34 3 9 15 4 10 16 5 11 17 9 15 21 10 16 22 11 17 23 15 21 27 16 22 28 17 23 29 21 27 33 22 28 34 23 29 35 4 10 16 5 11 17 6 12 18 10 16 22 11 17 23 12 18 24 16 22 28 17 23 29 18 24 30 22 28 34 23 29 35 24 30 36
行列積にするためにフィルタ・データを並び変える
% MATLAB 仕様の配列の場合
Filter = permute(Filter,[3 4 2 1]);
%% Python 仕様の配列の場合
%Filter = permute(Filter,[4 3 2 1]);
 
Filter = reshape(Filter,[ChannelFilters*Filter_height*Filter_width NumFilters])';
sz = size(Filter);
disp(['size(Filter) = [', num2str(sz),']'])
size(Filter) = [1 9]
行列積をするため,これを転置する.
Filter = permute(Filter,[2 1]);
disp(Filter)
0 1 0 1 0 1 1 2 0
画像データとこのフィルタおよびバイアスによるアフィン積をとる.これは affine_product として関数ファイルを作っておく(下記参照).今,Bias = 0 としているので,通常の畳み込み演算
Conv_out = affine_product(Col1,Filter,Bias);
sz = size(Conv_out);
disp(['size(Filter) = [', num2str(sz),']'])
size(Filter) = [16 1]
Conv_out = permute(Conv_out,[2 1]);
disp(Conv_out)
44 80 116 152 50 86 122 158 56 92 128 164 62 98 134 170
Conv_out = reshape(Conv_out,[NumFilters Output_width Output_height NumImages]);
size(Conv_out)
ans = 1×3
1 4 4
%% Im2Col をMATLABの配列方法に設定した場合.
Conv_out = permute(Conv_out,[4 1 2 3]);
%% Im2Col を Python 仕様の配列に設定した場合
% Conv_out = permute(Conv_out,[4 1 3 2]);
 
size(Conv_out)
ans = 1×4
1 1 4 4
for j=1:NumFilters
for i=1:1
sq = squeeze(Conv_out(i,j,:,:));
disp(['Conv_out(' num2str(i) ',' num2str(j) ',:,:) の表示'])
disp(sq)
end
end
Conv_out(1,1,:,:) の表示
44 50 56 62 80 86 92 98 116 122 128 134 152 158 164 170
これで Cnv2 が完成.

4.以上のコードをまとめて関数ファイル Cnv2 を作る

function Z = Cnv2(Image,Filter,Bias,Stride,Padding)
 
% Stride = 1, Padding = 0 -> 'valid'型畳み込み
% Stride = 1, Padding = 1 -> 'same'型畳み込み
 
%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 入力タイプ
% Image = Image(Number of Images, Channel, Image_height, Image_width)
% Filter = Filter(Number of Filters, Channel, Filter_height, Filter_width)
% Bias = number
% Stride = number
% Padding = 0 または 1
%
% 出力 Z のサイズ:
% Number of Images x Number of Filters x output_hight x output_width
% ここで
% Output_hight = fix((Image_height-Block_height+2*Padding)/Stride)+1
% Output_width = fix((Image_width-Block_width+2*Padding)/Stride)+1
%
% 本ノートの参考文献 [2], [3], [4] の Pythonプログラムにもとづく.
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
[NumImage, ~, Image_height, Image_width] = size(Image);
[NumFilter,Channel,Filter_height,Filter_width] = size(Filter);
Output_height = fix((Image_height -Filter_height +2*Padding)/Stride)+1;
Output_width = fix((Image_width -Filter_width +2*Padding)/Stride)+1;
Image = Im2Col(Image,[Filter_height,Filter_width],Stride,Padding);
 
% Im2Col が Python 仕様の配列の場合
%Filter = permute(Filter,[4 3 2 1]);
 
%% Im2Col が MATLAB 仕様の配列方式の場合
Filter = permute(Filter,[3 4 2 1]);
 
Filter = reshape(Filter,[Channel*Filter_height*Filter_width NumFilter])';
Filter = permute(Filter,[2 1]);
 
%Z = X*W+b;
Z = affine_product(Image,Filter,Bias);
 
Z = permute(Z,[2 1]);
Z = reshape(Z,[NumFilter Output_width Output_height NumImage]);
 
%% Python仕様の配列方式の場合
% Z = permute(Z,[4 1 3 2]);
 
%% MATLAB仕様の配列方式の場合
Z = permute(Z,[4 1 2 3]);
 
% フィルタのサイズが偶数の場合は切り取る.
if Padding == 1
if mod(Filter_height,2) == 0
Z = Z(:,:,2:Output_height,:);
end
if mod(Filter_width,2) == 0
Z = Z(:,:,:,2:Output_width);
end
end
end
function C = affine_product(A,B,b)
 
% 画像 A とファイルタ B,そしてバイアス = 実数とのアフィン積
 
C=A*B;
k=size(C,1);
ba=repmat(b,[k,1]);
C=C+ba;
end

5.Cnv2 を使って計算してみる

早速 Cnv2 を使った計算で検算をする.
Conv_out = Cnv2(Image_org,Filter_org,Bias,Stride,Padding);
size(Conv_out)
ans = 1×4
1 1 4 4
for j=1:NumFilters
for i=1:1
sq = squeeze(Conv_out(i,j,:,:));
disp(['Conv_out(' num2str(i) ',' num2str(j) ',:,:) の表示'])
disp(sq)
end
end
Conv_out(1,1,:,:) の表示
44 50 56 62 80 86 92 98 116 122 128 134 152 158 164 170

6.MATLAB の conv2 との比較

MATLAB に conv2 がある.それとここでの相関積(深層学習分野ではこれを畳み込みと読んでいる)との比較をする.
conv2 への入力画像データは,最初に設定した画像データと同じである.
A(:,:) = Image_org(1,1,:,:);
disp(A)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
フィルタも既に定めたフィルタと同じものとする.
F = zeros(Filter_height,Filter_width);
F(:,:) = Filter_org(1,1,:,:);
disp(F)
0 1 1 1 0 2 0 1 0
まず,MATLAB の畳み込みの valid を計算する.MATLAB の畳み込みの定義は,ここでの畳み込み(じつは相関積)とは異なる定義なので,結果も異なる.
Z_M = conv2(A,F,"valid");
Z_M(:,:)
ans = 4×4
52 58 64 70 88 94 100 106 124 130 136 142 160 166 172 178
しかし,MATLAB の conv2 でフィルタを 180度回転させると,相関積となる.すなわち
F_rot = rot90(F,2);
Z_M2 = conv2(A,F_rot,"valid");
Z_M2(:,:)
ans = 4×4
44 50 56 62 80 86 92 98 116 122 128 134 152 158 164 170
Z = Cnv2(Image_org, Filter_org, Bias, Stride, Padding);
squeeze(Z(1,1,:,:))
ans = 4×4
44 50 56 62 80 86 92 98 116 122 128 134 152 158 164 170
もう少し大きな1枚の画像データに対する conv2 との計算時間を比較する.
M = 10000;
A = rand(M,M);
TestImage = zeros(1,1,M,M);
TestImage(1,1,:,:) = A;
MATLAB の conv2
tic
Z_M3 = conv2(A,F_rot,"valid");
toc
経過時間は 0.155348 秒です。
Cnv2   - Ver 1.3 の訂正 -
tic
Z = Cnv2(TestImage, Filter_org, Bias, Stride, Padding);
toc
経過時間は 9.537640 秒です。
% conv2 と Cnv2 の結果の誤差
Y2(:,:) = Z(1,1,:,:);
max(max(abs(Z_M3-Y2)))
ans = 1.7764e-15

Appendix 1 Im2Col

function Col = Im2Col(Image,block,Stride,Padding)
 
%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 入力タイプ
% Image = Image(Number of Filter, Channel, Image_height, Image_width)
% block = [number1, number2]
% Stride = number, Padding = number
%
% 出力 Col のサイズ:
% (NumImage*Output_height * Output_width) x (Channel * Block_height*Block_width)
% ここで
% Output_hight = fix((Image_height-Block_height+2*Padding)/Stride)+1
% Output_width = fix((Image_width-Block_width+2*Padding)/Stride)+1
%
% 参考文献 [1], [2],[3], [4] の Pythonプログラムにもとづく.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
Block_height = block(1);
Block_width = block(2);
[NumImage, Channel, Image_height, Image_width] = size(Image);
Output_hight = fix((Image_height-Block_height+2*Padding)/Stride)+1;
Output_width = fix((Image_width-Block_width+2*Padding)/Stride)+1;
col = zeros(NumImage,Block_height*Block_width,Channel,Output_hight,Output_width);
 
Image = padarray(Image,[0 0 Padding Padding],0,'both');
 
HS=Output_hight*Stride;
WS=Output_width*Stride;
 
for h=1:Block_height
for w = 1:Block_width
% MATLAB の im2col の配列にするには次のようにする.
col(:,(h-1)*Block_width+w,:,:,:)=Image(:,:,w:Stride:w-1+WS ,h:Stride:h-1+HS);
% この部分は,Python などの他の文献のようにするには次のようにする.
% col(:,(h-1)*Block_width+w,:,:,:)=Image(:,:, h:Stride:h-1+HS, w:Stride:w-1+WS);
end
end
 
% MATLAB式 im2col にするには次のようにする:
Col = permute(col,[2 3 4 5 1]);
% この部分は,Python などの他の文献のようにするには次のようにする.
% Col = permute(col,[2 3 5 4 1]);
 
Col = reshape(Col, [Channel*Block_height*Block_width NumImage*Output_hight*Output_width ]);
Col = permute(Col,[2 1]);
end

  

参考文献

[1] https://docs.chainer.org/en/v7.8.1.post1/reference/generated/chainer.functions.im2col.html
[2] 斎藤康毅,ゼロから作る Deep Learning,O'REILLY, 2016.
[3] 立石賢吾,やさしく学ぶディープラーニングがわかる数学のきほん,マイナビ,2019.
[4] 我妻幸長,はじめてのディープラーニング - Python で学ぶニューラルネットワークとバックプロパゲーション,SB Creative, 2018.
[5] 新井仁之,深層学習で使われる im2col を MATLAB で解説,http://www.araiweb.matrix.jp/Program/Im2Col_tutorial2.html
  
履歴          
Ver 1.4.1 2024年1月2日
Ver 1.4 2024年12月30日
Ver 1.3 2024年12月30日
Ver.1.2 2024年12月8日
Ver 1.1 2024年11月18日
  
  
Copyright © Hitoshi Arai, 2024