<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Home on Alex Kim's blog</title><link>https://alex000kim.com/</link><description>Recent content in Home on Alex Kim's blog</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Tue, 31 Mar 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://alex000kim.com/index.xml" rel="self" type="application/rss+xml"/><item><title>The Claude Code Source Leak: fake tools, frustration regexes, undercover mode, and more</title><link>https://alex000kim.com/posts/2026-03-31-claude-code-source-leak/</link><pubDate>Tue, 31 Mar 2026 00:00:00 +0000</pubDate><guid>https://alex000kim.com/posts/2026-03-31-claude-code-source-leak/</guid><description>&lt;hr>
&lt;p>&lt;strong>Update:&lt;/strong> see HN discussions about this post: &lt;a href="https://news.ycombinator.com/item?id=47586778">https://news.ycombinator.com/item?id=47586778&lt;/a>&lt;/p>
&lt;hr>
&lt;p>I use Claude Code daily, so when &lt;a href="https://x.com/Fried_rice/status/2038894956459290963">Chaofan Shou&lt;/a> noticed earlier today that Anthropic had shipped a &lt;code>.map&lt;/code> file alongside their Claude Code npm package, one containing the full, readable source code of the CLI tool, I immediately wanted to look inside. The package has since been pulled, but not before the code was widely mirrored, &lt;a href="https://github.com/alex000kim/claude-code">including myself&lt;/a> and picked apart on &lt;a href="https://news.ycombinator.com/item?id=47584540">Hacker News&lt;/a>.&lt;/p>
&lt;p>This is Anthropic&amp;rsquo;s second accidental exposure in a week (the model spec leak was just days ago), and some people on Twitter are starting to wonder if someone inside is doing this on purpose. Probably not, but it&amp;rsquo;s a bad look either way. The timing is hard to ignore: just ten days ago, Anthropic &lt;a href="https://github.com/anomalyco/opencode/pull/18186">sent legal threats to OpenCode&lt;/a>, forcing them to remove built-in Claude authentication because third-party tools were using Claude Code&amp;rsquo;s internal APIs to access Opus at subscription rates instead of pay-per-token pricing. That &lt;a href="https://news.ycombinator.com/item?id=47444748">whole saga&lt;/a> makes some of the findings below more pointed.&lt;/p></description></item><item><title>How to Set Up Work and Personal Git Profiles</title><link>https://alex000kim.com/posts/2025-07-25-git-profiles/</link><pubDate>Fri, 25 Jul 2025 00:00:00 +0000</pubDate><guid>https://alex000kim.com/posts/2025-07-25-git-profiles/</guid><description>&lt;p>Tired of accidentally committing to work repos with your personal email? Here&amp;rsquo;s how to automatically use the right Git profile based on where your repositories live.&lt;/p>
&lt;h2 id="1-generate-ssh-keys">1. Generate SSH Keys&lt;/h2>
&lt;p>Create separate keys for each account:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Work key&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ssh-keygen -t ed25519 -C &lt;span style="color:#e6db74">&amp;#34;work@company.com&amp;#34;&lt;/span> -f ~/.ssh/id_ed25519_work
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Personal key&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ssh-keygen -t ed25519 -C &lt;span style="color:#e6db74">&amp;#34;personal@gmail.com&amp;#34;&lt;/span> -f ~/.ssh/id_ed25519_personal
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="2-configure-ssh-hosts">2. Configure SSH Hosts&lt;/h2>
&lt;p>Edit &lt;code>~/.ssh/config&lt;/code>:&lt;/p>
&lt;pre tabindex="0">&lt;code># Work GitHub
Host github-work
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_work
IdentitiesOnly yes
# Personal GitHub (default)
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_personal
IdentitiesOnly yes
&lt;/code>&lt;/pre>&lt;h2 id="3-set-up-git-profiles">3. Set Up Git Profiles&lt;/h2>
&lt;p>&lt;strong>Global config&lt;/strong> (&lt;code>~/.gitconfig&lt;/code>):&lt;/p></description></item><item><title>US Tariffs, DeepSeek and OpenAI</title><link>https://alex000kim.com/posts/2025-02-02-us-tariffs-deepseek-o3/</link><pubDate>Sun, 02 Feb 2025 00:00:00 +0000</pubDate><guid>https://alex000kim.com/posts/2025-02-02-us-tariffs-deepseek-o3/</guid><description>&lt;p>This week the new Trump administration announced new tariffs on key trading partners, Canada and Mexico.
Both countries vowed to retaliate, and tensions are high:&lt;/p>
&lt;p>&lt;a href="https://www.bbc.com/news/articles/cn4z23kndlyo">&amp;ldquo;Canada imposes 25% tariffs in trade war with US&amp;rdquo;&lt;/a>&lt;/p>
&lt;p>Living in Canada, I am obviusly curious about the impact this will all have both both sides of the border.
I am a self-described economics nerd: having never studied economics, I try to read and listen about when I have spare time.
One other topic that&amp;rsquo;s been on my mind recently is the recent release of the DeepSeek R1 model and the the release of the OpenAI O3 model that followed.
Both are SOTA (as of Feb 2025) LLM models that were trained to &amp;ldquo;reason&amp;rdquo; before providing an answer through a chain of thought Reinforcement Learning:&lt;/p></description></item><item><title>Orchestrating LLM Fine-tuning on Kubernetes with SkyPilot and MLflow: A Complete Guide</title><link>https://alex000kim.com/posts/2025-01-11-llm-fine-tune-skypilot-mlflow/</link><pubDate>Sat, 11 Jan 2025 00:00:00 +0000</pubDate><guid>https://alex000kim.com/posts/2025-01-11-llm-fine-tune-skypilot-mlflow/</guid><description>&lt;p>Training and fine-tuning Large Language Models (LLMs) requires significant computational resources and careful experiment tracking. While many focus on the modeling aspects, efficiently managing compute resources and experiment tracking is equally important for successful ML projects. This guide demonstrates how to use &lt;a href="https://github.com/skypilot-org/skypilot">SkyPilot&lt;/a> and &lt;a href="https://github.com/mlflow/mlflow">MLflow&lt;/a>, two open-source tools, to orchestrate LLM fine-tuning jobs effectively.&lt;/p>
&lt;h2 id="an-open-source-stack-for-llm-fine-tuning">An open-source stack for LLM fine-tuning&lt;/h2>
&lt;p>Modern LLM fine-tuning workflows involve multiple moving parts:&lt;/p>
&lt;ul>
&lt;li>Resource orchestration across different cloud providers&lt;/li>
&lt;li>Environment setup and dependency management&lt;/li>
&lt;li>Experiment tracking and monitoring&lt;/li>
&lt;li>Distributed training coordination&lt;/li>
&lt;li>System metrics collection&lt;/li>
&lt;/ul>
&lt;p>Using &lt;a href="https://github.com/skypilot-org/skypilot/">SkyPilot&lt;/a> for resource orchestration and &lt;a href="https://github.com/mlflow/mlflow">MLflow&lt;/a> for experiment tracking provides an easy-to-use and fully open-source stack for managing these complexities.&lt;/p></description></item><item><title>Kubernetes Mental Model</title><link>https://alex000kim.com/posts/2025-01-04-k8s-mental-model/</link><pubDate>Sat, 04 Jan 2025 00:00:00 +0000</pubDate><guid>https://alex000kim.com/posts/2025-01-04-k8s-mental-model/</guid><description>&lt;p>I am preparing for my CKAD (Certified Kubernetes Application Developer) exam.
Below is the mental model of K8S concepts that helps me understand Kubernetes. Hope it helps you too.&lt;/p>
&lt;h2 id="the-big-picture-kubernetes-as-an-orchestrator">The Big Picture: Kubernetes as an Orchestrator&lt;/h2>
&lt;p>&lt;strong>What is Kubernetes?&lt;/strong>&lt;br>
Kubernetes is an automation system for deploying and managing containerized applications at scale. Rather than manually handling each container, you define your desired state—like “I want three replicas of my service running.” Kubernetes ensures this state remains true even if servers fail or traffic surges.&lt;/p></description></item><item><title>Intro to SLURM for ML Practitioners</title><link>https://alex000kim.com/posts/2024-11-24-intro-to-slurm-for-ml/</link><pubDate>Sun, 24 Nov 2024 00:00:00 +0000</pubDate><guid>https://alex000kim.com/posts/2024-11-24-intro-to-slurm-for-ml/</guid><description>&lt;p>&lt;a href="https://slurm.schedmd.com/documentation.html">&lt;strong>SLURM&lt;/strong>&lt;/a> (Simple Linux Utility for Resource Management) is an open-source workload manager designed to schedule and manage jobs on large clusters. In the world of LLMs, SLURM has seen a resurgence in popularity due to the increased demand for training large models and scaling them to multiple nodes.&lt;/p>
&lt;p>This guide will introduce the fundamental concepts of SLURM, common commands and script structures, and show advanced scenarios like distributed multi-node training. I&amp;rsquo;ll also share some useful tips and tricks.&lt;/p></description></item><item><title>Experiments with OpenAI's Function Calling</title><link>https://alex000kim.com/posts/2024-05-05-function-calling-experiments/</link><pubDate>Sun, 05 May 2024 00:00:00 +0000</pubDate><guid>https://alex000kim.com/posts/2024-05-05-function-calling-experiments/</guid><description>&lt;h2 id="intro">Intro&lt;/h2>
&lt;p>This notebook (also on &lt;a href="https://github.com/alex000kim/openai-function-calling-demo-sqlite/blob/main/FunctionCalling.ipynb">github&lt;/a>) demonstrates how to use &lt;a href="https://platform.openai.com/docs/guides/function-calling">Function Calling&lt;/a> functionality with the OpenAI API.&lt;/p>
&lt;p>In this demo, we&amp;rsquo;ll use the Northwind database to convert natural language queries into SQL:&lt;/p>
&lt;pre tabindex="0">&lt;code>&amp;#34;What is the total revenue for each product in the database?&amp;#34; -&amp;gt;
-&amp;gt; &amp;#34;SELECT ... FROM ...&amp;#34; -&amp;gt; DataFrame
&lt;/code>&lt;/pre>&lt;p>There will be two function calling examples:&lt;/p>
&lt;ol>
&lt;li>A simple one-step function call to convert a natural language query into SQL, where we&amp;rsquo;ll put the database schema into the system prompt and them use function calling to convert a natural language query into SQL.&lt;/li>
&lt;li>A two-step function call first gets the schema of the database and then converts a natural language query into SQL.&lt;/li>
&lt;/ol>
&lt;p>At the end, we&amp;rsquo;ll compare the two approaches and do a quick-and-dirty evaluation of the results using a hand-curated list of questions and their expected SQL queries in &lt;a href="eval_questions.csv">&lt;code>eval_questions.csv&lt;/code>&lt;/a>.&lt;/p></description></item><item><title>Fine-Tuning Large Language Models with a Production-Grade Pipeline</title><link>https://alex000kim.com/posts/2023-09-08-finetune-llm-pipeline-dvc-skypilot/</link><pubDate>Fri, 08 Sep 2023 00:00:00 +0000</pubDate><guid>https://alex000kim.com/posts/2023-09-08-finetune-llm-pipeline-dvc-skypilot/</guid><description>&lt;h2 id="introduction---solving-cloud-resources-and-reproducibility-for-llms">Introduction - Solving cloud resources and reproducibility for LLMs&lt;/h2>
&lt;p>A few of weeks ago, I wrote a
&lt;a href="https://alex000kim.com/tech/2023-08-10-ml-experiments-in-cloud-skypilot-dvc/">post&lt;/a>
about the challenges of training large ML models, in particular:&lt;/p>
&lt;ol>
&lt;li>the need for more computing power and the complexity of managing cloud
resources;&lt;/li>
&lt;li>the difficulty of keeping track of ML experiments and reproducing results.&lt;/li>
&lt;/ol>
&lt;p>There I proposed a solution to these problems by using
&lt;a href="https://skypilot.readthedocs.io/en/latest/">SkyPilot&lt;/a> and
&lt;a href="https://dvc.org/">DVC&lt;/a> to manage cloud resources and track experiments,
respectively.&lt;/p></description></item><item><title>Trying to Understand Something Difficult? Minimize the Number of Attempts!</title><link>https://alex000kim.com/posts/2023-08-16-minimize-the-number-of-attempts/</link><pubDate>Wed, 16 Aug 2023 00:00:00 +0000</pubDate><guid>https://alex000kim.com/posts/2023-08-16-minimize-the-number-of-attempts/</guid><description>&lt;p>When you&amp;rsquo;re trying to wrap your head around challenging new ideas, your natural
instinct may be to chip away at it in several attempts. Read a bit, take a
break, come back later - like attacking a mountain ascent in short sprints. This
may feel like you&amp;rsquo;re making progress, but you&amp;rsquo;re actually doing yourself a
disservice. Every time you re-engage with the difficult material, you&amp;rsquo;re forcing
your mind to re-enter a state of intense flow. This refocusing requires mental
energy - energy that depletes your limited cognitive resources. Each time you
dip back into flow, you have to:&lt;/p></description></item><item><title>ML experiments in the cloud with SkyPilot and DVC</title><link>https://alex000kim.com/posts/2023-08-10-ml-experiments-in-cloud-skypilot-dvc/</link><pubDate>Thu, 10 Aug 2023 00:00:00 +0000</pubDate><guid>https://alex000kim.com/posts/2023-08-10-ml-experiments-in-cloud-skypilot-dvc/</guid><description>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>One of the things that makes machine learning hard is that you have to run a lot
of experiments. You have to try different models, different data sets, different
hyperparameters, different features. And each experiment can take a long time to
run, especially if you’re working on deep learning problems. You can’t just run
them on your laptop or desktop. You need more computing power, and you need it
fast.&lt;/p></description></item><item><title>Why Sales Engineers Exist</title><link>https://alex000kim.com/posts/2023-08-02-why-sales-engineers-exist/</link><pubDate>Wed, 02 Aug 2023 00:00:00 +0000</pubDate><guid>https://alex000kim.com/posts/2023-08-02-why-sales-engineers-exist/</guid><description>&lt;p>Paul Graham once &lt;a href="http://www.paulgraham.com/growth.html">wrote&lt;/a> that &amp;ldquo;a startup
is a company designed to grow fast.&amp;rdquo; For a startup to grow fast, especially a
B2B startup selling SaaS products to other companies, it needs sales engineers.&lt;/p>
&lt;p>Sales engineers are the technical experts who work closely with sales teams.
They exist because selling enterprise software requires deep technical knowledge
that salespeople typical don&amp;rsquo;t have. The salesperson establishes rapport with
the prospect and understands their business needs. But when it comes time to
demo the product and answer nitty gritty technical questions, that&amp;rsquo;s where the
sales engineer comes in.&lt;/p></description></item><item><title>Don’t know what to do next? Teach!</title><link>https://alex000kim.com/posts/2023-07-31-teach/</link><pubDate>Mon, 31 Jul 2023 00:00:00 +0000</pubDate><guid>https://alex000kim.com/posts/2023-07-31-teach/</guid><description>&lt;p>We (esp. those in the tech industry) have an instinctual aversion to pedagogy.
&amp;ldquo;Those who can&amp;rsquo;t do, teach&amp;rdquo; the old saying goes. But the truth is the opposite.
Teaching does not indicate an inability to do something. On the contrary,
teaching empowers and enables ability. It is the highest form of understanding.
When you teach something, you gain a deeper mastery over the subject matter than
you would as a passive student. As Leonardo da Vinci said:&lt;/p></description></item><item><title>About</title><link>https://alex000kim.com/about/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://alex000kim.com/about/</guid><description>&lt;img src="profile-pic.png" width="250"/>
&lt;p>Dad | ML Engineer | Technical Instructor | Consultant | Community Builder&lt;/p>
&lt;hr>
&lt;p>Hi! I’m Alex.&lt;/p>
&lt;p>I am an ML engineer by trade and a physicist by degree. I’ve lived in different parts of the world and am currently based in Montreal, 🇨🇦. I speak English, Russian, French and Spanish.
I am also a husband and a dad of two 👧🏻👦🏻.&lt;/p>
&lt;p>Most of my recent work sits at the intersection of MLOps and generative AI - building distributed training infrastructure for large models and deploying LLM-powered systems in production.&lt;/p></description></item><item><title>Week 1: Kick-starting an ML project</title><link>https://alex000kim.com/other/oreilly-mlops/week1/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://alex000kim.com/other/oreilly-mlops/week1/</guid><description>&lt;h2 id="slides-">Slides 🖼️&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="week1.pdf">Week 1: ML project lifecycle and MLOps best practices&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="learning-objectives">Learning objectives&lt;/h2>
&lt;ul>
&lt;li>Understand the core philosophy behind MLOps ideas&lt;/li>
&lt;li>Apply best practices for establishing ML project structure and dependencies management&lt;/li>
&lt;li>Manage project dependencies with pip and virtualenv&lt;/li>
&lt;li>Version datasets with DVC&lt;/li>
&lt;/ul>
&lt;h2 id="project-introduction">Project Introduction&lt;/h2>
&lt;h3 id="problem-description-and-dataset">&lt;strong>Problem Description and Dataset&lt;/strong>&lt;/h3>
&lt;p>This dataset contains 10,000 records, each of which corresponds to a different bank&amp;rsquo;s user. The target is &lt;code>Exited&lt;/code>, a binary variable that describes whether the user decided to leave the bank. There are row and customer identifiers, four columns describing personal information about the user (surname, location, gender, and age), and some other columns containing information related to the loan (such as credit score, the current balance in the user&amp;rsquo;s account and whether they are an active member among others).&lt;/p></description></item><item><title>Week 2: ML Pipelines, Reproducibility Experimentation</title><link>https://alex000kim.com/other/oreilly-mlops/week2/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://alex000kim.com/other/oreilly-mlops/week2/</guid><description>&lt;h2 id="slides-">Slides 🖼️&lt;/h2>
&lt;p>&lt;a href="week2.pdf">Week 2: ML Pipelines, Reproducibility and Experimentation&lt;/a>&lt;/p>
&lt;h2 id="learning-objectives">Learning objectives&lt;/h2>
&lt;ul>
&lt;li>Refactor a Jupyter notebook into a reproducible ML pipeline&lt;/li>
&lt;li>Version artifacts of an ML pipeline in a remote storage&lt;/li>
&lt;li>Iterate over a large number of ML experiments in a disciplined way&lt;/li>
&lt;/ul>
&lt;h3 id="steps">Steps&lt;/h3>
&lt;h3 id="refactor-jupyter-notebook-in-a-dvc-pipeline">Refactor Jupyter notebook in a DVC pipeline&lt;/h3>
&lt;ul>
&lt;li>
&lt;p>Docs: &lt;a href="https://dvc.org/doc/start/data-pipelines">https://dvc.org/doc/start/data-pipelines&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Create the following files to read parameter values from a file&lt;/p>
&lt;details>
&lt;summary>&lt;code>params.yaml&lt;/code>&lt;/summary>
&lt;p>
&lt;pre>&lt;code> base:
project: bank_customer_churn
raw_data_dir: data/raw
countries:
- France
- Spain
feat_cols:
- CreditScore
- Age
- Tenure
- Balance
- NumOfProducts
- HasCrCard
- IsActiveMember
- EstimatedSalary
targ_col: Exited
random_state: 42
data_split:
test_size: 0.25
processed_data_dir: data/processed
train:
model_type: randomforest
model_dir: models
model_path: models/clf-model.joblib
params:
n_estimators: 200
max_depth: 20
&lt;/code>&lt;/pre>
&lt;/p></description></item><item><title>Week 3: CI/CD for ML and ML-based Web API</title><link>https://alex000kim.com/other/oreilly-mlops/week3/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://alex000kim.com/other/oreilly-mlops/week3/</guid><description>&lt;h2 id="slides-">Slides 🖼️&lt;/h2>
&lt;p>&lt;a href="week3.pdf">Week 3: CI/CD for ML&lt;/a>&lt;/p>
&lt;h2 id="learning-objectives">Learning Objectives&lt;/h2>
&lt;ul>
&lt;li>Learn the basics of CI/CD&lt;/li>
&lt;li>Leverage the power of CI/CD tools for ML projects with CML&lt;/li>
&lt;li>Integrate an ML model into the FastAPI framework&lt;/li>
&lt;li>Build and test a Docker container running a web API service&lt;/li>
&lt;li>Deploy the resulting Docker container to cloud&lt;/li>
&lt;/ul>
&lt;h2 id="steps">Steps&lt;/h2>
&lt;h3 id="introduction-to-github-actions-and-cml">Introduction to GitHub Actions and CML&lt;/h3>
&lt;ul>
&lt;li>Introduction to GitHub Actions&lt;/li>
&lt;li>Introduction to CML&lt;/li>
&lt;/ul>
&lt;h3 id="cicd-automatic-reporting-for-model-related-changes">CI/CD: Automatic reporting for model-related changes&lt;/h3>
&lt;ul>
&lt;li>Add &lt;code>PERSONAL_ACCESS_TOKEN&lt;/code> , &lt;code>AWS_ACCESS_KEY_ID&lt;/code> and &lt;code>AWS_SECRET_ACCESS_KEY&lt;/code> to GH secrets: &lt;a href="https://docs.github.com/en/actions/security-guides/encrypted-secrets">https://docs.github.com/en/actions/security-guides/encrypted-secrets&lt;/a>
&lt;ul>
&lt;li>For AWS credentials, see &lt;a href="https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/getting-your-credentials.html">https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/getting-your-credentials.html&lt;/a>&lt;/li>
&lt;li>For &lt;code>PERSONAL_ACCESS_TOKEN&lt;/code>:
&lt;ul>
&lt;li>&lt;a href="https://github.com/settings/tokens/new">Generate a new personal access token&lt;/a> under GitHub developer settings&lt;/li>
&lt;li>in the &amp;ldquo;Note&amp;rdquo; field, type &lt;code>PERSONAL_ACCESS_TOKEN&lt;/code>&lt;/li>
&lt;li>select repo scope&lt;/li>
&lt;li>click &amp;ldquo;Generate token&amp;rdquo; and copy it&lt;/li>
&lt;li>In your GitHub repository and/or organization, navigate to &lt;strong>Settings&lt;/strong> -&amp;gt; &lt;strong>Secrets&lt;/strong> -&amp;gt; &lt;strong>New repository/organization secret&lt;/strong>&lt;/li>
&lt;li>in the &amp;ldquo;Name&amp;rdquo; field, type &lt;code>PERSONAL_ACCESS_TOKEN&lt;/code>&lt;/li>
&lt;li>in the &amp;ldquo;Value&amp;rdquo; field, paste the token&lt;/li>
&lt;li>click &lt;strong>Add secret&lt;/strong>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;details>
&lt;summary>Create&lt;code>.github/workflows/train-model.yaml&lt;/code>&lt;/summary>
&lt;pre>&lt;code>name: train-model
on:
push:
paths:
- &amp;quot;data/**&amp;quot;
- &amp;quot;src/**&amp;quot;
- &amp;quot;params.yaml&amp;quot;
- &amp;quot;dvc.*&amp;quot;
jobs:
train-model:
runs-on: ubuntu-latest
environment: cloud
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha }}
- uses: iterative/setup-cml@v1
- uses: actions/setup-python@v2
with:
python-version: &amp;quot;3.10&amp;quot;
- uses: actions/setup-node@v1
with:
node-version: '16'
- name: SetupGitUser
run: cml ci
env:
REPO_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
- name: TrainModel
env:
REPO_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
pip install -r requirements.txt
dvc pull
dvc exp run
dvc push
# Create CML report
echo &amp;quot;## Metrics&amp;quot; &amp;gt;&amp;gt; report.md
dvc metrics show --md &amp;gt;&amp;gt; report.md
echo &amp;quot;## Feature Importances&amp;quot; &amp;gt;&amp;gt; report.md
csv2md reports/feat_imp.csv &amp;gt;&amp;gt; report.md
echo &amp;quot;## Confusion Matrix&amp;quot; &amp;gt;&amp;gt; report.md
echo '![](reports/figures/cm.png)' &amp;gt;&amp;gt; report.md
cml comment create report.md
&lt;/code>&lt;/pre>
&lt;/details>
&lt;ul>
&lt;li>Push workflow file with git&lt;/li>
&lt;li>Modify some model parameters (e.g. &lt;code>max_depth&lt;/code>), rerun the pipeline (&lt;code>dvc exp run&lt;/code>) and push changes to DVC remote and git&lt;/li>
&lt;li>Review GitHub Actions runs&lt;/li>
&lt;/ul>
&lt;h3 id="web-app-development">Web App Development&lt;/h3>
&lt;details>
&lt;summary>Create web application &lt;code>src/app/main.py&lt;/code>&lt;/summary>
&lt;pre>&lt;code>import json
import sys
from pathlib import Path
import uvicorn
src_path = Path(__file__).parent.parent.resolve()
sys.path.append(str(src_path))
from typing import List
import pandas as pd
from fastapi import Body, FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from joblib import load
from pydantic import BaseModel
from utils.load_params import load_params
app = FastAPI()
# https://fastapi.tiangolo.com/tutorial/cors/#use-corsmiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=[&amp;quot;*&amp;quot;],
allow_credentials=True,
allow_methods=[&amp;quot;*&amp;quot;],
allow_headers=[&amp;quot;*&amp;quot;],
)
params = load_params(params_path='params.yaml')
model_path = params.train.model_path
feat_cols = params.base.feat_cols
model = load(filename=model_path)
class Customer(BaseModel):
CreditScore: int
Age: int
Tenure: int
Balance: float
NumOfProducts: int
HasCrCard: int
IsActiveMember: int
EstimatedSalary: float
class Request(BaseModel):
data: List[Customer]
@app.post(&amp;quot;/predict&amp;quot;)
async def predict(info: Request = Body(..., example={
&amp;quot;data&amp;quot;: [
{
&amp;quot;CreditScore&amp;quot;: 619,
&amp;quot;Age&amp;quot;: 42,
&amp;quot;Tenure&amp;quot;: 2,
&amp;quot;Balance&amp;quot;: 0,
&amp;quot;NumOfProducts&amp;quot;: 1,
&amp;quot;HasCrCard&amp;quot;: 1,
&amp;quot;IsActiveMember&amp;quot;: 1,
&amp;quot;EstimatedSalary&amp;quot;: 101348.88
},
{
&amp;quot;CreditScore&amp;quot;: 699,
&amp;quot;Age&amp;quot;: 39,
&amp;quot;Tenure&amp;quot;: 21,
&amp;quot;Balance&amp;quot;: 0,
&amp;quot;NumOfProducts&amp;quot;: 2,
&amp;quot;HasCrCard&amp;quot;: 0,
&amp;quot;IsActiveMember&amp;quot;: 0,
&amp;quot;EstimatedSalary&amp;quot;: 93826.63
}
]
})):
json_list = json.loads(info.json())
data = json_list['data']
input_data = pd.DataFrame(data)
probs = model.predict_proba(input_data)[:,0]
probs = probs.tolist()
return probs
if __name__ == &amp;quot;__main__&amp;quot;:
uvicorn.run(app, host=&amp;quot;0.0.0.0&amp;quot;, port=8000)
&lt;/code>&lt;/pre>
&lt;/details>
&lt;ul>
&lt;li>
&lt;p>Test API&lt;/p></description></item><item><title>Week 4: Monitoring for ML Projects</title><link>https://alex000kim.com/other/oreilly-mlops/week4/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://alex000kim.com/other/oreilly-mlops/week4/</guid><description>&lt;h2 id="slides-">Slides 🖼️&lt;/h2>
&lt;p>&lt;a href="week4.pdf">Week 4: Data Drift Monitoring for ML Projects&lt;/a>&lt;/p>
&lt;h2 id="learning-objectives">Learning Objectives&lt;/h2>
&lt;ul>
&lt;li>Distinguish between application monitoring and ML monitoring&lt;/li>
&lt;li>Use Alibi Detect framework to detect data drift&lt;/li>
&lt;/ul>
&lt;h2 id="steps">Steps&lt;/h2>
&lt;h3 id="introduction-to-data-drift-monitoring">Introduction to Data Drift Monitoring&lt;/h3>
&lt;ul>
&lt;li>
&lt;p>What’s data drift and why do we need to monitor for it?&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Intro to Alibi Detect&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Add &lt;code>Churn_Modelling_Germany.csv&lt;/code> to &lt;code>data/more_data/&lt;/code>&lt;/p>
&lt;p>&lt;a href="Churn_Modelling_Germany.csv">Churn_Modelling_Germany.csv&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Add &lt;code>/more_data&lt;/code> entry to &lt;code>data/.gitignore&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Create and explore &lt;code>notebooks/DriftDetection.ipynb&lt;/code>&lt;/p>
&lt;p>&lt;a href="DriftDetection.ipynb">DriftDetection.ipynb&lt;/a>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h3 id="incorporate-drift-detection-into-the-dvc-pipeline">Incorporate drift detection into the DVC pipeline&lt;/h3>
&lt;details>
&lt;summary>Create &lt;code>src/stages/drift_detector.py&lt;/code>&lt;/summary>
&lt;pre>&lt;code>import sys
from pathlib import Path
src_path = Path(__file__).parent.parent.resolve()
sys.path.append(str(src_path))
import argparse
import pandas as pd
from alibi_detect.cd import TabularDrift
from alibi_detect.saving import save_detector
from joblib import load
from utils.load_params import load_params
def train_drift_detector(params):
processed_data_dir = Path(params.data_split.processed_data_dir)
model_dir = Path(params.train.model_dir)
model_path = Path(params.train.model_path)
model = load(model_path)
X_test = pd.read_pickle(processed_data_dir/'X_test.pkl')
X_train = pd.read_pickle(processed_data_dir/'X_train.pkl')
X = pd.concat([X_test, X_train])
feat_names = X.columns.tolist()
preprocessor = model[:-1]
categories_per_feature = {i:None for i,k in enumerate(feat_names) if k.startswith('cat__')}
cd = TabularDrift(X,
p_val=.05,
preprocess_fn=preprocessor.transform,
categories_per_feature=categories_per_feature)
detector_path = model_dir/'drift_detector'
save_detector(cd, detector_path)
if __name__ == '__main__':
args_parser = argparse.ArgumentParser()
args_parser.add_argument('--config', dest='config', required=True)
args = args_parser.parse_args()
params = load_params(params_path=args.config)
train_drift_detector(params)
&lt;/code>&lt;/pre>
&lt;/details>
&lt;ul>
&lt;li>
&lt;p>Add &lt;code>train_drift_detector&lt;/code> stage to &lt;code>dvc.yaml&lt;/code>&lt;/p></description></item><item><title>Work With Me</title><link>https://alex000kim.com/work_with_me/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://alex000kim.com/work_with_me/</guid><description>&lt;p>I am available for consulting and training engagements.&lt;/p>
&lt;p>Organizations I&amp;rsquo;ve worked with include:
&lt;a href="https://www.mckinsey.com/">McKinsey &amp;amp; Company&lt;/a>,
&lt;a href="https://www.concordiabootcamps.ca/">Concordia Bootcamps&lt;/a>,
&lt;a href="https://www.splunk.com/">Splunk&lt;/a>,
&lt;a href="https://www.bloomtech.com/">BloomTech&lt;/a>,
&lt;a href="https://tripleten.com/">TripleTen&lt;/a>,
&lt;a href="https://www.oreilly.com/">O&amp;rsquo;Reilly Media&lt;/a>,
&lt;a href="https://iterative.ai/">Iterative.ai&lt;/a>,
&lt;a href="https://platacard.mx/en">Platacard.mx&lt;/a>,
&lt;a href="https://www.nebius.com/">Nebius&lt;/a>,
&lt;a href="https://www.grainger.com/">Grainger&lt;/a> and others.&lt;/p>
&lt;div style="display: flex; flex-wrap: wrap; align-items: center; gap: 16px; margin: 24px 0;">
&lt;img src="logos/mckinsey.png" alt="McKinsey &amp; Company" style="height: 60px; background: white; padding: 12px 14px; border-radius: 8px;"/>
&lt;img src="logos/splunk_logo.png" alt="Splunk" style="height: 60px; background: white; padding: 12px 14px; border-radius: 8px;"/>
&lt;img src="logos/grainger.png" alt="Grainger" style="height: 60px; background: white; padding: 12px 14px; border-radius: 8px;"/>
&lt;img src="logos/bloomtech.png" alt="BloomTech" style="height: 60px; background: white; padding: 12px 14px; border-radius: 8px;"/>
&lt;img src="logos/plata_logo.png" alt="Platacard" style="height: 60px; background: white; padding: 12px 14px; border-radius: 8px;"/>
&lt;img src="logos/tripleten.png" alt="TripleTen" style="height: 60px; background: white; padding: 12px 14px; border-radius: 8px;"/>
&lt;img src="logos/nebius.png" alt="Nebius" style="height: 60px; background: white; padding: 12px 14px; border-radius: 8px;"/>
&lt;img src="logos/concordia_logo.png" alt="Concordia University" style="height: 60px; background: white; padding: 12px 14px; border-radius: 8px;"/>
&lt;/div>
&lt;summary> &lt;b>Client testimonials&lt;/b> &lt;/summary>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://www.linkedin.com/in/anton-tarasenko-230a6877/">Anton Tarasenko&lt;/a>, Co-founder and CBPO of &lt;a href="https://platacard.mx/en">Platacard.mx&lt;/a>:&lt;/p></description></item></channel></rss>