1. 程式人生 > >【caffe-Windows】關於LSTM的簡單小例子

【caffe-Windows】關於LSTM的簡單小例子

前言

這裡主要是看到了一個簡單的LSTM例子,比上一個coco簡單很多,所以在這裡記錄一下,便於後續的分析,參考部落格在上一篇文章的末尾提到過:Recurrent neural nets with Caffe

需要說明的是這個例子也並非原原本本的使用caffe自帶的LSTM,而是加入了一些東西,所以我們還是得新增層

國際慣例,程式碼連結:

博主提供雲盤:連結:http://pan.baidu.com/s/1i474Dd7 密碼:31x8

第一步

首先對比一下兩個proto,看看我們的caffe-Windows少了哪幾個層。一般而言,操作多了就會發現,我們的第一反應去看看message LayerParameter這裡面登記的ID,可以發現作者添加了以下幾層:

①LSTM層:對應lstm_layer.hpp和lstm_layer.cpp注意這個地方由於上一篇部落格配置過LSTM,而且此處的LSTM與上一篇部落格實現不同,所以最好是重新解壓一個caffe-Windows執行新增層的操作

新增ID為

  optional LSTMParameter lstm_param = 201;
新增引數為
message LSTMParameter {
  optional uint32 num_output = 1; // The number of outputs for the layer
  optional float clipping_threshold = 2 [default = 0.0];
  optional FillerParameter weight_filler = 3; // The filler for weight
  optional FillerParameter bias_filler = 4; // The filler for the bias
  optional uint32 batch_size = 5 [default = 1];
}

②Repeat層:對應repeat_layer.hpp和repeat_layer.cpp

新增ID為

  optional RepeatParameter repeat_param = 202;
新增引數為
message RepeatParameter {
  optional uint32 num_repeats = 1; // The number of outputs for the layer
  optional int32 axis = 2 [default = 1];
}
③PowerFile層:對應power_file_layer.hpp和power_file_layer.cpp

新增ID為

  optional PowerFileParameter power_file_param = 146;

新增引數為

// added by Kaichun Mo
message PowerFileParameter {
	optional string shift_file = 1;
}
④loc_loss層:對應loc_loss_layer.hpp和loc_loss_layer.cpp

新增ID為

  optional LocLossParameter loc_loss_param = 147;
新增引數為
message LocLossParameter {
	required double threshold = 1;
}
⑤SpatialTransformer層:對應st_layer.hpp和st_layer.cpp

新增ID為

  optional SpatialTransformerParameter st_param = 148;
新增引數為
⑥STLoss層:對應st_loss_layer.hpp和st_loss_layer.cpp

新增ID為

optional STLossParameter st_loss_param = 145;
新增內容為
message STLossParameter {

	// Indicate the resolution of the output images after ST transformation
	required int32 output_H = 1;
	required int32 output_W = 2;
}
⑦smooth_L1_loss層:對應smooth_L1_loss_layer.hpp和smooth_L1_loss_layer.cpp

無ID無引數

這一步的主要問題在於:

新拷貝一個caffe-Windows再新增層,因為我第一次是將lstm_layer.hpp和lstm_layer.cpp都換了個名字,但是一直編譯不成功出錯。有C++大牛的話可以嘗試一下,也算是一種挑戰。

②新增層的方法沒有寫詳細,強烈建議大家一層一層新增,否則很容易出錯,而且你不知道錯誤原因何在。而且每一層新增完成測試的方法在上上篇部落格有說明,就是單獨對這個cpp編譯會顯示成功。

多句嘴:仔細、仔細、仔細

第二步

很多人在使用matlab介面的時候會說:“哎呀,咋有崩潰了啊”,“哎呀,又閃退了”,“又未響應了”。這種情況我的解決方法是使用exe除錯,當然每個人有每個人的除錯方法,都靠自己探索的。

所以第二步,先不在MATLAB中進行使用,而是測試一下我們書寫的LSTM的prototxt檔案是否正確,新手一般都會出現什麼parameterfailed什麼的錯誤,反正大概意思就是不存在這個層。那麼你就得回到第一步再檢查一遍了,提示的是誰,就檢查誰

廢話說多了,下面開始測試。使用的模型來源於參考部落格,先看看網路結構


看著挺簡單的,先介紹一下輸入(從原始部落格翻譯過來):

data:是T*N*I維度,其中I代表序列每一步中資料的維度(例子中為1),T*N代表序列長度,其中N是batchsize批大小。約束是資料大小必須是N的整數倍
clip:是T*N維度,如果clip=0,隱狀態和記憶單元就會被初始化為0,如果不是的話,之前的值將會被使用
label:每一步的預測輸出

根據模型搭建網路(lstm.prototxt)如下:

name: "LSTM"
input: "data"
input_shape { dim: 320 dim: 1 }
input: "clip"
input_shape { dim: 320 dim: 1 }
input: "label"
input_shape { dim: 320 dim: 1 }
layer {
  name: "Silence"
  type: "Silence"
  bottom: "label"
  include: { phase: TEST }
}
layer {
  name: "lstm1"
  type: "Lstm"
  bottom: "data"
  bottom: "clip"
  top: "lstm1"

  lstm_param {
    num_output: 15
    clipping_threshold: 0.1
    weight_filler {
      type: "gaussian"
      std: 0.1
    }
    bias_filler {
      type: "constant"%偏置初始化為0
    }
  }
}
layer {
  name: "ip1"
  type: "InnerProduct"
  bottom: "lstm1"
  top: "ip1"

  inner_product_param {
    num_output: 1
    weight_filler {
      type: "gaussian"
      std: 0.1
    }
    bias_filler {
      type: "constant"
    }
  }
}
layer {
  name: "loss"
  type: "EuclideanLoss"
  bottom: "ip1"
  bottom: "label"
  top: "loss"
  include: { phase: TRAIN }
}
有了網路模型,需要配置一個solver.prototxt去呼叫它
net: "lstm.prototxt"
test_iter: 1
test_interval: 2000000
base_lr: 0.0001
momentum: 0.95
lr_policy: "fixed"
display: 200
max_iter: 100000
solver_mode: CPU
average_loss: 200
#debug_info: true
接下來建立一個test.bat用於測試我們的caffe是否搭建好,是否適用於此prototxt,其中test.bat的內容如下
E:\CaffeDev2\caffe-windows\Build\x64\Release\caffe.exe train -solver=solver.prototxt
pause

看看我的目錄結構,主要還是為了強調路徑問題


執行之

如果這一步出錯,請重新操作第一步

第三步

既然模型沒問題了,那麼就可以新增資料進行預測了。採用參考博文中的輸入函式進行測試。

在此之前,參考前面的配置MATLAB介面的博文,確保自帶的classification.m能夠正常執行。

之後我們來測試一下模型預測效果:

①新增環境變數

clear
clc
addpath('../..')
caffe.reset_all
caffe.set_mode_cpu
②載入模型
solver=caffe.Solver('solver.prototxt');
執行看看,崩潰了請重新核對路徑以及MATLAB介面的配置還有第二步是否能成功
>> solver

solver = 

  Solver (具有屬性):

          net: [1x1 caffe.Net]
    test_nets: [1x1 caffe.Net]
設定遺忘門的偏置為5.0

③建立資料集

a=0:0.01:32;
a=a(1:end-1);
d=0.5 * sin(2*a) - 0.05 * cos( 17*a + 0.8  ) + 0.05 * sin( 25 * a + 10 ) - 0.02 * cos( 45 * a + 0.3);
d = d / max(max(d), -min(d));
d = d - mean(d);
④訓練
niter=5000;
solver.net.blobs('clip').set_data(solver.net.blobs('clip').get_data*0+1);
train_loss =zeros(1,niter);
for i=1:niter
    if mod(i,1000)==0
        fprintf('iter=%d\n',i);
    end
    seq_idx = mod(i ,(length(d) / 320));
    mid=solver.net.blobs('clip').get_data();
    solver.net.blobs('clip').set_data([seq_idx > 0,mid(2:end)]);
    solver.net.blobs('label').set_data(d( seq_idx * 320+1 : (seq_idx+1) * 320 ));
    solver.step(1)
    train_loss(i)= solver.net.blobs('loss').get_data();
end
畫出來看看
plot(1:niter,train_loss)

再測試一下
preds=zeros(length(d));
solver.test_nets.blobs('clip').set_data(solver.test_nets.blobs('clip').get_data*0+1)
for i=1:length(d)
    mid2=solver.test_nets.blobs('clip').get_data();
    solver.test_nets.blobs('clip').set_data([i>0,mid2(2:end)]);
    solver.test_nets.forward_prefilled()
    mid2=solver.test_nets.blobs('ip1').get_data();
    preds(i)=mid2(1);
end
figure
plot(preds)
hold on 
plot(d)
hold off

第四步

感覺和參考博文中的結果差距很大啊,去Python中測試一下

import caffe
solver = caffe.SGDSolver('solver.prototxt')
solver.net.params['lstm1'][2].data[15:30]=5
import numpy as np
a = np.arange(0,32,0.01)
d = 0.5*np.sin(2*a) - 0.05 * np.cos( 17*a + 0.8  ) + 0.05 * np.sin( 25 * a + 10 ) - 0.02 * np.cos( 45 * a + 0.3)
d = d / max(np.max(d), -np.min(d))
d = d - np.mean(d)
niter=5000
train_loss = np.zeros(niter)
solver.net.params['lstm1'][2].data[15:30]=5
solver.net.blobs['clip'].data[...] = 1
for i in range(niter) :
    seq_idx = i % (len(d) / 320)
    solver.net.blobs['clip'].data[0] = seq_idx > 0
    solver.net.blobs['label'].data[:,0] = d[ seq_idx * 320 : (seq_idx+1) * 320 ]
    solver.step(1)
    train_loss[i] = solver.net.blobs['loss'].data
import matplotlib.pyplot as plt
plt.plot(np.arange(niter), train_loss)

solver.test_nets[0].blobs['data'].reshape(2,1)
solver.test_nets[0].blobs['clip'].reshape(2,1)
solver.test_nets[0].reshape()
solver.test_nets[0].blobs['clip'].data[...] = 1
preds = np.zeros(len(d))
for i in range(len(d)):
    solver.test_nets[0].blobs['clip'].data[0] = i > 0
    preds[i] =  solver.test_nets[0].forward()['ip1'][0][0]
plt.plot(np.arange(len(d)), preds)
plt.plot(np.arange(len(d)), d)
plt.show()
然而也無法復現原文效果,有對Python比較瞭解的大牛希望能夠在評論區給出相關測試意見。



難道是作者博文寫錯了?這個以後再探討。

總結:

通過這兩個關於LSTM的部落格發現,不同的人對LSTM的改編都有所不同,根據不同的任務,可以對LSTM做相應的改動,使用方法也不盡相同。
但是語法結構應該是相似的,對比一下兩篇博文中關於LSTM層的寫法可能就會有不同的收穫