aitech-ium/IUM_07.Sacred.ipynb
2022-04-25 11:15:16 +02:00

72 KiB
Raw Blame History

Logo 1

Inżynieria uczenia maszynowego

7. Sacred [laboratoria]

Tomasz Ziętkiewicz (2021)

Logo 2

Sacred

Every experiment is sacred
Every experiment is great
If an experiment is wasted
God gets quite irate

https://github.com/IDSIA/sacred / Sens życia według Monty Pythona

  • Przeprowadzanie eksperymentów (zmiana parametrów, trenowanie, ewaluacja) uczenia maszynowego jest kosztowne i czasochłonne
  • Dlatego warto przeprowadzać je w zorganizowany sposób
  • I tak, żebyśmy mogli powtórzyć / odtworzyć raz uzyskane wyniki

Sacred is a tool to help you:

  • configure
  • organize
  • log
  • reproduce experiments.

It is designed to do all the tedious overhead work that you need to do around your actual experiment in order to:

  • keep track of all the parameters of your experiment
  • easily run your experiment for different settings
  • save configurations for individual runs in a database
  • reproduce your results

https://github.com/IDSIA/sacred

  • ConfigScopes A very convenient way of the local variables in a function to define the parameters your experiment uses.
  • Config Injection You can access all parameters of your configuration from every function. They are automatically injected by name.
  • Command-line interface You get a powerful command-line interface for each experiment that you can use to change parameters and run different variants.
  • Observers Sacred provides Observers that log all kinds of information about your experiment, its dependencies, the configuration you used, the machine it is run on, and of course the result. These can be saved to a MongoDB, for easy access later.
  • Automatic seeding helps controlling the randomness in your experiments, such that the results remain reproducible.

Instalacja

!pip3 install sacred
Collecting sacred
  Cache entry deserialization failed, entry ignored
  Cache entry deserialization failed, entry ignored
  Downloading https://files.pythonhosted.org/packages/f4/8c/b99f668e8ca9747dcd374bb46cac808e58f3cb8e446df1b3e667f6be9778/sacred-0.8.2-py2.py3-none-any.whl (106kB)
    100% |████████████████████████████████| 112kB 1.2MB/s ta 0:00:01
[?25hCollecting py-cpuinfo>=4.0 (from sacred)
  Cache entry deserialization failed, entry ignored
  Cache entry deserialization failed, entry ignored
  Downloading https://files.pythonhosted.org/packages/e6/ba/77120e44cbe9719152415b97d5bfb29f4053ee987d6cb63f55ce7d50fadc/py-cpuinfo-8.0.0.tar.gz (99kB)
    100% |████████████████████████████████| 102kB 1.4MB/s a 0:00:01
[?25hCollecting wrapt<2.0,>=1.0 (from sacred)
  Cache entry deserialization failed, entry ignored
  Downloading https://files.pythonhosted.org/packages/ba/8c/3d3dff02ae905157ba417b801f4a7aa4e6fedbc43882e9c765b7aae438ac/wrapt-1.14.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (74kB)
    100% |████████████████████████████████| 81kB 2.0MB/s ta 0:00:01
[?25hCollecting jsonpickle<2.0,>=1.2 (from sacred)
  Cache entry deserialization failed, entry ignored
  Cache entry deserialization failed, entry ignored
  Downloading https://files.pythonhosted.org/packages/e9/ec/35910cf6ab87f8a013036f01f732f871a23b6058123a7bd0c7b08fbbc937/jsonpickle-1.5.2-py2.py3-none-any.whl
Collecting colorama>=0.4 (from sacred)
  Cache entry deserialization failed, entry ignored
  Cache entry deserialization failed, entry ignored
  Downloading https://files.pythonhosted.org/packages/44/98/5b86278fbbf250d239ae0ecb724f8572af1c91f4a11edf4d36a206189440/colorama-0.4.4-py2.py3-none-any.whl
Collecting munch<3.0,>=2.0.2 (from sacred)
  Cache entry deserialization failed, entry ignored
  Cache entry deserialization failed, entry ignored
  Downloading https://files.pythonhosted.org/packages/cc/ab/85d8da5c9a45e072301beb37ad7f833cd344e04c817d97e0cc75681d248f/munch-2.5.0-py2.py3-none-any.whl
Collecting packaging>=18.0 (from sacred)
  Cache entry deserialization failed, entry ignored
  Using cached https://files.pythonhosted.org/packages/05/8e/8de486cbd03baba4deef4142bd643a3e7bbe954a784dc1bb17142572d127/packaging-21.3-py3-none-any.whl
Collecting GitPython (from sacred)
  Cache entry deserialization failed, entry ignored
  Downloading https://files.pythonhosted.org/packages/55/60/f884f01eef2a7255875862ec1b12d57d74113ec6e8d9e16c4d254cd6aa3c/GitPython-3.1.20-py3-none-any.whl (178kB)
    100% |████████████████████████████████| 184kB 1.9MB/s ta 0:00:01
[?25hCollecting docopt<1.0,>=0.3 (from sacred)
  Cache entry deserialization failed, entry ignored
  Cache entry deserialization failed, entry ignored
  Downloading https://files.pythonhosted.org/packages/a2/55/8f8cab2afd404cf578136ef2cc5dfb50baa1761b68c9da1fb1e4eed343c9/docopt-0.6.2.tar.gz
Collecting importlib-metadata; python_version < "3.8" (from jsonpickle<2.0,>=1.2->sacred)
  Cache entry deserialization failed, entry ignored
  Using cached https://files.pythonhosted.org/packages/a0/a1/b153a0a4caf7a7e3f15c2cd56c7702e2cf3d89b1b359d1f1c5e59d68f4ce/importlib_metadata-4.8.3-py3-none-any.whl
Collecting six (from munch<3.0,>=2.0.2->sacred)
  Using cached https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl
Collecting pyparsing!=3.0.5,>=2.0.2 (from packaging>=18.0->sacred)
  Cache entry deserialization failed, entry ignored
  Using cached https://files.pythonhosted.org/packages/d9/41/d9cfb4410589805cd787f8a82cddd13142d9bf7449d12adf2d05a4a7d633/pyparsing-3.0.8-py3-none-any.whl
Collecting gitdb<5,>=4.0.1 (from GitPython->sacred)
  Downloading https://files.pythonhosted.org/packages/a3/7c/5d747655049bfbf75b5fcec57c8115896cb78d6fafa84f6d3ef4c0f13a98/gitdb-4.0.9-py3-none-any.whl (63kB)
    100% |████████████████████████████████| 71kB 1.9MB/s ta 0:00:01
[?25hCollecting typing-extensions>=3.7.4.3; python_version < "3.10" (from GitPython->sacred)
  Using cached https://files.pythonhosted.org/packages/45/6b/44f7f8f1e110027cf88956b59f2fad776cca7e1704396d043f89effd3a0e/typing_extensions-4.1.1-py3-none-any.whl
Collecting zipp>=0.5 (from importlib-metadata; python_version < "3.8"->jsonpickle<2.0,>=1.2->sacred)
  Cache entry deserialization failed, entry ignored
  Using cached https://files.pythonhosted.org/packages/bd/df/d4a4974a3e3957fd1c1fa3082366d7fff6e428ddb55f074bf64876f8e8ad/zipp-3.6.0-py3-none-any.whl
Collecting smmap<6,>=3.0.1 (from gitdb<5,>=4.0.1->GitPython->sacred)
  Cache entry deserialization failed, entry ignored
  Downloading https://files.pythonhosted.org/packages/6d/01/7caa71608bc29952ae09b0be63a539e50d2484bc37747797a66a60679856/smmap-5.0.0-py3-none-any.whl
Building wheels for collected packages: py-cpuinfo, docopt
  Running setup.py bdist_wheel for py-cpuinfo ... [?25ldone
[?25h  Stored in directory: /home/tomek/.cache/pip/wheels/2e/15/f5/aa2a056d223903b52cf4870134e3a01df0c723816835dd08db
  Running setup.py bdist_wheel for docopt ... [?25ldone
[?25h  Stored in directory: /home/tomek/.cache/pip/wheels/9b/04/dd/7daf4150b6d9b12949298737de9431a324d4b797ffd63f526e
Successfully built py-cpuinfo docopt
Installing collected packages: py-cpuinfo, wrapt, typing-extensions, zipp, importlib-metadata, jsonpickle, colorama, six, munch, pyparsing, packaging, smmap, gitdb, GitPython, docopt, sacred
Successfully installed GitPython-3.1.20 colorama-0.4.4 docopt-0.6.2 gitdb-4.0.9 importlib-metadata-4.8.3 jsonpickle-1.5.2 munch-2.5.0 packaging-21.3 py-cpuinfo-8.0.0 pyparsing-3.0.8 sacred-0.8.2 six-1.16.0 smmap-5.0.0 typing-extensions-4.1.1 wrapt-1.14.0 zipp-3.6.0

Funkcja main

%%writefile sacred_hello.py
from sacred import Experiment

ex = Experiment()

@ex.automain
def my_main():
    print('Witaj świecie!')
Overwriting sacred_hello.py
!python3 IUM_07/sacred_hello.py
WARNING - sacred_hello - No observers have been added to this run
INFO - sacred_hello - Running command 'my_main'
INFO - sacred_hello - Started
Witaj świecie!
INFO - sacred_hello - Completed after 0:00:00
Co się dzieje w kodzie powyżej?
  1. Tworzymy obiekt klasy Experiment
  2. Dekorujemy funkcję "my_main" dekoratorem automain Dzięki temu:
    • otrzymujemy interfejs CLI, m.in. do kontrolowania poziomu logowania, przekazywania parametrów itp.
    • oznaczamy funkcję "my_main" jako główną funkcję, która będzie wywoływana podczas wykonywania eksperymentu
    • funkcja oznaczona jako główna musi być ostatnią funkcją zdefiniowaną w pliku!
Co nam daje interejs CLI:
!python3 IUM_07/sacred_hello.py -h
Usage:
  sacred_hello.py [(with UPDATE...)] [options]
  sacred_hello.py help [COMMAND]
  sacred_hello.py (-h | --help)
  sacred_hello.py COMMAND [(with UPDATE...)] [options]



Options:
  -b VALUE --beat-interval=VALUE  Set the heart-beat interval for this run.  Time
                                between two heartbeat events is measured in
                                seconds.
  -C VALUE --capture=VALUE      Control the way stdout and stderr are captured.
                                The argument value must be one of [no, sys, fd]
  -c VALUE --comment=VALUE      Add a comment to this run.
  -d --debug                    Set this run to debug mode.  Suppress warnings
                                about missing observers and don't filter the
                                stacktrace. Also enables usage with ipython
                                `--pdb`.
  -e --enforce_clean            Fail if any version control repository is
                                dirty.
  -F VALUE --file_storage=VALUE  Add a file-storage observer to the experiment.
                                The value of the arguement should be the base-
                                directory to write the runs to
  -f --force                    Disable warnings about suspicious changes for
                                this run.
  -h --help                     Print this help message and exit.
  -l VALUE --loglevel=VALUE     Set the LogLevel.  Loglevel either as 0 - 50 or
                                as string: DEBUG(10), INFO(20), WARNING(30),
                                ERROR(40), CRITICAL(50)
  -m VALUE --mongo_db=VALUE     Add a MongoDB Observer to the experiment.  The
                                argument value is the database specification.
                                Should be in the form:  `[host:port:]db_name[.c
                                ollection[:id]][!priority]`
  -n VALUE --name=VALUE         Set the name for this run.
  -D --pdb                      Automatically enter post-mortem debugging with
                                pdb on failure.
  -p --print-config             Always print the configuration first.
  -P VALUE --priority=VALUE     Sets the priority for a queued up experiment.
                                `--priority=NUMBER` The number represent the
                                priority for this run.
  -q --queue                    Only queue this run, do not start it.
  -S VALUE --s3=VALUE           Add a S3 File observer to the experiment.  The
                                argument value should be
                                `s3://<bucket>/path/to/exp`.
  -s VALUE --sql=VALUE          Add a SQL Observer to the experiment.  The
                                typical form is:
                                dialect://username:password@host:port/database
  -t VALUE --tiny_db=VALUE      Add a TinyDB Observer to the experiment.  The
                                argument is the path to be given to the
                                TinyDbObserver.
  -u --unobserved               Ignore all observers for this run.


Arguments:
  COMMAND   Name of command to run (see below for list of commands)
  UPDATE    Configuration assignments of the form foo.bar=17


Commands:
  print_config         Print the updated configuration and exit.
  print_dependencies   Print the detected source-files and dependencies.
  save_config          Store the updated configuration in a file.
  print_named_configs  Print the available named configs and exit.
  my_main              

Konfiguracje

  • Konfiguracje pozwalają nam sparametryzować wywołania eksperymentu.
  • Ułatwiają przekazywanie parametrów - zmienne z konfiguracji są wstrzykiwane do funkcji wywoływanych
  • Mogą być automatycznie zapisywane (dzięki czemu możemy śledzić jak zmieniały się parametry i jaki miały wpływ na wyniki)
  • Konfigurację można stworzyć w jeden z 3 sposobów:
    • używając config scopes (z dekoratorem @config)
    • jako słownik
    • wczytując ją z pliku

Konfiguracje - config scopes

Jeśli oznaczymy jakąś funkcję dekoratorem @config, to zostanie ona uruchoniona przed wywołaniem eksperymentu i wszystkie jej lokalne zmienne, które da się zserializować jako json, zostaną dodane do konfiguracji. Potem ich wartości zostaną wstrzyknięte do innych funkcji wywoływanych w eksperymencie.

from sacred import Experiment

exint = Experiment("sacred_scopes", interactive=True) #Jeśli wykonujemy interaktywnie (w konsoli Pythona albo w Jupyter):
# - musimy podać nazwę eksperymentu (domyślnie jako nazwa używana jest nazwa pliku źródłowego)
# - musimy dodać parametr "interactive=True"
# - zamiast dekoratora "@ex.automain" używamy "@ex.main"

@exint.config
def my_config():
    recipient = "Świecie"
    greeting = "Witaj"
    message = "{0} {1}!".format(greeting, recipient)


@exint.main
def my_main(message):
    print(message)
exint.run()
WARNING - sacred_scopes - No observers have been added to this run
INFO - sacred_scopes - Running command 'my_main'
INFO - sacred_scopes - Started
INFO - sacred_scopes - Completed after 0:00:00
Witaj Świecie!
<sacred.run.Run at 0x7f641e9ddba8>
Możemy podejrzeć wartości zmiennych w konfiguracji:
my_config()
{'recipient': 'Świecie', 'greeting': 'Witaj', 'message': 'Witaj Świecie!'}

Parametry możemy podejrzeć i modyfikować z poziomu CLI

  • wartości podane w CLI nadpiszą te podane w kodzie
# %load IUM_07/sacred_scopes.py
from sacred import Experiment

ex = Experiment()

@ex.config
def my_config():
    recipient = "Świecie"
    greeting = "Witaj"
    message = "{0} {1}!".format(greeting, recipient)

@ex.automain
def my_main(message):
    print(message)
!python3 IUM_07/sacred_scopes.py with 'recipient=Przygodo'
WARNING - sacred_scopes - No observers have been added to this run
INFO - sacred_scopes - Running command 'my_main'
INFO - sacred_scopes - Started
Witaj Przygodo!
INFO - sacred_scopes - Completed after 0:00:00
!python3 IUM_07/sacred_scopes.py print_config
INFO - sacred_scopes - Running command 'print_config'
INFO - sacred_scopes - Started
Configuration (modified, added, typechanged, doc):
  greeting = 'Witaj'
  message = 'Witaj Świecie!'
  recipient = 'Świecie'
  seed = 539001265                   # the random seed for this experiment
INFO - sacred_scopes - Completed after 0:00:00
!python IUM_07/sacred_scopes.py print_config with 'recipient=Przygodo'
INFO - sacred_scopes - Running command 'print_config'
INFO - sacred_scopes - Started
Configuration (modified, added, typechanged, doc):
  greeting = 'Witaj'
  message = 'Witaj Przygodo!'
  recipient = 'Przygodo'
  seed = 215765170                   # the random seed for this experiment
INFO - sacred_scopes - Completed after 0:00:00

Wczytywanie konfiguracji z pliku

# %load IUM_07/config.json
{
    "recipient": "samotności",
    "greeting": "Żegnaj"
}
{'recipient': 'samotności', 'greeting': 'Żegnaj'}
from sacred import Experiment

ex = Experiment("sacred_scopes", interactive=True) #Jeśli wykonujemy interaktywnie (w konsoli Pythona albo w Jupyter):
# - musimy podać nazwę eksperymentu (domyślnie jako nazwa używana jest nazwa pliku źródłowego)
# - musimy dodać parametr "interactive=True"
# - zamiast "automain" używamy parametru "main"

@ex.config
def my_config():
    recipient = "Świecie"
    greeting = "Witaj"

ex.add_config("IUM_07/config.json")


@ex.main
def my_main(recipient, greeting):
    print("{0} {1}!".format(greeting, recipient))
r = ex.run()
WARNING - sacred_scopes - No observers have been added to this run
INFO - sacred_scopes - Running command 'my_main'
INFO - sacred_scopes - Started
INFO - sacred_scopes - Completed after 0:00:00
Żegnaj samotności!
r.config
{'recipient': 'samotności', 'greeting': 'Żegnaj', 'seed': 529757761}

Możemy modyfikować części konfiguracji bezpośrednio przed wywołaniem

r = ex.run(config_updates={"recipient":"nudo"})
WARNING - sacred_scopes - No observers have been added to this run
INFO - sacred_scopes - Running command 'my_main'
INFO - sacred_scopes - Started
INFO - sacred_scopes - Completed after 0:00:00
Żegnaj nudo!

Wstrzykiwanie zależności

  • Oprócz funkcji głównej, wartości z konfiguracji są też wstrzykiwane do funkcji udekorowanych dekoratorem @ex.capture
  • Możemy korzystać w nich ze specjalnych parametrów, np.:
    • _log - daje nam dostęp do obiektu logera (więcej: logowanie)
    • _run - daje dostęp do obiektu reprezentującego aktualne wywołanie eksperymentu (przykład później)
from sacred import Experiment

ex = Experiment("sacred_scopes", interactive=True)

@ex.config
def my_config():
    recipient = "Świecie"
    greeting = "Witaj"

@ex.capture
def prepare_message(recipient, greeting, _log):
    _log.info("Enterred prepare_message")
    return "{0} {1}!".format(greeting, recipient)

@ex.main
def my_main():
    print(prepare_message()) ## Nie musimy przekazywać wartości
    
ex.run()
WARNING - sacred_scopes - No observers have been added to this run
INFO - sacred_scopes - Running command 'my_main'
INFO - sacred_scopes - Started
INFO - prepare_message - Enterred prepare_message
INFO - sacred_scopes - Completed after 0:00:00
Witaj Świecie!
<sacred.run.Run at 0x7f423c40d820>

Obserwowanie eksperymentów

Sacred zapisuje szereg informacji na temat każdego eksperymentu:

  • czas wykonania
  • konfigurację
  • tekst zwrócony na stdout/stderr
  • błędy, jeśli wystąpiły
  • podstawowe informacje o środowisku (maszynie), na której przeprowadzono eksperyment
  • użyte pliki źródłowe
  • użyte zależności i ich wersje
  • pliki otwarte za pomocą ex.open_resource
  • pliki dodane za pomocą ex.add_artifact

Obserwowane infromacje mogą zostać zapisane za pomocą jednego z obserwatorów:

  • Mongo Observer - zapisuje dane w MongoDB
  • File Storage Observer - zapisuje dane lokalnie w pliku
  • TinyDB Observer - korzysta z lokalnej bazy zapisanej w pliku JSON
  • SQL Observer - przechowuje informacje w bazie SQL
  • S3 Observer - korzysta z AWS S3
  • gcs_observer - korzysta z Google Cloud Storage
  • Queue Observer - rodzaj lokalnego bufora nakładanego na jeden z powyższych
  • Slack Observer - używany do powiadomień wysyłanych na komunikator Slack
  • Telegram Observer - używany do powiadomień wysyłanych na komunikator Telegram

File storage observer

  • zapisuje informacje o eksperymencie w lokalnych plikach
  • można go dodać tak: ex.observers.append(FileStorageObserver('my_runs_directory')), gdzie my_runs_directory to ścieżka, gdzie będą zapisywane informacje o eksperymentach
%%writefile IUM_07/file_observer.py
from sacred.observers import FileStorageObserver
from sacred import Experiment

ex = Experiment("file_observer")

ex.observers.append(FileStorageObserver('my_runs'))

@ex.config
def my_config():
    recipient = "Świecie"
    greeting = "Witaj"

@ex.capture
def prepare_message(recipient, greeting):
    return "{0} {1}!".format(greeting, recipient)

@ex.automain
def my_main(recipient, greeting):
    print(prepare_message()) ## Nie musimy przekazywać wartości
Overwriting IUM_07/file_observer.py
!python3 IUM_07/file_observer.py
INFO - file_observer - Running command 'my_main'
INFO - file_observer - Started run with ID "1"
Witaj Świecie!
INFO - file_observer - Completed after 0:00:00

Zobaczmy jakie informacje zostały zapisane

!ls -l my_runs
total 0
drwxr-xr-x 1 tomek tomek 512 Apr 25 09:51 1
drwxr-xr-x 1 tomek tomek 512 Apr 25 09:51 _sources
!ls -l my_runs/1
total 4
-rw-r--r-- 1 tomek tomek   77 Apr 25 09:51 config.json
-rw-r--r-- 1 tomek tomek  159 Apr 25 09:51 cout.txt
-rw-r--r-- 1 tomek tomek    2 Apr 25 09:51 metrics.json
-rw-r--r-- 1 tomek tomek 1659 Apr 25 09:51 run.json
# %load my_runs/1/config.json
{
  "greeting": "Witaj",
  "recipient": "\u015awiecie",
  "seed": 805857632
}
!cat my_runs/1/cout.txt
INFO - file_observer - Running command 'my_main'
INFO - file_observer - Started run with ID "1"
Witaj Świecie!
INFO - file_observer - Completed after 0:00:00
# %load my_runs/1/run.json
{
  "artifacts": [],
  "command": "my_main",
  "experiment": {
    "base_dir": "/home/tomek/repos/aitech/aitech-ium/IUM_07",
    "dependencies": [
      "sacred==0.8.2"
    ],
    "mainfile": "file_observer.py",
    "name": "file_observer",
    "repositories": [
      {
        "commit": "3055a4f1c2ef06ea1c29e3d41d862827cede7e2a",
        "dirty": true,
        "url": "git@git.wmi.amu.edu.pl:tzietkiewicz/aitech-ium.git"
      }
    ],
    "sources": [
      [
        "file_observer.py",
        "_sources/file_observer_cd34a0ef4a32fb0a966eaa01ea6371ad.py"
      ]
    ]
  },
  "heartbeat": "2022-04-25T07:51:37.853633",
  "host": {
    "ENV": {},
    "cpu": "Intel(R) Core(TM) i5-4200H CPU @ 2.80GHz",
    "hostname": "ASUSEK",
    "os": [
      "Linux",
      "Linux-4.4.0-19041-Microsoft-x86_64-with-Ubuntu-18.04-bionic"
    ],
    "python_version": "3.6.9"
  },
  "meta": {
    "command": "my_main",
    "options": {
      "--beat-interval": null,
      "--capture": null,
      "--comment": null,
      "--debug": false,
      "--enforce_clean": false,
      "--file_storage": null,
      "--force": false,
      "--help": false,
      "--loglevel": null,
      "--mongo_db": null,
      "--name": null,
      "--pdb": false,
      "--print-config": false,
      "--priority": null,
      "--queue": false,
      "--s3": null,
      "--sql": null,
      "--tiny_db": null,
      "--unobserved": false,
      "COMMAND": null,
      "UPDATE": [],
      "help": false,
      "with": false
    }
  },
  "resources": [],
  "result": null,
  "start_time": "2022-04-25T07:51:37.831461",
  "status": "COMPLETED",
  "stop_time": "2022-04-25T07:51:37.849334"
}
! ls -l my_runs/_sources
## W run.json możemy znaleźć ścieżkę do pliku z źródłami:         "_sources/file_observer_bb0a5c4720d1072b641d23da080696b6.py"
total 4
-rw-rw-r-- 1 tomek tomek 463 kwi 26 10:21 file_observer_bb0a5c4720d1072b641d23da080696b6.py
## Źródła zostały zapisane
# %load my_runs/_sources/file_observer_bb0a5c4720d1072b641d23da080696b6.py
from sacred.observers import FileStorageObserver
from sacred import Experiment

ex = Experiment("file_observer")

ex.observers.append(FileStorageObserver('my_runs'))

@ex.config
def my_config():
    recipient = "Świecie"
    greeting = "Witaj"

@ex.capture
def prepare_message(recipient, greeting):
    return "{0} {1}!".format(greeting, recipient)

@ex.automain
def my_main(recipient, greeting):
    print(prepare_message()) ## Nie musimy przekazywać wartości

Dodawanie własnych informacji

from sacred.observers import FileStorageObserver
from sacred import Experiment
from datetime import datetime

ex = Experiment("file_observer", interactive=True)

ex.observers.append(FileStorageObserver('my_runs'))

@ex.config
def my_config():
    recipient = "Świecie"
    greeting = "Witaj"

### - Do "przechwyconej" przez @ex.capture funkcji prepare_message dodaliśmy specjalny parametr _run
### - Daje on dostęp do obiektu wywołania eksperymentu w trakcie jego wywołania
### - umożliwia m.in. zapisywanie dodatkowych informacji w słowniku info
@ex.capture
def prepare_message(recipient, greeting, _run):
    _run.info["prepare_message_ts"] = str(datetime.now())
    return "{0} {1}!".format(greeting, recipient)

@ex.main
def my_main(recipient, greeting):
    print(prepare_message()) ## Nie musimy przekazywać wartości
    
r = ex.run()
INFO - file_observer - Running command 'my_main'
INFO - file_observer - Started run with ID "2"
INFO - file_observer - Completed after 0:00:00
Witaj Świecie!
cat my_runs/6/info.json
{
  "prepare_message_ts": "2021-04-26 10:39:59.268539"
}

Artefakty

  • Artefakty służą do zapisywania plików, np. z wytrenowanym modelem
  • Plik można zapisać jako artefakt korzystając z : ex.add_artifact()
    ex.add_artifact("model.pb")
    

Obserwator mongo

  • Żeby skorzystać z obserwatora Mongo, musimy mieć dostęp do bazy Mongo.
  • Można ją łatwo "postawić" za pomocą docker-compose .
  • W tym celu wystarczy skopiować katalog examples/docker z repozytorium SACRED i uruchomić docker-compose up - dostaniemy uruchomioną bazę MongoDB i dodatkowo Omniboard . Więcej informacji w dokumentacji
  • Baza taka została już postawiona na serwerze Jenkins, więc pracując na Jenkinsie można skorzystać z lokalnej bazy (localhost:27017)
!pip3 install pymongo
Collecting pymongo
  Downloading https://files.pythonhosted.org/packages/10/3b/46541b4ee3000019b8ef5b1847292ddc77f492c162bc4d49c424db7fc97a/pymongo-4.1.1-cp36-cp36m-manylinux1_x86_64.whl (464kB)
    100% |████████████████████████████████| 471kB 959kB/s ta 0:00:01
[?25hInstalling collected packages: pymongo
Successfully installed pymongo-4.1.1
from sacred.observers import MongoObserver
from sacred import Experiment

ex = Experiment("sacred_scopes", interactive=True)
ex.observers.append(MongoObserver(url='mongodb://mongo_user:mongo_password@localhost:27017',
                                  db_name='sacred'))  # Tutaj podajemy dane uwierzytelniające i nazwę bazy skonfigurowane w pliku .env podczas uruchamiania bazy.
# W przypadku instancji na Jenkinsie url będzie wyglądał następująco: mongodb://mongo_user:mongo_password_IUM_2021@localhost:27017
@ex.config
def my_config():
    recipient = "Świecie"
    greeting = "Witaj"

@ex.capture
def prepare_message(recipient, greeting):
    return "{0} {1}!".format(greeting, recipient)

@ex.main
def my_main(recipient, greeting):
    print(prepare_message()) ## Nie musimy przekazywać wartości
    
ex.run()
INFO - sacred_scopes - Running command 'my_main'
ERROR - sacred_scopes - Failed after 0:00:30!
ERROR - sacred_scopes - Traceback (most recent call last):
  File "/home/tomek/.local/lib/python3.6/site-packages/sacred/run.py", line 235, in __call__
    self._emit_started()
  File "/home/tomek/.local/lib/python3.6/site-packages/sacred/run.py", line 333, in _emit_started
    _id=self._id,
  File "/home/tomek/.local/lib/python3.6/site-packages/sacred/observers/mongo.py", line 258, in started_event
    self.insert()
  File "/home/tomek/.local/lib/python3.6/site-packages/sacred/observers/mongo.py", line 367, in insert
    c.next()["_id"] + 1 if self.runs.count_documents({}, limit=1) else 1
  File "/home/tomek/.local/lib/python3.6/site-packages/pymongo/collection.py", line 1811, in count_documents
    return self._retryable_non_cursor_read(_cmd, session)
  File "/home/tomek/.local/lib/python3.6/site-packages/pymongo/collection.py", line 1816, in _retryable_non_cursor_read
    with client._tmp_session(session) as s:
  File "/usr/lib/python3.6/contextlib.py", line 81, in __enter__
    return next(self.gen)
  File "/home/tomek/.local/lib/python3.6/site-packages/pymongo/mongo_client.py", line 1676, in _tmp_session
    s = self._ensure_session(session)
  File "/home/tomek/.local/lib/python3.6/site-packages/pymongo/mongo_client.py", line 1663, in _ensure_session
    return self.__start_session(True, causal_consistency=False)
  File "/home/tomek/.local/lib/python3.6/site-packages/pymongo/mongo_client.py", line 1608, in __start_session
    self._topology._check_implicit_session_support()
  File "/home/tomek/.local/lib/python3.6/site-packages/pymongo/topology.py", line 519, in _check_implicit_session_support
    self._check_session_support()
  File "/home/tomek/.local/lib/python3.6/site-packages/pymongo/topology.py", line 536, in _check_session_support
    readable_server_selector, self._settings.server_selection_timeout, None
  File "/home/tomek/.local/lib/python3.6/site-packages/pymongo/topology.py", line 229, in _select_servers_loop
    % (self._error_message(selector), timeout, self.description)
pymongo.errors.ServerSelectionTimeoutError: localhost:27017: [Errno 111] Connection refused, Timeout: 30s, Topology Description: <TopologyDescription id: 6266603d3269f60a67f61383, topology_type: Unknown, servers: [<ServerDescription ('localhost', 27017) server_type: Unknown, rtt: None, error=AutoReconnect('localhost:27017: [Errno 111] Connection refused',)>]>

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/tomek/.local/lib/python3.6/site-packages/sacred/run.py", line 429, in _final_call
    getattr(observer, method)(**kwargs)
  File "/home/tomek/.local/lib/python3.6/site-packages/sacred/observers/mongo.py", line 283, in failed_event
    self.final_save(attempts=1)
  File "/home/tomek/.local/lib/python3.6/site-packages/sacred/observers/mongo.py", line 421, in final_save
    os.makedirs(self.failure_dir, exist_ok=True)
  File "/usr/lib/python3.6/os.py", line 205, in makedirs
    head, tail = path.split(name)
  File "/usr/lib/python3.6/posixpath.py", line 107, in split
    p = os.fspath(p)
TypeError: expected str, bytes or os.PathLike object, not NoneType

---------------------------------------------------------------------------
ServerSelectionTimeoutError               Traceback (most recent call last)
<ipython-input-23-95f1b404dde7> in <module>
     19     print(prepare_message()) ## Nie musimy przekazywać wartości
     20 
---> 21 ex.run()

~/.local/lib/python3.6/site-packages/sacred/experiment.py in run(self, command_name, config_updates, named_configs, info, meta_info, options)
    274             command_name, config_updates, named_configs, info, meta_info, options
    275         )
--> 276         run()
    277         return run
    278 

~/.local/lib/python3.6/site-packages/sacred/run.py in __call__(self, *args)
    233         try:
    234             with capture_stdout() as self._output_file:
--> 235                 self._emit_started()
    236                 self._start_heartbeat()
    237                 self._execute_pre_run_hooks()

~/.local/lib/python3.6/site-packages/sacred/run.py in _emit_started(self)
    331                 config=self.config,
    332                 meta_info=self.meta_info,
--> 333                 _id=self._id,
    334             )
    335             if self._id is None:

~/.local/lib/python3.6/site-packages/sacred/observers/mongo.py in started_event(self, ex_info, command, host_info, start_time, config, meta_info, _id)
    256         # save sources
    257         self.run_entry["experiment"]["sources"] = self.save_sources(ex_info)
--> 258         self.insert()
    259         return self.run_entry["_id"]
    260 

~/.local/lib/python3.6/site-packages/sacred/observers/mongo.py in insert(self)
    365                 c = c.sort("_id", pymongo.DESCENDING).limit(1)
    366                 self.run_entry["_id"] = (
--> 367                     c.next()["_id"] + 1 if self.runs.count_documents({}, limit=1) else 1
    368                 )
    369             try:

~/.local/lib/python3.6/site-packages/pymongo/collection.py in count_documents(self, filter, session, comment, **kwargs)
   1809             return result["n"]
   1810 
-> 1811         return self._retryable_non_cursor_read(_cmd, session)
   1812 
   1813     def _retryable_non_cursor_read(self, func, session):

~/.local/lib/python3.6/site-packages/pymongo/collection.py in _retryable_non_cursor_read(self, func, session)
   1814         """Non-cursor read helper to handle implicit session creation."""
   1815         client = self.__database.client
-> 1816         with client._tmp_session(session) as s:
   1817             return client._retryable_read(func, self._read_preference_for(s), s)
   1818 

/usr/lib/python3.6/contextlib.py in __enter__(self)
     79     def __enter__(self):
     80         try:
---> 81             return next(self.gen)
     82         except StopIteration:
     83             raise RuntimeError("generator didn't yield") from None

~/.local/lib/python3.6/site-packages/pymongo/mongo_client.py in _tmp_session(self, session, close)
   1674             return
   1675 
-> 1676         s = self._ensure_session(session)
   1677         if s:
   1678             try:

~/.local/lib/python3.6/site-packages/pymongo/mongo_client.py in _ensure_session(self, session)
   1661             # Don't make implicit sessions causally consistent. Applications
   1662             # should always opt-in.
-> 1663             return self.__start_session(True, causal_consistency=False)
   1664         except (ConfigurationError, InvalidOperation):
   1665             # Sessions not supported.

~/.local/lib/python3.6/site-packages/pymongo/mongo_client.py in __start_session(self, implicit, **kwargs)
   1606         # Raises ConfigurationError if sessions are not supported.
   1607         if implicit:
-> 1608             self._topology._check_implicit_session_support()
   1609             server_session = _EmptyServerSession()
   1610         else:

~/.local/lib/python3.6/site-packages/pymongo/topology.py in _check_implicit_session_support(self)
    517     def _check_implicit_session_support(self):
    518         with self._lock:
--> 519             self._check_session_support()
    520 
    521     def _check_session_support(self):

~/.local/lib/python3.6/site-packages/pymongo/topology.py in _check_session_support(self)
    534             elif not self._description.readable_servers:
    535                 self._select_servers_loop(
--> 536                     readable_server_selector, self._settings.server_selection_timeout, None
    537                 )
    538 

~/.local/lib/python3.6/site-packages/pymongo/topology.py in _select_servers_loop(self, selector, timeout, address)
    227                 raise ServerSelectionTimeoutError(
    228                     "%s, Timeout: %ss, Topology Description: %r"
--> 229                     % (self._error_message(selector), timeout, self.description)
    230                 )
    231 

ServerSelectionTimeoutError: localhost:27017: [Errno 111] Connection refused, Timeout: 30s, Topology Description: <TopologyDescription id: 6266603d3269f60a67f61383, topology_type: Unknown, servers: [<ServerDescription ('localhost', 27017) server_type: Unknown, rtt: None, error=AutoReconnect('localhost:27017: [Errno 111] Connection refused',)>]>

Metryki

  • W trakcie eksperymentu możemy śledzić metryki, np. aktualny loss
  • W tym celu wystarczy:
    • dodać do funkcji udekorowanej @ex.main albo @ex.capure parametr _run
    • potem wywołać np. _run.log_scalar()
from sacred.observers import MongoObserver
from sacred import Experiment
import random
import time

ex = Experiment("sacred_scopes", interactive=True)
ex.observers.append(MongoObserver(url='mongodb://mongo_user:mongo_password@localhost:27017',
                                  db_name='sacred'))  # Tutaj podajemy dane uwierzytelniające i nazwę bazy skonfigurowane w pliku .env podczas uruchamiania bazy.
# W przypadku instancji na Jenkinsie url będzie wyglądał następująco: mongodb://mongo_user:mongo_password_IUM_2021@localhost:27017
@ex.config
def my_config():
    recipient = "Świecie"
    greeting = "Witaj"

@ex.capture
def prepare_message(recipient, greeting):
    return "{0} {1}!".format(greeting, recipient)

@ex.main
def my_main(recipient, greeting, _run):
    print(prepare_message()) ## Nie musimy przekazywać wartości  
    counter = 0
    while counter < 20:
        counter+=1
        value = counter
        ms_to_wait = random.randint(5, 5000)
        time.sleep(ms_to_wait/1000)
        noise = 1.0 + 0.1 * (random.randint(0, 10) - 5)
        # This will add an entry for training.loss metric in every second iteration.
        # The resulting sequence of steps for training.loss will be 0, 2, 4, ...
        if counter % 2 == 0:
           _run.log_scalar("training.loss", value * 1.5 * noise, counter)
        # Implicit step counter (0, 1, 2, 3, ...)
        # incremented with each call for training.accuracy:
        _run.log_scalar("training.accuracy", value * 2 * noise)

ex.run()    
INFO - sacred_scopes - Running command 'my_main'
INFO - sacred_scopes - Started run with ID "9"
Witaj Świecie!
INFO - sacred_scopes - Completed after 0:00:50
<sacred.run.Run at 0x7f423c2de550>

Wartości metryk możemy na żywo śledzić w Omniboard

Zadanie [15 pkt] (do 9 V 2021)

  1. "Owiń" wywołanie swojego eksperymentu za pomocą Sacred, w ten sposób, żeby zapisane zostały [10pkt]:
  • parametry, z którymi wywołany był trening
  • powstały plik z modelem (jako artefakt)
  • kod źródłowy użyty do przeprowadzenia treningu
  • wyniki (np. ostateczny loss albo wyniki ewaluacji)
  1. Wykorzystaj 2 obserwatory [5pkt]:
  • MongoObserver, skorzytaj nastęþującego URL: mongodb://mongo_user:mongo_password_IUM_2021@localhost:27017 (będziesz mógł przeglądać wyniki na http://tzietkiewicz.vm.wmi.amu.edu.pl:9000/sacred)
  • FileObserver - zapisane pliki zarchiwizuj na Jenkinsie jako jego artefakty