1
0
forked from pms/ium
ium/IUM_11.GitHub_Actions.ipynb
Paweł Skórzewski 30feed3862 Lab. 11
2024-05-28 10:35:09 +02:00

30 KiB
Raw Blame History

Inżynieria uczenia maszynowego

29 maja 2024

11. GitHub Actions

Terminologia GitHub Actions

  • _Workflow odpowiada pipeline'owi z Jenkinsa.
  • _Event to zdarzenie, które uruchamia/wyzwala (triggers) workflow. Np. wypchnięcie zmiany do repozytorium (push), utworzenie pull requestu (pełna lista tutaj).
  • _Job - zadanie. 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 (krok) - odpowiednik stage z Jenkinsa - służy do grupowania actions.
  • _Action/command (akcja/polecenie) - odpowiednik step z Jenkinsa - pojedyncze polecenie do wykonania, np. dodanie komentarza do pull requestu, wykonanie polecenia systemowego itp.
  • _Runner (wykonawca) - 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 - własny serwer, z zainstalowaną 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 (opcjonalne) - nazwa, pod którą _workflow/step będzie widoczny w UI. Domyślnie: ścieżka do pliku YAML.
    • on - definiuje, kiedy workflow ma być uruchomiony.
    • jobs - grupuje razem zadania (_jobs) do wykonania. Każde może być wykonane na innym „wykonawcy” (runner). Domyślnie wykonywane są równolegle (ale możemy definiować zależności między jobami, co powoduje wykonanie ich sekwencyjnie).
    • runs-on - parametr zadania (_job) definiują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/pawel/ium/IUM_11/github-actions-hello
/home/pawel/ium/venv/lib/python3.10/site-packages/IPython/core/magics/osm.py:417: UserWarning: This is now an optional IPython functionality, setting dhist requires you to install the `pickleshare` library.
  self.shell.db['dhist'] = compress_dhist(dhist)[-100:]
!git init
!git checkout -b main
!git remote add origin git@github.com:USERNAME/ium-ga-hello.git
!git push -u origin main
Initialized empty Git repository in /home/pawel/ium/IUM_11/github-actions-hello/.git/
Switched to a new branch 'main'
%%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.10'
      - run: python3 --version
Writing .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/skorzewski/ium-ga-hello/actions

!ls -al .github/workflows
total 24
drwxr-xr-x 2 pawel pawel 4096 May 28 10:10 .
drwxr-xr-x 3 pawel pawel 4096 May 28 10:10 ..
-rw-r--r-- 1 pawel pawel 1451 May 28 10:10 docker-artifact.yml
-rw-r--r-- 1 pawel pawel  882 May 28 10:10 docker.yml
-rw-r--r-- 1 pawel pawel  603 May 28 10:10 parametrized.yml
-rw-r--r-- 1 pawel pawel  306 May 28 10:10 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ą zadania, mają zainstalowany zbiór narzędzi. Przykładowa lista dla Ubuntu 24.04: https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md

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: 5 czerwca 2024)

  1. Utwórz konto w serwisie GitHub (jeśli jeszcze nie masz)
  2. Stwórz publiczne repozytorium. Link do niego wklej do kolumny _Link GitHub (Actions) w arkuszu IUM-2024.xlsx [1 pkt]
  3. Stwórz prosty _GitHub workflow, który:
    • zrobi checkout Twojego repozytorium [1 pkt]
    • pobierze pliki z danymi uczącymi (pliki można po prostu dodać do repozytorium albo pobrać przez wget jeśli są publicznie dostępne) [2 pkt]
    • będzie wywoływalny przez "Workflow dispatch" z parametrami uczenia [2 pkt]
    • będzie się składał z co najmniej 2 zadań (_job):
      • uczenie modelu jako osobna akcja wykonana w Dockerze [8 pkt]
      • ewaluacja modelu [6 pkt]