From 64a7c8c4b15a0afa7b72b80c0b9232f680525fef Mon Sep 17 00:00:00 2001 From: lhenry15 Date: Thu, 10 Sep 2020 14:55:06 -0500 Subject: [PATCH] update docs Former-commit-id: b4f9391f66a75d85d138cf6c3cbc70c74b2f29bd [formerly b5a8cc6a61eaef56d17bee96aa766bd34ae9f94a] [formerly f9d9933ef930265148d7248a041d0dc85ee7eb45 [formerly 1a4fc54c24a16a35f7fa8fd414e9ddfa8607eaf6]] [formerly 19b7d536d1f13989b01fa23f0b6c24124f9fbea4 [formerly 988ec5c83aa7a9374a67d5ca299af768cbdc6dd2] [formerly 7f480b67834df6503bc462f1d75b27d3e1f85386 [formerly fc11d81413904109e8f5bdc5f3e1c30c514c6cac]]] [formerly f9c3e7e042fd3b917a1b422268054723857ff207 [formerly d2a2ee485d0a74e13b6576e7db8ae8c2655100aa] [formerly c1a3d1dbbef7d762a3aba75300d29fdd2609090e [formerly 8f0498f35266241d3f4903c88677cef6b869ecfc]] [formerly f12d6e39ca5aa104f1c3d2cadb870ed9303d1daf [formerly 8ba54f5192974c0cca14a103c22e68a95f4df35e] [formerly ad523f7ac7ac7f715ccc02caf2a7cba396a655b9 [formerly 43a0aedc832092dfeaa97e0f55273bfe87de4795]]]] [formerly 4b851d9b6a31ca70df14604546b6550f358d32f1 [formerly 964b171b97a34a24073bd07db964cd27e0e459d3] [formerly 91ff1377adf1f4f3a20681b1462032dcc2e4ebfc [formerly b8e31652e5fd407548a4f6b9280c571a92bb799f]] [formerly 13472a5434e929452d9fbb0c31166c3145acee1c [formerly 0d13aa5de2c864f1fdc7f17370c0538d8a7f8936] [formerly a2a009a59703aa7a6c5bb6b5be0f1ce03ac3d2ff [formerly aa96b65c41104c34dab1918f1f7be2bab04c9956]]] [formerly 6efe91fa976b2db1808a2c827083eecbc869e14e [formerly 0e9b285cdcfbf12dbd3646f6c54477079c60bf23] [formerly f9099f57d687df623e261242863d21154f6e0812 [formerly 48c1ed465c0a5fa4a42b54a1335e3bbcc55f18d3]] [formerly a4c38752b228f455193f37333049dac23d11f60d [formerly 3f5372c0d7a4990e42521cdfe037bac26754d90d] [formerly f1fc509223c3176aef589fa966e1034a7d95afd8 [formerly 2c75d3ed66b189216d7a93824a6d20e4d1b28ed6]]]]] [formerly f73e0e79b5f0e8a19d98051d905ef49fcc5610b0 [formerly bb1592d62a5c44662763a2cb76001764260bcfaa] [formerly 9b775b7ce66731579641f1344a060ba6778fb490 [formerly 1a077236633fdfa3aa5006090f137e11c6e15f30]] [formerly 45b8b7a2e7d98786e2882b55d8c1c426d57d006e [formerly f505046a1edd63e329c46ec77a59c4c89285bc81] [formerly 2d91d39c00b5a044f82ff6421ae1ebad025e35e1 [formerly 41f2783fd8ad1c6afdc87da6b78026dcc9c80d75]]] [formerly 98c6897952f7b30f9b0043121d82684d4ff2fc00 [formerly bf39d987b64ba45c91e74047bde53fdf8a896151] [formerly b95d5e1d2fa566cac1e1bacbb152515224a51751 [formerly e99547b3c359bf1232e69d05dd18854696b5301a]] [formerly 36ee095cf597f55378a9762eae2356c9fc078877 [formerly 5f039a7a6a407d9326136350da4380281e690545] [formerly ad2ed53891404805702ab9eb0017553b9b57ef20 [formerly ea94d6e921a298d2d9ae9de5c13cff68823b210f]]]] [formerly 023d2ae8252d83d07e19d5cc75c8ab1f5a03ef6a [formerly e2099c84ff55f717dfd54d6d1e9c0febb5ac51dd] [formerly 385bbb9f020d2cbba3bb0fd01420dfeb55a8be63 [formerly 658d7bc094536181253952e09dba8400cb0f644b]] [formerly aeb9ae3f648abad67dbea26172d0e1a60fefcc3e [formerly 41461da4fbe4426b38017ea7f9e11b4956b25b27] [formerly d2aba9f4bb0ab0b156e0ed2d22e03efc70527fdc [formerly 214e71901b08a5d249d700136025bf9ff418281b]]] [formerly 85bc2482bd8d448aac9093d93962e2e51cfbf4fa [formerly bcdcb18d5a637c2d9a9662e12a1c11b83706cf04] [formerly 063bf00d72e4f1c3bdb53e7a1d1a5f5b9c483d96 [formerly 736726d1b6510a5a814a96841ecfb502eba25d72]] [formerly 3119de7da3cfec37903bfc5ba8c4c5766a245817 [formerly b04f8107faf1b7f7587a2853ec0b7780c3db8e90] [formerly 7d23dc080adbd90e829cc48ff902c41e91be5362 [formerly 8d22f0930b099e25f04611ace88bdef6690dfcfa]]]]]] Former-commit-id: e488b8c4a01487b0342e7bea295330c17e8b0a9e [formerly c0839cf283dfa120d302dab01288b4e7e04fca01] [formerly 35cda6c2222a534eef886a53bb68f4488a8a662d [formerly 21ecbeff1fcca60112e0668c8ceb12c327c40c00]] [formerly 0c39c474d03ff8eb9aba4e0d5ffd76896c210df7 [formerly 753b82ea179137cf754a1bb120438dea73255479] [formerly e1f3995b2f0afb624144c33bc780ba16e51d12a5 [formerly 905df014d2fe3e6862d11d6d0ae53e05329414ec]]] [formerly ce2ecf9247f9a231d31d2a89380f17799de5b1fb [formerly 5d1296adca5bc6c46ab8007f05fb1cf59f3b6676] [formerly c5748d206d06bce06bd83e28c346f74fdca20bdd [formerly 60b93c2611f31e11c982dce1b0d84386176b4f74]] [formerly 67f9fb672c4df74a1c2e21ff738faf98f1a6d4f2 [formerly 261d75c23a2c2d9b41e3366bd1d4f6b1a1178951] [formerly 656522a99c12a616303cbc58c5230b5c224fcceb [formerly 70dd19d77db345bbc8cf0e811a4e70de8ba646b8]]]] [formerly b1f6f39978c7b1b97610a86e8965223100204304 [formerly abb2faee1fec43dcb9908022551f43aee553316b] [formerly 7b8ebcc612f862996a48d6db0a3b4dc0390090c9 [formerly 5e106bbe7d04c89c0120bf5e8a2fd0cd829300a9]] [formerly a887c6ac6703f7fee766efa2f00207244873af63 [formerly 17ec569ae45b2850a92aca50971ed8ebc101d816] [formerly 9d4ffdc32d582e668df2af7bc8b817aa0f8a40d3 [formerly 93ea1a952db3216db85fb0534f43fe07a3be8865]]] [formerly b58006417c350506286b5976ee780c47f7d75b2b [formerly c68fe2a6040af5dfb94e45ff3cacb48a1f3b00a1] [formerly 03f60582a7e3fb4f161b8394010201dccf7ea7cf [formerly ce205f21e2d1f93ac48b05dc5d95982483a867bd]] [formerly 12b8da14983c276372928709ca59b0a7263adc26 [formerly 52ab6ac5509792ba32d62bccfb35b3cbc687f3a3] [formerly 7d23dc080adbd90e829cc48ff902c41e91be5362]]]] Former-commit-id: 9bbe314a259e2c7d58d175bb24b88cdbb417e4fa [formerly debf62c9fbdf3a6e8fc518bea00e7dd727470660] [formerly bee4ae87c815001029e1ca9ac885b671cc3701af [formerly 363e8c4cbb9340683370e40b0b8bbef762a5a152]] [formerly f43cd35c22d2078edf94e2a3b186e8556b16079e [formerly 0453186dc046519343bea0f46b128b208ee1483b] [formerly db37b88820fc55dc61ac36cdeaeba45b1d02be83 [formerly d2122719fcaaa1fda2edd4401aee4bbc2d72af96]]] [formerly a5298f4ead30ccf1b63d5eb6e84b22512bc9e73a [formerly 956a50169cacdd33d34f9a082ae85df326243a00] [formerly a5ff9c9f212b486ae8c8ddf7efa32a94b035540a [formerly 975b32abbb5f528f0c78ba6ae792ae1f45c58a1c]] [formerly bd6266cd7f2be53474add43fb04db21e70fda09d [formerly 3e712311e775ad480e15ce6abc8a09007a1c3a84] [formerly 1a60e83a77286af30c0e5605e6e651c4382570b5 [formerly 3aeaeec27128146b194c9b26478319571e122710]]]] Former-commit-id: 0bd595f9f28265d79894747850e3fd307d9f1e11 [formerly 5328055b46c772fff24317356d5eb01417206a51] [formerly a760df362f25b913926586e7450748eecdb364c6 [formerly 14fa9bb70c614f1be6405304b007d4f4cd5819f3]] [formerly 2268844207020e216c1ecec882c25152c29e01b3 [formerly f92402f2b7839c5863452745b68e459e662c9ceb] [formerly 38144d591e2a34767897cabf0c7fc46f90ea6bd0 [formerly 66df7c3ad8427e7de565941bd1e6775c65ec6f7b]]] Former-commit-id: b8a5d9e66a7dc8e96c495d0336f82eb678a66fef [formerly 1a5590cef5e0ade689868de480367e3b64680aad] [formerly 4122d313d77bf742a1882d3718f81d0f5255033f [formerly ff0ae449df1f869631428b220d1ceb3435cfb517]] Former-commit-id: 2f64bae9a096c4acdd60f94f545bb05d60c4e1cf [formerly 252d5866f86f62d466028e7da98fe89d01f5949d] Former-commit-id: 67c88848996c9a9cd5d404694d133e20889ba722 --- docs/source/conf.py | 13 +- docs/source/doctree.rst | 31 ++ docs/source/getting_started.rst | 595 +++++++++++++++++++++++++++++++++++ docs/source/img/framework.pdf | Bin 0 -> 53363 bytes docs/source/index.rst | 14 +- docs/source/modules.rst | 2 +- docs/source/overview.rst | 101 ++++++ docs/source/tods.rst | 2 +- docs/source/tods.searcher.search.rst | 4 +- 9 files changed, 753 insertions(+), 9 deletions(-) create mode 100644 docs/source/doctree.rst create mode 100644 docs/source/getting_started.rst create mode 100644 docs/source/img/framework.pdf create mode 100644 docs/source/overview.rst diff --git a/docs/source/conf.py b/docs/source/conf.py index 1acf538..08e7ba7 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -28,7 +28,7 @@ def setup(app): # -- Project information ----------------------------------------------------- -project = 'Time Series Outlier Detection System' +project = 'TODS' copyright = '2020, DataLab@Texas A&M University' author = 'DataLab@Texas A&M University' @@ -56,21 +56,30 @@ extensions = [ templates_path = ['_templates'] source_suffix = '.rst' +# The master toctree document. +master_doc = 'doctree' + # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = [] +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = None + # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'alabaster' +html_theme = 'sphinx_rtd_theme' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] +html_sidebars = { + '**': ['fulltoc.html', 'sourcelink.html', 'searchbox.html'] +} diff --git a/docs/source/doctree.rst b/docs/source/doctree.rst new file mode 100644 index 0000000..e358123 --- /dev/null +++ b/docs/source/doctree.rst @@ -0,0 +1,31 @@ +.. rlcard documentation master file, created by + sphinx-quickstart on Thu Sep 5 18:45:31 2019. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +.. toctree:: + :glob: + :caption: Documentation: + + overview + getting_started + + +.. toctree:: + :glob: + :caption: API Documents: + + tods.data_processing + tods.timeseries_processing + tods.feature_analysis + tods.detection_algorithm + tods.reinforcement + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst new file mode 100644 index 0000000..c1f96ba --- /dev/null +++ b/docs/source/getting_started.rst @@ -0,0 +1,595 @@ +Getting Started +=============== + +In this document, we provide some toy examples for getting started. All +the examples in this document and even more examples are available in +`examples/ `__. + +Playing with Random Agents +-------------------------- + +We have set up a random agent that can play randomly on each +environment. An example of applying a random agent on Blackjack is as +follow: + +.. code:: python + + import rlcard + from rlcard.agents import RandomAgent + from rlcard.utils import set_global_seed + + # Make environment + env = rlcard.make('blackjack', config={'seed': 0}) + episode_num = 2 + + # Set a global seed + set_global_seed(0) + + # Set up agents + agent_0 = RandomAgent(action_num=env.action_num) + env.set_agents([agent_0]) + + for episode in range(episode_num): + + # Generate data from the environment + trajectories, _ = env.run(is_training=False) + + # Print out the trajectories + print('\nEpisode {}'.format(episode)) + for ts in trajectories[0]: + print('State: {}, Action: {}, Reward: {}, Next State: {}, Done: {}'.format(ts[0], ts[1], ts[2], ts[3], ts[4])) + +The expected output should look like something as follows: + +:: + + Episode 0 + State: {'obs': array([20, 3]), 'legal_actions': [0, 1]}, Action: 0, Reward: 0, Next State: {'obs': array([15, 3]), 'legal_actions': [0, 1]}, Done: False + State: {'obs': array([15, 3]), 'legal_actions': [0, 1]}, Action: 1, Reward: -1, Next State: {'obs': array([15, 20]), 'legal_actions': [0, 1]}, Done: True + + Episode 1 + State: {'obs': array([15, 5]), 'legal_actions': [0, 1]}, Action: 1, Reward: 1, Next State: {'obs': array([15, 23]), 'legal_actions': [0, 1]}, Done: True + +Note that the states and actions are wrapped by ``env`` in Blackjack. In +this example, the ``[20, 3]`` suggests the current player obtains score +20 while the card that faces up in the dealer’s hand has score 3. Action +0 means “hit” while action 1 means “stand”. Reward 1 suggests the player +wins while reward -1 suggests the dealer wins. Reward 0 suggests a tie. +The above data can be directly fed into a RL algorithm for training. + +Deep-Q Learning on Blackjack +---------------------------- + +The second example is to use Deep-Q learning to train an agent on +Blackjack. We aim to use this example to show how reinforcement learning +algorithms can be developed and applied in our toolkit. We design a +``run`` function which plays one complete game and provides the data for +training RL agents. The example is shown below: + +.. code:: python + + import tensorflow as tf + import os + + import rlcard + from rlcard.agents import DQNAgent + from rlcard.utils import set_global_seed, tournament + from rlcard.utils import Logger + + # Make environment + env = rlcard.make('blackjack', config={'seed': 0}) + eval_env = rlcard.make('blackjack', config={'seed': 0}) + + # Set the iterations numbers and how frequently we evaluate/save plot + evaluate_every = 100 + evaluate_num = 10000 + episode_num = 100000 + + # The intial memory size + memory_init_size = 100 + + # Train the agent every X steps + train_every = 1 + + # The paths for saving the logs and learning curves + log_dir = './experiments/blackjack_dqn_result/' + + # Set a global seed + set_global_seed(0) + + with tf.Session() as sess: + + # Initialize a global step + global_step = tf.Variable(0, name='global_step', trainable=False) + + # Set up the agents + agent = DQNAgent(sess, + scope='dqn', + action_num=env.action_num, + replay_memory_init_size=memory_init_size, + train_every=train_every, + state_shape=env.state_shape, + mlp_layers=[10,10]) + env.set_agents([agent]) + eval_env.set_agents([agent]) + + # Initialize global variables + sess.run(tf.global_variables_initializer()) + + # Init a Logger to plot the learning curve + logger = Logger(log_dir) + + for episode in range(episode_num): + + # Generate data from the environment + trajectories, _ = env.run(is_training=True) + + # Feed transitions into agent memory, and train the agent + for ts in trajectories[0]: + agent.feed(ts) + + # Evaluate the performance. Play with random agents. + if episode % evaluate_every == 0: + logger.log_performance(env.timestep, tournament(eval_env, evaluate_num)[0]) + + # Close files in the logger + logger.close_files() + + # Plot the learning curve + logger.plot('DQN') + + # Save model + save_dir = 'models/blackjack_dqn' + if not os.path.exists(save_dir): + os.makedirs(save_dir) + saver = tf.train.Saver() + saver.save(sess, os.path.join(save_dir, 'model')) + +The expected output is something like below: + +:: + + ---------------------------------------- + timestep | 1 + reward | -0.7342 + ---------------------------------------- + INFO - Agent dqn, step 100, rl-loss: 1.0042707920074463 + INFO - Copied model parameters to target network. + INFO - Agent dqn, step 136, rl-loss: 0.7888197302818298 + ---------------------------------------- + timestep | 136 + reward | -0.1406 + ---------------------------------------- + INFO - Agent dqn, step 278, rl-loss: 0.6946825981140137 + ---------------------------------------- + timestep | 278 + reward | -0.1523 + ---------------------------------------- + INFO - Agent dqn, step 412, rl-loss: 0.62268990278244025 + ---------------------------------------- + timestep | 412 + reward | -0.088 + ---------------------------------------- + INFO - Agent dqn, step 544, rl-loss: 0.69050502777099616 + ---------------------------------------- + timestep | 544 + reward | -0.08 + ---------------------------------------- + INFO - Agent dqn, step 681, rl-loss: 0.61789089441299444 + ---------------------------------------- + timestep | 681 + reward | -0.0793 + ---------------------------------------- + +In Blackjack, the player will get a payoff at the end of the game: 1 if +the player wins, -1 if the player loses, and 0 if it is a tie. The +performance is measured by the average payoff the player obtains by +playing 10000 episodes. The above example shows that the agent achieves +better and better performance during training. The logs and learning +curves are saved in ``./experiments/blackjack_dqn_result/``. + +Running Multiple Processes +-------------------------- + +The environments can be run with multiple processes to accelerate the +training. Below is an example to train DQN on Blackjack with multiple +processes. + +.. code:: python + + ''' An example of learning a Deep-Q Agent on Blackjack with multiple processes + Note that we must use if __name__ == '__main__' for multiprocessing + ''' + + import tensorflow as tf + import os + + import rlcard + from rlcard.agents import DQNAgent + from rlcard.utils import set_global_seed, tournament + from rlcard.utils import Logger + + def main(): + # Make environment + env = rlcard.make('blackjack', config={'seed': 0, 'env_num': 4}) + eval_env = rlcard.make('blackjack', config={'seed': 0, 'env_num': 4}) + + # Set the iterations numbers and how frequently we evaluate performance + evaluate_every = 100 + evaluate_num = 10000 + iteration_num = 100000 + + # The intial memory size + memory_init_size = 100 + + # Train the agent every X steps + train_every = 1 + + # The paths for saving the logs and learning curves + log_dir = './experiments/blackjack_dqn_result/' + + # Set a global seed + set_global_seed(0) + + with tf.Session() as sess: + + # Initialize a global step + global_step = tf.Variable(0, name='global_step', trainable=False) + + # Set up the agents + agent = DQNAgent(sess, + scope='dqn', + action_num=env.action_num, + replay_memory_init_size=memory_init_size, + train_every=train_every, + state_shape=env.state_shape, + mlp_layers=[10,10]) + env.set_agents([agent]) + eval_env.set_agents([agent]) + + # Initialize global variables + sess.run(tf.global_variables_initializer()) + + # Initialize a Logger to plot the learning curve + logger = Logger(log_dir) + + for iteration in range(iteration_num): + + # Generate data from the environment + trajectories, _ = env.run(is_training=True) + + # Feed transitions into agent memory, and train the agent + for ts in trajectories[0]: + agent.feed(ts) + + # Evaluate the performance. Play with random agents. + if iteration % evaluate_every == 0: + logger.log_performance(env.timestep, tournament(eval_env, evaluate_num)[0]) + + # Close files in the logger + logger.close_files() + + # Plot the learning curve + logger.plot('DQN') + + # Save model + save_dir = 'models/blackjack_dqn' + if not os.path.exists(save_dir): + os.makedirs(save_dir) + saver = tf.train.Saver() + saver.save(sess, os.path.join(save_dir, 'model')) + + if __name__ == '__main__': + main() + +Example output is as follow: + +:: + + ---------------------------------------- + timestep | 17 + reward | -0.7378 + ---------------------------------------- + + INFO - Copied model parameters to target network. + INFO - Agent dqn, step 1100, rl-loss: 0.40940183401107797 + INFO - Copied model parameters to target network. + INFO - Agent dqn, step 2100, rl-loss: 0.44971221685409546 + INFO - Copied model parameters to target network. + INFO - Agent dqn, step 2225, rl-loss: 0.65466868877410897 + ---------------------------------------- + timestep | 2225 + reward | -0.0658 + ---------------------------------------- + INFO - Agent dqn, step 3100, rl-loss: 0.48663979768753053 + INFO - Copied model parameters to target network. + INFO - Agent dqn, step 4100, rl-loss: 0.71293979883193974 + INFO - Copied model parameters to target network. + INFO - Agent dqn, step 4440, rl-loss: 0.55871248245239263 + ---------------------------------------- + timestep | 4440 + reward | -0.0736 + ---------------------------------------- + +Training CFR on Leduc Hold’em +----------------------------- + +To show how we can use ``step`` and ``step_back`` to traverse the game +tree, we provide an example of solving Leduc Hold’em with CFR: + +.. code:: python + + import numpy as np + + import rlcard + from rlcard.agents import CFRAgent + from rlcard import models + from rlcard.utils import set_global_seed, tournament + from rlcard.utils import Logger + + # Make environment and enable human mode + env = rlcard.make('leduc-holdem', config={'seed': 0, 'allow_step_back':True}) + eval_env = rlcard.make('leduc-holdem', config={'seed': 0}) + + # Set the iterations numbers and how frequently we evaluate/save plot + evaluate_every = 100 + save_plot_every = 1000 + evaluate_num = 10000 + episode_num = 10000 + + # The paths for saving the logs and learning curves + log_dir = './experiments/leduc_holdem_cfr_result/' + + # Set a global seed + set_global_seed(0) + + # Initilize CFR Agent + agent = CFRAgent(env) + agent.load() # If we have saved model, we first load the model + + # Evaluate CFR against pre-trained NFSP + eval_env.set_agents([agent, models.load('leduc-holdem-nfsp').agents[0]]) + + # Init a Logger to plot the learning curve + logger = Logger(log_dir) + + for episode in range(episode_num): + agent.train() + print('\rIteration {}'.format(episode), end='') + # Evaluate the performance. Play with NFSP agents. + if episode % evaluate_every == 0: + agent.save() # Save model + logger.log_performance(env.timestep, tournament(eval_env, evaluate_num)[0]) + + # Close files in the logger + logger.close_files() + + # Plot the learning curve + logger.plot('CFR') + +In the above example, the performance is measured by playing against a +pre-trained NFSP model. The expected output is as below: + +:: + + Iteration 0 + ---------------------------------------- + timestep | 192 + reward | -1.3662 + ---------------------------------------- + Iteration 100 + ---------------------------------------- + timestep | 19392 + reward | 0.9462 + ---------------------------------------- + Iteration 200 + ---------------------------------------- + timestep | 38592 + reward | 0.8591 + ---------------------------------------- + Iteration 300 + ---------------------------------------- + timestep | 57792 + reward | 0.7861 + ---------------------------------------- + Iteration 400 + ---------------------------------------- + timestep | 76992 + reward | 0.7752 + ---------------------------------------- + Iteration 500 + ---------------------------------------- + timestep | 96192 + reward | 0.7215 + ---------------------------------------- + +We observe that CFR achieves better performance as NFSP. However, CFR +requires traversal of the game tree, which is infeasible in large +environments. + +Having Fun with Pretrained Leduc Model +-------------------------------------- + +We have designed simple human interfaces to play against the pretrained +model. Leduc Hold’em is a simplified version of Texas Hold’em. Rules can +be found `here `__. Example of playing against +Leduc Hold’em CFR model is as below: + +.. code:: python + + import rlcard + from rlcard import models + from rlcard.agents import LeducholdemHumanAgent as HumanAgent + from rlcard.utils import print_card + + # Make environment + # Set 'record_action' to True because we need it to print results + env = rlcard.make('leduc-holdem', config={'record_action': True}) + human_agent = HumanAgent(env.action_num) + cfr_agent = models.load('leduc-holdem-cfr').agents[0] + env.set_agents([human_agent, cfr_agent]) + + print(">> Leduc Hold'em pre-trained model") + + while (True): + print(">> Start a new game") + + trajectories, payoffs = env.run(is_training=False) + # If the human does not take the final action, we need to + # print other players action + final_state = trajectories[0][-1][-2] + action_record = final_state['action_record'] + state = final_state['raw_obs'] + _action_list = [] + for i in range(1, len(action_record)+1): + if action_record[-i][0] == state['current_player']: + break + _action_list.insert(0, action_record[-i]) + for pair in _action_list: + print('>> Player', pair[0], 'chooses', pair[1]) + + # Let's take a look at what the agent card is + print('=============== CFR Agent ===============') + print_card(env.get_perfect_information()['hand_cards'][1]) + + print('=============== Result ===============') + if payoffs[0] > 0: + print('You win {} chips!'.format(payoffs[0])) + elif payoffs[0] == 0: + print('It is a tie.') + else: + print('You lose {} chips!'.format(-payoffs[0])) + print('') + + input("Press any key to continue...") + +Example output is as follow: + +:: + + >> Leduc Hold'em pre-trained model + + >> Start a new game! + >> Agent 1 chooses raise + + =============== Community Card =============== + ┌─────────┐ + │░░░░░░░░░│ + │░░░░░░░░░│ + │░░░░░░░░░│ + │░░░░░░░░░│ + │░░░░░░░░░│ + │░░░░░░░░░│ + │░░░░░░░░░│ + └─────────┘ + =============== Your Hand =============== + ┌─────────┐ + │J │ + │ │ + │ │ + │ ♥ │ + │ │ + │ │ + │ J│ + └─────────┘ + =============== Chips =============== + Yours: + + Agent 1: +++ + =========== Actions You Can Choose =========== + 0: call, 1: raise, 2: fold + + >> You choose action (integer): + +We also provide a running demo of a rule-based agent for UNO. Try it by +running ``examples/uno_human.py``. + +Leduc Hold’em as Single-Agent Environment +----------------------------------------- + +We have wrraped the environment as single agent environment by assuming +that other players play with pre-trained models. The interfaces are +exactly the same to OpenAI Gym. Thus, any single-agent algorithm can be +connected to the environment. An example of Leduc Hold’em is as below: + +.. code:: python + + import tensorflow as tf + import os + import numpy as np + + import rlcard + from rlcard.agents import DQNAgent + from rlcard.agents import RandomAgent + from rlcard.utils import set_global_seed, tournament + from rlcard.utils import Logger + + # Make environment + env = rlcard.make('leduc-holdem', config={'seed': 0, 'single_agent_mode':True}) + eval_env = rlcard.make('leduc-holdem', config={'seed': 0, 'single_agent_mode':True}) + + # Set the iterations numbers and how frequently we evaluate/save plot + evaluate_every = 1000 + evaluate_num = 10000 + timesteps = 100000 + + # The intial memory size + memory_init_size = 1000 + + # Train the agent every X steps + train_every = 1 + + # The paths for saving the logs and learning curves + log_dir = './experiments/leduc_holdem_single_dqn_result/' + + # Set a global seed + set_global_seed(0) + + with tf.Session() as sess: + + # Initialize a global step + global_step = tf.Variable(0, name='global_step', trainable=False) + + # Set up the agents + agent = DQNAgent(sess, + scope='dqn', + action_num=env.action_num, + replay_memory_init_size=memory_init_size, + train_every=train_every, + state_shape=env.state_shape, + mlp_layers=[128,128]) + # Initialize global variables + sess.run(tf.global_variables_initializer()) + + # Init a Logger to plot the learning curve + logger = Logger(log_dir) + + state = env.reset() + + for timestep in range(timesteps): + action = agent.step(state) + next_state, reward, done = env.step(action) + ts = (state, action, reward, next_state, done) + agent.feed(ts) + + if timestep % evaluate_every == 0: + rewards = [] + state = eval_env.reset() + for _ in range(evaluate_num): + action, _ = agent.eval_step(state) + _, reward, done = env.step(action) + if done: + rewards.append(reward) + logger.log_performance(env.timestep, np.mean(rewards)) + + # Close files in the logger + logger.close_files() + + # Plot the learning curve + logger.plot('DQN') + + # Save model + save_dir = 'models/leduc_holdem_single_dqn' + if not os.path.exists(save_dir): + os.makedirs(save_dir) + saver = tf.train.Saver() + saver.save(sess, os.path.join(save_dir, 'model')) diff --git a/docs/source/img/framework.pdf b/docs/source/img/framework.pdf new file mode 100644 index 0000000000000000000000000000000000000000..692da33497122eeaa558a7eb8fd968c5b78272fa GIT binary patch literal 53363 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 aba--QW(qYkF*7v^B_%~qMhXD{0RR85+~{2Z literal 0 KcmV+b0RR6000031 diff --git a/docs/source/index.rst b/docs/source/index.rst index 0a7b6e9..d41d25a 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -3,17 +3,25 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to Time Series Outlier Detection System's documentation! +Welcome to TOD's documentation! ================================================================ .. toctree:: - :maxdepth: 2 + :maxdepth: 4 :caption: Contents: -Indices and tables +API Documents ================== +.. toctree:: + :maxdepth: 4 + :caption: API Documents: + tods.data_processing + tods.timeseries_processing + tods.feature_analysis + tods.detection_algorithm + tods.reinforcement * :ref:`genindex` * :ref:`modindex` diff --git a/docs/source/modules.rst b/docs/source/modules.rst index 0380d02..c2c8ff9 100644 --- a/docs/source/modules.rst +++ b/docs/source/modules.rst @@ -2,6 +2,6 @@ tods ==== .. toctree:: - :maxdepth: 3 + :maxdepth: 4 tods diff --git a/docs/source/overview.rst b/docs/source/overview.rst new file mode 100644 index 0000000..9f402c7 --- /dev/null +++ b/docs/source/overview.rst @@ -0,0 +1,101 @@ +Overview +======== + +Design Principles +~~~~~~~~~~~~~~~~~ + +The toolkit wraps each game by ``Env`` class with easy-to-use +interfaces. The goal of this toolkit is to enable the users to focus on +algorithm development without caring about the environment. The +following design principles are applied when developing the toolkit: + * **Reproducible.** Results on the environments can be reproduced. The same result should be obtained with the same random seed in different runs. + * **Accessible.** The experiences are collected and well organized after each game with easy-to-use interfaces. Uses can conveniently configure state representation, action encoding, reward design, or even the game rules. + * **Scalable.** New card environments can be added conveniently into the toolkit with the above design principles. We also try to minimize the dependencies in the toolkit so that the codes can be easily maintained. + +TODS High-level Design +~~~~~~~~~~~~~~~~~~~~~~~~ + +This document introduces the high-level design for the environments, the +games, and the agents (algorithms). + +.. image:: img/framework.pdf + :width: 800 + + + +Data-Processing +--------------- + +We wrap each game with an ``Env`` class. The responsibility of ``Env`` +is to help you generate trajectories of the games. For developing +Reinforcement Learning (RL) algorithms, we recommend to use the +following interfaces: + +- ``set_agents``: This function tells the ``Env`` what agents will be + used to perform actions in the game. Different games may have a + different number of agents. The input of the function is a list of + ``Agent`` class. For example, + ``env.set_agent([RandomAgent(), RandomAgent()])`` indicates that two + random agents will be used to generate the trajectories. +- ``run``: After setting the agents, this interface will run a complete + trajectory of the game, calculate the reward for each transition, and + reorganize the data so that it can be directly fed into a RL + algorithm. + +For advanced access to the environment, such as traversal of the game +tree, we provide the following interfaces: + +- ``step``: Given the current state, the environment takes one step + forward, and returns the next state and the next player. +- ``step_back``: Takes one step backward. The environment will restore + to the last state. The ``step_back`` is defaultly turned off since it + requires expensively recoeding previous states. To turn it on, set + ``allow_step_back = True`` when ``make`` environments. +- ``get_payoffs``: At the end of the game, this function can be called + to obtain the payoffs for each player. + +We also support single-agent mode and human mode. Examples can be found +in ``examples/``. + +- Single agent mode: single-agent environments are developped by + simulating other players with pre-trained models or rule-based + models. You can enable single-agent mode by + ``rlcard.make(ENV_ID, config={'single_agent_mode':True})``. Then the + ``step`` function will return ``(next_state, reward, done)`` just as + common single-agent environments. ``env.reset()`` will reset the game + and return the first state. + +Games +----- + +Card games usually have similar structures. We abstract some concepts in +card games and follow the same design pattern. In this way, +users/developers can easily dig into the code and change the rules for +research purpose. Specifically, the following classes are used in all +the games: + +- ``Game``: A game is defined as a complete sequence starting from one + of the non-terminal states to a terminal state. +- ``Round``: A round is a part of the sequence of a game. Most card + games can be naturally divided into multiple rounds. +- ``Dealer``: A dealer is responsible for shuffling and allocating a + deck of cards. +- ``Judger``: A judger is responsible for making major decisions at the + end of a round or a game. +- ``Player``: A player is a role who plays cards following a strategy. + +To summarize, in one ``Game``, a ``Dealer`` deals the cards for each +``Player``. In each ``Round`` of the game, a ``Judger`` will make major +decisions about the next round and the payoffs in the end of the game. + +Agents +------ + +We provide examples of several representative algorithms and wrap them +as ``Agent`` to show how a learning algorithm can be connected to the +toolkit. The first example is DQN which is a representative of the +Reinforcement Learning (RL) algorithms category. The second example is +NFSP which is a representative of the Reinforcement Learning (RL) with +self-play. We also provide CFR and DeepCFR which belong to Conterfactual +Regret Minimization (CFR) category. Other algorithms from these three +categories can be connected in similar ways. diff --git a/docs/source/tods.rst b/docs/source/tods.rst index fe07262..942e310 100644 --- a/docs/source/tods.rst +++ b/docs/source/tods.rst @@ -5,7 +5,7 @@ Subpackages ----------- .. toctree:: - :maxdepth: 4 + :maxdepth: 2 tods.data_processing tods.detection_algorithm diff --git a/docs/source/tods.searcher.search.rst b/docs/source/tods.searcher.search.rst index 9c6232a..2504d77 100644 --- a/docs/source/tods.searcher.search.rst +++ b/docs/source/tods.searcher.search.rst @@ -9,7 +9,7 @@ tods.searcher.search.brute\_force\_search module .. automodule:: tods.searcher.search.brute_force_search :members: - :undoc-members: + :noindex: :show-inheritance: Module contents @@ -17,5 +17,5 @@ Module contents .. automodule:: tods.searcher.search :members: - :undoc-members: + :noindex: :show-inheritance: