diff --git a/core/include/jml/math/loss_functions.hpp b/core/include/jml/math/loss_functions.hpp new file mode 100644 index 0000000..e3f7652 --- /dev/null +++ b/core/include/jml/math/loss_functions.hpp @@ -0,0 +1,46 @@ +/** + * This offers loss functions for a model. Only one such loss function exists + * right now, but we may need more in the future. + * + * Each loss function needs an actual calculation (put in a jml::Vector, get out + * a double), and a derivative with respect to a particular entry. That is, if + * we change the i-th entry of the input vector, how much the loss will change + * by. This will be very important later for gradient descent. + * + * Author: Adam Hutchings + * Date: 10-17-23 +*/ + +#pragma once + +#include + +#include +#include + +namespace jml { + +typedef std::function LF; +typedef std::function DL; + +class JML_API LossFunction { + +private: + LF loss; + DL dl; + +public: + // Create a loss function with a given loss and derivative. + LossFunction(const LF& lf, const DL& dl); + double get_loss(const jml::Vector& actual, const jml::Vector& expected); + double get_loss_derivative( + const jml::Vector& actual, const jml::Vector& expected, int index + ); + +}; + +// Also, we provide an example set of loss functions (L^2 norm) +extern JML_API LF l2lf; +extern JML_API DL l2dl; + +} diff --git a/core/meson.build b/core/meson.build index 00f7bfc..ef4bcc4 100644 --- a/core/meson.build +++ b/core/meson.build @@ -10,7 +10,9 @@ endif # TODO: pkg-config stuff -core_sources = ['src/math/matrix.cpp', 'src/math/vector.cpp', 'src/math/activation_functions.cpp', 'src/model.cpp', 'src/logger.cpp', 'capi/cjml.cpp'] +core_sources = ['src/math/matrix.cpp', 'src/math/vector.cpp', +'src/math/activation_functions.cpp', 'src/model.cpp', 'src/logger.cpp', +'capi/cjml.cpp', 'src/math/loss_functions.cpp'] jmlcore = library('jmlcore', core_sources, include_directories: core_inc, diff --git a/core/src/math/loss_functions.cpp b/core/src/math/loss_functions.cpp new file mode 100644 index 0000000..8cbfdda --- /dev/null +++ b/core/src/math/loss_functions.cpp @@ -0,0 +1,50 @@ +#include + +#include + +#include + +namespace jml { + +LossFunction::LossFunction(const LF& lf, const DL& dl) { + this->loss = lf; + this->dl = dl; +} + +double LossFunction::get_loss( + const Vector& actual, const Vector& expected +) { + return this->loss(actual, expected); +} + +double LossFunction::get_loss_derivative( + const Vector& actual, const Vector& expected, int index +) { + return this->dl(actual, expected, index); +} + +JML_API LF l2lf = [](const Vector& actual, const Vector& expected) { + int a = actual.get_size(), e = expected.get_size(); + if (a != e) { + LOGGER->log(Log(WARN) + << "Tried to compare a vector of length " << a + << "to a vector of length " << e << ".\n"); + } + double total = 0; + double diff; + for (int i = 0; i < a; ++i) { + diff = actual.get_entry(i) - expected.get_entry(i); + diff *= diff; + total += diff; + } + return sqrt(total); +}; + +JML_API DL l2dl = [](const Vector& actual, const Vector& expected, int i) { + double l = l2lf(actual, expected); + double ret = 1.0 / (2 * l); + ret *= 2 * (actual.get_entry(i) - expected.get_entry(i)); + return ret; +}; + +} diff --git a/tests/core/math/test_losses.cpp b/tests/core/math/test_losses.cpp new file mode 100644 index 0000000..c005cf0 --- /dev/null +++ b/tests/core/math/test_losses.cpp @@ -0,0 +1,17 @@ +#include "catch2/catch_test_macros.hpp" +#include +#include + +SCENARIO("Loss can be calculated from two vectors") { + GIVEN("Two vectors of the same size") { + jml::Vector vec1(3), vec2(3); + vec1.set_entry(0, 1.0); + vec1.set_entry(1, 2.0); + vec1.set_entry(2, 3.0); + vec2.set_entry(0, 4.0); + vec2.set_entry(1, 2.0); + vec2.set_entry(2,-1.0); + auto loss = jml::l2lf(vec1, vec2); + REQUIRE(loss == 5.0); + } +} \ No newline at end of file diff --git a/tests/meson.build b/tests/meson.build index 5d2749f..c5d8c0c 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -2,6 +2,7 @@ core_test_sources = [ 'core/math/test_matrix.cpp', 'core/math/test_vector.cpp', 'core/math/test_activation_functions.cpp', + 'core/math/test_losses.cpp', 'core/test_model.cpp', ]