aitech-ium/IUM_11.CML.ipynb

32 KiB
Raw Blame History

Logo 1

Inżynieria uczenia maszynowego

11. Github actions [laboratoria]

Tomasz Ziętkiewicz (2023)

Logo 2

Github actions

Terminologia Github Actions

  • _Workflow - workflow odpowiada "Pipeline" z Jenkinsa.
  • _Event - zdarzenie, które odapala ("triggers") "Workflow". Np. wypchnięcie zmiany do repozytorium ("push"), utworzenie Pull requesta. Pełna lista
  • _Job - workflow składa się z jednego lub kilku zadań ("jobs"). Każde z nich może być wykonywane równolegle na innej maszynie (patrz "runner")
  • _Step - odpowiednik "Stage" z Jenkinsa - służu do grupowania "Actions"
  • _Action/command - odpowiednik "Step" z Jenkinsa - pojedyncze polecenie do wykonania, np. dodanie komentarze do Pull requesta, wykonanie polecenia systemowego itp.
  • _Runner - odpowiednik Jenkinsowego "Agent" - serwer, na którym mogą być wykonywane zadania ("jobs")
    • _Github-hosted runner - serwer utrzymywany przez Github (2-core CPU, 7 GB RAM, 14 GB SSD). Windows, Linux albo macOS
    • _Self-hosted runner - nasz własny serwer, z zinstalowaną aplikacją Github actions runner

Definicja workflow

  • Workflow definiuje się w plikach YAML(o rozszerzeniu *.yml albo *.yaml) umieszczonych w specjalnym folderze .github/workflows/ wewnątrz repozytorium
  • Pełna składnia jest opisana tutaj
  • Podstawowe pola:
    • name [opcjonalna] - nazwa, pod którą workflow/step będzie widoczny w UI. Domyślnie ścieżka do pliku yaml
    • on - definiuje kiedy workflow ma być odpalony
    • jobs - grupuje razem "zadania" do wykonania. Każde może być wykonane na innym "runnerze". Domyślnie wykonywane są równolegle (ale możemy definiować zależności między jobami, co powoduje wykonanie ich sekwencyjnie
    • runs-on - parametr joba, definujący na jakiej maszynie wirtualnej ma być uruchomiony (np. ubuntu-latest)
    • uses - umożliwia użycie gotowych akcji zdefiniowanych przez nas, albo przez innych użytkowników, np. -uses: actions/checkout@v2 spowoduje checkout plików z repozytorium
    • run - pozwala uruchomić dowolne (dostępne/zainstalowane) polecenie, np. python3 train.py
    • env - pozwala zdefiniować zmienne środowiskowe dostępne dla akcji lub skorzystać ze zmiennych ustawionych przez Github
!mkdir -p IUM_11/github-actions-hello
%cd IUM_11/github-actions-hello
!mkdir -p .github/workflows
/home/tomek/repos/aitech-ium/IUM_11/github-actions-hello
!git init
!git branch -M main
!git remote add origin git@github.com:TomekZet/ium-ga-hello.git
!git push -u origin main
Reinitialized existing Git repository in /home/tomek/repos/aitech-ium/IUM_11/github-actions-hello/.git/
Enumerating objects: 6, done.
Counting objects: 100% (6/6), done.
Delta compression using up to 4 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 780 bytes | 780.00 KiB/s, done.
Total 6 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:TomekZet/ium-ga-hello.git
 * [new branch]      main -> main
Branch 'main' set up to track remote branch 'main' from 'origin'.
%%writefile .github/workflows/workflow.yml
name: github-actions-hello
on: [push]
jobs:
  hello-job:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repo
        uses: actions/checkout@v2
      - name: Setup Python
        uses: actions/setup-python@v2.2.2
        with:
            python-version: '3.7'
      - run: python3 --version
Overwriting .github/workflows/workflow.yml
!git add .github/workflows/workflow.yml
!git commit -m "Github Actions Workflow"
!git push
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
Everything up-to-date

Zakładka actions na stronie repozytorium:

https://github.com/TomekZet/ium-ga-hello/actions

!ls -al .github/workflows
total 16
drwxr-sr-x 2 tomek tomek 4096 May 17 11:51 .
drwxr-sr-x 3 tomek tomek 4096 May 17 11:51 ..
-rw-r--r-- 1 tomek tomek  456 May 17 11:51 parametrized.yml
-rw-r--r-- 1 tomek tomek  305 May 17 12:01 workflow.yml

Ręczne wywoływanie

Workflow można również wywołać ręcznie, podając parametry. Więcej informacji np. tutaj: https://github.blog/changelog/2020-07-06-github-actions-manual-triggers-with-workflow_dispatch/

%%writefile .github/workflows/parametrized.yml
name: github-actions-hello-parametrized
on: 
    workflow_dispatch:
        inputs:
            input_text:
                description: 'Text to display'     
                required: true
                default: 'Hello World'
jobs:
    hello-job:
        runs-on: ubuntu-latest
        steps:
          - name: Checkout repo
            uses: actions/checkout@v2
          - name: Install dependencies
            run:
                sudo apt update;
                sudo apt install -y figlet
          - name: Write
            run:
                figlet "${{ github.event.inputs.input_text }}"
Overwriting .github/workflows/parametrized.yml
!git add -u .github/workflows
!git commit -m "just dispatch"
!git push
[main a98938d] just dispatch
 1 file changed, 6 deletions(-)
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 4 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (5/5), 411 bytes | 411.00 KiB/s, done.
Total 5 (delta 1), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To github.com:TomekZet/ium-ga-hello.git
   6c4a361..a98938d  main -> main

Zależności

Maszyny wirtualne ("runners"), na których uruchamiane są "joby" mają zainstalowany zbiór narzędzi. Przykładowa lista dla Ubuntu 20.04

Brakujące zależności można zainstalować, korzystając z:

  • akcji
  • poleceń systemowych takich jak apt install czy pip install uruchomionych poprzez run. Patrz przykład

Akcje

Za pomocą polecenia uses możemy używać przygotowanych wcześniej akcji. Mogą one pochodzić:

Akcje wykonywane w kontenerze Docker

Akcja może być wywołana w kontenerze Docker (pobranym z Docker hub albo zbudowanym z Dockerfile) W tym celu należy stworzyć własną akcję w pliku action.yaml i potem użyć jej w Workflow

%%writefile action.yml
name: 'Hello World'
description: 'Greet someone and record the time'
inputs:
  who-to-greet:  # id of input
    description: 'Who to greet'
    required: true
    default: 'World'
outputs:
  time: # id of output
    description: 'The time we greeted you'
runs:
  using: 'docker'
  image: 'Dockerfile'
  args:
    - ${{ inputs.who-to-greet }}
Overwriting action.yml
%%writefile Dockerfile
# Container image that runs your code
FROM ubuntu:latest
    
RUN apt update && apt install -y figlet

# Copies your code file from your action repository to the filesystem path `/` of the container
COPY entrypoint.sh /entrypoint.sh

VOLUME /github/workspace/

WORKDIR /github/workspace/

# Code file to execute when the docker container starts up (`entrypoint.sh`)
ENTRYPOINT ["/entrypoint.sh"]
Overwriting Dockerfile
%%writefile entrypoint.sh
#!/bin/sh -l

figlet "Hello $1" | tee figlet.txt
echo "Entrypoint invoked in: $PWD"
readlink -f figlet.txt
time=$(date)
echo "time=$time" >> $GITHUB_OUTPUT
Overwriting entrypoint.sh
!chmod +x entrypoint.sh
%%writefile .github/workflows/docker.yml
name: github-actions-hello-docker
on: 
    workflow_dispatch:
        inputs:
            input_text:
                description: 'Who to greet'     
                required: true
                default: 'World'
jobs:
    hello-job:
        runs-on: ubuntu-latest
        steps:
          - name: Checkout repo
            uses: actions/checkout@v2
          - name: Use docker action
            id: hello
            uses: ./
            with:
                who-to-greet: "${{ github.event.inputs.input_text }}"
      # Use the output from the `hello` step
          - name: Get the output time
            run: echo "The time was ${{ steps.hello.outputs.time }}"
                   
         
Overwriting .github/workflows/docker.yml
!git add .github entrypoint.sh Dockerfile
!git commit -m "Fix path"
!git push
[main 22a5094] Fix path
 1 file changed, 1 insertion(+)
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 4 threads
Compressing objects: 100% (5/5), done.
Writing objects: 100% (5/5), 570 bytes | 570.00 KiB/s, done.
Total 5 (delta 1), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To github.com:TomekZet/ium-ga-hello.git
   97c7272..22a5094  main -> main

Archiwizowanie artefaktów

https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts

Do archiwizowania artefaktów służy akcja "upload-artifact":

      - name: Archive artifacts
        uses: actions/upload-artifact@v3
        with:
          name: figlet-output
          path: figlet.txt
%%writefile .github/workflows/docker-artifact.yml
name: github-actions-hello-docker-artifact
on: 
    workflow_dispatch:
        inputs:
            input_text:
                description: 'Who to greet'     
                required: true
                default: 'World'
jobs:
    hello-job:
        name: "Do all the hard stuff"
        runs-on: ubuntu-latest
        steps:
          - name: Checkout repo
            uses: actions/checkout@v2
          - name: Use docker action
            id: hello
            uses: ./
            with:
                who-to-greet: "${{ github.event.inputs.input_text }}"
          # Use the output from the `hello` step
          - name: Get the output time
            run: echo "The time was ${{ steps.hello.outputs.time }}"  > time.txt
          - name: Archive artifacts
            uses: actions/upload-artifact@v3
            with:
                name: figlet-output
                path: |
                    figlet.txt
                    time.txt
    publish:
        name: "Publish as github comment"
        runs-on: ubuntu-latest
        needs: hello-job
        steps:
          - uses: actions/checkout@v3
          #We need to download the artifact first, jobs do not share workflow files
          - name: get-artifact  
            uses: actions/download-artifact@v3
            with:
                name: figlet-output
          - name: display_artifact_contents
            run:
                cat time.txt ; tr ' ' '#' < figlet.txt
Overwriting .github/workflows/docker-artifact.yml
!git add -u
!git commit -m "Archive in one job, use in other"
!git push
[main 5a40228] Archive in one job, use in other
 1 file changed, 1 insertion(+)
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 4 threads
Compressing objects: 100% (5/5), done.
Writing objects: 100% (5/5), 622 bytes | 622.00 KiB/s, done.
Total 5 (delta 2), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To github.com:TomekZet/ium-ga-hello.git
   4df6dc0..5a40228  main -> main

CML - Continous Machine Learning

  • Tworzone przez iterative.ai (tak jak DVC)
  • https://cml.dev/
  • Dokumentacja: https://dvc.org/doc/cml
  • Korzysta z Github Actions lub Gitlab CI (a także Bitbucket Pipelines)
  • CML dodaje do Github Actions kilka "akcji":
    • iterative/setup-cml - dodaje poniższe akcje
    • cml-send-comment - dodaje raport CML jako komentarz do Pull Requesta na Githubie
    • cml-send-github-check - dodaje raport CML do zakładki "Checks" Pull Requesta na Githubie
    • cml-publish - umożliwia dodanie obrazka do raportu

Przykładowy Workflow CML:

!git clone git@github.com:TomekZet/example_cml.git
/home/tomek/AITech/repo/aitech-ium-private/IUM_11
Cloning into 'example_cml'...
remote: Enumerating objects: 25, done.
remote: Total 25 (delta 0), reused 0 (delta 0), pack-reused 25
Receiving objects: 100% (25/25), 222.95 KiB | 920.00 KiB/s, done.
Resolving deltas: 100% (6/6), done.
%cd example_cml
!mkdir -p .github/workflows/
/home/tomek/AITech/repo/aitech-ium-private/IUM_11/example_cml
%%writefile .github/workflows/cml.yaml
name: model-training
on: [push]
jobs:
  run:
    runs-on: [ubuntu-latest]
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-python@v2
      - uses: iterative/setup-cml@v1
      - name: Train model
        env:
          REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          pip install -r requirements.txt
          python train.py

          cat metrics.txt >> report.md
          cml-publish confusion_matrix.png --md >> report.md
          cml-send-comment report.md
Overwriting .github/workflows/cml.yaml
# %load train.py
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import plot_confusion_matrix
import matplotlib.pyplot as plt
import json
import os
import numpy as np

# Read in data
X_train = np.genfromtxt("data/train_features.csv")
y_train = np.genfromtxt("data/train_labels.csv")
X_test = np.genfromtxt("data/test_features.csv")
y_test = np.genfromtxt("data/test_labels.csv")


# Fit a model
depth = 2
clf = RandomForestClassifier(max_depth=depth)
clf.fit(X_train,y_train)

acc = clf.score(X_test, y_test)
print(acc)
with open("metrics.txt", 'w') as outfile:
        outfile.write("Accuracy: " + str(acc) + "\n")


# Plot it
disp = plot_confusion_matrix(clf, X_test, y_test, normalize='true',cmap=plt.cm.Blues)
plt.savefig('confusion_matrix.png')

Wprowadźmy zmianę do pliku (linijka 17: depth= = 6)

%%writefile train.py
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import plot_confusion_matrix
import matplotlib.pyplot as plt
import json
import os
import numpy as np

# Read in data
X_train = np.genfromtxt("data/train_features.csv")
y_train = np.genfromtxt("data/train_labels.csv")
X_test = np.genfromtxt("data/test_features.csv")
y_test = np.genfromtxt("data/test_labels.csv")


# Fit a model
depth = 6
clf = RandomForestClassifier(max_depth=depth)
clf.fit(X_train,y_train)

acc = clf.score(X_test, y_test)
print(acc)
with open("metrics.txt", 'w') as outfile:
        outfile.write("Accuracy: " + str(acc) + "\n")


# Plot it
disp = plot_confusion_matrix(clf, X_test, y_test, normalize='true',cmap=plt.cm.Blues)
plt.savefig('confusion_matrix.png')
Overwriting train.py

Stwórzmy nowy branch "deep_depth":

!git checkout -b deep_depth
!git add train.py .github/workflows/cml.yaml
!git commit -m "Changed depth and added cml workflow"
!git push origin deep_depth
Switched to a new branch 'deep_depth'
[deep_depth 0df0f2c] Changed depth and added cml workflow
 2 files changed, 19 insertions(+), 2 deletions(-)
 create mode 100644 .github/workflows/cml.yaml
Enumerating objects: 8, done.
Counting objects: 100% (8/8), done.
Delta compression using up to 4 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 738 bytes | 738.00 KiB/s, done.
Total 6 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
remote: 
remote: Create a pull request for 'deep_depth' on GitHub by visiting:
remote:      https://github.com/TomekZet/example_cml/pull/new/deep_depth
remote: 
To github.com:TomekZet/example_cml.git
 * [new branch]      deep_depth -> deep_depth

Zadania [20 pkt] (termin: 24.05.2023)

  1. Utwórz konto na Github (jeśli jeszcze nie masz)
  2. Stwórz publiczne repozytorium. Link do niego wklej do kolumny "Link Github" w arkuszu "Zapisy na zbiory" [1 pkt]
  3. Stwórz prosty Github workflow który:
  • zrobi checkout Twojego repozytorium [1 pkt]
  • ściągnie pliki trenujące. Najlepiej byłoby to zrobić za pomocą DVC, ale tym razem uprośćmy zadanie ze względu na komplikacje, które mogą się pojawić przy konfiguracji uwierzytelniania. Pliki można po prostu dodać do repozytorium albo ściągnąć przez wget jeśli są publicznie dostępne [2 pkt]
  • będzie wywoływalny przez "Workflow dispatch" z parametrami trenowania [2 pkt]
  • składał się będzie z co najmniej 3 jobów:
    1. dokona trenowania jako osobnej akcji wykonanej w Dockerze [8 pkt]
    2. dokona ewaluacji modelu [6 pkt]
    3. zarchiwizuje plik z modelem