New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Suggestion: simplify learner interface #94
Comments
originally posted by Joseph Weston (@jbweston) at 2018-01-31T12:46:30.169Z on GitLab
But an ABC is such a specification. The problem of explicitly depending on adaptive is solved by us implementing
I am not sure that the learner is the correct place for this. Currently in adaptive the end condition is supplied to
I'm not sure that this is a good idea. This means that the following: for point in learner:
pass changes the state of the learner. I understand that this generally true for iterators, but I think it is very confusing. Also, in the above, the learner is the only one who can decide how many points to give back in each call to For example, imagine we have a 1D learner with just the boundary points evaluated and we want to
However, if we have to do 2 separate calls to
which leads to a suboptimal distribution of points, given the current information! In this example we get "better" In the cquad example I could imagine that we would write the learner algorithm purely in terms of intervals,
This already exists; it is called
This is a nice addition! @anton-akhmerov reminded me that this does not always make sense, though, because not all learners are trying |
originally posted by Christoph Groth (@cwg) at 2018-02-01T23:20:08.574Z on GitLab
My point is that simplifying the learner interface could turn it into an Of course its OK to depend on adapative, but it's better to avoid
But the runner has no understanding at all of the class of functions Of course, with the current design you can also write:
but I don't see how it helps the runner to be able to trigger the goal As far as custom goals that have not been foreseen by the Learner author
You have a good point here. When starting to think about learner's interface I was looking for some My first idea was to use a (generator-like) coroutine, that as you know Perhaps you have a better idea? In C++ I would write something like:
The nice thing here is that getting no points is also the exit condition
The iterator trick allows to simplify this to
The C++ version is indeed nicer because
The problem with this approach is that it's nondeterministic. The But your point is valid. One can imagine cases where it is useful for How about this:
Unfortunately, this is getting much more complicated than my original
Yes, but what's your goal here?
I think that it's a nice and crips definition to say that learners learn I'm not saying that the There could be of course also an averaging learner of function of a |
originally posted by Anton Akhmerov (@anton-akhmerov) at 2018-02-02T08:37:29.136Z on GitLab
Good point, it sounds very reasonable that How to deal with the need to keep track of the collection of prng seeds? What should keep track of them? |
originally posted by Joseph Weston (@jbweston) at 2018-02-02T09:42:13.080Z on GitLab
This claim is demonstrably false: import abc
class Animal(abc.ABC):
@classmethod
def __subclasshook__(cls, obj):
return any(hasattr(parent, 'make_noise') for parent in obj.__mro__)
@abc.abstractmethod
def make_noise(self):
pass
class Duck:
def make_noise(self):
return "quack"
duck = Duck()
print(isinstance(duck, Animal)) Also the Python docs say so. I maintain that using an ABC is as good a documentation as any for describing our "learner protocol".
I agree that it will be not be deterministic from the user's perspective, however I do not perceive this as a problem. A given run produces
At the moment we get around this by having the option of maintaining a log of the calls made to the learner.
Yes, but the user does. The user is the one who provides the goal. Here are some examples of reasonable goals that we have already used in adaptive:
I believe that (1) is the type of goal that you were considering up till now. At the moment the "loss" is just an arbitrary figure of merit that cannot (2) is also a very common goal (we often use it to compare a learner's performance to a naive gridding of parameter space). I think you will agree (3) is clearly outside the remit of the learner, and there is not really a natural place to "start the timer" if the logic is inside the learner. |
originally posted by Joseph Weston (@jbweston) at 2018-02-02T10:00:45.189Z on GitLab
That the learner can return objects that are not mapped 1-1 onto "points" that can be evaluated by the runner. The learner's internal algorithm is not necessarily easily expressed in terms of points, but in terms of other objects such as intervals. |
originally posted by Christoph Groth (@cwg) at 2018-04-19T11:43:57.486Z on GitLab I only knew two ways of becoming the instance of an ABC: deriving from it and using However, I have to say that Using But OK, since my proposal is to use duck typing for learners, an ABC with |
originally posted by Christoph Groth (@cwg) at 2018-04-19T12:03:19.695Z on GitLab Joseph wrote:
I think that this is too weak. That means that when you do computations on a cluster and start getting weird results the problem will be extremely difficult to debug. If you're lucky, you can reduce the size of the problem and run it on a single CPU, but what to do if this is not possible? But even if there are no bugs being non-deterministic is highly problematic. So you run a simulation and it gives you different results each time even when starting from exactly the same state? That's like working with an analog computer! There are certainly problems where non-deterministic concurrent algorithms may be the only way to go, but I do not have the impression that learners belong to that category. Let's not open the can of worms of non-determinism without need! That brings me to the following. Further up I wrote:
I think now that I was wrong there. There's no need for any parameter to So sure, one could provide the number of currently free cores to |
originally posted by Christoph Groth (@cwg) at 2018-04-19T14:50:11.555Z on GitLab Joseph, concerning the 4 types of goals that you raised, I think that they are perfectly compatible with the iterator API. (Although I consider usage type 2 only useful for benchmarking learners.) A reasonable default "loss" in that case would be zero, i.e. continue forever. Then case 1 would be handled by specifying a different precision goal, and cases 2 to 4 would be handled by breaking out of the loop when the appropriate condition is met. You say that you find it problematic that
changes the state of the learner. I guess that you would prefer instead
if only that was valid Python. Now it happens to be that PEP 572 (currently in the works) proposes to make the above valid Python and one strong argument against it is that assignment expressions are not really necessary in the above case because iterators already provide a way to do the same. So if you can convince me why the second syntax is actually better than the first one, I'd be actually happy because that would give me an argument in support of PEP 572! The second syntax allows values to be fed-back to the "iterator", but I actually can't think of that being very useful in general. (As I detailed in a comment above, IMO it's not useful in the case of learners.) |
originally posted by Joseph Weston (@jbweston) at 2018-05-23T14:09:11.898Z on GitLab
|
(original issue on GitLab)
opened by Christoph Groth (@cwg) at 2018-01-20T10:03:09.612Z
It seems to me that "adaptive" could greatly profit by becoming trivially extensible.
The adaptive package consists of: a collection of learners and a collection of runners. I think that "adaptive" should not strive to contain all learners and runners that are useful to someone. Rather, it seems to me that it would create the most value by establishing a simple notion of a "learner" and providing a set of generally useful learners as well as generally useful runners.
One can easily imagine learners and runners that are out-of-scope of a single package. Just to name a few that I have in mind:
Even if one believes that the above should be all included into "adaptive", my point is that adaptive evaluation is such a general idea that it is beyond the scope of a single package. This is why I suggest specifying a simple and "natural" basic runner-learner interface, such that it would become trivial to write a simple runner. One could then stop using an ABC for learners and other packages could contain compatible learners and runners without even explicitly depending on "adaptive", but "adaptive" would be useful together with them.
If the learner interface is simple enough, third-party packages will hopefully adapt it instead of using callback functions.
What is a learner? It seems to me, that a learner is best seen as something that, in accordance with some internal model of a function, can intelligently query points of a function and, based on partial or complete results of the queries, predict that function. Thus, the the core business of a learner consists of
In today's "adaptive", in addition to the above, learners have to do considerable bookkeeping (because results may be fed back arbitrarily while the learner might need specific groups of them).
What would be a natural, almost invisible, interface to the above functionality? How about the following? (From the point of view of a trivial runner):
The above pretty minimal interface to a learner consists of the following parts:
In addition to this strict minimum, there could be some optional functionality, like estimating the intermediate quality of the prediction.
Compared to my earlier attempts at adaptive evaluation I have been convinced by you guys that:
However, I still think that it is useful if learners are able to provide points in bunches and expect them back in the same bunches. Moreover, it this implicitly establishes a common priority of all the points of a bunch. At the same time, as subsequent bunches are requested without feeding back results, these have a lower and lower relative priority.
A useful corollary of this is that it is now naturally possible to drive a learner with the least possible amount of speculation but still utilizing parallelism. Consider the situation where there are only a few available cores per active learner. We want to utilize some parallelism, but without becoming too speculative, because this will increase the overall workload and ultimately slow down the whole process. With the above protocol, this means that the runner should only request one bunch of points at a time for each learner, and only request the next bunch when the result has been fed back. In the common case where this provides enough work to occupy all available cores, it is the most efficient approach.
Note that there is no way to do the same with current "adaptive", because it doesn't have the notion of, even implicit, priority. The best one can do if there are a few active learners is requesting one point at a time in a round-robin fashion. This is inefficient not only because of the lack of vectorization, but also because learner A might need only one point right now without speculating, but learner B might need three.
So it seems to me that points should arrive in bunches (i.e. a Python sequence), and should be fed back in the same bunches. In practice, one can assume for many learners and runners that points are sequences (of sequences ...) of numbers, i.e. numerical arrays. (For safety, it's useful if these arrays are immutable.)
The goal of a learner is to predict a function. What is the most natural interface to a predicted function? That of a function call:
learner(points)
.To summarize, I propose to define learners as iterators that yield sequences-of-points to be evaluated. The results are fed back using a
feed
-method. The learner's current prediction can be queried by a simple function call. I believe that this minimal interface encapsulates all the essential functionality of a learner for a wide spectrum of applications. It is sufficient for all kinds of synchronous and asynchronous runners as well as just-in-time function plotters, etc.The text was updated successfully, but these errors were encountered: