diff --git a/docs/guide/bandits.ipynb b/docs/guide/bandits.ipynb index 2859a007..71ce6f12 100644 --- a/docs/guide/bandits.ipynb +++ b/docs/guide/bandits.ipynb @@ -17,7 +17,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -43,7 +43,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -191,7 +191,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -210,7 +210,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -218,9 +218,8 @@ "output_type": "stream", "text": [ "Completed 100% [====================]\n", - "Saved population to file /tmp/tmpshx7uzc1/population.json\n", "saving final population as archive...\n", - "score: 0.8900709673753779\n" + "score: 0.8972961714305525\n" ] } ], @@ -239,7 +238,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -287,7 +286,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -297,7 +296,7 @@ "Search Space\n", "===\n", "terminal_map: {\"ArrayB\": [\"1.00\"], \"ArrayI\": [\"x5\", \"x7\", \"1.00\"], \"ArrayF\": [\"x0\", \"x1\", \"x2\", \"x3\", \"x4\", \"x6\", \"1.00\", \"1.00*MeanLabel\"]}\n", - "terminal_weights: {\"ArrayB\": [-nan], \"ArrayI\": [0.5, 0.5, 0.5], \"ArrayF\": [0.5302013, 0.5300546, 0.55172414, 0.55825245, 0.5, 0.55445546, 0.407767, 0.3604651]}\n", + "terminal_weights: {\"ArrayB\": [-nan], \"ArrayI\": [0.5, 0.5, 0.5], \"ArrayF\": [0.31623933, 0.37096775, 0.28431374, 0.3359375, 0.36923078, 0.3006993, 0.5485714, 0.5503876]}\n", "node_map[ArrayI][[\"ArrayI\", \"ArrayI\"]][SplitBest] = SplitBest, weight = 1\n", "node_map[MatrixF][[\"ArrayF\", \"ArrayF\", \"ArrayF\", \"ArrayF\"]][Logabs] = Logabs, weight = 1\n", "node_map[MatrixF][[\"ArrayF\", \"ArrayF\", \"ArrayF\", \"ArrayF\"]][Exp] = Exp, weight = 1\n", @@ -335,7 +334,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -354,15 +353,15 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "0.19159255921840668\n", - "{'delete': 0.3327217102050781, 'insert': 0.014492753893136978, 'point': 0.47653430700302124, 'subtree': 0.687393069267273, 'toggle_weight_off': 0.40665435791015625, 'toggle_weight_on': 0.019417475908994675}\n" + "0.1598760038614273\n", + "{'delete': 0.272018700838089, 'insert': 0.019999999552965164, 'point': 0.38034459948539734, 'subtree': 0.6300863027572632, 'toggle_weight_off': 0.5979797840118408, 'toggle_weight_on': 0.036269430071115494}\n" ] } ], @@ -388,7 +387,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.1.undefined" + "version": "3.12.2" } }, "nbformat": 4, diff --git a/docs/guide/search_space.ipynb b/docs/guide/search_space.ipynb index 2f01433e..25bde8e4 100644 --- a/docs/guide/search_space.ipynb +++ b/docs/guide/search_space.ipynb @@ -29,18 +29,133 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 11, "id": "b667948a", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idsexracetarget
count993.00000993.000000993.000000993.000000
mean496.000000.4874122.6253788.219092
std286.798710.5000931.7252401.101319
min0.000000.0000000.0000001.337280
25%248.000000.0000001.0000007.836757
50%496.000000.0000003.0000008.404038
75%744.000001.0000004.0000008.810710
max992.000001.0000005.00000011.410597
\n", + "
" + ], + "text/plain": [ + " id sex race target\n", + "count 993.00000 993.000000 993.000000 993.000000\n", + "mean 496.00000 0.487412 2.625378 8.219092\n", + "std 286.79871 0.500093 1.725240 1.101319\n", + "min 0.00000 0.000000 0.000000 1.337280\n", + "25% 248.00000 0.000000 1.000000 7.836757\n", + "50% 496.00000 0.000000 3.000000 8.404038\n", + "75% 744.00000 1.000000 4.000000 8.810710\n", + "max 992.00000 1.000000 5.000000 11.410597" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "import pandas as pd\n", "from pybrush import Dataset, SearchSpace\n", "\n", - "df = pd.read_csv('../examples/datasets/d_enc.csv')\n", - "X = df.drop(columns='label')\n", - "y = df['label']\n", + "df = pd.read_csv('../examples/datasets/d_example_patients.csv')\n", + "X = df.drop(columns='target')\n", + "y = df['target']\n", "\n", + "df.describe()" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "6d563aae", + "metadata": {}, + "outputs": [], + "source": [ "data = Dataset(X,y)\n", "\n", "search_space = SearchSpace(data)" @@ -59,7 +174,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 14, "id": "23d6f552", "metadata": {}, "outputs": [], @@ -93,7 +208,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 15, "id": "a2953719", "metadata": {}, "outputs": [ @@ -101,17 +216,17 @@ "name": "stdout", "output_type": "stream", "text": [ - "Search Space\n", - "===\n", - "terminal_map: {\"ArrayB\": [\"1.00\"], \"ArrayI\": [\"x_5\", \"x_7\", \"1.00\"], \"ArrayF\": [\"x_0\", \"x_1\", \"x_2\", \"x_3\", \"x_4\", \"x_6\", \"1.00\", \"1.00*MeanLabel\"]}\n", - "terminal_weights: {\"ArrayB\": [-nan], \"ArrayI\": [0.011619061, 0.03579926, 0.023709161], \"ArrayF\": [0.6343385, 0.67299956, 0.42711574, 0.8625447, 0.8957853, 0.20750472, 0.6167148, 0.6167148]}\n", + "=== Search space ===\n", + "terminal_map: {\"ArrayI\": [\"x_2\", \"1.00\"], \"ArrayB\": [\"x_1\", \"1.00\"], \"ArrayF\": [\"x_0\", \"1.00\", \"1.00*MeanLabel\"]}\n", + "terminal_weights: {\"ArrayI\": [0.01214596, 0.01214596], \"ArrayB\": [0.026419641, 0.026419641], \"ArrayF\": [0.056145623, 0.056145623, 0.056145623]}\n", + "node_map[ArrayB][[\"ArrayB\", \"ArrayB\"]][SplitBest] = SplitBest, weight = 0.2\n", "node_map[ArrayI][[\"ArrayI\", \"ArrayI\"]][SplitBest] = SplitBest, weight = 0.2\n", "node_map[ArrayF][[\"ArrayF\", \"ArrayF\"]][SplitBest] = SplitBest, weight = 0.2\n", "node_map[ArrayF][[\"ArrayF\", \"ArrayF\"]][Div] = Div, weight = 0.1\n", "node_map[ArrayF][[\"ArrayF\", \"ArrayF\"]][Mul] = Mul, weight = 1\n", "node_map[ArrayF][[\"ArrayF\", \"ArrayF\"]][Sub] = Sub, weight = 0.5\n", "node_map[ArrayF][[\"ArrayF\", \"ArrayF\"]][Add] = Add, weight = 0.5\n", - "===\n" + "\n" ] } ], @@ -129,6 +244,48 @@ "Note also that the default behavior is to give both of these nodes the same weight as specified by the user. " ] }, + { + "cell_type": "markdown", + "id": "ca903d90", + "metadata": {}, + "source": [ + "## Loading datatypes\n", + "\n", + "If you pass a numpy array, Brush will try to infer datatypes based on its values.\n", + "If instead of passing the data directly you rather pass a pandas dataframe, then it will use the data types retrieved from the powerful pandas sniffer to use as its own data type." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "1c8c72c1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "=== Search space ===\n", + "terminal_map: {\"ArrayI\": [\"x_2\", \"1.00\"], \"ArrayB\": [\"x_1\", \"1.00\"], \"ArrayF\": [\"x_0\", \"1.00\", \"1.00*MeanLabel\"]}\n", + "terminal_weights: {\"ArrayI\": [0.01214596, 0.01214596], \"ArrayB\": [0.026419641, 0.026419641], \"ArrayF\": [0.056145623, 0.056145623, 0.056145623]}\n", + "node_map[ArrayB][[\"ArrayB\", \"ArrayB\"]][SplitBest] = SplitBest, weight = 0.2\n", + "node_map[ArrayI][[\"ArrayI\", \"ArrayI\"]][SplitBest] = SplitBest, weight = 0.2\n", + "node_map[ArrayF][[\"ArrayF\", \"ArrayF\"]][SplitBest] = SplitBest, weight = 0.2\n", + "node_map[ArrayF][[\"ArrayF\", \"ArrayF\"]][Div] = Div, weight = 0.1\n", + "node_map[ArrayF][[\"ArrayF\", \"ArrayF\"]][Mul] = Mul, weight = 1\n", + "node_map[ArrayF][[\"ArrayF\", \"ArrayF\"]][Sub] = Sub, weight = 0.5\n", + "node_map[ArrayF][[\"ArrayF\", \"ArrayF\"]][Add] = Add, weight = 0.5\n", + "\n" + ] + } + ], + "source": [ + "data = Dataset(X.values, y.values)\n", + "\n", + "search_space = SearchSpace(data, user_ops)\n", + "search_space.print()" + ] + }, { "cell_type": "markdown", "id": "d662c5a7", diff --git a/src/bandit/thompson.cpp b/src/bandit/thompson.cpp index 93214012..f297c5e9 100644 --- a/src/bandit/thompson.cpp +++ b/src/bandit/thompson.cpp @@ -26,11 +26,39 @@ ThompsonSamplingBandit::ThompsonSamplingBandit(map arms_probs) template std::map ThompsonSamplingBandit::sample_probs(bool update) { + + // from https://stackoverflow.com/questions/4181403/generate-random-number-based-on-beta-distribution-using-boost + // You'll first want to draw a random number uniformly from the + // range (0,1). Given any distribution, you can then plug that number + // into the distribution's "quantile function," and the result is as + // if a random value was drawn from the distribution. + // from https://stackoverflow.com/questions/10358064/random-numbers-from-beta-distribution-c + // The beta distribution is related to the gamma distribution. Let X be a + // random number drawn from Gamma(α,1) and Y from Gamma(β,1), where the + // first argument to the gamma distribution is the shape parameter. + // Then Z=X/(X+Y) has distribution Beta(α,β). + if (update) { + // 1. use a beta distribution based on alphas and betas to sample probabilities + // 2. normalize probabilities so the sum is 1? + + float alpha, beta, X, Y, prob; for (const auto& pair : this->probabilities) { T arm = pair.first; - float prob = static_cast(alphas[arm] - 1) / static_cast(alphas[arm] + betas[arm] - 2); + + alpha = alphas[arm]; + beta = betas[arm]; + + // TODO: stop using boost and use std::gamma_distribution (first, search to see if it is faster) + boost::math::gamma_distribution<> gammaX(alpha); + boost::math::gamma_distribution<> gammaY(beta); + + X = boost::math::quantile(gammaX, Brush::Util::r.rnd_flt()); + Y = boost::math::quantile(gammaY, Brush::Util::r.rnd_flt()); + + prob = X/(X+Y); + this->probabilities[arm] = prob; } } diff --git a/src/bandit/thompson.h b/src/bandit/thompson.h index c6ab3dda..f12fa2e1 100644 --- a/src/bandit/thompson.h +++ b/src/bandit/thompson.h @@ -1,7 +1,14 @@ #include "bandit_operator.h" -// https://www.boost.org/doc/libs/1_85_0/doc/html/boost/random/beta_distribution.html -#include +// #include +// #include + +#include + +// // https://www.boost.org/doc/libs/1_85_0/doc/html/boost/random/beta_distribution.html +// #include + +#include "../util/utils.h" // to use random generator #ifndef THOMPSON_H #define THOMPSON_H @@ -9,9 +16,6 @@ namespace Brush { namespace MAB { -// TODO: rename thompson to proportional. implement thompson in other file -using namespace boost::random; - template class ThompsonSamplingBandit : public BanditOperator { @@ -25,7 +29,6 @@ class ThompsonSamplingBandit : public BanditOperator private: // additional stuff should come here - beta_distribution<> BetaDistribution; std::map alphas; std::map betas; diff --git a/tests/cpp/test_bandit.cpp b/tests/cpp/test_bandit.cpp index 54ba4bc2..ff9e0863 100644 --- a/tests/cpp/test_bandit.cpp +++ b/tests/cpp/test_bandit.cpp @@ -60,7 +60,6 @@ TEST_P(BanditTest, BanditProbabilities) { // things dont change std::map sampledProbs = bandit.sample_probs(true); - EXPECT_EQ(sampledProbs, initialProbs); // Update the bandit with arm 1 and reward 1.0 bandit.update("foo1", 1.0);