Compare commits

..

No commits in common. "main" and "v0.1.0" have entirely different histories.
main ... v0.1.0

267 changed files with 7056 additions and 12230 deletions

View file

@ -1,5 +1,6 @@
.env
.git
__pycache__
.cache
.mypy_cache
.pytest_cache
@ -10,9 +11,6 @@ Dockerfile
.github
.vscode
docs
!docs/hello_world.py
scripts
tests
mkdocs.yaml
**/.mypy_cache
**/__pycache__
**/tracing_database.json

View file

@ -1,57 +0,0 @@
name: Publish on DockerHub
on:
push:
tags: ['*']
workflow_dispatch:
jobs:
publish:
runs-on: docker
steps:
- name: Checkout
uses: actions/checkout@v4
# The `docker` runner image is node-based and may not ship the docker CLI
# that the docker/* actions below drive; install it idempotently, as the
# sibling repos do for their Docker jobs.
- name: Ensure Docker CLI
run: |
set -eux
if ! command -v docker >/dev/null 2>&1; then
ARCH=$(uname -m)
curl -fsSL --retry 3 --retry-connrefused \
"https://download.docker.com/linux/static/stable/${ARCH}/docker-27.5.1.tgz" \
| tar xz --strip-components=1 -C /usr/local/bin docker/docker
fi
docker --version
# Marketplace actions must be referenced by full URL: this instance's
# DEFAULT_ACTIONS_URL points at code.forgejo.org, so only bare `actions/*`
# names resolve to GitHub automatically.
- name: Set up QEMU
uses: https://github.com/docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: https://github.com/docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: https://github.com/docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata for the Docker image
id: meta
uses: https://github.com/docker/metadata-action@v5
with:
images: schmelczera/great-ai
- name: Build and push
uses: https://github.com/docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64

View file

@ -1,52 +0,0 @@
name: Publish documentation
on:
push:
branches: [main]
paths:
- 'docs/**'
- 'great_ai/**'
- 'mkdocs.yaml'
- '.forgejo/workflows/docs.yml'
workflow_dispatch:
concurrency:
group: pages
cancel-in-progress: false
jobs:
publish:
runs-on: docker
steps:
- uses: actions/checkout@v4
with:
# The mkdocs git-revision-date plugin reads each page's git history.
fetch-depth: 0
- name: Install uv
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Set up Python
run: uv python install 3.10
- name: Install dependencies
run: |
uv venv --python 3.10
uv pip install '.[dev]'
- name: Build documentation
run: |
. .venv/bin/activate
mkdocs build
# The old workflow ran `mkdocs gh-deploy` (GitHub Pages); on this instance
# built sites are rsynced into the host /pages mount that nginx serves,
# via the shared ci-actions/deploy-pages action — same as photos/reconcile.
- name: Deploy to pages mount
if: github.ref == 'refs/heads/main'
uses: http://forgejo:3000/andras/ci-actions/deploy-pages@main
with:
source: site
target: great-ai

View file

@ -1,28 +0,0 @@
name: Publish on PyPI
on:
push:
tags: ['*']
workflow_dispatch:
jobs:
publish:
runs-on: docker
steps:
- uses: actions/checkout@v4
- name: Install uv
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Set up Python
run: uv python install 3.10
# Forgejo cannot use PyPI trusted publishing (OIDC), so authenticate with
# an API token, the same way the sibling repos publish their packages.
- name: Build and publish
env:
FLIT_USERNAME: __token__
FLIT_PASSWORD: ${{ secrets.PYPI_TOKEN }}
run: uvx flit publish

View file

@ -1,73 +0,0 @@
name: Check
on:
push:
branches: [main, dev]
pull_request:
branches: [main, dev]
workflow_dispatch:
concurrency:
group: ${{ gitea.workflow }}-${{ gitea.ref }}
cancel-in-progress: true
jobs:
lint:
name: Lint, format & type checks
runs-on: docker
steps:
- uses: actions/checkout@v4
- name: Install uv
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Set up Python
run: uv python install 3.10
- name: Install dependencies
# --seed gives the venv its own pip, which scripts/check-python.sh shells
# out to for its linters; '.[dev]' provides the rest (mypy resolves the
# package's own imports against the installed tree).
run: |
uv venv --seed --python 3.10
uv pip install '.[dev]'
- name: Check code and formatting
run: |
. .venv/bin/activate
scripts/check-python.sh great_ai tests
test:
name: Test on Python ${{ matrix.python-version }}
runs-on: docker
strategy:
fail-fast: false
matrix:
# pyproject declares >= 3.7, but uv's standalone CPython and the current
# dependency floors (scikit-learn/numpy now require >= 3.9) no longer
# build on the older interpreters, so we test the current supported set.
# The windows leg of the old GitHub matrix is dropped: this Forgejo
# instance only has a Linux `docker` runner.
python-version: ['3.10', '3.11', '3.12', '3.13']
steps:
- uses: actions/checkout@v4
- name: Install uv
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}
- name: Install dependencies
run: |
uv venv --python ${{ matrix.python-version }}
uv pip install '.[dev]'
- name: Run tests
run: |
. .venv/bin/activate
pytest --doctest-modules --asyncio-mode=strict

6
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"

36
.github/workflows/codeql-analysis.yml vendored Normal file
View file

@ -0,0 +1,36 @@
name: "Analyse withCodeQL"
on:
push:
branches: [ "main" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "main" ]
schedule:
- cron: '45 13 * * 5'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'python' ]
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

39
.github/workflows/docker.yaml vendored Normal file
View file

@ -0,0 +1,39 @@
name: publish on DockerHub
on:
push:
tags:
- '*'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Checkout
uses: actions/checkout@v3
- name: Extract metadata for the Docker image
id: meta
uses: docker/metadata-action@v4
with:
images: schmelczera/great-ai
- name: Build and push
uses: docker/build-push-action@v3
with:
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64

27
.github/workflows/docs.yaml vendored Normal file
View file

@ -0,0 +1,27 @@
name: publish documentation
on:
workflow_dispatch:
push:
branches:
- main
- dev
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Python 3.9
uses: actions/setup-python@v4
with:
python-version: 3.9
- name: Install dependencies
run: pip install --upgrade './[dev]'
- name: Build documentation
run: mkdocs gh-deploy

26
.github/workflows/publish.yaml vendored Normal file
View file

@ -0,0 +1,26 @@
name: publish on PyPI
on:
push:
tags:
- '*'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.9
uses: actions/setup-python@v4
with:
python-version: 3.9
- name: Install Flit
run: pip install --upgrade flit --user
- name: Build and publish
run: flit publish --setup-py
env:
FLIT_USERNAME: __token__
FLIT_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}

96
.github/workflows/test.yml vendored Normal file
View file

@ -0,0 +1,96 @@
name: tests
on:
workflow_dispatch:
push:
branches:
- main
- dev
jobs:
test:
name: Build and test on ${{ matrix.operating-system }} with Python ${{ matrix.python-version }}
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10"]
operating-system: [windows-latest, ubuntu-latest]
runs-on: ${{ matrix.operating-system }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python3 -m pip install --upgrade pip
pip install --upgrade './[dev]'
- name: Check code and formatting
run: scripts/check-python.sh great_ai tests
- name: Run tests
run: python3 -m pytest --doctest-modules --cov=. --cov-report=xml --junit-xml pytest.xml --asyncio-mode=strict || true
- name: Upload results
if: always()
uses: actions/upload-artifact@v3
with:
name: Unit test coverage (Python ${{ matrix.python-version }} - ${{ matrix.operating-system }})
path: "*.xml"
sonar:
name: Analyse Python project with SonarQube
needs: test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install --upgrade .
rm -rf build
- name: Download test results
uses: actions/download-artifact@v3
with:
path: artifacts
- uses: sonarsource/sonarqube-scan-action@master
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
with:
args: >
-Dsonar.projectKey=great-ai
-Dsonar.login=${{ secrets.SONAR_TOKEN }}
-Dsonar.verbose=true
-Dsonar.python.coverage.reportPaths=artifacts/*/coverage.xml
-Dsonar.coverage.exclusions=**/external/**/*
-Dsonar.exclusions=**/external/**/*,artifacts/**/*
publish-test-results:
name: Publish unit test results
needs: test
runs-on: ubuntu-latest
if: always()
steps:
- name: Download artifacts
uses: actions/download-artifact@v3
with:
path: artifacts
- name: Publish results
uses: EnricoMi/publish-unit-test-result-action@v2
with:
files: artifacts/*/pytest.xml

1
.gitignore vendored
View file

@ -9,4 +9,3 @@ __pycache__
*.egg-info
build
tracing_database.json
.tox

11
.vscode/settings.json vendored
View file

@ -2,7 +2,6 @@
"cSpell.words": [
"alru",
"Analyse",
"András",
"basereload",
"boto",
"botocore",
@ -22,7 +21,6 @@
"initialising",
"inplace",
"ipynb",
"langcodes",
"lemmatize",
"levelname",
"levelno",
@ -36,13 +34,11 @@
"organisation's",
"Parcoords",
"plotly",
"pretrained",
"proba",
"pydantic",
"pymongo",
"pyplot",
"redoc",
"scibert",
"serialise",
"sklearn",
"starlette",
@ -68,17 +64,16 @@
"**/.pytest_cache": true,
"**/*.egg-info": true,
"**/*.cache": true,
"**/*.tox": true,
"**/tracing_database.json": true
},
"notebook.output.textLineLimit": 400,
"python.defaultInterpreterPath": ".env/bin/python",
"python.testing.pytestArgs": ["tests"],
"editor.rulers": [88],
"python.testing.pytestArgs": [
"tests"
],
"python.linting.flake8Enabled": false,
"python.linting.pylintEnabled": false,
"python.linting.mypyEnabled": true,
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"editor.wordWrap": "on"
}

33
.vscode/tasks.json vendored
View file

@ -4,49 +4,30 @@
{
"label": "Format and lint",
"type": "shell",
"command": "source .env/bin/activate && scripts/format-python.sh great_ai docs tests",
"command": "source .env/bin/activate && scripts/format-python.sh great_ai tests",
"windows": {
"command": ".env\\bin\\activate.bat; scripts\\format-python.sh great_ai docs tests"
"command": ".env\\bin\\activate.bat; scripts\\format-python.sh great_ai tests"
},
"group": "test",
"presentation": {
"reveal": "always",
"showReuseMessage": false,
"panel": "shared"
"panel": "new"
},
"options": {
"cwd": "${workspaceFolder}"
}
},
{
"label": "Test (quick)",
"label": "Test",
"type": "shell",
"command": "source .env/bin/activate && python3 -m pytest . --doctest-modules --asyncio-mode=strict",
"command": "source .env/bin/activate && python3 -m pytest . --doctest-modules",
"windows": {
"command": ".env\\bin\\activate.bat; python3 -m pytest . --doctest-modules --asyncio-mode=strict"
"command": ".env\\bin\\activate.bat; python3 -m pytest . --doctest-modules"
},
"group": "test",
"presentation": {
"reveal": "always",
"showReuseMessage": false,
"panel": "shared"
},
"options": {
"cwd": "${workspaceFolder}"
}
},
{
"label": "Test (all Python versions)",
"type": "shell",
"command": "source .env/bin/activate && python3 -m tox",
"windows": {
"command": ".env\\bin\\activate.bat; python3 -m tox"
},
"group": "test",
"presentation": {
"reveal": "always",
"showReuseMessage": false,
"panel": "shared"
"panel": "new"
},
"options": {
"cwd": "${workspaceFolder}"

View file

@ -1,14 +1,12 @@
# syntax=docker/dockerfile:1.4
FROM python:3.10.4-slim-bullseye
LABEL org.opencontainers.image.title="GreatAI package wrapper image"
LABEL org.opencontainers.image.title="GreatAI package wrapper container"
LABEL org.opencontainers.image.vendor="ScoutinScience B.V."
LABEL org.opencontainers.image.authors="andras@schmelczer.dev"
LABEL org.opencontainers.image.source="https://github.com/schmelczer/great-ai"
SHELL ["/bin/bash", "-c"]
ENV ENVIRONMENT=production
EXPOSE 6060
# curl is needed for the healthcheck
# build-essentials are needed for building packages
@ -23,24 +21,16 @@ RUN python3 -m pip --no-cache-dir install --upgrade pip &&\
rm -rf great_ai
HEALTHCHECK \
--interval=30s \
--timeout=180s \
--start-period=60s \
--interval=10s \
--timeout=60s \
--start-period=30s \
--retries=5 \
CMD [ "curl", "--fail", "http://localhost:6060/health" ]
WORKDIR /app
COPY <<EOF hello_world.py
from great_ai import GreatAI
\
@GreatAI.create
def hello_world(name: str) -> str:
"""Learn more about GreatAI at https://great-ai.scoutinscience.com"""
return f"Hello {name}!"
EOF
EXPOSE 6060
VOLUME /app
COPY docs/hello_world.py .
ENTRYPOINT ["/usr/local/bin/python3", "-m", "great_ai"]
CMD ["hello_world.py"]

116
README.md
View file

@ -1,67 +1,13 @@
# <img src="https://raw.githubusercontent.com/schmelczer/great-ai/main/docs/media/logo.png" alt="logo of great-ai" width=60 /> GreatAI
# GreatAI
> Easily transform your prototype AI code into production-ready software.
**work in progress, do not use!**
[![PyPI version](https://badge.fury.io/py/great-ai.svg)](https://badge.fury.io/py/great-ai)
[![Test](https://github.com/schmelczer/great-ai/actions/workflows/test.yml/badge.svg)](https://github.com/schmelczer/great-ai/actions/workflows/check.yml)
[![Quality Gate Status](https://sonar.scoutinscience.com/api/project_badges/measure?project=great-ai&metric=alert_status)](https://sonar.schmelczer.com/dashboard?id=great-ai)
[![Publish on PyPI](https://github.com/schmelczer/great-ai/actions/workflows/publish.yaml/badge.svg)](https://github.com/schmelczer/great-ai/actions/workflows/publish.yaml)
[![Publish on DockerHub](https://github.com/schmelczer/great-ai/actions/workflows/docker.yaml/badge.svg)](https://github.com/schmelczer/great-ai/actions/workflows/docker.yaml)
[![Downloads](https://pepy.tech/badge/great-ai/month)](https://pepy.tech/project/great-ai)
[![Docker Pulls](https://img.shields.io/docker/pulls/schmelczera/great-ai)](https://hub.docker.com/repository/docker/schmelczera/great-ai)
[![Test](https://github.com/schmelczer/great-ai/actions/workflows/test.yml/badge.svg)](https://github.com/schmelczer/great-ai/actions/workflows/test.yml)
[![Sonar line coverage](https://sonar.scoutinscience.com/api/project_badges/measure?project=great-ai&metric=coverage)](https://sonar.scoutinscience.com/dashboard?id=great-ai)
[![Sonar LoC](https://sonar.scoutinscience.com/api/project_badges/measure?project=great-ai&metric=ncloc)](https://sonar.scoutinscience.com/dashboard?id=great-ai)
Applying AI is becoming increasingly more accessible, but many case studies have shown that these applications are often deployed poorly. This may lead to suboptimal performance and to introducing unintended biases. GreatAI helps fix this by allowing you to easily transform your prototype AI code into production-ready software.
## Example
```sh
pip install great-ai
```
Create a new file called `demo.py`
```python
from great_ai import GreatAI
@GreatAI.create
def greeter(name: str) -> str:
return f"Hello {name}!"
```
Start it by executing `great-ai demo.py`, and find the dashboard at [http://localhost:6060](http://localhost:6060/dashboard).
![demo screen capture](https://raw.githubusercontent.com/schmelczer/great-ai/main/docs/media/demo.gif)
That's it. Your GreatAI service is _nearly_ ready for production use. Many of the [SE4ML best practices](https://se-ml.github.io) are configured and implemented automatically (of course, these can be customised as well).
[Check out the full documentation here](https://great-ai.scoutinscience.com).
## Why is this GREAT?
![scope of GreatAI](https://raw.githubusercontent.com/schmelczer/great-ai/main/docs/media/scope-simple.drawio.svg)
GreatAI fits between the prototype and deployment phases of your AI development lifecycle. This is highlighted in blue in the diagram. Here, several best practices can be automatically implemented, aiming to achieve the following attributes:
- **G**eneral: use any Python library without restriction
- **R**obust: have error-handling and well-tested utilities out-of-the-box
- **E**nd-to-end: utilise end-to-end feedback as a built-in, first-class concept
- **A**utomated: focus only on what actually requires your attention
- **T**rustworthy: deploy models that you and society can confidently trust
## Why GreatAI?
There are other existing solutions aiming to facilitate this phase. [Amazon SageMaker](https://aws.amazon.com/sagemaker) and [Seldon Core](https://www.seldon.io/solutions/open-source-projects/core) provide the most comprehensive suite of features. If you have the opportunity to use them, do that because they're great.
However, [research indicates](https://great-ai.scoutinscience.com) that professionals rarely use them. This may be due to their inherent setup and operational complexity. **GreatAI is designed to be as simple to use as possible.** Its straightforward, high-level API and sensible default configuration make it easy to start using. Despite its relative simplicity over Seldon Core, it still implements many of the [SE4ML best practices](https://se-ml.github.io), and thus, can meaningfully improve your deployment without requiring prohibitively great effort.
## [Learn more](https://great-ai.scoutinscience.com)
[Check out the full documentation here](https://great-ai.scoutinscience.com).
## Find `great-ai` on [PyPI](https://pypi.org/project/great-ai/)
```sh
pip install great-ai
```
## Find `great-ai` on [DockerHub](https://hub.docker.com/repository/docker/schmelczera/great-ai)
@ -69,35 +15,43 @@ pip install great-ai
docker run -p6060:6060 schmelczera/great-ai
```
## Contribute
Find the dashboard at [http://localhost:6060](http://localhost:6060/dashboard/).
Contributions are welcome.
### Install for development
## Find `great-ai` on [PyPI](https://pypi.org/project/great-ai/)
```sh
pip install great-ai
```
```python
from great_ai import GreatAI
@GreatAI.create
def hello_world(name: str) -> str:
return f"Hello {name}!"
```
> Create a new file called `main.py`
Deploy by executing `python3 -m great-ai main.py`
> Or: `python3 -m great-ai main.py`
Find the dashboard at [http://localhost:6060](http://localhost:6060/dashboard/).
### Contribute
#### Install
```sh
python3 -m venv --copies .env
source .env/bin/activate
pip install --upgrade flit pip
flit install --symlink
python3 -m pip install flit
python3 -m flit install --symlink --deps=all
```
### Develop
#### Documentation
```sh
scripts/format-python.sh great_ai docs tests
mkdocs serve --dirtyreload
```
> Format code.
```sh
python3 -m pytest --doctest-modules --asyncio-mode=strict .
```
> Run tests.
```sh
mkdocs serve
```
> Serve documentation.

View file

@ -1 +0,0 @@
great-ai.scoutinscience.com

16
docs/README.md Normal file
View file

@ -0,0 +1,16 @@
# **S**coutinScience **U**tilitie**S** for text processing [![Lint and test ScoutinScience utilities](https://github.com/ScoutinScience/platform/actions/workflows/sus-general.yaml/badge.svg)](https://github.com/ScoutinScience/platform/actions/workflows/sus-general.yaml)
## Exports
- [clean](src/sus/clean.py)
- [language](src/sus/clean.py)
- [unique](src/sus/unique.py)
- [parallel_map](src/sus/parallel_map.py)
- [evaluate_ranking](src/sus/evaluate_ranking/evaluate_ranking.py)
- [get_sentences](src/sus/get_sentences.py)
## Development
- Optional booleans must have a default value of `False`.
- No imports in top-level `__init__.py`, in order to not load anything unnecessary automatically
- Should only be updated through a PR

View file

@ -1,75 +0,0 @@
# Additional files in the repository
In order to give you a smooth experience while comprehending this example, all non-notebook files are presented on this page in one place. In reality, these files should be in your project's top-level directory.
## config.ini
```ini title="config.ini"
ENVIRONMENT = DEVELOPMENT
ENVIRONMENT = ENV:ENVIRONMENT
MONGO_CONNECTION_STRING=ENV:MONGO_CONNECTION_STRING
MONGO_DATABASE=highlights
AWS_REGION_NAME = eu-west-2
AWS_ACCESS_KEY_ID = MY_DEFAULT_AWS_ACCESS_KEY_ID_FOR_DEVELOPMENT
AWS_SECRET_ACCESS_KEY = MY_DEFAULT_AWS_SECRET_ACCESS_KEY_FOR_DEVELOPMENT
LARGE_FILES_BUCKET_NAME = my-orgs-large-files
AWS_REGION_NAME = ENV:AWS_REGION_NAME
AWS_ACCESS_KEY_ID = ENV:AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY = ENV:AWS_SECRET_ACCESS_KEY
LARGE_FILES_BUCKET_NAME = ENV:LARGE_FILES_BUCKET_NAME
```
> All necessary configuration which is read by [great_ai.utilities.ConfigFile][]. This will resolve values starting with `ENV:` from your environment variables.
## requirements.txt
```requirements.txt title="requirements.txt"
torch==1.12.0
transformers==4.20.1
numpy==1.23.0
```
> Usually, it is recommended to pin (freeze) the library versions on which we depend. This file is referenced by the [Dockerfile](#dockerfile).
## Dockerfile
```Dockerfile title="Dockerfile"
FROM schmelczera/great-ai:v0.1.6
COPY requirements.txt ./
RUN pip install --no-cache-dir --requirement requirements.txt
COPY . ./
RUN large-file --backend s3 --secrets s3.ini --cache scibert-highlights
CMD ["deploy.ipynb"]
```
> This is used by the CD pipeline to create the production deployment of the service.
## .dockerignore
```dockerignore title=".dockerignore"
.cache
.git
data
.gitignore
.env
.vscode
.dockerignore
Dockerfile
.mypy_cache
.gitignore
**/__pycache__
**/.DS_Store
README.md
```
> It is useful not to send, for example, the `.cache` folder used by LargeFile to the docker daemon; this will speed up your local build times substantially.
!!! Note ".gitignore"
A very similar looking `.gitignore` file should also be present.

File diff suppressed because one or more lines are too long

View file

@ -1,324 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Create an inference function\n",
"\n",
"Everything is ready to wrap the previously trained model and deploy it. \n",
"\n",
"First, we need to configure the LargeFileBackend, the TracingDatabase and GreatAI."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"\u001b[38;5;226mThe value of `ENVIRONMENT` contains the \"ENV` prefix but `ENVIRONMENT` is not defined as an environment variable, using the default value defined above (`DEVELOPMENT`)\u001b[0m\n",
"\u001b[38;5;226mEnvironment variable ENVIRONMENT is not set, defaulting to development mode ‼️\u001b[0m\n",
"\u001b[38;5;39mMongoDbDriver has been already configured: skipping initialisation\u001b[0m\n",
"\u001b[38;5;39mLargeFileS3 has been already configured: skipping initialisation\u001b[0m\n",
"\u001b[38;5;39mGreatAI (v0.1.6): configured ✅\u001b[0m\n",
"\u001b[38;5;39m 🔩 tracing_database: MongoDbDriver\u001b[0m\n",
"\u001b[38;5;39m 🔩 large_file_implementation: LargeFileS3\u001b[0m\n",
"\u001b[38;5;39m 🔩 is_production: False\u001b[0m\n",
"\u001b[38;5;39m 🔩 should_log_exception_stack: True\u001b[0m\n",
"\u001b[38;5;39m 🔩 prediction_cache_size: 4096\u001b[0m\n",
"\u001b[38;5;39m 🔩 dashboard_table_size: 100\u001b[0m\n",
"\u001b[38;5;226mYou still need to check whether you follow all best practices before trusting your deployment.\u001b[0m\n",
"\u001b[38;5;226m> Find out more at https://se-ml.github.io/practices\u001b[0m\n"
]
}
],
"source": [
"from great_ai.utilities import ConfigFile\n",
"from great_ai.large_file import LargeFileS3\n",
"from great_ai import configure, MongoDbDriver\n",
"\n",
"configuration = ConfigFile(\"config.ini\")\n",
"\n",
"LargeFileS3.configure_credentials_from_file(configuration)\n",
"MongoDbDriver.configure_credentials_from_file(configuration)\n",
"\n",
"configure(\n",
" dashboard_table_size=100, # traces are small, we can show many\n",
" prediction_cache_size=4096, # predictions are expensive, cache them\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For a pleasant developer experience, we create some typed models that will show up in the automatically generated OpenAPI schema specification and will also provide runtime type validation."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"from typing import List\n",
"from pydantic import BaseModel\n",
"\n",
"\n",
"class Attention(BaseModel):\n",
" weight: float\n",
" token: str\n",
"\n",
"\n",
"class EvaluatedSentence(BaseModel):\n",
" score: float\n",
" text: str\n",
" explanation: List[Attention]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Even though `@use_model` caches the remote files locally and it also handles deserialising objects, we only use it to store a directory. In this case, it gives back a path, the path to that directory. So, we need to load the files from that folder ourselves. In order to only load it once per process, we create a small model loader helper function.\n",
"\n",
"> This is usually not needed, however, when we can outsmart `dill` so for optimisation purposes, we do it."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"\u001b[38;5;39mLatest version of scibert-highlights is 0 (from versions: 0)\u001b[0m\n",
"\u001b[38;5;39mFile scibert-highlights-0 found in cache\u001b[0m\n"
]
}
],
"source": [
"from great_ai import use_model\n",
"from pathlib import Path\n",
"from typing import Tuple\n",
"from transformers import (\n",
" PreTrainedModel,\n",
" PreTrainedTokenizer,\n",
")\n",
"from transformers import (\n",
" AutoConfig,\n",
" AutoModelForSequenceClassification,\n",
" AutoTokenizer,\n",
")\n",
"\n",
"_tokenizer: PreTrainedTokenizer = None\n",
"_loaded_model: PreTrainedModel = None\n",
"\n",
"\n",
"@use_model(\"scibert-highlights\", version=\"latest\", model_kwarg_name=\"model_path\")\n",
"def get_tokenizer_and_model(\n",
" model_path: Path, original_model: str = \"allenai/scibert_scivocab_uncased\"\n",
") -> Tuple[PreTrainedTokenizer, PreTrainedModel]:\n",
" global _tokenizer, _loaded_model\n",
"\n",
" if _tokenizer is None:\n",
" _tokenizer = AutoTokenizer.from_pretrained(original_model)\n",
"\n",
" if _loaded_model is None:\n",
" config = AutoConfig.from_pretrained(\n",
" model_path, output_hidden_states=True, output_attentions=True\n",
" )\n",
" _loaded_model = AutoModelForSequenceClassification.from_pretrained(\n",
" model_path, config=config\n",
" )\n",
"\n",
" return _tokenizer, _loaded_model"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally, implement the inference function."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"from great_ai import GreatAI\n",
"from great_ai.utilities import clean\n",
"\n",
"import re\n",
"import numpy as np\n",
"import torch\n",
"from transformers.modeling_outputs import SequenceClassifierOutput\n",
"\n",
"\n",
"@GreatAI.create\n",
"def find_highlights(sentence: str) -> EvaluatedSentence:\n",
" \"\"\"Get the interestingness prediction of the input sentence using SciBERT.\n",
"\n",
" Run the SciBERT model in inference mode and evaluate the sentence.\n",
" Additionally, provide explanation in the form of the last layer's sum attention\n",
" between `[CLS]` and the other tokens.\n",
" \"\"\"\n",
"\n",
" tokenizer, loaded_model = get_tokenizer_and_model()\n",
" sentence = clean(sentence, convert_to_ascii=True, remove_brackets=True)\n",
"\n",
" tensors = tokenizer(sentence, return_tensors=\"pt\", truncation=True, max_length=512)\n",
"\n",
" with torch.inference_mode():\n",
" result: SequenceClassifierOutput = loaded_model(**tensors)\n",
" positive_likelihood = torch.nn.Softmax(dim=1)(result.logits)[0][1]\n",
" tokens = tensors[\"input_ids\"][0]\n",
"\n",
" attentions = np.sum(result.attentions[-1].numpy()[0], axis=0)[0][1:-1]\n",
" # Tuple of `torch.FloatTensor` (one for each layer) of shape\n",
" # `(batch_size, num_heads, sequence_length, sequence_length)`.\n",
"\n",
" explanation = []\n",
"\n",
" token_attentions = list(zip(attentions, tokens[1:-1]))\n",
" for token in re.split(r\"([ .,])\", sentence):\n",
" token = token.strip()\n",
" if not token:\n",
" continue\n",
" bert_tokens = tokenizer(\n",
" token, return_tensors=\"pt\", truncation=True, max_length=512\n",
" )[\"input_ids\"][0][\n",
" 1:-1\n",
" ] # truncation=True needed to fix `RuntimeError: Already borrowed`\n",
" weight = 0\n",
" for t1 in bert_tokens:\n",
" if not token_attentions:\n",
" break\n",
" a, t2 = token_attentions.pop(0)\n",
" assert t1 == t2, sentence\n",
" weight += a\n",
" explanation.append(\n",
" Attention(\n",
" token=token if token in \".,\" else \" \" + token, weight=round(weight, 4)\n",
" )\n",
" )\n",
" if not token_attentions:\n",
" break\n",
"\n",
" return EvaluatedSentence(\n",
" score=positive_likelihood, text=sentence, explanation=explanation\n",
" )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A simple test to see everything works. Note that the models list is filled by the `@use_model` call even though it's not on the main inference function."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(Trace[EvaluatedSentence]({'created': '2022-07-16T18:47:29.581701',\n",
" 'exception': None,\n",
" 'feedback': None,\n",
" 'logged_values': { 'arg:sentence:length': 51,\n",
" 'arg:sentence:value': 'Our solution has outperformed the '\n",
" 'state-of-the-art.'},\n",
" 'models': [{'key': 'scibert-highlights', 'version': 0}],\n",
" 'original_execution_time_ms': 7127.2063,\n",
" 'output': { 'explanation': [ {'token': ' Our', 'weight': 0.3993},\n",
" {'token': ' solution', 'weight': 0.3481},\n",
" {'token': ' has', 'weight': 0.2945},\n",
" {'token': ' outperformed', 'weight': 0.4011},\n",
" {'token': ' the', 'weight': 0.1484},\n",
" {'token': ' state-of-the-art', 'weight': 0.5727},\n",
" {'token': '.', 'weight': 7.775}],\n",
" 'score': 0.9991180300712585,\n",
" 'text': 'Our solution has outperformed the state-of-the-art.'},\n",
" 'tags': ['find_highlights', 'online', 'development'],\n",
" 'trace_id': '56e20e94-79df-4793-ae61-d20820ebe2d3'}),\n",
" Trace[EvaluatedSentence]({'created': '2022-07-16T18:47:37.020275',\n",
" 'exception': None,\n",
" 'feedback': None,\n",
" 'logged_values': { 'arg:sentence:length': 36,\n",
" 'arg:sentence:value': 'Their solution did not perform '\n",
" 'well.'},\n",
" 'models': [{'key': 'scibert-highlights', 'version': 0}],\n",
" 'original_execution_time_ms': 170.7057,\n",
" 'output': { 'explanation': [ {'token': ' Their', 'weight': 1.1475},\n",
" {'token': ' solution', 'weight': 0.8205},\n",
" {'token': ' did', 'weight': 0.3254},\n",
" {'token': ' not', 'weight': 0.2921},\n",
" {'token': ' perform', 'weight': 0.4293},\n",
" {'token': ' well', 'weight': 0.2772},\n",
" {'token': '.', 'weight': 4.4723}],\n",
" 'score': 0.12305451184511185,\n",
" 'text': 'Their solution did not perform well.'},\n",
" 'tags': ['find_highlights', 'online', 'development'],\n",
" 'trace_id': '7fcf8271-1738-4025-8305-d5a1e5100aea'}))"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"if __name__ == \"__main__\":\n",
" find_highlights(\n",
" \"Our solution has outperformed the state-of-the-art.\"\n",
" ), find_highlights(\"Their solution did not perform well.\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In this case, the service is built as a docker image, pushed to our image registry and subsequent rolling update is performed in the production cluster.\n",
"To check out the Dockerimage, go to [the additional files page](/examples/scibert/additional-files)."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.10.4 ('.env': venv)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
},
"orig_nbformat": 4,
"vscode": {
"interpreter": {
"hash": "02dd6d3afbfa9fbbe1037d64ad9014965528a1ccad21929d6e72f466389a68ad"
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View file

@ -1,50 +0,0 @@
# Summarising scientific publications from a tech-transfer perspective
This is a simplified example illustrating how `great-ai` is used in practice at [ScoutinScience](https://www.scoutinscience.com/){ target=_blank }. The subpages show `great-ai` in action by going over the lifecycle of fine-tuning and deploying a BERT-based software service.
??? note "Propriety data"
The purpose of this example is to show you different ways in which `great-ai` can assist you. The exact NLP task being solved is not central. Stemming from this and from the difficult nature of obtaining appropriate training data, the propriety dataset used for the experiments is not shared.
## Objectives
1. You will see how the [great_ai.utilities](/reference/utilities) can integrate into your Data Science workflow.
2. You will see how [great_ai.large_file](/reference/large-file) can be used to version and store your trained model.
3. You will see how [GreatAI][great_ai.GreatAI] should be used to prepare your model for a robust and responsible deployment.
4. You will see multiple ways of customising your deployment.
## Overview
One of the core features of the ScoutinScience platform is summarising research papers from a tech-transfer perspective. In short, extractive summarisation is preferred using a binary classifier trained on clients' judgement of sentence interestingness. Thus, documents are sentences, and the expected output is a binary label showing whether a sentence is "worthy" of being in the tech-transfer summary. Explaining each decision is imperative since ScoutinScience embraces applying only explainable AI (XAI) methods wherever feasible.
!!! success
You are ready to start the tutorial. Feel free to return to the [summary](#summary) section once you're finished.
<div style="display: flex; justify-content: space-evenly;" markdown>
[:material-database: Examine data](data.ipynb){ .md-button .md-button--primary }
[:fontawesome-solid-chart-simple: Train model](train.ipynb){ .md-button .md-button--primary }
[:material-cloud-tags: Deploy service](deploy.ipynb){ .md-button .md-button--primary }
</div>
## Summary
### [Data notebook](data.ipynb)
We load and analyse the data by calculating inter-rater reliability and checking the feasibility of using an AI-based approach by testing the accuracy of a trivial baseline method.
### [Training notebook](train.ipynb)
We simply fine-tune SciBERT.
After training and evaluating a model, it is exported using [great_ai.save_model][]. For more info, check out [the configuration how-to page](/how-to-guides/configure-service).
### [Deployment notebook](deploy.ipynb)
We customise the GreatAI configuration, create custom caching for the model and implement an inference function that can be hardened by wrapping it in a [GreatAI][great_ai.GreatAI] instance. We also extract the attention weights as a quasi-explanation.
Finally, we test the model's inference function through the GreatAI dashboard. [The only thing left is to deploy the hardened service properly.](/how-to-guides/use-service)
#### [Additional files](additional-files.md)
There are some other files required for deploying the notebook. For example, the config file for S3 and MongoDB or a Dockerfile for building a custom image. These are gathered and shown on a separate page.

View file

@ -1,374 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Fine-tune SciBERT\n",
"\n",
"We are planning to do a simple classification task on scientific text. For that, [SciBERT](https://github.com/allenai/scibert) is an ideal model to fine-tune since it has been pretrained of academic publications.\n",
"\n",
"This notebook was updated so that it can run in [Google Colab](https://colab.research.google.com/).\n",
"\n",
"First, we need to install the dependencies."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"executionInfo": {
"elapsed": 2529,
"status": "ok",
"timestamp": 1656596749103,
"user_tz": -120
},
"id": "j7l0nD9hDQbB",
"outputId": "88a9931b-396a-4cf1-c659-8a7b098b3cdd"
},
"outputs": [],
"source": [
"!pip install transformers datasets great-ai > /dev/null"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Load the training data from S3. (We have uploaded this to S3 in the `data` notebook.)"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"\u001b[38;5;39mLatest version of summary-train-dataset-small is 0 (from versions: 0)\u001b[0m\n",
"\u001b[38;5;39mFile summary-train-dataset-small-0 found in cache\u001b[0m\n"
]
}
],
"source": [
"from great_ai.large_file import LargeFileS3\n",
"import json\n",
"\n",
"LargeFileS3.configure_credentials_from_file(\"config.ini\")\n",
"\n",
"with LargeFileS3(\"summary-train-dataset-small\", encoding=\"utf-8\") as f:\n",
" # splitting training and test data is done later by `datasets`\n",
" X, y = json.load(f)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finetune SciBERT, for more info about this step, check out [HuggingFace](https://huggingface.co/docs/transformers/training).\n",
"If you're only here for `great-ai`, feel free to skip the next cell."
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {
"executionInfo": {
"elapsed": 118131,
"status": "ok",
"timestamp": 1656593941974,
"user_tz": -120
},
"id": "AL3etUQ3LtKN",
"outputId": "fe00589f-64dd-4b70-e612-3873b504c00a"
},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "9c57de70e68a41ecbde5093bd671715a",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
" 0%| | 0/1 [00:00<?, ?ba/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"\n",
" <div>\n",
" \n",
" <progress value='130' max='650' style='width:300px; height:20px; vertical-align: middle;'></progress>\n",
" [130/650 01:43 < 07:01, 1.23 it/s, Epoch 10/50]\n",
" </div>\n",
" <table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: left;\">\n",
" <th>Epoch</th>\n",
" <th>Training Loss</th>\n",
" <th>Validation Loss</th>\n",
" <th>F1</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <td>1</td>\n",
" <td>0.586800</td>\n",
" <td>0.512138</td>\n",
" <td>0.719101</td>\n",
" </tr>\n",
" <tr>\n",
" <td>2</td>\n",
" <td>0.411600</td>\n",
" <td>0.416675</td>\n",
" <td>0.849057</td>\n",
" </tr>\n",
" <tr>\n",
" <td>3</td>\n",
" <td>0.245600</td>\n",
" <td>0.417070</td>\n",
" <td>0.864000</td>\n",
" </tr>\n",
" <tr>\n",
" <td>4</td>\n",
" <td>0.147800</td>\n",
" <td>0.575878</td>\n",
" <td>0.852459</td>\n",
" </tr>\n",
" <tr>\n",
" <td>5</td>\n",
" <td>0.056800</td>\n",
" <td>0.474259</td>\n",
" <td>0.896552</td>\n",
" </tr>\n",
" <tr>\n",
" <td>6</td>\n",
" <td>0.022500</td>\n",
" <td>0.754236</td>\n",
" <td>0.843137</td>\n",
" </tr>\n",
" <tr>\n",
" <td>7</td>\n",
" <td>0.001000</td>\n",
" <td>0.857636</td>\n",
" <td>0.834783</td>\n",
" </tr>\n",
" <tr>\n",
" <td>8</td>\n",
" <td>0.000500</td>\n",
" <td>0.920232</td>\n",
" <td>0.869565</td>\n",
" </tr>\n",
" <tr>\n",
" <td>9</td>\n",
" <td>0.000300</td>\n",
" <td>0.970790</td>\n",
" <td>0.877193</td>\n",
" </tr>\n",
" <tr>\n",
" <td>10</td>\n",
" <td>0.000300</td>\n",
" <td>0.948689</td>\n",
" <td>0.862385</td>\n",
" </tr>\n",
" </tbody>\n",
"</table><p>"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"...\n",
"Deleting older checkpoint [models/checkpoint-39] due to args.save_total_limit\n",
"***** Running Evaluation *****\n",
" Num examples = 100\n",
" Batch size = 32\n",
"Saving model checkpoint to models/checkpoint-117\n",
"Configuration saved in models/checkpoint-117/config.json\n",
"Model weights saved in models/checkpoint-117/pytorch_model.bin\n",
"Deleting older checkpoint [models/checkpoint-52] due to args.save_total_limit\n",
"***** Running Evaluation *****\n",
" Num examples = 100\n",
" Batch size = 32\n",
"Saving model checkpoint to models/checkpoint-130\n",
"Configuration saved in models/checkpoint-130/config.json\n",
"Model weights saved in models/checkpoint-130/pytorch_model.bin\n",
"Deleting older checkpoint [models/checkpoint-78] due to args.save_total_limit\n",
"\n",
"\n",
"Training completed. Do not forget to share your model on huggingface.co/models =)\n",
"\n",
"\n",
"Loading best model from models/checkpoint-65 (score: 0.896551724137931).\n"
]
}
],
"source": [
"from transformers import (\n",
" AutoModelForSequenceClassification,\n",
" AutoTokenizer,\n",
" DataCollatorWithPadding,\n",
" Trainer,\n",
" TrainingArguments,\n",
" EarlyStoppingCallback,\n",
")\n",
"from pathlib import Path\n",
"import numpy as np\n",
"from datasets import Dataset, load_metric\n",
"\n",
"MODEL = \"allenai/scibert_scivocab_uncased\"\n",
"BATCH_SIZE = 32\n",
"\n",
"tokenizer = AutoTokenizer.from_pretrained(MODEL)\n",
"model = AutoModelForSequenceClassification.from_pretrained(MODEL, num_labels=2)\n",
"data_collator = DataCollatorWithPadding(tokenizer=tokenizer)\n",
"\n",
"\n",
"def tokenize_function(v):\n",
" return tokenizer(v[\"text\"])\n",
"\n",
"\n",
"dataset = (\n",
" Dataset.from_dict({\"text\": X, \"label\": y})\n",
" .map(lambda v: tokenizer(v[\"text\"], truncation=True), batched=True)\n",
" .remove_columns(\"text\")\n",
" .train_test_split(test_size=0.2, shuffle=True) # test is actually validation\n",
")\n",
"\n",
"f1_score = load_metric(\"f1\")\n",
"\n",
"\n",
"def compute_metrics(p):\n",
" pred, labels = p\n",
" pred = np.argmax(pred, axis=1)\n",
" return f1_score.compute(predictions=pred, references=labels)\n",
"\n",
"\n",
"training_args = TrainingArguments(\n",
" output_dir=Path(\"models\"),\n",
" per_device_train_batch_size=BATCH_SIZE,\n",
" per_device_eval_batch_size=BATCH_SIZE,\n",
" save_total_limit=5,\n",
" num_train_epochs=50,\n",
" save_strategy=\"epoch\",\n",
" evaluation_strategy=\"epoch\",\n",
" logging_strategy=\"epoch\",\n",
" weight_decay=0.01,\n",
" metric_for_best_model=\"f1\",\n",
" load_best_model_at_end=True,\n",
")\n",
"\n",
"result = Trainer(\n",
" model=model,\n",
" args=training_args,\n",
" train_dataset=dataset[\"train\"],\n",
" eval_dataset=dataset[\"test\"],\n",
" data_collator=data_collator,\n",
" compute_metrics=compute_metrics,\n",
" callbacks=[EarlyStoppingCallback(early_stopping_patience=5)],\n",
").train()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The best macro F1-score on the test set is **0.89** which is (not surprisingly) substantially more than the SVM achieved. We have a great model, it's time to deploy it. But first, we have to store it in a secure place."
]
},
{
"cell_type": "code",
"execution_count": 44,
"metadata": {
"executionInfo": {
"elapsed": 25368,
"status": "ok",
"timestamp": 1656594537509,
"user": {
"displayName": "Schmelczer András",
"userId": "08401926777942666437"
},
"user_tz": -120
},
"id": "fyNKltdquZSP",
"outputId": "e8c2cbb1-78e1-41a3-b7cf-b0cd573bc45d"
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"Configuration saved in pretrained/config.json\n",
"Model weights saved in pretrained/pytorch_model.bin\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
" adding: pretrained/ (stored 0%)\n",
" adding: pretrained/config.json (deflated 49%)\n",
" adding: pretrained/pytorch_model.bin (deflated 7%)\n"
]
}
],
"source": [
"from great_ai import save_model\n",
"\n",
"# save Torch model to local disk\n",
"model.save_pretrained(\"pretrained\")\n",
"\n",
"# upload model from local disk to S3\n",
"# (because the S3 credentials have been already set, `save_model` will use LargeFileS3)\n",
"save_model(\"pretrained\", key=\"scibert-highlights\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next: [Part 3](/examples/scibert/deploy)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.10.4 ('.env': venv)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
},
"vscode": {
"interpreter": {
"hash": "02dd6d3afbfa9fbbe1037d64ad9014965528a1ccad21929d6e72f466389a68ad"
}
}
},
"nbformat": 4,
"nbformat_minor": 0
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

File diff suppressed because one or more lines are too long

View file

@ -1,233 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Simple example: data engineering\n",
"\n",
"Here, we solve a problem similar to the tutorial's but with an explainable Naive Bayes classifier and more best practices. In short, we train a domain classifier on the [semantic scholar dataset](https://api.semanticscholar.org/corpus) by taking full advantage of `great-ai`. Subsequently, we create a production-ready deployment.\n",
"\n",
"![position of this step in the lifecycle](/media/scope-data.svg)\n",
"> The blue boxes show the steps of a typical AI-development lifecycle implemented in this notebook.\n",
"\n",
"Since the true scope of `great-ai` is the phase between proof-of-concept code and production-ready service, it is predominantly used in the [deployment notebook](/examples/simple/deploy). Feel free to skip there, or continue reading if you'd like to see the full picture.\n",
"\n",
"### Extract\n",
"\n",
"This can be achieved by downloading a public dataset (such as in this case), or by having a Data Engineer setup and give us access to the organisation's data.\n",
"\n",
"In this example, we download the semantic scholar dataset from a public S3 bucket."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"MAX_CHUNK_COUNT = 4"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'Processing 4 out of the 6002 available chunks'"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import urllib.request\n",
"from random import shuffle\n",
"\n",
"manifest = (\n",
" urllib.request.urlopen(\n",
" \"https://s3-us-west-2.amazonaws.com/ai2-s2-research-public/\"\n",
" \"open-corpus/2022-02-01/manifest.txt\"\n",
" )\n",
" .read()\n",
" .decode()\n",
") # a list of available chunks separated by '\\n' characters\n",
"\n",
"lines = manifest.split()\n",
"shuffle(lines)\n",
"chunks = lines[:MAX_CHUNK_COUNT]\n",
"\n",
"f\"\"\"Processing {len(chunks)} out of the {\n",
" len(manifest.split())\n",
"} available chunks\"\"\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Transform\n",
"\n",
"- Filter out non-English abstracts using `great_ai.utilities.predict_language`\n",
"- Project it to only keep the necessary components (text and labels), clean the textual content using `great_ai.utilities.clean`\n",
"- We will speed up processing using `great_ai.utilities.simple_parallel_map`."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"100%|██████████| 4/4 [04:22<00:00, 65.51s/it] \n"
]
}
],
"source": [
"from typing import List, Tuple\n",
"import json\n",
"import gzip\n",
"from great_ai.utilities import (\n",
" simple_parallel_map,\n",
" clean,\n",
" is_english,\n",
" predict_language,\n",
" unchunk,\n",
")\n",
"\n",
"\n",
"def preprocess_chunk(chunk_key: str) -> List[Tuple[str, List[str]]]:\n",
" response = urllib.request.urlopen(\n",
" f\"https://s3-us-west-2.amazonaws.com/ai2-s2-research-public/\"\n",
" f\"open-corpus/2022-02-01/{chunk_key}\"\n",
" ) # a gzipped JSON Lines file\n",
"\n",
" decompressed = gzip.decompress(response.read())\n",
" decoded = decompressed.decode()\n",
" chunk = [json.loads(line) for line in decoded.split(\"\\n\") if line]\n",
"\n",
" # Transform\n",
" return [\n",
" (\n",
" clean(\n",
" f'{c[\"title\"]} {c[\"paperAbstract\"]} '\n",
" f'{c[\"journalName\"]} {c[\"venue\"]}',\n",
" convert_to_ascii=True,\n",
" ), # The text is cleaned to remove common artifacts\n",
" c[\"fieldsOfStudy\"],\n",
" ) # Create pairs of `(text, [...domains])`\n",
" for c in chunk\n",
" if (c[\"fieldsOfStudy\"] and is_english(predict_language(c[\"paperAbstract\"])))\n",
" ]\n",
"\n",
"\n",
"preprocessed_data = unchunk(\n",
" simple_parallel_map(preprocess_chunk, chunks, concurrency=4)\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"X, y = zip(*preprocessed_data) # X is the input, y is the expected output"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Load\n",
"\n",
"Upload the dataset (or a part of it) to a central repository using `great_ai.add_ground_truth`. This step automatically tags each data-point with a split label according to the ratios we set. Additional tags can be also given.\n",
"\n",
"#### Production-ready backend\n",
"\n",
"The MongoDB driver is automatically configured if `mongo.ini` exists with the following scheme:\n",
"\n",
"```ini\n",
"mongo_connection_string=mongodb://localhost:27017/\n",
"mongo_database=my_great_ai_db\n",
"```\n",
"> You can install MongoDB from [here](https://www.mongodb.com/docs/manual/installation) or [use it as a service](https://www.mongodb.com/cloud/atlas/register)\n",
"\n",
"Otherwise, TinyDB is used which is just a local JSON file."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"\u001b[38;5;226mEnvironment variable ENVIRONMENT is not set, defaulting to development mode ‼️\u001b[0m\n",
"\u001b[38;5;226mCannot find credentials files, defaulting to using ParallelTinyDbDriver\u001b[0m\n",
"\u001b[38;5;226mThe selected tracing database (ParallelTinyDbDriver) is not recommended for production\u001b[0m\n",
"\u001b[38;5;226mCannot find credentials files, defaulting to using LargeFileLocal\u001b[0m\n",
"\u001b[38;5;39mGreatAI (v0.1.6): configured ✅\u001b[0m\n",
"\u001b[38;5;39m 🔩 tracing_database: ParallelTinyDbDriver\u001b[0m\n",
"\u001b[38;5;39m 🔩 large_file_implementation: LargeFileLocal\u001b[0m\n",
"\u001b[38;5;39m 🔩 is_production: False\u001b[0m\n",
"\u001b[38;5;39m 🔩 should_log_exception_stack: True\u001b[0m\n",
"\u001b[38;5;39m 🔩 prediction_cache_size: 512\u001b[0m\n",
"\u001b[38;5;39m 🔩 dashboard_table_size: 50\u001b[0m\n",
"\u001b[38;5;226mYou still need to check whether you follow all best practices before trusting your deployment.\u001b[0m\n",
"\u001b[38;5;226m> Find out more at https://se-ml.github.io/practices\u001b[0m\n"
]
}
],
"source": [
"from great_ai import add_ground_truth\n",
"\n",
"add_ground_truth(X, y, train_split_ratio=0.8, test_split_ratio=0.2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Next: [Part 2](/examples/simple/train)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.10.4 ('.env': venv)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
},
"orig_nbformat": 4,
"vscode": {
"interpreter": {
"hash": "02dd6d3afbfa9fbbe1037d64ad9014965528a1ccad21929d6e72f466389a68ad"
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 792 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,11 +0,0 @@
# Explanation
A lot more details and discussion about the problem context and approaches of GreatAI, along with its evaluation, can be found in my thesis.
<div style="display: flex; justify-content: center;">
<img src="/media/thesis-frontpage.png" style="height: 500px;" alt="front page"/>
</div>
<div style="display: flex; justify-content: space-evenly;" markdown>
[::fontawesome-solid-graduation-cap: Download](thesis/main.pdf){ .md-button .md-button--primary download="greatai_schmelczer.pdf" }
</div>

View file

@ -0,0 +1,6 @@
.venv
.env
**/.cache
.git
**/__pycache__
.dockerignore

10
docs/great_ai_example-main/.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
.env
.venv
.DS_Store
__pycache__
.cache
.mypy_cache
.pytest_cache
**/.ipynb_checkpoints
**/tracing_database.json
*.egg-info

View file

@ -0,0 +1,10 @@
{
"files.exclude": {
"**/__pycache__": true,
"**/.ipynb_checkpoints": true,
"**/.mypy_cache": true,
"**/.pytest_cache": true
},
"notebook.output.textLineLimit": 400,
"python.defaultInterpreterPath": ".env/bin/python"
}

View file

@ -0,0 +1,21 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Format and lint",
"type": "shell",
"command": "source .env/bin/activate; ./format.sh .",
"windows": {
"command": ".env\\bin\\activate.bat; .\\format.sh ."
},
"group": "test",
"presentation": {
"reveal": "always",
"panel": "new"
},
"options": {
"cwd": "${workspaceFolder}"
}
}
]
}

View file

@ -0,0 +1,11 @@
FROM test
COPY requirements.txt .
RUN pip install --no-cache-dir ./great_ai
COPY mongo.ini .
COPY deploy.ipynb .
# RUN python3 -m large_file --backend s3 -secrets ~/.aws/credentials --cache my_first_file.json:3 my_second_file my_folder:0
CMD ["deploy.ipynb"]

View file

@ -0,0 +1,23 @@
# Train a domain classifier on the [semantic scholar dataset](https://api.semanticscholar.org/corpus)
![steps](diagrams/scope.svg)
## Install
### System dependencies
Make sure you have `python3`, `pip`, and `venv` installed.
> On Ubuntu, execute: `sudo apt install -y python3 python3-pip python3-venv`
### Install dependencies
```sh
python3 -m venv --copies .env
source .env/bin/activate
pip install -r requirements.txt
```
## Execute
- [Part 1](src/data.ipynb)
- [Part 2](src/train.ipynb)
- [Part 3](src/deploy.ipynb)

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

View file

@ -0,0 +1,31 @@
#!/bin/sh
set -e
echo "Installing dependencies if necessary"
python3 -m pip install --upgrade autoflake isort black[jupyter] mypy flake8
echo "Formatting and checking $1"
cd $1
echo Running autoflake
python3 -m autoflake --expand-star-imports --remove-all-unused-imports --ignore-init-module-imports --remove-unused-variables --in-place -r .
echo Running isort
python3 -m isort --profile black --skip .env .
echo Running black
python3 -m black . --exclude .env
if ls *.py 1> /dev/null 2>&1; then
echo Running mypy
python3 -m mypy --namespace-packages --ignore-missing-imports --install-types --non-interactive --disallow-untyped-defs --disallow-incomplete-defs --pretty --follow-imports=silent --exclude=external/ --exclude=/build/ .
fi
echo Running Flake8
python3 -m flake8 . --count --show-source --statistics --exclude=__init__.py,.env,external --ignore=E501,E722,E402,W503,E203
cd -
echo "Finished formatting"

View file

@ -0,0 +1,4 @@
great_ai
notebook
nbformat
nbconvert

View file

@ -0,0 +1,265 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Train a domain classifier on the [semantic scholar dataset](https://api.semanticscholar.org/corpus)\n",
"> Part 1: obtain and clean data\n",
"\n",
"![position of this step in the lifecycle](../diagrams/scope-data.svg)\n",
"> The blue boxes show the steps implemented in this notebook.\n",
"\n",
"### Extract\n",
"\n",
"This can be achieved by downloading a public dataset (such as in this case), or by having a Data Engineer setup and give us access to the organisation's data.\n",
"\n",
"In this case, we download the semantic scholar dataset from a public S3 bucket."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"MAX_CHUNK_COUNT = 1"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'Processing 1 out of the 6002 available chunks'"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import urllib.request\n",
"from random import shuffle\n",
"\n",
"manifest = (\n",
" urllib.request.urlopen(\n",
" \"https://s3-us-west-2.amazonaws.com/ai2-s2-research-public/open-corpus/2022-02-01/manifest.txt\"\n",
" )\n",
" .read()\n",
" .decode()\n",
") # a list of available chunks separated by '\\n' characters\n",
"\n",
"lines = manifest.split()\n",
"shuffle(lines)\n",
"chunks = lines[:MAX_CHUNK_COUNT]\n",
"\n",
"f\"Processing {len(chunks)} out of the {len(manifest.split())} available chunks\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Transform\n",
"\n",
"- Filter out non-English abstracts using `great_ai.utilities.predict_language`\n",
"- Project it to only keep the necessary components (text and labels), clean the textual content using `great_ai.utilities.clean`\n",
"- We will speed up processing using `great_ai.utilities.parallel_map`."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Spacy model en_core_web_sm not found locally, downloading...\n",
"Collecting en-core-web-sm==3.3.0\n",
" Downloading https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.3.0/en_core_web_sm-3.3.0-py3-none-any.whl (12.8 MB)\n",
" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 12.8/12.8 MB 3.6 MB/s eta 0:00:00\n",
"Requirement already satisfied: spacy<3.4.0,>=3.3.0.dev0 in ./.env/lib/python3.10/site-packages (from en-core-web-sm==3.3.0) (3.3.1)\n",
"Requirement already satisfied: tqdm<5.0.0,>=4.38.0 in ./.env/lib/python3.10/site-packages (from spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (4.64.0)\n",
"Requirement already satisfied: wasabi<1.1.0,>=0.9.1 in ./.env/lib/python3.10/site-packages (from spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (0.9.1)\n",
"Requirement already satisfied: srsly<3.0.0,>=2.4.3 in ./.env/lib/python3.10/site-packages (from spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (2.4.3)\n",
"Requirement already satisfied: typer<0.5.0,>=0.3.0 in ./.env/lib/python3.10/site-packages (from spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (0.4.1)\n",
"Requirement already satisfied: setuptools in ./.env/lib/python3.10/site-packages (from spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (59.6.0)\n",
"Requirement already satisfied: catalogue<2.1.0,>=2.0.6 in ./.env/lib/python3.10/site-packages (from spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (2.0.7)\n",
"Requirement already satisfied: spacy-loggers<2.0.0,>=1.0.0 in ./.env/lib/python3.10/site-packages (from spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (1.0.2)\n",
"Requirement already satisfied: blis<0.8.0,>=0.4.0 in ./.env/lib/python3.10/site-packages (from spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (0.7.8)\n",
"Requirement already satisfied: murmurhash<1.1.0,>=0.28.0 in ./.env/lib/python3.10/site-packages (from spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (1.0.7)\n",
"Requirement already satisfied: spacy-legacy<3.1.0,>=3.0.9 in ./.env/lib/python3.10/site-packages (from spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (3.0.9)\n",
"Requirement already satisfied: requests<3.0.0,>=2.13.0 in ./.env/lib/python3.10/site-packages (from spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (2.28.0)\n",
"Requirement already satisfied: numpy>=1.15.0 in ./.env/lib/python3.10/site-packages (from spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (1.23.0)\n",
"Requirement already satisfied: cymem<2.1.0,>=2.0.2 in ./.env/lib/python3.10/site-packages (from spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (2.0.6)\n",
"Requirement already satisfied: pathy>=0.3.5 in ./.env/lib/python3.10/site-packages (from spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (0.6.1)\n",
"Requirement already satisfied: jinja2 in ./.env/lib/python3.10/site-packages (from spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (3.1.2)\n",
"Requirement already satisfied: langcodes<4.0.0,>=3.2.0 in ./.env/lib/python3.10/site-packages (from spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (3.3.0)\n",
"Requirement already satisfied: pydantic!=1.8,!=1.8.1,<1.9.0,>=1.7.4 in ./.env/lib/python3.10/site-packages (from spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (1.8.2)\n",
"Requirement already satisfied: preshed<3.1.0,>=3.0.2 in ./.env/lib/python3.10/site-packages (from spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (3.0.6)\n",
"Requirement already satisfied: thinc<8.1.0,>=8.0.14 in ./.env/lib/python3.10/site-packages (from spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (8.0.17)\n",
"Requirement already satisfied: packaging>=20.0 in ./.env/lib/python3.10/site-packages (from spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (21.3)\n",
"Requirement already satisfied: pyparsing!=3.0.5,>=2.0.2 in ./.env/lib/python3.10/site-packages (from packaging>=20.0->spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (3.0.9)\n",
"Requirement already satisfied: smart-open<6.0.0,>=5.0.0 in ./.env/lib/python3.10/site-packages (from pathy>=0.3.5->spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (5.2.1)\n",
"Requirement already satisfied: typing-extensions>=3.7.4.3 in ./.env/lib/python3.10/site-packages (from pydantic!=1.8,!=1.8.1,<1.9.0,>=1.7.4->spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (4.2.0)\n",
"Requirement already satisfied: charset-normalizer~=2.0.0 in ./.env/lib/python3.10/site-packages (from requests<3.0.0,>=2.13.0->spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (2.0.12)\n",
"Requirement already satisfied: certifi>=2017.4.17 in ./.env/lib/python3.10/site-packages (from requests<3.0.0,>=2.13.0->spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (2022.6.15)\n",
"Requirement already satisfied: idna<4,>=2.5 in ./.env/lib/python3.10/site-packages (from requests<3.0.0,>=2.13.0->spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (3.3)\n",
"Requirement already satisfied: urllib3<1.27,>=1.21.1 in ./.env/lib/python3.10/site-packages (from requests<3.0.0,>=2.13.0->spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (1.26.9)\n",
"Requirement already satisfied: click<9.0.0,>=7.1.1 in ./.env/lib/python3.10/site-packages (from typer<0.5.0,>=0.3.0->spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (8.1.3)\n",
"Requirement already satisfied: MarkupSafe>=2.0 in ./.env/lib/python3.10/site-packages (from jinja2->spacy<3.4.0,>=3.3.0.dev0->en-core-web-sm==3.3.0) (2.1.1)\n",
"Installing collected packages: en-core-web-sm\n",
"Successfully installed en-core-web-sm-3.3.0\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"\u001b[38;5;226m2022-06-25 14:21:57,983 | WARNING | Limiting concurrency to 1 because there are only 1 chunks\u001b[0m\n",
"\u001b[38;5;39m2022-06-25 14:21:57,984 | INFO | Starting parallel map (concurrency: 1, chunk size: 1)\u001b[0m\n",
"\u001b[38;5;226m2022-06-25 14:21:57,984 | WARNING | Running in series, there is no reason for parallelism\u001b[0m\n",
"100%|██████████| 1/1 [03:26<00:00, 206.86s/it]\n"
]
}
],
"source": [
"from typing import List, Tuple\n",
"import json\n",
"import gzip\n",
"from great_ai import parallel_map, clean, is_english, predict_language\n",
"\n",
"\n",
"def preprocess_chunk(chunk_key: str) -> List[Tuple[str, List[str]]]:\n",
" # Extract\n",
" response = urllib.request.urlopen(\n",
" f\"https://s3-us-west-2.amazonaws.com/ai2-s2-research-public/open-corpus/2022-02-01/{chunk_key}\"\n",
" ) # a gzipped JSON Lines file\n",
"\n",
" decompressed = gzip.decompress(response.read())\n",
" decoded = decompressed.decode()\n",
" chunk = [json.loads(line) for line in decoded.split(\"\\n\") if line]\n",
"\n",
" # Transform\n",
" return [\n",
" (\n",
" clean(\n",
" f'{c[\"title\"]} {c[\"paperAbstract\"]} {c[\"journalName\"]} {c[\"venue\"]}',\n",
" convert_to_ascii=True,\n",
" ), # The text is cleaned to remove PDF extraction, web scraping, and other common artifacts\n",
" c[\"fieldsOfStudy\"],\n",
" ) # Create pairs of `(text, [...domains])`\n",
" for c in chunk\n",
" if c[\"fieldsOfStudy\"] and is_english(predict_language(c[\"paperAbstract\"]))\n",
" ]\n",
"\n",
"\n",
"preprocessed_chunks = parallel_map(preprocess_chunk, chunks)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"from itertools import chain\n",
"\n",
"preprocessed_data = list(chain(*preprocessed_chunks))\n",
"X, y = zip(\n",
" *preprocessed_data\n",
") # X is the input, y is the expected (ground truth) output"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Load\n",
"\n",
"Upload the dataset (or a part of it) to a central repository using `great_ai.add_ground_truth`. This step automatically tags each datapoint with a split label according to the ratios we set. Additional tags can be also given.\n",
"\n",
"#### Production-ready backend\n",
"\n",
"The MongoDB driver is automatically configured if `mongo.ini` exists with the following scheme:\n",
"\n",
"```ini\n",
"mongo_connection_string=mongodb://localhost:27017/\n",
"mongo_database=my_great_ai_db\n",
"```\n",
"> You can install MongoDB from [here](https://www.mongodb.com/docs/manual/installation) or [use it as a service](https://www.mongodb.com/cloud/atlas/register)\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"\u001b[38;5;226m2022-06-25 14:25:24,989 | WARNING | Environment variable ENVIRONMENT is not set, defaulting to development mode ‼️\u001b[0m\n",
"\u001b[38;5;39m2022-06-25 14:25:24,990 | INFO | Found credentials file (/data/projects/great_ai_example/mongo.ini), initialising MongodbDriver\u001b[0m\n",
"\u001b[38;5;39m2022-06-25 14:25:24,991 | INFO | Found credentials file (/data/projects/great_ai_example/mongo.ini), initialising LargeFileMongo\u001b[0m\n",
"\u001b[38;5;39m2022-06-25 14:25:24,992 | INFO | Settings: configured ✅\u001b[0m\n",
"\u001b[38;5;39m2022-06-25 14:25:24,993 | INFO | 🔩 tracing_database: MongodbDriver\u001b[0m\n",
"\u001b[38;5;39m2022-06-25 14:25:24,994 | INFO | 🔩 large_file_implementation: LargeFileMongo\u001b[0m\n",
"\u001b[38;5;39m2022-06-25 14:25:24,994 | INFO | 🔩 is_production: False\u001b[0m\n",
"\u001b[38;5;39m2022-06-25 14:25:24,995 | INFO | 🔩 should_log_exception_stack: True\u001b[0m\n",
"\u001b[38;5;39m2022-06-25 14:25:24,996 | INFO | 🔩 prediction_cache_size: 512\u001b[0m\n",
"\u001b[38;5;39m2022-06-25 14:25:24,997 | INFO | 🔩 dashboard_table_size: 50\u001b[0m\n",
"\u001b[38;5;226m2022-06-25 14:25:24,998 | WARNING | You still need to check whether you follow all best practices before trusting your deployment.\u001b[0m\n",
"\u001b[38;5;226m2022-06-25 14:25:24,998 | WARNING | > Find out more at https://se-ml.github.io/practices/\u001b[0m\n"
]
}
],
"source": [
"from great_ai import add_ground_truth\n",
"\n",
"add_ground_truth(X, y, train_split_ratio=0.8, test_split_ratio=0.2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Next: [Part 2](train.ipynb)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.10.4 ('.env': venv)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
},
"orig_nbformat": 4,
"vscode": {
"interpreter": {
"hash": "c1f394f9662881005685eeb18d8f9f77079b1b8b9a5ece1f825bfa01fcb7f52f"
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,2 @@
mongo_connection_string=mongodb://localhost:27017/ # change this
mongo_database=great_ai_example # this will be automatically created

File diff suppressed because it is too large Load diff

87
docs/hello_world.ipynb Normal file
View file

@ -0,0 +1,87 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"\u001b[38;5;226m2022-07-08 10:26:12,806 | WARNING | Environment variable ENVIRONMENT is not set, defaulting to development mode ‼️\u001b[0m\n",
"\u001b[38;5;226m2022-07-08 10:26:12,807 | WARNING | Cannot find credentials files, defaulting to using ParallelTinyDbDriver\u001b[0m\n",
"\u001b[38;5;226m2022-07-08 10:26:12,807 | WARNING | The selected tracing database (ParallelTinyDbDriver) is not recommended for production\u001b[0m\n",
"\u001b[38;5;226m2022-07-08 10:26:12,808 | WARNING | Cannot find credentials files, defaulting to using LargeFileLocal\u001b[0m\n",
"\u001b[38;5;39m2022-07-08 10:26:12,808 | INFO | Settings: configured ✅\u001b[0m\n",
"\u001b[38;5;39m2022-07-08 10:26:12,809 | INFO | 🔩 tracing_database: ParallelTinyDbDriver\u001b[0m\n",
"\u001b[38;5;39m2022-07-08 10:26:12,809 | INFO | 🔩 large_file_implementation: LargeFileLocal\u001b[0m\n",
"\u001b[38;5;39m2022-07-08 10:26:12,811 | INFO | 🔩 is_production: False\u001b[0m\n",
"\u001b[38;5;39m2022-07-08 10:26:12,811 | INFO | 🔩 should_log_exception_stack: True\u001b[0m\n",
"\u001b[38;5;39m2022-07-08 10:26:12,812 | INFO | 🔩 prediction_cache_size: 512\u001b[0m\n",
"\u001b[38;5;39m2022-07-08 10:26:12,813 | INFO | 🔩 dashboard_table_size: 20\u001b[0m\n",
"\u001b[38;5;226m2022-07-08 10:26:12,813 | WARNING | You still need to check whether you follow all best practices before trusting your deployment.\u001b[0m\n",
"\u001b[38;5;226m2022-07-08 10:26:12,814 | WARNING | > Find out more at https://se-ml.github.io/practices/\u001b[0m\n"
]
}
],
"source": [
"from great_ai import GreatAI\n",
"from asyncio import sleep\n",
"\n",
"\n",
"@GreatAI.create\n",
"async def hello_world(name: str) -> str:\n",
" await sleep(1)\n",
" return f\"Hello {name}!\""
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Trace(trace_id='27c5e581-ad6e-4d50-bb77-862dfb7963fb', created='2022-07-08T08:26:12.911975', original_execution_time_ms=1.933, logged_values={'arg:name:value': 'hi', 'arg:name:length': 2}, models=[], exception=None, output='Hello hi!', feedback=None, tags=['hello_world', 'online', 'development'])"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"await hello_world('hi')"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.10.4 ('.env': venv)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
},
"orig_nbformat": 4,
"vscode": {
"interpreter": {
"hash": "02dd6d3afbfa9fbbe1037d64ad9014965528a1ccad21929d6e72f466389a68ad"
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}

8
docs/hello_world.py Normal file
View file

@ -0,0 +1,8 @@
from great_ai import GreatAI
@GreatAI.create
def hello_world(name: str) -> str:
"""Learn more about GreatAI at https://github.com/schmelczer/great-ai"""
return f"Hello {name}!"

View file

@ -1,91 +0,0 @@
# How to call remote GreatAI instances
Microservices architecture (or [SOA](https://en.wikipedia.org/wiki/Service-oriented_architecture)) work well with ML applications. This is because their interfaces are usually very narrow, while the functionality provided is quite comprehensive. Hence, drawing the boundaries of responsibilities is more straightforward in the case of ML services than in the case of more traditional business applications. For this reason, it is common to have a tree of models (preferably wrapped in GreatAI instances) communicating with each other.
Although regular HTTP POST requests could be sent to each service's `/predict` endpoint, `great-ai` comes with two convenience functions: [call_remote_great_ai][great_ai.call_remote_great_ai] and [call_remote_great_ai_async][great_ai.call_remote_great_ai_async] to wrap this request. These provide you with some level of robustness and deserialisation.
!!! note "Inside notebooks"
The async variant, [call_remote_great_ai_async][great_ai.call_remote_great_ai_async], requires a running event loop while the synchronous variant disallows other running event-loops. Therefore, when running inside a Jupyter Notebook, always call [call_remote_great_ai_async][great_ai.call_remote_great_ai_async].
## Simple example
Let's create two processes: a server and a client.
### Server
```python title="server.py"
from great_ai import GreatAI
from asyncio import sleep
@GreatAI.create
async def slow_greeter(your_name):
await sleep(2)
return f'Hi {your_name}!'
```
> Run this in development mode by executing `great-ai server.py` or `python3 -m great_ai server.py` if you're on Windows and [`great-ai` is not in your `PATH`](/how-to-guides/install).
### Client
```python title="client.py"
from great_ai import call_remote_great_ai
names = ['Olivér', 'Balázs', 'András']
results = [
call_remote_great_ai(
'http://localhost:6060',
{
'your_name': name
}
).output #(1)
for name in names
]
print(results)
```
1. Only return the outputs, so we don't clutter up the terminal.
> Run this script as a regular Python script by executing `python3 client.py`.
![screenshot of result](/media/remote-sync.png){ loading=lazy }
As you can see, everything worked as expected. There is one way to improve it, though.
## An `async` example
Let's send multiple requests simultaneously to speed up the overall execution time. To do this, we will use the [call_remote_great_ai_async][great_ai.call_remote_great_ai_async] function.
??? note "Why is this possible?"
Note that in `server.py`, the inference function is declared `async`. This means that multiple "copies" of it can run at the same time in the same thread. Since there is no CPU bottleneck, the server has a quite large throughput (requests responded to per second), but its latency will stay around 2 seconds due to the async `sleep` command.
If your great-ai server is not `async`, higher throughput can be achieved by running multiple instances of it, either manually or by running it with multiple `uvicorn` workers like this: `ENVIRONMENT=production great-ai server.py --worker_count 4`
### Async client
```python title="async-client.py"
from great_ai import call_remote_great_ai_async
import asyncio
names = ['Olivér', 'Balázs', 'András']
async def main():
futures = [
call_remote_great_ai_async(
'http://localhost:6060',
{
'your_name': name
}
) for name in names
]
results = await asyncio.gather(*futures)
print([r.output for r in results])
asyncio.run(main())
```
> Replace `client.py` with this async client. Note that although async support is significantly more streamlined in recent Python versions, it still requires a bit more boilerplate than its synchronous counterpart.
![screenshot of result](/media/remote-async.png){ loading=lazy }
This also works and might be considerably quicker in some use cases.

View file

@ -1,102 +0,0 @@
# How to configure GreatAI
GreatAI aims to provide reasonable defaults wherever possible. The current configuration is always prominently displayed (and updated) on the dashboard and in the command-line start-up banner.
## Using [great_ai.configure][]
You can override any of the default settings by calling [great_ai.configure][]. If you don't call `configure`, the default settings are applied on the first call to most `great-ai` functions.
!!! warning
You must call [great_ai.configure][] before calling (or decorating with) any other `great-ai` function. However, importing other functions before calling [great_ai.configure][] is permitted.
```python title="configure-demo.py"
from great_ai import configure, RouteConfig
import logging
configure(
version='1.0.0',
log_level=logging.INFO,
seed=2,
should_log_exception_stack=False,
prediction_cache_size=0, #(1)
disable_se4ml_banner=True,
dashboard_table_size=200,
route_config=RouteConfig( #(2)
feedback_endpoints_enabled=False,
dashboard_enabled=False
)
)
```
1. Completely disable caching.
2. The unspecified routes are enabled by default.
## Using remote storage
The only aspect that cannot be automated is choosing the backing storage for the database and file storage.
Right now, you have 3 options for storing the models and large datasets: [LargeFileLocal][great_ai.large_file.LargeFileLocal], [LargeFileMongo][great_ai.large_file.LargeFileMongo], and [LargeFileS3][great_ai.large_file.LargeFileS3].
Without explicit configuration, [LargeFileLocal][great_ai.large_file.LargeFileLocal] is selected by default. This one still version-controls your files but it only stores them in a local path (which of course can be a remote volume attached by [NFS](https://en.wikipedia.org/wiki/Network_File_System){ target=_blank }, [HDFS](https://hadoop.apache.org/docs/r1.2.1/hdfs_design.html){ target=_blank }, etc.).
!!! important
If your working directory contains a `mongo.ini` or `s3.ini` file, an attempt is made to auto-configure [LargeFileMongo][great_ai.large_file.LargeFileMongo] or [LargeFileS3][great_ai.large_file.LargeFileS3] respectively.
To use [LargeFileMongo][great_ai.large_file.LargeFileMongo] or [LargeFileS3][great_ai.large_file.LargeFileS3] explicitly, configure them before calling any other `great-ai` function.
### S3-compatible
```toml title="s3.ini"
aws_region_name = eu-west-2
aws_access_key_id = MY_AWS_ACCESS_KEY # ENV:MY_AWS_ACCESS_KEY would also work
aws_secret_access_key = MY_AWS_SECRET_KEY
large_files_bucket_name = bucket-for-models
```
```python title="use-s3.py"
from great_ai.large_file import LargeFileS3
from great_ai import save_model
LargeFileS3.configure_credentials_from_file('s3.ini') #(1)
model = [4, 3]
save_model(model, 'my-model')
```
1. This line isn't strictly necessary because if `s3.ini` (or `mongo.ini`) is available in the current working directory, they are automatically used to configure their respective LargeFile implementations/databases.
??? note "Departing from AWS"
With the `aws_endpoint_url` argument, it is possible to use any other S3-compatible service such as [Backblaze](https://www.backblaze.com/){ target=_blank }. In that case, it would be `aws_endpoint_url=https://s3.us-west-002.backblazeb2.com`.
### GridFS
[GridFS](https://www.mongodb.com/docs/manual/core/gridfs/#:~:text=GridFS%20is%20a%20specification%20for,chunk%20as%20a%20separate%20document.){ target=_blank } specifies how to store files in MongoDB. The official MongoDB server and many compatible implementations support it.
```toml title="mongo.ini"
MONGO_CONNECTION_STRING=mongodb://localhost:27017 # this is the default value
# if `MONGO_CONNECTION_STRING` is specified, this default is overridden
MONGO_CONNECTION_STRING=ENV:MONGO_CONNECTION_STRING
MONGO_DATABASE=my-database # it is automatically created if it doesn't exist
```
```python title="use-mongo.py"
from great_ai.large_file import LargeFileMongo
from great_ai import save_model
LargeFileMongo.configure_credentials_from_file('mongo.ini')
model = [4, 3]
save_model(model, 'my-model')
```
!!! note "Simplifying config files"
You can combine `mongo.ini` or `s3.ini` with your application's config file because the unneeded keys are ignored by the `configure_credentials_from_file` method.
## Using a database
By default, a thread-safe version of [TinyDB](https://tinydb.readthedocs.io/en/latest/){ target=_blank } is utilised for saving the prediction traces into a local file. Unfortunately, for most production needs, this method is not suitable.
### MongoDB
Currently, only MongoDB is supported as a production-ready `TracingDatabase`. In order to use it, you have to either place a file named `mongo.ini` in your working directory or explicitly call either [MongoDbDriver.configure_credentials_from_file][great_ai.MongoDbDriver] or [MongoDbDriver.configure_credentials][great_ai.MongoDbDriver.configure_credentials].

View file

@ -1,120 +0,0 @@
# How to create a GreatAI service
The core value of `great-ai` lies in its [GreatAI][great_ai.GreatAI] class. To take advantage of it, you need to create an instance wrapping your code.
Let's say that you have the following greeter function:
```python title="greeter.py"
def my_greeter_function(your_name):
return f'Hi {your_name}!'
```
You can simply decorate (wrap) this function using the [@GreatAI.create][great_ai.GreatAI.create] factory.
```python title="greeter.py"
from great_ai import GreatAI
@GreatAI.create
def greeter(your_name):
return f'Hi {your_name}!'
```
??? info "Why not simply use `@GreatAI?`"
The purpose of [@GreatAI.create][great_ai.GreatAI.create] is simply to provide you with type-checking through MyPy, Pylance, and similar libraries. However, the overloading support for `__new__` is lacking in MyPy. Thus, a static factory method is used instead.
## With types
Even though it's not required by GreatAI, [type annotating your codebase](https://realpython.com/python-type-checking/){ target=_blank } can save you from lots of trivial mistakes; that's why it's highly advised. Simply add the expected types to your function's signature.
```python title="type_safe_greeter.py"
from great_ai import GreatAI
@GreatAI.create
def type_safe_greeter(your_name: str) -> str:
return f'Hi {your_name}!'
```
This not only allows you to statically type-check your code, but by default, GreatAI will check it during runtime as well using [typeguard](https://github.com/agronholm/typeguard){ target=_blank }.
## With async
Asynchronous code can result in immense performance gains in some instances. For example, you might rely on a third-party service, do database access, or [call a remote GreatAI instance](/how-to-guides/call-remote). In these cases, you can make your function `async` without any other changes.
```python title="async_greeter.py"
from great_ai import GreatAI
from asyncio import sleep
@GreatAI.create
async def async_greeter(your_name: str) -> str:
await sleep(2) # simulate IO-bound operation
return f'Hi {your_name}!'
```
## With decorators
GreatAI can decorate already decorated functions. The only restriction is that [@GreatAI.create][great_ai.GreatAI.create] must come last. There are two built-in decorators that you can use to customise your function, but you can use any third-party decorator as well.
### Using `@use_model`
If you have previously saved a model with [save_model][great_ai.save_model], you can inject it into your function by calling [@use_model][great_ai.use_model].
```python title="greeter_with_model.py"
from great_ai import GreatAI, use_model
@GreatAI.create
@use_model('name_of_my_model', version='latest') #(1)
def type_safe_greeter(your_name: str, model) -> str:
return f'Hi {your_name}!'
assert type_safe_greeter('Andras').output == 'Hi Andras'
```
1. By default, the parameter named `model` will be replaced by the loaded model. This behaviour can be customised by setting the `model_kwarg_name`. This way, even multiple models can be injected into a single function.
!!! important
You must call [@use_model][great_ai.use_model] before [@GreatAI.create][great_ai.GreatAI.create]. Note that decorators are applied starting from the bottom-most one. Feel free to use [@use_model][great_ai.use_model] in other places of the codebase, and it works equally well outside GreatAI services.
### Using `@parameter`
If you wish to turn off logging or specify custom validation for your parameters, you can use the [@parameter][great_ai.parameter] decorator.
!!! note
By default, all parameters that are not affected by an explicit [@parameter][great_ai.parameter] or [@use_model][great_ai.use_model] are automatically decorated with [@parameter][great_ai.parameter] when [@GreatAI.create][great_ai.GreatAI.create] is called.
```python title="greeter_with_validation.py"
from great_ai import GreatAI, use_model
@GreatAI.create
@parameter('your_name', disable_logging=True)
def type_safe_greeter(your_name: str, model) -> str:
return f'Hi {your_name}!'
assert type_safe_greeter('Andras').output == 'Hi Andras'
```
!!! important
You must call [@parameter][great_ai.parameter] before [@GreatAI.create][great_ai.GreatAI.create]. Note that decorators are applied starting from the bottom-most one. Feel free to use [@parameter][great_ai.parameter] in other places of the codebase, and it works equally well outside GreatAI services.
## Complex example
The following example summarises the options you have when instantiating a GreatAI service.
```python title="complex.py"
from great_ai import save_model, GreatAI, parameter, use_model, log_metric
save_model(4, 'secret-number') #(1)
@GreatAI.create
@parameter('positive_number', validate=lambda n: n > 0, disable_logging=True)
@use_model('secret-number', version='latest', model_kwarg_name='secret')
def add_number(positive_number: int, secret: int) -> int:
log_metric(
'log directly into the returned Trace',
positive_number * 2
)
return positive_number + secret
assert add_number(1).output == 5
```
1. Refer to [the configuration page](/how-to-guides/configure-service) for specifying where to store your models.

View file

@ -1,69 +0,0 @@
# How to manage training data
In order to simplify your training data management, `great-ai` provide two complementing approaches for inputting new data points.
## Upload data
At the start of your experiments' first iteration, after you've gathered suitable samples for training, you can call [great_ai.add_ground_truth][]. This automatically stores a timestamp and also allows you to assign tags to the data. Using these attributes, [great_ai.query_ground_truth][] can be called to get a filtered view of the training data.
!!! important "Train-test-validation splits"
It is a best practice to lock away the test split of your data that is only used for the final quality assessment. This prevents you from accidentally training on it or inadvertently tuning the model to have the highest accuracy metrics on the test split. This, of course, may lead to dubious results; hence, care must be taken to avoid it.
With [great_ai.add_ground_truth][], there is an option to tag the samples with `train`, `test`, and `validation` randomly, following a predefined distribution. This happens as soon as they're written in the database. Later, these can be queried by providing the name of the appropriate tags.
The nice thing about this is that the 'input-expected output' pairs are stored as traces. Thus, they behave exactly like regular prediction traces.
```python
from great_ai import add_ground_truth
add_ground_truth(
[1, 2],
['odd', 'even'],
tags='my_tag',
train_split_ratio=1, #(1)
test_split_ratio=1
)
```
1. Note that the ratios don't have to add up to 1. They are just weights. There is also a `validation_split_ratio` which is 0 by default.
```python
>>> from great_ai import query_ground_truth
>>> query_ground_truth('my_tag')
[Trace[str]({'created': '2022-07-12T18:36:12.825706',
'exception': None,
'feedback': 'odd', #(1)
'logged_values': {'input': 1}, #(2)
'models': [],
'original_execution_time_ms': 0.0,
'output': 'odd',
'tags': ['ground_truth', 'test', 'my_tag'], #(3)
'trace_id': '4fcf2ce6-a172-469d-94b2-874577655814'}),
Trace[str]({'created': '2022-07-12T18:36:12.825706',
'exception': None,
'feedback': 'even',
'logged_values': {'input': 2},
'models': [],
'original_execution_time_ms': 0.0,
'output': 'even',
'tags': ['ground_truth', 'train', 'my_tag'],
'trace_id': 'abee0671-beb9-4284-8c3b-c65e5836ce38'})]
```
1. Expected output. This can also be accessed through the `.output` property.
2. The input value is stored here.
3. Notice how `ground_truth` is always included as a tag when using [great_ai.add_ground_truth][].
## Get feedback
After the initial data gathering, end-to-end feedback can also be integrated into the dataset.
The scaffolded REST API contains endpoints for managing traces and their feedbacks.
![screenshot of swagger](/media/feedback.png){ loading=lazy }
When [great_ai.query_ground_truth][] is executed, it implicitly filters for traces that have feedback. Therefore, both the `ground_truth` and the `online` traces that have received feedback are returned. No matter the origin of the data, it can be accessed using the same API.
## Remove clutter
Traces can be deleted either through the REST API or by calling [great_ai.delete_ground_truth][]. The latter provides the same interface as [great_ai.query_ground_truth][] except it deletes the matched points.

View file

@ -1,53 +0,0 @@
# Installation guide
Provided you already have [Python3](https://www.python.org/downloads/){ target=_blank } (and pip) installed, simply execute:
```sh
pip install great-ai
```
> Python 3.7 or later is required.
This will work on all major operating systems.
## Google Colab
In order to use GreatAI in [Google Colab](https://colab.research.google.com){ target=_blank }, you need to downgrade `pyyaml` to a Colab compatible version. [See related StackOverflow question](https://stackoverflow.com/questions/69564817/typeerror-load-missing-1-required-positional-argument-loader-in-google-col){ target=_blank }.
```sh
pip install great-ai pyyaml==5.4.1
```
> This will make GreatAI work in Colab.
## Command-line tools
After installation, `great-ai` and `large-file` are available as commands. The former is required for deploying your application, while the latter lets you manage models and datasets from your terminal.
??? note "Snakes & kebabs"
The library is called `great-ai`; therefore, its command-line entry point is also called `great-ai`. However, Python module names cannot contain hyphens, that's why you have to `import great_ai` with an underscore. The `great-ai` CLI tool is also available as `python3 -m great_ai`.
To help with the confusion, a CLI executable called `great_ai` (and `large_file`) are also installed. Thus, if you prefer, you can always refer to GreatAI using its underscored name variant (`great_ai`).
!!! warning "Windows"
On Windows, you might encounter a similar warning from `pip`:
> `WARNING: The scripts great-ai.exe, great_ai.exe, large-file.exe and large_file.exe are installed in 'C:\Users\...\Scripts', which is not on PATH.`
> `Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.`
This means that `great-ai.exe` and `large-file.exe` are not in your `PATH`. Either add their containing directory ('C:\Users\...\Scripts' in this case) to your `PATH` or use `python3 -m great_ai` and `python3 -m great_ai.large_file` instead of the exe-s.
## Update
If you wish to update to the latest version, execute:
```sh
pip install --upgrade great-ai
```
## Bleeding edge
You can also install the latest (usually unreleased) version from GitHub.
```sh
pip install --upgrade git+https://github.com/schmelczer/great-ai.git
```
> Python 3.7 or later is required.

View file

@ -1,114 +0,0 @@
# How to use LargeFiles
The functions [save_model][great_ai.use_model] and [@use_model][great_ai.use_model] wrap LargeFile instances. Hence, besides configuring [LargeFile](/reference/large-file), users have few reasons to use LargeFiles directly.
## Motivation
Often, especially when working with data-heavy applications, large files can proliferate in a repository. Version controlling them is an obvious next step. However, GitHub's git LFS implementation [doesn't support deleting](https://docs.github.com/en/repositories/working-with-files/managing-large-files/removing-files-from-git-large-file-storage#git-lfs-objects-in-your-repository) large files, making it easy for them to eat-up the LFS quota and explode the size of your repos.
[DVC](https://dvc.org/) is a viable alternative; however, it requires users to learn to use one more CLI tool.
??? note "Using LargeFile-s directly (usually not needed)"
LargeFile doesn't require users to learn too much new. It is a nearly exact copy of Python's built-in `open()` function, with which users are undoubtedly already familiar.
## Simple example
```python
from great_ai.large_file import LargeFileS3
LargeFileS3.configure_credentials({
"aws_region_name": "your_region_like_eu-west-2",
"aws_access_key_id": "YOUR_ACCESS_KEY_ID",
"aws_secret_access_key": "YOUR_VERY_SECRET_ACCESS_KEY",
"large_files_bucket_name": "create_a_bucket_and_put_its_name_here",
})
# Creates a new version and deletes the older version
# leaving the three most recently used intact
with LargeFileS3("test.txt", "w", keep_last_n=3) as f:
for i in range(100000):
f.write('test\n')
# The latest version is returned by default
# but an optional `version` keyword argument can be provided as well
with LargeFileS3("test.txt", "r") as f: #(1)
print(f.readlines()[0])
```
1. The latest version is already in the local cache; no download is required.
### More details
`LargeFile` behaves like an opened file (in the background, it is a temp file after all). Binary reads and writes are supported along with the [different keywords `open()` accepts](https://docs.python.org/3/library/functions.html#open){ target=_blank }.
The local cache can be configured with these properties:
```python
LargeFileS3.cache_path = Path('.cache')
LargeFileS3.max_cache_size = "30 GB"
```
#### I only need a path
In case you only need a path to the (proxy of the) remote file, this pattern can be applied:
```python
path_to_model = LargeFileS3("folder-of-my-bert-model", version=31).get()
```
> This will first download the file/folder into your local cache folder. Then, it returns a `Path` object to the local version. Which can be turned into a string with `str(path_to_model)`.
The same approach works for uploads:
```python
LargeFileS3("folder-of-my-bert-model").push('path_to_local/folder_or_file')
```
> This way, both regular files and folders can be handled. The uploaded file is called **folder-of-my-bert-model**, the local name is ignored.
Lastly, all version of the remote object can be deleted by calling `LargeFileS3("my-file").delete()`. It will still reside in your local cache afterwards; its deletion will happen next time your local cache has to be pruned.
## From the command-line
The main reason for using the `large-file` or `python3 -m great_ai.large_file` commands is to upload or download models from the terminal. For example, when building a docker image, it is best practice to cache the referred models.
### Setup
Create an .ini file (or use *~/.aws/credentials*). It may look like this:
```ini
aws_region_name = your_region_like_eu-west-2
aws_access_key_id = YOUR_ACCESS_KEY_ID
aws_secret_access_key = YOUR_VERY_SECRET_ACCESS_KEY
large_files_bucket_name = my_large_files
```
### Upload some files
```sh
large-file --backend s3 --secrets secrets.ini \
--push my_first_file.json folder/my_second_file my_folder
```
> Only the filename is used as the S3 name; the rest of the path is ignored.
!!! important "Using MongoDB"
The possible values for `--backend` are `s3`, `mongo`, and `local`. The latter doesn't need credentials. It only versions and stores your files in a local folder. MongoDB, on the other hand, requires a `mongo_connection_string` and a `mongo_database` to be specified. For storing large files, it uses the [GridFS](https://www.mongodb.com/docs/manual/core/gridfs){ target=_blank } specification.
### Download some files to the local cache
This can be useful when building a Docker image, for example. This way, the files can already reside inside the container and need not be downloaded later.
```sh
large-file --backend s3 --secrets ~/.aws/credentials \
--cache my_first_file.json:3 my_second_file my_folder:0
```
> Versions may be specified by using `:`-s.
### Delete remote files
```sh
large-file --backend s3 --secrets ~/.aws/credentials \
--delete my_first_file.json
```

View file

@ -1,131 +0,0 @@
# How to perform prediction with GreatAI
After [creating a GreatAI service](/how-to-guides/create-service) by wrapping your prediction function, and optionally [configuring it](/how-to-guides/configure-service), it's time to do some prediction.
Let's take the following example:
```python title="greeter.py"
from great_ai import GreatAI
@GreatAI.create
def greeter(your_name: str) -> str:
return f'Hi {your_name}!'
```
## One-off prediction
Even though `greeter` is now an instance of [GreatAI][great_ai.GreatAI], you can continue using it as a regular function.
```python
>>> greeter('Bob')
Trace[str]({'created': '2022-07-11T14:31:46.183764',
'exception': None,
'feedback': None,
'logged_values': {'arg:your_name:length': 3, 'arg:your_name:value': 'Bob'},
'models': [],
'original_execution_time_ms': 0.0381,
'output': 'Hi Bob!',
'tags': ['greeter', 'online', 'development'],
'trace_id': '7c284fd7-7f0d-4464-b5f8-3ef126df34af'})
```
As you can see, the original return value is wrapped in a [Trace][great_ai.Trace] object (which is also persisted in your database of choice). You can access the original value under the `output` property.
## Online prediction
Likely, the main way you would like to expose your model is through an HTTP API. [@GreatAI.create][great_ai.GreatAI.create] scaffolds many REST API endpoints for your model and creates a [FastAPI](https://fastapi.tiangolo.com/){ target=_blank } app available under [GreatAI.app][great_ai.GreatAI]. This can be served using [uvicorn](https://www.uvicorn.org/){ target=_blank } or any other [ASGI server](https://asgi.readthedocs.io/en/latest/){ target=_blank }.
Since most ML code lives in [Jupyter](https://jupyter.org/){ target=_blank } notebooks, therefore, deploying a notebook containing the inference function is supported. To achieve this, `uvicorn` is wrapped by the `great-ai` command-line utility, which &mdash; among others &mdash; takes care of feeding a notebook into `uvicorn`. It also supports auto-reloading.
### In development
```sh
great-ai greeter.py
```
!!! success
Your model is accessible at [localhost:6060](http:/127.0.0.1:6060){ target=_blank }.
Some configuration options are also supported.
```sh
great-ai greeter.py --port 8000 --host 127.0.0.1 --timeout_keep_alive 10
```
??? note "More options"
For more options (but no Notebook support), simply use [uvicorn](https://www.uvicorn.org/){ target=_blank } for starting your app (available at `greeter.app`).
### In production
There are three main approaches for deploying a GreatAI service.
#### Manual deployment
The app is run in *production-mode* if the value of the `ENVIRONMENT` environment variable is set to `production`.
```sh
ENVIRONMENT=production great-ai greeter.py
```
Simply run `ENVIRONMENT=production great-ai deploy.ipynb` in the command-line of a production machine.
> This is the crudest approach; however, it might be fitting for some contexts.
#### Containerised deployment
Run the notebook directly in a container or create a service for it using your favourite container orchestrator.
```sh
docker run -p 6060:6060 --volume `pwd`:/app --rm \
schmelczera/great-ai deploy.ipynb
```
> You can replace ``pwd`` with the path to your code's folder.
#### Use a Platform-as-a-Service
Similar to the previous approach, your code will run in a container. However, instead of manually managing it, you can just choose from a plethora of PaaS providers (such as [AWS ECS](https://aws.amazon.com/ecs/){ target=_blank }, [DO App platform](https://www.digitalocean.com/products/app-platform){ target=_blank }, [MLEM](https://mlem.ai/){ target=_blank }, [Streamlit](https://streamlit.io/){ target=_blank }) that take a Docker image as a source and handle the rest of the deployment.
To this end, you can also create a custom Docker image. It is especially useful if you have third-party dependencies, such as [PyTorch](https://pytorch.org/){ target=_blank } or [TensorFlow](https://www.tensorflow.org/){ target=_blank }.
```Dockerfile
FROM schmelczera/great-ai:latest
# Remove this block if you don't have a requirements.txt
COPY requirements.txt ./
RUN pip install --no-cache-dir --requirement requirements.txt
# If you store your models in S3 or GridFS, it may be a
# good idea to cache them in the image so that you don't
# have to download it each time a container starts
RUN large-file --backend s3 --secrets s3.ini --cache my-domain-predictor
# Add your application code to the image
COPY . .
# The default ENTRYPOINT is great-ai; specify its argument using CMD
CMD ["deploy.ipynb"]
```
## Batch prediction
Processing larger amounts of data on a single machine is made easy by the [GreatAI][great_ai.GreatAI]'s [process_batch][great_ai.GreatAI.process_batch] method. This relies on multiprocessing ([parallel_map][great_ai.utilities.parallel_map.parallel_map.parallel_map]) to take full advantage of all available CPU-cores.
```python
>>> greeter.process_batch(['Alice', 'Bob'])
[Trace[str]({'created': '2022-07-11T14:36:37.119183',
'exception': None,
'feedback': None,
'logged_values': {'arg:your_name:length': 5, 'arg:your_name:value': 'Alice'},
'models': [],
'original_execution_time_ms': 0.1251,
'output': 'Hi Alice!',
'tags': ['greeter', 'online', 'development'],
'trace_id': '90ffa15f-e839-41c4-8e7a-3211168bc138'}),
Trace[str]({'created': '2022-07-11T14:36:37.166659',
'exception': None,
'feedback': None,
'logged_values': {'arg:your_name:length': 3, 'arg:your_name:value': 'Bob'},
'models': [],
'original_execution_time_ms': 0.0571,
'output': 'Hi Bob!',
'tags': ['greeter', 'online', 'development'],
'trace_id': 'f48e94c7-0815-48b3-a864-41349d3dae84'})]
```

230
docs/ideas.drawio Normal file

File diff suppressed because one or more lines are too long

View file

@ -1,54 +1,43 @@
<div style="display: flex; justify-content: space-between; align-items: center;">
<h1 style="margin: 0">Overview of GreatAI</h1>
<img src="media/logo.png" width=80>
</div>
[![PyPI version](https://badge.fury.io/py/great-ai.svg)](https://badge.fury.io/py/great-ai)
# Overview of GreatAI
[![Test](https://github.com/schmelczer/great-ai/actions/workflows/test.yml/badge.svg)](https://github.com/schmelczer/great-ai/actions/workflows/check.yml)
[![Quality Gate Status](https://sonar.scoutinscience.com/api/project_badges/measure?project=great-ai&metric=alert_status)](https://sonar.schmelczer.com/dashboard?id=great-ai)
[![Publish on PyPI](https://github.com/schmelczer/great-ai/actions/workflows/publish.yaml/badge.svg)](https://github.com/schmelczer/great-ai/actions/workflows/publish.yaml)
[![Publish on DockerHub](https://github.com/schmelczer/great-ai/actions/workflows/docker.yaml/badge.svg)](https://github.com/schmelczer/great-ai/actions/workflows/docker.yaml)
[![Downloads](https://pepy.tech/badge/great-ai/month)](https://pepy.tech/project/great-ai)
[![Docker Pulls](https://img.shields.io/docker/pulls/schmelczera/great-ai)](https://hub.docker.com/repository/docker/schmelczera/great-ai)
[![Test](https://github.com/schmelczer/great-ai/actions/workflows/test.yml/badge.svg)](https://github.com/schmelczer/great-ai/actions/workflows/test.yml)
[![Sonar line coverage](https://sonar.scoutinscience.com/api/project_badges/measure?project=great-ai&metric=coverage)](https://sonar.scoutinscience.com/dashboard?id=great-ai)
[![Sonar LoC](https://sonar.scoutinscience.com/api/project_badges/measure?project=great-ai&metric=ncloc)](https://sonar.scoutinscience.com/dashboard?id=great-ai)
Applying AI is becoming increasingly easier, but many case studies have shown that these applications are often deployed poorly. This may lead to suboptimal performance and to introducing [unintended biases](https://en.wikipedia.org/wiki/Weapons_of_Math_Destruction){ target=_blank }. GreatAI helps fix this by allowing you to ==easily transform your prototype AI code into production-ready software==.
Applying AI is becoming increasingly easier but many case studies have shown that these applications are often deployed poorly. This may lead to suboptimal performance and to introducing [unintended biases](https://en.wikipedia.org/wiki/Weapons_of_Math_Destruction){ target=_blank }. To extend the list of available solutions, ==GreatAI helps you easily transform your prototype AI code into production-ready software.==
??? quote "Case studies"
"There is a need to consider and adapt well established SE practices which have been ignored or had a very narrow focus in ML literature."
&mdash; [John et al.](https://ieeexplore.ieee.org/abstract/document/9359253){ target=_blank }
&mdash; [John et al.](https://ieeexplore.ieee.org/abstract/document/9359253)
"Finally, we have found that existing tools to aid Machine Learning development do not address the specificities of different projects, and thus, are seldom adopted by teams." &mdash; [Haakman et al.](https://link.springer.com/article/10.1007/s10664-021-09993-1){ target=_blank }
"Finally, we have found that existing tools to aid Machine Learning development do not address the specificities of different projects, and thus, are seldom adopted by teams." &mdash; [Haakman et al.](https://link.springer.com/article/10.1007/s10664-021-09993-1)
"Because a mature system might end up being (at most) 5% machine learning code and (at least) 95% glue code, it may be less costly to create a clean native solution rather than re-use a generic package." &mdash; [Sculley et al.](https://www.researchgate.net/profile/Todd-Phillips/publication/319769912_Hidden_Technical_Debt_in_Machine_Learning_Systems/links/61e716d68d338833e37a7fd6/Hidden-Technical-Debt-in-Machine-Learning-Systems.pdf)
"For example, practice 25 is very important for “Traceability", yet relatively weakly adopted. We expect that the results from this type of analysis can, in the future, provide useful guidance for practitioners in terms of aiding them to assess their rate of adoption for each practice and to create roadmaps for improving their processes. &mdash; [Serban et al.](https://dl.acm.org/doi/abs/10.1145/3382494.3410681?casa_token=uCFz0dtDR6gAAAAA:4_8OMJ-5njwopYkB1KSGAu9JfbNq4nfa8LRE0fj84ckjfo-GgtcYQivZTGxal3M4haoA8r_xwpw)
"Because a mature system might end up being (at most) 5% machine learning code and (at least) 95% glue code, it may be less costly to create a clean native solution rather than re-use a generic package." &mdash; [Sculley et al.](https://www.researchgate.net/profile/Todd-Phillips/publication/319769912_Hidden_Technical_Debt_in_Machine_Learning_Systems/links/61e716d68d338833e37a7fd6/Hidden-Technical-Debt-in-Machine-Learning-Systems.pdf){ target=_blank }
"For example, practice 25 is very important for "Traceability", yet relatively weakly adopted. We expect that the results from this type of analysis can, in the future, provide useful guidance for practitioners in terms of aiding them to assess their rate of adoption for each practice and to create roadmaps for improving their processes. &mdash; [Serban et al.](https://dl.acm.org/doi/abs/10.1145/3382494.3410681?casa_token=uCFz0dtDR6gAAAAA:4_8OMJ-5njwopYkB1KSGAu9JfbNq4nfa8LRE0fj84ckjfo-GgtcYQivZTGxal3M4haoA8r_xwpw){ target=_blank }
## Features
- [x] Save prediction traces of each prediction, including arguments and model versions
- [x] Save prediction traces of each prediction including arguments and model versions
- [x] Save feedback and merge it into a ground-truth database
- [x] Version and store models and data on shared infrastructure *(MongoDB GridFS, S3-compatible storage, shared volume)*
- [x] Version and store models and data on shared infrastructure *(MongoDB GridFS, S3-compatible storage, shared local-volume)*
- [x] Automatically scaffolded custom REST API (and OpenAPI schema) for easy integration
- [x] Input validation
- [x] Sensible cache-policy
- [x] Graceful error handling
- [x] Seamless support for both synchronous and asynchronous inference methods
- [x] Easy integration with remote GreatAI instances
- [x] Seamless support for both synchronous and `async` inference methods
- [x] Easy integration with other remote GreatAI instances
- [x] Built-in parallelisation (with support for multiprocessing, async, and mixed modes) for batch processing
- [x] Well-tested utilities for common NLP tasks (cleaning, language-tagging, sentence-segmentation, etc.)
- [x] A simple, unified configuration interface
- [x] Fully-typed API for [Pylance](https://github.com/microsoft/pylance-release){ target=_blank } and [MyPy](http://mypy-lang.org){ target=_blank } support
- [x] Fully-typed API for IntelliSense support
- [x] Auto-reload for development
- [x] Docker support for deployment
- [x] Deployable Jupyter Notebooks
- [x] Dashboard for online monitoring and analysing traces
- [x] Active support for Python 3.7, 3.8, 3.9, and 3.10
## Roadmap
- [ ] Prometheus & Grafana integration
- [ ] Well-tested feature extraction code for non-NLP data
- [ ] Support for direct file input
- [ ] Support for PostgreSQL
- [x] Dashboard for high-level overview and searching traces
- [ ] Shadow deployment
## Hello world
@ -56,39 +45,44 @@ Applying AI is becoming increasingly easier, but many case studies have shown th
pip install great-ai
```
```python title="demo.py"
```python title="hello-world.py"
from great_ai import GreatAI
@GreatAI.create #(1)
def greeter(name: str) -> str: #(2)
def hello_world(name: str) -> str: #(2)
return f"Hello {name}!"
```
1. `@GreatAI.create` wraps your `greeter` function with a `GreatAI` instance. The function will behave very similarly but:
1. `@GreatAI.create` wraps your `hello_world` function with a `GreatAI` instance. The function will behave very similarly but:
1. its return value becomes a `Trace[str]`,
2. it gets a `process_batch` method for supporting parallel execution,
3. and it can be deployed using the `great-ai` command-line tool.
2. [Typing functions](https://docs.python.org/3/library/typing.html){ target=_blank } is recommended in general, however, not required for GreatAI to work.
2. [Typing functions](https://docs.python.org/3/library/typing.html){ target=_blank } is recommended in general, however, not necessary for GreatAI to work.
??? note
In practice, `greeter` could be an inference function of some AI/ML application. But it could also just wrap a black-box solution of some SaaS. Either way, it is [imperative to have continuous oversight](https://digital-strategy.ec.europa.eu/en/library/ethics-guidelines-trustworthy-ai){ target=_blank } of the services you provide and the data you process, especially in the context of AI/ML applications.
In practice, `hello_world` could be an inference function of some AI/ML application. But it could also just wrap a black-box solution of some SaaS. Either ways, it is imperative to have continuos oversight of the services you provide and data you process.
```sh title="terminal"
great-ai demo.py
great-ai hello-world.py
```
> Navigate to [localhost:6060](http://127.0.0.1:6060) in your browser.
> Navigate to [localhost:6060](http://127.0.0.1:6060/) in your browser.
![demo screen capture](media/demo.gif){ loading=lazy }
<div style="display: flex; justify-content: space-evenly;" markdown>
![](media/hello-world-dashboard.png){ loading=lazy }
![](media/hello-world-docs.png){ loading=lazy }
</div>
!!! success
Your GreatAI service is ready for production use. Many of the [SE4ML best practices](https://se-ml.github.io){ target=_blank } are configured and implemented automatically. To have full control over your service and to understand what else you might need to do in your use case, continue reading this documentation.
Your GreatAI service is ready for production use. Many of the [SE4ML best-practices](https://se-ml.github.io/){ target=_blank } are configured and implemented automatically. To have full control over your service and to understand what else you might need to do in your use case, continue reading this documentation.
## Why is this GREAT?
![scope of GreatAI](media/scope-simple.drawio.svg)
![scope of GreatAI](scope-simple.drawio.svg)
GreatAI fits between the prototype and deployment phases of your (or your organisation's) AI development lifecycle. This is highlighted in blue in the diagram. Here, several best practices can be automatically implemented, aiming to achieve the following attributes:
GreatAI fits between the prototype and deployment phase of your (or your organisation's) AI development lifecycle. This is highlighted with blue in the diagram. Here, a number of best practices can be automatically implemented concerning the following 5 aspects:
- **G**eneral: use any Python library without restriction
- **R**obust: have error-handling and well-tested utilities out-of-the-box
@ -98,22 +92,13 @@ GreatAI fits between the prototype and deployment phases of your (or your organi
## Why GreatAI?
There are other existing solutions aiming to facilitate this phase. [Amazon SageMaker](https://aws.amazon.com/sagemaker){ target=_blank } and [Seldon Core](https://www.seldon.io/solutions/open-source-projects/core){ target=_blank } provide the most comprehensive suite of features. If you have the opportunity to use them, do that because they're great.
There are other, existing solutions aiming to facilitate this phase. [Amazon SageMaker](https://aws.amazon.com/sagemaker/){ target=_blank } and [Seldon Core](https://www.seldon.io/solutions/open-source-projects/core){ target=_blank } provide the most comprehensive suite of features. If you have the opportunity use those, do that because they're great.
However, research indicates that professionals rarely use them. This may be due to their inherent setup and operational complexity. ==GreatAI is designed to be as simple to use as possible.== Its straightforward, high-level API and sensible default configuration make it extremely easy to start using. Despite its relative simplicity over Seldon Core, it still implements many of the [SE4ML best practices](https://se-ml.github.io){ target=_blank }, and thus, can meaningfully improve your deployment without requiring prohibitively great effort.
However, research indicates that professionals rarely use them. This may be due to their inherent setup and operating complexity. GreatAI is designed to be as simple to use as possible. Its clear, high-level API and sensible default configuration makes it extremely easy to start using. Despite its relative simplicity over Seldon Core, it still implements many [best-practices](https://se-ml.github.io/){ target=_blank }, and thus, can meaningfully improve your deployment without requiring prohibitively large effort.
<div style="display: flex; justify-content: space-evenly; flex-wrap: wrap;" markdown>
[:fontawesome-brands-python: Find it on PyPI](https://pypi.org/project/great-ai){ .md-button .md-button--primary }
<div style="display: flex; justify-content: space-evenly;" markdown>
[:fontawesome-brands-python: Find it on PyPI](https://pypi.org/project/great-ai/){ .md-button .md-button--primary }
[:fontawesome-brands-docker: Find it on DockerHub](https://hub.docker.com/repository/docker/schmelczera/great-ai){ .md-button .md-button--primary }
[:fontawesome-solid-laptop-code: Check out the tutorial](/tutorial){ .md-button .md-button--primary }
</div>
## Production use
GreatAI has been battle-tested on the core platform services of [ScoutinScience](https://www.scoutinscience.com/){ target=_blank }.
[![ScoutinScience logo](media/scoutinscience.svg#only-light){ loading=lazy }
![ScoutinScience logo](media/scoutinscience-white.svg#only-dark){ loading=lazy }](https://www.scoutinscience.com/){ target=_blank }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 920 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 MiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 619 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

View file

@ -1,122 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Group_805" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 369.5 67.2" style="enable-background:new 0 0 369.5 67.2;" xml:space="preserve" fill="white">
<style type="text/css">
.st0{clip-path:url(#SVGID_00000084499246685778113730000005394386986623376315_);fill:#14C9C0;}
.st1{clip-path:url(#SVGID_00000084499246685778113730000005394386986623376315_);fill:#ffffff;}
</style>
<g>
<defs>
<rect id="SVGID_1_" width="369.5" height="67.2" />
</defs>
<clipPath id="SVGID_00000094609971218746498830000013677106623865950620_">
<use xlink:href="#SVGID_1_" style="overflow:visible;" />
</clipPath>
<path id="Path_549" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#14C9C0;" d="
M14.4,22.4l6.1-0.5c-0.3-2.3-1.7-4.4-3.7-5.6c-2-1.2-4.4-1.8-6.8-1.6c-2.4,0-4.6,0.8-6.5,2.2c-1.9,1.4-3,3.6-2.9,5.9
c-0.1,2.2,1,4.3,2.9,5.5c2,1.2,4.2,2.1,6.5,2.7c3.4,1.1,5.1,2.1,5.1,3.2c0,1-0.5,1.9-1.4,2.4c-0.9,0.6-1.9,0.9-3,0.8
c-1.1,0.1-2.1-0.2-3.1-0.7c-0.9-0.4-1.4-1.3-1.5-2.3L0,34.7c0.1,2.4,1.5,4.6,3.6,5.8c2.2,1.3,4.7,2,7.3,1.9c2.5,0,5-0.7,7.1-2.1
c2-1.3,3.2-3.6,3.2-6c0-2.1-1-4.1-2.8-5.2c-2-1.1-4-2-6.2-2.6c-1.3-0.4-2.5-0.9-3.8-1.4c-1.1-0.6-1.7-1.4-1.7-2.2
c0-0.9,0.4-1.8,1.1-2.4c0.7-0.6,1.6-0.9,2.5-0.8c0.9-0.1,1.8,0.2,2.6,0.6C13.8,20.7,14.3,21.5,14.4,22.4 M44.7,34.3
c-3.2,3-8.1,2.9-11.2-0.2c-3.1-3.1-3.2-8.2,0-11.4c0,0,0,0,0,0c3.1-3.1,8.1-3.2,11.2-0.2l3.9-4.2c-1.3-1.2-2.7-2.1-4.4-2.8
c-1.6-0.6-3.3-0.9-5-0.9c-7.6,0-13.8,6.2-13.8,13.8c0,7.6,6.2,13.8,13.8,13.8c1.7,0,3.4-0.3,5-0.9c1.6-0.6,3.1-1.6,4.4-2.8
L44.7,34.3z M124.6,20.5h7.8v-5.4h-7.8V0l-6.1,1.2v40.4h6.1L124.6,20.5z" />
<path id="Path_550" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#14C9C0;" d="
M143.7,19.2L143.7,19.2l0-4.2l-6.1,1.2v3.5h0v21.9h6.1V19.2z" />
<path id="Path_551" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#14C9C0;" d="
M243.6,19.2L243.6,19.2l0-4.1l-6.1,1.2v3.5h0v21.9h6.1L243.6,19.2z" />
<path id="Path_552" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#14C9C0;" d="
M110.7,29V15.1h0V15l-6.1,1.2v3.5h0v9.6c0,4-3.2,7.2-7.2,7.2c-4,0-7.2-3.2-7.2-7.2v-1.1l-6.1,1.2c0.3,7.3,6.4,13.1,13.7,12.8
C105.1,42,110.7,36.1,110.7,29 M84.2,19.8l6.1-1.2v-3.5l0,0V15l-6.1,1.2V19.8L84.2,19.8L84.2,19.8z" />
<path id="Path_553" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#14C9C0;" d="
M369.5,30.7c0.1-2.1-0.2-4.2-0.7-6.3c-0.7-2.1-1.8-4.1-3.3-5.7c-5.6-5.5-14.5-5.5-20,0c-2.6,2.5-4,6-4,9.6c-0.1,4.1,1.8,7.9,5,10.4
c1.4,1.1,2.9,2,4.6,2.5c1.7,0.6,3.5,0.9,5.3,0.9c4.7,0.1,9.2-2,12-5.7l-4.8-3.1c-1.1,1.4-2.6,2.4-4.3,3c-1.6,0.6-3.3,0.7-5,0.5
c-1.6-0.2-3.2-0.9-4.5-1.9c-1.3-1.1-2.3-2.5-2.7-4.2L369.5,30.7z M363.3,25.8h-16c0.5-1.7,1.6-3.1,3-4.1c1.4-1.1,3.2-1.7,5-1.7
c1.8,0,3.5,0.6,4.9,1.6C361.7,22.6,362.8,24.1,363.3,25.8 M323.8,34.1c-3.1-3.1-3.2-8.2,0-11.4c0,0,0,0,0,0
c3.1-3.1,8.1-3.2,11.2-0.2l3.9-4.2c-1.3-1.2-2.7-2.1-4.4-2.8c-1.6-0.6-3.3-0.9-5-0.9c-7.6,0-13.8,6.2-13.8,13.8c0,0,0,0,0,0
c0,7.6,6.2,13.8,13.8,13.8c1.7,0,3.4-0.3,5-0.9c1.6-0.6,3.1-1.6,4.4-2.8l-3.9-4.2C331.9,37.3,326.9,37.2,323.8,34.1 M303.9,41.6
h6.1v-14c0.1-2.8-0.7-5.6-2.2-7.9c-1.3-2-3.2-3.5-5.4-4.3c-2.1-0.9-4.4-1.1-6.7-0.7c-2.3,0.3-4.4,1.3-6.1,2.9V15l-6.1,1.2v0.4h0v25
h6.1v-14c-0.1-2,0.8-3.9,2.3-5.3c2.8-2.3,7-2.3,9.8,0c1.5,1.3,2.4,3.3,2.3,5.3L303.9,41.6z M255.4,30.7h22.3
c0.1-2.1-0.2-4.2-0.7-6.3c-0.7-2.1-1.8-4.1-3.3-5.7c-5.6-5.5-14.5-5.5-20,0c-2.6,2.5-4,6-4,9.6c-0.1,4.1,1.8,7.9,5,10.4
c1.4,1.1,2.9,2,4.6,2.5c1.7,0.6,3.5,0.9,5.3,0.9c4.7,0.1,9.2-2,12-5.7l-4.8-3.1c-1.1,1.4-2.6,2.4-4.3,3c-1.6,0.6-3.3,0.7-5,0.5
c-1.6-0.2-3.2-0.9-4.5-1.9C256.8,33.8,255.8,32.4,255.4,30.7 M255.6,25.8c0.5-1.7,1.6-3.1,3-4.1c1.4-1.1,3.2-1.7,5-1.7
c1.8,0,3.5,0.6,4.9,1.6c1.5,1,2.6,2.4,3,4.2H255.6z M217.1,34.1c-3.1-3.1-3.2-8.2,0-11.4c0,0,0,0,0,0c3.1-3.1,8.1-3.2,11.2-0.2
l3.9-4.2c-1.3-1.2-2.7-2.1-4.4-2.8c-1.6-0.6-3.3-0.9-5-0.9c-7.6,0-13.8,6.2-13.8,13.8c0,7.6,6.2,13.8,13.8,13.8
c1.7,0,3.4-0.3,5-0.9c1.6-0.6,3.1-1.6,4.4-2.8l-3.9-4.2C225.2,37.3,220.2,37.2,217.1,34.1 M190.4,22.8c0-0.9,0.4-1.8,1.1-2.4
c0.7-0.6,1.6-0.9,2.5-0.8c0.9-0.1,1.8,0.2,2.6,0.6c0.8,0.5,1.3,1.3,1.5,2.2l6.1-0.5c-0.3-2.3-1.7-4.4-3.7-5.6
c-2-1.2-4.4-1.8-6.8-1.6c-2.4,0-4.6,0.8-6.5,2.2c-1.9,1.4-3,3.6-2.9,5.9c-0.1,2.2,1,4.3,2.9,5.5c2,1.2,4.2,2.1,6.5,2.7
c3.4,1.1,5.1,2.1,5.1,3.2c0,1-0.5,1.9-1.4,2.4c-0.9,0.6-1.9,0.9-3,0.8c-1.1,0.1-2.1-0.2-3.1-0.7c-0.9-0.4-1.4-1.3-1.5-2.3l-6.1,0.2
c0.1,2.4,1.5,4.6,3.6,5.8c2.2,1.3,4.7,2,7.3,1.9c2.5,0,5-0.7,7.1-2.1c2-1.3,3.2-3.6,3.2-6c0-2.1-1-4.1-2.8-5.2c-2-1.1-4-2-6.2-2.6
c-1.3-0.4-2.5-0.9-3.8-1.4C191,24.4,190.4,23.6,190.4,22.8 M172,41.7h6.1v-14c0.1-2.8-0.7-5.6-2.2-7.9c-1.3-2-3.2-3.5-5.4-4.3
c-2.1-0.9-4.4-1.1-6.7-0.7c-2.3,0.3-4.4,1.3-6.1,2.9V15l-6.1,1.2v0.1h0v25.3h6.1v-14c-0.1-2,0.8-3.9,2.3-5.3c2.8-2.3,7-2.3,9.8,0
c1.5,1.3,2.4,3.3,2.3,5.3L172,41.7z" />
<path id="Path_554" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#ffffff;" d="
M94.5,25.3l-1-5.2l-14.1,2.8l0.3,1.4l-1.5,0.3c-0.6-2.3-1.9-4.3-3.5-5.9c-5.4-5.4-14.1-5.4-19.5,0c-2.6,2.6-4.1,6.1-4.1,9.8
c0,7.6,6.1,13.8,13.7,13.8c3.7,0,7.2-1.5,9.8-4.1c2.6-2.5,4.1-6.1,4.1-9.7c0-0.5,0-1-0.1-1.6l1.5-0.3l0.3,1.5L94.5,25.3z
M72.4,28.4c0.1,1.9-0.6,3.8-1.9,5.3c-3,3.1-8.1,3.2-11.2,0.2c-0.1-0.1-0.1-0.1-0.2-0.2c-1.3-1.5-1.9-3.3-1.9-5.3
c-0.1-1.9,0.6-3.8,1.9-5.3c1.5-1.6,3.5-2.5,5.7-2.4c2.1-0.1,4.2,0.8,5.6,2.4C71.8,24.6,72.4,26.5,72.4,28.4" />
<path id="Path_555" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#ffffff;" d="
M71.2,64.6h-4L66.4,67h-1.8l3.9-10.2h1.6L73.9,67h-1.9L71.2,64.6z M67.8,63.2h3L69.2,59L67.8,63.2z" />
<rect id="Rectangle_1378" x="78.1" y="56.8" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#ffffff;" width="1.8" height="10.2" />
<rect id="Rectangle_1379" x="84.4" y="62" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#ffffff;" width="3.7" height="1.4" />
<path id="Path_556" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#ffffff;" d="M92.5,67
V56.8H96c0.9-0.1,1.9,0.2,2.6,0.7c0.6,0.5,1,1.3,0.9,2.1c0,0.5-0.1,0.9-0.4,1.3c-0.3,0.4-0.7,0.7-1.1,0.9c0.5,0.1,1,0.4,1.3,0.8
c0.3,0.4,0.5,1,0.5,1.5c0.1,0.8-0.3,1.7-0.9,2.2c-0.8,0.6-1.7,0.8-2.6,0.8L92.5,67z M94.3,61.1H96c0.5,0,0.9-0.1,1.3-0.4
c0.3-0.3,0.5-0.7,0.5-1.1c0-0.4-0.1-0.8-0.4-1.1c-0.4-0.3-0.9-0.4-1.3-0.3h-1.7V61.1z M94.3,62.4v3.2h2c0.5,0,0.9-0.1,1.3-0.4
c0.6-0.6,0.6-1.7,0-2.3c-0.3-0.3-0.7-0.5-1.1-0.5L94.3,62.4z" />
<path id="Path_557" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#ffffff;" d="
M110.2,64.6h-4l-0.8,2.4h-1.9l3.9-10.2h1.6l3.9,10.2H111L110.2,64.6z M106.7,63.2h3l-1.5-4.2L106.7,63.2z" />
<path id="Path_558" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#ffffff;" d="
M122.3,64.4c0-0.4-0.2-0.8-0.5-1c-0.5-0.3-1.1-0.6-1.7-0.7c-0.7-0.2-1.3-0.5-2-0.8c-0.9-0.4-1.4-1.3-1.4-2.3c0-0.8,0.4-1.6,1-2
c0.8-0.6,1.7-0.8,2.6-0.8c0.7,0,1.3,0.1,1.9,0.4c0.5,0.2,1,0.6,1.3,1.1c0.3,0.5,0.5,1,0.5,1.6h-1.8c0-0.5-0.2-0.9-0.5-1.3
c-0.4-0.3-0.9-0.5-1.4-0.5c-0.5,0-1,0.1-1.4,0.4c-0.3,0.2-0.5,0.6-0.5,1c0,0.4,0.2,0.7,0.5,0.9c0.5,0.3,1.1,0.6,1.7,0.7
c0.7,0.2,1.3,0.5,1.9,0.8c0.4,0.3,0.8,0.6,1.1,1c0.2,0.4,0.3,0.9,0.3,1.4c0,0.8-0.3,1.6-1,2c-0.8,0.5-1.7,0.8-2.7,0.8
c-0.7,0-1.4-0.1-2-0.4c-0.6-0.2-1.1-0.6-1.4-1.1c-0.3-0.5-0.5-1.1-0.5-1.7h1.8c0,0.5,0.2,1,0.6,1.3c0.5,0.3,1.1,0.5,1.7,0.5
c0.5,0,1-0.1,1.4-0.4C122.1,65.1,122.3,64.8,122.3,64.4" />
<path id="Path_559" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#ffffff;" d="
M134.4,62.5h-4.2v3.1h4.9V67h-6.7V56.8h6.6v1.4h-4.9v2.8h4.2L134.4,62.5z" />
<path id="Path_560" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#ffffff;" d="
M139.5,67V56.8h3c0.8,0,1.7,0.2,2.4,0.6c0.7,0.4,1.3,1,1.6,1.7c0.4,0.8,0.6,1.7,0.6,2.5v0.5c0,0.9-0.2,1.8-0.6,2.6
c-0.4,0.7-0.9,1.3-1.6,1.7c-0.8,0.4-1.6,0.6-2.5,0.6L139.5,67z M141.3,58.2v7.4h1.2c0.8,0.1,1.6-0.3,2.1-0.9
c0.5-0.7,0.8-1.6,0.8-2.5v-0.6c0.1-0.9-0.2-1.8-0.7-2.5c-0.5-0.6-1.3-0.9-2.1-0.9L141.3,58.2z" />
<path id="Path_561" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#ffffff;" d="
M165.2,58.2H162V67h-1.8v-8.8h-3.2v-1.4h8.1V58.2z" />
<path id="Path_562" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#ffffff;" d="
M175.4,62.5h-4.2v3.1h4.9V67h-6.7V56.8h6.6v1.4h-4.9v2.8h4.2L175.4,62.5z" />
<path id="Path_563" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#ffffff;" d="
M188.2,63.7c0,1-0.5,1.9-1.2,2.6c-0.8,0.6-1.8,1-2.8,0.9c-0.8,0-1.5-0.2-2.2-0.6c-0.6-0.4-1.1-1-1.4-1.6c-0.4-0.8-0.5-1.6-0.5-2.5
v-1c0-0.9,0.2-1.7,0.5-2.6c0.3-0.7,0.8-1.3,1.5-1.7c0.7-0.4,1.4-0.6,2.2-0.6c1,0,2,0.3,2.7,0.9c0.7,0.7,1.2,1.6,1.2,2.6h-1.8
c0-0.6-0.2-1.2-0.6-1.6c-0.4-0.4-1-0.5-1.5-0.5c-0.7,0-1.4,0.3-1.8,0.8c-0.5,0.7-0.7,1.6-0.6,2.5v0.9c-0.1,0.9,0.2,1.8,0.6,2.5
c0.4,0.6,1,0.9,1.7,0.9c0.6,0,1.1-0.1,1.6-0.5c0.4-0.4,0.6-1,0.7-1.6L188.2,63.7z" />
<path id="Path_564" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#ffffff;" d="
M200.8,67H199v-4.5h-4.6V67h-1.8V56.8h1.8v4.3h4.6v-4.3h1.8L200.8,67z" />
<path id="Path_565" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#ffffff;" d="
M219.2,58.2H216V67h-1.8v-8.8h-3.2v-1.4h8.1V58.2z" />
<path id="Path_566" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#ffffff;" d="
M227.2,63.1h-2V67h-1.8V56.8h3.6c1-0.1,1.9,0.2,2.7,0.8c0.7,0.6,1,1.4,1,2.3c0,0.6-0.2,1.2-0.5,1.7c-0.4,0.5-0.8,0.8-1.4,1.1
l2.3,4.3V67h-1.9L227.2,63.1z M225.2,61.7h1.8c0.5,0,1-0.1,1.4-0.5c0.3-0.3,0.5-0.8,0.5-1.2c0-0.5-0.1-0.9-0.5-1.3
c-0.4-0.3-0.9-0.5-1.4-0.5h-1.9L225.2,61.7z" />
<path id="Path_567" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#ffffff;" d="
M241,64.6h-4l-0.8,2.4h-1.8l3.9-10.2h1.6l3.9,10.2h-1.9L241,64.6z M237.6,63.2h3L239,59L237.6,63.2z" />
<path id="Path_568" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#ffffff;" d="
M255.9,67h-1.8l-4.6-7.3V67h-1.8V56.8h1.8l4.6,7.3v-7.3h1.8L255.9,67z" />
<path id="Path_569" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#ffffff;" d="
M266.2,64.4c0-0.4-0.2-0.8-0.5-1c-0.5-0.3-1.1-0.6-1.7-0.7c-0.7-0.2-1.3-0.5-2-0.8c-0.9-0.4-1.4-1.3-1.4-2.3c0-0.8,0.4-1.6,1-2
c0.8-0.6,1.7-0.8,2.6-0.8c0.7,0,1.3,0.1,1.9,0.4c0.5,0.2,1,0.6,1.3,1.1c0.3,0.5,0.5,1,0.5,1.6h-1.8c0-0.5-0.2-0.9-0.5-1.3
c-0.4-0.3-0.9-0.5-1.4-0.5c-0.5,0-1,0.1-1.4,0.4c-0.3,0.2-0.5,0.6-0.5,1c0,0.4,0.2,0.7,0.5,0.9c0.5,0.3,1.1,0.6,1.7,0.7
c0.7,0.2,1.3,0.5,1.9,0.8c0.4,0.3,0.8,0.6,1.1,1c0.2,0.4,0.3,0.9,0.3,1.4c0,0.8-0.3,1.6-1,2c-0.8,0.5-1.7,0.8-2.7,0.8
c-0.7,0-1.4-0.1-2-0.4c-0.6-0.2-1.1-0.6-1.4-1.1c-0.3-0.5-0.5-1.1-0.5-1.7h1.8c0,0.5,0.2,1,0.6,1.3c0.5,0.3,1.1,0.5,1.7,0.5
c0.5,0,1-0.1,1.4-0.4C266,65.1,266.2,64.8,266.2,64.4" />
<path id="Path_570" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#ffffff;" d="
M278.3,62.7h-4.1V67h-1.8V56.8h6.5v1.4h-4.7v3h4.1L278.3,62.7z" />
<path id="Path_571" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#ffffff;" d="
M289.2,62.5H285v3.1h4.9V67h-6.7V56.8h6.6v1.4H285v2.8h4.2L289.2,62.5z" />
<path id="Path_572" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#ffffff;" d="
M298,63.1h-2V67h-1.8V56.8h3.6c1-0.1,1.9,0.2,2.7,0.8c0.7,0.6,1,1.4,1,2.3c0,0.6-0.2,1.2-0.5,1.7c-0.4,0.5-0.8,0.8-1.4,1.1l2.3,4.3
V67H300L298,63.1z M296,61.7h1.8c0.5,0,1-0.1,1.4-0.5c0.3-0.3,0.5-0.8,0.5-1.2c0-0.5-0.1-0.9-0.5-1.3c-0.4-0.3-0.9-0.5-1.4-0.5H296
L296,61.7z" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 12 KiB

View file

@ -1,121 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Group_805" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 369.5 67.2" style="enable-background:new 0 0 369.5 67.2;" xml:space="preserve">
<style type="text/css">
.st0{clip-path:url(#SVGID_00000084499246685778113730000005394386986623376315_);fill:#14C9C0;}
.st1{clip-path:url(#SVGID_00000084499246685778113730000005394386986623376315_);fill:#161615;}
</style>
<g>
<defs>
<rect id="SVGID_1_" width="369.5" height="67.2" />
</defs>
<clipPath id="SVGID_00000094609971218746498830000013677106623865950620_">
<use xlink:href="#SVGID_1_" style="overflow:visible;" />
</clipPath>
<path id="Path_549" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#14C9C0;" d="
M14.4,22.4l6.1-0.5c-0.3-2.3-1.7-4.4-3.7-5.6c-2-1.2-4.4-1.8-6.8-1.6c-2.4,0-4.6,0.8-6.5,2.2c-1.9,1.4-3,3.6-2.9,5.9
c-0.1,2.2,1,4.3,2.9,5.5c2,1.2,4.2,2.1,6.5,2.7c3.4,1.1,5.1,2.1,5.1,3.2c0,1-0.5,1.9-1.4,2.4c-0.9,0.6-1.9,0.9-3,0.8
c-1.1,0.1-2.1-0.2-3.1-0.7c-0.9-0.4-1.4-1.3-1.5-2.3L0,34.7c0.1,2.4,1.5,4.6,3.6,5.8c2.2,1.3,4.7,2,7.3,1.9c2.5,0,5-0.7,7.1-2.1
c2-1.3,3.2-3.6,3.2-6c0-2.1-1-4.1-2.8-5.2c-2-1.1-4-2-6.2-2.6c-1.3-0.4-2.5-0.9-3.8-1.4c-1.1-0.6-1.7-1.4-1.7-2.2
c0-0.9,0.4-1.8,1.1-2.4c0.7-0.6,1.6-0.9,2.5-0.8c0.9-0.1,1.8,0.2,2.6,0.6C13.8,20.7,14.3,21.5,14.4,22.4 M44.7,34.3
c-3.2,3-8.1,2.9-11.2-0.2c-3.1-3.1-3.2-8.2,0-11.4c0,0,0,0,0,0c3.1-3.1,8.1-3.2,11.2-0.2l3.9-4.2c-1.3-1.2-2.7-2.1-4.4-2.8
c-1.6-0.6-3.3-0.9-5-0.9c-7.6,0-13.8,6.2-13.8,13.8c0,7.6,6.2,13.8,13.8,13.8c1.7,0,3.4-0.3,5-0.9c1.6-0.6,3.1-1.6,4.4-2.8
L44.7,34.3z M124.6,20.5h7.8v-5.4h-7.8V0l-6.1,1.2v40.4h6.1L124.6,20.5z" />
<path id="Path_550" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#14C9C0;" d="
M143.7,19.2L143.7,19.2l0-4.2l-6.1,1.2v3.5h0v21.9h6.1V19.2z" />
<path id="Path_551" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#14C9C0;" d="
M243.6,19.2L243.6,19.2l0-4.1l-6.1,1.2v3.5h0v21.9h6.1L243.6,19.2z" />
<path id="Path_552" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#14C9C0;" d="
M110.7,29V15.1h0V15l-6.1,1.2v3.5h0v9.6c0,4-3.2,7.2-7.2,7.2c-4,0-7.2-3.2-7.2-7.2v-1.1l-6.1,1.2c0.3,7.3,6.4,13.1,13.7,12.8
C105.1,42,110.7,36.1,110.7,29 M84.2,19.8l6.1-1.2v-3.5l0,0V15l-6.1,1.2V19.8L84.2,19.8L84.2,19.8z" />
<path id="Path_553" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#14C9C0;" d="
M369.5,30.7c0.1-2.1-0.2-4.2-0.7-6.3c-0.7-2.1-1.8-4.1-3.3-5.7c-5.6-5.5-14.5-5.5-20,0c-2.6,2.5-4,6-4,9.6c-0.1,4.1,1.8,7.9,5,10.4
c1.4,1.1,2.9,2,4.6,2.5c1.7,0.6,3.5,0.9,5.3,0.9c4.7,0.1,9.2-2,12-5.7l-4.8-3.1c-1.1,1.4-2.6,2.4-4.3,3c-1.6,0.6-3.3,0.7-5,0.5
c-1.6-0.2-3.2-0.9-4.5-1.9c-1.3-1.1-2.3-2.5-2.7-4.2L369.5,30.7z M363.3,25.8h-16c0.5-1.7,1.6-3.1,3-4.1c1.4-1.1,3.2-1.7,5-1.7
c1.8,0,3.5,0.6,4.9,1.6C361.7,22.6,362.8,24.1,363.3,25.8 M323.8,34.1c-3.1-3.1-3.2-8.2,0-11.4c0,0,0,0,0,0
c3.1-3.1,8.1-3.2,11.2-0.2l3.9-4.2c-1.3-1.2-2.7-2.1-4.4-2.8c-1.6-0.6-3.3-0.9-5-0.9c-7.6,0-13.8,6.2-13.8,13.8c0,0,0,0,0,0
c0,7.6,6.2,13.8,13.8,13.8c1.7,0,3.4-0.3,5-0.9c1.6-0.6,3.1-1.6,4.4-2.8l-3.9-4.2C331.9,37.3,326.9,37.2,323.8,34.1 M303.9,41.6
h6.1v-14c0.1-2.8-0.7-5.6-2.2-7.9c-1.3-2-3.2-3.5-5.4-4.3c-2.1-0.9-4.4-1.1-6.7-0.7c-2.3,0.3-4.4,1.3-6.1,2.9V15l-6.1,1.2v0.4h0v25
h6.1v-14c-0.1-2,0.8-3.9,2.3-5.3c2.8-2.3,7-2.3,9.8,0c1.5,1.3,2.4,3.3,2.3,5.3L303.9,41.6z M255.4,30.7h22.3
c0.1-2.1-0.2-4.2-0.7-6.3c-0.7-2.1-1.8-4.1-3.3-5.7c-5.6-5.5-14.5-5.5-20,0c-2.6,2.5-4,6-4,9.6c-0.1,4.1,1.8,7.9,5,10.4
c1.4,1.1,2.9,2,4.6,2.5c1.7,0.6,3.5,0.9,5.3,0.9c4.7,0.1,9.2-2,12-5.7l-4.8-3.1c-1.1,1.4-2.6,2.4-4.3,3c-1.6,0.6-3.3,0.7-5,0.5
c-1.6-0.2-3.2-0.9-4.5-1.9C256.8,33.8,255.8,32.4,255.4,30.7 M255.6,25.8c0.5-1.7,1.6-3.1,3-4.1c1.4-1.1,3.2-1.7,5-1.7
c1.8,0,3.5,0.6,4.9,1.6c1.5,1,2.6,2.4,3,4.2H255.6z M217.1,34.1c-3.1-3.1-3.2-8.2,0-11.4c0,0,0,0,0,0c3.1-3.1,8.1-3.2,11.2-0.2
l3.9-4.2c-1.3-1.2-2.7-2.1-4.4-2.8c-1.6-0.6-3.3-0.9-5-0.9c-7.6,0-13.8,6.2-13.8,13.8c0,7.6,6.2,13.8,13.8,13.8
c1.7,0,3.4-0.3,5-0.9c1.6-0.6,3.1-1.6,4.4-2.8l-3.9-4.2C225.2,37.3,220.2,37.2,217.1,34.1 M190.4,22.8c0-0.9,0.4-1.8,1.1-2.4
c0.7-0.6,1.6-0.9,2.5-0.8c0.9-0.1,1.8,0.2,2.6,0.6c0.8,0.5,1.3,1.3,1.5,2.2l6.1-0.5c-0.3-2.3-1.7-4.4-3.7-5.6
c-2-1.2-4.4-1.8-6.8-1.6c-2.4,0-4.6,0.8-6.5,2.2c-1.9,1.4-3,3.6-2.9,5.9c-0.1,2.2,1,4.3,2.9,5.5c2,1.2,4.2,2.1,6.5,2.7
c3.4,1.1,5.1,2.1,5.1,3.2c0,1-0.5,1.9-1.4,2.4c-0.9,0.6-1.9,0.9-3,0.8c-1.1,0.1-2.1-0.2-3.1-0.7c-0.9-0.4-1.4-1.3-1.5-2.3l-6.1,0.2
c0.1,2.4,1.5,4.6,3.6,5.8c2.2,1.3,4.7,2,7.3,1.9c2.5,0,5-0.7,7.1-2.1c2-1.3,3.2-3.6,3.2-6c0-2.1-1-4.1-2.8-5.2c-2-1.1-4-2-6.2-2.6
c-1.3-0.4-2.5-0.9-3.8-1.4C191,24.4,190.4,23.6,190.4,22.8 M172,41.7h6.1v-14c0.1-2.8-0.7-5.6-2.2-7.9c-1.3-2-3.2-3.5-5.4-4.3
c-2.1-0.9-4.4-1.1-6.7-0.7c-2.3,0.3-4.4,1.3-6.1,2.9V15l-6.1,1.2v0.1h0v25.3h6.1v-14c-0.1-2,0.8-3.9,2.3-5.3c2.8-2.3,7-2.3,9.8,0
c1.5,1.3,2.4,3.3,2.3,5.3L172,41.7z" />
<path id="Path_554" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#161615;" d="
M94.5,25.3l-1-5.2l-14.1,2.8l0.3,1.4l-1.5,0.3c-0.6-2.3-1.9-4.3-3.5-5.9c-5.4-5.4-14.1-5.4-19.5,0c-2.6,2.6-4.1,6.1-4.1,9.8
c0,7.6,6.1,13.8,13.7,13.8c3.7,0,7.2-1.5,9.8-4.1c2.6-2.5,4.1-6.1,4.1-9.7c0-0.5,0-1-0.1-1.6l1.5-0.3l0.3,1.5L94.5,25.3z
M72.4,28.4c0.1,1.9-0.6,3.8-1.9,5.3c-3,3.1-8.1,3.2-11.2,0.2c-0.1-0.1-0.1-0.1-0.2-0.2c-1.3-1.5-1.9-3.3-1.9-5.3
c-0.1-1.9,0.6-3.8,1.9-5.3c1.5-1.6,3.5-2.5,5.7-2.4c2.1-0.1,4.2,0.8,5.6,2.4C71.8,24.6,72.4,26.5,72.4,28.4" />
<path id="Path_555" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#161615;" d="
M71.2,64.6h-4L66.4,67h-1.8l3.9-10.2h1.6L73.9,67h-1.9L71.2,64.6z M67.8,63.2h3L69.2,59L67.8,63.2z" />
<rect id="Rectangle_1378" x="78.1" y="56.8" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#161615;" width="1.8" height="10.2" />
<rect id="Rectangle_1379" x="84.4" y="62" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#161615;" width="3.7" height="1.4" />
<path id="Path_556" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#161615;" d="M92.5,67
V56.8H96c0.9-0.1,1.9,0.2,2.6,0.7c0.6,0.5,1,1.3,0.9,2.1c0,0.5-0.1,0.9-0.4,1.3c-0.3,0.4-0.7,0.7-1.1,0.9c0.5,0.1,1,0.4,1.3,0.8
c0.3,0.4,0.5,1,0.5,1.5c0.1,0.8-0.3,1.7-0.9,2.2c-0.8,0.6-1.7,0.8-2.6,0.8L92.5,67z M94.3,61.1H96c0.5,0,0.9-0.1,1.3-0.4
c0.3-0.3,0.5-0.7,0.5-1.1c0-0.4-0.1-0.8-0.4-1.1c-0.4-0.3-0.9-0.4-1.3-0.3h-1.7V61.1z M94.3,62.4v3.2h2c0.5,0,0.9-0.1,1.3-0.4
c0.6-0.6,0.6-1.7,0-2.3c-0.3-0.3-0.7-0.5-1.1-0.5L94.3,62.4z" />
<path id="Path_557" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#161615;" d="
M110.2,64.6h-4l-0.8,2.4h-1.9l3.9-10.2h1.6l3.9,10.2H111L110.2,64.6z M106.7,63.2h3l-1.5-4.2L106.7,63.2z" />
<path id="Path_558" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#161615;" d="
M122.3,64.4c0-0.4-0.2-0.8-0.5-1c-0.5-0.3-1.1-0.6-1.7-0.7c-0.7-0.2-1.3-0.5-2-0.8c-0.9-0.4-1.4-1.3-1.4-2.3c0-0.8,0.4-1.6,1-2
c0.8-0.6,1.7-0.8,2.6-0.8c0.7,0,1.3,0.1,1.9,0.4c0.5,0.2,1,0.6,1.3,1.1c0.3,0.5,0.5,1,0.5,1.6h-1.8c0-0.5-0.2-0.9-0.5-1.3
c-0.4-0.3-0.9-0.5-1.4-0.5c-0.5,0-1,0.1-1.4,0.4c-0.3,0.2-0.5,0.6-0.5,1c0,0.4,0.2,0.7,0.5,0.9c0.5,0.3,1.1,0.6,1.7,0.7
c0.7,0.2,1.3,0.5,1.9,0.8c0.4,0.3,0.8,0.6,1.1,1c0.2,0.4,0.3,0.9,0.3,1.4c0,0.8-0.3,1.6-1,2c-0.8,0.5-1.7,0.8-2.7,0.8
c-0.7,0-1.4-0.1-2-0.4c-0.6-0.2-1.1-0.6-1.4-1.1c-0.3-0.5-0.5-1.1-0.5-1.7h1.8c0,0.5,0.2,1,0.6,1.3c0.5,0.3,1.1,0.5,1.7,0.5
c0.5,0,1-0.1,1.4-0.4C122.1,65.1,122.3,64.8,122.3,64.4" />
<path id="Path_559" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#161615;" d="
M134.4,62.5h-4.2v3.1h4.9V67h-6.7V56.8h6.6v1.4h-4.9v2.8h4.2L134.4,62.5z" />
<path id="Path_560" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#161615;" d="
M139.5,67V56.8h3c0.8,0,1.7,0.2,2.4,0.6c0.7,0.4,1.3,1,1.6,1.7c0.4,0.8,0.6,1.7,0.6,2.5v0.5c0,0.9-0.2,1.8-0.6,2.6
c-0.4,0.7-0.9,1.3-1.6,1.7c-0.8,0.4-1.6,0.6-2.5,0.6L139.5,67z M141.3,58.2v7.4h1.2c0.8,0.1,1.6-0.3,2.1-0.9
c0.5-0.7,0.8-1.6,0.8-2.5v-0.6c0.1-0.9-0.2-1.8-0.7-2.5c-0.5-0.6-1.3-0.9-2.1-0.9L141.3,58.2z" />
<path id="Path_561" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#161615;" d="
M165.2,58.2H162V67h-1.8v-8.8h-3.2v-1.4h8.1V58.2z" />
<path id="Path_562" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#161615;" d="
M175.4,62.5h-4.2v3.1h4.9V67h-6.7V56.8h6.6v1.4h-4.9v2.8h4.2L175.4,62.5z" />
<path id="Path_563" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#161615;" d="
M188.2,63.7c0,1-0.5,1.9-1.2,2.6c-0.8,0.6-1.8,1-2.8,0.9c-0.8,0-1.5-0.2-2.2-0.6c-0.6-0.4-1.1-1-1.4-1.6c-0.4-0.8-0.5-1.6-0.5-2.5
v-1c0-0.9,0.2-1.7,0.5-2.6c0.3-0.7,0.8-1.3,1.5-1.7c0.7-0.4,1.4-0.6,2.2-0.6c1,0,2,0.3,2.7,0.9c0.7,0.7,1.2,1.6,1.2,2.6h-1.8
c0-0.6-0.2-1.2-0.6-1.6c-0.4-0.4-1-0.5-1.5-0.5c-0.7,0-1.4,0.3-1.8,0.8c-0.5,0.7-0.7,1.6-0.6,2.5v0.9c-0.1,0.9,0.2,1.8,0.6,2.5
c0.4,0.6,1,0.9,1.7,0.9c0.6,0,1.1-0.1,1.6-0.5c0.4-0.4,0.6-1,0.7-1.6L188.2,63.7z" />
<path id="Path_564" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#161615;" d="
M200.8,67H199v-4.5h-4.6V67h-1.8V56.8h1.8v4.3h4.6v-4.3h1.8L200.8,67z" />
<path id="Path_565" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#161615;" d="
M219.2,58.2H216V67h-1.8v-8.8h-3.2v-1.4h8.1V58.2z" />
<path id="Path_566" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#161615;" d="
M227.2,63.1h-2V67h-1.8V56.8h3.6c1-0.1,1.9,0.2,2.7,0.8c0.7,0.6,1,1.4,1,2.3c0,0.6-0.2,1.2-0.5,1.7c-0.4,0.5-0.8,0.8-1.4,1.1
l2.3,4.3V67h-1.9L227.2,63.1z M225.2,61.7h1.8c0.5,0,1-0.1,1.4-0.5c0.3-0.3,0.5-0.8,0.5-1.2c0-0.5-0.1-0.9-0.5-1.3
c-0.4-0.3-0.9-0.5-1.4-0.5h-1.9L225.2,61.7z" />
<path id="Path_567" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#161615;" d="
M241,64.6h-4l-0.8,2.4h-1.8l3.9-10.2h1.6l3.9,10.2h-1.9L241,64.6z M237.6,63.2h3L239,59L237.6,63.2z" />
<path id="Path_568" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#161615;" d="
M255.9,67h-1.8l-4.6-7.3V67h-1.8V56.8h1.8l4.6,7.3v-7.3h1.8L255.9,67z" />
<path id="Path_569" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#161615;" d="
M266.2,64.4c0-0.4-0.2-0.8-0.5-1c-0.5-0.3-1.1-0.6-1.7-0.7c-0.7-0.2-1.3-0.5-2-0.8c-0.9-0.4-1.4-1.3-1.4-2.3c0-0.8,0.4-1.6,1-2
c0.8-0.6,1.7-0.8,2.6-0.8c0.7,0,1.3,0.1,1.9,0.4c0.5,0.2,1,0.6,1.3,1.1c0.3,0.5,0.5,1,0.5,1.6h-1.8c0-0.5-0.2-0.9-0.5-1.3
c-0.4-0.3-0.9-0.5-1.4-0.5c-0.5,0-1,0.1-1.4,0.4c-0.3,0.2-0.5,0.6-0.5,1c0,0.4,0.2,0.7,0.5,0.9c0.5,0.3,1.1,0.6,1.7,0.7
c0.7,0.2,1.3,0.5,1.9,0.8c0.4,0.3,0.8,0.6,1.1,1c0.2,0.4,0.3,0.9,0.3,1.4c0,0.8-0.3,1.6-1,2c-0.8,0.5-1.7,0.8-2.7,0.8
c-0.7,0-1.4-0.1-2-0.4c-0.6-0.2-1.1-0.6-1.4-1.1c-0.3-0.5-0.5-1.1-0.5-1.7h1.8c0,0.5,0.2,1,0.6,1.3c0.5,0.3,1.1,0.5,1.7,0.5
c0.5,0,1-0.1,1.4-0.4C266,65.1,266.2,64.8,266.2,64.4" />
<path id="Path_570" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#161615;" d="
M278.3,62.7h-4.1V67h-1.8V56.8h6.5v1.4h-4.7v3h4.1L278.3,62.7z" />
<path id="Path_571" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#161615;" d="
M289.2,62.5H285v3.1h4.9V67h-6.7V56.8h6.6v1.4H285v2.8h4.2L289.2,62.5z" />
<path id="Path_572" style="clip-path:url(#SVGID_00000094609971218746498830000013677106623865950620_);fill:#161615;" d="
M298,63.1h-2V67h-1.8V56.8h3.6c1-0.1,1.9,0.2,2.7,0.8c0.7,0.6,1,1.4,1,2.3c0,0.6-0.2,1.2-0.5,1.7c-0.4,0.5-0.8,0.8-1.4,1.1l2.3,4.3
V67H300L298,63.1z M296,61.7h1.8c0.5,0,1-0.1,1.4-0.5c0.3-0.3,0.5-0.8,0.5-1.2c0-0.5-0.1-0.9-0.5-1.3c-0.4-0.3-0.9-0.5-1.4-0.5H296
L296,61.7z" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

118
docs/open_s3.md Normal file
View file

@ -0,0 +1,118 @@
# [open(S3)](https://pypi.org/project/open-large/)
Storing, versioning, and downloading files from S3 made as easy as using `open()` in Python. Caching included.
## Motivation
Oftentimes, especially when working with data-heavy applications, large files can proliferate in a repository. Version controlling them is an obvious next step, however, GitHub's git LFS implementation [doesn't support the deletion](https://docs.github.com/en/repositories/working-with-files/managing-large-files/removing-files-from-git-large-file-storage#git-lfs-objects-in-your-repository) of large files, making it easy for them to eat-up the LFS quota and explode the size of your repos.
## Solution
```
pip install open-large
```
### Simple example
```python
from great_ai.large_file import LargeFileS3
LargeFileS3.configure_credentials({
"aws_region_name": "your_region_like_eu-west-2",
"aws_access_key_id": "YOUR_ACCESS_KEY_ID",
"aws_secret_access_key": "YOUR_VERY_SECRET_ACCESS_KEY",
"large_files_bucket_name": "create_a_bucket_and_put_its_name_here",
})
# Creates a new version and deletes the older version leaving the 3 most recently used intact
with LargeFileS3("test.txt", "w", keep_last_n=3) as f:
for i in range(100000):
f.write('test\n')
# By default the latest version is returned
# but an optional `version` keyword argument can be provided as well
with LargeFileS3("test.txt", "r") as f:
print(f.readlines()[0])
```
> Automatically creates a file, writes to it, uploads it to S3, and then queries the most recent version of it.
> In this case, the latest version is already in the local cache, no download is required.
### More details
`LargeFile` behaves like an opened file (in the background it is a temp file after all). Binary reading and writing is supported along with the [different keywords](https://docs.python.org/3/library/functions.html#open) `open()` accepts.
The local cache can be configured with these properties:
```python
LargeFile.cache_path = Path('.cache')
LargeFile.max_cache_size = "30 GB"
```
#### I only need a path
In case you only need a path to the "remote" file, this pattern can be applied:
```python
path_to_model = LargeFile("folder-of-my-bert-model", version=31).get()
```
> This will first download the file/folder into your local cache folder. Then, it returns a `Path` object to the local version. Which can be turned into a string with `str(path_to_model)`.
The same approach works for uploads:
```python
LargeFile("folder-of-my-bert-model").push('path_to_local/folder_or_file')
```
> This way, both regular files and folders can be handled. The uploaded file is called **folder-of-my-bert-model**, the local name is ignored.
Lastly, all version of the remote object can be deleted by calling `LargeFile("my-file").delete()`. It will still reside in your local cache afterwards, its deletion will happen next time your local cache has to be pruned.
### Command-line example
The package can be used as a module from the command-line to give you more flexibility.
#### Setup
Create an .ini file (or use _~/.aws/credentials_). It may look like this:
```ini
aws_region_name = your_region_like_eu-west-2
aws_access_key_id = YOUR_ACCESS_KEY_ID
aws_secret_access_key = YOUR_VERY_SECRET_ACCESS_KEY
large_files_bucket_name = my_large_files
endpoint_url = this is optional, for backblaze, use this: https://s3.us-west-002.backblazeb2.com
```
> Just like in [example secrets](example_secrets.ini).
#### Print the expected options
```sh
python3 -m large_file --help
```
#### Upload some files
```sh
python3 -m large_file --backend s3 --secrets secrets.ini --push my_first_file.json folder/my_second_file my_folder
```
> Only the filename is used as the S3 name, the rest of the path is ignored.
#### Download some files to the local cache
This can be useful when building a Docker image for example. This way, the files can already reside inside the container and need not be downloaded later.
```sh
python3 -m large_file --backend s3 -secrets ~/.aws/credentials --cache my_first_file.json:3 my_second_file my_folder:0
```
> Versions may be specified by using `:`-s.
#### Delete remote files
```sh
python3 -m large_file --backend s3 --secrets ~/.aws/credentials --delete my_first_file.json
```

View file

@ -1,22 +1,6 @@
{% extends "base.html" %}
{% block extrahead %}
<meta property="og:title" content="">
<meta property="og:site_name" content="">
<meta property="og:url" content="">
<meta property="og:description" content="Transform your prototype AI code into production-ready software.">
<meta property="og:type" content="">
<meta property="og:image" content=https://great-ai.scoutinscience.com/media/og-image.png>
<style>
.jupyter-wrapper a {
color: var(--md-typeset-a-color) !important;
}
</style>
{% endblock %}
{% block content %}
{% if page.nb_url %}
<a href="{{ page.nb_url }}" title="Download Notebook" class="md-content__button md-icon">
{% include ".icons/material/download.svg" %}

1
docs/reference.md Normal file
View file

@ -0,0 +1 @@
::: great_ai.utilities.unique

View file

@ -1,70 +0,0 @@
# GreatAI reference
```python
from great_ai import *
```
## Core
::: great_ai.GreatAI
options:
filters: ['!__init__']
show_root_heading: true
::: great_ai.configure
options:
show_root_heading: true
::: great_ai.save_model
options:
show_root_heading: true
::: great_ai.use_model
options:
show_root_heading: true
::: great_ai.parameter
options:
show_root_heading: true
::: great_ai.log_metric
options:
show_root_heading: true
## Remote calls
::: great_ai.call_remote_great_ai
options:
show_root_heading: true
::: great_ai.call_remote_great_ai_async
options:
show_root_heading: true
## Ground-truth
::: great_ai.add_ground_truth
options:
show_root_heading: true
::: great_ai.query_ground_truth
options:
show_root_heading: true
::: great_ai.delete_ground_truth
options:
show_root_heading: true
## Tracing databases
::: great_ai.TracingDatabaseDriver
options:
show_root_heading: true
::: great_ai.MongoDbDriver
options:
show_root_heading: true
::: great_ai.ParallelTinyDbDriver
options:
show_root_heading: true

View file

@ -1,23 +0,0 @@
# LargeFile
```python
from great_ai.large_file import *
```
For more details about using LargeFiles, check out the [how to guide](/how-to-guides/large-file/).
::: great_ai.large_file.LargeFileLocal
options:
show_root_heading: true
::: great_ai.large_file.LargeFileS3
options:
show_root_heading: true
::: great_ai.large_file.LargeFileMongo
options:
show_root_heading: true
::: great_ai.large_file.LargeFileBase
options:
show_root_heading: true

View file

@ -1,45 +0,0 @@
# Utilities
```python
from great_ai.utilities import *
```
## NLP tools
Well-tested tools that can be used in production with confidence. The toolbox of feature-extraction functions is expected to grow to cover other domains as well.
::: great_ai.utilities.clean
::: great_ai.utilities.get_sentences
::: great_ai.utilities.language.predict_language
::: great_ai.utilities.language.english_name_of_language
::: great_ai.utilities.language.is_english
::: great_ai.utilities.evaluate_ranking.evaluate_ranking
## Parallel processing
Multiprocessing and multithreading-based parallelism with support for `async` functions. Its main purpose is to implement [great_ai.GreatAI.process_batch][], however, the parallel processing functions are also convenient for covering other types of mapping needs with a friendlier API than [joblib](https://joblib.readthedocs.io/en/latest/parallel.html){ target=_blank } or [multiprocess](https://pypi.org/project/multiprocess/){ target=_blank }.
::: great_ai.utilities.simple_parallel_map
options:
show_root_heading: true
::: great_ai.utilities.parallel_map.parallel_map
::: great_ai.utilities.threaded_parallel_map
options:
show_root_heading: true
## Composable parallel processing
Because both [threaded_parallel_map][great_ai.utilities.parallel_map.threaded_parallel_map.threaded_parallel_map] and [parallel_map][great_ai.utilities.parallel_map.parallel_map.parallel_map] have a streaming interface, it is easy to compose them and end up with, for example, a process for each CPU core with its own thread-pool or event-loop. Longer pipelines are also easy to imagine. The chunking methods help in these compositions.
::: great_ai.utilities.chunk
::: great_ai.utilities.unchunk
## Operations
::: great_ai.utilities.ConfigFile
options:
show_root_heading: true
::: great_ai.utilities.get_logger
options:
show_root_heading: true

View file

@ -1,25 +0,0 @@
# View models
::: great_ai.Trace
options:
show_root_heading: true
::: great_ai.RouteConfig
options:
show_root_heading: true
::: great_ai.ClassificationOutput
options:
show_root_heading: true
::: great_ai.MultiLabelClassificationOutput
options:
show_root_heading: true
::: great_ai.RegressionOutput
options:
show_root_heading: true
::: great_ai.SequenceLabelingOutput
options:
show_root_heading: true

View file

@ -1,4 +1,4 @@
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1181px" height="281px" viewBox="-0.5 -0.5 1181 281" content="&lt;mxfile&gt;&lt;diagram id=&quot;ccudx8biEBM-x_P_p45R&quot; name=&quot;Page-1&quot;&gt;3VrbcuI4EP0aqpKHpHzBBh7ZwMxuVVKTKnZrJo+yJUAV2/JIIsH79duy5bsJ5jqZ5QFLLaktqfucbgkG9kO4/cpRvH5imAQDy8DbgT0bWJZpWSN4KEmSScYjLVhxinWnUrCg/xItNLR0QzERtY6SsUDSuC70WRQRX9ZkiHP2Xu+2ZEH9rTFakZZg4aOgLf1OsVzrVeTLUvI/CV2t8zeb7iRrCVHeWa9ErBFm7xWRPR/YD5wxmZXC7QMJ1Obl+5KN+7KjtZgYJ5HsM0DPWMgkXxvBsFRdjVgEjz/WMgygZkJxySKp7WEOod5+n56CYBvua5WWNhHiK6J7DTORelllmJ7jV8JCInkCHTgJkKRv9X1H2nyrol+5QijoRXYvWM/lDQUbrXRguYHUS6tthftzw/KGO5EuegodzGG8LRuhtFLPZ05iznwiBMHKs5FEuWKYUqY769nacfCBWBX9JKARJtyGTu9rKskiRukWvgOK6mbw2AZ64kevECD/dcWV9NtGghqi5ULbyvnIVm+ES7L90A661Z642RANXDv34/cSBia8LJWtqxAwjNNt5x7orLBOnvyAipFXXlTl3smrs221cZZUa8+EU5gh4Vp4lOfbbc+3fpXn2zs93ytd8xxQmKW+byD/54YKKimLdiKhELem4PFccqP1zaMV+DVYJVrd7sQQ2AMFAQnYiqMQBscVK9baKubdB7Yl3ZI8CJnnwZE5Nuo4sjtwlPep4mh8BhiNr8H5w7bnO7/K84fX8vz5Ng4YR5nLg6VcFCpfyr7bnl10iDyhHqFOlLwNDTC4+Vlgs/ApiXzy20PGcRqhpwsy7oUgY9pXCj1NrGEk1gTnSrdU/ijVQO2l0lJqVJWkUmmGsl4AdtoAnpwbwOnQKecoqXSIGY2kqGh+VoLSEcZWnTuHY6Nhykxjadhiar1s7VwoQ/ybI4hfKjlMcS6a6O9iDANySslkAkCFCUZLwhWW05MNJv/D/HI0HO3PL4eXyi/N4edFuXE1lOeH7DPDvIXjkdvAsdUwYTZPPap6hm0ocsy6IqfJ+NnqWopOJR7LPS/xmO7+PGVGIMNIUkAAHpSroggjjvcnF4yvUERFmpwg9VqY2Zph0S+bOJgCrS4KbM7pZpGIKQ4pZEuwO8a3WGSFGXmD8u2hBHeNTKZ3MnzAYcAc7k9tCnI6d2pTsMKxx4FO8srLFT7cTV7HHtOPJT1z0sF67nVYb+zUSWTUDFy9Wa9xhHTNfqx3BDF1Xpo1fEYlBymy2SPySO4r/dFRXNYiL1dpdBtAj5o02bgTNB3ngbOgZrKfqv8RKlFD6iQ4/Qu+cErdIUmN2GbYBUILeOtz+jCuyco3T4/zp4x4p6+vlGVFIv3723aO+jsycuG/+yk5d6aOvLPTmdxzpJ3GR850wnEjEkvGw/QiAQ4Pku1PEuC4gTe+ShHuOEFY7Ygg/I36pJ9DgitlgfuT+IOVtgfBAwsYT6dimx4yiaX7VeTL9KOONJKzV1JpMQx3Pv1yqmddxZNOvdo7/bqhiOVl+H6ptp0Uy4u4syeYX+aiohWC70ZOd952aCy/MxuKep5gjonlXT8DHOwzOTowWaJNID+XsXqjZXcQP+2mZ8olXSJfpklN6NEoo2C5VglBfsmrD3AqK0iYzgo8IuRdzGEoMK44NNhe+n7nkxCpbrVG93XQTDquipxLXRUVv98fflV0IDH2+S3yGKbuCVijDVjzWoiFavlPiIzryv+T2PP/AA==&lt;/diagram&gt;&lt;/mxfile&gt;">
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1181px" height="281px" viewBox="-0.5 -0.5 1181 281" content="&lt;mxfile&gt;&lt;diagram id=&quot;ccudx8biEBM-x_P_p45R&quot; name=&quot;Page-1&quot;&gt;1VrbcuI4EP0aqpIHUr5gA49sYGa3KqlJFbs1k0fZFkYV2/JIIsH79duy5bsJBhySzUMstaS2pD59uiUzMu/D/XeG4u0j9XAwMjRvPzKXI8MwdMuAh5QkmcSaK4HPiJeJ9FKwJv9iJdSUdEc8zGsdBaWBIHFd6NIowq6oyRBj9K3ebUOD+ltj5OOWYO2ioC39STyxzaQzY1rK/8TE3+Zv1u151hKivLNaCd8ij75VROZqZN4zSkVWCvf3OJCbl+9LNu7bgdZiYgxHos8ANWMuknxt2IOlqmpEI3j8sRVhADUdihsaCWUPfQL19vvUFDjdMVepVMYViPlY9ZpkIvmyyjA1x++YhliwBDowHCBBXuv7jpT5/KJfuUIoqEV2L1jN5RUFO6V0ZNiBUEurbYX9e0fzhjFPF72ADvok3peNUPLl84nhmFEXc449iWwkUK4YppTpznq2dhwwEMuimwQk8jAzodPblgi8jlG6hW/gRXUzOHQHPb0HpxAg98VnUvpjJ0ANVnKubGW9Z6tXzATev2sH1WrO7WyIclwzx/Fb6QY6vCyVbasuoGmX284+EaywTpb8goqWV55l5c7Kq8t9tXGZVGtPmBGYIWZKeBbyzTbyjc9CvnkQ+U4JzSFcYZliX0Pu7x3hRBAaHfSEQtyagsNyyY3St4p8wDVYJfJvD/oQ2AMFAQ6oz1AIg+OKFWttFfMec7YN2eM8COnD+JE+0+p+ZHb4Ud6n6kezAdxodg3On7SRb30W8ifXQv5qHweUoQzyYCkbhRJL2f82sosOkcPlI1SJkrMjgQcwH8Rt1i7BkYs/12U2JAjuKexN+mpTd5CODbWrFfkm/ZNhSzD6gistmmavFt+GcT7LagSxLuezP8j5dPNKQazptR7iW+zlSvdE/CrVQO250lJqlJWkUmkGxV5UYLWpYD40FaRDF4yhpNIhpiQSvKL5SQpKIMyMOgtPZlrDlJnG0rDF1HrZ2vqgXPNvhiASyjQzZQze5JEu7tEgOxVUJODyMMFog5lkhfSM5OGvlql+KbqYTqbHc97JR+W8+uTr8oV2Nb7ID/4DE0aLEaZ2gxGMhgmzeapR1XN1Q5Gl1xVZzdiRra6l6FIKM+xhKUy3j+dOSwxZT5I6BPiDhCqKPMS84wkPZT6KCE8TJiRfCzPbUo/3y3BOJlOji0ybc7pZJ3zhhQQyONgd7UfMs8ISv0L59lSqvEp21TdBP+GAok+OJ0kFOQ2dJBWscO4RpZO88nKFDw+T17lXB+eSnj7vYD37Oqw3s+okMm0Grt6s1zjW2no/1juDmDov8hqYkWlG6tn0ATk4x0p/7ygukJGTq9S6DaBGzZts3Ok0HSeLQbxmfpyq/+Ey5UPydLr4C/55KXWHODVim2HXCK3hrU/pQ7smK988PqweM+JdvLwQmhWxcO9u29nu/5GRC/wep+QcTB15ZyeY7CHSTu09MF1wcIn4hrIwvdyAY4igx5MEOLh4O1emCGOGkSd3hGP2SlzcD5AApSxwfxE8GJfi4Sr2v/SS8PLrhiICl0H3udp2UQQuosWREPwxFxWtwDmeWt3Z1qkReKw3FPU8d5wTgbs+KJyMmfzQ7uEN2gUlcTd2/ALrXeGCWT8cei+76VkwQTbIFWkqEjokyohTbGUYz6+L1bFLxvKEqljuYC7GMYOhwJP81BB5/S+RJ9Bf/iuC6V0d6vOOaxnro65liu/3p1/LnEhnfb5FnsOvPb1Ka3uVPjgpHth1qJa/hMgYqvw9ibn6Dw==&lt;/diagram&gt;&lt;/mxfile&gt;">
<defs/>
<g>
<path d="M 331 140 L 379.63 140" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
@ -18,7 +18,7 @@
</div>
</div>
</foreignObject>
<text x="274" y="157" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
<text x="274" y="156" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
Preprocessed data
</text>
</switch>
@ -50,13 +50,13 @@
</g>
<path d="M 526 140 L 560.63 140" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 565.88 140 L 558.88 143.5 L 560.63 140 L 558.88 136.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 376 180 L 396 100 L 536 100 L 516 180 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 376 180 L 396 100 L 536 100 L 516 180 Z" fill="#1ba1e2" stroke="#006eaf" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 140px; margin-left: 377px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<div data-drawio-colors="color: #ffffff; " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(255, 255, 255); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<b>
<font style="font-size: 14px">
Exploration &amp;
@ -70,21 +70,21 @@
</div>
</div>
</foreignObject>
<text x="456" y="144" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
<text x="456" y="144" fill="#ffffff" font-family="Helvetica" font-size="12px" text-anchor="middle">
Exploration &amp;...
</text>
</switch>
</g>
<path d="M 639.5 190 L 639.92 240 Q 640 250 650 250 L 743.63 250" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/>
<path d="M 748.88 250 L 741.88 253.5 L 743.63 250 L 741.88 246.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 567 105 C 567 96.72 599.46 90 639.5 90 C 658.73 90 677.17 91.58 690.77 94.39 C 704.36 97.21 712 101.02 712 105 L 712 175 C 712 183.28 679.54 190 639.5 190 C 599.46 190 567 183.28 567 175 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 712 105 C 712 113.28 679.54 120 639.5 120 C 599.46 120 567 113.28 567 105" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 567 105 C 567 96.72 599.46 90 639.5 90 C 658.73 90 677.17 91.58 690.77 94.39 C 704.36 97.21 712 101.02 712 105 L 712 175 C 712 183.28 679.54 190 639.5 190 C 599.46 190 567 183.28 567 175 Z" fill="#1ba1e2" stroke="#006eaf" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 712 105 C 712 113.28 679.54 120 639.5 120 C 599.46 120 567 113.28 567 105" fill="none" stroke="#006eaf" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 143px; height: 1px; padding-top: 153px; margin-left: 568px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<div data-drawio-colors="color: #ffffff; " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(255, 255, 255); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<font style="font-size: 14px">
Trained models  &amp; prototype inference code
</font>
@ -92,7 +92,7 @@
</div>
</div>
</foreignObject>
<text x="640" y="157" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
<text x="640" y="156" fill="#ffffff" font-family="Helvetica" font-size="12px" text-anchor="middle">
Trained models  &amp; protot...
</text>
</switch>
@ -151,13 +151,13 @@
</text>
</switch>
</g>
<path d="M 740 60 L 760 0 L 950 0 L 930 60 Z" fill="#1ba1e2" stroke="#006eaf" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 740 60 L 760 0 L 950 0 L 930 60 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 208px; height: 1px; padding-top: 30px; margin-left: 741px;">
<div data-drawio-colors="color: #ffffff; " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(255, 255, 255); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;">
<b style="font-size: 14px">
Transforming into
<br/>
@ -169,7 +169,7 @@
</div>
</div>
</foreignObject>
<text x="845" y="34" fill="#ffffff" font-family="Helvetica" font-size="12px" text-anchor="middle">
<text x="845" y="34" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
Transforming into...
</text>
</switch>
@ -178,22 +178,22 @@
<path d="M 845 218.88 L 841.5 211.88 L 845 213.63 L 848.5 211.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 845 190 L 845 213.63" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 845 218.88 L 841.5 211.88 L 845 213.63 L 848.5 211.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 767.5 105 C 767.5 96.72 802.2 90 845 90 C 865.55 90 885.27 91.58 899.8 94.39 C 914.33 97.21 922.5 101.02 922.5 105 L 922.5 175 C 922.5 183.28 887.8 190 845 190 C 802.2 190 767.5 183.28 767.5 175 Z" fill="#1ba1e2" stroke="#006eaf" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 922.5 105 C 922.5 113.28 887.8 120 845 120 C 802.2 120 767.5 113.28 767.5 105" fill="none" stroke="#006eaf" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 767.5 105 C 767.5 96.72 802.2 90 845 90 C 865.55 90 885.27 91.58 899.8 94.39 C 914.33 97.21 922.5 101.02 922.5 105 L 922.5 175 C 922.5 183.28 887.8 190 845 190 C 802.2 190 767.5 183.28 767.5 175 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 922.5 105 C 922.5 113.28 887.8 120 845 120 C 802.2 120 767.5 113.28 767.5 105" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 153px; height: 1px; padding-top: 153px; margin-left: 769px;">
<div data-drawio-colors="color: #ffffff; " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(255, 255, 255); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;">
<font style="font-size: 14px">
Artifact combining the model with deplyoment best practices
Artifact combining the model with deplyoment best-practices
</font>
</div>
</div>
</div>
</foreignObject>
<text x="845" y="157" fill="#ffffff" font-family="Helvetica" font-size="12px" text-anchor="middle">
<text x="845" y="156" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
Artifact combining the mod...
</text>
</switch>

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

1781
docs/simple-mag/train.ipynb Normal file

File diff suppressed because one or more lines are too long

View file

@ -1,11 +0,0 @@
"How many years of software engineering experience do you have?","How many years of data science experience do you have?","Write reusable scripts for data cleaning and merging","Make datasets available on shared infrastructure","Use versioning for data, model, configurations and training scripts","Continuously monitor the behaviour of deployed models","Log production predictions with the models version and input data","Store models in a single format for ease of use","Equip with web interface, package image, provide REST API","Provide simple API for serving batch and real-time requests","Integration with existing data infrastructure","Querying, visualising and understanding metrics and event logging","Allow experimentation with the inference code","Keep the models API and documentation together","Parallelise feature extraction","Cache predictions","Async support for top-down chaining models"
"3","1","Strongly agree","Agree","Strongly agree","Neither agree nor disagree","Strongly agree","Strongly agree","Strongly agree","Strongly agree","Strongly agree","Strongly agree","Strongly agree","Strongly agree","Strongly agree","Neither agree nor disagree","Strongly agree"
"6","2","Neither agree nor disagree","Agree","Neither agree nor disagree","Agree","Agree","Neither agree nor disagree","Disagree","Strongly disagree","Disagree","Strongly disagree","Disagree","Strongly disagree","Disagree","Strongly agree","Strongly disagree"
"1","5","Strongly disagree","Disagree","Disagree","Strongly disagree","Disagree","Strongly disagree","Strongly disagree","Neither agree nor disagree","Strongly disagree","Disagree","Neither agree nor disagree","Agree","Strongly disagree","Strongly disagree","Disagree"
"3","3","Agree","Agree","Disagree","Neither agree nor disagree","Agree","Not applicable","Not applicable","Neither agree nor disagree","Agree","Disagree","Agree","Strongly agree","Not applicable","Neither agree nor disagree","Agree"
"1","7","Neither agree nor disagree","Disagree","Neither agree nor disagree","Disagree","Strongly disagree","Not applicable","Not applicable","Disagree","Strongly disagree","Disagree","Disagree","Neither agree nor disagree","Strongly disagree","Strongly agree","Not applicable"
"6","0","Strongly agree","Strongly agree","Agree","Neither agree nor disagree","Agree","Strongly agree","Agree","Strongly agree","Agree","Strongly agree","Disagree","Strongly agree","Agree","Strongly agree","Not applicable"
"2","2","Disagree","Neither agree nor disagree","Agree","Strongly agree","Neither agree nor disagree","Disagree","Strongly agree","Disagree","Disagree","Agree","Neither agree nor disagree","Neither agree nor disagree","Not applicable","Disagree","Strongly agree"
"1","2","Disagree","Neither agree nor disagree","Disagree","Agree","Disagree","Strongly disagree","Strongly agree","Strongly agree","Strongly disagree","Disagree","Agree","Strongly disagree","Not applicable","Strongly disagree","Strongly disagree"
"0","1","Strongly disagree","Disagree","Strongly disagree","Disagree","Strongly disagree","Disagree","Agree","Strongly disagree","Disagree","Strongly disagree","Disagree","Strongly disagree","Disagree","Disagree","Strongly disagree"
"7","1","Strongly agree","Strongly agree","Agree","Strongly agree","Agree","Agree","Strongly agree","Strongly disagree","Strongly agree","Not applicable","Agree","Agree","Strongly agree","Strongly agree","Agree"
1 How many years of software engineering experience do you have? How many years of data science experience do you have? Write reusable scripts for data cleaning and merging Make datasets available on shared infrastructure Use versioning for data, model, configurations and training scripts Continuously monitor the behaviour of deployed models Log production predictions with the model’s version and input data Store models in a single format for ease of use Equip with web interface, package image, provide REST API Provide simple API for serving batch and real-time requests Integration with existing data infrastructure Querying, visualising and understanding metrics and event logging Allow experimentation with the inference code Keep the model’s API and documentation together Parallelise feature extraction Cache predictions Async support for top-down chaining models
2 3 1 Strongly agree Agree Strongly agree Neither agree nor disagree Strongly agree Strongly agree Strongly agree Strongly agree Strongly agree Strongly agree Strongly agree Strongly agree Strongly agree Neither agree nor disagree Strongly agree
3 6 2 Neither agree nor disagree Agree Neither agree nor disagree Agree Agree Neither agree nor disagree Disagree Strongly disagree Disagree Strongly disagree Disagree Strongly disagree Disagree Strongly agree Strongly disagree
4 1 5 Strongly disagree Disagree Disagree Strongly disagree Disagree Strongly disagree Strongly disagree Neither agree nor disagree Strongly disagree Disagree Neither agree nor disagree Agree Strongly disagree Strongly disagree Disagree
5 3 3 Agree Agree Disagree Neither agree nor disagree Agree Not applicable Not applicable Neither agree nor disagree Agree Disagree Agree Strongly agree Not applicable Neither agree nor disagree Agree
6 1 7 Neither agree nor disagree Disagree Neither agree nor disagree Disagree Strongly disagree Not applicable Not applicable Disagree Strongly disagree Disagree Disagree Neither agree nor disagree Strongly disagree Strongly agree Not applicable
7 6 0 Strongly agree Strongly agree Agree Neither agree nor disagree Agree Strongly agree Agree Strongly agree Agree Strongly agree Disagree Strongly agree Agree Strongly agree Not applicable
8 2 2 Disagree Neither agree nor disagree Agree Strongly agree Neither agree nor disagree Disagree Strongly agree Disagree Disagree Agree Neither agree nor disagree Neither agree nor disagree Not applicable Disagree Strongly agree
9 1 2 Disagree Neither agree nor disagree Disagree Agree Disagree Strongly disagree Strongly agree Strongly agree Strongly disagree Disagree Agree Strongly disagree Not applicable Strongly disagree Strongly disagree
10 0 1 Strongly disagree Disagree Strongly disagree Disagree Strongly disagree Disagree Agree Strongly disagree Disagree Strongly disagree Disagree Strongly disagree Disagree Disagree Strongly disagree
11 7 1 Strongly agree Strongly agree Agree Strongly agree Agree Agree Strongly agree Strongly disagree Strongly agree Not applicable Agree Agree Strongly agree Strongly agree Agree

View file

@ -1,11 +0,0 @@
"I believe the use of GreatAI improves the quality of AI deployments.","I believe the use of GreatAI would increase my productivity.","I believe the use of GreatAI can lead to robust and trustworthy deployments.","Overall, I found GreatAI useful when working with AI.","I found the GreatAI easy to learn.","I found it is easy to employ GreatAI in practice.","I found it is easy to integrate GreatAI into an existing project.","Overall, I found GreatAI easy to use.","Assuming GreatAI is applicable to my task, I predict that I will use it on a regular basis in the future.","Overall, I intend to use the GreatAI in my personal or professional projects."
"7","7","7","7","7","7","7","7","7","7"
"7","7","6","7","5","6","7","6","7","7"
"4","4","5","6","6","5","4","4","5","4"
"6","7","6","7","7","6","7","6","7","6"
"7","7","6","7","4","5","5","5","6","6"
"6","6","6","6","6","6","6","6","6","6"
"6","6","6","4","5","7","6","6","7","7"
"7","6","6","6","4","5","3","5","5","6"
"4","5","5","5","7","2","3","3","3","3"
"7","7","7","7","5","6","5","6","7","7"
1 I believe the use of GreatAI improves the quality of AI deployments. I believe the use of GreatAI would increase my productivity. I believe the use of GreatAI can lead to robust and trustworthy deployments. Overall, I found GreatAI useful when working with AI. I found the GreatAI easy to learn. I found it is easy to employ GreatAI in practice. I found it is easy to integrate GreatAI into an existing project. Overall, I found GreatAI easy to use. Assuming GreatAI is applicable to my task, I predict that I will use it on a regular basis in the future. Overall, I intend to use the GreatAI in my personal or professional projects.
2 7 7 7 7 7 7 7 7 7 7
3 7 7 6 7 5 6 7 6 7 7
4 4 4 5 6 6 5 4 4 5 4
5 6 7 6 7 7 6 7 6 7 6
6 7 7 6 7 4 5 5 5 6 6
7 6 6 6 6 6 6 6 6 6 6
8 6 6 6 4 5 7 6 6 7 7
9 7 6 6 6 4 5 3 5 5 6
10 4 5 5 5 7 2 3 3 3 3
11 7 7 7 7 5 6 5 6 7 7

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

File diff suppressed because one or more lines are too long

View file

@ -1,18 +1,16 @@
\begin{abstract}
\absdiv{Background}
Despite its long-standing history, artificial intelligence (AI) has only recently started enjoying widespread industry awareness and adoption, partly thanks to the prevalence of libraries that accessibly expose state-of-the-art models. However, the transition from prototypes to production-ready AI applications is still a source of struggle across the industry. Even though professionals already have access to frameworks for deploying AI, case studies and developer surveys have found that many deployments do not follow best practices.
Despite its long-standing history, artificial intelligence (AI) has only recently started enjoying widespread industry awareness and adoption; partly thanks to the prevalence of accessible frameworks exposing state-of-the-art models through simple API-s. In order to achieve robust production deployments, the successful integration of AI components demands strong engineering methods. Concerningly, a tendency seems to be unfolding: even though industry professionals already have access to frameworks for deploying AI correctly and responsibly, case-studies and developer surveys have found that a large fraction of deployments do not follow best practices.
\absdiv{Objective}
This thesis investigates the causes of and presents a possible solution to the asymmetry between the adoption of libraries for \textit{applying} and those for \textit{deploying} AI. The potential solution is validated through designing a software framework called \textit{GreatAI}, which aims to facilitate \underline{G}eneral \underline{R}obust \underline{E}nd-to-end \underline{A}utomated \underline{T}rustworthy deployments while attempting to overcome the practical drawbacks of earlier similar tools, e.g., \textit{Seldon Core}, \textit{AWS SageMaker}, and \textit{TensorFlow Extended}.
\absdiv{Methods}
\textit{GreatAI} serves as a proxy for exploring the proposed design decisions; moreover, its initial focus is limited to the domain of natural language processing (NLP). Its design is created by applying the principles of design science methodology through iteratively shaping it in two case studies of a commercial NLP pipeline. Subsequently, interviews are conducted with ten practitioners to assess its applicability and generalisability.
This thesis sets out to investigate the reasons behind the asymmetry between the adoption of accessible AI libraries and existing reusable solutions to robust deployments. A software framework called \textit{GreatAI} is designed which aims to facilitate \underline{G}eneral \underline{R}obust \underline{E}nd-to-end \underline{A}utomated \underline{T}rustworthy AI deployments while attempting to overcome the practical drawbacks of its predecessors.
\absdiv{Method}
The utility of \textit{GreatAI} is validated using the principles of design science methodology through iteratively designing its API and implementation along with the text mining pipeline of a commercial product. Subsequently, interviews are conducted with practitioners for validating the generalisability of the design.
\absdiv{Results}
\textit{GreatAI} helps implement 33 best practices through an accessible interface. These target the transition between the prototype and production phases of the AI development lifecycle. Feedback from professional data scientists and software engineers showed that ease of use and functionality are equally important in deciding to adopt deployment technologies, and the proposed framework was rated positively in both dimensions.
To do.
\absdiv{Conclusions}
Increasing the overall maturity of industrial AI deployments by devising APIs with ease of adoption in mind is proved to be feasible. While \textit{GreatAI} mainly focuses on NLP, the results show that the development and deployment of trustworthy AI services, in general, can be assisted by frameworks prioritising easy adoption while still streamlining the implementation of various best practices.
To do.
\keywords{SE4ML \and AI engineering \and Trustworthy AI \and Deployment \and Text mining}
\end{abstract}

View file

@ -1,34 +1,56 @@
\chapter{Introduction}
Artificial intelligence (AI) techniques have recently started enjoying widespread industry awareness and adoption; the use of AI is increasingly prevalent in all sectors \cite{wirtz2019artificial,bosch2021engineering}. The reasons behind this are manifold \cite{jordan2015machine}, to name a few: recent breakthroughs in deep learning (DL), increased public awareness, an abundance of available data, access to powerful low-cost commodity hardware, education, but most interestingly, the rise of high-level libraries making ready-to-use state-of-the-art (SOTA) models easily available. The latter radically lowers the barrier of entry for applying AI --- and with that --- can help use cases in various areas.
Artificial intelligence (AI) techniques have recently started enjoying widespread industry awareness and adoption; the use of AI is increasingly prevalent in all sectors \cite{wirtz2019artificial,bosch2021engineering}. The reasons behind this are manifold \cite{jordan2015machine}, to name a few: recent breakthroughs in deep-learning, increased public awareness, abundance of available data, access to powerful low-cost commodity hardware, education, but most interestingly, the rise of high-level libraries making ready-to-use state-of-the-art (SOTA) models easily available. The latter practically abolishes the barrier of entry for applying AI --- and with that --- can help use-cases in many areas.
However, to achieve robust deployments, the successful integration of AI components into production-ready applications demands strong engineering methods \cite{serban2020adoption}. That is why it is as essential as ever to also focus on the quality of deployed models and software. For instance, the lack of a proper overview of data transformation steps may lead to suboptimal performance and to introducing unintended biases, which might contribute to the ever-increasing negative externality of misused AI \cite{o2016weapons}.
However, the successful integration of AI components into production-ready applications demands strong engineering methods in order to achieve robust deployments \cite{serban2020adoption}. That is why it is as important as ever to also focus on the quality and robustness of deployed models and software. For instance, the lack of a proper overview of the data transformation steps may lead to suboptimal performance and to introducing unintended biases which may contribute to the ever-increasing negative externality of misused AI \cite{o2016weapons}.
Concerningly, a peculiar tendency seems to be unfolding: even though industry professionals already have access to numerous frameworks for deploying AI correctly and responsibly, case studies and developer surveys have found that a considerable fraction of deployments does not follow best practices \cite{serban2020adoption,haakman2021ai,amershi2019software,de2019understanding,sculley2015hidden}. Utilising state-of-the-art machine learning (ML) models has become reasonably simple; applying them correctly is as intricate and nuanced as ever.
Concerningly, a peculiar tendency seems to be unfolding: even though industry professionals already have access to numerous frameworks for deploying AI correctly and responsibly, case-studies and developer surveys have found that a considerable fraction of deployments do not follow best practices \cite{serban2020adoption,haakman2021ai,amershi2019software,de2019understanding,sculley2015hidden}. Utilising state-of-the-art machine-learning (ML) models has become reasonably simple; applying them properly is as difficult and nuanced as ever.
This thesis sets out to investigate the reasons behind the apparent asymmetry between industry adoption of accessible AI-libraries and existing reusable solutions for robust AI deployments. It is hypothesised that the primary reason for the underwhelming adoption rate of best practices is the short supply of professionals equally proficient in the domains of both data science and software engineering. Nevertheless, even without their presence, practitioners could rely on frameworks to achieve some level of automation and maturity in their deployment processes. However, the barrier of entry for using such existing libraries is too high, especially when compared with the simplicity of AI-libraries.
This thesis sets out to investigate the reasons behind the apparent asymmetry between the adoption of accessible AI libraries and existing reusable solutions for robust AI deployments. It is hypothesised that the primary reason for the underwhelming adoption rate of best practices is the short supply or professionals equally proficient in the domains of both data science and software engineering. Nevertheless, even without their presence, practitioners could rely on frameworks for automated mature deployment processes. However, the barrier of entry for using such existing libraries is too high, especially when compared with the complexity of AI-libraries.
Therefore, we design a software framework called \href{https://github.com/schmelczer/great-ai}{\textit{GreatAI}} and present it in this thesis. The principal motivation behind the construction of \textit{GreatAI} is to facilitate the responsible and robust deployment of algorithms and models by designing a more accessible API in an attempt to overcome the practical drawbacks of other similar frameworks. Its name stands for its main aim: to assist easily creating \underline{G}eneral \underline{R}obust \underline{E}nd-to-end \underline{A}utomated, and \underline{T}rustworthy AI deployments.
Therefore, a software framework --- called \textit{GreatAI} --- is designed and its design is presented in this thesis. The principal motivation behind the construction of \textit{GreatAI} is to facilitate the responsible and robust deployment of algorithms and models by designing an accessible API in an attempt to overcome the practical drawbacks of other, similar frameworks. Its name stands for its main aim: to assist easily creating \underline{G}eneral \underline{R}obust \underline{E}nd-to-end \underline{A}utomated, and \underline{T}rustworthy AI deployments.
The utility of \textit{GreatAI} is examined and refined using the principles of design science methodology \cite{wieringa2014design} through iteratively designing its API and implementation in two case studies concerning the natural language processing (NLP) pipeline of a commercial product in collaboration with \href{https://scoutinscience.com/}{ScoutinScience B.V.} The goal of the aforementioned software suite is to evaluate technology-transfer opportunities in scientific publications. Subsequently, interviews are conducted with practitioners to validate the broader applicability and generalisability of the design.
The choice of case study subject is no coincidence; while working on the ScoutinScience Platform for the last two years, my colleagues and I have increasingly noticed the same recurring challenges in deploying and operating AI/ML pipelines. This has motivated me to pursue a general solution. Considering that the company's predominant field is NLP, the case studies, and hence, the prototype of \textit{GreatAI} will also focus primarily on deploying NLP models. Nonetheless, the motivation for creating a general solution for all AI/ML contexts remains and will be taken into account every step of the way.
The utility of \textit{GreatAI} is validated using the principles of design science methodology \cite{wieringa2014design} through iteratively designing its API and implementation along with the text mining pipeline for a commercial product in collaboration with ScoutinScience B.V. The goal of the aforementioned software suite is to evaluate technical transfer opportunities in scientific publications. Subsequently, interviews are conducted with practitioners for validating the generalisability of the design.
\section{Research questions}
We hypothesise that facilitating the adoption of AI deployment best practices is viable by finding less complex framework\footnote{The terms \textit{framework} and \textit{library} will be used interchangeably in this work stemming from their vague and often holistic differentiation.} designs that are easier to adopt in order to decrease the negative externality of misused AI. This paper investigates the hypothesis by answering the following research questions.
I hypothesise that facilitating the adoption of AI deployment best practices is viable by finding less complex framework designs which are easier to adopt in order to decrease the negative externality of misused AI. This paper is set out to investigate this hypothesis by answering the following research questions.
\begin{rqlist}
\item To what extent does the complexity of deploying AI hinder industrial applications?
\item What API design techniques can be effectively applied in order to decrease the complexity of correctly deploying AI services?
\item To what extent can \textit{GreatAI} automatically implement AI deployment best practices?
\item How suitable is the design of \textit{GreatAI} for helping to apply best practices in other contexts?
\item Does the complexity of AI deployment frameworks hinder industrial projects?
\item What is an effective way of decreasing the complexity of existing frameworks?
\item Does \textit{GreatAI}'s design improve the efficiency of working with AI while also introducing best practices?
\item Can the design of \textit{GreatAI} decrease the barrier of entry for applying best practices in other contexts?
\end{rqlist}
In this case, complexity refers to the difficulty faced by professionals (Data Scientists and Software Engineers alike) when integrating third-party libraries with their solutions. This could also be described as the barrier of entry or steepness of the learning curve. If the aforementioned hypothesis is correct, the adoption of best practices can be efficiently increased by decreasing this complexity. AI deployment best practices entail the technical steps that ought to be taken to achieve robust, end-to-end, automated, and trustworthy deployments. These are detailed in Section \ref{section:requirements}.
In this case, complexity is used to refer to the difficulty faced by professionals (data scientists and software engineers alike) when integrating libraries with their solutions. This could also be described as the barrier of entry or steepness of the learning curve. If the aforementioned hypothesis is correct, the adoption of best practices can be efficiently increased by decreasing this complexity.
The existence question regarding the problem itself (\textbf{RQ1}) is answered by reviewing the literature of more than 30 published case studies in Chapter \ref{chapter:background}. \textbf{RQ2} and \textbf{RQ3} are closely connected: the design and evaluation phases utilised to answer them follow an iterative process. They are examined in Chapters \ref{chapter:design} and \ref{chapter:case} respectively. The final evaluation step is to ascertain the capability of the framework's design to generalise beyond a single subdomain and problem context. This question, \textbf{RQ4}, is investigated through interviews with industry professionals in Chapter \ref{chapter:interviews}.
AI deployment best practices entail the technical steps that ought to be taken in order to achieve robust, end-to-end, automated, and trustworthy deployments. These are detailed in Section \ref{section:requirements}.
The existence question regarding the problem itself (\textbf{RQ1}) is answered by reviewing the literature of the more than 30 published case-studies. \textbf{RQ2} and \textbf{RQ3} are closely connected, the design and evaluation phases utilised to answer them follow an iterative process. They are examined in Chapter \ref{chapter:design} and Chapter \ref{chapter:case} respectively. The final evaluation step is to ascertain the capability of the framework design to generalise beyond a single subdomain and problem context. This question, \textbf{RQ4}, is investigated through interviews with industry professionals in Chapter \ref{chapter:interviews}.
\section{Requirements} \label{section:requirements}
The best practices (which will be referenced throughout the thesis) with which the \textit{GreatAI} design is concerned are a subset of those compiled by Serban et al. \cite{serban2020adoption}. The core requirements --- sets of covered best practices --- for a software solution that has the potential of improving our problem context are presented in the following along with some explanation and clarification of each of them.
\paragraph{General} Albeit not explicitly in the list of best practices, compatibility is vital in encouraging adoption. Large projects oftentimes 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 on the application.
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 programming language (PL) of the library should be its only non-general property. Fortunately, the de facto PL for data science is Python, hence, implementing the library in it should not significantly limit its applicability.
\paragraph{Robustness} in software development can be achieved by preparing the application to gracefully handle errors, 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 safe-guards as feasible. \textit{GreatAI} should support its clients in doing so.
\paragraph{End-to-end} In this case, it refers to end-to-end feedback. That is, feedback should be gathered on the real-world performance of the system, and this 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.
These requirements were chosen stemming from their general importance and potential to be mostly handled (implemented) by a software framework\footnote{The terms \textit{framework} and \textit{library} are used interchangeably in this work stemming from their vague and often holistic differentiation.}. 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 software engineering.
\newpage
\section{Structure}
The rest of the thesis is organised as follows: Chapter \ref{chapter:background} approaches the problem and the state-of-the-art from three perspectives: the recent trends of AI-library API designs, the experiences gained from practical applications, and a comparison of existing deployment options. Next, the methodology utilised for the subsequent chapters is described in Chapter \ref{chapter:methods}. The design cycle is broken into two chapters, Chapter \ref{chapter:design} and \ref{chapter:case}. The former clarifies the scope and describes the design principles, while the latter details the specifics of the practical case studies and the framework's interaction with them. The contributions of the novel design and obtained results are shown and further validated by conducting interviews with industry professionals in Chapter \ref{chapter:interviews}. The thesis is concluded in Chapter \ref{chapter:conclusion}.
The rest of the thesis is organised as follows: Chapter \ref{chapter:background} approaches the problem and the state-of-the-art from three perspectives: the trends of AI library API design, the experiences gained from practical applications, and a comparison of existing deployment options. Next, the methodology utilised for the subsequent chapters is described in Chapter \ref{chapter:methods}. The design cycle is broken into two chapters, Chapter \ref{chapter:design} and \ref{chapter:case}. The former clarifies the scope and describes the design principles, while the latter details the specifics of the practical use-case and the framework's interaction with it, and technological contributions of the novel design. The results are further validated by conducting interviews with industry professionals in Chapter \ref{chapter:interviews}. The thesis is concluded in Chapter \ref{chapter:conclusion}.

Some files were not shown because too many files have changed in this diff Show more