3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

PyTorchのc++版、LibTorchで遊んでみる その1

Last updated at Posted at 2022-07-18

PyTorchにはc++版のLibTorchというものがあります。
m1 Macでのインストール方法はこちらにまとめました。
他の場合は適当にググってもらえると出てくると思います。

レイヤーの作成とパラメータ微分

こちらと同じような構成だとします。つまり、あるディレクトリに

example-app.cpp
CMakeLists.txt

とあるとします。このexample-app.cppを使って遊んでみましょう。

まず、

y = W x + b

という形でインプットがxでアウトプットがyの関数を考えます。パラメータは行列Wとベクトルbです。これはLibTorchで実装します。

コードは

namespace Neuralnetworks{
    struct  LinearImpl : torch::nn::Module {
        LinearImpl(int in, int out){
            weight = register_parameter("W", torch::randn({in,out}));
            bias = register_parameter("b", torch::randn(out));
        };

        torch::Tensor forward(const torch::Tensor& input);
        torch::Tensor weight, bias;
    };
    TORCH_MODULE(Linear);
}

torch::Tensor Neuralnetworks::LinearImpl::forward(const torch::Tensor& input){
    return at::add(matmul(input,this->weight),this->bias); //+ this->bias;
}

こんな感じです。通常のコードと見た目が違うところは、TORCH_MODULE(Linear)でしょう。LinearImplという構造体を定義した後にTORCH_MODULE(Linear)とすることで、LibTorchがいい感じにLinearという構造体を定義してくれます。
また、誤差逆伝播をしたい場合にはしたいテンソルを登録しておく必要がありまして、これがregister_parameterです。このコードではweightbiasというtorch::Tensor型の変数がWbという名前で登録されています。forwardという関数ではat::add(matmul(input,this->weight),this->bias)とありますが、PyTorchを触ったことがある人ならわかるのではないでしょうか。行列とベクトルの積をしてバイアスを足していますね。

このコードを使って、

#include <torch/torch.h>
#include <iostream>

namespace Neuralnetworks{
    struct  LinearImpl : torch::nn::Module {
        LinearImpl(int in, int out){
            weight = register_parameter("W", torch::randn({in,out}));
            bias = register_parameter("b", torch::randn(out));
        };

        torch::Tensor forward(const torch::Tensor& input);
        torch::Tensor weight, bias;
    };
    TORCH_MODULE(Linear);
}

torch::Tensor Neuralnetworks::LinearImpl::forward(const torch::Tensor& input){
    return at::add(matmul(input,this->weight),this->bias); //+ this->bias;
}

int main() {
  torch::Tensor tensor = torch::rand({3});
  std::cout << tensor << std::endl;

  auto layer1 = Neuralnetworks::Linear(3,10);
  auto layer2 = Neuralnetworks::Linear(10,1);
  auto y = layer1 -> forward(tensor);
  y = torch::tanh(y);
  y = layer2 -> forward(y);
  std::cout << y << std::endl;
  y.backward();
  std::cout << "layer 1" << std::endl;
  for (auto &param : layer1->named_parameters())
    {
        std::cout << "value" << std::endl;
        std::cout << param.value() << std::endl;
        std::cout << "gradient " << std::endl;
        std::cout << param.value().grad() << std::endl;
    } 
   std::cout << "layer 2" << std::endl;
  for (auto &param : layer2->named_parameters())
    {
        std::cout << "value" << std::endl;
        std::cout << param.value() << std::endl;
        std::cout << "gradient " << std::endl;
        std::cout << param.value().grad() << std::endl;
    } 

}

としてみます。Linearというレイヤーが2枚定義されています。

auto y = layer1 -> forward(tensor);
  y = torch::tanh(y);
  y = layer2 -> forward(y);

の部分が順伝播になっており、レイヤーを適用してから活性化関数にtanhを用いて、またレイヤーを重ねています。
y.backward();は微分を計算するもので、PyTorchでお馴染みですね。今、出力のyがサイズ1のテンソルですから、これのパラメータ微分が欲しい場合はgrad()とするわけです。

for (auto &param : layer1->named_parameters())

の部分ですが、 layer1->named_parameters()で微分を計算するパラメータを取り出しています。
このコードを実行すると、

 ./example-app
 0.3096
 0.8701
 0.4622
[ CPUFloatType{3} ]
 7.6397
[ CPUFloatType{1} ]
layer 1
value
 0.0232 -0.2855  0.0019 -0.1011  0.6166 -0.4943 -0.6817  0.0134  0.5346  0.6875
-0.2144  0.4094  0.2965 -0.0209  0.6593  0.2868 -0.3015 -2.3122  0.0152  0.6704
 0.2426  0.9212 -1.9494  0.9541 -0.7791 -0.3230 -1.1348  0.9297 -0.3153  0.3671
[ CPUFloatType{3,10} ]
gradient 
 0.0092  0.3007 -0.0911 -0.1539  0.0025 -0.0825 -0.2492 -0.0308  0.3602 -0.0924
 0.0259  0.8452 -0.2562 -0.4326  0.0072 -0.2320 -0.7006 -0.0866  1.0125 -0.2598
 0.0137  0.4489 -0.1361 -0.2298  0.0038 -0.1232 -0.3721 -0.0460  0.5377 -0.1380
[ CPUFloatType{3,10} ]
value
 1.7889
-0.1526
-0.0572
-0.8074
 2.2048
 0.5678
-0.1218
-0.8317
-0.3139
-0.3481
[ CPUFloatType{10} ]
gradient 
 0.0297
 0.9714
-0.2944
-0.4972
 0.0082
-0.2666
-0.8052
-0.0995
 1.1635
-0.2986
[ CPUFloatType{10} ]
layer 2
value
 0.2478
 1.2846
-0.4636
-0.5883
 0.3838
-0.3438
-2.3133
-3.1333
 1.2578
-0.4278
[ CPUFloatType{10,1} ]
gradient 
 0.9381
 0.4938
-0.6041
-0.3934
 0.9892
 0.4739
-0.8074
-0.9840
-0.2738
 0.5496
[ CPUFloatType{10,1} ]
value
 1.6730
[ CPUFloatType{1} ]
gradient 
 1
[ CPUFloatType{1} ]

のようになります。最後のレイヤーのバイアス項は、出力にそのまま出てきますから、バイアス項で微分すると1になります。その1が最後に出力されていることがわかると思います。

全結合モデルの作成

上ではレイヤーを一つ一つ作用させていきましたが、まとめることもできます。その場合には、

namespace Neuralnetworks{
class DenseNetImpl : public torch::nn::Module
    {
    public:
        DenseNetImpl(const std::vector<int>& units){
            size_t numlayers = units.size()-1;
            this -> numlayers = numlayers;
            for (int i = 0;i < numlayers;i++){
                auto n_in = units.at(i);
                auto n_out = units.at(i+1);
                Linear layer_temp = Linear(n_in, n_out);
                this -> linears_.push_back(layer_temp);

                register_module("linear_"+std::to_string(i), this -> linears_[i]);
            }
        };
        torch::Tensor forward(const torch::Tensor& input);
    
    private:
        std::vector<Linear> linears_;
        size_t numlayers;
    };
    TORCH_MODULE(DenseNet);
}
torch::Tensor Neuralnetworks::DenseNetImpl::forward(const torch::Tensor& input){
    torch::Tensor x = input.clone();
    for (int i = 0;i < this -> numlayers-1;i++){
        x = torch::tanh(this->linears_.at(i) -> forward(x));
    }
     x = this->linears_.at(this -> numlayers-1) -> forward(x);

    return at::sum(x,0);
}

のように先ほど定義したLayerを組み合わせて作ります。これを利用すると、先ほどと同じことをやるコードは

#include <torch/torch.h>
#include <iostream>
#include <vector>

namespace Neuralnetworks{
    struct  LinearImpl : torch::nn::Module {
        LinearImpl(int in, int out){
            weight = register_parameter("W", torch::randn({in,out}));
            bias = register_parameter("b", torch::randn(out));
        };

        torch::Tensor forward(const torch::Tensor& input);
        torch::Tensor weight, bias;
    };
    TORCH_MODULE(Linear);

    class DenseNetImpl : public torch::nn::Module
    {
    public:
        DenseNetImpl(const std::vector<int>& units){
            size_t numlayers = units.size()-1;
            this -> numlayers = numlayers;
            for (int i = 0;i < numlayers;i++){
                auto n_in = units.at(i);
                auto n_out = units.at(i+1);
                Linear layer_temp = Linear(n_in, n_out);
                this -> linears_.push_back(layer_temp);

                register_module("linear_"+std::to_string(i), this -> linears_[i]);
            }
        };
        torch::Tensor forward(const torch::Tensor& input);
    
    private:
        std::vector<Linear> linears_;
        size_t numlayers;
    };
    TORCH_MODULE(DenseNet);
}

torch::Tensor Neuralnetworks::LinearImpl::forward(const torch::Tensor& input){
    return at::add(matmul(input,this->weight),this->bias); //+ this->bias;
}

torch::Tensor Neuralnetworks::DenseNetImpl::forward(const torch::Tensor& input){
    torch::Tensor x = input.clone();
    for (int i = 0;i < this -> numlayers-1;i++){
        x = torch::tanh(this->linears_.at(i) -> forward(x));
    }
     x = this->linears_.at(this -> numlayers-1) -> forward(x);

    return at::sum(x,0);
}

int main() {
  torch::Tensor tensor = torch::rand({3});
  std::cout << tensor << std::endl;

  std::vector<int> layers{3,10,1};
  auto model = Neuralnetworks::DenseNet(layers);
  auto y = model -> forward(tensor);
std::cout << y << std::endl;
  y.backward();
  for (auto &param : model->named_parameters())
    {
        std::cout << "value" << std::endl;
        std::cout << param.value() << std::endl;
        std::cout << "gradient " << std::endl;
        std::cout << param.value().grad() << std::endl;
    } 
}

とすっきりかけますね。

今回はここまでです。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?