├── .github
├── ISSUE_TEMPLATE
│ ├── bug-issue-template.md
│ └── feature-request-template.md
├── PULL_REQUEST_TEMPLATE.md
├── labeler.yml
└── workflows
│ ├── deploy.yml
│ └── pull-request.yml
├── .gitignore
├── .pre-commit-config.yaml
├── README.md
├── babel.config.js
├── community
├── contributors.md
├── discussion.md
└── how-to-contribute.md
├── docs
├── api-serving
│ ├── _category_.json
│ ├── img
│ │ ├── api-serving-1.png
│ │ ├── api-serving-2.png
│ │ ├── api-serving-3.png
│ │ ├── api-serving-4.png
│ │ └── api-serving-5.png
│ ├── model-api-on-docker-compose.mdx
│ ├── model-api.mdx
│ └── overview.mdx
├── database
│ ├── _category_.json
│ ├── data-generator-docker-compose.mdx
│ ├── data-generator-docker.mdx
│ ├── data-insertion-loop.mdx
│ ├── data-insertion.mdx
│ ├── db-server-creation.mdx
│ ├── img
│ │ ├── db-1.png
│ │ ├── db-2.png
│ │ ├── db-3.png
│ │ ├── db-4.png
│ │ └── db-5.png
│ ├── overview.mdx
│ └── table-creation.mdx
├── fastapi
│ ├── _category_.json
│ ├── fasapi-crud-pydantic.mdx
│ ├── fastapi-crud.mdx
│ ├── fastapi-on-docker.mdx
│ ├── fastapi-tutorial.mdx
│ ├── img
│ │ ├── fastapi-1.png
│ │ ├── fastapi-2.png
│ │ ├── fastapi-3.png
│ │ ├── fastapi-4.png
│ │ ├── fastapi-5.png
│ │ ├── fastapi-6.png
│ │ ├── fastapi-7.png
│ │ ├── fastapi-8.png
│ │ └── fastapi-9.png
│ └── overview.mdx
├── img
│ ├── architecture.png
│ ├── google-trend.png
│ ├── mlops-level-0.png
│ ├── pipeline.png
│ └── rest-api.png
├── intro.mdx
├── kafka
│ ├── _category_.json
│ ├── connect-connector.mdx
│ ├── img
│ │ ├── kafka-1.png
│ │ ├── kafka-10.png
│ │ ├── kafka-11.png
│ │ ├── kafka-12.png
│ │ ├── kafka-13.png
│ │ ├── kafka-14.png
│ │ ├── kafka-15.png
│ │ ├── kafka-2.png
│ │ ├── kafka-3.png
│ │ ├── kafka-4.png
│ │ ├── kafka-5.png
│ │ ├── kafka-6.png
│ │ ├── kafka-7.png
│ │ ├── kafka-8.png
│ │ ├── kafka-9.png
│ │ └── kafka-gif-1.gif
│ ├── kafka-introduction.mdx
│ ├── kafka-system.mdx
│ ├── overview.mdx
│ ├── producer-consumer.mdx
│ ├── sink-connector.mdx
│ └── source-connector.mdx
├── model-deployment.mdx
├── model-development
│ ├── _category_.json
│ ├── base-model-development.mdx
│ ├── base-model-pipeline.mdx
│ ├── img
│ │ ├── model-development-1.png
│ │ └── model-development-2.png
│ ├── load-data-from-database.mdx
│ └── overview.mdx
├── model-registry
│ ├── _category_.json
│ ├── img
│ │ ├── model-registry-1.png
│ │ ├── model-registry-10.png
│ │ ├── model-registry-11.png
│ │ ├── model-registry-2.png
│ │ ├── model-registry-3.png
│ │ ├── model-registry-4.png
│ │ ├── model-registry-5.png
│ │ ├── model-registry-6.png
│ │ ├── model-registry-7.png
│ │ ├── model-registry-8.png
│ │ └── model-registry-9.png
│ ├── load-model-from-registry.mdx
│ ├── mlflow-setup.mdx
│ ├── overview.mdx
│ └── save-model-to-registry.mdx
└── stream
│ ├── _category_.json
│ ├── dashboard.mdx
│ ├── img
│ ├── grafana-add-panel-1.png
│ ├── grafana-add-panel-2.png
│ ├── grafana-add-panel2.png
│ ├── grafana-after-login.png
│ ├── grafana-after-save.png
│ ├── grafana-click-gear.png
│ ├── grafana-click-new.png
│ ├── grafana-dashboard-setup.png
│ ├── grafana-data-source-1.png
│ ├── grafana-data-source-2.png
│ ├── grafana-data-source-3.png
│ ├── grafana-data-source-4.png
│ ├── grafana-data-source-5.png
│ ├── grafana-data-source-6.png
│ ├── grafana-data-source2-1.png
│ ├── grafana-data-source2-2.png
│ ├── grafana-final-result.gif
│ ├── grafana-init-setup.png
│ ├── grafana-initial-dashboard.png
│ ├── grafana-intro.png
│ ├── grafana-move-to-board-1.png
│ ├── grafana-move-to-board-2.png
│ ├── grafana-panel-1.png
│ ├── grafana-panel-2.png
│ ├── grafana-panel-3.png
│ ├── grafana-save-dashboard.png
│ ├── grafana-save-panel-1.png
│ ├── grafana-save-panel2.png
│ ├── grafana-set-panel-1.png
│ ├── grafana-set-panel2.png
│ ├── grafana-set-time-1.png
│ ├── grafana-set-time-2.png
│ ├── grafana-set-time-3.png
│ ├── grafana-shape.png
│ ├── stream-1.png
│ └── stream-2.png
│ ├── overview.mdx
│ └── stream-serving.mdx
├── docusaurus.config.js
├── i18n
├── en
│ ├── code.json
│ ├── docusaurus-plugin-content-blog
│ │ └── options.json
│ ├── docusaurus-plugin-content-docs-community
│ │ └── current.json
│ ├── docusaurus-plugin-content-docs
│ │ ├── current.json
│ │ └── current
│ │ │ ├── api-serving
│ │ │ ├── _category_.json
│ │ │ ├── img
│ │ │ │ ├── api-serving-1.png
│ │ │ │ ├── api-serving-2.png
│ │ │ │ ├── api-serving-3.png
│ │ │ │ ├── api-serving-4.png
│ │ │ │ └── api-serving-5.png
│ │ │ ├── model-api-on-docker-compose.mdx
│ │ │ ├── model-api.mdx
│ │ │ └── overview.mdx
│ │ │ ├── database
│ │ │ ├── _category_.json
│ │ │ ├── data-generator-docker-compose.mdx
│ │ │ ├── data-generator-docker.mdx
│ │ │ ├── data-insertion-loop.mdx
│ │ │ ├── data-insertion.mdx
│ │ │ ├── db-server-creation.mdx
│ │ │ ├── img
│ │ │ │ ├── db-1.png
│ │ │ │ ├── db-2.png
│ │ │ │ ├── db-3.png
│ │ │ │ ├── db-4.png
│ │ │ │ └── db-5.png
│ │ │ ├── overview.mdx
│ │ │ └── table-creation.mdx
│ │ │ ├── fastapi
│ │ │ ├── _category_.json
│ │ │ ├── fasapi-crud-pydantic.mdx
│ │ │ ├── fastapi-crud.mdx
│ │ │ ├── fastapi-on-docker.mdx
│ │ │ ├── fastapi-tutorial.mdx
│ │ │ ├── img
│ │ │ │ ├── fastapi-1.png
│ │ │ │ ├── fastapi-2.png
│ │ │ │ ├── fastapi-3.png
│ │ │ │ ├── fastapi-4.png
│ │ │ │ ├── fastapi-5.png
│ │ │ │ ├── fastapi-6.png
│ │ │ │ ├── fastapi-7.png
│ │ │ │ ├── fastapi-8.png
│ │ │ │ └── fastapi-9.png
│ │ │ └── overview.mdx
│ │ │ ├── img
│ │ │ ├── architecture.png
│ │ │ ├── google-trend.png
│ │ │ ├── mlops-level-0.png
│ │ │ ├── pipeline.png
│ │ │ └── rest-api.png
│ │ │ ├── intro.mdx
│ │ │ ├── kafka
│ │ │ ├── _category_.json
│ │ │ ├── connect-connector.mdx
│ │ │ ├── img
│ │ │ │ ├── kafka-1.png
│ │ │ │ ├── kafka-10.png
│ │ │ │ ├── kafka-11.png
│ │ │ │ ├── kafka-12.png
│ │ │ │ ├── kafka-13.png
│ │ │ │ ├── kafka-14.png
│ │ │ │ ├── kafka-15.png
│ │ │ │ ├── kafka-2.png
│ │ │ │ ├── kafka-3.png
│ │ │ │ ├── kafka-4.png
│ │ │ │ ├── kafka-5.png
│ │ │ │ ├── kafka-6.png
│ │ │ │ ├── kafka-7.png
│ │ │ │ ├── kafka-8.png
│ │ │ │ ├── kafka-9.png
│ │ │ │ └── kafka-gif-1.gif
│ │ │ ├── kafka-introduction.mdx
│ │ │ ├── kafka-system.mdx
│ │ │ ├── overview.mdx
│ │ │ ├── producer-consumer.mdx
│ │ │ ├── sink-connector.mdx
│ │ │ └── source-connector.mdx
│ │ │ ├── model-deployment.mdx
│ │ │ ├── model-development
│ │ │ ├── _category_.json
│ │ │ ├── base-model-development.mdx
│ │ │ ├── base-model-pipeline.mdx
│ │ │ ├── img
│ │ │ │ ├── model-development-1.png
│ │ │ │ └── model-development-2.png
│ │ │ ├── load-data-from-database.mdx
│ │ │ └── overview.mdx
│ │ │ ├── model-registry
│ │ │ ├── _category_.json
│ │ │ ├── img
│ │ │ │ ├── model-registry-1.png
│ │ │ │ ├── model-registry-10.png
│ │ │ │ ├── model-registry-11.png
│ │ │ │ ├── model-registry-2.png
│ │ │ │ ├── model-registry-3.png
│ │ │ │ ├── model-registry-4.png
│ │ │ │ ├── model-registry-5.png
│ │ │ │ ├── model-registry-6.png
│ │ │ │ ├── model-registry-7.png
│ │ │ │ ├── model-registry-8.png
│ │ │ │ └── model-registry-9.png
│ │ │ ├── load-model-from-registry.mdx
│ │ │ ├── mlflow-setup.mdx
│ │ │ ├── overview.mdx
│ │ │ └── save-model-to-registry.mdx
│ │ │ └── stream
│ │ │ ├── _category_.json
│ │ │ ├── dashboard.mdx
│ │ │ ├── img
│ │ │ ├── grafana-add-panel-1.png
│ │ │ ├── grafana-add-panel-2.png
│ │ │ ├── grafana-add-panel2.png
│ │ │ ├── grafana-after-login.png
│ │ │ ├── grafana-after-save.png
│ │ │ ├── grafana-click-gear.png
│ │ │ ├── grafana-click-new.png
│ │ │ ├── grafana-dashboard-setup.png
│ │ │ ├── grafana-data-source-1.png
│ │ │ ├── grafana-data-source-2.png
│ │ │ ├── grafana-data-source-3.png
│ │ │ ├── grafana-data-source-4.png
│ │ │ ├── grafana-data-source-5.png
│ │ │ ├── grafana-data-source-6.png
│ │ │ ├── grafana-data-source2-1.png
│ │ │ ├── grafana-data-source2-2.png
│ │ │ ├── grafana-final-result.gif
│ │ │ ├── grafana-init-setup.png
│ │ │ ├── grafana-initial-dashboard.png
│ │ │ ├── grafana-intro.png
│ │ │ ├── grafana-move-to-board-1.png
│ │ │ ├── grafana-move-to-board-2.png
│ │ │ ├── grafana-panel-1.png
│ │ │ ├── grafana-panel-2.png
│ │ │ ├── grafana-panel-3.png
│ │ │ ├── grafana-save-dashboard.png
│ │ │ ├── grafana-save-panel-1.png
│ │ │ ├── grafana-save-panel2.png
│ │ │ ├── grafana-set-panel-1.png
│ │ │ ├── grafana-set-panel2.png
│ │ │ ├── grafana-set-time-1.png
│ │ │ ├── grafana-set-time-2.png
│ │ │ ├── grafana-set-time-3.png
│ │ │ ├── grafana-shape.png
│ │ │ ├── stream-1.png
│ │ │ └── stream-2.png
│ │ │ ├── overview.mdx
│ │ │ └── stream-serving.mdx
│ ├── docusaurus-plugin-content-pages
│ │ ├── components
│ │ │ └── HomepageFeatures
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.module.css
│ │ ├── index.module.css
│ │ └── index.tsx
│ └── docusaurus-theme-classic
│ │ ├── footer.json
│ │ └── navbar.json
└── ko
│ ├── code.json
│ ├── docusaurus-plugin-content-blog
│ └── options.json
│ ├── docusaurus-plugin-content-docs-community
│ └── current.json
│ ├── docusaurus-plugin-content-docs
│ └── current.json
│ └── docusaurus-theme-classic
│ ├── footer.json
│ └── navbar.json
├── package-lock.json
├── package.json
├── sidebars.js
├── sidebarsCommunity.js
├── src
├── components
│ ├── BrowserWindow
│ │ ├── index.tsx
│ │ └── styles.module.css
│ ├── CodeDescription
│ │ ├── index.tsx
│ │ └── styles.module.css
│ ├── Highlight.tsx
│ ├── HomepageFeatures
│ │ ├── index.tsx
│ │ └── styles.module.css
│ ├── PreviewDescription
│ │ ├── index.tsx
│ │ └── styles.module.css
│ └── TeamProfileCards
│ │ └── index.tsx
├── css
│ └── custom.css
└── pages
│ ├── index.module.css
│ └── index.tsx
├── static
├── .nojekyll
├── googlee5904fe980148e9b.html
└── img
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── apple-touch-icon.png
│ ├── docusaurus.png
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon.ico
│ ├── logo.png
│ ├── logo.svg
│ ├── makinarocks.png
│ ├── undraw_docusaurus_mountain.svg
│ ├── undraw_docusaurus_react.svg
│ └── undraw_docusaurus_tree.svg
└── tsconfig.json
/.github/ISSUE_TEMPLATE/bug-issue-template.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug issue template
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Describe the bug
11 | A clear and concise description of what the bug is.
12 |
13 | ## To Reproduce
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | ## Expected behavior
21 | A clear and concise description of what you expected to happen.
22 |
23 | ## Additional context
24 | Add any other context about the problem here.
25 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request-template.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request template
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Is your feature request related to a problem? Please describe.
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | ## Describe the solution you'd like
14 | A clear and concise description of what you want to happen.
15 |
16 | ## Additional context
17 | Add any other context or screenshots about the feature request here.
18 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Changes?
2 |
3 |
4 | ## Why we need?
5 |
6 |
7 | ## Test?
8 |
9 |
10 | ## Related issues?
11 |
12 |
13 | ## Anything Else? (Optional)
14 |
15 |
--------------------------------------------------------------------------------
/.github/labeler.yml:
--------------------------------------------------------------------------------
1 | version: 1
2 | labels:
3 | - label: "WIP"
4 | title: "^WIP:.*"
5 | - label: "feature"
6 | branch: "^feature/.*"
7 | - label: "bugfix"
8 | branch: "^bugfix/.*"
9 | - label: "hotfix"
10 | branch: "^hotfix/.*"
11 | - label: "enhancement"
12 | branch: "^enhance/.*"
13 | - label: "need-to-change-branch-name"
14 | branch: "^(?!^feature/.*$)(?!^bugfix/.*$)(?!^hotfix/.*$)(?!^enhance/.*$)(?!^style/.*$)(?!^docs/.*$).*$"
15 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy to GitHub Pages
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | # Review gh actions docs if you want to further define triggers, paths, etc
8 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on
9 | workflow_dispatch:
10 |
11 |
12 | jobs:
13 | deploy:
14 | name: Deploy to GitHub Pages
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/checkout@v2
18 | - uses: actions/setup-node@v3
19 | with:
20 | node-version: 18
21 | cache: npm
22 |
23 | - name: Install dependencies
24 | run: npm ci
25 | - name: Build website
26 | run: npm run build
27 |
28 | # Popular action to deploy to GitHub Pages:
29 | # Docs: https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-docusaurus
30 | - name: Deploy to GitHub Pages
31 | uses: peaceiris/actions-gh-pages@v3
32 | with:
33 | github_token: ${{ secrets.GITHUB_TOKEN }}
34 | # Build output to publish to the `gh-pages` branch:
35 | publish_dir: ./build
36 | # The following lines assign commit authorship to the official
37 | # GH-Actions bot for deploys to `gh-pages` branch:
38 | # https://github.com/actions/checkout/issues/13#issuecomment-724415212
39 | # The GH actions bot is used by default if you didn't specify the two fields.
40 | # You can swap them out with your own user credentials.
41 | user_name: github-actions[bot]
42 | user_email: 41898282+github-actions[bot]@users.noreply.github.com
43 |
--------------------------------------------------------------------------------
/.github/workflows/pull-request.yml:
--------------------------------------------------------------------------------
1 | name: "Pull Request"
2 | on:
3 | pull_request:
4 | types: [opened, synchronize, edited, reopened, closed]
5 |
6 | jobs:
7 | label:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: anencore94/labeler@v1.1.0
11 |
12 | pre-commit:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v2
16 | - uses: pre-commit/action@v2.0.0
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Dependencies
2 | /node_modules
3 |
4 | # Production
5 | /build
6 |
7 | # Generated files
8 | .docusaurus
9 | .cache-loader
10 |
11 | # Misc
12 | .DS_Store
13 | .env.local
14 | .env.development.local
15 | .env.test.local
16 | .env.production.local
17 |
18 | npm-debug.log*
19 | yarn-debug.log*
20 | yarn-error.log*
21 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 |
2 | # See https://pre-commit.com for more information
3 | # See https://pre-commit.com/hooks.html for more hooks
4 | repos:
5 | - repo: https://github.com/pre-commit/pre-commit-hooks
6 | rev: v2.5.0
7 | hooks:
8 | - id: end-of-file-fixer
9 | - id: check-added-large-files
10 | args: ["--maxkb=30000"]
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MLOps for MLE Tutorial
2 |
3 | "머신러닝 엔지니어를 위한 MLOps" 튜토리얼 문서가 있는 레포입니다.
4 |
5 | ## How to start
6 |
7 | 본 저장소는 [Docusaurus 2](https://docusaurus.io/) 로 build 되며, Backend 는 `npm` 을 사용합니다.
8 |
9 | ### Installation
10 |
11 | MacOS 기준으로 설치는 다음과 같습니다.
12 |
13 | `npm` 을 설치합니다.
14 | ```bash
15 | $ brew install npm
16 | ```
17 |
18 | 필요한 패키지들을 설치합니다.
19 | ```bash
20 | $ npm install
21 | ```
22 |
23 | ### Check local build
24 |
25 | 수정한 후 로컬에서 실행이 되는지 확인합니다.
26 |
27 | ```bash
28 | $ npm run start
29 | ```
30 |
31 | ### pre-commit
32 |
33 | pre-commit 을 통과하는지 확인합니다.
34 |
35 | ```bash
36 | $ pre-commit run --a
37 | ```
38 |
39 | ## How to Contribute?
40 | [How to contribute](community/how-to-contribute.md)
41 |
42 | # For English
43 |
44 | Run site with below code:
45 |
46 | ```bash
47 | $ npm run start -- --locale en
48 | ```
49 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
3 | };
4 |
--------------------------------------------------------------------------------
/community/contributors.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # Contributors
6 |
7 | ## Main Authors
8 |
9 | import {
10 | MainAuthorRow,
11 | } from '@site/src/components/TeamProfileCards';
12 |
13 |
14 |
15 |
16 | ## Reviewers
17 | Thank you for reviewing our tutorials!
18 |
19 | import {
20 | ReviewersRow,
21 | } from '@site/src/components/TeamProfileCards';
22 |
23 |
24 |
--------------------------------------------------------------------------------
/community/discussion.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 2
3 | ---
4 |
5 | # Discussion
6 |
7 | MLOps for MLE 는 게시판으로 [Github Discussion](https://github.com/mlops-for-mle/tutorial/discussions) 과 [Github Issue](https://github.com/mlops-for-mle/tutorial/issues) 를 이용하고 있습니다.
8 |
9 | ## Question
10 | 학습 중 생기는 궁금증은 [Q&A](https://github.com/mlops-for-mle/tutorial/discussions/categories/q-a) 게시판에 남겨주세요.
11 | 빠른 시일 내에 확인 후 답변하도록 하겠습니다.
12 |
13 | ## Idea
14 | 프로젝트에 추가하면 좋을 것 같은 아이디어는 [IDEA](https://github.com/mlops-for-mle/tutorial/discussions/categories/ideas) 게시판에 남겨주세요.
15 |
16 | ## Issue
17 | 오타 및 오류를 발견하시는 경우에는 [Github Issue](https://github.com/mlops-for-mle/tutorial/issues) 에 제보해주세요!
18 |
19 | 혹은 직접 Pull Request를 요청하셔도 됩니다.
20 | PR 과 관련된 사항은 [How to Contribute](/community/how-to-contribute.md) 를 참고해주세요.
21 |
22 | ## Copyright
23 |
24 | 1. 본 문서를 “비상업적 목적” 사용 시 하기와 같이 출처를 반드시 표시해주세요.
25 | - MLOps for MLE, https://mlops-for-mle.github.io
26 | 2. 상업적 용도로 인용/사용/차용하고자 하는 경우 마키나락스(contact@makinarocks.ai)로 사전에 문의주시기 바랍니다.
27 |
--------------------------------------------------------------------------------
/community/how-to-contribute.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 3
3 | ---
4 |
5 | # How to Contribute
6 |
7 | ## Code Sync
8 |
9 | ML Engineer 를 위한 MLOps 는 전체 코드를 두 레포에서 관리합니다.
10 |
11 | 코드의 수정이 있을 경우 양 레포 모두에 수정하는 PR을 작성해주셔야 합니다.
12 | - [Code Repo](https://github.com/mlops-for-mle/mlops-for-mle)
13 | - [Tutorial Repo](https://github.com/mlops-for-mle/tutorial)
14 |
15 | ## Table of Contents
16 |
17 | 1. 전체 단위는 파트, 챕터 순으로 작아집니다.
18 | - 파트는 `01.` , `02.` 와 같이 `XX.` 으로 작성합니다.
19 | - 챕터는 `1)`, `2)` 와 같이 `X)` 로 작성합니다.
20 | 2. 이전 파트 혹은 챕터를 언급해야 할 경우 다음과 같이 작성합니다.
21 | - 문서 최상단에 `import { Chapter, Part } from '@site/src/components/Highlight';` 를 입력합니다.
22 | - 파트의 경우 `01. Database` 와 같이 작성합니다.
23 | - 챕터의 경우 `2) Save Model to Registry` 와 같이 작성합니다.
24 | 3. 파트의 구성은 다음과 같이 합니다.
25 | - 모든 파트의 시작 챕터는 `0) Overview` 입니다.
26 | - 여기서는 파트 전체에서 배울 내용의 전반적이 셜명과 구조에 대한 이미지를 작성합니다.
27 | - 단, 실습 코드가 없는 경우에는 `0) Overview` 가 없어도 됩니다.
28 | - 각 챕터의 이름은 영어 명사구로 작성합니다.
29 | 4. 챕터의 구성은 다음과 같이 작성합니다.
30 | - 목표: 여기서 무엇을 배울 건지?
31 | - 목표는 1,2,3 으로 넘버링합니다.
32 | - 스펙 명세서
33 | - 스펙 명세서는 다음과 같이 작성합니다.
34 | 1. 스펙 1
35 | - 스펙 1의 구체적인 내용 A
36 | - 스펙 1의 구체적인 내용 B
37 | 2. 스펙 2
38 | 3. 스펙 3
39 | - e.g) DB 이름은 `mydatabase` 로 하겠습니다.
40 | - 해설
41 |
42 | ## Tone & Manner
43 |
44 | 1. 한글로 작성했을 때 어색한 영어 단어는 최대한 영어로 작성합니다. 이 때 영어 단어 이후 한 칸을 띈 후에 한글을 작성한다.
45 | - ~~영어한글 → API에서~~ 🙅🏻♂️
46 | - 영어 한글 → API 에서 🙆🏻♂️
47 | 2. 코드 스타일
48 | - Python code 는 복붙해서 주피터 노트북에서 실행되는 형태로 합니다.
49 | - `print(df)`
50 | - Bash 코드는 앞에 `$` 붙여 명시합니다.
51 | - 다음과 같이 작성하여 명시할 수 있습니다.
52 | ```md
53 | # terminal-command
54 | your-command
55 | ```
56 | - 출력은 다음과 같이 됩니다.
57 | ```bash
58 | $ your-command
59 | ```
60 | - 코드는 해설에 적당한 크기로 끊어서 설명하되 마지막에 코드를 모아서 작성합니다.
61 | 3. 변수에 대한 해석
62 | - 변수에 대한 해석 및 설명은 최초에 한번만 진행합니다.
63 | - 예를 들어, Docker Network 에 대한 설명이 앞장에 있다면 다시 설명하지 않고 넘어갑니다.
64 | - 단, 앞장에서의 상황과 차이점이 발생하는 경우 설명을 추가합니다.
65 | 4. 글꼴 및 문법
66 | - **bold** 는 사용하지 않습니다.
67 | - *Italic* 는 * 또는 <var>key</var> 태그로 단어나 문장을 감싸 표현합니다.
68 | - `코드 블럭` 을 사용하는 경우
69 | - Python, shell script, Dockerfile, 파일 제목 등 실제로 코드에 작성되는 내용들은 모두 `코드 블럭` 으로 표현합니다.
70 | - DB 테이블 이름, MLflow 의 구성요소 이름과 같이 시스템을 구성하는 요소의 이름도 `코드 블럭` 으로 표현합니다.
71 | - `코드 블럭` 은 ` 또는 <code>value</code> 태그로 단어나 문장을 감싸 표현합니다.
72 | - 단 colon(`:`)을 사용하는 경우 colon 을 기준으로 왼쪽은 *Italic* 오른쪽은 `코드 블럭` 을 사용합니다.
73 | - 영어 단어를 사용하는 경우, 일반 명사는 문장의 시작일 경우에만 대문자로 쓰고 나머지는 소문자로 작성합니다.
74 | - 기술 스택과 같은 영어 대명사의 경우, 모두 대문자로 시작하게 작성합니다.
75 |
--------------------------------------------------------------------------------
/docs/api-serving/_category_.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "06. API Serving",
3 | "position": 7,
4 | "link": {
5 | "type": "generated-index",
6 | "description": "API Serving"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/docs/api-serving/img/api-serving-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/api-serving/img/api-serving-1.png
--------------------------------------------------------------------------------
/docs/api-serving/img/api-serving-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/api-serving/img/api-serving-2.png
--------------------------------------------------------------------------------
/docs/api-serving/img/api-serving-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/api-serving/img/api-serving-3.png
--------------------------------------------------------------------------------
/docs/api-serving/img/api-serving-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/api-serving/img/api-serving-4.png
--------------------------------------------------------------------------------
/docs/api-serving/img/api-serving-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/api-serving/img/api-serving-5.png
--------------------------------------------------------------------------------
/docs/api-serving/model-api-on-docker-compose.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 2
3 | description: 📌 API 서버를 Docker Compose 로 띄우기 위한 내용을 다룹니다.
4 | ---
5 |
6 | # 2) Model API on Docker Compose
7 | import CodeDescription from '@site/src/components/CodeDescription';
8 | import PreviewDescription from '@site/src/components/PreviewDescription';
9 | import BrowserWindow from '@site/src/components/BrowserWindow';
10 | import { Chapter, Part } from '@site/src/components/Highlight';
11 | import APIServingImage5 from './img/api-serving-5.png';
12 |
13 |
14 |
15 | ## Chapter Preview
16 | ---
17 | ### 목표
18 |
19 | 1. 앞서 작성한 API 를 실행할 수 있는 Dockerfile 을 작성합니다.
20 | 2. API 서버를 띄우기 위한 Docker Compose 파일을 작성합니다.
21 | 3. API 서버에 데이터를 전달하여 제대로 작동하는지 확인합니다.
22 |
23 | ### 스펙 명세서
24 |
25 | 1. 작성한 API 를 실행하는데 필요한 모델을 이미지에 복사하는 Dockerfile 을 작성합니다.
26 | - 이미지를 실행했을 때, 별도의 모델 정보를 필요로 하지 않습니다.
27 | - Base image 는 `amd64/python:3.9-slim` 을 사용합니다.
28 | - 포트는 기본 포트인 8000번 포트를 이용합니다.
29 | 2. Model API 서버를 띄우기 위한 Docker Compose 파일을 작성합니다.
30 | - Service name: api-with-model
31 | - Image: Dockerfile
32 | - Container name: api-with-model
33 | - Port: 8000:8000
34 | 3. Docker Compose 파일을 실행하여 API 를 작동시키고 데이터를 전달하여 예측 결과를 제대로 반환하는지 확인합니다.
35 | - Swagger UI 를 통해 확인합니다.
36 | - `curl` 을 통해 확인합니다.
37 |
38 |
39 |
40 |
41 |
42 | 해당 파트의 전체 코드는 [mlops-for-mle/part6/](https://github.com/mlops-for-mle/mlops-for-mle/tree/main/part6) 에서 확인할 수 있습니다.
43 |
44 | ```js
45 | part6
46 | // highlight-next-line
47 | ├── Dockerfile
48 | ├── Makefile
49 | ├── README.md
50 | ├── app.py
51 | // highlight-next-line
52 | ├── docker-compose.yaml
53 | ├── download_model.py
54 | └── schemas.py
55 | ```
56 |
57 |
58 |
59 |
60 |
61 |
62 | [그림 6-5] Model API Diagram
63 |
64 |
65 | ## 1. Dockerfile 작성
66 |
67 | Dockerfile 을 이용하여 앞서 작성한 Model API 를 작동시킬 수 있는 API 서버의 Docker 이미지를 만들어보겠습니다.
68 |
69 | ```docker title="Dockerfile"
70 | FROM amd64/python:3.9-slim
71 |
72 | WORKDIR /usr/app
73 |
74 | RUN pip install -U pip &&\
75 | pip install mlflow==1.30.0 pandas scikit-learn "fastapi[all]"
76 |
77 | COPY schemas.py schemas.py
78 | COPY app.py app.py
79 | COPY sk_model/ sk_model/
80 |
81 | CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--reload"]
82 | ```
83 |
84 | - RUN :
85 |
86 | - `pip` 를 업데이트 한 후 필요한 패키지들을 설치합니다.
87 | - COPY :
88 |
89 | - API 를 작동시키는데 필요한 `.py` 파일들과 모델이 저장된 디렉토리 `sk_model` 을 컨테이너 내부로 복사합니다.
90 | - API 에서 사용할 모델 파일을 직접 넣어줌으로써 이미지를 실행했을 때에는 별도의 모델 정보를 필요로 하지 않게 됩니다.
91 | - CMD :
92 |
93 | - 컨테이너가 실행될 때 수행할 명령어의 기본값을 적어줍니다.
94 | - 여기서는 `uvicorn` 을 이용해 `app.py` 에서 만든 FastAPI 의 객체 `app` 을 실행합니다.
95 |
96 | ## 2. Docker Compose
97 |
98 | 이제 Model API 서비스를 띄우는 Docker Compose 파일을 작성하겠습니다.
99 |
100 |
101 |
102 | ```yaml title="docker-compose.yaml"
103 | version: "3"
104 |
105 | services:
106 | api-with-model:
107 | build:
108 | context: .
109 | dockerfile: Dockerfile
110 | container_name: api-with-model
111 | ports:
112 | - 8000:8000
113 | healthcheck:
114 | test:
115 | - CMD
116 | - curl -X POST http://localhost:8000/predict
117 | - -H
118 | - "Content-Type: application/json"
119 | - -d
120 | - '{"sepal_length": 6.7, "sepal_width": 3.3, "petal_length": 5.7, "petal_width": 2.1}'
121 | interval: 10s
122 | timeout: 5s
123 | retries: 5
124 |
125 | networks:
126 | default:
127 | name: mlops-network
128 | external: true
129 | ```
130 | - healthcheck :
131 |
132 | - curl 을 이용하여 API 서버의 동작을 테스트합니다.
133 | - 정상 동작이 확인되면 서비스를 띄웁니다.
134 |
135 | - networks :
136 |
137 | - 서비스들을 연결할 Docker 네트워크를 01. Database 파트에서 생성한 `mlops-network` 로 사용합니다.
138 | - `external: true` 옵션은 `docker compose down -v` 로 이번 파트에서 생성되는 서비스를 종료하더라도 01. Database 파트에서 생성한 `mlops-network` 를 삭제하지 않을 수 있게 됩니다.
139 |
140 |
141 |
142 | 이제 완성된 Docker Compose 파일을 다음의 명령어를 통해 실행하여 Model API 서비스를 작동시킵니다.
143 |
144 | ```bash
145 | # terminal-command
146 | docker compose up -d
147 | ```
148 |
149 | 컨테이너가 잘 실행되고 있는지 확인해보면 다음과 같이 `api-with-model` 이라는 이름의 컨테이너가 실행되고 있음을 확인할 수 있습니다.
150 |
151 | ```bash
152 | # terminal-command
153 | docker ps
154 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
155 | a88b1a6cff44 part6-api-with-model "uvicorn app:app --h…" 5 seconds ago Up 4 seconds
156 | ```
157 |
158 | ## 3. API 서버 작동 확인
159 |
160 | ### 3.1 Swagger UI 이용
161 |
162 | 이제 API 서버가 잘 작동하는지 확인해 보겠습니다. API 서버 컨테이너를 실행할 때 포트 포워딩을 `8000:8000` 으로 했으므로 8000번 포트로 접속하면 API 서버로 접속할 수 있습니다.
163 |
164 | [`http://localhost:8000/docs`](http://localhost:8000/docs) 에 접속하여 Request Body 의 형태에 알맞게 데이터를 전달해주면 Response Body 로 inference 결과가 잘 반환되는 것을 확인할 수 있습니다.
165 |
166 | ### 3.2 `curl` 이용
167 |
168 | FastAPI 의 Swagger UI 를 이용하지 않고 다음과 같이 `curl` 을 이용하여 API 가 잘 작동하는지 확인하는 방법도 있습니다.
169 |
170 | ```bash
171 | # terminal-command
172 | curl -X POST http://localhost:8000/predict -H "Content-Type: application/json" -d '{"sepal_length": 6.7, "sepal_width": 3.3, "petal_length": 5.7, "petal_width": 2.1}'
173 | ```
174 |
175 | 위의 명령어를 실행하면 다음과 같이 주어진 데이터에 대한 모델의 예측 결과 (`{"iris_class":2}`) 를 잘 반환해 주는 것을 확인할 수 있습니다.
176 |
177 | ```bash
178 | # terminal-command
179 | {"iris_class":2}
180 | ```
181 |
--------------------------------------------------------------------------------
/docs/api-serving/overview.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 0
3 | description: 📌 Overview of API Serving Chapter
4 | ---
5 |
6 | # 0) Overview
7 | import { Chapter, Part } from '@site/src/components/Highlight';
8 |
9 |
10 | :::caution
11 |
12 | 📌 해당 파트는 01. Database 파트의 DB 와 03. Model Registry 파트의 모델을 이용합니다.
13 | 📌 DB 를 띄우지 않은 경우 01. Database 파트를 완료하고 DB 가 띄워진 상태에서 진행해주세요.
14 | 📌 학습된 모델을 불러올 Model Registry 를 띄우지 않은 경우 03. Model Registry 파트를 완료한 상태에서 진행해주세요.
15 |
16 | :::
17 |
18 | 이번 파트에서는 학습한 모델을 04. Model Deployment 파트에서 소개한 Request Driven 방식을 통해 사용하는 방법을 구현해보겠습니다.
19 | 05. FastAPI 파트에서 학습한 FastAPI 를 이용하여 데이터를 입력받아 모델의 예측값을 반환하는 REST API 를 구현합니다.
20 |
21 | 이번 파트를 통해 완성되는 workflow 를 그림으로 나타내면 [그림 6-1]과 같습니다.
22 |
23 |
28 |
--------------------------------------------------------------------------------
/docs/database/_category_.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "01. Database",
3 | "position": 2,
4 | "link": {
5 | "type": "generated-index",
6 | "description": "Database"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/docs/database/data-insertion-loop.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 4
3 | description: 📌 생성된 테이블 안에 데이터를 계속해서 생성하는 스크립트를 작성합니다.
4 | ---
5 |
6 | # 4) Data Insertion Loop
7 | import CodeDescription from '@site/src/components/CodeDescription';
8 | import PreviewDescription from '@site/src/components/PreviewDescription';
9 | import BrowserWindow from '@site/src/components/BrowserWindow';
10 | import { Chapter, Part } from '@site/src/components/Highlight';
11 |
12 |
13 |
14 | ## Chapter Preview
15 | ---
16 | ### 목표
17 |
18 |
19 | 1. 생성된 테이블 안에 데이터를 계속해서 생성하는 스크립트를 작성합니다.
20 | 2. DB 에 데이터가 계속해서 삽입되고 있는지 확인합니다.
21 |
22 | ### 스펙 명세서
23 |
24 | 1. 3) Data Insertion 챕터에서 작성한 스크립트를 이용하여 계속해서 데이터를 생성하는 스크립트를 작성합니다.
25 | 2. 생성된 `iris_data` 테이블에 `psycopg2` 를 이용하여 스크립트를 실행해 계속해서 데이터를 삽입합니다.
26 | 3. `psql` 을 이용하여 삽입되고 있는 데이터를 확인합니다.
27 |
28 |
29 |
30 |
31 |
32 | 해당 파트의 전체 코드는 [mlops-for-mle/part1/](https://github.com/mlops-for-mle/mlops-for-mle/tree/main/part1) 에서 확인할 수 있습니다.
33 |
34 | ```js
35 | part1
36 | ├── Dockerfile
37 | ├── Makefile
38 | ├── data_generator.py
39 | ├── data_insertion.py
40 | // highlight-next-line
41 | ├── data_insertion_loop.py
42 | ├── docker-compose.yaml
43 | └── table_creator.py
44 | ```
45 |
46 |
47 |
48 | ## 1. 데이터 생성
49 |
50 | ### 1.1 Loop 추가
51 |
52 | 3) Data Insertion 챕터에서 작성한 data_insertion.py 스크립트를 참고하여 계속해서 데이터를 추가하는 data_insertion_loop.py 를 작성해보겠습니다.
53 | 여기서는 추가적으로 while 문을 사용하여 코드를 작성하겠습니다. while True 를 통해 외부의 개입이 없다면 계속해서 while 문을 실행할 수 있습니다.
54 |
55 | ```python
56 | def generate_data(db_connect, df):
57 | while True:
58 | insert_data(db_connect, df.sample(1).squeeze())
59 | ```
60 |
61 | 다만 위와 같이 작성할 경우 너무 빠른 시간에 데이터가 추가되기 때문에 DB 에 부하가 생길 수 있습니다. 부하를 방지하기 위해서는 데이터를 삽입 후 잠시 대기하는 시간을 추가하면 됩니다.
62 | Python 의 `time` 패키지의 `sleep` 함수를 이용하여 데이터를 삽입하고 나서 다음 데이터를 삽입하기 전에 1초동안 대기하도록 다음과 같이 코드를 작성합니다.
63 |
64 | ```python
65 | import time
66 |
67 | def generate_data(db_connect, df):
68 | while True:
69 | insert_data(db_connect, df.sample(1).squeeze())
70 | time.sleep(1)
71 | ```
72 |
73 | ### 1.2 Query 실행
74 |
75 | #### 1.2.1 `data_insertion_loop.py`
76 |
77 | 3) Data Insertion 챕터에서 작성한 내용들과 이번 챕터에서 설명한 내용을 모아서 data_insertion_loop.py 로 작성합니다.
78 |
79 | ```python title="data_insertion_loop.py"
80 | # data_insertion_loop.py
81 | import time
82 |
83 | import pandas as pd
84 | import psycopg2
85 | from sklearn.datasets import load_iris
86 |
87 |
88 | def get_data():
89 | X, y = load_iris(return_X_y=True, as_frame=True)
90 | df = pd.concat([X, y], axis="columns")
91 | rename_rule = {
92 | "sepal length (cm)": "sepal_length",
93 | "sepal width (cm)": "sepal_width",
94 | "petal length (cm)": "petal_length",
95 | "petal width (cm)": "petal_width",
96 | }
97 | df = df.rename(columns=rename_rule)
98 | return df
99 |
100 |
101 | def insert_data(db_connect, data):
102 | insert_row_query = f"""
103 | INSERT INTO iris_data
104 | (timestamp, sepal_length, sepal_width, petal_length, petal_width, target)
105 | VALUES (
106 | NOW(),
107 | {data.sepal_length},
108 | {data.sepal_width},
109 | {data.petal_length},
110 | {data.petal_width},
111 | {data.target}
112 | );
113 | """
114 | print(insert_row_query)
115 | with db_connect.cursor() as cur:
116 | cur.execute(insert_row_query)
117 | db_connect.commit()
118 |
119 |
120 | def generate_data(db_connect, df):
121 | while True:
122 | insert_data(db_connect, df.sample(1).squeeze())
123 | time.sleep(1)
124 |
125 |
126 | if __name__ == "__main__":
127 | db_connect = psycopg2.connect(
128 | user="myuser",
129 | password="mypassword",
130 | host="localhost",
131 | port=5432,
132 | database="mydatabase",
133 | )
134 | df = get_data()
135 | generate_data(db_connect, df)
136 | ```
137 |
138 | #### 1.2.2 실행
139 |
140 | 위에서 작성한 스크립트를 실행하면, 다음과 같이 출력되는 것을 확인할 수 있습니다.
141 |
142 | ```bash
143 | # terminal-command
144 | python data_insertion_loop.py
145 |
146 | INSERT INTO iris_data
147 | (timestamp, sepal_length, sepal_width, petal_length, petal_width, target)
148 | VALUES (
149 | NOW(),
150 | 6.5,
151 | 2.8,
152 | 4.6,
153 | 1.5,
154 | 1.0
155 | );
156 |
157 |
158 | INSERT INTO iris_data
159 | (timestamp, sepal_length, sepal_width, petal_length, petal_width, target)
160 | VALUES (
161 | NOW(),
162 | 5.5,
163 | 4.2,
164 | 1.4,
165 | 0.2,
166 | 0.0
167 | );
168 |
169 |
170 | INSERT INTO iris_data
171 | (timestamp, sepal_length, sepal_width, petal_length, petal_width, target)
172 | VALUES (
173 | NOW(),
174 | 4.4,
175 | 2.9,
176 | 1.4,
177 | 0.2,
178 | 0.0
179 | );
180 | ```
181 |
182 | ## 2. 데이터 확인
183 |
184 | `psql` 을 이용하여 DB 에 접속하고, 계속해서 데이터가 삽입되고 있는지 확인해보겠습니다.
185 |
186 | 1. `psql` 을 이용하여 DB 에 접속합니다.
187 |
188 | ```bash
189 | # terminal-command
190 | PGPASSWORD=mypassword psql -h localhost -p 5432 -U myuser -d mydatabase
191 | psql (14.3, server 14.0 (Debian 14.0-1.pgdg110+1))
192 | Type "help" for help.
193 |
194 | mydatabase=#
195 | ```
196 |
197 | 2. 3) Data Insertion 챕터에서 작성한 iris_data 테이블에 있는 데이터 전체를 확인하기 위한 query 를 실행합니다.
198 |
199 | ```bash
200 | # terminal-command
201 | mydatabase=# select * from iris_data;
202 | id | sepal_length | sepal_width | petal_length | petal_width | target
203 | ----+--------------+-------------+--------------+-------------+--------
204 | 1 | 5.6 | 3 | 4.5 | 1.5 | 1
205 | 2 | 5.9 | 3 | 5.1 | 1.8 | 2
206 | 3 | 5.5 | 2.4 | 3.8 | 1.1 | 1
207 | 4 | 5.4 | 3.9 | 1.3 | 0.4 | 0
208 | (4 rows)
209 | ```
210 |
211 | 계속해서 데이터가 추가되고 있는 것을 확인할 수 있습니다.
212 |
--------------------------------------------------------------------------------
/docs/database/db-server-creation.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | description: 📌 Docker 를 이용하여 DB 서버를 생성합니다.
4 | ---
5 |
6 | # 1) DB Server Creation
7 | import CodeDescription from '@site/src/components/CodeDescription';
8 | import PreviewDescription from '@site/src/components/PreviewDescription';
9 | import BrowserWindow from '@site/src/components/BrowserWindow';
10 |
11 |
12 |
13 | ## Chapter Preview
14 | ---
15 | ### 목표
16 |
17 | 1. Docker 를 이용하여 DB 서버를 생성합니다.
18 | 2. 생성된 DB 의 role name 과 attributes 를 확인합니다.
19 |
20 | ### 스펙 명세서
21 |
22 | 1. Docker 를 설치합니다.
23 | 2. PostgreSQL DB 서버를 생성합니다.
24 | - Image : postgres:14.0
25 | - Container name : postgres-server
26 | - POSTGRES_USER : myuser
27 | - POSTGRES_PASSWORD : mypassword
28 | - POSTGRES_DB : mydatabase
29 | - Port forwarding : 5432:5432
30 | 3. 생성된 DB 서버를 확인합니다.
31 | - `psql` 로 DB 에 접근하여 role name 과 attributes 확인
32 |
33 |
34 |
35 |
36 |
37 |
38 | 해당 파트의 전체 코드는 [mlops-for-mle/part1/](https://github.com/mlops-for-mle/mlops-for-mle/tree/main/part1) 에서 확인할 수 있습니다.
39 |
40 | ```js
41 | part1
42 | ├── Dockerfile
43 | ├── Makefile
44 | ├── data_generator.py
45 | ├── data_insertion.py
46 | ├── data_insertion_loop.py
47 | ├── docker-compose.yaml
48 | └── table_creator.py
49 | ```
50 |
51 |
52 |
53 | ## 1. Docker 설치
54 | ### 1.1 Docker Desktop
55 | - 아래의 링크를 통해 각 OS 에 맞는 Docker desktop 을 설치합니다.
56 | [Docker: Accelerated, Containerized Application Development](https://www.docker.com/)
57 |
58 | ### 1.2 Docker Engine
59 | - Ubuntu 에서 desktop 형태가 아닌 engine 형태로 설치하고 싶을 경우, 아래의 방법을 통해 설치합니다.
60 | [Install Docker Engine on Ubuntu](https://docs.docker.com/engine/install/ubuntu/)
61 |
62 | ## 2. DB 서버 생성
63 |
64 | `docker run` 명령어를 이용하면 간단한 옵션들을 통해 DB 서버를 생성할 수 있습니다.
65 |
66 |
67 |
68 | ```bash
69 | # terminal-command
70 | docker run -d \
71 | --name postgres-server \
72 | -p 5432:5432 \
73 | -e POSTGRES_USER=myuser \
74 | -e POSTGRES_PASSWORD=mypassword \
75 | -e POSTGRES_DB=mydatabase \
76 | postgres:14.0
77 | ```
78 |
79 | - -d :
80 |
81 | - 컨테이너가 detached 모드로 실행하게 되며, `-d` 옵션 없이 실행했다면 해당 터미널에서 `Ctrl + C` 를 눌러서 빠져나오는 순간 해당 컨테이너는 종료됩니다.
82 | - --name :
83 |
84 | - 컨테이너의 이름을 지정합니다.
85 | - -p :
86 |
87 | - 컨테이너에서 외부로 노출할 포트 포워딩 (port forwarding) 을 설정합니다.
88 | - 형식은 `host:container` 으로 사용이 되며, 여기서는 `5432:5432` 로 설정하겠습니다.
89 | - -e : 필요한 환경 변수를 설정합니다.
90 |
91 | - POSTGRES_USER : 유저의 이름을 설정합니다.
92 | - POSTGRES_PASSWORD : 유저의 비밀번호를 설정합니다.
93 | - POSTGRES_DB : DB 의 이름을 설정합니다.
94 | - postgres:14.0 :
95 |
96 | - 사용할 이미지를 지정합니다.
97 |
98 |
99 |
100 | `docker ps` 명령어를 통해 DB 서버가 잘 생성되었는지 확인합니다.
101 |
102 | ```bash
103 | # terminal-command
104 | docker ps
105 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
106 | 13bd38add3e7 postgres:14.0 "docker-entrypoint.s…" 40 seconds ago Up 39 seconds 0.0.0.0:5432->5432/tcp postres-server
107 | ```
108 |
109 | ## 3. DB 서버 확인
110 |
111 | ### 3.1 `psql` 설치
112 |
113 | PostgreSQL DB 서버를 확인할 때 사용하는 CLI 툴인 `psql` 설치합니다.
114 | [공식 홈페이지](https://www.postgresql.org/download/)를 참고하여 본인의 환경에 맞게 설치합니다.
115 |
116 | ### 3.2 DB 서버 확인
117 |
118 | `psql` 을 통해 생성된 PostgreSQL DB 서버로 접속합니다.
119 |
120 |
121 |
122 | ```bash
123 | # terminal-command
124 | PGPASSWORD=mypassword psql -h localhost -p 5432 -U myuser -d mydatabase
125 | ```
126 | - PGPASSWORD= :
127 |
128 | - 접속할 유저의 비밀번호를 입력합니다.
129 | - -h :
130 |
131 | - 호스트를 지정합니다.
132 | - -p :
133 |
134 | - 포트를 지정합니다.
135 | - -U :
136 |
137 | - 접속할 유저의 이름을 입력합니다.
138 | - -d :
139 |
140 | - DB 의 이름을 입력합니다.
141 |
142 |
143 |
144 | 접속에 성공하면 다음과 같이 출력됩니다.
145 |
146 | ```sql
147 | # terminal-command
148 | PGPASSWORD=mypassword psql -h localhost -p 5432 -U myuser -d mydatabase
149 | psql (14.3, server 14.0 (Debian 14.0-1.pgdg110+1))
150 | Type "help" for help.
151 |
152 | mydatabase=#
153 | ```
154 |
155 | `\du` 를 통해 DB 의 role name 과 attributes 을 확인합니다.
156 |
157 | ```sql
158 | mydatabase=# \du
159 | List of roles
160 | Role name | Attributes | Member of
161 | -----------+------------------------------------------------------------+-----------
162 | myuser | Superuser, Create role, Create DB, Replication, Bypass RLS | {}
163 | ```
164 |
--------------------------------------------------------------------------------
/docs/database/img/db-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/database/img/db-1.png
--------------------------------------------------------------------------------
/docs/database/img/db-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/database/img/db-2.png
--------------------------------------------------------------------------------
/docs/database/img/db-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/database/img/db-3.png
--------------------------------------------------------------------------------
/docs/database/img/db-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/database/img/db-4.png
--------------------------------------------------------------------------------
/docs/database/img/db-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/database/img/db-5.png
--------------------------------------------------------------------------------
/docs/database/overview.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 0
3 | description: 📌 Overview of Database Chapter
4 | ---
5 |
6 | # 0) Overview
7 | import DBImage1 from './img/db-1.png';
8 |
9 |
10 |
11 |
12 |
13 | [그림 1-1] DB Workflow
14 |
15 |
16 | 이번 파트에서는 Docker 를 이용하여 DB server 를 생성하고, `psycopg2` 패키지를 이용하여 테이블 생성 및 데이터 삽입을 진행합니다.
17 | 또한 Dockerfile 과 Docker Compose 파일을 만들어 Docker 컨테이너 안에서 계속해서 데이터를 생성하는 서비스를 구축해보겠습니다.
18 |
--------------------------------------------------------------------------------
/docs/fastapi/_category_.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "05. FastAPI",
3 | "position": 6,
4 | "link": {
5 | "type": "generated-index",
6 | "description": "FastAPI"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/docs/fastapi/fasapi-crud-pydantic.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 3
3 | description: 📌 앞서 작성한 API 에서 Create 부분을 Pydantic 을 이용하여 수정합니다.
4 | ---
5 |
6 | # 3) FastAPI CRUD (Pydantic)
7 | import CodeDescription from '@site/src/components/CodeDescription';
8 | import PreviewDescription from '@site/src/components/PreviewDescription';
9 | import BrowserWindow from '@site/src/components/BrowserWindow';
10 |
11 |
12 |
13 | ## Chapter Preview
14 | ---
15 | ### 목표
16 |
17 | 1. 앞서 작성한 API 에서 Create 부분을 Pydantic 을 이용하여 수정합니다.
18 | 2. Pydantic 을 사용하기 전과 후에 어떠한 차이가 있는지 학습합니다.
19 |
20 |
21 | ### 스펙 명세서
22 | 1. Create API 에서 입력으로 받아야 하는 값들을 `pydantic.BaseModel` 을 이용하여 수정합니다. [[Request Body](https://fastapi.tiangolo.com/tutorial/body/)]
23 | - `Class CreateIn(BaseModel)` 을 이용
24 | - `CreateIn` 에서는 이름과 별명을 입력받을 수 있도록 `name` 과 `nickname` 변수를 만듭니다.
25 | - `Class CreateOut(BaseModel)` 을 이용
26 | - `CreateOut` 에는 `status` 와 `id` 변수를 만들어 Create 의 operation function 에서 두 변수의 값을 return 하도록 합니다.
27 | - `id` 는 create 되는 시점의 memory 에 존재하는 데이터의 개수로 정의하여 작성합니다.
28 | 2. Create 후 return 하는 값을 Response Model 을 이용하여 수정합니다. [[Response Model](https://fastapi.tiangolo.com/tutorial/response-model/)]
29 | 3. Pydantic Model 을 사용하기 전과 후에 API 가 어떻게 달라지는지 비교해 봅니다.
30 |
31 |
32 |
33 |
34 |
35 | 해당 파트의 전체 코드는 [mlops-for-mle/part5/](https://github.com/mlops-for-mle/mlops-for-mle/tree/main/part5) 에서 확인할 수 있습니다.
36 |
37 | ```js
38 | part5
39 | ├── Dockerfile
40 | ├── Makefile
41 | ├── crud_path.py
42 | // highlight-next-line
43 | ├── crud_pydantic.py
44 | ├── crud_query.py
45 | ├── main.py
46 | ├── multi_param.py
47 | ├── path_param.py
48 | └── query_param.py
49 | ```
50 |
51 |
52 |
53 | ## 1. Pydantic Model
54 |
55 | Request Body 는 client 에서 API 로 전송하는 데이터를 의미합니다.
56 | 반대로 Response Body 는 API 가 client 로 전송하는 데이터를 의미합니다.
57 |
58 | 이렇게 client 와 API 사이에 데이터를 주고 받을 때 데이터의 형식을 지정해 줄 수 있는데, 이를 위해 Pydantic Model 을 사용할 수 있습니다.
59 |
60 | ### 1.1 Base Setting
61 |
62 | 먼저, 다음과 같이 `pydantic` 으로부터 `BaseModel` 을 import 합니다.
63 |
64 | ```python
65 | from fastapi import FastAPI, HTTPException
66 | from pydantic import BaseModel
67 | ```
68 |
69 | 앞서 작성한 API 와 마찬가지로 FastAPI 인스턴스를 생성한 후 입력받은 데이터를 저장할 수 있도록 `USER_DB` 를 생성합니다.
70 | 또한, `HTTPException` 을 이용하여 에러를 발생할 수 있도록 합니다.
71 |
72 | ```python
73 | # Create a FastAPI Instance
74 | app = FastAPI()
75 |
76 | # User database
77 | USER_DB = {}
78 |
79 | # Fail response
80 | NAME_NOT_FOUND = HTTPException(status_code=400, detail="Name not found.")
81 | ```
82 |
83 | ### 1.2 Define Input Schema
84 |
85 | 다음으로는 입력받아야 하는 데이터의 형태를 지정해줄 수 있도록 `CreateIn` 클래스를 작성합니다.
86 | `pydantic` 의 `BaseModel` 을 상속받은 `CreateIn` 클래스에 Request Body 의 구성 요소가 될 변수들을 attribute 로 지정합니다.
87 | 여기서는 이름과 별명을 입력받아야 하므로 다음과 같이 작성합니다.
88 |
89 | ```python
90 | class CreateIn(BaseModel):
91 | name: str
92 | nickname: str
93 | ```
94 |
95 | ### 1.3 Define Output Schema
96 |
97 | 이번에는 반환하고자 하는 데이터의 형태를 지정해줄 수 있도록 `CreateOut` 클래스를 작성합니다.
98 | 마찬가지로 `pydantic` 의 `BaseModel` 을 상속받은 `CreateOut` 클래스에 Response Body 의 구성 요소가 될 변수들을 attribute 로 지정합니다.
99 | 스펙 명세서에 맞도록 `status` 와 `id` 변수를 지정해 줍니다.
100 |
101 | ```python
102 | class CreateOut(BaseModel):
103 | status: str
104 | id: int
105 | ```
106 |
107 | ## 2. Response Model
108 |
109 | ### 2.1 Response Model
110 |
111 | `@app.get()`, `@app.post()` 등 다양한 Path Operation 에 `response_model` 을 이용하여 Response Body 에 사용될 데이터 모델을 지정해줄 수 있습니다.
112 | 또한, output data 의 type 을 체크하여 자동으로 변환시키고, type 이 유효한지 확인해주고, response 를 위해 자동으로 JSON Schema 를 추가해주는 등의 역할을 할 수 있습니다.
113 |
114 | 그 중에서도 `response_model` 의 가장 중요한 역할은 output data 의 형태를 제한해 줄 수 있다는 것입니다.
115 | 예를 들면, `response_model=CreateOut` 과 같이 지정해주면 해당 Path Operation 이 실행되었을 때 `CreateOut` 에 존재하는 attribute 의 형태로 데이터를 반환하게 됩니다.
116 | 이를 통해, Create API 에 입력하는 데이터로는 `CreateIn` 모델을, 반환하는 데이터로는 `CreateOut` 모델을 사용하도록 지정할 수 있습니다.
117 |
118 | ### 2.2 API Code
119 |
120 | 다음과 같이 Response Model 을 활용하여 Create API 를 수정할 수 있습니다.
121 |
122 | ```python
123 | @app.post("/users", response_model=CreateOut)
124 | def create_user(user: CreateIn) -> CreateOut:
125 | USER_DB[user.name] = user.nickname
126 | return CreateOut(status="success", id=len(USER_DB))
127 | ```
128 |
129 | 위의 Path Operation Function 을 보면 parameter 로 `user` 를 입력 받고, type 은 `CreateIn` 인 것을 알 수 있습니다.
130 |
131 | `USER_DB[user.name] = user.nickname` 와 같이 Pydantic Model 에 선언된 변수를 사용하여 DB 에 사용자 정보를 저장해 줍니다.
132 |
133 | Response Body 에 필요한 변수는 `response_model` 로 지정된 `CreateOut` 모델의 변수인 `status` 와 `id` 이기 때문에 이 변수들의 값을 저장해 주어야 합니다.
134 | `status` 와 `id` 값을 준 `CreateOut` 의 객체를 return 하면 됩니다.
135 |
136 | 정리하면, `create_user` 함수는 입력으로 `CreateIn` 모델을 받고, `CreateOut` 모델을 반환함으로써 request 할 때와 response 할 때 주고받는 데이터에 다른 변수를 사용할 수 있는 것입니다.
137 |
138 | ### 2.3 `crud_pydantic.py`
139 |
140 | 전체 코드를 작성한 `crud_pydantic.py` 는 다음과 같습니다.
141 |
142 | ```python title="crud_pydantic.py"
143 | # crud_pydantic.py
144 | from fastapi import FastAPI, HTTPException
145 | from pydantic import BaseModel
146 |
147 |
148 | class CreateIn(BaseModel):
149 | name: str
150 | nickname: str
151 |
152 |
153 | class CreateOut(BaseModel):
154 | status: str
155 | id: int
156 |
157 | # Create a FastAPI instance
158 | app = FastAPI()
159 |
160 | # User database
161 | USER_DB = {}
162 |
163 | # Fail response
164 | NAME_NOT_FOUND = HTTPException(status_code=400, detail="Name not found.")
165 |
166 |
167 | @app.post("/users", response_model=CreateOut)
168 | def create_user(user: CreateIn):
169 | USER_DB[user.name] = user.nickname
170 | user_dict = user.dict()
171 | user_dict["status"] = "success"
172 | user_dict["id"] = len(USER_DB)
173 | return user_dict
174 |
175 |
176 | @app.get("/users")
177 | def read_user(name: str):
178 | if name not in USER_DB:
179 | raise NAME_NOT_FOUND
180 | return {"nickname": USER_DB[name]}
181 |
182 |
183 | @app.put("/users")
184 | def update_user(name: str, nickname: str):
185 | if name not in USER_DB:
186 | raise NAME_NOT_FOUND
187 | USER_DB[name] = nickname
188 | return {"status": "success"}
189 |
190 |
191 | @app.delete("/users")
192 | def delete_user(name: str):
193 | if name not in USER_DB:
194 | raise NAME_NOT_FOUND
195 | del USER_DB[name]
196 | return {"status": "success"}
197 | ```
198 |
199 | ### 2.4 실행
200 | 작성한 코드를 다음의 명령어를 통해 실행합니다.
201 |
202 | ```bash
203 | # terminal-command
204 | uvicorn crud_pydantic:app --reload
205 | ```
206 |
207 | 이제 [`http://localhost:8000/docs`](http://localhost:8000/docs) 에 접속하면 다음과 같은 Swagger UI 화면을 볼 수 있습니다.
208 |
209 |
210 |
211 | 
212 | [그림 5-6] `crud_pydantic.py` 실행 화면
213 |
214 |
215 |
216 | [그림 5-6]의 화면에서 Pydantic Model 을 사용하여 수정한 POST Method 를 클릭하여 수정하기 전후를 비교해 봅시다.
217 |
218 |
219 |
220 | 
221 | [그림 5-7] Pydantic Model 로 수정하기 전 POST Method
222 |
223 |
224 |
225 | Pydantic Model 로 수정하기 전에는 [그림 5-7]과 같이 parameter 를 이용하여 데이터를 전달했습니다.
226 |
227 |
228 |
229 | 
230 | [그림 5-8] Pydantic Model 로 수정한 후 POST Method
231 |
232 |
233 |
234 | 반면, Pydantic Model 로 수정한 후에는 [그림 5-8]과 같이 Request Body 를 통해 데이터를 전달하는 것을 확인할 수 있습니다.
235 |
236 | ## 3. Pydantic Model 사용 전후 비교
237 |
238 | 위와 같이 Pydantic Model 을 사용한 클래스를 Response Model 로 지정하여 Create API 를 수정하였습니다.
239 | 이처럼 Pydantic Model 을 통해 Response Model 을 사용하면 입력 받는 파라미터와 생성 후 반환하는 파라미터를 다르게 지정해 줄 수 있습니다.
240 | 이러한 기능은 비밀번호처럼 사용자가 필수적으로 입력해야 하지만 반환 값에는 나타나면 안 되는 파라미터를 지정할 때 유용하게 사용될 수 있습니다.
241 |
--------------------------------------------------------------------------------
/docs/fastapi/fastapi-on-docker.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 4
3 | description: 📌 앞서 작성한 API 를 Docker 를 이용하여 실행합니다.
4 | ---
5 |
6 | # 4) FastAPI on Docker
7 | import CodeDescription from '@site/src/components/CodeDescription';
8 | import PreviewDescription from '@site/src/components/PreviewDescription';
9 | import BrowserWindow from '@site/src/components/BrowserWindow';
10 |
11 |
12 |
13 | ## Chapter Preview
14 | ---
15 | ### 목표
16 |
17 | 1. 앞서 작성한 API 를 Docker 를 이용하여 실행합니다.
18 |
19 | ### 스펙 명세서
20 |
21 | 1. 앞서 Pydantic 을 이용하여 수정한 API 를 서버로 실행하기 위해 Dockerfile 을 작성합니다.
22 | - Base image는 `amd64/python:3.9-slim` 을 사용합니다.
23 | - `crud_pydantic.py` 를 이용합니다.
24 | - 포트는 기본 포트인 8000번 포트를 이용합니다.
25 | 2. [`http://localhost:8000/docs`](http://localhost:8000/docs) 에 접속하여 앞서 수행한 시나리오가 제대로 작동하는지 확인합니다.
26 |
27 |
28 |
29 |
30 |
31 | 해당 파트의 전체 코드는 [mlops-for-mle/part5/](https://github.com/mlops-for-mle/mlops-for-mle/tree/main/part5) 에서 확인할 수 있습니다.
32 |
33 | ```js
34 | part5
35 | // highlight-next-line
36 | ├── Dockerfile
37 | ├── Makefile
38 | ├── crud_path.py
39 | ├── crud_pydantic.py
40 | ├── crud_query.py
41 | ├── main.py
42 | ├── multi_param.py
43 | ├── path_param.py
44 | └── query_param.py
45 | ```
46 |
47 |
48 |
49 | ## 1. Dockerfile 작성
50 |
51 | ### 1.1 Dockerfile
52 |
53 | Dockerfile 을 이용하여 앞서 Pydantic 을 이용하여 수정한 API 를 작동시킬 수 있는 API 서버의 Docker 이미지를 만들어보겠습니다.
54 |
55 |
56 |
57 | ```docker title="Dockerfile"
58 | FROM amd64/python:3.9-slim
59 |
60 | WORKDIR /usr/app
61 |
62 | RUN pip install -U pip \
63 | && pip install "fastapi[all]"
64 |
65 | COPY crud_pydantic.py crud_pydantic.py
66 |
67 | CMD ["uvicorn", "crud_pydantic:app", "--host", "0.0.0.0", "--reload"]
68 | ```
69 |
70 | - RUN :
71 |
72 | - `pip` 를 먼저 업데이트한 후에 `fastapi[all]` 을 설치합니다.
73 | - COPY :
74 |
75 | - Pydantic 을 이용하여 수정한 API 의 코드가 담겨 있는 `crud_paydantic.py` 를 컨테이너 내부로 복사합니다.
76 | - CMD :
77 |
78 | - 컨테이너가 실행될 때 수행할 명령어의 기본값을 적어줍니다.
79 | - 여기서는 `uvicorn` 을 이용해 `crud_pydantic.py` 에서 만든 FastAPI 의 객체 `app` 을 실행해 줍니다.
80 |
81 |
82 |
83 | ### 1.2 Build
84 |
85 | 작성한 Dockerfile 을 이용해 이미지를 build 합니다.
86 |
87 | 이미지 이름은 `part5-api-server` 로 하겠습니다.
88 |
89 | ```bash
90 | # terminal-command
91 | docker build -t part5-api-server .
92 | ```
93 |
94 | 이미지가 잘 생성되었는지 확인합니다.
95 |
96 | ```bash
97 | # terminal-command
98 | docker image ls
99 | ```
100 |
101 | 다음과 같이 `part5-api-server` 이미지가 잘 생성되었음을 확인할 수 있습니다.
102 |
103 | ```bash
104 | # terminal-command
105 | docker image ls
106 | REPOSITORY TAG IMAGE ID CREATED SIZE
107 | part5-api-server latest 3dec4bf727fe 3 hours ago 249MB
108 | ```
109 |
110 | ### 1.3 Run
111 |
112 | 이제 build 한 이미지를 실행해 보겠습니다.
113 |
114 |
115 |
116 | ```bash
117 | # terminal-command
118 | docker run -d \
119 | --name api-server \
120 | -p 8000:8000 \
121 | part5-api-server
122 | ```
123 |
124 | - --name :
125 |
126 | - 컨테이너의 이름을 api-server 로 설정합니다.
127 | - -p :
128 |
129 | - 포트 포워딩을 8000:8000 으로 설정합니다.
130 |
131 |
132 |
133 | 이제 컨테이너가 잘 실행되고 있는지 확인합니다.
134 |
135 | ```bash
136 | # terminal-command
137 | docker ps
138 | ```
139 |
140 | 위의 명령어를 실행해보면 `api-server` 라는 이름을 가진 컨테이너가 실행되고 있음을 확인할 수 있습니다.
141 |
142 | ```bash
143 | # terminal-command
144 | docker ps
145 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
146 | fb72ccf8fb47 part5-api-server "uvicorn crud_pydant…" 13 minutes ago Up 13 minutes 0.0.0.0:8000->8000/tcp api-server
147 | ```
148 |
149 | ## 2. API 서버 접속하여 작동 확인
150 |
151 | ### 2.1 작동 확인
152 | 이제 FastAPI 를 통해 만든 API 서버가 잘 작동하는지 확인해 보겠습니다.
153 | API 서버 컨테이너를 실행할 때 포트 포워딩을 `8000:8000` 으로 했으므로 8000번 포트로 접속하면 API 서버로 접속할 수 있습니다.
154 |
155 | [`http://localhost:8000/docs`](http://localhost:8000/docs) 에 접속하면 다음과 같이 접속이 되는 것을 확인할 수 있습니다.
156 |
157 |
163 |
164 | [그림 5-6]의 화면에서 앞서 수행한 시나리오와 같은 다양한 작업을 해 보면, 만든 API 가 제대로 작동하는 것을 확인할 수 있습니다.
165 |
166 | ### 2.2 컨테이너 종료
167 |
168 | 작동이 잘 되는 것을 확인한 후에는 다음 실습을 위해 컨테이너를 종료해 줍니다. 다음의 명령어를 통해 종료할 수 있습니다.
169 |
170 | ```bash
171 | # terminal-command
172 | docker rm --force api-server
173 | ```
174 |
--------------------------------------------------------------------------------
/docs/fastapi/img/fastapi-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/fastapi/img/fastapi-1.png
--------------------------------------------------------------------------------
/docs/fastapi/img/fastapi-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/fastapi/img/fastapi-2.png
--------------------------------------------------------------------------------
/docs/fastapi/img/fastapi-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/fastapi/img/fastapi-3.png
--------------------------------------------------------------------------------
/docs/fastapi/img/fastapi-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/fastapi/img/fastapi-4.png
--------------------------------------------------------------------------------
/docs/fastapi/img/fastapi-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/fastapi/img/fastapi-5.png
--------------------------------------------------------------------------------
/docs/fastapi/img/fastapi-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/fastapi/img/fastapi-6.png
--------------------------------------------------------------------------------
/docs/fastapi/img/fastapi-7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/fastapi/img/fastapi-7.png
--------------------------------------------------------------------------------
/docs/fastapi/img/fastapi-8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/fastapi/img/fastapi-8.png
--------------------------------------------------------------------------------
/docs/fastapi/img/fastapi-9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/fastapi/img/fastapi-9.png
--------------------------------------------------------------------------------
/docs/fastapi/overview.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 0
3 | description: 📌 Overview of FastAPI Chapter
4 | ---
5 |
6 | # 0) Overview
7 | 이번 파트에서는 모델을 서빙하기에 앞서 API 의 개념에 대해 학습합니다.
8 | 실습을 위해서는 Python 을 이용해서 API 를 만들 수 있는 웹 프레임워크인 FastAPI 를 사용합니다.
9 |
10 | 이번 파트에서 구현할 workflow 는 다음과 같습니다.
11 |
12 |
18 |
19 | Docker 를 이용하여 FastAPI 로 만든 API 서버를 실행하고 client 가 서버에 request 를 보냅니다.
20 | Request 를 받은 API 서버는 다시 client 에게 response 를 주게 됩니다.
21 |
--------------------------------------------------------------------------------
/docs/img/architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/img/architecture.png
--------------------------------------------------------------------------------
/docs/img/google-trend.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/img/google-trend.png
--------------------------------------------------------------------------------
/docs/img/mlops-level-0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/img/mlops-level-0.png
--------------------------------------------------------------------------------
/docs/img/pipeline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/img/pipeline.png
--------------------------------------------------------------------------------
/docs/img/rest-api.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/img/rest-api.png
--------------------------------------------------------------------------------
/docs/intro.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # 00. Introduction
6 | import CodeDescription from '@site/src/components/CodeDescription';
7 | import PreviewDescription from '@site/src/components/PreviewDescription';
8 | import BrowserWindow from '@site/src/components/BrowserWindow';
9 |
10 | ## Introduction
11 |
12 | 2022년 MLOps 에 대한 관심은 폭발적으로 증가했습니다. [그림 0-1] 은 “MLOps” 키워드에 대한 구글 트랜드 그래프입니다. 2022년을 기점으로 검색량이 폭발적으로 증가한 것을 확인할 수 있습니다.
13 |
14 |
19 |
20 |
21 | 이런 관심의 집중에 따라 MLOps에 관한 많은 포스팅 글들을 찾아볼 수 있습니다. 하지만 대부분은 MLOps 의 개념에 관해서만 다루고 있습니다.
22 | 또한, MLOps는 그 자체로 제품 혹은 플랫폼이 될 정도로 방대한 범위를 다루고 있습니다. 대표적인 클라우드 회사에서는 SageMaker (AWS), VertexAI (Google Cloud) 과 같이 MLOps 제품을 출시했습니다.
23 |
24 | 그런데 정작 이를 사용해야 하는 머신러닝 엔지니어 입장에서는 “그래서 어떻게 MLOps 를 할 수 있는 건데?” 라는 의문이 들 수 밖에 없습니다.
25 | 이러한 의문을 조금이라도 풀 수 있도록 “머신러닝 엔지니어를 위한 MLOps”를 집필하였습니다.
26 |
27 | ## 다루는 내용
28 |
29 | ### MLOps level: 0
30 |
31 | [그림 0-2]는 MLOps 를 설명할 때 많이 쓰이는 구글 클라우드에서 제시한 MLOps 의 0단계 입니다. MLOps 의 목표는 0단계를 자동화시키는 것과 모델 개발 환경과 모델 운영 환경을 일치시키는 것에 있습니다.
32 |
33 |
38 |
39 | “머신러닝 엔지니어를 위한 MLOps” 에서는 직접 구현해보며 각 컴포넌트들이 어떤 역할을 하는지 컴포넌트끼리는 어떻게 연결해야 하는지 설명하고자 합니다. 여기서 다루는 내용을 간단히 도식화 하면 [그림 0-3]과 같이 표시할 수 있습니다.
40 |
41 |
56 |
57 | 각 컴포넌트를 구현하는데 사용하는 패키지 및 소프트웨어는 다음의 기준으로 선정하였습니다.
58 |
59 | 1. 오픈소스
60 | 2. 공식 Documentation 의 관리 여부
61 |
62 | 대표적으로 사용하는 패키지는 FastAPI, MLflow, Kafka 등이 있습니다.
63 | 단순히 따라해보는 것에 그치지 않고 본인의 업무에 필요한 방향으로 적용해 볼 수 있도록 간단한 튜토리얼을 작성하고 MLOps 에 맞게 변형하는 과정으로 진행됩니다.
64 |
65 | ### Docker
66 |
67 | “머신러닝 엔지니어를 위한 MLOps” 는 모든 컴포넌트들을 Docker 로 실행됩니다.
68 |
69 | 머신러닝의 성능 재현은 머신러닝 모델을 다룰 때 가장 중요한 부분 중 하나입니다.
70 | 이를 위해서는 OS, 파이썬 버전, 패키지 버전, 코드, 가중치 등이 모델을 학습했을 때의 환경과 동일해야 합니다.
71 | 이런 동일한 환경을 제공하기 위해서 Containerization 은 머신러닝에서 중요한 기술 중 하나이며 이를 대표하는 소프트웨어가 바로 Docker 입니다.
72 | 이러한 배경으로 머신러닝 엔지니어는 Docker 를 이해하고 다룰 수 있어야 합니다. 머신러닝 엔지니어가 Docker 를 다루고 이해할 수 있도록 본 문서에서는 로컬 환경에서 실행되는 스크립트를 작성한 후 이를 Docker 환경에서 실행하는 순서로 진행됩니다.
73 |
74 | ## 이 문서를 학습하는 방법
75 |
76 | ### Architecture
77 |
78 | 본 문서에 있는 다음과 같은 구조로 작성되어 있습니다.
79 |
80 | 1. 각 챕터에서 학습해야 할 목표
81 | 2. 목표를 구현하기 위한 스펙 명세서
82 | 3. 각 챕터의 전체 코드를 확인할 수 있는 경로
83 | 4. 스펙 명세서를 작성하기 위한 코드와 그에 대한 해설
84 |
85 | 예를 들어 다음에 학습할 챕터는 아래와 같이 작성되어 있습니다.
86 |
87 |
88 |
89 |
90 |
91 | ### Chapter Preview
92 | ---
93 | #### 목표
94 |
95 | 1. Docker 를 이용하여 DB 서버를 생성합니다.
96 | 2. 생성된 DB 의 role name 과 attributes 를 확인합니다.
97 |
98 | #### 스펙 명세서
99 |
100 | 1. Docker 를 설치합니다.
101 | 2. PostgreSQL DB 서버를 생성합니다.
102 | - Image : postgres:14.0
103 | - Container name : postgres-server
104 | - POSTGRES_USER : myuser
105 | - POSTGRES_PASSWORD : mypassword
106 | - POSTGRES_DB : mydatabase
107 | - Port forwarding : 5432:5432
108 | 3. 생성된 DB 서버를 확인합니다.
109 | - `psql` 로 DB 에 접근하여 role name 과 attributes 확인
110 |
111 |
112 |
113 |
114 |
115 | 해당 파트의 전체 코드는 [mlops-for-mle/part1/](https://github.com/mlops-for-mle/mlops-for-mle/tree/main/part1) 에서 확인할 수 있습니다.
116 |
117 | ```js
118 | part1
119 | ├── Dockerfile
120 | ├── Makefile
121 | ├── data_generator.py
122 | ├── data_insertion.py
123 | ├── data_insertion_loop.py
124 | ├── docker-compose.yaml
125 | └── table_creator.py
126 | ```
127 |
128 |
129 |
130 |
131 |
132 | 좀 더 효율적인 학습을 위해서는 해설을 먼저 보기 보다는 스펙 명세서를 직접 구현한 뒤 설명하는 내용들을 읽는 것을 권장합니다.
133 |
--------------------------------------------------------------------------------
/docs/kafka/_category_.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "07. Kafka",
3 | "position": 8,
4 | "link": {
5 | "type": "generated-index",
6 | "description": "Kafka"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/docs/kafka/connect-connector.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 3
3 | description: 📌 Kafka 의 Connect 와 Connector 에 대해 알아봅니다.
4 | ---
5 |
6 | # 3) Connect & Connector
7 | import CodeDescription from '@site/src/components/CodeDescription';
8 | import PreviewDescription from '@site/src/components/PreviewDescription';
9 | import BrowserWindow from '@site/src/components/BrowserWindow';
10 | import { Chapter, Part } from '@site/src/components/Highlight';
11 | import KafkaImage7 from './img/kafka-7.png';
12 | import KafkaImage8 from './img/kafka-8.png';
13 |
14 |
15 |
16 | ## Chapter Preview
17 | ---
18 | ### 목표
19 |
20 | 1. Kafka 의 Connect 와 Connector 에 대해 알아봅니다.
21 |
22 |
23 |
24 |
25 |
26 |
27 | 해당 파트의 전체 코드는 [mlops-for-mle/part7/](https://github.com/mlops-for-mle/mlops-for-mle/tree/main/part7) 에서 확인할 수 있습니다.
28 |
29 | ```js
30 | part7
31 | ├── Makefile
32 | ├── README.md
33 | ├── connect.Dockerfile
34 | ├── create_table.py
35 | ├── kafka-docker-compose.yaml
36 | ├── naive-docker-compose.yaml
37 | ├── sink_connector.json
38 | ├── source_connector.json
39 | ├── target-docker-compose.yaml
40 | └── target.Dockerfile
41 | ```
42 |
43 |
44 |
45 | ## 1. Connect & Connector
46 |
47 | ### 1.1 Producer & Consumer 의 한계
48 |
49 | Kafka 는 2) Producer & Consumer 챕터처럼 Producer 와 Consumer client 를 통해 메시지 파이프라인을 쉽게 구성할 수 있었습니다.
50 |
51 | 하지만 실제 시스템에서는 어떨까요? 아래의 예제를 살펴보겠습니다.
52 | [그림 7-5] 처럼 어떠한 DB server 1에서 DB server 2로 데이터를 전달하는 시스템을 가정해보겠습니다.
53 |
54 |
55 |
56 | 
57 | [그림7-5] Single Kafka System with Producer & Consumer
58 |
59 |
60 |
61 | DB server 1로부터 데이터를 가져오는 Producer 가 있고, 데이터를 브로커의 어떤 토픽으로 보낸 뒤, Consumer 가 DB server 2에 데이터를 전달하는 과정입니다.
62 |
63 | 그렇다면 이렇게 전달할 DB 들이 100개, 1000개, 10000개가 있다면 어떨까요? 아마도 아래의 과정처럼 Producer 와 Consumer 를 100개, 1000개, 10000개로 만들어야 할 것입니다.
64 |
65 |
71 |
72 | 하지만 메시지 파이프라인 구성을 위해 매번 Producer 와 Consumer 를 개발하는 것은 쉽지 않습니다.
73 | 특히, 비슷한 데이터 시스템이 많아지면 많아질수록 Producer 와 Consumer 를 개발하는 데에는 비용도 계속 들고 반복 작업이 많아질 수 있습니다.
74 |
75 | 따라서, 더 간편하고 효율적으로 메시지 파이프라인을 구축하는 방법으로 Kafka 에서는 Connect 와 Connector 라는 것이 탄생하게 되었습니다.
76 |
77 | ### 1.2 Connect & Connector 소개
78 |
79 | Connect 는 데이터 시스템과 Kafka 간의 데이터를 확장 가능하고, 안전한 방법으로 streaming 하기 위한 도구입니다.
80 | Connect 를 사용하기 위해서는 데이터를 어디로부터 가져오는지, 어디에다가 전달해야 하는지를 알려주는 Connector 를 정의해야 합니다.
81 | 여기서 Connector 란 메시지 파이프라인에 대한 추상 객체이며, task 들을 관리합니다.
82 |
83 | Connect 와 Connector 의 역할을 살펴보면, Connect 는 프레임워크이고 Connector 는 그 안에서 돌아가는 플러그인입니다.
84 | 따라서 Connect 프레임워크를 실행하고 특정 Connector 플러그인을 실행시키면 메시지 파이프라인을 쉽게 구축할 수 있습니다.
85 | 이렇게 구축된 Connect 와 Connector 를 실행함으로써 개발 비용을 줄이고 반복 작업도 줄일 수 있게 됩니다.
86 |
87 | Connector 에는 아래와 같은 두 가지 종류의 Connector 가 존재합니다.
88 |
89 | #### Source Connector :
90 |
91 | - Source system 의 데이터를 브로커의 토픽으로 publish 하는 Connector 입니다.
92 | - 즉, Producer 의 역할을 하는 Connector 입니다.
93 |
94 | #### Sink Connector :
95 | - 브로커의 토픽에 있는 데이터를 subscribe 해서 target system 에 전달하는 Connector 입니다.
96 | - 즉, Consumer 의 역할을 하는 Connector 입니다.
97 |
98 | 각각의 Connector 에 관한 설정 명세를 Connect 에 전달하면, 구성된 Connector 는 주기적으로 메시지를 확인하고 새로운 메시지가 있으면 파이프라인을 통해 흘려보냅니다.
99 |
100 | 앞서 예제로 다뤘던 [그림7-5] 에 대해서 DB server 1에서 DB server 2로 데이터를 전달하는 시스템을 다시 살펴보면 [그림 7-7]과 같은 흐름으로 생각할 수 있습니다.
101 |
102 |
103 |
104 |
105 |
106 | [그림7-7] Single Kafka System with Connect & Connector
107 |
108 |
109 |
110 | DB 들이 100개, 1000개, 10000개로 늘어난다면 어떻게 될까요?
111 | 아마도 [그림 7-8]처럼 Source Connector 와 Sink Connector 를 100개, 1000개, 10000개로 만들어야 할 것입니다.
112 |
113 |
114 |
115 |
116 |
117 |
118 | [그림7-8] Multiple Kafka System with Connect & Connector
119 |
120 |
121 |
122 | 그렇다면 앞서 다뤘던 Producer 와 Consumer 를 만드는 것보다 Source Connector 와 Sink Connector 를 만드는 것의 장점이 무엇일까요?
123 |
124 | 바로 Connector 에 대한 설정파일만 있으면 개발 비용 없이 간단하게 띄울 수 있다는 것입니다.
125 | Source Connector 의 경우, Connector 의 유형, 연결할 URL, user 와 password, 테이블 이름, 토픽의 파티션 수, Replication Factor 수 등을 설정해주면 Connect 에서 인스턴스로 생성할 수 있습니다.
126 | 이렇게 생성된 Connector 들이 100개, 1000개, 10000개의 Producer 를 개발하는 것보다는 훨씬 비용이 적고 간편합니다.
127 |
128 | ### 1.3 Schema Registry 소개
129 |
130 | 마지막으로 Connect 와 함께 쓰이는 Schema Registry 에 대해 알아보겠습니다.
131 | 앞서 1) Kafka Introduction 챕터에서 알아본 것과 같이 Kafka 는 decoupling 이라는 특징을 가지고 있습니다.
132 | Producer 와 Consumer 가 존재하고, 서로 의존적이지 않고 완벽하게 분리되어 있습니다.
133 | 또한 브로커는 메시지를 한 번 저장하면 이후에는 수정할 수 없습니다.
134 |
135 | 이처럼 Kafka 의 구조적인 특징과 내부 구조로 인해 Kafka 운영에서는 [그림 7-9]와 같은 상황이 발생할 수 있습니다.
136 |
137 |
143 |
144 | [그림 7-9] 상황은 다음과 같습니다.
145 | 1. Producer 1과 2는 각자 브로커의 토픽 A 에 메시지를 보냅니다.
146 | 2. Consumer 는 토픽 A 에 있는 메시지를 읽습니다.
147 | 3. 이때, Producer 2가 schema 를 변경하여 메시지 (4번)를 발행합니다.
148 | 4. 하지만 Consumer 는 이 상황을 알지 못하기 때문에, 4번 메시지를 구독하여 처리하는 과정에서 메시지를 읽어들이지 못하고 장애가 발생합니다.
149 |
150 | 위와 같은 상황처럼 결국 구조적인 결합도는 낮췄지만 내부적인 결합도 문제는 여전히 가지고 있게 됩니다.
151 |
152 | 이러한 문제에 더하여 동일한 schema 의 메시지가 계속 들어오는 경우, 같은 schema 를 계속해서 저장해야하기 때문에 메시지의 크기가 커지며, schema 가 중복이 되어 불필요한 데이터 용량을 차지하게 됩니다.
153 |
154 | 이러한 구조적인 결합도를 낮추고 불필요한 데이터 용량을 줄이기 위해 Kafka 에서는 Schema Registry 를 사용합니다.
155 | Schema Registry 란 메시지의 Schema 를 저장해주는 일종의 저장소입니다.
156 |
157 | 다음은 Kafka Connector 가 만들어 내는 메시지 구조입니다.
158 |
159 |
160 |
161 | 
162 | [그림7-10] 메시지 구조 (출처: https://scorpio-mercury.tistory.com/30)
163 |
164 |
165 |
166 | 메시지는 key 와 value 로 구성되어 있으며, 각 key 와 value 는 schema 와 payload 로 구성되어 있습니다.
167 | 여기서 key 는 PK 와 같이 데이터를 식별할 수 있는 정보가 들어있고, value 는 데이터의 전체 값이 들어있습니다.
168 | payload 는 데이터 값이 저장되며, schema 에는 이 데이터 값의 데이터 타입이 명시되어 있습니다.
169 |
170 | [그림 7-11]은 Producer, Schema Registry, Kafka 간의 관계를 나타냅니다.
171 |
172 |
178 |
179 | [그림 7-11] 에서 각 컴포넌트가 작동하는 순서는 다음과 같습니다.
180 | 1. Producer 에서 Kafka 의 Serializer (또는 Converter) 에게 메시지를 보냅니다.
181 | 2. Serializer 는 메시지를 받아 메시지의 schema 를 Schema Registry 에 보냅니다.
182 | 3. 이어서 schema ID 를 받고, schema ID 와 데이터를 Kafka 에게 보냅니다.
183 |
184 | :::info
185 |
186 | Connect 와 Connector 를 이용할 때는 Serializer 를 직접 구현할 필요없이 Connect 를 띄울 때 환경 변수로 적어주면 됩니다.
187 |
188 | :::
189 |
190 |
191 | 앞서 살펴봤던 schema 중복 문제는 Schema Registry 에 key 와 value 에 명시된 schema 를 따로 저장하기 때문에 Connector 가 schema 대신 Schema Registry 의 schema ID 를 명시하여 해결할 수 있게 됩니다.
192 | Schema ID 를 쓰면 메세지의 크기가 줄어들어 불필요한 데이터의 용량도 줄일 수 있습니다.
193 |
194 | 또한 앞서 발생했던 내부적인 결합도 문제는 Schema Registry 에서 제공하는 기능 중 하나인 schema 호환성 규칙 강제 기능으로 해결할 수 있습니다.
195 | Schema 호환성 규칙 강제란 schema 를 등록하여 사용할 수 있지만, schema 버전 간의 호환성을 강제함으로써 일종의 규칙을 세우는 것입니다.
196 |
197 |
203 |
204 | [그림 7-12]은 여러 호환성 중 Forward 라는 호환성을 갖는 경우에 대한 예시입니다.
205 |
206 | 1. Consumer 는 version 1로 메시지를 처리하고 있습니다.
207 | 2. 그리고 Gender 라는 column 이 version 2에서 추가되었고, Consumer 는 version 2 의 schema 를 메시지를 구독하여 처리합니다.
208 | 3. 이때, Consumer 는 새로 추가된 column 을 제외하고, 기존 version 1에 맞춰 메시지를 처리합니다.
209 |
210 | 이렇게 schema 의 호환성 규칙을 강제하여 schema 가 다른 메시지도 읽을 수 있도록 만듭니다.
211 |
--------------------------------------------------------------------------------
/docs/kafka/img/kafka-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/kafka/img/kafka-1.png
--------------------------------------------------------------------------------
/docs/kafka/img/kafka-10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/kafka/img/kafka-10.png
--------------------------------------------------------------------------------
/docs/kafka/img/kafka-11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/kafka/img/kafka-11.png
--------------------------------------------------------------------------------
/docs/kafka/img/kafka-12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/kafka/img/kafka-12.png
--------------------------------------------------------------------------------
/docs/kafka/img/kafka-13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/kafka/img/kafka-13.png
--------------------------------------------------------------------------------
/docs/kafka/img/kafka-14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/kafka/img/kafka-14.png
--------------------------------------------------------------------------------
/docs/kafka/img/kafka-15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/kafka/img/kafka-15.png
--------------------------------------------------------------------------------
/docs/kafka/img/kafka-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/kafka/img/kafka-2.png
--------------------------------------------------------------------------------
/docs/kafka/img/kafka-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/kafka/img/kafka-3.png
--------------------------------------------------------------------------------
/docs/kafka/img/kafka-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/kafka/img/kafka-4.png
--------------------------------------------------------------------------------
/docs/kafka/img/kafka-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/kafka/img/kafka-5.png
--------------------------------------------------------------------------------
/docs/kafka/img/kafka-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/kafka/img/kafka-6.png
--------------------------------------------------------------------------------
/docs/kafka/img/kafka-7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/kafka/img/kafka-7.png
--------------------------------------------------------------------------------
/docs/kafka/img/kafka-8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/kafka/img/kafka-8.png
--------------------------------------------------------------------------------
/docs/kafka/img/kafka-9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/kafka/img/kafka-9.png
--------------------------------------------------------------------------------
/docs/kafka/img/kafka-gif-1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/kafka/img/kafka-gif-1.gif
--------------------------------------------------------------------------------
/docs/kafka/overview.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 0
3 | description: 📌 Overview of Kafka Chapter
4 | ---
5 |
6 | # 0) Overview
7 | import { Chapter, Part } from '@site/src/components/Highlight';
8 |
9 | 04. Model Deployment 파트에서 설명한 Stream Serving 을 구현하기 위해서는 우선 실시간으로 데이터를 전달할 수 있는 데이터 파이프라인을 구축해야 합니다.
10 | 이번 파트에서는 Kafka 를 이용하여 데이터 파이프라인을 구축해보겠습니다.
11 |
12 | 이번 파트를 통해 완성되는 workflow 를 그림으로 나타내면 [그림 7-1]과 같습니다.
13 |
14 |
20 |
21 | 실습에 앞서 다음과 같은 가정을 합니다.
22 |
23 | 1. Source DB: 데이터가 계속해서 쌓이고 있는 외부 DB
24 | 2. Target DB: 외부에서 가져온 데이터를 처리한 뒤 쌓이는 내부 DB
25 |
26 | 여기서 Source DB 로 사용하는 DB 서버는 01. Database 파트에서 작성한 PostgreSQL DB 서버를 사용합니다.
27 | 이번 파트에서는 Kafka 를 이용하여 Source DB 서버에 있는 데이터를 Target DB 로 전달하는 시스템을 구축해보겠습니다.
28 |
--------------------------------------------------------------------------------
/docs/model-deployment.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 5
3 | ---
4 | # 04. Model Deployment
5 | import { Chapter, Part } from '@site/src/components/Highlight';
6 |
7 | ## Request Driven - API Serving
8 |
9 | 학습이 완료된 모델을 다른 사람이 사용할 수 있도록 하려면 어떻게 해야 할까요?
10 | 예를 들어서 개와 고양이를 분류할 수 있는 모델을 학습하고, 누군가 이 모델을 통해 개와 고양이를 분류하고 싶어하는 상황을 가정해봅시다.
11 |
12 | 단순하게 생각했을 때 저장된 모델과 추론에 사용된 코드를 사용자에게 전달하면, 사용자는 전달받은 모델과 코드를 이용해 개와 고양이를 분류할 수 있을 것입니다.
13 | 그런데 만약 추론하고자 하는 데이터가 핸드폰에서 촬영한 사진이고, 이를 핸드폰에서 바로 분류하고 싶다면 전달받은 것만으로는 사용하기 힘들 수 있습니다.
14 | 왜냐하면 전달받은 모델이 너무 커서 핸드폰에서 불러올 수 없을 가능성이 있기 때문입니다.
15 | 또한, 핸드폰에 설치되어 있는 패키지의 버전이 달라 다르게 작동할 가능성이 있습니다.
16 | 이처럼 디바이스의 환경에 따라 모델의 크기 문제, 설치되어 있는 패키지 버전 문제 등 다양한 문제에 의해 모델을 사용할 수 없는 상황이 발생합니다.
17 |
18 | 그렇다면 각기 다른 디바이스에서 모델을 직접 사용하기 어려운 문제를 해결하기 위한 방안으로써 데이터만 전달해주면 어떨까요?
19 | 위의 예시에서는 개 또는 고양이를 찍은 사진을 모델을 돌릴 수 있는 환경으로 전송하면, 그 환경에서 직접 모델을 돌려서 나온 결과를 사용자에게 전달하게 됩니다.
20 | 이렇게 하면 모델을 사용하고 싶은 사람은 모델을 직접 돌리지 않고도 원하는 결과를 얻을 수 있습니다.
21 | 이와 같이 요청을 보내고 해당 요청에 대한 응답을 받아 결과를 얻는 방식은 비단 머신러닝 모델뿐만 아니라 많은 소프트웨어에서 사용하는 방식입니다.
22 | 이러한 방식을 Request-Response 방식이라고 부릅니다.
23 |
24 |
29 |
30 | 디바이스끼리 Request-Response 를 하기 위해서는 요청과 응답을 어떻게 할 것인지에 대해서 사전에 정의하는 절차가 필요합니다.
31 | 이러한 것 중 가장 대표적인 방법이 바로 REST API 입니다.
32 |
33 | 05. FastAPI 파트에서는 REST API 를 구현하는 오픈 소스 중 가장 대중적인 FastAPI 를 학습합니다.
34 | 그리고 06. API Serving 파트에서는 REST API 를 통해 모델을 사용하여 결과를 얻는 API Serving 에 대해서 학습합니다.
35 |
36 | ## Event Driven - Stream Serving
37 |
38 | 만약, 데이터가 계속해서 쌓이고 있는 상황이라면 어떻게 될까요?
39 | 예를 들어 공장에서 어떤 센서가 부착되어 있고 센서는 정해진 주기마다 계속해서 데이터를 수집하고 저장하고 있는 상황을 가정해 봅시다.
40 | 이렇게 지속적으로 수집되고 있는 데이터에 대해서 실시간으로 이상 탐지를 할 수 있는 모델을 서빙해야 한다면 어떻게 할 수 있을까요?
41 |
42 | 이러한 경우, 데이터를 수집하고 있는 곳에서 모델에게 계속해서 요청을 보내 결과를 받아오면 될 것 같습니다. 하지만 이 방법은 여러 문제를 가지고 있습니다.
43 | 수집하는 센서는 보통 아주 작은 단위의 업무만 처리할 수 있기에 요청을 보낼 수 없는 경우가 많습니다. 또한 요청을 통해 결과를 받는 주체가 아닌 경우가 많습니다.
44 | 이를 해결하기 위해서는 대신 요청을 보내고 결과를 수집할 수 있는 주체가 필요합니다.
45 |
46 | 위의 예시와 같이 지속적으로 데이터를 수집하고 요청을 보내 결과를 수집하는 상황을 Stream 이라고 표현합니다.
47 | 이를 위해 07. Kafka 파트에서 인프라를 주로 다루는 Kafka 를 학습합니다.
48 | 그리고 08. Stream 파트에서 모델에 요청을 보내서 결과를 수집하는 Stream Serving 을 학습하고, 이를 Dashboard 로 연결하여 시각화하는 Grafana 에 대해 학습합니다.
49 |
--------------------------------------------------------------------------------
/docs/model-development/_category_.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "02. Model Development",
3 | "position": 3,
4 | "link": {
5 | "type": "generated-index",
6 | "description": "Model Development"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/docs/model-development/base-model-pipeline.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 2
3 | description: 📌 여러 개의 모델들을 하나의 파이프라인으로 작성합니다.
4 | ---
5 |
6 | # 2) Model Pipeline
7 | import CodeDescription from '@site/src/components/CodeDescription';
8 | import PreviewDescription from '@site/src/components/PreviewDescription';
9 | import BrowserWindow from '@site/src/components/BrowserWindow';
10 | import { Chapter, Part } from '@site/src/components/Highlight';
11 |
12 |
13 |
14 | ## Chapter Preview
15 | ---
16 | ### 목표
17 |
18 | 1. 여러 개의 모델들을 하나의 파이프라인으로 작성합니다.
19 |
20 | ### 스펙 명세서
21 |
22 | 1. 모델들의 파이프라인화
23 | - `scikit-learn` 에 있는 pipeline 을 이용합니다.
24 | 2. 저장된 파이프라인 검증
25 | - 저장된 파이프라인이 정상적으로 동작하는지 확인합니다.
26 |
27 |
28 |
29 |
30 |
31 | 해당 파트의 전체 코드는 [mlops-for-mle/part2/](https://github.com/mlops-for-mle/mlops-for-mle/tree/main/part2) 에서 확인할 수 있습니다.
32 |
33 | ```js
34 | part2
35 | ├── Makefile
36 | ├── README.md
37 | ├── base_train.py
38 | ├── base_validate_save_model.py
39 | ├── db_train.py
40 | ├── db_validate_save_model.py
41 | // highlight-next-line
42 | ├── pipeline_train.py
43 | // highlight-next-line
44 | └── pipeline_validate_save_model.py
45 | ```
46 |
47 |
48 |
49 | ## 1. Model Pipeline
50 |
51 | ### 1.1 Scaler & SVC
52 | 1) Base Model Development 챕터에서 예측을 위해 사용한 모델은 scaler 와 SVC 두 가지가 있었습니다.
53 | 또한 SVC 가 정상적으로 예측하기 위해서는 scaler 가 필요하다는 것도 알아 보았습니다.
54 | 즉 SVC 모델을 사용하기 위해서는 아래와 같이 사용해야 합니다.
55 |
56 | ```python
57 | scaled_X_train = scaler.transform(X_train)
58 | train_pred = classifier.predict(scaled_X_train)
59 | ```
60 |
61 | 하지만 위 방법은 scaler 를 사용하지 않거나 다른 scaler 를 사용하는 경우가 발생할 수 있습니다.
62 | 이러한 실수를 막을 수 있는 방법이 모델들을 파이프라인화 시키는 것입니다.
63 | 파이프라인된 모델은 아래처럼 사용할 수 있습니다.
64 |
65 | ```python
66 | model_pipeline.predict(X_train)
67 | ```
68 |
69 | `model_pipeline` 안에는 학습이 완료된 scaler 와 SVC 가 같이 존재하기 때문에 위에서 발생할 수 있는 실수를 없애줍니다.
70 |
71 | 물론 이 방법에도 단점이 있습니다. 한번 구축된 파이프라인은 수정하기 어렵다는 점과 scaler 처럼 한 모델에서만 쓰이는게 아니라 여러 모델에도 사용할 수 있는 것들을 중복적으로 학습해야 한다는 점이 있습니다.
72 |
73 | ### 1.2 Code
74 | 직접 모델들을 파이프라인으로 작성해 보겠습니다.
75 |
76 | `sklearn.pipeline` 의 `Pipeline` 을 통해 파이프라인을 작성할 수 있습니다. 이 때 파이프라인 안에 들어가는 값은 리스트이며 리스트 안에는 `(모델 이름, 모델 객체)` 가 값으로 들어갑니다.
77 |
78 | ```python title="pipeline_train.py"
79 | from sklearn.preprocessing import StandardScaler
80 | from sklearn.pipeline import Pipeline
81 | from sklearn.svm import SVC
82 |
83 | model_pipeline = Pipeline([("scaler", StandardScaler()), ("svc", SVC())])
84 | ```
85 |
86 | 이제 생성한 파이프라인을 학습해 보도록 하겠습니다. 학습 방법은 일반적인 `scikit-learn` 의 모델처럼 진행하면 됩니다.
87 |
88 | ```python title="pipeline_train.py"
89 | model_pipeline.fit(X_train, y_train)
90 | ```
91 |
92 | 학습이 완료된 파이프라인은 바로 예측을 하거나 각 단계별로 진행해 볼 수 있습니다.
93 |
94 | 예를 들어서 scaler 만 사용하고 싶은 경우에는 아래처럼 할 수 있습니다.
95 |
96 | ```python title="pipeline_train.py"
97 | print(model_pipeline[0].transform(X_train[:1]))
98 | # [[-1.71687346 -0.1513372 -1.37527528 -1.29070478]]
99 | ```
100 |
101 | 다음으로 파이프라인으로 예측을 하고 정확도를 계산합니다.
102 |
103 | ```python title="pipeline_train.py"
104 | from sklearn.metrics import accuracy_score
105 |
106 | train_pred = model_pipeline.predict(X_train)
107 | valid_pred = model_pipeline.predict(X_valid)
108 |
109 | train_acc = accuracy_score(y_true=y_train, y_pred=train_pred)
110 | valid_acc = accuracy_score(y_true=y_valid, y_pred=valid_pred)
111 |
112 | print("Train Accuracy :", train_acc)
113 | print("Valid Accuracy :", valid_acc)
114 | # Train Accuracy : 0.9833333333333333
115 | # Valid Accuracy : 0.9666666666666667
116 | ```
117 |
118 | 마지막으로 모델을 저장합니다. 1) Base Model Development 챕터처럼 따로 할 필요없이 하나의 파일로 저장할 수 있습니다.
119 |
120 | ```python title="pipeline_train.py"
121 | import joblib
122 |
123 | joblib.dump(model_pipeline, "model_pipeline.joblib")
124 | ```
125 |
126 | ### 1.3 `pipeline_train.py`
127 |
128 | 위에서 작성한 코드를 모아서 `pipeline_train.py` 로 작성합니다.
129 |
130 | ```python title="pipeline_train.py"
131 | # pipeline_train.py
132 | import joblib
133 | from sklearn.datasets import load_iris
134 | from sklearn.metrics import accuracy_score
135 | from sklearn.model_selection import train_test_split
136 | from sklearn.preprocessing import StandardScaler
137 | from sklearn.pipeline import Pipeline
138 | from sklearn.svm import SVC
139 |
140 | # 1. get data
141 | X, y = load_iris(return_X_y=True, as_frame=True)
142 | X_train, X_valid, y_train, y_valid = train_test_split(X, y, train_size=0.8, random_state=2022)
143 |
144 | # 2. model development and train
145 | model_pipeline = Pipeline([("scaler", StandardScaler()), ("svc", SVC())])
146 | model_pipeline.fit(X_train, y_train)
147 |
148 | train_pred = model_pipeline.predict(X_train)
149 | valid_pred = model_pipeline.predict(X_valid)
150 |
151 | train_acc = accuracy_score(y_true=y_train, y_pred=train_pred)
152 | valid_acc = accuracy_score(y_true=y_valid, y_pred=valid_pred)
153 |
154 | print("Train Accuracy :", train_acc)
155 | print("Valid Accuracy :", valid_acc)
156 |
157 | # 3. save model
158 | joblib.dump(model_pipeline, "model_pipeline.joblib")
159 | ```
160 |
161 | ## 2. `pipeline_validate_save_model.py`
162 |
163 | 저장된 파이프라인이 정상적으로 동작하는 지 검증하기 위해 1) Base Model Development 챕터에서 작성한 `base_validate_save_model.py` 의 코드를 수정하여 `pipeline_validate_save_model.py` 로 작성합니다.
164 |
165 | ```python title="pipeline_validate_save_model.py"
166 | # pipeline_validate_save_model.py
167 | import joblib
168 | from sklearn.datasets import load_iris
169 | from sklearn.metrics import accuracy_score
170 | from sklearn.model_selection import train_test_split
171 |
172 | # 1. reproduce data
173 | X, y = load_iris(return_X_y=True, as_frame=True)
174 | X_train, X_valid, y_train, y_valid = train_test_split(X, y, train_size=0.8, random_state=2022)
175 |
176 | # 2. load model
177 | model_pipeline_load = joblib.load("model_pipeline.joblib")
178 |
179 | # 3. validate
180 | load_train_pred = model_pipeline_load.predict(X_train)
181 | load_valid_pred = model_pipeline_load.predict(X_valid)
182 |
183 | load_train_acc = accuracy_score(y_true=y_train, y_pred=load_train_pred)
184 | load_valid_acc = accuracy_score(y_true=y_valid, y_pred=load_valid_pred)
185 |
186 | print("Load Model Train Accuracy :", load_train_acc)
187 | print("Load Model Valid Accuracy :", load_valid_acc)
188 | ```
189 |
--------------------------------------------------------------------------------
/docs/model-development/img/model-development-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/model-development/img/model-development-1.png
--------------------------------------------------------------------------------
/docs/model-development/img/model-development-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mlops-for-mle/tutorial/0a59b069f0e5305c990dcad2eb7fa1a3b2348553/docs/model-development/img/model-development-2.png
--------------------------------------------------------------------------------
/docs/model-development/load-data-from-database.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 3
3 | description: 📌 DB 에서 데이터를 가져오는 파이프라인을 작성합니다.
4 | ---
5 |
6 | # 3) Load Data from Database
7 | import CodeDescription from '@site/src/components/CodeDescription';
8 | import PreviewDescription from '@site/src/components/PreviewDescription';
9 | import BrowserWindow from '@site/src/components/BrowserWindow';
10 | import { Chapter, Part } from '@site/src/components/Highlight';
11 |
12 |
13 |
14 | ## Chapter Preview
15 | ---
16 | ### 목표
17 |
18 | 1. DB 에서 데이터를 가져오는 파이프라인을 작성합니다.
19 |
20 | ### 스펙 명세서
21 |
22 | 1. 데이터 불러오기
23 | - 01. Database 파트에서 생성한 DB 에서 데이터를 가져옵니다.
24 | - `id` column 을 기준으로 최신 데이터 100개를 추출하는 쿼리문을 작성합니다.
25 | - `pandas.read_sql` 함수를 이용해 데이터를 추출합니다.
26 | 2. 모델 파이프라인 수정
27 | - 1) Base Model Development 챕터에서 작성한 파이프라인 중 데이터를 불러오는 부분을 위에서 작성한 함수로 수정합니다.
28 | - 모델을 학습하고 저장합니다.
29 | - 저장된 모델이 정상적으로 동작하는지 확인합니다.
30 |
31 |
32 |
33 |
34 |
35 | 해당 파트의 전체 코드는 [mlops-for-mle/part2/](https://github.com/mlops-for-mle/mlops-for-mle/tree/main/part2) 에서 확인할 수 있습니다.
36 |
37 | ```js
38 | part2
39 | ├── Makefile
40 | ├── README.md
41 | ├── base_train.py
42 | ├── base_validate_save_model.py
43 | // highlight-next-line
44 | ├── db_train.py
45 | // highlight-next-line
46 | ├── db_validate_save_model.py
47 | ├── pipeline_train.py
48 | └── pipeline_validate_save_model.py
49 | ```
50 |
51 |
52 |
53 |
58 |
59 |
60 | ## 1. 모델 불러오기
61 |
62 | 2) Save Model to Registry 챕터에서 작성한 코드로 학습된 모델을 서버로부터 불러오는 코드를 작성합니다.
63 |
64 | ### 1.1 환경 변수 설정
65 |
66 | 2) Save Model to Registry 챕터와 같이 MLflow 서버에 접근하기 위한 환경 변수를 설정합니다.
67 |
68 | ```python
69 | import os
70 |
71 | os.environ["MLFLOW_S3_ENDPOINT_URL"] = "http://localhost:9000"
72 | os.environ["MLFLOW_TRACKING_URI"] = "http://localhost:5001"
73 | os.environ["AWS_ACCESS_KEY_ID"] = "minio"
74 | os.environ["AWS_SECRET_ACCESS_KEY"] = "miniostorage"
75 | ```
76 |
77 | ### 1.2 모델 불러오기
78 |
79 | #### 1.2.1 `sklearn` 모델 불러오기
80 |
81 | 2) Save Model to Registry 챕터에서 저장했던 모델을 불러오기 위해, mlflow.sklearn.load_model 함수를 사용하여 저장된 모델을 불러옵니다.
82 | 모델을 포함하고 있는 run_id 와 모델을 저장할 때 설정했던 모델 이름을 받을 수 있도록 외부 변수를 설정합니다.
83 |
84 | ```python
85 | parser = ArgumentParser()
86 | parser.add_argument("--run-id", dest="run_id", type=str)
87 | parser.add_argument("--model-name", dest="model_name", type=str, default="sk_model")
88 | args = parser.parse_args()
89 | ```
90 |
91 | 앞서 받은 두 가지 변수를 이용해 `runs:/run_id/model_name` 의 형식으로 문자열을 만들어 줍니다.
92 | 만들어진 문자열을 `mlflow.sklearn.load_model` 의 입력으로 넣고 모델을 불러옵니다.
93 |
94 | ```python
95 | model_pipeline = mlflow.sklearn.load_model(f"runs:/{args.run_id}/{args.model_name}")
96 | ```
97 |
98 | 불러온 모델을 확인하면 아래와 같습니다.
99 |
100 | ```python
101 | print(model_pipeline)
102 | # Pipeline(steps=[('scaler', StandardScaler()), ('svc', SVC())])
103 | ```
104 |
105 | #### 1.2.2 `pyfunc` 모델 불러오기
106 |
107 | MLflow 에서는 지정한 방식 [[MLFlow Storage Format](https://www.mlflow.org/docs/latest/models.html#storage-format)]에 따라 저장되어있는 모델에 대해서는 종류에 관계없이 `mlflow.pyfunc.load_model` 을 이용하여 쉽게 모델을 불러올 수 있습니다.
108 |
109 | 이 때 로드된 모델은 기존의 클래스가 아닌 `mlflow.pyfunc.PyFuncModel` 클래스로 불러와집니다. `PyFuncModel` 이란 `mlflow` 에서 정의된 새로운 클래스로, 결과 추론을 위해 학습한 모델의 `predict` method 를 호출하도록 wrapping 된 클래스입니다.
110 |
111 | ```python
112 | model_pipeline = mlflow.pyfunc.load_model(f"runs:/{args.run_id}/{args.model_name}")
113 | ```
114 |
115 | 마찬가지로 앞서 받은 두 가지 변수를 형식에 맞춰 `mlflow.pyfunc.load_model` 의 입력으로 넣어 모델을 로드합니다.
116 |
117 | 불러와진 모델을 확인하면 아래와 같습니다.
118 |
119 | ```python
120 | print(model_pipeline)
121 | # mlflow.pyfunc.load_model:
122 | # artifact_path: sk_model
123 | # flavor: mlflow.sklearn
124 | # run_id: `RUN_ID`
125 | ```
126 |
127 | ### 1.3 추론 코드 작성하기
128 |
129 | 2) Save Model to Registry 챕터에서 저장했던 데이터인 data.csv 파일로부터 데이터를 불러옵니다.
130 |
131 | ```python
132 | df = pd.read_csv("data.csv")
133 | ```
134 |
135 | 학습 조건과 같도록 불필요한 columns 를 제거하고, 학습 데이터와 평가 데이터로 분리합니다.
136 |
137 | ```python
138 | X = df.drop(["id", "timestamp", "target"], axis="columns")
139 | y = df["target"]
140 | X_train, X_valid, y_train, y_valid = train_test_split(X, y, train_size=0.8, random_state=2022)
141 | ```
142 |
143 | 02. Model Development 파트와 같이 결과를 계산하고 출력합니다.
144 |
145 | ```python
146 | train_pred = model_pipeline.predict(X_train)
147 | valid_pred = model_pipeline.predict(X_valid)
148 |
149 | train_acc = accuracy_score(y_true=y_train, y_pred=train_pred)
150 | valid_acc = accuracy_score(y_true=y_valid, y_pred=valid_pred)
151 |
152 | print("Train Accuracy :", train_acc)
153 | print("Valid Accuracy :", valid_acc)
154 | # Train Accuracy : 0.95
155 | # Valid Accuracy : 0.95
156 | ```
157 |
158 | ## 2. 전체 코드 완성
159 |
160 | 추가 작성한 코드를 전체 코드에 적용하여 완성합니다.
161 |
162 | ### 2.1 `load_model_from_registry.py`
163 |
164 | ```python title="load_model_from_registry.py"
165 | # load_model_from_registry.py
166 | import os
167 | from argparse import ArgumentParser
168 |
169 | import mlflow
170 | import pandas as pd
171 | from sklearn.metrics import accuracy_score
172 | from sklearn.model_selection import train_test_split
173 |
174 | # 0. set mlflow environments
175 | os.environ["MLFLOW_S3_ENDPOINT_URL"] = "http://localhost:9000"
176 | os.environ["MLFLOW_TRACKING_URI"] = "http://localhost:5001"
177 | os.environ["AWS_ACCESS_KEY_ID"] = "minio"
178 | os.environ["AWS_SECRET_ACCESS_KEY"] = "miniostorage"
179 |
180 | # 1. load model from mlflow
181 | parser = ArgumentParser()
182 | parser.add_argument("--model-name", dest="model_name", type=str, default="sk_model")
183 | parser.add_argument("--run-id", dest="run_id", type=str)
184 | args = parser.parse_args()
185 |
186 | model_pipeline = mlflow.sklearn.load_model(f"runs:/{args.run_id}/{args.model_name}")
187 |
188 | # 2. get data
189 | df = pd.read_csv("data.csv")
190 |
191 | X = df.drop(["id", "timestamp", "target"], axis="columns")
192 | y = df["target"]
193 | X_train, X_valid, y_train, y_valid = train_test_split(X, y, train_size=0.8, random_state=2022)
194 |
195 | # 3. predict results
196 | train_pred = model_pipeline.predict(X_train)
197 | valid_pred = model_pipeline.predict(X_valid)
198 |
199 | train_acc = accuracy_score(y_true=y_train, y_pred=train_pred)
200 | valid_acc = accuracy_score(y_true=y_valid, y_pred=valid_pred)
201 |
202 | print("Train Accuracy :", train_acc)
203 | print("Valid Accuracy :", valid_acc)
204 | ```
205 |
206 | ### 2.2 실행 및 결과 확인
207 |
208 | 1. [localhost:5001](http://localhost:5001) 에 접속하여 저장된 모델의 `run` 을 클릭하여 `run_id` 와 `model_name` 을 확인합니다.
209 |
210 |
211 | 
212 | [그림 3-10] run-id 및 model-name 확인
213 |
214 | 2. 아래 코드의 `--model-name` , `--run-id` 뒤에 해당 값을 사용하여 실행합니다.
215 |
216 | ```bash
217 | # terminal-command
218 | python load_model_from_registry.py --model-name "sk_model" --run-id "RUN_ID"
219 | ```
220 |
221 | 3. MLflow 서버의 metrics 를 확인하여 학습했던 결과와 같은지 확인합니다.
222 |
223 |
224 | 
225 | [그림 3-11] 모델 추론 결과 확인
226 |
227 |
--------------------------------------------------------------------------------
/docs/model-registry/overview.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 0
3 | description: 📌 Overview of Model Registry Chapter
4 | ---
5 |
6 | # 0) Overview
7 | import { Chapter, Part } from '@site/src/components/Highlight';
8 |
9 | :::caution
10 |
11 | 📌 해당 파트는 01. Database 파트의 DB 를 이용합니다.
12 | 📌 DB 를 띄우지 않은 경우 01. Database 파트를 완료하고 DB 가 띄워진 상태에서 진행해주세요.
13 |
14 | :::
15 |
16 | 03. Model Registry 파트에서는 02. Model development 파트를 통해 만들어진 모델을 저장, 관리 하는 방법을 학습합니다.
17 |
18 | 우선 MLflow 서버를 구축을 한 후 구축된 MLflow 서버에 저장하는 과정을 진행합니다.
19 | 이 때 코드는 02. Model Development 파트의 3) Load Data from Database 챕터에서 작성한 db_train.py 코드 중에서 `# 3. save model` 부분을 변경합니다.
20 |
21 |
20 | 이번 파트에서는 04. Model Deployment 파트에서 소개한 Event-Driven 방식을 구현해봅니다.
21 | 07. Kafka 파트에서 생성된 Kafka System 과 Target DB 에 더하여, Consumer 를 통해 토픽으로부터 데이터를 읽어와서 06. API Serving 파트에서 생성된 API 서버의 입력으로 전달하고, inference 결과를 반환받아 Target DB 로 전달하는 Data Subscriber 를 구현합니다.
22 | 더 자세한 내용은 다음 챕터에서 알아보겠습니다.
23 |
24 |
25 | 또한, Stream Serving 뿐만 아니라 Grafana 를 이용하여 원본 데이터와 예측 결과 값을 실시간으로 시각화해주는 대시보드를 만듭니다.
26 | 대시보드를 통해 Stream Serving 이 잘 되고 있는지 확인합니다.
27 |