BayBE: A Bayesian Back End for Experimental Planning in the Low-To-No-Data Regime
In the evolving landscape of data science, advanced computational tools are crucial for driving innovation and efficiency. This tutorial introduces the Bayesian Back End (BayBE), an AI-assisted open-source experimental planner developed by Merck KGaA, which utilizes Bayesian Optimization and machine learning to smartly streamline experimental workflows in the low-to-no-date regime. From chemical reactions to biological assays to coffee machine settings - with BayBE users can find optimal configurations in an iterative manner, which is anyway the main working mode of many experimentalists.
We will start the first part with a brief introduction to Bayesian Optimization, highlighting its principles and advantages in experimental design. Following this, we will showcase BayBE's unique features, including elegant categorical encodings and advanced capabilities like active learning, transfer learning or Pareto optimization.
In the second part, we explain some of our code and test design choices that went into the open-source Python package baybe. This will include learnings about our built-in (de-)serialization engine, CI/CD, advanced hypothesis tests, autodocumentation and open-source tools BayBE is built on.
The final part will comprise of a hands-on tutorial. We will look at representative problems and guide potential users from formalization of the problem to performing the iterative loop to analyzing the results including an assessment of parameter relevance. The tutorials can be accessed here.
This session took place in track PyData & Scientific Libraries Stack and was classified suitable for intermediate domain / novice python by the speaker.
Transcript (auto)
Auto-generated from the recording utilizing Open-Source AI. Speaker labels (Speaker 1, Speaker 2) reflect diarization, not identity. Timestamps refer to the recording.
Speaker 1 [00:07]
Thank you so much. So with this I guess I will shove off my co-speakers for the moment because we have actually a little split into three sections today. So this tutorial, this talk today is about Baby, which is a shortcut for Bayesian backend. And you can use it for experimental planning, particularly in the load-to-node data regime. And that is actually an extremely relevant regime in industrial context. So before I start, again, here we have this split into three sections, roughly one for each. There will be a hands-on section in the end. I will start with some basics and introductions and show you some examples of the outcome. And between that, there will be actually some details about the code decisions, because this kind of code is a very heterogeneous, quite complex code, and so there were quite some interesting choices. So we will talk about all of this. But before we start with any of the actual content, some words from, you know, some quick facts about where we're from. So we come from Merck. Actually, we are just down the street. So this is our home base here. One important fact about Merck is there's two companies in this world that go by Merck. We use the name everywhere except in Canada and US. And there's another company, an American pharmaceutical company, that uses the name only in Canada and the US. And it's very important that you attribute, let's say, press or LinkedIn posts to the right company, especially if it's kind of bad press, of course. So make sure to not make the mistake of confusing these two companies when you read that somewhere. A little bit about what we do. So we are basically a very old science and technology company. And there are three kind of sectors that we work in. So healthcare products, life science products, and electronics. So healthcare is, of course, the classical drug business, for instance, for fertility treatment. Life science is reagents for biochemistry and services for chemistry and also reagents for chemistry. And electronics is display materials or, for instance, semiconductor materials. And this is already my segue to the method that we're going to talk about today, because what we do in these three sectors is quite different. So no one who is a healthcare professional would probably be able to do a materials problem in the electronics sector. So that's quite, yeah, from a scientific perspective, a very different type of work. But we do all these things in the three sectors, which makes our life a bit interesting. Because sometimes we find methods that are applicable in all of these sectors. And this is where I will, you know, introduce the method to you. So it is Bayesian optimization. A quick word about why, again, is this such a good match? So here I brought five problems. From the left, something that's purely virtual. Model parameters could be for a machine learning model. Hyperparameters could be for any kind of financial model. Reaction conditions like chemistry, experimental design for material science or formulation. Like once you have your drug, you need to put it into the right tablet or something like this. And process design in general. So again, those are five sections that the scientists working on this would probably classify as very different. But actually, if you think about them abstractly, you have something where you can control the parameters, so the things you're going to change in an experiment, and you will measure the outcome, because most of the time those problems are black box problems. So we do not understand what parameters lead to what outcome. This is why we treat all of these problems most of the time as black box problems. So we probe the black box, we set the parameters, we observe the outcome in an experimental measurement, and then we try to learn from that. So this is actually something that unifies all of these problems. And here is an example that is not from the industry. So first of all, if you have such a stack, many people might have something similar at home. If you have this at home, congratulations, you're doing very well. This costs 15,000 euros, I think. But even if you have cheaper equipment, you can also recognize that there are many process parameters here. And if you get for your first time your fancy espresso machine, you have to select all these parameters, like which beans, the grind size, and the dose, do I do pre-infusion, how long, the pressure, the temperature, all these kind of things. And the thing you want to optimize is obviously the taste. So this kind of iterative optimization of black box problems is ubiquitous. It's everywhere. And here, once again, I brought five examples. five people think that they you know typically they would say i have a very different problem but we put on our data science goggles or you know our abstraction goggles and we look at that and actually what we see in all of these cases people want to do optimization obviously so um they want to maximize the yield you want to minimize model loss optimize the absorption of a tablet by choosing the right parameters how it is printed even in 3d printers for example or you know Generally made but all of these problems are optimization problems and this black box approach means we probe them in iterative cycles It turns out there's a perfect match for this problem and that's patient optimization. So at the end of this talk of our tutorial, I hope that you agree with us. This is a perfect match and That is basically the aim that we would like to convince you Before we start to get into the method one more word about motivation because we should not just think about the problems but also about what kind of data regime are we in. Nowadays, there's, of course, a lot of fancy deep learning methods, but also big data methods. So I put this little axis here, where on the left we would say, okay, we have no data, we have to treat a problem without having any data. And on the right, we would say we have big data. So where is the typical industrial problem here? If you have no data, you actually cannot do data-driven modeling. So what people typically do is use one of these equations, these are workhorse equations in all of industry, very prominently used to simulate your process in the computer. So the outcome of your experiment, if you can simulate it, congratulations, but most of the time we cannot actually simulate them. So when we have no data we have to use these methods. If we have big data, again we can use big data methods, most of the time actually we do not have big data. have big data, so we have big data in terms of a lot of text, unstructured text or so, maybe a reaction database, but that's already it, so industries do not often have big data. And I'm saying, or at least that's my experience, most of the time we are in this regime where we have either no data, but we can perform a couple of experiments, or we have a handful of experiments, so small compared to the number of parameters or so, and we adopted the low-to-no data regime. These two quotes here are things that colleagues often ask us. They say, I have no data. Can you help me? I can perform some experiments. Can you help me with a data-driven model? Or they say, we have some experiments, but we don't know how to continue, how to find the right parameters. So those actually, again, there is an excellent match for this data regime that's also Bayesian optimization. So let's very, very quickly go into the basics here so everyone is on the same page. I tend to think about Bayesian optimization as two important ingredients that are merged in a kind of third ingredient. So the first ingredient is we will use a machine learning model. So even if we are in the low to no data regime we will use a machine learning model and that might sound very trivial to you but I can tell you the state of the art for these experimental designs often uses very simple models that you would probably not even classify as machine learning models. And then comes the most important We need probabilistic models, so models that are where uncertainty comes with them. And we will simply combine those two in the third ingredient. That's called the acquisition function. So a very quick word about both. So here is why we cannot just use predictive models. This is a situation where you see this works really, really well. So we have some measured points. We have the true curve we want to minimize in this case. And we have our surrogate model that's modeling this process. And now we want to ask the model, where should we go to minimize this? And it would, of course, point us directly into the space in there. So all good, right? Actually, in reality, it will look more like this. So you will have many parts of your space that are unexplored, and the model is not good there. And if you look at now the bottom right, actually the true minimum is here to the very right, but the model has no data in the vicinity there, and actually believes the values go up. So if you ask this model, it would never, ever recommend you to go there if you just go according to the predictions, which is the line. So just using predictive models in the low-tunnel data regime is not necessarily the best idea. This is why we need this uncertainty. So typically, we show these things in curves like this, where you have, again, predicted. You have measured points, which are the black dots. You have this surrogate model with its uncertainty node, the shaded region in purple. And we have a true response function. And here are some popular models that are used with this. The Gaussian process, random forest, or this very ominous tree parsing estimator that you might know if you use this Optuna or HyperOp package, quite well known, I think. But there's better and worse models. So we generally don't like to use random forest, for instance, in approach. I will not go into the details, so we don't have the slide here, and we have it in the I think, but it's not that the point is you should not just use any model. There are certain kind of restrictions. For instance, uncertainty should be smooth. You need to join posterior and these kind of things. So just a quick word about the model, but let's go into the third ingredient. So if we only go towards the predictions, that's called exploitation. So that's on the left. I told you we need this other ingredient, the uncertainty. If we were only following the uncertainty about our predictions, that would be called exploration. In practice, we want to do neither, so we need a balance of these two things, and that is the acquisition function. This is simply combining those two aspects. There are some use cases where you want to do pure exploration, and you can do that by choosing the according acquisition function, but in general, you need something that mixes, and here is this very basic, but I think quite good, GIF from Wikipedia that shows you notice measurement process in each rate of action so on the top again this picture and you see data points are being added and the uncertainty in some regions then reduces and actually if you if you observe closely the very first measurement is not close to the optimum so in this case it should be maximized but it measures some point to the right because at that point the acquisition values there were highest and you see the acquisition three possible choices for acquisition functions plotted below and I think you You know, one of the simplest acquisitions functions you can think of is the probability of improvement. That is literally just the integral of the probability of the point you would consider as a candidate, the integral of the probability that this point gives a higher value than your best observed point currently. So that's an extremely simple one. In practice, we use other ones. But yeah, this is how it works. And now let's, so technically how it works, let's look at that in practice. So in practice, of course, then we have our experimentalists. They design their problems, so that just means I have to tell the algorithm what parameters do I want to change and what ranges, what are my categories, and the values of that. We call that setup. Then in Bayesian optimization, you can add pre-existing data. This is actually, again, a point that might seem very obvious, but the competitor of this method, it's not even possible to do that, at least not easily. So you can add your pre-existing data. optimization, and then you go into this loop where you get the recommendation, you perform that recommendation, you look, if you're happy you can stop your experiment, but if not then just continue until you're happy or run out of budget. So I think a very easy kind of workflow. This is the competitor, classical DOE, classical design of experiments. I will not go into detail, but look how it's different just visually. It's one straight line and there's no loop. So you design your problem, you cannot add initial data, then you receive a plan. That plan is, of course, motivated by smart mathematical choices, but it's a rigid plan. It tells you, do my 30 experiments, and then I give you a simple linear model. This is what comes out at the end. And, yeah, then you have that model, and then you can ask the model, where should I go to find my optimal point? So it's very different. There is no loop. Of course, you could repeat that process. Sometimes people do that. but I want to highlight here this the start is different, you cannot add initial data and there's no flexible loop I can perform this loop one experiment after the other, I can perform the loop on the top also in batches of 10, so it's very flexible and one reason that's also very nice that we need to think about in our industrial role how can we convince colleagues to even use this? And you would not believe, even you can show the best benchmarks that a technically minded audience would be immediately convinced experimentally minded people will still not necessarily follow that and use that so you need something that's easy for them to understand and adapt and this is also why Bayesian optimization is very nice it is actually visually it's very similar to the human approach, the only difference is in the beginning again the human, you wouldn't say at data points, you would say I study literature and I have education and experience but it's very similar and then the analysis of the results and the proposal of the recommendation is not done by algorithm but by the human of course by the expert and so this is um this is the main difference but other than that it looks very similar and that's great for us because it's easy to sort of yeah convince or you know bring this method to to an existing workflow or project in the company so a very quick example um this is virtual process you know it's not easy for us to always show internal data but this is a virtual process so we can show that and it was designed by some of of our experts who work on similar processes all the time. So you just have to imagine a process with knobs. In this case, it was five knobs, so five parameters. And it follows this explicit equation, not very difficult, but we were assured that it's a realistic use case for a simple process. So five parameters, we want to maximize the outcome of this function. And we just had one target, so very simple use case. What comes out of this is an optimization curve of baby. And that's, in this case, in blue. So we perform our experiments on the x-axis. On the y-axis, we show the best result that has been achieved up to that iteration. And all of this is repeated, of course, to average out the random influence of the start and such things. So you see this little shade there as well. I will not talk about a random curve. It's just a basic baseline. But I will talk about the CCD-DOE. So that is a type of classical design of experiments. And you see there's no curve. That goes back to the thing I said earlier. It gives you a rigid plan. You perform that plan, and then you get your one-shot recommendation. That's where you end up here. And that's why there's no learning curve. There's just one result for this DOE. And, of course, it looks much worse, right? So if we were to, like, it doesn't reach the absolute optimum, which is on the top at the dashed line. And even if we were to compare, you know, when does baby reach the value of the DOE, it is about one third one fourth of the number of experiments you need on the on the x-axis so you save a lot of time and effort now we made this a bit difficult a bit more difficult just because we wanted to study how well does this for instance if we have more parameters and i added just five dummy parameters so we optimize the same equation but now the black box has 10 knobs five of them are irrelevant to the outcome but you know we don't know that when we look at it from the outside So this is the result with baby. You see it's gotten a little bit worse. It is expected, I think, you know, more parameters. It's more difficult. But the DOE is not on here. And maybe you can guess why, because actually the result is off the chart, so it would be over there. This is why I also zoom out. So this is a de-optimal DOE. It's really a state-of-the-art design. And this is really how bad the result would be. So it's so many more experiments, and it's not even close to the optimum. In practice, this means nobody would ever even attempt to use DOE for this problem. So Bayesian optimization can also, you know, open doors to tackle problems you would not be able to tackle before. Of course, there are things you can do, you know, in classical DOE. You would probably first reduce the number of parameters you have and then do your classical DOE thing. But we don't need to think about that much in Bayesian optimization. So it's a big advantage here. And I think this result speaks for itself. So there was a point when we realized, actually, there will be many, many, many, many use cases in the company. And this is why we sort of tried to join efforts. The speakers you see today on stage are from different groups. And we decided just in the company, let's collaborate and combine these efforts. It doesn't make sense that everyone writes their own code base. So this is why we created Baby, the Bayesian backend. It has been fully open sourced, actually, with a permissive license, so Apache 2. Nothing hidden here in the industrial sense or so. You can freely use it. Happy that it is accepted, accepted publication, digital discovery. And so with this, we also partnered with other institutions and consortia. So if you are interested in this field, just free to use it. But the next thing I want to actually talk to you about is, you know, there is, you might be aware that there's many other packages that do something similar compared to what I've shown you now. This is because I've only talked so far about the core of Bayesian optimization, which is this simple loop. And I've only shown you an example with simple continuous five parameters. In reality, actually, our requirements are much different. So in the industry, we rarely have people who only have five continuous numbers as a parameter. That is the first thing that's very different. For instance, people have categorical parameters. So this requires you to be able to handle hybrid spaces and also optimize in hybrid spaces. We have mixtures, so there are certain constraints in mixtures. We have our categorical parameters often corresponding to molecules, so you can do some smart encoding. There's the potential of distributed workflow, so there's not just one lab, but there could be five labs working on the same problem distributed across the world. They should, of course, not do the same experiment here, so you need to do something where the recommender is aware of that. You could do active learning. You can do transfer learning between them. So, transfer learning in this context, we call it, enables you to add data from similar but not identical campaigns. It's actually, we believe it's a game changer because you do not have to start from scratch like in classical DOE. We also have something for people who are not so experimentally minded. So, for instance, Bandits can be used in websites for A-B testing, for instance. There's multi-target parameter optimization. You can check your parameter importance with model insights, and with a few lines, you perform this backtest that we also had to do a lot actually to convince people again in the company. So all of these features are things that come on top of this basic Bayesian optimization core and I think for most use cases it's actually mix and match. So you need to think about your problem then you can mix and match these features. We have many tutorials online and examples and user guides with also an educational character. I will not go into any of those except for the central one. So I picked just one to show you one of these advanced features, and I picked the chemical one because, of course, it's close to our heart at Merck, yeah, as a chemical company, but also it's a super simple and very powerful thing. This is why I also like to talk about that. So let's talk about chemical encoding. You are probably aware that you need to encode things that are labeled for certain machine learning models. So everyone knows integer or ordinal encoding, and you know one-hot encoding. So now imagine your categorical parameter corresponds to the choice of substance. So the question is, which solvent do I have to pour into my chemical reaction? That could be the question. Then you have certain choices. And here I brought three simple molecules. And even if you have no chemistry education at all, I think visually you can see that the first and the third molecule are very similar to each other. And the second and the other molecules are not so similar to each other. You know it's a ring, it has no OH group, actually the first and second molecule only differ where the position of the OH group is. So I hope just visually you can appreciate that molecule one and three are similar and molecule one and two are not so similar. So what's the problem with these encodings? So in integer encoding we impose this kind of order, which is bad. And why is that bad? I brought here just a very simple way of understanding that. So imagine you would then train a random forest on this. A random forest needs to make splits along the ordered histogram. And now I want to probably split the second molecule off from the other two because they are less similar, right? So I want to separate molecule two from the other two. And you can try that out. So it's actually not possible to do that in one split if the molecules are ordered according to the numbers 1, 2, 3. So this split mixes all three together. This mixes molecule two and three. This mixes molecule 1 and 2 And this mixes all three together again So you cannot separate them In one split Of course you can do subsequent splits But that's already I say you put stones into the way of the algorithm You should make life easy for the algorithm So we need actually encodings that The better encoding Is one that enables the algorithm To find one simple split to separate These dissimilar molecules A quick word about one-hot encoding too In one-hot encoding you do not have the issue of order, but the distances between these labels in this vector space that this imposes is uniform, so each label has equal similarity to the other one. So that's also not optimal, and you would also need multiple splits here. The solution is so simple, we call it chemical encoding, so instead of just one hot or ordinal, you actually come up with numbers to describe the chemistry of these labels, not the chemistry of the entire process that's a big important thing just the chemistry of your labels and for instance here we have the molecular weight we have the density of the material and the boiling temperature sometimes you can there's actually 40 years of literature about what descriptors can i find for small molecules and that's just one thing we've implemented in baby so you can tell you can say i have labels here that correspond to substances i want to encode them smartly with chemical descriptors. Then you get something like that on the right. And I just want to show you one example here, one result, how impactful this can be. So this is, once again, a chemical reaction. What you have to imagine, what we want to do is we want to merge the first two molecules on the left. We want to combine it to the molecule on the right. It's actually not trivial. So even though this type of reaction has been discovered 40 years ago or so, people never know what other stuff to pour in to make this work. So this reaction will not work if you just pour in the two molecules. You need to pour also ligand solvent base in the right concentrations and these kind of things. And even though it's 40 years invented, people start almost all the time from scratch. So they do a screening. They have no idea what they should pour. They optimize it as a black box. So it's an excellent use case for us and for demonstrating the outcome. So you can actually enumerate, in this case, all the possibilities. And there was a lab, I believe, at Harvard or Princeton, but I have to check it. But they actually measured all the combinations, which enables us to do this back test. And on the right, you actually just see the outcome of all of these combinations in a histogram. So what you want to maximize is the yield. That is just how much did I actually get of the molecule on the right. And if it's 100, congratulations, that's the best outcome. That's what we want to have. But you see, most of these combinations don't give you much. The majority gives you actually zero yield, which is calamity, because then you have a lot of waste that you have to pour in somewhere. So we want to find the best yield here in as few as possible iterations, and here, just an outcome. Actually, this is then using our chemical encodings, and it's also compared to other algorithms. So you see vendor one and vendor two, so actually those are startup kind of companies. They approach us also with similar methods, and then baby and say, do you want to buy this? Do you want to test this? And yes, we did want to test this, and this was the result. So you can imagine we were quite happy about the result, and I think one of the deciding factors here, the deciding differences is, for instance, is chemical encoding. What you will also see is Optuna. So again, quite well known in this crowd probably. It's not suited for experimental optimization. So Optuna, for instance, does not have the chemical encoding. I'm not even sure how you would implement it there, so it's not possible in the tree method at least, and it does not have batch recommendations. So this can have a tremendous impact, you know, if you look at how many experiments we need, it's again roughly one-third to reach the same outcome as the other methods. And you cannot even compare it with Optuna really, so it never reaches the optimum. So just an example of how powerful this really simple idea that I just explained can be. So here are just some examples where it's no use. So we have at Merck actually fully self-driving labs. So the labs in chemistry, there are certain labs that you just tell them what you want to do, and they optimize themselves in six hours or so. They give you an optimized process. And the brain behind deciding which experiments to perform is baby. The same you can do for digital products, like we have Bioreactor Twin, or on the bottom you see, again, three sort of products that we sell where baby is kind of in the back end. A quick word about features before we get into now a coding part. You know, baby's in development, and we are looking also for ideas and requests for features, but here are some of them which are already in the work. Again, to add to this big, big list of different features that are often needed in experimental context. For instance, one big thing is multi-fidelity, you know. There is in the semiconductor industry, for instance, measurements that you where you have to send your material to Taiwan. So it's super expensive, takes a long time. We need a simple surrogate measurement that measures something that's correlated with the target, but it's simpler. It will not give you the exact same answer, but it's sort of on a lower fidelity and it's cheaper. And that can be leveraged, for instance, by doing some measurements on the low fidelity and only then when you have the requirement to perform one on the high fidelity measurement. That is just one example. Let me see any others I want to mention. I guess the transfer learning, that's really, as I explained, a real game changer. So we are planning many more features. And you can see this actually from a design choice or a code design choice, it's getting very heterogeneous. So we have to put in many things into this code base that do different things and are robust and also extendable modular you know all the kinds of requirements and i will hand over now to adrian who will explain to you how we did that and what other open source package packages we use for instance for this purpose
Speaker 2 [28:12]
Thank you Martin. Maybe first, are there any questions? Should we have a quick question session here in between? Thank you.
Speaker 3 [28:48]
Yeah, we've had two questions from the audience. So one was, how do you know with baby if you found a global optimum or stuck in a local minimum?
Speaker 1 [29:00]
So essentially, you do not know. So if you have a process with many combinations, how would you know that you found a global optimizer? I think Bayesian optimization has less danger to run into local minima than, for instance, gradient approaches because there will always be an uncertainty about unseen spaces, and that eventually drives you then to explore that again. I think just for some rare computational settings, we've observed that there was you was stuck around a minimum um there was another aspect i had but i have to think about it if anything to add essentially you do not really know in practical settings
Speaker 3 [29:41]
And have you noticed that you integrated active learning policies in the new release? What policies did you integrate?
Speaker 1 [29:49]
Yeah, so active learning, basically just then we want to go where the model is uncertain. And I think there's two policies we have. So the first policy is sort of the local uncertainty. So you calculate the uncertainty of the model about each single point, and you measure the point that's most uncertain. But this is probably not the smartest. It's cheap, but it's sort of this would be active learning in the simple version. And we have the second variant. It's called negative integrated posterior variance. So what you do there is you fantasize the outcome of all the points, and then you condition the model on that, and you integrate the uncertainty of all the other candidates. So you choose the point that leads to the largest reduction of all the other candidates. And that's a global uncertainty measure. Again, it's called NIPV, negative integrated posterior variance. And it's much more expensive because you have to do this integral, but it's probably the best for active learning purpose.
Speaker 3 [30:50]
Okay, thanks. That was all the questions for now, then I will let you continue the tutorial and if you have any further questions to be answered next Just ask them on Slido
Speaker 2 [30:59]
All right, then I'll take over from here. So very warm welcome also from my side. I'm Adrian, and I'm really happy to have the opportunity today to talk about code. I mean, we are at the PyCon, and admittedly, I've created most of these slides already for another talk and didn't even get the chance to talk about this because usually it's the type of more business-oriented or motivation talk that Martin gives. But today, I really have the opportunity to show you some code and show some of the design choices and patterns that got into this framework. So to start, or to motivate my part, let's try to recapitulate why this is really a hard problem. And I don't mean hard problem statistically speaking, I mean it has its gist and it's complicated from a mathematical point of view, but here I'm really talking about why is this a hard problem from a code design perspective. Martin has already mentioned this, you have these different components, how do you put this into like a common code base so that things stay and remain maintainable and extendable. So there's this phenomenon which I usually call the feature creep, and this is what typically happens or might happen when you talk to people facing these kind of experiments problems. So let's start simple and assume there's like a person who wants to, as Martin gave several examples, optimize a number of continuous parameters. So you could easily solve this with classical DOE methods, and that's where they actually shine, and there's a lot of commercial software out there. But then, very quickly, things start to get out of hands. So, for example, just say that there's the requirement that you want to iterate this, and Martin is perfectly motivated why people usually want to do this. You want to continue, or you want to first measure your results and then do your next round of experiments and this is already the part where there's many different pitfalls that can start when you switch to base and optimization and i guess this is also why there's such a huge adoption barrier in industry even though you have a lot of commercial software for classical due base optimization is really at the start still i would say in industry so you can for example look at the boat torch landing page like Botorch is one of the most famous packages for DOE and even in the landing page if you read the minimum code that you have to write to get something started you suddenly have to think about stuff like what is the margin likelihood you have to think about normalization about optimization um and i guess like the the average person who does the experiments has no clue about all of this i'm not saying that this is like super complicated but suddenly you need to think about completely different aspects of coding which the average person simply has no capacity to deal with and yeah it just goes on yeah so suddenly for example say you want to run things in an iterative way but still in each iteration you want to run experiments in parallel always in batches of 10. suddenly you need to think about batch optimization martin mentioned it sometimes you have discrete parameters and suddenly your optimization problem becomes a mixed integer problem you have to operate in hybrid spaces some of your parameters might be chemicals and you need to think about these parameter codings that martin showed you might put them into a mixture suddenly you have mixture constraints or things like permutation invariance because the different ingredients of your mixture basically are permutation invariant you can exchange one with the other you have might have cardinality constraints because experimentally if you optimize such a mixture, you might have the requirement that you cannot put more than, say, L different components into the same mixture, because otherwise the experiment would just be too complicated. And the list goes on and on. You know, you can have certain restrictions on the parameter ranges, you have to optimize different targets, and so on. So I don't want to go into the details, but you get the idea. And each of these layers is basically a different complexity, and you can mix and match them. Depending on the problem, many Many of them might be active, maybe sometimes only a few. But how do you put this into a common piece of code? And there might be a seemingly simple answer. Well, there is Botorch, as I mentioned, is one of the most developed base optimization packages out there. And probably all of that could be implemented in Botorch. But there's a little gist. To do that, you need to have some extra stuff. I mean, this is basically meant as a joke, but there's a truth to it. If you really want to get this running, you need to think about each of these individual pieces in isolation, really drill down into the code, understand what it's doing, and this creates this huge adoption barrier. And then, say, you have your realistic optimization problem, you write your bolt-torch code, and sometime you get it running after a few weeks or maybe even months, you're done, and then comes the next new use case, and you start programming the same thing more or less from scratch because no different of these complexity layers have been added to the problem and this is very ineffective. That's how we actually did it also at Merck in the early days before we decided to actually join forces and come up with something better. And what is that better? Well it's our solution baby here. The core idea is that we completely modularized these different complexity layers and abstract away all the necessary low-level details. So what you get is a completely composable interface that is completely user-friendly. So you don't need to bother about the optimization routines that happen in the back, because depending on what pieces you put together, the right routines will be called without having to worry about it, but it's still completely configurable if you want to. You can still go down to the very low level and configure things. So basically you can think of Baby as being a sort of gluing language that allows you to describe optimization problems and then solve it in the back. And this entire Bowtorch thing is just one little tiny piece that sits in Baby's recommendation engine and there's many other components around that take care of these constraints and all of that. So what you get is sort of this result where you can configure experimental campaigns. You have these different building blocks, like you define your search spaces, your objective, what is it that you want to optimize. And each of them, again, has like lower level definitions. You define your parameters, your targets, and so on, and you can completely mix and match them. So you can swap out different pieces, add constraints, all of that, add data. Descriptive way of describing your problem and then you just Interact with the campaign by running it against your actual Reworld process. And this is basically what Baby offers to the user. And you might now ask, so where is Now that bayesian part actually? well, it turns out if you look At the structure down there, the bayesian part is when you Specifically use a bayesian recommender. So it's really just a minor part of this entire hierarchy. And if you zoom in a little bit, you will see that's where then all this Bowtorch thing happens, or the probabilistic modeling where the surrogate model is attached and the acquisition function, which itself comes with different components like different optimizers, kernels, and so on. So it's really like a small piece of the entire puzzle. And how can we put this together now into one coherent code base? And that's where I will now take some time to really dive into some of our design decisions. I have several chapters here, probably will not cover all of them. I just want to give you a little flavor of things that we, after a while, concluded is a good design for our purpose. So let's first talk about class design, because this entire hierarchy is basically object-oriented. And for our class design, this is not really going into the code. we built our design on Atters and Katters. So I don't know who of you has interacted with these packages. Probably many of you know Pydantic. It's a bit more popular. But Atters is a similar thing with a slightly different underlying philosophy. So this is just a random snippet of code from our main user interfacing campaign class. And what you see here is how this Atters framework works in general. So it's a declarative way to describe classes. You basically describe them in form of a blueprint You might know data classes from the standard Python library Address is basically data classes on steroids. Yeah, so it's a generalization of data classes It's actually from the same guy who invented both And the way how it works is you declare Inside your class simply the attributes that you want to have together with how you want them to interact with each other and like different converters all of them so you see for example you have your your define the decorator that activates the entire process here and then you have your attribute definitions together with fine-tuning like defaults and converters and all of that and one thing that we really have learned in this entire process where we added and added more classes and made more design choices is this here I cannot actually highlight this enough this is actually a snippet taken from the address web page it's like just sits there as a sentence but I think this is one of the most important things that helped us resolve our class design so just gonna read it out in Python instance initialization happens in the init method, generally speaking, you should keep as little logic as possible in it, and you should think about what the class needs and not how it's going to be instantiated. So it's a separation of concerns, basically. And I have to say, whenever we had trouble designing code and then reviewed, like, what went wrong, it was probably because we didn't adhere to this mantra here. And I can show you a concrete example of what this, I hope this is big enough, could look like. So here is one of our latest additions. Like it was a feature for SHAP value computation. And this was an early draft of the class during the pull request. So if you look at that, what you see is basically a lot of different attributes. It is complicated how they interact within the class. It's very hard to test because whenever you need to instantiate the class, you basically have to specify all of them. That was rather difficult. But if you then looked at this code with the address mantra in mind, you would quickly realize that, for example, the component here is not needed at all for the class object to work. It's only needed for initialization. So if you took the address mantra seriously and tear things apart, this is what you end up with, where the actual remaining class only has two attributes that it really needs to operate, and all the other attributes are only needed during initialization. So you completely decouple the class definition itself from the way how you instantiate it. And for the instantiation, you now have these different factory methods, which first of all makes the class much leaner, but it comes with the additional bonus that you can now instantiate the class in very different ways, depending on what you actually have at hand. If it's a surrogate model, you instantiate it from surrogate method. If you have a campaign at the end, you use this other entry point, you know? And this is basically the address mantra in life, and you can see that most of the time once it's instantiated, you only care about these two attributes here. And with this in mind, I would now like to shed a bit more light on this entire address execution chain that is happening when you define classes in address. So what you typically have is that when the class is built, you go through these different stages where you have your defaults for the different attributes, you convert them, you have validators. And I want to show you some examples of all of them, giving you an idea how you can flexibly keep your code simple and maintainable. So let's have a look at the default factories first. So what are the factories? Factories are basically the interface between what is coming in from the outside world and what you want to have on your class So the attributes you have defined, they need to be somehow populated, and you don't want the user to necessarily provide all of them. So default factories are basically to fill in the gaps. How does that look like in terms of code? So, for example, we have here a little class example where it has a single attribute that is a list. If you would write that this way, it would work, but it basically means that whenever the user wants to instantiate an object of that type, they would need to provide this list. Well, you could set a default, but this is really not the right way because we know the hassle with Python mutable arguments. It's not a good idea to put a mutable argument in the class definition because this object would be created at initialization time and would then be shared across instances. So the right way here is then to use one of these factories, which says whenever you instantiate a new instance, give it a fresh list object, and this is how you would do it. So factories basically fill in the gaps for what the user does not provide when instantiating an object. In terms of baby, here's a little example of how that's used. So say you don't have to bother about the details here, by the way. So it's just to give you the necessary context. We have a particular objective type. It comes with different weights and targets. And the weights tell you, well, how to weigh the different targets for your optimization. What is a good default for the weights? Well, you want that if the user doesn't provide any weights, it should use uniform weights. This is how you can do it, basically by saying, well, the weights have a default setting, and that depends, of course, on the number of targets that you have. Because if you have five targets, well, each target would get a weight of one-fifth. So you can see these defaults basically per attribute can also access attributes, other attributes of the same class. So next come the converters. So the converter, again, forms a sort of interface between the messy real world, what the user inputs, but that can basically be anything, and what you want to have on the cleaned-up side on your class. So the idea here is that converters help you to make sure that after initialization the attribute really is what you think it is, because you cannot control what the user puts as an input. Here is, again, an example. Again, we have this little class here which has a single attribute, a Boolean field. So what would now happen if the user enters an integer? Well, Python wouldn't complain, right, because Python is dynamically typed. Types are not checked at all at runtime. But at least the static type checkers, they would complain and tell you, well, you're attempting to populate the Boolean field with an int. Something is fishy, right? The converter is the solution to that because you can simply say whatever comes in first goes to the converter and it gets converted to what you want it to be, in this case a bool. And since the boolean representation of one is true, everything works. Again, a real world baby example and what can be the nice consequences of this. So again, here say we have an attribute like an acquisition function that can be of type acquisition function or it can be none. And you can see there is a converter actually in place. That converter has two steps. I don't want to go into details. It basically checks if it's like an actual object of that type or Is it a string. But if you put that into place Correctly, what it allows you to do is you get a lot of Flexibility how you can initialize and instantiate your Class. So because it's an optional an optional type, you can either say, well, I leave it away, the argument, or I can populate it with an actual object of type acquisition function, but now since I have the converter in place, I can also do things like string-based instantiation. And this is really nice because if you think about defining things in terms of JSON, so when you work with APIs, that means you can write the string literally right away in your JSON config. Yeah, it just gives a lot of convenience and flexibility. And finally we have the validators. Again they act as a sort of interface between the messy real world where the validators now again try to assert this thing here shown on the right but now they care about content not about type. So here again the same example with the boolean converter which is completely valid but also you might say well if the user provides something that is not a bool that's probably a mistake and I should not allow this in the first place. So instead of putting a converter, you could actually also put a validator, and that would then give an actual runtime error, because it checks, is this of type bool? And if not, throw an error. And here is, again, a real-world baby example. It's a lot of code. Don't worry about the details. Just showing you there's a lot of things that you actually need to validate here to make sure this is a valid user input. So first you have this converter that actually converts it to the expected format, like a a tuple of targets, but then you need to, for desirability of the objective to make even sense, you have to ensure that there are at least two targets, that each of the entries in that tuple is actually a target, that there are no two targets with the same name and so on. And you can see, again, the validators, they can also perform cross-attributes checks. So it's a very flexible framework that you can compose to make sure your objects are intact. well with this address and instantiation chain we now have the point where we can fully instantiate this object hierarchy and make sure that it's intact well but how do we get from that python world now to the right side where we can convert this to a json config back and forth which is needed if you want to run this behind an api which in fact we do at merck of course we have not just built the baby tool itself but we have an api that also handles all these requests coming from the users. Of course, you could now start writing your own serialization engine. Probably not the best idea. For address, there's a simple solution. There's this companion package called Catress, which I can highly recommend, which you could call like the onion peeler. It goes from this top-down level and peels the different layers, deserializes them, and this is what you get. And for address classes, this serialization comes out of the box. But you can completely customize it yeah so here's a simple example where you simply say catalyst please structure me this input which is a collection of different things like a string rep number represent as a string an integer float a boolean please structure me that as a list of floats and this is what you get and you can completely customize this define your own um deserialization and civilization hooks That hook into this process and you can completely fine tune it. And what you get if you do this rigorously for your entire class Hierarchy, and this doesn't apply just for baby, you can do This for your own hierarchies, is something that you can now Very easily on any object simply call to json and it goes down Through the hierarchy, deserializes it, and you can Easily deserialize it back into a python object. And as i said, we have much, many add-ons here that allow us That allow to fine-tune this where you can say you want to fine-tune how data frames are serialized You can invoke alternative constructors all of that We have a user guide if you're interested check out the serialization user guide where you can see what what all of different Options you have to to fine-tune this this process So one Important part is now you have this method now and you may have to make sure that this actually must work for all different contents and types so how do you do that I mean that's a very simple example still say you want to serialize a search space which is a complicated object of its own right it has a potentially a discrete subspace a continuous one each of them comes with their own parameters with different types constraints which are made up of these lower level of objects and so on so how do you make sure that for every possible input the user could give the this serialization still works. And how do you test this? And even if you think one step further in this hierarchy, if you now run this process, this iterative Bayesian optimization loop, how can you make sure that all these interactions, they work, irregardless of what the user sets as their specific object hierarchy? And there is a very good answer to this. So in case you haven't heard of it, I can highly recommend this hypothesis testing framework. is perfect for these kinds of situations. So the way it works, it switches from the classical PyTest idea, where you give specific inputs and expected outputs, it switches to a property-based testing approach. So what does that mean? Let's take the example of serialization. For serialization, we don't care about what specific input we have or what is the expected target for that input. What we care about is if you give any input, You serialize it and deserialize it back, you get the equivalent object. It's a property that you want to test, regardless of the specific input. And how hypothesis does it, you can specify a way how to generate different inputs. This is the corresponding hypothesis code that would now basically generate random objects of type objective that we need to test. So it's like you can basically define this sampling process, but it's much more than just defining the sampling process. What it also does, it has a clever mechanism attached that actually tries to cleverly sample such that it tries to find examples that actually break your code. And if it has found such an example, it will try to shrink this example down to a minimum breaking example, showing you a simple statement that breaks your code. So it's really a genius idea, and I can trust you when you throw this against, I promise you when you throw this against your code, it will find a weak point that you haven't thought about. It happened all the time to us. That's what they promise in their manuals, and I can completely confirm this. Okay, so maybe because of the matter of time, maybe last point here that I find really interesting for this baby context because it's all about composition and customization in the end. We want to provide a framework that the users can use to optimize their own processes. So say we have now this particular recommender class that does the Bayesian magic, and internally it uses a surrogate model. We offer, of course, generic surrogate models like the Gaussian process or the random forest or whatever that people can use. But now, say, you have an application where you have very specific ideas what your process should look like. So you might have a physics-inspired circuit model that you want to use for your particular project. How can you ingest that into Baby or into any other code? Certainly, what you could do is you could use our base class, derive from it, and then implement your custom logic. But certainly you don't want that the user needs to bother about all this, what we do internally for our surrogates. What they want is a simple entry point where they can just ingest or inject their own mechanism. And same problem from our perspective, even internally. Say we have this campaign and we have, for example, this recommender object that takes care of generating the recommendations. We have different types of recommenders of our own. So we have pure recommenders and some more of these meta recommenders that wrap other recommenders. How can we make sure that regardless of what recommender type we use, our code still works? And again, here, of course, the user could also want to, like, plug in their own recommendation engine. And the answer to both of these problems is the same. It's abstraction. And in this case, we do it by using abstraction via protocols. So it's basically the duck typing thing. You might know that quote, like, if it walks like a duck, it quacks like a duck. This is the idea now in code. So what it means is we think about what does our code really need from those objects. We specify that in terms of protocols. These protocols, they act like a contract. They tell you whenever you enter something that fulfills this contract, our code will work with it. So you really have to think about what are the essential components that the code really needs. And in case you want to ingest it or take this approach and integrate it into your own code base, I can highly recommend you use a static type checker like, for example, MyPy, which is the ideal tool to identify what are the requirements for those protocols. Because the way how you would simply do it, in practice, define this protocol empty, just plug it into your code, and then MyPy will complain what is missing from the protocol. And that's like an easy way how you can incrementally build up the definitions. And then once you have defined those, bringing them into action is really simple. So this is the original code for the two examples that I showed, where you have these specific classes here. The only thing you need to do is just swap them out against the protocols. And now, basically, your code is guaranteed to work with user-defined objects. as long as they basically satisfy these protocols. Okay, so I think I have one minute left for way too much content. So just going to briefly mention, I'm not going into details, one thing that took quite some effort, but I'm happy that we found a good solution for that. So one thing here is that some of our dependencies that we use like PyTorch and GPyTorch and Botorch that basically form this kind of hierarchy, they are super heavy. So there you might note, especially Torch, it's a huge library, it comes with a lot of dependencies, it has a large footprint, and you don't want that at runtime you always have to rely on these libraries. You don't want to even import them if not necessary, because imagine you run this behind an API, you want your API, for example, to only check if your baby configuration is valid. no need to import that costly dependency. And the problem is now if you base your code, like baby code, on these dependencies, baby itself would be put on top of this pyramid. So baby would also have this heavy footprint. So one way to get around this is to basically use lazy imports. I'm just going to show you in one example how you can solve this problem and then I'll hand over to Alex so here on the left hand side you have an example of a situation where your code relies on such heavy dependencies like Torch where on the top level you would have this Torch import that is then internally used by these methods you can get around this very easily by turning this into lazy imports where instead of putting it as a top level import you import it only where you need it inside the class inside the methods. And then for the type checking purposes here, because you still have defined these as types, you can do these type checking conditions, basically meaning that this import only happens at static type checking time. And another solution that complements this idea is when you have, like, say, modules that heavily depend on these dependencies and you don't want to use these lazy imports all over the place because it would require you to rewrite all your imports. Another solution that you can do is you will just move them all into one compute-heavy module where the top-level import is then allowed, and you only import those in your consuming modules in a lazy fashion. So that way you can basically condense all the compute-heavy stuff into one module that is allowed to load it, and then in practice you import that lazily. And I think here I'll stop so that Alex has enough time to guide you through some real-world examples. Yeah, maybe some questions here.
Speaker 3 [61:10]
So then I will ask let's say three questions, and then if there's the questions remaining or more that come up, we can ask them in the end. So one question is, is the atters an alternative to bydantic or can they be used complementary?
Speaker 2 [61:25]
I think, so I've also asked myself that question a lot. They serve different purposes slightly. So, Pydantic is foremost a validation library, and I think it really shines at validating user input, whereas the philosophy of Adders is really to offer a class-building framework. So, the difference is that when you define your classes with Adders, what you get is a pure Python class, like the way if you had written it on your own, Whereas if you use pydantic, it does all this inheritance and all of that. You get a very complicated object that is then a pydantic object, which has a lot of consequences. Good consequences if your primary focus is validation. But if you don't care about validation solely, then I think address is really the better choice.
Speaker 3 [62:12]
Did you run into Excel problems with the converters? For example, inputs getting misinterpreted or faulty results? Shouldn't the class complain about phishy arguments?
Speaker 2 [62:22]
Yeah, that's exactly the difference between just using it as a converter and or validator. So I think it's a matter of taste at some points. Sometimes it depends on the context. Of course, if the converter crashes, it crashes, right? So the converter could also just throw an exception. It's a fluid boundary, whether it's conversion or validation. There is also no clear answer sometimes what is the better approach. It really depends on the use case, I would say.
Speaker 3 [62:50]
And then could you repeat which is the package that you use for the hypothesis testing framework or is it part of baby?
Speaker 2 [62:56]
No, it's not our package. This is like
Speaker 3 [62:56]
No.
Speaker 2 [62:59]
Hypothesis that's the name of the package Google for pi test hypothesis. You will find it. I can also Yeah, it's this Where is it? You will find this this logo here, then you're on the right page
Speaker 3 [63:13]
And then maybe last quick one. What is the advantage of using protocols over abstract base classes?
Speaker 2 [63:19]
With abstract base classes, you have strict inheritance, right? So I can recommend go to the MyPy page. There's a good explanation on the difference between structural subtyping and nominal subtyping. That's basically the difference between using abstraction via base classes or abstraction via protocols. The abstraction via protocols is way less invasive because you don't need to inherit from anything. That means that your users can be conformed with the protocol without putting the requirement on them to actually inherit from anything. And I think that's the right thing for customization. But really, again, it depends on the context. For this customization thing, I think protocols are the way better option.
Speaker 3 [64:03]
Thank you. So then let's go to the last part of the tutorial.
Speaker 4 [64:08]
So, thanks, Martin. Thanks, Adrian. So, also have from me. So, I'm Alex. I'm the third core developer of Baby. And I want to give a short hands-on demonstration or basically a how to get started with Baby session now. So for that, first let me get out of this and just let me put up the baby GitHub page. So the goal of this part of the session is now that hopefully Martin and Adrian convinced you that Bayesian optimization is super cool and super useful and that baby is a package that you want to check out. So ideally, you now want to work with baby, you want to do something with baby, and I want to help you to answer the question, okay, and how do I do that? Now, where do I go to? Where are some resources? How is stuff organized? So, first of all, here we have the general GitHub page of Baby. So, just on emdgroups.com slash baby on GitHub, you can find Baby. There's a very recent release having the newest and coolest features that we made ready for you. And I want to share a little bit our documentation. because one thing that's really important for us and that comes also from our industrial background is, as we mentioned earlier, it's really a struggle for us to get people to actually use baby and to actually adopt baby. And also we have seen in a lot of our work and research that if a package is really, really good but you don't understand how to use it, the documentation is crap, it's outdated, there are no examples, there's no explanation, basically the package is unusable. And that is why we spend a lot of time and effort for our documentation, and this is also why I want to guide you a little bit through the documentation, how it's organized, because if you really want to get started, I want you to look at the right places and not at the wrong places so that you're not frustrated. So the general documentation is organized as a usual documentation. There is a readme with quick start and installation, batteries included, blah, blah, blah, blah, blah, not important. important stuff here if you really want to get started are the examples and the user guides and there is a slight difference between what we mean by these so first of all we try to have examples for basically all of our features so you see there are a lot of examples and we are aware that this is not the most visually pleasing so we still think about reorganizing stuff a little bit but examples are basically just plain Python files that you can also find in the package and that you can just run and execute so if you say I just want to have a dot Python file that I can run a very basic example just go to the package go to the examples folder and just look for the example that you want to have a look at and you will get what is here just rendered a bit differently you will get this example and due to our CI CD pipeline and it's also clear and it's tested that you can actually always run this. So if you say for example, oh I'm really interested in the basics or for me multi-target optimization is something super super important that I just want to have a script that runs and that I can use as a base class, you go to the examples part. The other part are the user guides. User guides are more like something that Martin and Adrian have done in the first two parts of the talk which is explaining stuff not just showing plain code that you can execute but actually explaining the logic behind stuff so for example hey active learning what even is that and how is it included into baby and how can I use that or campaigns which are like the central object to baby what are those how are they how do we use them how do I do stuff like getting basic recommendations or for example let me just quickly go So here, as we mentioned earlier, there is an Insights package or an Insights sub-package. So what is this? How do I use this? What are the explanations here? So if you want to have these more like kind of explanations of how stuff works and why we decided for the designs and similar things, you go here. or also it was mentioned earlier by Adrian, there is a whole user guide on serialization because an example for serialization is not that exciting because it's to JSON, from JSON, and in the search. But the explanation how it works is something that we think might be really valuable. So if you want to check out Baby, if you just want to play around with it, the user guides and the examples are where I would lead you to in the very first place. So, as a second part that we also have, and that is something very, very new, we started to also develop a dedicated repository for so-called baby resources, which as of now contains three Marimo notebooks, and we will go through these Marimo notebooks during the next 15 or so minutes, such that there is a little bit of time for questions. So the goal of this repository here, and you should be able to access that, is to just put stuff there that somehow belongs into the baby ecosystem and the baby context, like, for example, more examples that we do not want to have in the main repository, because the main repository is still about baby and the baby Python package. And you wouldn't want, for example, dozens of examples or ten folders of some Streamlit apps that might still be interesting for people, but that are not really part of the package themselves. And this is something that we will develop in the future, which is why there's not that much content here. But this is where you can actually find the notebooks that we will go through this through. Something else coming back to the main repository that I really want to stress and highlight that was also mentioned by martin and adrian before baby is still under development and it is still actively being developed we are very very open to all kind of contributions questions um opinions and so on on baby so if you try out baby if you have a look at it and if you either say hey there is something that i don't understand i don't get the part of your documentation or if you find a bug or something like that please just contact us so we are really depending on external feedback of course here and don't ever hesitate to just open an issue write us an email also if you struggle with baby or if you say hey for whatever i'm doing baby is super interesting but i have trouble setting things up can we maybe jump on a 30-minute call or something like that just contact us okay so with that being said let me get to actual examples of how to use baby in the style of a marimo notebook so if you don't know marimo it's basically an alternative to jupiter so just think of these as slightly different jupiter notebooks
Speaker 1 [71:26]
so
Speaker 4 [71:28]
In this example, we want to, so that's the reaction optimization example, we want to optimize that reaction that I think Martin also showed previously in his experiment. And yes, we can also make it wider, so we can also have this view. And in this example, as Martin said, I don't know, maybe I should also zoom in a little bit. Is it better? Yes. So, in this example, there are basically five different parameters that can be varied overall. As I said earlier, there is something that's called a ligand, there is a base, there is a solvent, there is the concentration of the solvent, and there are different temperatures that we can choose of. So, again, roughly 1,700 different parameter configurations that you have here, and this is not just a visualization of the data that we have. And as you can see here, the same yield distribution as earlier, And only 18 of these 1,700 possible configurations are actually in the top 10 percentile. So we want to find these using baby. So this is not just data and blah, blah, blah. So let's go over to baby. So here, these are basically the terms that we use for baby. And I won't go into too much detail here because Adrian and Martin have already covered these. But in Baby, there are things like parameters, which will respond to the things that you can control in your experiment. These are together put into a search space that defines your experimental conditions. There's a target and a recommender, and in the end, all of this is put together into a campaign object. So how does it now actually work? So here's now finally some real true Baby code. So Baby has the so-called categorical parameter, and the categorical parameter just tells you, This is some sort of a category, like, I don't know, there are different interns or there are different machines. And you can just give it a name. In this case, for example, for the ligand, it's the ligand name. And the values, which is here now just being extracted from a data frame. It's important to say here, this is now not this substance-encoded parameter that Martin just talked about in the chemical and coding example. But I will come to this later because that's a different example. But this is now just the way of defining, okay, I just have a category, it has a name, and it has values. That's all that I know about that parameter. And in this case, we have such a parameter for ligand, solvents, and the base. And similar for the numerical discrete parameter, because ligand, base, and solvent, these are like strings, these are names, this is nothing numerical. But if you say, okay, my categories actually have a numerical value and have a meaning, there is a numerical discrete parameter. and here again we try to make it as easy as possible you just give it a name for example the concentration is just the concentration and you just hand the values over in some meaningful way for example by extracting them from a pandas data frame okay and now that we have collected basically our five different parameters let's go into defining the search space and defining the search space as easy is as easy as just collecting all of your parameters in a list and then just calling the from product constructor handing it over the parameters and this will now create the full search space as a not categorical product this possible all combinations together I'm missing the term here but you hmm cat's easy and product right that one and so this is what this from product constructor does. So in the end we now have the baby search space which corresponds to all of the experiments that we could do. But of course we don't only need a search space but we also need to optimize something and what we want to optimize is the yield. So what we do is we first define a numerical target because what we want to maximize is something numerical so it's a numerical target it gets a name which is the yield in this case and also we want to tell the algorithm to maximize it so we set the mode to max and this target is then put into an objective and this might be a bit counterintuitive why there is a target and an objective but the reason is that if you have multiple target optimization you could have several of these like I want to maximize the yield and I want to minimize the cost these would be different targets but these would still be wrapped together into a single objective. So this is why there is this distinction here between a target and objective, because the objective is like the object that you actually want to optimize in the end. Okay, then the next step is now the definition of the actual recommender and although baby also offers a lot of good default choices here for the sake of of this example I wanted to make it explicit but I think if I'm remembering correctly at least the Botorch recommender is always used as the default recommender and baby uses something that's called a two-phase meta recommender or two-phase recommender. Why is that the case? Well if you have absolutely no data in the beginning you might want to do something different or you might not be even be able to do the same thing that you would do with the recommender that you want to use when you have data. So for example if your recommender relies on training a model based on your data, have fun training a model based on data without data. So for the initial recommender you might need to choose something differently here. So this is why here we choose the farthest point sampling recommender and just to make it explicit after our initial recommendation we want to switch over to the Botorch Recommender. Okay, this is now the collection of all of the ingredients that we have. So we now collect all of these together. We put them together in a campaign object. The campaign object is what orchestrates now the whole process, so where we get the recommendations from. So what do we need for it? We need the search space, because that's where our experimental data is. We need the objective, and we need the recommender. So we hand all of this over to the campaign object and have created the campaign. Okay, so now we have everything together to actually start our recommendation loop. And we can just start by saying as an initial recommendation, campaign.recommend. We want to get a recommendation with a batch size of 10 here. So we get blah, blah, blah. so we get 10 recommendations and you see them here now and these are now the experiments where baby says hey as an initial batch of experiments run these experiments see how they look this is the data that i would that i would recommend you to to test so let's say we now do this in this case again getting the actual yield is just fetching the data from the the yield column from the data so that's not that exciting but you see here in the yield column that was produced it's quite a wide range of different results that we get there are some results that were actually there's no yield and this since this is on a scale from 0 to 100 it's quite a diverse set of yield that the first few reactions and the first few examples yielded okay but what we have done now is we've just used baby to get the recommendation so baby itself the campaign object does not know about the result of the experiments yet so what we do is we also inform the campaign object about that so we tell it okay we have done the recommendation we have added the measurement so here campaign here's the data that you will use later we add the measurements to you and now the campaign is informed about the actual data that we that we created and that we got and this is basically the first step of this iterative cycle that Martin has shown earlier so we got one recommendation we made our experiments which is here looking something up in the pandas data frame and we informed our campaign about it so now we just do that iteratively so So over the course of ten iterations I've done it here, we now recommend things in a batch of five, we add the data from the year that we have, so this is where you would normally go into your lab and do your experiment, inform the campaign about the results, and we do this for a bit of time until we have gathered a nice collection of points. here we have done that until we had 60 experiments in total being recommended by baby and being performed and you see now here the collection of all of the measurements that we have done and some statistics on it and you actually see that we managed to find within 60 experiments that we did at least two experiments and two two configurations two parameter configurations that are within the top 10% of what we could have hoped for so it took us probably not even the 60 rows but it's sometimes a bit hard to get these numbers as low as possible because that's probabilistic here to actually get something that's in the very very top given that we have 1700 experiments this is how you would do this in in baby with with just 60 experiments okay looking at the time so it's officially I have seven and a half minutes left I don't know I could either go very quickly through one of the other examples I could just show you some more plots or we could have more times for questions in general I don't know how the situation is in Slido or what's more reasonable I could also just very other there is a question up there okay didn't it work if you just installed it from the requirements file that's provided yes okay there should be a requirements file that that you just need to install so just pip install or with whatever a package manager you use and that should install Marimo and baby
Speaker 3 [82:47]
So we have a few questions on slido and probably if you go through the next example We won't have time to answer them. So okay
Speaker 4 [82:52]
So, okay.
Speaker 3 [82:53]
If you want, we can go ahead and answer the questions. Yeah, sure. And the time is left. One question was if you plan to making a first official release, because currently there are only development releases.
Speaker 4 [83:05]
That's a good question, Martin, Adrian, there are no current official plans to having a baby 1.0, right? I would say. I mean, for the final round of questions, maybe you can also come to stage so we can take all of them together. But yeah, so there are currently no plans to have an official baby 1.0 version within the next time is the short answer.
Speaker 2 [83:37]
It's my mic? Yeah, can you hear me? Yeah, I think it's a bit further down the road. The reason is that currently there are still upcoming breaking changes because we are really constantly developing the framework further. Just recently with the recent release, we have added this support for multi-target optimization and it's already foreseeable that in the next two months there will be a few breaking changes regarding the target construction. And this will continue I guess for at least maybe a year. Maybe in the time span of a year we might consider doing it.
Speaker 4 [84:09]
But we try to add deprecations as good as possible. So normally, even when we introduce a breaking change, your code should not break, but just start to spit out warnings to please use the new syntax or something like that.
Speaker 1 [84:28]
Yeah, I was also going to add that almost all of the things are deprecated, so it will not break. There are some things upcoming where it's not possible, but in general, even though it's released, the versions, it will not necessarily break. It's a use in our production, right? Yes.
Speaker 2 [84:46]
We actually take invest quite some time into that. Yes
Speaker 4 [84:49]
And as I said, if you encounter any kind of problems, just contact us. We are always happy to help.
Speaker 3 [84:56]
So another question is, how does Baybee compare to other Bayesian optimization frameworks like Bowfire?
Speaker 4 [85:03]
We didn't do a benchmark against Bowfire. I think the main difference is that there are different features that we have that Bowfire doesn't have and the other way around because it's just two different packages. But I'm not aware of any direct comparisons that we did.
Speaker 1 [85:26]
You know, for this question, actually, you know, you're Asking the wrong person, the wrong people, so the people who Should decide that are the users, because these packages Are so much in development, any answer i give you today is Outdated in four weeks, so we cannot constantly compare and Benchmark, we're actually in contact with some of the Developers, so there's an exchange, like alex said, Certain intersections of features, some missing things, missing things. We are, for instance, very proud of our documentation, so we let the users decide which is the best documentation has the features they need.
Speaker 2 [86:04]
Also, maybe to add to that, so everything that we have done so far was always driven by our internal use cases. So, I guess the same holds for other packages like Warfire. They build what is needed to solve their problems just like we do. And that's just natural that things diverge a little bit in terms of the expected problems that you try to solve. So, I wouldn't say there's one package that's better than the other. It's just with a slightly different focus according to the use cases, I would say.
Speaker 3 [86:34]
One question is also can you use BABY for hyperparameter tuning of ML models and if yes do you recommend using it for this purpose?
Speaker 1 [86:43]
So the answer is yes, you can use it. Whether I would recommend it is not so sure. The reason is hyperparameter tuning for ML models is often happening in nested spaces. So if you think about, for instance, a neural network where you have 10 layers or 5 layers, then you have a different set of hyperparameters that you tune. We call it nested spaces. And for that, actually, different modeling approaches might be better. so i would say we have you know i showed you many many use cases earlier hyperparameter tuning is one of them but if i would pick out one out of the use cases where i think there's very good alternatives then it's probably hyperparameter tuning for that reason that the nested spaces is something that's not well um yeah represented in our current structure but
Speaker 2 [87:32]
So conceptually there's no reason not to use it, it's really just a usability perspective.
Speaker 1 [87:36]
If we have some continuous parameters, I think it's, for instance, perfectly reasonable to also use Baby for hyperparameter modeling.
Speaker 3 [87:44]
And then I think we have time for one last question. Are there any plans on building a frontend for Baby, for example using Streamlit?
Speaker 1 [87:53]
So we already have one internally. So one of the challenges here is It needs to be hosted and the computation needs to be paid There's no real incentive as a company to do that to enable everyone like to run on your computation So it's a bit difficult. We are currently though in talks and Ideation phase with our partners at the acceleration consortium. So that's a Canadian consortium for self-driving labs Maybe together with them we will figure something out where we can make a graphical interface publicly available.
Speaker 4 [88:25]
One of the ideas that we also had is in this baby resources repository that I showed to maybe have then one specified streamlet for demonstrating I don't know multi-target optimization or maybe a very quick and short demo for showing another feature or something like that but that won't be a front end containing everything and we are still not 100% sure what we want to do there and how we want to do that but there might be something like that in the future.
Speaker 2 [88:55]
The struggle is always maintainability, you know Marty mentioned we have this internal front-end and that is always already lagging behind in terms of features Compared to what we do in baby. So with baby you always get the latest features and you Once you want to bring that to the front end. It's always this additional step. So
Speaker 3 [89:13]
So thank you. There's still a few questions left, but maybe you can come to the front later or even to the booth of the speakers And they can answer them later, so I want to thank you three For the talk. Thank you
Speaker 4 [89:24]
talk. Thank you. Thanks. And we also have a whole bunch of people to thank.