Mercurial > pylearn
changeset 1048:0464f891129b
merge
author | Dumitru Erhan <dumitru.erhan@gmail.com> |
---|---|
date | Wed, 08 Sep 2010 14:14:02 -0400 |
parents | 1b61cbe0810b (current diff) f1732269bce8 (diff) |
children | ff9361e39c97 |
files | |
diffstat | 1 files changed, 207 insertions(+), 92 deletions(-) [+] |
line wrap: on
line diff
--- a/doc/v2_planning/learner.txt Wed Sep 08 14:13:43 2010 -0400 +++ b/doc/v2_planning/learner.txt Wed Sep 08 14:14:02 2010 -0400 @@ -173,116 +173,231 @@ the picture and make a useful boosting implementation. +Using External Hyper-Parameter Optimization Software +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +TODO: use-case - show how we could use the optimizer from +http://www.cs.ubc.ca/labs/beta/Projects/ParamILS/ + Implementation Details / API ---------------------------- -TODO: PUT IN TERMINOLOGY OF LEARNER, HYPER-LEARNER. - -TODO: SEPARATE DISCUSSION OF PERSISTENT STORAGE FROM LEARNER INTERFACE. - -TODO: API describing hyperparameters (categorical, integer, bounds on values, etc.) - -TODO: use-case - show how we could use the optimizer from - http://www.cs.ubc.ca/labs/beta/Projects/ParamILS/ - -ExperimentGraph -~~~~~~~~~~~~~~~ - -One API that needs to be defined for this perspective to be practical is the -ExperimentGraph. I'll present it in terms of global functions, but an -object-oriented things probably makes more sense in the code itself. - - - def explored_nodes(graph): - """Return iterator over explored nodes (ints? objects?)""" +Learner +~~~~~~~ + An object that allows us to explore the graph discussed above. Specifically, it represents + an explored node in that graph. - def forget_nodes(graph, nodes): - """Clear the nodes from memory (save space)""" - - def all_edges_from(graph, node): - """Return iterator over all possible edges - - Edges might be parametric - like "set learn_rate to (float)" - - Edges might contain a reference to their 'from' end... not sure. - - """ - def explored_edges_from(graph, node): - """Return the edges that have been explored - """ - - def add_node(graph, new_node): - """add a node. It may be serialized.""" - - def add_edge(graph, edge): - """add edge, it may be serialize""" - - def connect(graph, from_node, to_node, edge): - """ - to_node = None for un-explored edge + def active_instructions() + """ Return a list/set of Instruction instances (see below) that the Learner is prepared + to handle. """ -It makes sense to have one ExperimentGraph implementation for each storage -mechanism - Memory, JobMan, sqlite, couchdb, mongodb, etc. - -The nodes should be serializable objects (like the 'learner' objects in Yoshua's -text above, so that you can do node.learner.predict() if the edge leading to -`node` trained something new). - -The nodes could also contain the various costs (train, valid, test), and other -experiment statistics that are node-specific. + def copy(), deepcopy() + """ Learners should be serializable """ -Some implementations might also include functions for asynchronous updating of -the ExperimentGraph: + To make the implementation easier, I found it was helpful to introduce a string-valued + `fsa_state` member attribute and associate methods to these states. That made it + syntactically easy to build relatively complex finite-state transition graphs to describe + which instructions were active at which times in the life-cycle of a learner. -ExperimentGraphEdge -~~~~~~~~~~~~~~~~~~~ - -The ExperimentGraph is primarily a dictionary container for nodes and edges. -An ExperimentGraphEdge implementation is the model-dependent component that -actually interprets the edges as computations. - - def estimate_compute_time(graph, node, edge): - """Return an estimated walltime expense for the computation""" +Instruction +~~~~~~~~~~~ + An object that represents a potential edge in the graph discussed above. It is an + operation that a learner can perform. - def compute_edge(graph, node, edge, async=False, priority=1): - """Run the computations assocated with this graph edge, and store the - resulting 'to_node' to the graph when complete. - - If async is True, the function doesn't return until the graph is updated - with `to_node`. + arg_types + """a list of Type object (see below) indicating what args are required by execute""" - The priority is used by implementations that use cluster software or - something to manage a worker pool that computes highest-priority edges - first. - - """ - - def list_compute_queue(graph): - """Return edges scheduled for exploration (and maybe a handle for - where/when they started running and other backend details) + def execute(learner, args, kwargs): + """ Perform some operation on the learner (follow an edge in the graph discussed above) + and modify the learner in-place. Calling execute 'moves' the learner from one node in + the graph along an edge. To have the old learner as well, it must be copied prior to + calling execute(). """ -Different implementations of ExperimentGraphExplorer will correspond to -different experiments. There can also be ExperimentGraphExplorer -implementations that are proxies, and perform the computations in different -threads, or across ssh, or cluster software. + def expense(learner, args, kwargs, resource_type='CPUtime'): + """ Return an estimated cost of performing this instruction (calling execute), in time, + space, number of computers, disk requierement, etc. + """ + +Type +~~~~ + An object that describes a parameter domain for a call to Instruction.execute. + It is not necessary that a Type specifies exactly which arguments are legal, but it should + `include` all legal arguments, and exclude as many illegal ones as possible. + + def includes(value): + """return True if value is a legal argument""" -Learner -~~~~~~~ - -A learner is a program that implements a policy for graph exploration by -exploiting the ExperimentGraph and ExperimentGraphEdge interfaces. - -The convenience of the API hinges on the extent to which we can implement -policies that work on different experiment-graphs (where the labels on the edges -and semantics are different). The use-cases above make me optimistic that it -will work sufficiently well to be worth doing in the absence of better ideas. + To make things a bit more practical, there are some Type subclasses like Int, Float, Str, + ImageDataset, SgdOptimizer, that include additional attributes (e.g. min, max, default) so + that automatic graph exploration algorithms can generate legal arguments with reasonable + efficiency. +The proxy pattern is a powerful way to combine learners. Especially when proxy Learner +instances also introduce Proxy Instruction classes. +For example, it is straightforward to implement a hyper-learner by implementing a Learner with +another learner (sub-learner) as a member attribute. The hyper-learner makes some +modifications to the instruction_set() return value of the sub-learner, typically to introduce +more powerful instructions and hide simpler ones. + +It is less straightforward, but consistent with the design to implement a Learner that +encompasses job management. Such a learner would retain the semantics of the +instruction_set of the sub-learner, but would replace the Instruction objects themselves with +Instructions that arranged for remote procedure calls (e.g. jobman, multiprocessing, bqtools, +etc.) Such a learner would replace synchronous instructions (return on completion) with +asynchronous ones (return after scheduling) and the active instruction set would also change +asynchronously, but neither of these things is inconsistent with the Learner API. + + +TODO +~~~~ + +I feel like something is missing from the API - and that is an interface to the graph structure +discussed above. The nodes in this graph are natural places to store meta-information for +visualization, statistics-gathering etc. But none of the APIs above corresponds to the graph +itself. In other words, there is no API through which to attach information to nodes. It is +not good to say that the Learner instance *is* the node because (a) learner instances change +during graph exploration and (b) learner instances are big, and we don't want to have to keep a +whole saved model just to attach meta-info e.g. validation score. Choosing this API spills +over into other committees, so we should get their feedback about how to resolve it. + +Comment by OD +~~~~~~~~~~~~~ +(I hope it's ok to leave comments even though I'm not in committee... I'm +interested to see how the learner interface is shaping up so I'll be keeping +an eye on this file) +I'm wondering what's the benefit of such an API compared to simply defining a +new method for each instruction. It seems to me that typically, the 'execute' +method would end up being something like + if instruction == 'do_x': + self.do_x(..) + elif instruction == 'do_y': + self.do_y(..) + ... +so why not directly call do_x / do_y instead? + + +Comment by RP +~~~~~~~~~~~~~ + +James correct me if I'm wrong, but I think each instruction has a execute +command. The job of the learner is to traverse the graph and for each edge +that it decides to cross to call the execute of that edge. Maybe James has +something else in mind, but this was my understanding. + + + +Just another view/spin on the same idea (Razvan) +================================================ + + +My idea is probably just a spin off from what James wrote. It is an extension +of what I send on the mailing list some time ago. + +Big Picture +----------- + +What do we care about ? +~~~~~~~~~~~~~~~~~~~~~~~ + +This is the list of the main points that I have in mind : + + * Re-usability + * Extensibility + * Simplicity or easily readable code ( connected to re-usability ) + * Modularity ( connected to extensibility ) + * Fast to write code ( - sort of comes out of simplicity) + * Efficient code + + +Composition +~~~~~~~~~~~ + +To me this reads as code generated by composing pieces. Imagine this : +you start of with something primitive that I will call a "variable", which +probably is a very unsuitable name. And then you compose those intial +"variables" or transform them through several "functions". Each such +"function" hides some logic, that you as the user don't care about. +You can have low-level or micro "functions" and high-level or macro +"functions", where a high-level function is just a certain compositional +pattern of low-level "functions". There are several classes of "functions" +and "variables" that can be inter-changable. This is how modularity is +obtained, by chainging between functions from a certain class. + +Now when you want to research something, what you do is first select +the step you want to look into. If you are lucky you can re-write this +step as certain decomposition of low-level transformations ( there can be +multiple such decompositions). If not you have to implement such a +decompositions acording to your needs. Pick the low-level transformations you want +to change and write new versions that implement your logic. + +I think the code will be easy to read, because it is just applying a fixed +set of transformations, one after the other. The one who writes the code can +decide how explicit he wants to write things by switching between high-level +and low-level functions. + +I think the code this way is re-usable, because you can just take this chain +of transformation and replace the one you care about, without looking into +the rest. + +You get this fractal property of the code. Zooming in, you always get just +a set of functions applied to a set of variables. In the begining those might +not be there, and you would have to create new "low level" decompositions, +maybe even new "variables" that get data between those decompositions. + +The thing with variables here, is that I don't want this "functions" to have +a state. All the information is passed along through these variables. This +way understanding the graph is easy, debugging it is also easier ( then having +all these hidden states ..) + +Note that while doing so we might ( and I strongly think we should) create +a (symbolic) DAG of operations. ( this is where it becomes what James was saying). +In such a DAG the "variables" will the nodes and the functions will be edges. +I think having a DAG is useful in many ways (all this are things that one +might think about implementing in a far future, I'm not proposing to implement +them unless we want to use them - like the reconstruction ): + * there exist the posibility of writing optimizations ( theano style ) + * there exist the posibility to add global view utility functions ( like + a reconstruction function for SdA - extremely low level here), or global + view diagnostic tools + * the posibility of creating a GUI ( where you just create the Graph by + picking transforms and variables from a list ) or working interactively + and then generating code that will reproduce the graph + * you can view the graph and different granularity levels to understand + things ( global diagnostics) + +We should have a taxonomy of possible classes of functions and possible +classes of variables, but those should not be exclusive. We can work at a high +level for now, and decompose those high level functions to lower level when +we need to. We can introduce new classes of functions or intermediate +variables between those low level functions. + + +Similarities with James' idea +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + As I said before, this is I think just another view on what James proposed. + The learner in his case is the module that traverses the graph of this + operations, which makes sense here as well. + + The 'execute' command in his api is just applying a function to some variables in + my case. + + The learner keeps track of the graph that is formed I think in both cases. + + His view is a bit more general. I see the graph as fully created by the user, + and the learner just has to go from the start to the end. In his case the + traversal is conditioned on some policies. I think these ideas can be mixed / + united. What I would see in my case to have this functionality is something + similar to the lazy linker for Theano. + + + +