30 KiB
Inżynieria uczenia maszynowego
29 maja 2024
11. GitHub Actions
- https://docs.github.com/en/actions
- System ciągłej integracji „wbudowany” w GitHub
- Darmowy dla publicznych repozytoriów (z większymi niż w płatnych planach ograniczeniami dotyczącymi zasobów)
- https://youtu.be/cP0I9w2coGU
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:
!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.[K
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
czypip install
uruchomionych poprzezrun
. Patrz przykład
Akcje
Za pomocą polecenia uses
możemy używać przygotowanych wcześniej akcji. Mogą one pochodzić:
- z tego samego repozytorium co workflow (więcej)
- z dowolnego publicznego repozytorium Github (np. repozytorioum iterative/setup-clm, patrz przykład poniżej
- z Github Marketplace
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.[K
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.[K
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 akcjecml-send-comment
- dodaje raport CML jako komentarz do Pull Requesta na Githubiecml-send-github-check
- dodaje raport CML do zakładki "Checks" Pull Requesta na Githubiecml-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.[K remote: Total 25 (delta 0), reused 0 (delta 0), pack-reused 25[K 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.[K remote: remote: Create a pull request for 'deep_depth' on GitHub by visiting:[K remote: https://github.com/TomekZet/example_cml/pull/new/deep_depth[K remote: To github.com:TomekZet/example_cml.git * [new branch] deep_depth -> deep_depth
Zadania [20 pkt] (termin: 5 czerwca 2024)
- Utwórz konto w serwisie GitHub (jeśli jeszcze nie masz)
- Stwórz publiczne repozytorium. Link do niego wklej do kolumny _Link GitHub (Actions) w arkuszu
IUM-2024.xlsx
[1 pkt] - 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]