Skip to content

Commit

Permalink
update zh quick start
Browse files Browse the repository at this point in the history
  • Loading branch information
reiase committed Nov 30, 2022
1 parent fbe91a9 commit 0aedbdb
Show file tree
Hide file tree
Showing 6 changed files with 307 additions and 156 deletions.
134 changes: 134 additions & 0 deletions docs/quick_start.zh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
快速开始
=======

`HyperParameter` 是一个配置参数管理框架,为Python应用提供超参配置与参数调优等功能。可通过如下命令快速安装:

```shell
pip install hyperparameter
```

主要特性:

1. `param_scope` 上下文,向Python应用提供线程安全的、可嵌套的参数管理上下文;提供对象化的树状参数管理,并支持默认值;

```python
>>> from hyperparameter import param_scope
>>> with param_scope(param1=1) as ps:
... print(f"param1={ps.param1}, param2={ps.param2('undefined')}")
param1=1, param2=undefined

```

2. `auto_param` 装饰器,自动将函数(或者class)的默认参数转化为超参,并接受`param_scope`的参数控制;

```python
>>> from hyperparameter import auto_param, param_scope
>>> @auto_param
... def foo(a, b="default"):
... print(f"a={a}, b={b}")

>>> foo(0)
a=0, b=default

>>> with param_scope(**{"foo.b": "modified"}):
... foo(0)
a=0, b=modified

```

超参配置
-------

1. 通过`param_scope`可以直接读取超参配置,而无需任何配置:

```python
>>> from hyperparameter import param_scope
>>> def foo():
... # read parameter from param_scope
... p = param_scope.param("default")
... p2 = param_scope.namespace.param2("default2")
... print(f"p={p}, p2={p2}")

```

在上述函数`foo`中,尝试访问名为`param`的超参,超参默认值为`default``param_scope`首先尝试从上下文中读取同名参数并返回给调用者,若超参未定义则返回默认值。为了更好的组织参数,也可以给参数名添加命名空间`namespace.param`。命名空间也支持嵌套多层,比如`namespace.subspace.param`


2. 通过`param_scope`传递超参

```python
# call `foo` with default parameter
>>> foo()
p=default, p2=default2

# call `foo` with modified parameter
>>> with param_scope("namespace.param2=modified"):
... foo()
p=default, p2=modified

```

通过`with param_scope(...)`传递参数的时候支持两种语法,字符串语法与字典语法。字典语法如下所示:

```python
# call `foo` with modified parameter
>>> with param_scope(**{
... "param": "modified",
... "namespace": {"param2": "modified2"}
... }):
... foo()
p=modified, p2=modified2

```
字典语法适合配合配置文件使用。


3. `param_scope`可以穿透多层函数调用传递参数:

```python
>>> def bar():
... foo()

# call `foo` within nested function call
>>> with param_scope("namespace.param2=modified"):
... bar()
p=default, p2=modified

```

### manage hyper-parameters with `param_scope`

`param_scope` create a `HyperParameter` object for the context it manages:

```python
>>> from hyperparameter import param_scope
>>> with param_scope(param1=1, param2="A") as ps:
... ps.param1
1

```

自动超参
-------

1. `auto_param` 可以自动为函数(或者class)添加超参配置功能

```python
>>> from hyperparameter import auto_param
>>> @auto_param
... def foo(param, param1=1):
... print(f"param={param}, param1={param1}")

>>> foo(0)
param=0, param1=1

```

2. 通过`param_scope``auto_param`传递参数:

```python
>>> with param_scope(**{"foo.param1": 0}):
... foo(0)
param=0, param1=0

```
25 changes: 17 additions & 8 deletions hyperparameter/__init__.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
from .hyperparameter import HyperParameter
from .hyperparameter import param_scope, reads, writes, all_params
from .hyperparameter import auto_param, set_auto_param_callback
from .hyperparameter import dynamic_dispatch, lazy_dispatch, suggest_from
from .hyperparameter import (
auto_param,
dynamic_dispatch,
HyperParameter,
param_scope,
set_auto_param_callback,
)
from .tracker import all_params, reads, writes
from .tune import suggest_from, lazy_dispatch

__all__ = [
# base class for hyper-parameters
"HyperParameter",
# api for parameter configuration
"auto_param",
"param_scope",
"dynamic_dispatch",
"set_auto_param_callback",
# api for parameter tuning
"suggest_from",
"lazy_dispatch",
"param_scope",
# api for parameter tracking
"reads",
"writes",
"all_params",
"auto_param",
"set_auto_param_callback",
]

VERSION = "0.3.3"
VERSION = "0.4.0"
152 changes: 4 additions & 148 deletions hyperparameter/hyperparameter.py
Original file line number Diff line number Diff line change
@@ -1,111 +1,9 @@
import inspect
from operator import getitem
import threading
from typing import Any, Callable, Dict, List, Optional, Set
from typing import Any, Callable, Dict, Set


def _sorted_set(s):
retval = list(s)
retval.sort()
return retval


class _Tracker:
"""parameter access tracker
Examples
--------
>>> _tracker.clear()
>>> ps = param_scope(a=1, b={'c': 2})
>>> reads(), writes() # empty read/write trackers
([], [])
Only access through accessor is tracked, raw access
to the parameter is not tracked.
>>> ps.a, ps().b.c(3)
(1, 2)
>>> reads()
['b.c']
>>> ps.a = 1
>>> ps().a.b.c = 1
>>> writes()
['a.b.c']
"""

def __init__(self) -> None:
self._get: Set[str] = set()
self._put: Set[str] = set()

def clear(self):
self._get.clear()
self._put.clear()

def read(self, key: Optional[str] = None) -> Optional[List[str]]:
return self._get.add(key) if key else _sorted_set(self._get)

def write(self, key: Optional[str] = None) -> Optional[List[str]]:
return self._put.add(key) if key else _sorted_set(self._put)

def all(self):
return _sorted_set(self._get.union(self._put))


_tracker = _Tracker()


def reads():
return _tracker.read()


def writes():
return _tracker.write()


def all_params():
return _tracker.all()


class Suggester:
def __init__(self, callback: Callable) -> None:
self._callback = callback

def __call__(self):
return self._callback()


def suggest_from(callback: Callable) -> Suggester:
"""Suggest parameter from a callback function
Examples
--------
>>> class ValueWrapper:
... def __init__(self, lst):
... self._lst = lst
... self._offset = 0
... def __call__(self):
... index, self._offset = self._offset % len(self._lst), self._offset + 1
... return self._lst[index]
>>> with param_scope(suggested = suggest_from(ValueWrapper([1,2,3]))) as ps:
... ps().suggested()
... ps().suggested()
... ps().suggested()
1
2
3
"""
return Suggester(callback)


def _unwrap_suggester(func):
def wrapper(*args, **kwargs):
retval = func(*args, **kwargs)
if isinstance(retval, Suggester):
return retval()
return retval

return wrapper
from .tracker import _tracker
from .tune import unwrap_suggester


class _Accessor(dict):
Expand All @@ -117,7 +15,7 @@ def __init__(self, root, path=None, scope=None):
self._path = path
self._scope = scope

@_unwrap_suggester
@unwrap_suggester
def get_or_else(self, default: Any = None):
"""Get value for the parameter, or get default value if the parameter is not defined."""
if self._scope is not None:
Expand Down Expand Up @@ -217,48 +115,6 @@ def dynamic_dispatch(func, name=None, index=None):
return new_class(func, name, index)


class LazyDispatch:
"""Dynamic call dispatcher
Examples
--------
>>> class ExampleObj:
... def get_42(self, offset):
... return 42+offset
>>> lazy_obj = lazy_dispatch(ExampleObj())
>>> lazy_obj.get_42(0)()
42
"""

def __init__(self, obj: Any, name=None, index=None):
self._obj = obj
self._name = name
self._index = index

def __call__(self, *args, **kws) -> Any:
obj = self._obj
for n in self._name.split("."):
obj = getattr(obj, n)
if self._index is not None:
obj = getitem(obj, self._index)
return Suggester(lambda: obj(*args, **kws))

def __getattr__(self, name: str) -> Any:
if self._name is not None:
name = f"{self._name}.{name}"
return lazy_dispatch(self._obj, name, self._index)

def __getitem__(self, index):
return lazy_dispatch(self._obj, self._name, index)


def lazy_dispatch(obj, name=None, index=None):
"""Wraps an object for lazy dispatch"""
return LazyDispatch(obj, name, index)


class HyperParameter(dict):
"""HyperParameter is an extended dict designed for parameter storage.
Expand Down
Loading

0 comments on commit 0aedbdb

Please sign in to comment.