-
Notifications
You must be signed in to change notification settings - Fork 150
Activation function refactor #28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
14cb71e
2aa7020
069679d
a3f3ce7
1cf8a4f
74b0a49
26a725d
b41d172
482b69a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| #include "activations.h" | ||
|
|
||
| std::unordered_map<std::string, activations::Activation*> activations::Activation::_activations = { | ||
| {"Tanh", new activations::ActivationTanh()}, | ||
| {"Hardtanh", new activations::ActivationHardTanh()}, | ||
| {"Fasttanh", new activations::ActivationFastTanh()}, | ||
| {"ReLU", new activations::ActivationReLU()}, | ||
| {"Sigmoid", new activations::ActivationSigmoid()}}; | ||
|
|
||
| activations::Activation* tanh_bak = nullptr; | ||
|
|
||
| activations::Activation* activations::Activation::get_activation(const std::string name) | ||
| { | ||
| if (_activations.find(name) == _activations.end()) | ||
| return nullptr; | ||
|
|
||
| return _activations[name]; | ||
| } | ||
|
|
||
| void activations::Activation::enable_fast_tanh() | ||
| { | ||
| if (_activations["Tanh"] != _activations["Fasttanh"]) | ||
| { | ||
| tanh_bak = _activations["Tanh"]; | ||
| _activations["Tanh"] = _activations["Fasttanh"]; | ||
| } | ||
| } | ||
|
|
||
| void activations::Activation::disable_fast_tanh() | ||
| { | ||
| if (_activations["Tanh"] == _activations["Fasttanh"]) | ||
| { | ||
| _activations["Tanh"] = tanh_bak; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,125 @@ | ||
| #pragma once | ||
|
|
||
| #include <string> | ||
| #include <cmath> // expf | ||
| #include <unordered_map> | ||
| #include <Eigen/Dense> | ||
|
|
||
| namespace activations | ||
| { | ||
| float relu(float x) | ||
| inline float relu(float x) | ||
| { | ||
| return x > 0.0f ? x : 0.0f; | ||
| return x > 0.0f ? x : 0.0f; | ||
| }; | ||
| float sigmoid(float x) | ||
|
|
||
| inline float sigmoid(float x) | ||
| { | ||
| return 1.0f / (1.0f + expf(-x)); | ||
| }; | ||
|
|
||
| inline float hard_tanh(float x) | ||
| { | ||
| const float t = x < -1 ? -1 : x; | ||
| return t > 1 ? 1 : t; | ||
| } | ||
|
|
||
| inline float fast_tanh(const float x) | ||
| { | ||
| const float ax = fabsf(x); | ||
| const float x2 = x * x; | ||
|
|
||
| return (x * (2.45550750702956f + 2.45550750702956f * ax + (0.893229853513558f + 0.821226666969744f * ax) * x2) | ||
| / (2.44506634652299f + (2.44506634652299f + x2) * fabsf(x + 0.814642734961073f * x * ax))); | ||
| } | ||
|
|
||
| class Activation | ||
| { | ||
| public: | ||
| Activation(){}; | ||
| virtual void apply(Eigen::MatrixXf& matrix) | ||
| { | ||
| apply(matrix.data(), matrix.rows() * matrix.cols()); | ||
| } | ||
| virtual void apply(Eigen::Block<Eigen::MatrixXf> block) | ||
| { | ||
| apply(block.data(), block.rows() * block.cols()); | ||
| } | ||
| virtual void apply(Eigen::Block<Eigen::MatrixXf, -1, -1, true> block) | ||
| { | ||
| apply(block.data(), block.rows() * block.cols()); | ||
| } | ||
| virtual void apply(float* data, long size) {} | ||
|
Comment on lines
+39
to
+51
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Putting functions which will be called per-sample (or maybe multiple times per-sample depending on the network architecture) behind a virtual interface will likely have a significant performance impact, since it prevents the compiler from inlining these function calls in most cases. In RTNeural, this is a big reason why the "dynamic" API is much slower than the "static" API.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was worried about inlining, too, which is why I made sure to keep everything as block operations. I did not see any slowdown over the existing implementation in my testing, but that could be compiler-specific? I'm testing using VS on Windows. The override methods definitely make it more convenient to use, but it would be worth giving that up if there is a clearly demonstrable performance gain to be had.
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The functions that |
||
|
|
||
| static Activation* get_activation(const std::string name); | ||
| static void enable_fast_tanh(); | ||
| static void disable_fast_tanh(); | ||
|
|
||
| protected: | ||
| static std::unordered_map<std::string, Activation *> _activations; | ||
| }; | ||
|
|
||
| class ActivationTanh : public Activation | ||
| { | ||
| return 1.0f / (1.0f + expf(-x)); | ||
| public: | ||
| void apply(float *data, long size) override | ||
| { | ||
| for (long pos = 0; pos < size; pos++) | ||
| { | ||
| data[pos] = std::tanh(data[pos]); | ||
| } | ||
|
Comment on lines
+66
to
+69
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would probably be better to use Eigen's math functions for implementing some of these things, since they do some internal vectorization. (And that vectorization can be even better if the Matrix size is known at compile-time)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To be honest, tanh is best avoided completely... I'm pretty sure I looked for a way to do a matrix-level tanh with Eigen, and didn't see one. |
||
| } | ||
| }; | ||
|
|
||
| class ActivationHardTanh : public Activation | ||
| { | ||
| public: | ||
| ActivationHardTanh(){}; | ||
| void apply(float* data, long size) override | ||
| { | ||
| for (long pos = 0; pos < size; pos++) | ||
| { | ||
| data[pos] = hard_tanh(data[pos]); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| class ActivationFastTanh : public Activation | ||
| { | ||
| public: | ||
| ActivationFastTanh(){}; | ||
| void apply(float* data, long size) override | ||
| { | ||
| for (long pos = 0; pos < size; pos++) | ||
| { | ||
| data[pos] = fast_tanh(data[pos]); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| class ActivationReLU : public Activation | ||
| { | ||
| public: | ||
| ActivationReLU(){}; | ||
| void apply(float* data, long size) override | ||
| { | ||
| for (long pos = 0; pos < size; pos++) | ||
| { | ||
| data[pos] = relu(data[pos]); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| class ActivationSigmoid : public Activation | ||
| { | ||
| public: | ||
| ActivationSigmoid(){}; | ||
| void apply(float* data, long size) override | ||
| { | ||
| for (long pos = 0; pos < size; pos++) | ||
| { | ||
| data[pos] = sigmoid(data[pos]); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| }; // namespace activations | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,17 +29,13 @@ void wavenet::_Layer::process_(const Eigen::MatrixXf& input, const Eigen::Matrix | |
| this->_conv.process_(input, this->_z, i_start, ncols, 0); | ||
| // Mix-in condition | ||
| this->_z += this->_input_mixin.process(condition); | ||
| if (this->_activation == "Hardtanh") | ||
| hard_tanh_(this->_z); | ||
| else if (this->_activation == "Tanh") | ||
| tanh_(this->_z); | ||
| else if (this->_activation == "ReLU") | ||
| relu_(this->_z, 0, channels, 0, this->_z.cols()); | ||
| else | ||
| throw std::runtime_error("Unrecognized activation."); | ||
|
|
||
| this->_activation->apply(this->_z); | ||
|
|
||
| if (this->_gated) | ||
| { | ||
| sigmoid_(this->_z, channels, 2 * channels, 0, this->_z.cols()); | ||
| activations::Activation::get_activation("Sigmoid")->apply(this->_z.block(channels, 0, channels, this->_z.cols())); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI, I wouldn't call this "hard-coded" in this case--this is the most common implementation of a gated activation (messing with the sigmoid is, to my knowledge, not really done). |
||
|
|
||
| this->_z.topRows(channels).array() *= this->_z.bottomRows(channels).array(); | ||
| // this->_z.topRows(channels) = this->_z.topRows(channels).cwiseProduct( | ||
| // this->_z.bottomRows(channels) | ||
|
|
@@ -172,7 +168,7 @@ void wavenet::_LayerArray::_rewind_buffers_() | |
|
|
||
| wavenet::_Head::_Head(const int input_size, const int num_layers, const int channels, const std::string activation) | ||
| : _channels(channels) | ||
| , _activation(activation) | ||
| , _activation(activations::Activation::get_activation(activation)) | ||
| , _head(num_layers > 0 ? channels : input_size, 1, true) | ||
| { | ||
| assert(num_layers > 0); | ||
|
|
@@ -220,12 +216,7 @@ void wavenet::_Head::set_num_frames_(const long num_frames) | |
|
|
||
| void wavenet::_Head::_apply_activation_(Eigen::MatrixXf& x) | ||
| { | ||
| if (this->_activation == "Tanh") | ||
| tanh_(x); | ||
| else if (this->_activation == "ReLU") | ||
| relu_(x); | ||
| else | ||
| throw std::runtime_error("Unrecognized activation."); | ||
| this->_activation->apply(x); | ||
| } | ||
|
|
||
| // WaveNet ==================================================================== | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like this memory will be leaked?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it makes me uncomfortable, too - even though it is global, so should happen once and stick around for the lifetime of the program. My c++ chops are rusty - not sure what the preferred modern c++ way of handling this is.
I also thought about creating individual global instances and just taking a pointer to them in the map.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to work?
Seems fine to me, though I know that this is stretching my discipline w/ C++, so either of y'all can lmk if you think there's anything wrong with this ;)