Deep Learning 學習隨記(八)CNN(Convolutional neural network)理解


前面Andrew Ng的講義基本看完了。Andrew講的真是通俗易懂,只是不過癮啊,講的太少了。趁着看完那章convolution and pooling, 自己又去翻了翻CNN的相關東西。

當時看講義時,有一點是不太清楚的,就是講義只講了一次convolution和一次pooling,而且第一次的convolution很容易理解,針對一副圖像來的,但是經過一次convolution和pooling

后,一副圖像變成了好多副特征圖(feature map)這時候再進行convolution時,該怎么辦呢?所以去瞅了瞅CNN的相關論文。

CNN最經典的案例應該是LeNet-5這個數字識別的任務了吧。這里可以看下Yann Lecun大牛網頁 http://yann.lecun.com/exdb/lenet/index.html, 以及tutorial: http://deeplearning.net/tutorial/lenet.html。

另外,一篇比較詳細的講CNN的中文博客(懶得看英語的話,就直接看這篇博客了):http://blog.csdn.net/zouxy09/article/details/8781543。

這里面都給出了CNN的結構圖如下。

具體每個的含義這里也不說了,可以參考前面提到的資料。

看了結構圖基本了解了。有幾點一開始沒看懂的需要說明的:

1. 關於每一個C層的feature map的個數。

比如C1是6個,C3是16個,這個應該是經驗值,或者是通過實驗給出的一個比較優的值,這點好多資料都沒有說清楚。不過要注意的是,一般后面的要比前面的個數多些。

2. 關於后面的C層。比如S2到C3,並不是一一對應的。

也就是說,並不是對S2中的每一個feature map與后面16個卷積核進行卷積。而是取其中幾個。看了下面圖應該很容易理解:

縱向是S2層的6個feature map,橫向是C3的16個卷積核,X表示兩者相連。比如說,第0個卷積核,只用在了前面3個feature map上,把這3個卷積結果加權相加或者平均就得到C3層的第一個(如按照上圖標示應該是第0個)feature map。至於這個對應表示怎么來的,也不得而知啊,應該也是經驗或者通過大量實驗得來的吧。這點還不是很清楚...當然,如果想全部相連也不是不可以,只是對5個相加或者進行加權平均而已(比如第15號卷積核)。

3. 關於每一層的卷積核是怎么來的。

從Andrew的講義中,我們是先從一些小patch里用稀疏自編碼學習到100個特征(隱層100個單元),然后相當於100個卷積核(不知這樣理解對不對)。這樣子后面的卷積層的核怎么做呢?每一層都用前一層pooling(或者降采樣)后得到的feature map再進行一次自編碼學習?。這里就想到了去看toolbox里的CNN的代碼,但感覺不是同一個套路:

下面是cnnsetup.m的代碼:

function net = cnnsetup(net, x, y)
    inputmaps = 1;
    mapsize = size(squeeze(x(:, :, 1)));

    for l = 1 : numel(net.layers)   %  layer
        if strcmp(net.layers{l}.type, 's')
            mapsize = mapsize / net.layers{l}.scale;
            assert(all(floor(mapsize)==mapsize), ['Layer ' num2str(l) ' size must be integer. Actual: ' num2str(mapsize)]);
            for j = 1 : inputmaps
                net.layers{l}.b{j} = 0;
            end
        end
        if strcmp(net.layers{l}.type, 'c')
            mapsize = mapsize - net.layers{l}.kernelsize + 1;
            fan_out = net.layers{l}.outputmaps * net.layers{l}.kernelsize ^ 2;
            for j = 1 : net.layers{l}.outputmaps  %  output map
                fan_in = inputmaps * net.layers{l}.kernelsize ^ 2;
                for i = 1 : inputmaps  %  input map
                    net.layers{l}.k{i}{j} = (rand(net.layers{l}.kernelsize) - 0.5) * 2 * sqrt(6 / (fan_in + fan_out));
                end
                net.layers{l}.b{j} = 0;
            end
            inputmaps = net.layers{l}.outputmaps;
        end
    end
    % 'onum' is the number of labels, that's why it is calculated using size(y, 1). If you have 20 labels so the output of the network will be 20 neurons.
    % 'fvnum' is the number of output neurons at the last layer, the layer just before the output layer.
    % 'ffb' is the biases of the output neurons.
    % 'ffW' is the weights between the last layer and the output neurons. Note that the last layer is fully connected to the output layer, that's why the size of the weights is (onum * fvnum)
    fvnum = prod(mapsize) * inputmaps;
    onum = size(y, 1);

    net.ffb = zeros(onum, 1);
    net.ffW = (rand(onum, fvnum) - 0.5) * 2 * sqrt(6 / (onum + fvnum));
end

 

里面inputmaps是上一層的feature map數。outputmaps當前層的feature map數。 其中有一行代碼是 

net.layers{l}.k{i}{j} = (rand(net.layers{l}.kernelsize) - 0.5) * 2 * sqrt(6 / (fan_in + fan_out));

這一句應該就是初始化卷積核了。這里是隨機生成的一個在某個范圍內的kernelsize*kernelsize的卷積核。其中i和j分別對應inputmaps和outputmaps。也就是說是為每一個連接初始化了一個卷積核。

下面再看下cnnff.m即前向傳播的部分代碼:

function net = cnnff(net, x)
    n = numel(net.layers);
    net.layers{1}.a{1} = x;
    inputmaps = 1;

    for l = 2 : n   %  for each layer
        if strcmp(net.layers{l}.type, 'c')
            %  !!below can probably be handled by insane matrix operations
            for j = 1 : net.layers{l}.outputmaps   %  for each output map
                %  create temp output map
                z = zeros(size(net.layers{l - 1}.a{1}) - [net.layers{l}.kernelsize - 1 net.layers{l}.kernelsize - 1 0]);
                for i = 1 : inputmaps   %  for each input map
                    %  convolve with corresponding kernel and add to temp output map
                    z = z + convn(net.layers{l - 1}.a{i}, net.layers{l}.k{i}{j}, 'valid');
                end
                %  add bias, pass through nonlinearity
                net.layers{l}.a{j} = sigm(z + net.layers{l}.b{j});
            end
            %  set number of input maps to this layers number of outputmaps
            inputmaps = net.layers{l}.outputmaps;
        elseif strcmp(net.layers{l}.type, 's')
            %  downsample
            for j = 1 : inputmaps
                z = convn(net.layers{l - 1}.a{j}, ones(net.layers{l}.scale) / (net.layers{l}.scale ^ 2), 'valid');   %  !! replace with variable
                net.layers{l}.a{j} = z(1 : net.layers{l}.scale : end, 1 : net.layers{l}.scale : end, :);
            end
        end
    end

    %  concatenate all end layer feature maps into vector
    net.fv = [];
    for j = 1 : numel(net.layers{n}.a)
        sa = size(net.layers{n}.a{j});
        net.fv = [net.fv; reshape(net.layers{n}.a{j}, sa(1) * sa(2), sa(3))];
    end
    %  feedforward into output perceptrons
    net.o = sigm(net.ffW * net.fv + repmat(net.ffb, 1, size(net.fv, 2)));

end

 

其中卷積層的代碼確實是用了提前初始化的卷積核:

for j = 1 : net.layers{l}.outputmaps   %  for each output map
         %  create temp output map
         z = zeros(size(net.layers{l - 1}.a{1}) - [net.layers{l}.kernelsize - 1 net.layers{l}.kernelsize - 1 0]);
         for i = 1 : inputmaps   %  for each input map
             %  convolve with corresponding kernel and add to temp output map
             z = z + convn(net.layers{l - 1}.a{i}, net.layers{l}.k{i}{j}, 'valid');
         end
         %  add bias, pass through nonlinearity
         net.layers{l}.a{j} = sigm(z + net.layers{l}.b{j});
end

 

這里,使用的全連接的方式,不像2中提到的那樣有選擇性的連接。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM