Git Product home page Git Product logo

pyglove's Introduction

logo logo

PyGlove: Manipulating Python Programs

PyPI version codecov pytest

Getting started | Installation | Examples | Reference docs

What is PyGlove

PyGlove is a general-purpose library for Python object manipulation. It introduces symbolic object-oriented programming to Python, allowing direct manipulation of objects that makes meta-programs much easier to write. It has been used to handle complex machine learning scenarios, such as AutoML, as well as facilitating daily programming tasks with extra flexibility.

PyGlove is lightweight and has very few dependencies beyond the Python interpreter. It provides:

  • A mutable symbolic object model for Python;
  • A rich set of operations for Python object manipulation;
  • A solution for automatic search of better Python programs, including:
    • An easy-to-use API for dropping search into an arbitrary pre-existing Python program;
    • A set of powerful search primitives for defining the search space;
    • A library of search algorithms ready to use, and a framework for developing new search algorithms;
    • An API to interface with any distributed infrastructure (e.g. Open Source Vizier) for such search.

It's commonly used in:

  • Automated machine learning (AutoML);
  • Evolutionary computing;
  • Machine learning for large teams (evolving and sharing ML code, reusing ML techniques, etc.);
  • Daily programming tasks in Python (advanced binding capabilities, mutability, etc.).

PyGlove has been published at NeurIPS 2020. It is widely used within Alphabet, including Google Research, Google Cloud, Youtube and Waymo.

PyGlove is developed by Daiyi Peng and colleagues in Google Brain Team.

Hello PyGlove

import pyglove as pg

@pg.symbolize
class Hello:
  def __init__(self, subject):
    self._greeting = f'Hello, {subject}!'

  def greet(self):
    print(self._greeting)


hello = Hello('World')
hello.greet()

Hello, World!

hello.rebind(subject='PyGlove')
hello.greet()

Hello, PyGlove!

hello.rebind(subject=pg.oneof(['World', 'PyGlove']))
for h in pg.iter(hello):
  h.greet()

Hello, World!
Hello, PyGlove!

Install

pip install pyglove

Or install nightly build with:

pip install pyglove --pre

Examples

Citing PyGlove

@inproceedings{peng2020pyglove,
  title={PyGlove: Symbolic programming for automated machine learning},
  author={Peng, Daiyi and Dong, Xuanyi and Real, Esteban and Tan, Mingxing and Lu, Yifeng and Bender, Gabriel and Liu, Hanxiao and Kraft, Adam and Liang, Chen and Le, Quoc},
  booktitle={Advances in Neural Information Processing Systems (NeurIPS)},
  volume={33},
  pages={96--108},
  year={2020}
}

Disclaimer: this is not an officially supported Google product.

pyglove's People

Contributors

chansoo-google avatar daiyip avatar rchen152 avatar sinopalnikov avatar tomvdw avatar xingyousong avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

pyglove's Issues

Missing type error in the error log

When I feed a value with the wrong type into the pg.Object, the error message missing the expected type, see this example below:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[15], line 8
      4 class A(pg.Object):
      6     x: Annotated[Optional[List[Any]], '124'] = None
----> 8 A(x=(1, 2))

File [], in Object._update_signatures_based_on_schema.._init(self, *args, **kwargs)
    346 @functools.wraps(pseudo_init)
    347 def _init(self, *args, **kwargs):
    348   # We pass through the arguments to `Object.__init__` instead of
    349   # `super()` since the parent class uses a generated `__init__` will
    350   # be delegated to `Object.__init__` eventually. Therefore, directly
    351   # calling `Object.__init__` is equivalent to calling `super().__init__`.
--> 352   Object.__init__(self, *args, **kwargs)

File [], in Object.__init__(self, allow_partial, sealed, root_path, explicit_init, *args, **kwargs)
    550     keys_str = object_utils.comma_delimited_str(missing_args)
    551     raise TypeError(
    552         f'{self.__class__.__name__}.__init__() missing {len(missing_args)} '
    553         f'required {arg_phrase}: {keys_str}.')
    555 self._set_raw_attr(
    556     '_sym_attributes',
--> 557     pg_dict.Dict(
    558         field_args,
    559         value_spec=self.__class__.sym_fields,
    560         allow_partial=allow_partial,
    561         sealed=sealed,
    562         accessor_writable=self.__class__.allow_symbolic_assignment,
    563         root_path=root_path,
    564         as_object_attributes_container=True,
    565     ),
    566 )
    567 self._sym_attributes.sym_setparent(self)
    568 self._on_init()
...

File [~/.local/lib/python3.9/site-packages/pyglove/core/typing/class_schema.py:688), in Field.apply(self, value, allow_partial, transform_fn, root_path)
    659 def apply(
    660     self,
    661     value: Any,
   (...)
    664         [object_utils.KeyPath, 'Field', Any], Any]] = None,
    665     root_path: Optional[object_utils.KeyPath] = None) -> Any:
    666   """Apply current field to a value, which validate and complete the value.
    667 
    668   Args:
   (...)
    686       allow_partial is set to False.
    687   """
--> 688   value = self._value.apply(value, allow_partial, transform_fn, root_path)
    689   if transform_fn:
    690     value = transform_fn(root_path, self, value)

File [~/.local/lib/python3.9/site-packages/pyglove/core/typing/value_specs.py:232), in ValueSpecBase.apply(self, value, allow_partial, child_transform, root_path)
    229   converter = type_conversion.get_first_applicable_converter(
    230       type(value), self.value_type)
    231   if converter is None:
--> 232     raise TypeError(
    233         object_utils.message_on_path(
    234             f'Expect {self.value_type} '
    235             f'but encountered {type(value)!r}: {value}.', root_path))
    236   value = converter(value)
    238 # NOTE(daiyip): child nodes validation and transformation is done before
    239 # parent nodes, which makes sure when child_transform is called on current
    240 # node (which is in Field.apply), input are in good shape.
    241 # It also lets users to create complex object types from transform function
    242 # without downstream constraint.

TypeError: Expect  but encountered : (1, 2). (path=x)

AttributeError: Attribute 'sym_sealed' does not exist in <class 'pyglove.core.symbolic.dict.Dict'>.

(most recent call last):
  File "/.local/lib/python3.9/site-packages/pyglove/core/symbolic/dict.py", line 628, in __setitem__
    if base.treats_as_sealed(self):
  File "/.local/lib/python3.9/site-packages/pyglove/core/symbolic/base.py", line 2386, in treats_as_sealed
    return value.sym_sealed if sealed_in_scope is None else sealed_in_scope
  File ".local/lib/python3.9/site-packages/pyglove/core/symbolic/dict.py", line 697, in __getattr__
    raise AttributeError(
AttributeError: Attribute 'sym_sealed' does not exist in <class 'pyglove.core.symbolic.dict.Dict'>.

Get some deeply nested errors and can not be able to figure out the root reasons.

Release 0.4.1

Hi Daiyi, given the recent enhancement, would you mind release it as 0.4.1 XDD ?

Untyped Check

PyGlove's rebind function is very useful, while its signature is unbounded about type checking, so that if a user rebind inappropriate value to an attribute. We can only know something is wrong at the runtime, instead of being discovered by pytype check before run. Is there any solution that we can improve this aspect?

Lack of type parameters to `Callable`

Hey there! I'm excited about this project. I've been wanting metaprogramming capabilities like the ones pyglove supports in Python for a long time, and I'm trying this out for a project at work. I'm lurking right now in the codebase and playing with it as a prototype.

During a bit of my exploration, I've noticed that pg.typing.Callable doesn't take any type parameters. This is confusing as the default Python typing does - is there a reason for this? Could we add type parameters to be more precise when defining callables in pg.members?

Questions about `pg.symbolize`, `pg.members` and compatibility with pyright

I've noticed that we have both pg.symbolize and pg.members to do effectively the same thing, symbolizing a class, albeith in different ways. The benefits of pg.symbolize is that one can use it around a regular class definition and add type hints for all parameters, whereas pg.members creates all of that under the hood given the list of arguments.

Using pg.members is nice as one can also provide the documentation for each parameter and express the nested structure of parameters. But it has the shortcoming that typecheckers like pyright cannot know what the types of the parameters are because they are created on the fly by the annotation.

I have two questions:

  1. When should we prefer pg.symbolize over pg.members? Most of the usages I've seen of pg.symbolize in the notebooks is to wrap around existing classes but it's also handy as an alternative to pg.members as far as I understand?

  2. What is the story between pyright compatibility in pyglove? I know the dynamic capability of pg.members doesn't bode well with typings, but is there a way we could generate pyglove type stubs dynamically? (In this context, ParamSpec seems relevant.) This way one could use pg.members and benefit from type checking when using the parameters.

Clarification on Efficiency

Hello lovely people,

Thank you for opensourcing this lovely library ! As an AutoML researcher, I am quite grateful.

I however stumbled upon this comment in the Travelling Salesman notebook. "PyGlove is designed to be used with problems with evaluation time greater than sub-second," which proved to be true when I rewrote a Simplified AutoML Zero implementation I coded in numba/numpy vs an attempt to recode it in Pyglove. The NumPy version does about 600-800 iterations per second, while when I run the Pyglove version it takes about a minute to run 400-800 iterations on the same device.

Here is where I define the search space and search process. I let the DNA objects be created automatically (I presume through DNAGenerator?) by using pg.List and pg.oneof:

INIT_MEMORY = initialize_memory(memory_size = MEMORY_SIZE, matrix_size= MATRIX_SIZE, start=-10, end=10)
search_space = pg.List([pg.oneof([key for key in OP_dict_basic.keys()]), 
                pg.oneof([i for i in range(0,len(INIT_MEMORY))]), 
                pg.oneof([i for i in range(0,len(INIT_MEMORY))]),
                pg.oneof([i for i in range(0,len(INIT_MEMORY))]),
                pg.oneof([i for i in range(0,len(INIT_MEMORY))])] * OP_DEPTH)
search_algorithm = pg.evolution.regularized_evolution(
    population_size=POPULATION_COUNT, tournament_size=TOURNAMENT_COUNT)

auto_ml_zero_session(search_space, search_algorithm)

Is there a solution to improve efficiency? Would it be reasonable to subclass DNA and create a version of DNA that runs with numpy under the hood?

Or would it be more reasonable to do as the onemax example and create a custom decoder subclassing pg.hyper.CustomHyper and a custom mutator which both run numpy under the hood to address this efficiency gap?

Thanks

pg.sample returns same function from functor

The below code block samples the same hyperparameters over and over again and ignores the num_examples=2 argument. Am I using this incorrectly?

import pyglove as pg

@pg.symbolize
def foo(a, b):
    return a + b

hyper_foo = foo(pg.oneof([1, 2, 3]), pg.oneof([13, 29]))

for foo_sample, feedback in pg.sample(hyper_foo, pg.generators.Random(), num_examples=2):
    print(foo_sample())

Breadth-first search for pg.iter

Hi,

what is the easiest way to implement a breadth-first search of the candidate space? Right now, pg.iter uses depth-first from my understanding. I couldn't find any straight-forward way to use evolution operators -- any suggestions?

Question on Type Annotation and Docstring

Does the following two things perform differently in pyglove?

class Foo:
    field_a: int = 42
    "field_a docstring"

and

class Foo:
    field_a: Annotated[int, "field_a docstring"] = 42

pg.diff between two identical objects

import pyglove as pg

class A(pg.Object):
    x: str

print(pg.diff(A(x="1"), A(x="2")))

print(pg.diff(A(x="1"), A(x="1")))

The later one outputs the entire A, shouldn't it just produce A()?

Dict is not accepting int as key.

import pyglove as pg

class A(pg.Object):

  x: Annotated[Dict, "."] = dict()
  y: Annotated[str, "."] = ".."

# Define a simple dictionary
data = A(x={1:1})

Most normal user may expect this code snippet work well?

Having tied parameters?

Hi,

I'm having issues figuring out how to tie hyperparameters together. What I would like to have is something like

activation_1 = ActivationLayer(pg.one_of(["sigmoid", "relu"]))
activation_2 = ActivationLayer(pg.one_of(["sigmoid", "relu"]))
space = pg.List([Weights(), activation_2, Weights(), activation_1, Weights(), activation_1, Weights(), activation_2])

where activation_1 and activation_2 change together. However, because it is passed by value, all of these end up being separate. Any ideas on how to achieve my desired outcome?

Defining symbolic attributes not in initialization

I apologize in advance if this is a stupid question, but I really like the design of the library and I am afraid I am missing some context to "grok" it. Consider a simple MLP in PyTorch:

class SimpleMLP(Module):

  def __init__(self, in_channels, hidden_channels, out_channels, activation=F.relu):
    super().__init__()
    self.hidden = Linear(in_channels, hidden_channels)
    self.out = Linear(hidden_channels, out_channels)
    self.activation = activation

  def forward(self, x):
    return self.out(self.activation(self.hidden(x)))

Assume I now want to patch the linear layers to always add dropout afterwards. Is there a way to manipulate the decorator to force this? All the examples in the documentation use variants of Sequential, where the layers are explicitly passed during initialization. Since this is a rather common use case I am wondering if I completely misunderstanding the documentation and there is an easy way to accomplish this, or else if I should refactor the above code. Thanks!

Questions about the generator algorithms.

Hi, experts.
I am very interested about PyGlove and want to try it out for my NAS project.
However, I am not sure what is a reasonable way to generate candidates other than using Random().

(1) The natsbench.py code uses pg.generators.PPO(). However, PPO is not defined in the codebase. How can I use PPO()?
(2) nasbench.py uses pg.evolution.regularized_evolution(), which is defined. However, the mutator is defined as follows, which I do not understand what it means

mutator=(
            pg.evolution.mutators.Uniform(
                where=node_selector(hints=OP_HINTS))         # pylint: disable=no-value-for-parameter
            >> pg.evolution.mutators.Uniform(
                where=node_selector(hints=EDGE_HINTS)) ** 3  # pylint: disable=no-value-for-parameter
        )

Can you explain (1) how I can use PPO() and/or (2) how I can define the mutator effectively (what does the above code do)?

Thank you for your help.

How to use load and save for non-pyglove types

import pyglove as pg
from typing import Annotated, Optional, List, Any

class A(pg.Object):

  x: Annotated[Optional[List[Any]], '124'] = None
  y: Annotated[dict, "124"] = dict()

class B:
  pass

res = A(x=[1, 2], y={'b': B()})
print(res)

I want to be able to pg.save(res, 'res.json') and successfully reload it via pg.load('res.json'). Is there a way to implement?

Can I easily parallelize the search with multiple GPUs?

Hi, experts.
I am really enjoying PyGlove!
I have a question regarding accelerating the search. If I have multiple GPUs, does PyGlove supports parallelizing the search easily?
If so, it would be great if you can give me any pointers.

Thank you!

Any example(or document) for Efficient NAS?

Firstly, thanks for a wonderful job!
But there are something bothers me:
I can not find Switch (that is introduced in your An introduction to AutoML and PyGlove slide) in pyglove repository
Did I miss something?
Thanks!

image

Use `pg.typing` as type annotations

PyGlove provides a runtime typing feature through pg.typing, for example, a symbolic class can be created via:

from dataclasses import dataclass
import pyglove as pg

@pg.symbolize([
  ('x', pg.typing.Int(min_value=1, max_value=2)),
  ('y', pg.typing.Callable([pg.typing.Int(max_value=0)], returns=pg.typing.Bool))
])
@dataclass
def Foo:
    x: int
    y: Callable[[int], bool]

There is a redundancy in this definition. Ideally, users should be able to do:

@pg.symbolize
@dataclass
def Foo:
   x: pg.typing.Int(min_value=1, max_value=2)
   y: pg.typing.Callable([pg.typing.Int(max_value=0)], returns=pg.typing.Bool())

And this code should work for both static type check (PyType) and runtime type check.

Additionally, we hope x: int can be recognized by PyGlove as a shortcut to x: pg.typing.Int().

More documentation on use with OSS Vizier

I'm trying to follow the documentation regarding the use of OSS Vizier as a backend for PyGlove, but find it incomplete - shouldn't we need to create server and client objects?

For example, the documented interface for retrieving optimal trials in the OSS Vizier docs is to iterate over study_client.optimal_trials(), but the PyGlove example seems to abstract away the initialization of the server into the call

pg_vizier.init("my_study")

It's unclear how this matches up, and is currently a blocker to my use of the library. Any help would be appreciated.

Can we disable __init__ for the users?

PyGlove disabled a few functions for the users, such as __init__ because some of its magics rely on hacking the built-in python class functions. However, if a user does not know it and still write a __init__ function as usual, PyGlove does not raise any error and just quietly pass the codes and failed at somewhere later.

Therefore, could we explicitly raise some errors right after the user define a PG class with init?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.