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
です。このコードではweight
とbias
というtorch::Tensor
型の変数がW
とb
という名前で登録されています。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 ¶m : 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 ¶m : 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 ¶m : 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 ¶m : 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;
}
}
とすっきりかけますね。
今回はここまでです。