From 374f19deceacf5cb89cb9ca406ec07f95c962d89 Mon Sep 17 00:00:00 2001 From: Seb James Date: Wed, 27 Sep 2023 12:03:45 +0100 Subject: [PATCH 1/3] Correct the logistic function used --- morph/vvec.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/morph/vvec.h b/morph/vvec.h index b3ff1621..3f65452f 100644 --- a/morph/vvec.h +++ b/morph/vvec.h @@ -1148,20 +1148,20 @@ namespace morph { for (auto& i : *this) { i = std::exp (i*i/(S{-2}*sigma*sigma)); } } - //! Replace each element with the generalised logistic function of the element: - //! f(x) = [ 1 + exp(xoff - x) ]^-alpha - vvec logistic (const S xoff = S{0}, const S alpha = S{1}) const + //! Return a vvec containing the generalised logistic function of this vvec: + //! f(x) = 1 / [ 1 + exp(-k*(x - x0)) ] + vvec logistic (const S k = S{1}, const S x0 = S{0}) const { vvec rtn(this->size()); - auto _element = [alpha, xoff](S i) { return std::pow((S{1} + std::exp (xoff - i)), -alpha); }; - std::transform (this->begin(), this->end(), rtn.begin(), _element); + auto _logisticfn = [k, x0](S _x) { return S{1} / (S{1} + std::exp (k*(x0 - _x))); }; + std::transform (this->begin(), this->end(), rtn.begin(), _logisticfn); return rtn; } - void logistic_inplace (const S xoff = S{0}, const S alpha = S{1}) + //! Replace each element x with the generalised logistic function of the element: + //! f(x) = 1 / [ 1 + exp(-k*(x - x0)) ] + void logistic_inplace (const S k = S{1}, const S x0 = S{0}) { - for (auto& i : *this) { - i = std::pow((S{1} + std::exp (xoff - i)), -alpha); - } + for (auto& _x : *this) { _x = S{1} / (S{1} + std::exp (k*(x0 - _x))); } } //! Smooth the vector by convolving with a gaussian filter with Gaussian width From 03ddc77d8ff3f9b123d687534ebe85cea6a8f238 Mon Sep 17 00:00:00 2001 From: Seb James Date: Wed, 27 Sep 2023 12:04:43 +0100 Subject: [PATCH 2/3] More useful text for logistic function graphs --- examples/graph_logist.cpp | 41 +++++++++++++++++------- examples/graph_logist2.cpp | 64 +++++++++++++++++++++++++------------ examples/graph_logist2.json | 9 +++--- 3 files changed, 77 insertions(+), 37 deletions(-) diff --git a/examples/graph_logist.cpp b/examples/graph_logist.cpp index bdd616e6..82851728 100644 --- a/examples/graph_logist.cpp +++ b/examples/graph_logist.cpp @@ -3,6 +3,28 @@ #include #include +// Make an equation string for the legend +std::string make_legend_str (double k, double x0) +{ + using morph::unicode; + std::stringstream ktxt; + if (k != 1.0) { ktxt << k; } + std::stringstream brtxt; + std::stringstream ostxt; + if (x0 != 0.0) { + brtxt << "("; + if (x0 > 0.0) { + ostxt << " - " << x0 <<")"; + } else { + ostxt << " + " << -x0 <<")"; + } + } + std::stringstream eqn; + eqn << "k="< x; // This works like numpy's linspace() (the 3 args are "start", "end" and "num"): x.linspace (-100, 30, 200); - // Logistic functions. Args are parameters to the function are (xoffset, alpha) - std::string lftag = std::string("ofst=-10, ") + unicode::toUtf8(unicode::alpha) + std::string("=0.1"); // A dataset tag + // Logistic functions. Args are parameters to the function: (k, x0) // vvec::logistic() returns a new vvec with the logistic function-transformed values: - gv->setdata (x, x.logistic(-10, 0.1), lftag); + gv->setdata (x, x.logistic(0.1, -10), make_legend_str (0.1, -10)); // For this one, demonstrate use of logistic_inplace(): - lftag = std::string("ofst=-5, ") + unicode::toUtf8(unicode::alpha) + std::string("=0.25"); morph::vvec xlogistic = x; - xlogistic.logistic_inplace(-5, 0.25); - gv->setdata (x, xlogistic, lftag); - lftag = std::string("ofst=0, ") + unicode::toUtf8(unicode::alpha) + std::string("=0.5"); - gv->setdata (x, x.logistic(0, 0.5), lftag); - lftag = std::string("ofst=5, ") + unicode::toUtf8(unicode::alpha) + std::string("=1"); - gv->setdata (x, x.logistic(5, 1), lftag); - lftag = std::string("ofst=10, ") + unicode::toUtf8(unicode::alpha) + std::string("=2"); - gv->setdata (x, x.logistic(10, 2), lftag); + xlogistic.logistic_inplace(0.25, -5); + gv->setdata (x, xlogistic, make_legend_str (0.25, -5)); + gv->setdata (x, x.logistic(0.5, 0), make_legend_str (0.5, 0)); + gv->setdata (x, x.logistic(1, 5), make_legend_str (1, 5)); + gv->setdata (x, x.logistic(2, 10), make_legend_str (2, 10)); // finalize() makes the GraphVisual compute the vertices of the OpenGL model gv->finalize(); // Add the GraphVisual OpenGL model to the Visual scene, transferring ownership of the unique_ptr diff --git a/examples/graph_logist2.cpp b/examples/graph_logist2.cpp index a6d97533..bf24d37b 100644 --- a/examples/graph_logist2.cpp +++ b/examples/graph_logist2.cpp @@ -17,25 +17,24 @@ int main() auto gv = std::make_unique> (morph::vec({-0.5f,-0.5f,0.0f})); v.bindmodel (gv); // Params are read from a JSON file - double ofst=0, alpha=0, x0=0, x1=0, m=0; + double x0=0, k=0, g1x0=0, g1x1=0; { morph::Config conf ("../examples/graph_logist2.json"); - ofst = conf.get ("ofst", 4.0); - alpha = conf.get ("alpha", 10.0); - x0 = conf.get ("x0", -10.0); - x1 = conf.get ("x1", 10.0); - m = conf.get ("m", 12.0); + k = conf.get ("k", 10.0); + x0 = conf.get ("x0", 4.0); + g1x0 = conf.get ("g1x0", -10.0); + g1x1 = conf.get ("g1x1", 10.0); } // Data for the x axis. A vvec is like std::vector, but with built-in maths methods morph::vvec x; // This works like numpy's linspace() (the 3 args are "start", "end" and "num"): - x.linspace (x0, x1, 100); + x.linspace (g1x0, g1x1, 100); // Logistic functions. Args are parameters to the function are (xoffset, alpha) std::stringstream lftag; - lftag << "m=" << m << ", ofst=" << ofst << ", " << unicode::toUtf8(unicode::alpha) << "=" << alpha; // A dataset tag + lftag << "k=" << k << ", x" << unicode::toUtf8(unicode::subs0) << "=" << x0; // A dataset tag // vvec::logistic() returns a new vvec with the logistic function-transformed values: - gv->setdata (x, (x*m).logistic(ofst, alpha), lftag.str()); - + gv->setdata (x, x.logistic (k, x0), lftag.str()); + gv->ylabel = "f(x)"; // finalize() makes the GraphVisual compute the vertices of the OpenGL model gv->finalize(); // Add the GraphVisual OpenGL model to the Visual scene, transferring ownership of the @@ -47,7 +46,9 @@ int main() morph::vvec x2; x2.linspace (0, 1, 100); gv2->setlimits (0,1,0,1); - gv2->setdata (x2, (x2*m).logistic(ofst, alpha), lftag.str()); + gv2->setdata (x2, x2.logistic(k, x0), lftag.str()); + gv2->ylabel = "f(x)"; + gv2->finalize(); morph::GraphVisual* gv2ptr = v.addVisualModel (gv2); @@ -60,19 +61,42 @@ int main() // Update from config file with every render so that changes in the file are immediately reflected in the graph. try { morph::Config conf ("../examples/graph_logist2.json"); - ofst = conf.get ("ofst", 4.0); - alpha = conf.get ("alpha", 10.0); - x0 = conf.get ("x0", -10.0); - x1 = conf.get ("x1", 10.0); - m = conf.get ("m", 12.0); + k = conf.get ("k", 10.0); + x0 = conf.get ("x0", 4.0); + //g1x0 = conf.get ("g1x0", -10.0); // These aren't reset in graph1. + //g1x1 = conf.get ("g1x1", 10.0); std::stringstream newtag; - newtag << "m=" << m << ", ofst=" << ofst << ", " << unicode::toUtf8(unicode::alpha) << "=" << alpha; + newtag << "k=" << k << ", x" << unicode::toUtf8(unicode::subs0) << "=" << x0; + gvptr->clearTexts(); // Update the graphs via their non-owning pointers - gvptr->update (x, (x*m).logistic(ofst, alpha), newtag.str(), 0); - gv2ptr->update (x2, (x2*m).logistic(ofst, alpha), newtag.str(), 0); - // Hmm - on update, the graph legends don't change. Can I fix? + gvptr->update (x, x.logistic(k, x0), newtag.str(), 0); + // Show general eqn here + std::stringstream eqngen; + eqngen << "f(x) = 1 / [1 + exp (-k(x - x"<< unicode::toUtf8(unicode::subs0) << ")]"; + gvptr->addLabel (eqngen.str(), morph::vec({0.1f, -0.3f, 0.0f}), morph::TextFeatures(0.05f)); + + // Remove label and existing legends + gv2ptr->clearTexts(); + // Update legend + gv2ptr->update (x2, x2.logistic(k, x0), newtag.str(), 0); + // Add a new eqn label + std::stringstream ktxt; + if (k != 1.0) { ktxt << k; } + std::stringstream brtxt; + std::stringstream ostxt; + if (x0 != 0.0) { + brtxt << "("; + if (x0 > 0.0) { + ostxt << " - " << x0 <<")"; + } else { + ostxt << " + " << -x0 <<")"; + } + } + std::stringstream eqn; + eqn << "f(x) = 1 / [1 + exp (-"<< ktxt.str() << brtxt.str() << "x" << ostxt.str() << ")]"; + gv2ptr->addLabel (eqn.str(), morph::vec({0.1f, -0.3f, 0.0f}), morph::TextFeatures(0.05f)); if (shown_error) { std::cout << "JSON parsed successfully\n"; diff --git a/examples/graph_logist2.json b/examples/graph_logist2.json index 996e2c9a..80fc4ff9 100644 --- a/examples/graph_logist2.json +++ b/examples/graph_logist2.json @@ -1,7 +1,6 @@ { - "x0" : -10, - "x1" : 10, - "m" : 12, - "ofst" : 4, - "alpha" : 10 + "g1x0" : -10, + "g1x1" : 10, + "k" : 1, + "x0" : 0 } From b462c2fb63691644c9b3585890b83f815bda902b Mon Sep 17 00:00:00 2001 From: Seb James Date: Wed, 27 Sep 2023 12:46:47 +0100 Subject: [PATCH 3/3] Trivial tweaks --- examples/graph_logist.cpp | 4 ++-- examples/graph_logist2.cpp | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/examples/graph_logist.cpp b/examples/graph_logist.cpp index 82851728..a5c5bffe 100644 --- a/examples/graph_logist.cpp +++ b/examples/graph_logist.cpp @@ -2,10 +2,12 @@ #include #include #include +#include // Make an equation string for the legend std::string make_legend_str (double k, double x0) { + // We'll use morphologica's awesome unicode chars for the subscript 0 on x0 using morph::unicode; std::stringstream ktxt; if (k != 1.0) { ktxt << k; } @@ -27,8 +29,6 @@ std::string make_legend_str (double k, double x0) int main() { - // We'll use morphologica's awesome unicode chars - using morph::unicode; // Set up a morph::Visual 'scene environment'. morph::Visual v(1024, 768, "Logistic functions"); // Create a GraphVisual object (obtaining a unique_ptr to the object) with a spatial offset within the scene of 0,0,0 diff --git a/examples/graph_logist2.cpp b/examples/graph_logist2.cpp index bf24d37b..05b44d43 100644 --- a/examples/graph_logist2.cpp +++ b/examples/graph_logist2.cpp @@ -63,21 +63,19 @@ int main() morph::Config conf ("../examples/graph_logist2.json"); k = conf.get ("k", 10.0); x0 = conf.get ("x0", 4.0); - //g1x0 = conf.get ("g1x0", -10.0); // These aren't reset in graph1. - //g1x1 = conf.get ("g1x1", 10.0); std::stringstream newtag; newtag << "k=" << k << ", x" << unicode::toUtf8(unicode::subs0) << "=" << x0; + // Remove label and existing legends with VisualModel::clearTexts() gvptr->clearTexts(); // Update the graphs via their non-owning pointers gvptr->update (x, x.logistic(k, x0), newtag.str(), 0); - // Show general eqn here + // Show the general eqn by adding a label below the first graph std::stringstream eqngen; eqngen << "f(x) = 1 / [1 + exp (-k(x - x"<< unicode::toUtf8(unicode::subs0) << ")]"; gvptr->addLabel (eqngen.str(), morph::vec({0.1f, -0.3f, 0.0f}), morph::TextFeatures(0.05f)); - // Remove label and existing legends gv2ptr->clearTexts(); // Update legend gv2ptr->update (x2, x2.logistic(k, x0), newtag.str(), 0); @@ -94,6 +92,7 @@ int main() ostxt << " + " << -x0 <<")"; } } + // Show the specific equation on the second graph std::stringstream eqn; eqn << "f(x) = 1 / [1 + exp (-"<< ktxt.str() << brtxt.str() << "x" << ostxt.str() << ")]"; gv2ptr->addLabel (eqn.str(), morph::vec({0.1f, -0.3f, 0.0f}), morph::TextFeatures(0.05f));