back to index

fastai v2 walk-thru #5


Chapters

0:0
5:48 Transforms
13:20 Metaclass
17:41 Why Are We Using Metaclass
22:9 The Python Data Model
22:14 The Python Data Model
22:27 Customizing Class Creation
23:0 Metaclasses
29:52 Type Dispatch Class
44:36 Treat a Transform Subclass as a Decorator

Whisper Transcript | Transcript Only Page

00:00:00.000 | Okay, is that better?
00:00:24.200 | Can you see and hear me now?
00:00:45.640 | Great.
00:00:46.640 | Sorry about the delay.
00:00:55.400 | Okay.
00:00:58.080 | I just had to restart Chrome for some reason.
00:01:17.460 | All right.
00:01:18.460 | So there's a few possible directions we can go, but maybe we could just dive into transforms.
00:01:26.960 | Not because particularly it's code that many people need to understand, but I think it's
00:01:35.440 | kind of interesting code.
00:01:36.520 | It's interesting Python code.
00:01:38.720 | So it's fun to see how the basics, the lowest level stuff is put together.
00:01:43.840 | And then we can kind of build back from there, look at data source and look at data blocks.
00:01:53.120 | Maybe first of all, though, we'll have a quick look at data blocks.
00:02:01.120 | Because you can kind of see where we're heading with this, which we can simplify.
00:02:11.140 | I guess that makes sense.
00:02:17.800 | Okay.
00:02:18.800 | So quickly, where we're getting to is here are some examples.
00:02:27.360 | So here's an example of MNIST using the new data blocks API.
00:02:33.180 | And as you can see, it's not a fluent API anymore.
00:02:40.640 | Just a moment.
00:02:44.400 | Sorry about that.
00:02:50.480 | Okay.
00:02:51.960 | So it's not a fluent API anymore.
00:02:55.600 | But now it's more of a, I don't know how you describe it.
00:02:59.480 | It's kind of similar to these other callbacks/subclass APIs like the data loader one.
00:03:06.680 | So you can now subclass from a data block and define the normal things that we're used
00:03:14.360 | to from version one, or you can construct it and pass in exactly the same things as functions.
00:03:25.560 | So the data box API ends up looking kind of similar.
00:03:30.280 | But in some ways it's kind of easier to use because you can see easily what things you
00:03:35.600 | can pass to it.
00:03:38.520 | But then it ends up looking pretty similar.
00:03:40.480 | So there's MNIST, here's pets, as you can see, looks pretty familiar.
00:03:50.880 | Here's planet, same kind of output.
00:04:00.600 | And so planet, there's a few ways to do it.
00:04:03.240 | One is, again, bypassing things in, actually, here's a super, super simple way to do it.
00:04:09.840 | Very nice and easy.
00:04:13.820 | Here's segmentation, sorry, here's Canva.
00:04:19.360 | Here is the buoy points one.
00:04:25.400 | And finally, here is Cocoa, bounding boxes.
00:04:30.320 | So you can see we're going to end up with a nice simple data block API.
00:04:32.800 | But here's the coolest part.
00:04:35.680 | Here is the entire definition of the data block class.
00:04:41.800 | As you can see, it fits on less than one screen of code.
00:04:47.120 | And the reason for that is all that intermediate API stuff we built makes creating stuff like
00:04:53.680 | this super, super simple.
00:04:57.000 | For questions about any of the data sets I just showed, have a look at the most recent
00:05:01.360 | courses, because they're all described there.
00:05:05.600 | And yes, David, this new API does present accidental wrong ordering, because there's
00:05:10.600 | no order to these things, they're just either subclasses, they're either subclasses or parameters.
00:05:21.440 | I think I'm getting a cold, excuse me.
00:05:25.800 | So yeah, that's one of the nice things here.
00:05:32.960 | So that's the kind of payoff of all this.
00:05:37.840 | It's just so easy to create these kinds of things.
00:05:41.840 | So yeah, let's start pretty close to the bottom of the layers now.
00:05:48.640 | And let's look at transforms.
00:05:50.460 | So before we do, I will answer this question from Gokor, which is my thought process when
00:05:55.680 | designing an API.
00:05:58.160 | I don't design really anything much at all.
00:06:01.080 | I just throw code out there, and I then refactor it again and again and again.
00:06:07.640 | So I try to make sure I've got pretty good tests.
00:06:10.960 | And I keep refactoring it until it gets to the point where I feel like it's so easy that
00:06:14.880 | even I will understand it in six months' time.
00:06:19.680 | With the previous versions of fast.ai, I haven't had the time to fully do that.
00:06:25.280 | But with this version, I do.
00:06:27.840 | So as you can see, every function is super short and easy, which is really nice.
00:06:37.600 | So yeah, lots and lots of refactoring.
00:06:41.400 | So transforms, the basic transform class is here, again, less than a line of code-- sorry,
00:06:54.800 | a screen of code.
00:07:00.920 | But the work's really happening in the meta class, because what happens is when we call
00:07:09.080 | done to call or decode, it's going to call end codes and decodes by this little intermediate
00:07:20.720 | thing, underscore call, which we haven't really looked at filters much yet.
00:07:27.240 | But once we get to data source, you'll see this is how we do things on just the training
00:07:31.960 | set or just the validation set or both.
00:07:33.880 | That's what filters are.
00:07:37.280 | Basically, we check whether we're doing as item as true or false.
00:07:42.760 | If we're doing as item, then we just call the function, decodes or end codes.
00:07:50.480 | If it's tuple behavior rather than item behavior, then we call it for each element of the list
00:08:00.520 | and turn it into a tuple.
00:08:04.000 | So that's all pretty simple, but the trick is what are end codes and decodes, because
00:08:15.640 | we know these are things which have a few cool things we can do.
00:08:20.920 | We can-- let's see what we can do.
00:08:25.440 | We can pass methods in or we can subclass.
00:08:35.120 | Most interestingly, we can have a type annotation and we can have multiple end codes/decodes
00:08:48.320 | with each with different type annotation.
00:08:57.800 | So there's some other things we'll look at.
00:09:05.920 | So let's look at how we-- OK, so the first two are pretty straightforward.
00:09:18.240 | Now in it, you can pass in an encoder and/or a decoder.
00:09:26.720 | And if you pass either of those in, then it will create the encoder and decoder-- encodes
00:09:34.640 | and decodes based on what you pass in.
00:09:40.440 | But as you can see, they are not just methods.
00:09:48.720 | There's something called type dispatch.
00:09:49.720 | So that's what we're going to have to look at.
00:09:51.680 | If you don't pass in an enc or dec, which will be-- that means this will be false, then
00:09:59.440 | somehow encodes and decodes have already been created for us.
00:10:04.040 | So we have to figure out how that happens.
00:10:07.280 | OK, so let's take a look.
00:10:12.000 | So answering questions, mixing, tabular and text, for example, yes, we certainly want
00:10:18.720 | to do that, but we haven't started on it.
00:10:24.980 | Not sure what you're asking exactly, AJ, about post-student projects at USF, but maybe ask
00:10:29.720 | on the forums.
00:10:30.720 | Do I have to subclass something? Simply means to type class something and then put something
00:10:36.880 | in brackets.
00:10:39.040 | So for example, there is a subclass of transform.
00:10:43.040 | In other words, I am subclassing transform.
00:10:48.200 | OK, so if we look underneath here, we can see some of the kind of behaviors being used.
00:10:59.120 | So here's another interesting behavior, which is that we can use a class of transform as
00:11:14.600 | a decorator to add a encodes or decodes to an existing class.
00:11:21.760 | So let's add that, decorator behavior.
00:11:30.040 | OK, yeah, Max, we're going to be talking about filters later when we look at data source,
00:11:39.040 | but basically just it's something that we're going to have zero generally as a training
00:11:44.520 | set, filter zero as a training set, filter one is generally the validation set, you can
00:11:48.080 | have more than just those two.
00:11:51.960 | Transforms know basically which data set they're being applied to so they can have different
00:11:55.680 | behavior for training versus validation optionally, but we'll look at that later.
00:12:03.120 | OK, so here's an example of a class transform a, which is getting an encodes, which is just
00:12:10.720 | x plus one.
00:12:11.720 | So we can check that that works, we should be able to subclass that transform, so that
00:12:17.880 | checks that that works, and here's an empty transform, so it should do nothing at all.
00:12:24.360 | So that checks that that works, but then later on, we'll get to the tests where, for example,
00:12:32.020 | we are making sure things only work on a particular subclass, in this case, tensor image.
00:12:40.000 | So here's the test, we're going to create one of these a objects, and we will call it, we
00:12:48.880 | should end up with a negative number, assuming that this was a something of type and tensor
00:12:54.320 | image, or else if we pass something which is not a type tensor image, in this case an
00:12:58.400 | int, nothing should happen at all.
00:13:02.960 | Also, when we do this, we should not be changing the type, so we check that the type is still
00:13:11.440 | a tensor image.
00:13:12.920 | OK, so let's see how some of that works.
00:13:18.520 | So the trick is that all the stuff is happening in the meta class.
00:13:23.680 | What's a meta class?
00:13:27.240 | Redex given some good examples and code walkthroughs of some meta class stuff in the forums, so
00:13:32.120 | we check that out, but basically, when you go class something like that, that's creating
00:13:44.640 | a new class.
00:13:45.640 | By the way, if you don't know, dot dot dot is basically the same as typing pass in Python.
00:13:50.800 | So that's created a new class, as you can see.
00:13:58.360 | And how does it do that?
00:13:59.920 | Well class foo, writing this, is basically syntax sugar for calling type and passing in
00:14:10.560 | these three things, a name, so foo, let's call this one foo2, some bases, so the base
00:14:23.080 | class is always implicitly, unless you write otherwise, object, so it's object.
00:14:30.680 | And a dictionary of stuff to put in the class, which in this case is empty, and so let's
00:14:46.960 | call that foo2, you can see they're very similar looking things.
00:15:02.640 | So in other words, this is just syntax sugar for calling this thing called type.
00:15:09.760 | What is type?
00:15:12.040 | You can find out the type of something by using the single argument version of type,
00:15:17.480 | and the type of type is type, in other words, type is a class, and so this is a constructor,
00:15:28.400 | and it constructs something, so if I go type foo2, it constructs something of type type,
00:15:41.440 | and so there are all kinds of attributes that a type has.
00:16:04.240 | So in particular, one of the important ones is I can put here a colon one.
00:16:11.760 | The most important is dundadict, and dundadict is basically a dictionary, it's a slightly
00:16:21.960 | special kind of dictionary, but it's a dictionary which contains a mapping from names to values,
00:16:41.200 | and in particular anything that I passed in here ends up in that dictionary.
00:16:46.160 | There's another way to put things in the dictionary, which is to use this special syntax sugar
00:16:49.720 | that we get in Python, which is like this.
00:16:56.920 | And so now, if I say foo.dict, you can see it's got the same thing, a equals one.
00:17:10.640 | So when you type that into a class, it's actually just a shortcut for creating an A attribute
00:17:21.280 | in the dundadict attribute of a class.
00:17:29.040 | Okay, a bunch of questions, what's for recommended systems, no plans yet.
00:17:42.360 | Why are we using meta class?
00:17:46.720 | Basically among the reason I started using meta classes in version 2 is because I wanted
00:17:56.080 | to change the things about how Python worked that I didn't really have the time to change
00:18:01.260 | version 1.
00:18:02.760 | So things like this, all the stuff we're going to look at doing with transforms are impossible
00:18:08.120 | to do without meta classes.
00:18:12.160 | So yes, max type creates class objects and then class objects create instances, exactly
00:18:18.840 | right.
00:18:20.480 | So, it also means in Python, it's worth knowing how these syntax sugar things work.
00:18:28.140 | When you go like this, if you go like that, for example, then that's the same as saying
00:18:39.800 | f equals and then passing in some function.
00:18:44.680 | And so if you look at the dict, you'll see you end up with f and then some function.
00:18:54.040 | So really, there's not that much - there's a small, concise, elegant set of foundations
00:19:04.480 | in Python and the kind of stuff that we type day to day is a bunch of little bits of syntax
00:19:10.400 | sugar for those foundations.
00:19:13.980 | So if I go foo.a, that is also syntax sugar.
00:19:20.880 | And specifically, it's syntax sugar for dunder dict a.
00:19:34.080 | So this is all important to understand when we look at meta classes.
00:19:41.380 | And the reason why is because a meta class is something where we're going to replace
00:19:45.200 | type.
00:19:46.200 | We're going to say I want to create a class that does not use a type constructor but uses
00:19:53.560 | some other constructor.
00:19:54.560 | And the way you do that in Python is you type meta class equals and then you type the name
00:20:01.680 | of the class you want to construct this class.
00:20:05.720 | You can create a class from scratch, but normally you wouldn't.
00:20:09.860 | It's easiest to subclass type.
00:20:13.240 | So here I'm going to inherit from type.
00:20:18.640 | And if you remember, type takes three things, object or name, basis, dict.
00:20:29.040 | So dunder new always requires class first and then here it is, name, basis, dict.
00:20:35.220 | So if I wanted to create a super simple meta class, then I could just...
00:20:56.280 | So here's something which just returns super.
00:20:59.640 | So it's not going to change anything at all, but it will work, right?
00:21:04.920 | Meta class equals m.
00:21:07.400 | There we go, I have a class.
00:21:15.240 | It has a dunder dict.
00:21:17.600 | So you can now start inserting things in here.
00:21:26.200 | And see how this printed as soon as I typed class t pass, right?
00:21:29.960 | It didn't print after I created an object of type t, it appeared as soon as I created
00:21:35.960 | the class.
00:21:37.680 | So Python is going to call this code any time I try to actually create a class, not when
00:21:44.040 | I try to instantiate it.
00:21:47.520 | Yep, res is result.
00:21:51.280 | So in this case, we are placing three things, new, call and prepare.
00:22:07.600 | So there is a really cool piece of documentation called the Python data model.
00:22:13.360 | And the Python data model describes how all this works.
00:22:18.480 | And not just all this, but everything, it describes how Python is, how everything happens.
00:22:26.900 | So there's a section called customizing class creation.
00:22:33.000 | Where you can see all of the stuff that happens, including three, three, three, one meta classes.
00:22:38.240 | By default, they're constructed using type.
00:22:40.560 | So I could click on type.
00:22:45.060 | And we can see in three arguments, it returns a new type object.
00:22:48.520 | And then we can find out about type objects.
00:22:55.840 | And so forth.
00:22:57.460 | So meta classes, as it says, you say meta class equals blah.
00:23:06.960 | And the first thing that happens is it has to prepare the class namespace.
00:23:14.400 | And the class namespace is the dunderdict, is the dunderdict object.
00:23:25.760 | So if we were to keep creating our underscore m, we could just return an empty dictionary.
00:23:42.880 | And as you can see, it all works.
00:23:49.240 | And I guess we should be able to put something in it, even.
00:23:55.880 | OK, there it is, right?
00:24:02.300 | So you can see dunderdict is created by calling your dunderprepare.
00:24:06.920 | And this is actually a way you can insert something into every single class.
00:24:12.080 | Which has a meta class.
00:24:17.080 | So to train the different arguments to the meta class constructor, you would want to
00:24:27.040 | read 3.3.3 of the data model reference in the Python docs.
00:24:39.240 | So in our case, we've replaced prepare.
00:24:44.080 | So it's not returning a dictionary, but instead it's returning some special kind of dictionary
00:24:49.340 | called a tuffundict.
00:24:51.480 | So a tuffundict is a dictionary that overrides dunder set item.
00:25:05.380 | Actually I'm planning to change this.
00:25:07.680 | So I had things set up so you could use either underscore or encodes.
00:25:11.660 | I'm actually going to get rid of the thing that lets you use underscore.
00:25:14.280 | So ignore that.
00:25:16.400 | So basically, if you're calling something that isn't encodes or decodes, then it's just
00:25:23.360 | the normal dictionary.
00:25:24.360 | As you can see, this inherits from dict.
00:25:27.160 | But if it is encodes or decodes, then what I do is I check whether or not I already have
00:25:36.640 | different encodes or decodes in my class.
00:25:40.760 | And if I don't, then I create one using dicts set item.
00:25:51.720 | And what I set it to is I set it to a type dispatch object.
00:25:57.940 | So in other words, this tuffundict is something which behaves exactly like a normal dictionary
00:26:06.520 | and so it's going to be inside my dunder dict for anything that uses this meta class and
00:26:11.560 | it's not going to work differently at all except for two special things called encodes
00:26:15.480 | and decodes.
00:26:16.480 | And in those cases, it's going to use something called type dispatch.
00:26:21.400 | So let's try that.
00:26:25.080 | So if I go plus a meta class equals to for meta, well, let's and then let's say def
00:26:54.440 | encodes self, turn X, so a dot encodes, oh, that's not a normal function, what type is
00:27:09.120 | Oh, it's a type dispatch.
00:27:12.120 | And that's because of this.
00:27:14.400 | So do I create a meta class instead of inheriting from the class?
00:27:18.680 | They do different things.
00:27:20.280 | By inheriting from a class, you can't change the behavior of type creation.
00:27:25.760 | So we're trying to create something where anything that is inheriting from transform
00:27:33.320 | gets a different class behavior.
00:27:37.200 | And so it's impossible to do the things we're describing by inheriting.
00:27:41.720 | There's no way, for example, normally to be able to say, I want to have two different
00:27:50.920 | encodes for two different types, for instance.
00:27:56.800 | That would normally be impossible.
00:27:59.120 | But thanks to meta classes and to done to prepare, we know that each time it sees this,
00:28:05.180 | it's going to call our replacement dicts under set item with a key of encodes and the value
00:28:11.520 | of the function, and we can do whatever we like.
00:28:16.720 | And so what we're going to do is we're going to create an encodes, decodes type dispatch
00:28:21.360 | object if we don't already have one, and then we're going to add this function to that type
00:28:26.300 | dispatch object.
00:28:27.840 | So let's try that.
00:28:33.520 | If I add this twice with two different things, you can see now float has one thing and int,
00:28:41.320 | it's a different thing.
00:28:43.320 | So let's then have a look at type dispatch.
00:28:52.240 | So yeah, this is like a huge rabbit hole.
00:28:56.480 | It's basically how do you make Python do whatever you wanted to do.
00:29:02.560 | Python is a dynamic language, and so they created this amazing data model to allow us
00:29:15.300 | to customize anything we like in Python, but it takes some time to get used to.
00:29:20.680 | So it taught me a lot of reading and studying and looking at lots of different places to
00:29:27.720 | get the hang of all this, and so I don't expect to understand it all the first time around.
00:29:32.600 | I'm not sure what built-in functionality you're referring to, Max.
00:29:37.960 | I don't think Python has anything which does what I just described, which is why I'm doing
00:29:44.920 | something I don't think it does before.
00:29:48.560 | Okay, so here is the type dispatch class, and basically it's going to be something which
00:29:57.640 | we know it's going to have to work a lot like a dictionary because we're going to be adding
00:30:02.920 | things to it, and what's going to happen--actually, I haven't quite shown you all this yet--what's
00:30:11.380 | going to happen is we're going to--let's see, we're going to grab encodes, we're going to
00:30:21.840 | call--oh, yes, so we're going to call the function, and when we call the function--well, Max,
00:30:31.480 | we're not just checking types, we're dispatching on types.
00:30:33.760 | There's a big difference between checking types and dispatching on types, so we're actually
00:30:36.680 | building something where we're able to call different code depending on types, so yeah.
00:30:45.680 | So there isn't a way to do that in Python, so as we've discussed in previous walkthroughs,
00:30:54.400 | when you look at, for example, data augmentation, we're going to have class rotate, which when
00:31:00.520 | we first define it might be empty, and then later on when you say, "Oh, I've got something
00:31:05.080 | for a tensor image," then we'll say @rotate, and it'll say def encodes, x colon tensor image,
00:31:18.960 | and then degrees, and then there'll be some functionality for that, and then in some other
00:31:23.960 | place there's going to be x colon bounding box, and it's going to be different also whether
00:31:29.400 | we're encoding or decoding.
00:31:31.800 | So it's a--it's quite different functionality.
00:31:39.340 | So yeah, so type dispatch.
00:31:42.920 | Dispatch is referring to how do you--how does a programming language decide what piece of
00:31:47.100 | code to run when you call something?
00:31:52.480 | So for example, there's all kinds of different ways of doing dispatch in Python.
00:32:01.540 | The main one that is used for methods, for example, is something called MRO, which is
00:32:09.800 | a method resolution order.
00:32:11.720 | Yeah, so it's like--it's basically all the rules in a language about how do you decide
00:32:18.860 | which piece of code to call when you call some function, and different languages do
00:32:25.280 | it all kinds of different ways, and yeah, check out some of the earlier walkthroughs
00:32:30.560 | if you want more information about why we're doing this dispatch.
00:32:35.000 | Yes, thank you, I mentioned that should have been transformed if we want that to work.
00:32:47.720 | So, what we want is we want something which works--looks like a function, so that means
00:33:01.280 | it has to have it done to call, but when you call it with some argument, we're not just
00:33:10.240 | going to call a function, but we're going to look at the type of that argument and we're
00:33:14.800 | going to try to find the appropriate function or method to call based on the type of that
00:33:21.360 | argument, and based on which methods have been created so far.
00:33:33.400 | And so, what's going to happen is that inside our type dispatch object, there will be a
00:33:40.560 | dictionary called "funx" and that's going to contain a dictionary where the key is the
00:33:46.240 | type.
00:33:47.240 | So, for example, in this case, we're going to have keys "tensor image" and "bbox" and
00:33:53.920 | the value is the actual function to call.
00:34:00.800 | So that's the key thing, is the "funx".
00:34:06.480 | So then, there was an "add" method and that is what FIFM dict calls.
00:34:17.560 | It adds this function, and the "add" method is going to find out the type annotation for
00:34:27.480 | the first parameter, "p1-anno" is parameter number one annotation, so it will grab the
00:34:33.320 | type. If there is none, then it's assuming that it's object because that's the highest
00:34:37.560 | level of the type hierarchy, and it's going to pop that into our functions dictionary.
00:34:44.480 | So then, later on, when you call "dumda call", it's going to look up the type of the parameter
00:34:53.560 | that you're calling this function on, and it's going to look it up in this object.
00:35:00.080 | If it doesn't find it, then it does nothing at all, so that's kind of the rule, right?
00:35:05.800 | If you only have like a "rotate" defined for "tensor image" and "bbox" and then you call
00:35:12.400 | it with "int", then nothing happens because that function isn't defined.
00:35:21.920 | If we did find it, then we just call it with that argument and anything else that you passed
00:35:30.040 | along. And you can actually tag things to say, "I want you to turn it into a method",
00:35:36.920 | which is something we might talk about later if people are interested, but basically it's
00:35:42.440 | just going to create a normal function unless you ask it to be a method.
00:35:48.360 | So the key thing, then, is how does this line of code work? How does it look up the type?
00:35:54.800 | So as you can see, it's calling "dunda get item", and basically what we're going to do
00:36:00.600 | is we're going to keep a cache, which is a dictionary mapping from types to functions.
00:36:10.480 | And we need a special cache dictionary because the way type dispatch works is it doesn't
00:36:15.480 | just look up, say, "tensor image" or "bbox", but it also looks really subclasses of those
00:36:22.160 | things. So, for example, we could have also, as we discussed in an earlier walkthrough,
00:36:29.200 | "tensor". And in this case, then it's going to, if you pass a tensor image, it'll grab
00:36:35.160 | the most specific version it can, which is a tensor image version.
00:36:47.480 | So I think I just answered your question, which is the opposite of that. If you call
00:36:50.440 | it on something which is a subclass of "tensor image", it will be invoked. And the reason
00:36:55.360 | why is because of how we create this cache. And so if we don't find it in the cache, then
00:37:05.760 | we're going to add it to the cache. And what we do is we create a list of all of the types
00:37:15.040 | that are registered, for which there is the appropriate subclass relationship. And we,
00:37:28.760 | how do we do this? And notice that the functions have been ordered by a comparator which checks
00:37:43.880 | for subclass relationships. And so we grab the first one. And so the first one is the
00:37:49.680 | most specific type of the ones that it's a subclass of. This takes a little bit of time.
00:37:58.080 | We don't want to have to do this every time we call this function. So once we find the
00:38:01.400 | right one, we pop it in the cache. So next time around, we can just grab it. Okay. So
00:38:16.000 | that is type dispatch. And so the key way to understand it really is to look at the tests,
00:38:22.120 | right? So you can see here we've got things at lots of different levels of hierarchy.
00:38:26.600 | There's a parameter that's a collection, some kind of integer, tensor, mask or image, or
00:38:36.800 | some kind of number, right? So these are all functions. So we can create a types dispatch
00:38:44.580 | object with all of those different functions in. And so we should try, if we look up int
00:38:55.720 | for instance, then we should get back this one because none of them were defined with
00:39:05.400 | int specifically, but it's going to match number and integral. Integral is more specific
00:39:14.760 | type than number. So that's why it matches that. String doesn't match any of them. So
00:39:23.880 | it's a none. So here's the same kind of thing, but this time after creating the object, we'll
00:39:34.040 | actually start calling some functions and make sure that they do the right thing. Okay.
00:39:47.560 | So that is type dispatch. So yeah, to get the Python data model in your head requires kind
00:39:56.960 | of putting all these things together. But the nice thing is once it is all in your head,
00:40:02.320 | you can put it together the way we have here. So we were able to replace the normal dictionary
00:40:11.760 | by replacing prepare with to firm dict. The firm dict is something which instead of a
00:40:19.640 | normal dictionary, when you set the item as encodes or decodes, it actually creates a type
00:40:25.620 | dispatch object and adds to it. And that means that now when we inherit from transform and
00:40:39.440 | we create an encodes or decodes, um, attribute, uh, it will actually add it to the encodes
00:40:46.360 | or decodes type dispatch. And so when we call encodes or decodes, we get this behavior, which
00:40:55.400 | is to get a negative version of this because it's a tensor image and a non-negative version
00:41:01.600 | of this because it's not a tensor image. Um, so, um, this specifically is single dispatch,
00:41:13.080 | not multiple dispatch. So, um, it only looks up. Let's find it. Um, when you add it, it
00:41:24.980 | only adds the, so P one ano is a function, any tiny little function here, which just
00:41:35.240 | grabs the, um, added the first parameter annotation. So this will only work. Uh, so this will only
00:41:40.720 | do type dispatch based on the first, um, non-self parameter. So for me, that's a very good question.
00:41:50.060 | Why not throw an error? Um, basically because of what I was describing before, if you are
00:41:56.680 | defining say, uh, rotate data augmentation for various types, um, then generally speaking,
00:42:06.860 | if you don't have it defined for your type, then probably what we want to do is nothing
00:42:14.160 | at all. Um, so the basic idea is that these kind of transforms are things you can opt
00:42:21.040 | into. Um, if you do want to create a transform, um, um, if you do want to create a transform
00:42:37.440 | which, uh, throws an error, uh, if you, um, if you call it with something that doesn't
00:42:45.060 | exist, you certainly can. Let's do it. So here's a transform. And so if we say def encodes,
00:42:57.000 | and we'll say here, raise not implemented, and then we'll go def encodes self comma x
00:43:09.600 | colon int return x plus one. So let's go a equals a, and then we can go a one that returns
00:43:22.640 | two a high, and that's going to return. Oh, is that not the right one? There we go. Not
00:43:37.280 | implemented error. So you can certainly, um, um, add that. And so the reason this works
00:43:43.600 | is because to remember that if you don't have an annotation, it's the same as saying object.
00:43:48.440 | It's kind of the way Python normally does things. That's the way we do things too. And
00:43:52.760 | since that's the highest thing up in the inheritance, in the inheritance hierarchy, that's the one
00:43:57.360 | that it would end up calling if you don't provide some other behavior. Uh, so thank
00:44:05.440 | you for that. Excellent question. Um, okay, so that is the way that we handle dispatch.
00:44:31.840 | So the next thing is what about the way we can treat a transform sub class as a decorator?
00:44:44.680 | So if you remember a decorator, um, uh, when you see something like this, um, we'll actually
00:44:52.840 | call this as a callable and pass this function to it. So that is basically identical to saying
00:45:04.600 | something like def underscore encodes equals that. And then encodes equals, um, a parenthesis.
00:45:16.520 | And actually what I should say is no, I just say, yeah, a underscore encodes. It's basically
00:45:28.800 | the same as doing that. Um, and we should find that the same test then passes. Um, but
00:45:37.400 | we have to say a equals that. Um, let's see. Ah, yes, it's slightly different because it
00:45:52.680 | thinks of it as not as a method. Okay. So it's not exactly the same. It's nearly the same,
00:46:02.720 | well because of Python, because of stuff we do. So we'll come back to that later. Um, okay.
00:46:09.000 | So the reason we can use this, um, as a decorator is because that means that our class, um,
00:46:22.640 | is going to have to support, um, the callable basically. Um, now classes are normally callables
00:46:29.360 | because you can, um, instantiate a class, obviously. So if you go class B like this,
00:46:37.800 | then you go B equals B, then you're calling B as a callable or B's type, B's metatype
00:46:46.200 | as a callable. Uh, so that means if we go to our meta class, we can redefine under call.
00:46:54.080 | And that means we can redefine what happens when we instantiate this class. Um, so if we
00:47:04.760 | instantiate this class or if we, if we done the call this class, um, and we pass in some
00:47:12.920 | argument, um, and if that argument is callable, um, and it's not decodes or encodes, then we
00:47:28.840 | are going to, um, add that function, uh, to our type dispatch. Um, and so that's exactly
00:47:38.800 | what happens when we say at a def encodes, blah, blah, blah. It's just going to add that
00:47:46.040 | function to this class's type dispatch. So you can see here it calls dot add, which is
00:47:58.280 | the dot add that we had here in type dispatch. So this is the thing that basically registers
00:48:05.800 | another type. Um, okay. So, uh, that's the hat. Uh, there's one last piece, which is done
00:48:23.160 | to new. So this is the thing that first gets called when you're creating a new type. Um,
00:48:32.240 | and one of the things I discovered, which is super annoying, is that if I create a new
00:48:37.240 | type, like so any title type, even without being a meta type, and I define a done to
00:48:44.240 | new, like so, so here's some class and then I define some sub class and I say in it, like
00:49:13.080 | so. And then so I want to instantiate that and then I shift tab. Um, oh, that was not
00:49:23.200 | what I expected to happen. Oh, sorry. And plus B inherits from a shift tab. You can
00:49:33.920 | see the signature is star, star, star quags, or else I would have expected the signature
00:49:39.200 | to be a, as it would be if I removed my base class. Um, so I found that super annoying because
00:49:49.080 | like you want to customize new all the time. Not all the time, but very frequently. Um,
00:49:54.480 | and pretty much most of the time when you customize it, you're going to use star, star,
00:49:58.280 | star quags because you don't want to define what base classes can do. Um, but as soon
00:50:03.480 | as you do that, you, you kill the signature, like so. So, um, I don't know why it works
00:50:20.240 | that way and maybe I'm missing something, but what I did here was I replaced the signature
00:50:29.640 | for the class with a signature for the dunder init so that we get the right nice signature.
00:50:37.080 | So if we try that, here we have a look at transform. You can see we get the correct
00:50:45.240 | signature as we would want. Um, because otherwise, uh, it's not just under new, it would also
00:50:55.120 | be done to call from the meta class, uh, would also replace the signature otherwise. Um, so
00:51:02.680 | that's why that's there. Um, so believe it or not, that's actually all the pieces. Um,
00:51:12.580 | and so if you want to study this, like, so the first thing to point out is none of this
00:51:17.120 | is at all necessary to understand fast AI version two. It's no more important than understanding
00:51:24.720 | the meta object data model is to, um, use Python day to day. It's an advanced technique which
00:51:33.600 | you can learn about if you're interested. And if you're interested in learning more
00:51:38.340 | about how Python works behind the scenes, so you can try doing stuff like this yourself
00:51:42.600 | if you want to create, um, change how Python works and fully use its dynamic features.
00:51:49.840 | So if you want to fully understand what transform does, just check out all the tests. Um, we've
00:51:55.840 | tried to make it so that each test does one thing, you know, one shows one clear type
00:52:03.540 | of behavior. We try to add comments explaining what each behavior is that it's showing. Um,
00:52:13.720 | so yeah, hopefully that all is useful for those of you that are interested. So then you can
00:52:20.440 | see tuple transform and item transform just force an item to be true or false and lots
00:52:28.880 | more tests of that behavior, as you can see. Um, all right. Well, I think that's enough
00:52:38.240 | for today because that was super dense. Um, and, uh, yeah, if you start looking through
00:52:43.040 | this code and want to learn more about it, feel free to ask any questions you like. All
00:52:48.480 | right. Thanks, everybody. Bye.
00:52:51.360 | [BLANK_AUDIO]