Deployed 37b5142 with MkDocs version: 1.3.1

This commit is contained in:
2022-08-21 18:39:00 +00:00
parent fbc107f453
commit c767efef3d
43 changed files with 178 additions and 345 deletions

View file

@ -1,12 +1,12 @@
\chapter{Designing the framework} \label{chapter:design}
Providing users with a high level of abstraction is not unheard of in the domain of practical AI/ML platforms. Many software-as-a-service products offer features for hiding the technicalities of machine learning. However --- as we have discussed in Section \ref{section:existing} --- these tend to abstract away the details of both data science and AI engineering, overall hindering the development process. The design proposed here aims to tackle and simplify only the deployment-related concepts.
Providing users with a high level of abstraction is not unheard of in the context of practical AI/ML platforms. Many software-as-a-service products offer features for hiding the technicalities of machine learning. However --- as we have discussed in Section \ref{section:existing} --- these tend to abstract away the details of both data science and AI engineering, overall hindering the development process. The design proposed here aims to tackle and simplify only the deployment-related concepts.
\section{Scope} \label{section:scope}
As highlighted by several case studies in Chapter \ref{chapter:background}, the transition from prototypes to production-ready systems is often named as the source of unexpected struggle. Maybe it is not a coincidence that a significant portion of the SE4ML best practices should be implemented in this phase. Unfortunately, it is easy to gloss over them while tackling the underestimated difficulties of this \textit{transition}. Therefore, the aim of \textit{GreatAI} is to ease this step of the lifecycle. Consequently, its scope is limited to the \textit{transition} step.
There have been attempts that at least partially address this issue; however, as we have seen in Chapter \ref{chapter:background}, these have limitations either from the perspective of best practices or stemming from their difficulty in being adopted. The scope has to be well-defined and limited to provide the best chance of providing an easy-to-adopt solution. To understand the API of a library, users first need to understand its aim and surface, and have to become familiar with the problems it solves. Thus, focusing only on the \textit{transition} step seems reasonable. This step is highlighted in Figure \ref{fig:scope}.
There have been attempts that at least partially address this issue; however, as we have seen in Chapter \ref{chapter:background}, these have limitations either from the perspective of best practices or stemming from their difficulty in being adopted. The scope has to be well-defined and limited to provide the best chance of providing an easy-to-adopt solution. To understand the API of a library, users first need to understand its aim and surface and have to become familiar with the problems it solves. Thus, focusing only on the \textit{transition} step seems reasonable. This step is highlighted in Figure \ref{fig:scope}.
\begin{figure}
\centering
@ -16,7 +16,7 @@ There have been attempts that at least partially address this issue; however, as
\label{fig:scope}
\end{figure}
It is interesting to mention that \href{https://xkcd.com/927/}{there is a proliferation} of platform/software as a service (PaaS/SaaS) products for deploying AI\footnote{Such as \href{https://mlem.ai/}{MLEM}, \href{https://streamlit.io/cloud}{Streamlit} or any AutoML SaaS platform, for example, \href{https://www.akkio.com/role/software-engineers}{Akkio} as these often have a one-click deployment feature as well.}. At first, these may look intriguing. However, they tend to only focus on getting code easily deployed in the cloud: AI best practices are not prioritised in this setup. Nevertheless, in many cases, it may be a suitable option to use such a service, and these can also complement \textit{GreatAI} as illustrated in Figure \ref{fig:scope}: first, the prototype is transformed into a \textit{GREAT} service and materialised as a common software artifact implementing the best practices. Then, it is either deployed using a deployment SaaS or by using the organisation's existing software deployment setup.
It is interesting to mention that \href{https://xkcd.com/927/}{there is a proliferation} of platform/software as a service (PaaS/SaaS) products for deploying AI\footnote{Such as \href{https://mlem.ai/}{MLEM}, \href{https://streamlit.io/cloud}{Streamlit} or any AutoML SaaS platform, for example, \href{https://www.akkio.com/role/software-engineers}{Akkio} as these often have a one-click deployment feature as well.}. At first, these may look intriguing. However, they tend to only focus on getting code easily deployed in the cloud: AI best practices are not prioritised in this setup. Nevertheless, in many cases, it may be a suitable option to use such a service, and these can also complement \textit{GreatAI} as illustrated in Figure \ref{fig:scope}: first, the prototype is transformed into a \textit{GREAT} service and materialised as a common software artifact implementing the best practices. Then, it is either deployed using a deployment SaaS or the organisation's existing software deployment setup.
\section{Requirements} \label{section:requirements}
@ -24,23 +24,23 @@ The best practices (which are referenced throughout the thesis) with which the d
\paragraph{General} Albeit not explicitly in the list of best practices, compatibility is vital in encouraging adoption. Large projects frequently end up depending on numerous packages, each of which may impose some restrictions on the code: since these all have to be satisfied simultaneously, this can result in severe constraints.
The open-source scene of data-related libraries is vibrant. To take the example of data validation, there are at least 4 popular choices which offer varying but similar features: \href{https://github.com/SeldonIO/alibi-detect}{Alibi detect}, \href{https://github.com/PAIR-code/facets}{Facets}, \href{https://github.com/great-expectations/great_expectations}{Great Expectations}, and Data Linter \cite{hynes2017data}. The responsibility of choosing the most fitting solution falls on the user. Thus, they should not be limited in this by \textit{GreatAI}.
The open-source scene of data-related libraries is vibrant. To take the example of data validation, there are at least four popular choices which offer varying but similar features: \href{https://github.com/SeldonIO/alibi-detect}{Alibi detect}, \href{https://github.com/PAIR-code/facets}{Facets}, \href{https://github.com/great-expectations/great_expectations}{Great Expectations}, and Data Linter \cite{hynes2017data}. The responsibility of choosing the most fitting solution falls on the user. Thus, they should not be limited in this by \textit{GreatAI}.
The programming language (PL) of the library should be its only non-general property. Fortunately, the de facto PL for data science is Python, so implementing the library in it should not significantly limit its applicability.
\paragraph{Robustness} in software development can be achieved by preparing the application to handle errors gracefully, even unexpected ones \cite{bishop1998robust}. Errors can and will happen in practice: storing and investigating what has led to them is required to prevent future ones. In the case of ML, errors might not be as obvious to detect as in more traditional applications (see the above-mentioned data validators). Even if a single feature's value falls outside the expected distribution, unexpected results can happen. In cases where this might lead to real-world repercussions, extra care has to be taken to construct as many safeguards as feasible. \textit{GreatAI} should support its clients in this.
\paragraph{Robustness} in software development can be achieved by preparing the application to handle errors gracefully, even unexpected ones \cite{bishop1998robust}. Errors can and will happen in practice: storing and investigating what has led to them is required to prevent future ones. In the case of ML, errors might not be as obvious to detect as in more traditional applications (see the above-mentioned data validators). Even if a single feature's value falls outside the expected distribution, unexpected results can happen. In cases where this might lead to real-world repercussions, extra care has to be taken to construct as many safeguards as practicable. \textit{GreatAI} should support its clients in this.
\paragraph{End-to-end} In this case, it refers to end-to-end feedback. That is, feedback should be gathered on the system's real-world performance, which should be taken into account when designing/training the next iteration of the model. Static datasets may fail to capture the changing nature of real life and can become outdated if they are not revised continuously. A well-packaged deployment should make it trivial to integrate new training data.
\paragraph{Automated} The available time of data scientists and software engineers is limited and expensive. For this reason, humans should only be involved when their involvement is necessary. Steps in the development process that can be automated without negative consequences must be automated in order to achieve efficient development processes and let the experts focus on the issues that require their attention the most.
\paragraph{Trustworthy} As detailed by the \textit{Ethics guidelines for trustworthy AI}\footnote{\href{https://digital-strategy.ec.europa.eu/en/library/ethics-guidelines-trustworthy-ai}{digital-strategy.ec.europa.eu/en/library/ethics-guidelines-trustworthy-ai}}, human oversight, transparency, and accountability are some of the key requirements for trustworthy AI applications. For increasing public acceptance and trust while minimising negative societal impact, trustworthiness is essential.
\paragraph{Trustworthy} As detailed by the \href{https://digital-strategy.ec.europa.eu/en/library/ethics-guidelines-trustworthy-ai}{\textit{Ethics guidelines for trustworthy AI}}, human oversight, transparency, and accountability are some of the key requirements for trustworthy AI applications. For increasing public acceptance and trust while minimising negative societal impact, trustworthiness is essential.
These requirements were chosen stemming from their general importance and potential to be mostly handled (implemented) by a software framework. That is why these provide an ideal initial direction for tackling the issue. Of course, these do not cover all best practices; for instance, the ones relating to organisational processes fall outside the realm of computer science.
The requirements were chosen stemming from their general importance and potential to be mostly handled (implemented) by a software framework. That is why these provide an ideal initial direction for tackling the issue. Of course, these do not cover all best practices; for instance, the ones relating to organisational processes fall outside the realm of computer science.
\section{Design principles}
Before diving into the concrete issues solved, let us detail the principles that should be used for implementing them in the scope of this framework. As implied in Section \ref{section:scope}, the Unix philosophy \cite{ritchie1978unix,salus1994quarter} of software design is followed. Most notably, the design goal that encourages to \textit{write programs that do one thing and do it well.}\footnote{Of course, \textit{write programs to work together} is also very much applicable since allowing interoperability is one of the core requirements for \textit{GreatAI}.}. Apart from providing a clear and simple picture of the intended use-cases for the library, this is also in line with the main notion of \textit{A Philosophy of Software Design} \cite{ousterhout2018philosophy}: APIs should be narrow and deep. A narrow width refers to having a small exposed surface area, i.e. having a small number of functions and classes in the public API. In contrast, depth implies that each accomplishes an involved, complex goal.
Before diving into the concrete issues solved, let us detail the principles that should be used for implementing them in the scope of this framework. As implied in Section \ref{section:scope}, the Unix philosophy \cite{ritchie1978unix,salus1994quarter} of software design is followed. Most notably, the design goal that encourages to \textit{write programs that do one thing and do it well.}\footnote{Of course, \textit{write programs to work together} is also very much applicable since allowing interoperability is one of the core requirements for \textit{GreatAI}.}. Apart from providing a clear and simple picture of the intended use cases for the library, this is also in line with the main notion of \textit{A Philosophy of Software Design} \cite{ousterhout2018philosophy}: APIs should be narrow and deep. A narrow width refers to having a small exposed surface area, i.e. having a small number of functions and classes in the public API. In contrast, depth implies that each accomplishes an involved, complex goal.
In a way, the width of an API is the price users have to pay (the effort required for learning it) to use it, while the depth is analogous to the return they get from it. Having to learn little and being provided with a lot of functionality maximises return on investment (ROI), hence, developer experience (DX). The theoretical frameworks presented in \textit{The Programmer's Brain} \cite{hermans2021programmer} provides us with explanations and vocabulary from psychology for arguing about the cognitive aspects of API design. In the following, two of them will be used for detailing the design principles: cognitive dimensions of code bases (CDCB) which is an extension of the cognitive dimensions of notation (CDN) framework \cite{blackwell2001cognitive}, and linguistic anti-patterns \cite{arnaoudova2016linguistic}. The former comes with a set of dimensions describing different (often competing) cognitive aspects of code that influence one's ability to perform specific tasks.
@ -50,13 +50,13 @@ Nonetheless, simple APIs come at a high technical cost. The library has to imple
\subsection{Default configuration}
\href{https://grugbrain.dev/#grug-on-apis}{Existing frameworks oftentimes suffer from the entanglement of numerous levels of abstractions.} Instead of exposing each implementation detail and encouraging users to interact with most of them, many of these could be abstracted away in a more high-level layer. Even where configuration may be helpful for advanced users, default values can still be chosen automatically while providing an override option where necessary.
\href{https://grugbrain.dev/#grug-on-apis}{Existing frameworks frequently suffer from the entanglement of numerous levels of abstractions.} Instead of exposing each implementation detail and encouraging users to interact with most of them, many of these could be abstracted away in a more high-level layer. Even where configuration may be helpful for advanced users, default values can still be chosen automatically while providing an override option where necessary.
For example, tracing the evaluations and the model versions used in a distributed fashion is very much expected of a trustworthy system. Hence, turning this feature on by default but allowing opting-out from it can result in less scaffolding required from the library's users. It also decreases their up-front cognitive load, which by definition flattens the learning-curve \cite{hermans2021programmer}. Similar features can be imagined for providing a service API for the algorithms, giving feedback, marking outliers, and more.
Being \textit{automated} is listed as a requirement, but it is imperative to only automate for simplifying and not for hiding decisions. More precisely, guessing must not be a part of automation. For instance --- an otherwise handy WebGL library --- TWGL.js, has a feature for automatically guessing the type of vectors based on their names. Suppose it matches the \texttt{/colou?r/i} pattern. In that case, it is treated as a vector with three components\footnote{\href{https://github.com/greggman/twgl.js/blob/e3a8d0ed09f7f5cd4be0e4cb5976081c2b5013aa/src/attributes.js\#L139}{\tiny github.com/greggman/twgl.js/blob/e3a8d0ed09f7f5cd4be0e4cb5976081c2b5013aa/src/attributes.js\#L139}}. It is easy to imagine that this can help in certain scenarios. Still, it does so at the cost of immense confusion when correctly renaming a variable breaks the application. In CDCB, this equates to scoring high on the dimension of \textit{Hidden dependencies} and low on \textit{Visibility}.
Learning from this, any guessing must be avoided to create a pleasant API. However, this conflicts with providing defaults for each configuration value. Even if these would be reasonable defaults derived from educated guesses, they are still merely guesses. Nevertheless, if the users were required to specify each configuration option, that would lead to considerably more boilerplate code. This verbosity is captured by the \textit{Diffuseness} dimension of CDCB and, of course, should be minimised.
Learning from this, any guessing must be avoided to create a pleasant API. However, this conflicts with providing defaults for each configuration value. Even if these would be reasonable defaults derived from educated guesses, they are still merely guesses. Nevertheless, if the users were required to specify each configuration option, that would lead to vastly more boilerplate code. This verbosity is captured by the \textit{Diffuseness} dimension of CDCB and, of course, should be minimised.
To resolve this conflict, \textit{GreatAI} should have recommended values instead of defaults. This can mean a context object (as suggested in \cite{ousterhout2018philosophy}), which contains the result of each design consideration that has to be made for a service's deployment. If not configured manually, the recommended values are applied automatically, just like defaults. The values chosen for each parameter must be clearly highlighted. Coming from the library's single responsibility, the number of parameters should not be immense; hence, the user can be expected to comprehend them instead of just being overwhelmed and skipping them.
@ -64,19 +64,19 @@ This way, the library attempts to notify its user about the existence of these d
\subsection{Documentation}
Little value can be derived from software without good documentation; undoubtedly, good documentation is a prerequisite for adoption. Documentation comes in many shapes: modern integrated development environments (IDEs) tend to show a popup of a function's description when requested (for instance, on mouse hover), and at the same time, a more comprehensive online manual and example projects are also still expected. Descriptive error messages can also be viewed as documentation.
Little value can be derived from software without good documentation; undoubtedly, good documentation is a prerequisite for adoption. Documentations come in many shapes: modern integrated development environments (IDEs) tend to show a popup of a function's description when requested (for instance, on mouse hover), and at the same time, a more comprehensive online manual and example projects are also still expected. Descriptive error messages can also be viewed as documentation.
The library must have quality documentation for all categories. Accordingly, for structuring it, the \textit{Diátaxis} philosophy is preferred \cite{Procida_Diataxis_documentation_framework} which prescribes dividing documentation into 4 parts along 2 axes: practical-theoretical and passive-active consumption. The four quadrants derived from this are tutorials, how-to guides, references, and explanations.
Once again, we might notice two competing interests: the level of detail and the length of the documentation. For example, FastAPI\footnote{\href{https://fastapi.tiangolo.com/async/\#concurrent-burgers}{fastapi.tiangolo.com}}, a popular Python web framework, has extensive descriptions and explanations on all topics related to Python's import system, the HTTP protocol, concurrency, deployment, etc. The actual framework's documentation is sprinkled over these overly broad topics. This is undoubtedly helpful for beginners to acquire knowledge from a single place. Nevertheless, this high level of accessibility actually hinders the process of finding the relevant sections (in CDCB, this shows a trade-off between the support of \textit{Searching} and \textit{Comprehension} tasks). Diátaxis' take is that linking to external resources about the library's domain is welcome, but the documentation must have a single responsibility: describing the library itself.
Once again, we might notice two competing interests: the level of detail and the length of the documentation. For example, FastAPI\footnote{\href{https://fastapi.tiangolo.com/async/\#concurrent-burgers}{fastapi.tiangolo.com}}, a popular Python web framework, has extensive descriptions and explanations on all topics related to Python's import system, the HTTP protocol, concurrency, deployment, and more. The actual framework's documentation is sprinkled over these overly broad topics. This is undoubtedly helpful for beginners to acquire knowledge from a single place. Yet, this high level of accessibility actually hinders the process of finding the relevant sections; in CDCB, this shows a trade-off between the support of \textit{Searching} and \textit{Comprehension} tasks. Diátaxis' take is that linking to external resources about the library's domain is welcome, but the documentation must have a single responsibility: describing the library itself.
A large portion of software documentations is automatically generated from source code, and this has the advantage of always keeping it in sync with code changes. However, it might also signal that the API is too large because it is inconvenient for the developers to document it by hand. Striking the right balance between handcrafted and automatically extracted documentation may be a vital component of good documentation.
When it comes to example code, showing at least a minimal starter code and the way of customising it has to be showcased front and centre. It is a well-known observation that developers only read the documentation when they are stuck, and there might be some merit to this. Making them not get stuck --- by providing a starter code from which they can explore the API using IntelliSense-like solutions --- should be preferred. For example, another widely popular Python web framework, Flask\footnote{\href{https://flask.palletsprojects.com/en/2.1.x/}{flask.palletsprojects.com/en/2.1.x}}, at this time, has 324 homogeneously styled links on its landing page. Out of these, only two lead to the quick-start code. Of course, it is not hidden, but I argue that the DX could be improved by displaying where to start more prominently.
When it comes to example code, showing at least a minimal starter code and the way of customising it has to be showcased front and centre. It is a well-known observation that developers only read the documentation when they are stuck, and there might be some merit to this. Making them not get stuck --- by providing a starter code from which they can explore the API using IntelliSense-like solutions --- should be preferred. Take the example of another widely popular Python web framework, Flask\footnote{\href{https://flask.palletsprojects.com/en/2.1.x/}{flask.palletsprojects.com/en/2.1.x}}, at this time, has 324 homogeneously styled links on its landing page. Out of these, only two lead to the quick-start code. Of course, it is not hidden, but I argue that the DX could be improved by displaying where to start more prominently.
\subsection{Developer experience}
Subjectively, a key component of good DX is \textit{Progressive evaluation} through which development can become a highly iterative, experimental process. This is well understood by popular data science tools, such as Jupyter Notebooks. \textit{GreatAI} also has to support some level of this, for example, in the form of auto-reload on code changes. Further key ingredients for good DX are consistency and discoverability. To give one more example, the MySQL connector's Python implementation\footnote{\href{https://dev.mysql.com/doc/connector-python/en/}{dev.mysql.com/doc/connector-python/en/}} has a cursor object which exposes a \texttt{fetchone} method. Even though this naming scheme is not conventional in Python since it does not follow \href{https://peps.python.org/pep-0008/}{PEP 8}, at least the API is intuitive: changing \texttt{sql\_cursor.fetchone()} to \texttt{sql\_cursor.fetchall()} returns all items instead of just one. Using good and consistent names is the key to good DX.
Subjectively, a key component of good DX is \textit{Progressive evaluation} through which development can become a highly iterative, experimental process. This is well-understood by popular data science tools, such as Jupyter Notebooks. \textit{GreatAI} also has to support some level of this, for example, in the form of auto-reload on code changes. Further key ingredients for good DX are consistency and discoverability. To give one more example, the MySQL connector's Python implementation\footnote{\href{https://dev.mysql.com/doc/connector-python/en/}{dev.mysql.com/doc/connector-python/en}} has a cursor object which exposes a \texttt{fetchone} method. Even though this naming scheme is not conventional in Python since it does not follow \href{https://peps.python.org/pep-0008/}{PEP 8}, at least the API is intuitive: changing \texttt{sql\_cursor.fetchone()} to \texttt{sql\_cursor.fetchall()} returns all items instead of just one. Using good and consistent names is the key to good DX.
At the same time, Python codebases are rarely strictly object-oriented (OO). They are a mix of the functional, data-driven, and OO paradigms. Consequently, relying on classes for grouping related functions is not always desirable; therefore, it is even more imperative to name similar functions similarly. This helps discoverability and chunking \cite{hermans2021programmer}, which amounts to quicker comprehension.