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

PyTorchにはc++版のLibTorchというものがあります。
m1 Macでのインストール方法は[こちら](https://qiita.com/cometscome_phys/items/96b94074fdaf1c2bfa04)にまとめました。
他の場合は適当にググってもらえると出てくると思います。

# レイヤーの作成とパラメータ微分
[こちら](https://qiita.com/cometscome_phys/items/96b94074fdaf1c2bfa04)と同じような構成だとします。つまり、あるディレクトリに

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

まず、

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

コードは

```c++
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```です。このコードでは```weight```と```bias```という```torch::Tensor```型の変数が```W```と```b```という名前で登録されています。```forward```という関数では```at::add(matmul(input,this->weight),this->bias)```とありますが、PyTorchを触ったことがある人ならわかるのではないでしょうか。行列とベクトルの積をしてバイアスを足していますね。

このコードを使って、

```c++
#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枚定義されています。

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

```c++
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が最後に出力されていることがわかると思います。

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

```c++
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を組み合わせて作ります。これを利用すると、先ほどと同じことをやるコードは

```c++
#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;
    } 
}
```
とすっきりかけますね。

今回はここまでです。