基本模塊搭建
模塊化編程的思想非常重要,通過模塊化編程可以大幅減少重復的敲代碼過程,同時代碼可讀性也會增加。本章將講述如何使用libtorch搭建一些MLP和CNN的基本模塊。
MLP基本單元
首先是線性層的聲明和定義,包括初始化和前向傳播函數。代碼如下:
class LinearBnReluImpl : public torch::nn::Module{
public:
LinearBnReluImpl(int intput_features, int output_features);
torch::Tensor forward(torch::Tensor x);
private:
//layers
torch::nn::Linear ln{nullptr};
torch::nn::BatchNorm1d bn{nullptr};
};
TORCH_MODULE(LinearBnRelu);
LinearBnReluImpl::LinearBnReluImpl(int in_features, int out_features){
ln = register_module("ln", torch::nn::Linear(torch::nn::LinearOptions(in_features, out_features)));
bn = register_module("bn", torch::nn::BatchNorm1d(out_features));
}
torch::Tensor LinearBnReluImpl::forward(torch::Tensor x){
x = torch::relu(ln->forward(x));
x = bn(x);
return x;
}
在MLP的構造線性層模塊類時,我們繼承了torch::nn::Module類,將初始化和前向傳播模塊作為public,可以給對象使用,而里面的線性層torch::nn::Linear和歸一化層torch::nn::BatchNorm1d被隱藏作為私有變量。
定義初始化函數時,需要將原本的指針對象ln和bn進行賦值,同時將兩者的名稱也確定。前向傳播函數就和pytorch中的forward類似。
CNN基本單元
CNN的基本單元構建和MLP的構建類似,但是又稍有不同,首先需要定義的時卷積超參數確定函數。
inline torch::nn::Conv2dOptions conv_options(int64_t in_planes, int64_t out_planes, int64_t kerner_size,
int64_t stride = 1, int64_t padding = 0, bool with_bias = false) {
torch::nn::Conv2dOptions conv_options = torch::nn::Conv2dOptions(in_planes, out_planes, kerner_size);
conv_options.stride(stride);
conv_options.padding(padding);
conv_options.bias(with_bias);
return conv_options;
}
該函數返回torch::nn::Conv2dOptions對象,對象的超參數由函數接口指定,這樣可以方便使用。同時指定inline,提高Release模式下代碼執行效率。
隨后則是和MLP的線性模塊類似,CNN的基本模塊由卷積層,激活函數和歸一化層組成。代碼如下:
class ConvReluBnImpl : public torch::nn::Module {
public:
ConvReluBnImpl(int input_channel=3, int output_channel=64, int kernel_size = 3, int stride = 1);
torch::Tensor forward(torch::Tensor x);
private:
// Declare layers
torch::nn::Conv2d conv{ nullptr };
torch::nn::BatchNorm2d bn{ nullptr };
};
TORCH_MODULE(ConvReluBn);
ConvReluBnImpl::ConvReluBnImpl(int input_channel, int output_channel, int kernel_size, int stride) {
conv = register_module("conv", torch::nn::Conv2d(conv_options(input_channel,output_channel,kernel_size,stride,kernel_size/2)));
bn = register_module("bn", torch::nn::BatchNorm2d(output_channel));
}
torch::Tensor ConvReluBnImpl::forward(torch::Tensor x) {
x = torch::relu(conv->forward(x));
x = bn(x);
return x;
}
簡單MLP
在MLP的例子中,我們以搭建一個四層感知機為例,介紹如何使用cpp實現深度學習模型。該感知機接受in_features個特征,輸出out_features個編碼后的特征。中間特征數定義為32,64和128,其實一般逆序效果更佳,但是只是作為例子也無關緊要。
class MLP: public torch::nn::Module{
public:
MLP(int in_features, int out_features);
torch::Tensor forward(torch::Tensor x);
private:
int mid_features[3] = {32,64,128};
LinearBnRelu ln1{nullptr};
LinearBnRelu ln2{nullptr};
LinearBnRelu ln3{nullptr};
torch::nn::Linear out_ln{nullptr};
};
MLP::MLP(int in_features, int out_features){
ln1 = LinearBnRelu(in_features, mid_features[0]);
ln2 = LinearBnRelu(mid_features[0], mid_features[1]);
ln3 = LinearBnRelu(mid_features[1], mid_features[2]);
out_ln = torch::nn::Linear(mid_features[2], out_features);
ln1 = register_module("ln1", ln1);
ln2 = register_module("ln2", ln2);
ln3 = register_module("ln3", ln3);
out_ln = register_module("out_ln",out_ln);
}
torch::Tensor MLP::forward(torch::Tensor x){
x = ln1->forward(x);
x = ln2->forward(x);
x = ln3->forward(x);
x = out_ln->forward(x);
return x;
}
每一層的實現均是通過前面定義的基本模塊LinearBnRelu。
簡單CNN
前面介紹了構建CNN的基本模塊ConvReluBn,接下來嘗試用c++搭建CNN模型。該CNN由三個stage組成,每個stage又由一個卷積層一個下采樣層組成。這樣相當於對原始輸入圖像進行了8倍下采樣。中間層的通道數變化與前面MLP特征數變化相同,均為輸入->32->64->128->輸出。
class plainCNN : public torch::nn::Module{
public:
plainCNN(int in_channels, int out_channels);
torch::Tensor forward(torch::Tensor x);
private:
int mid_channels[3] = {32,64,128};
ConvReluBn conv1{nullptr};
ConvReluBn down1{nullptr};
ConvReluBn conv2{nullptr};
ConvReluBn down2{nullptr};
ConvReluBn conv3{nullptr};
ConvReluBn down3{nullptr};
torch::nn::Conv2d out_conv{nullptr};
};
plainCNN::plainCNN(int in_channels, int out_channels){
conv1 = ConvReluBn(in_channels,mid_channels[0],3);
down1 = ConvReluBn(mid_channels[0],mid_channels[0],3,2);
conv2 = ConvReluBn(mid_channels[0],mid_channels[1],3);
down2 = ConvReluBn(mid_channels[1],mid_channels[1],3,2);
conv3 = ConvReluBn(mid_channels[1],mid_channels[2],3);
down3 = ConvReluBn(mid_channels[2],mid_channels[2],3,2);
out_conv = torch::nn::Conv2d(conv_options(mid_channels[2],out_channels,3));
conv1 = register_module("conv1",conv1);
down1 = register_module("down1",down1);
conv2 = register_module("conv2",conv2);
down2 = register_module("down2",down2);
conv3 = register_module("conv3",conv3);
down3 = register_module("down3",down3);
out_conv = register_module("out_conv",out_conv);
}
torch::Tensor plainCNN::forward(torch::Tensor x){
x = conv1->forward(x);
x = down1->forward(x);
x = conv2->forward(x);
x = down2->forward(x);
x = conv3->forward(x);
x = down3->forward(x);
x = out_conv->forward(x);
return x;
}
假定輸入一個三通道圖片,輸出通道數定義為n,輸入表示一個[1,3,224,224]的張量,將得到一個[1,n,28,28]的輸出張量。
簡單LSTM
最后則是一個簡單的LSTM的例子,用以處理時序型特征。在直接使用torch::nn::LSTM類之前,我們先頂一個返回torch::nn::LSTMOptions對象的函數,該函數接受關於LSTM的超參數,返回這些超參數定義的結果。
inline torch::nn::LSTMOptions lstmOption(int in_features, int hidden_layer_size, int num_layers, bool batch_first = false, bool bidirectional = false){
torch::nn::LSTMOptions lstmOption = torch::nn::LSTMOptions(in_features, hidden_layer_size);
lstmOption.num_layers(num_layers).batch_first(batch_first).bidirectional(bidirectional);
return lstmOption;
}
//batch_first: true for io(batch, seq, feature) else io(seq, batch, feature)
class LSTM: public torch::nn::Module{
public:
LSTM(int in_features, int hidden_layer_size, int out_size, int num_layers, bool batch_first);
torch::Tensor forward(torch::Tensor x);
private:
torch::nn::LSTM lstm{nullptr};
torch::nn::Linear ln{nullptr};
std::tuple<torch::Tensor, torch::Tensor> hidden_cell;
};
聲明好LSTM以后,我們將內部的初始化函數和前向傳播函數實現如下:
LSTM::LSTM(int in_features, int hidden_layer_size, int out_size, int num_layers, bool batch_first){
lstm = torch::nn::LSTM(lstmOption(in_features, hidden_layer_size, num_layers, batch_first));
ln = torch::nn::Linear(hidden_layer_size, out_size);
lstm = register_module("lstm",lstm);
ln = register_module("ln",ln);
}
torch::Tensor LSTM::forward(torch::Tensor x){
auto lstm_out = lstm->forward(x);
auto predictions = ln->forward(std::get<0>(lstm_out));
return predictions.select(1,-1);
}
分享不易,如果有用請不吝給我一個👍,轉載注明出處:https://allentdan.github.io/
代碼見LibtorchTutorials