跳至主要内容

GitHub Actions 与 MakeFile:实操入门

学习使用 Makefile 和 GitHub Actions 自动化生成数据报告。
· 11分钟 阅读

使用 AI 探索

作为数据科学家,我们通常不参与部署与运维——我们构建统计模型,其余由工程师完成。

不过,这种情况正在改变!

随着市场对能够贯通模型创建与生产落地的数据科学家的需求不断增加,熟悉自动化与 CI/CD(持续集成/持续交付)工具将成为一项战略优势。

在本教程中,我们将学习两种常用的自动化工具:Make(本地自动化)和 GitHub Actions(云端自动化)。我们的重点是将这些工具融入数据项目中。

GitHub Actions and MakeFile tutorial cover

GitHub Action 与 Make。图片作者:Abid Ali Awan。

如果您想进一步了解数据科学中的自动化,可查看此课程:Fully Automated MLOps。

认识 MakeFile

Makefile 是用于构建和管理软件项目的蓝图。它是一个包含任务自动化指令的文件,能简化复杂的构建流程并确保一致性。

要执行 Makefile 中的命令,我们使用 make 命令行工具。这个工具的用法类似 Python 程序,可以传入参数或直接运行。

核心组成

一个 Makefile 通常由目标、依赖、动作与变量构成:

  • Target(目标):期望的结果,如 "build"、"test" 或 "clean"。可以将其理解为通过 Makefile 要达成的目标。
  • Dependencies(依赖):构建目标所需的文件。可将其视为每道“配方”(目标)所需的原料。
  • Actions(动作):告诉系统如何构建目标的指令或脚本,例如 python test.py。
  • Variables(变量):Makefile 提供的全局变量,作用类似传递给动作的参数。以上述示例为例,我们可以使用 python test.py $variable_1 $variable_2。

简而言之,MakeFile 的模板可能如下所示:

Variable = some_value

Target: Dependencies
    Actions (commands to build the target) $Variable

结合上述示例,MakeFile 可能如下:

# Define variables
variable_1 = 5
variable_2 = 10

# Target to run a test with variables
test: test_data.txt # Dependency on test data file
	python test.py $variable_1 $variable_2

安装 Make CLI 工具

我们可以在所有操作系统上安装 Make CLI 工具。

Linux

在 Linux 上,在终端中使用以下命令:

$ sudo apt install make

MacOS

在 macOS 上,可使用 homebrew 安装 make:

$ brew install make

Windows

Windows 略有不同。Make 工具有多种使用方式。最常见的选项包括 WSL(Ubuntu Linux)、w64devkit 和 GnuWin32。

让我们从 SourceForge 页面下载并安装 GnuWin32。

若要在终端中将其作为命令行工具使用,需要将其文件夹位置添加到 Windows 环境变量。可按以下步骤操作:

  • 在开始菜单中搜索 "environment variables"——这会跳转至系统属性窗口。
  • 在系统属性窗口中,单击环境变量按钮。
  • 在系统变量区域找到 "Path" 变量并编辑——将会打开一个新窗口,您可以在其中添加 Make 工具目录的新路径。

Setting environment variable in windows

要测试 Make 是否安装成功,可在 PowerShell 中输入 make -h。

Make CLI tool in Windows PowerShell

在数据科学项目中使用 MakeFile

本节我们将学习如何在数据科学项目中使用 Makefile 和 Make CLI 工具。

我们将使用 World Happiness Report 2023 数据集,涵盖数据处理、分析,以及保存数据摘要与可视化。

环境搭建

首先进入项目目录并创建名为 Makefile-Action 的文件夹。然后在其中创建 data_processing.py 文件并启动 VSCode 编辑器。

$ cd GitHub
$ mkdir Makefile-Action
$ cd .\Makefile-Action\
$ code data_processing.py

数据处理

我们先用 Python 处理数据。下方代码块中,我们将执行以下步骤(注意:这是示例代码——在真实场景中,步骤可能更多):

  1. 从用户提供的目录位置加载原始数据集。
  2. 重命名所有列以提升可读性。
  3. 填充缺失值。
  4. 保存清洗后的数据集。

文件:data_processing.py

import sys
import pandas as pd

# Check if the data location argument is provided
if len(sys.argv) != 2:
    print("Usage: python data_processing.py <data_location>")
    sys.exit(1)

# Load the raw dataset (step 1)
df = pd.read_csv(sys.argv[1])

# Rename columns to more descriptive names (step 2)
df.columns = [
    "Country",
    "Happiness Score",
    "Happiness Score Error",
    "Upper Whisker",
    "Lower Whisker",
    "GDP per Capita",
    "Social Support",
    "Healthy Life Expectancy",
    "Freedom to Make Life Choices",
    "Generosity",
    "Perceptions of Corruption",
    "Dystopia Happiness Score",
    "GDP per Capita",
    "Social Support",
    "Healthy Life Expectancy",
    "Freedom to Make Life Choices",
    "Generosity",
    "Perceptions of Corruption",
    "Dystopia Residual",
]

# Handle missing values by replacing them with the mean (step 3)
df.fillna(df.mean(numeric_only=True), inplace=True)

# Check for missing values after cleaning
print("Missing values after cleaning:")
print(df.isnull().sum())

print(df.head())

# Save the cleaned and normalized dataset to a new CSV file (step 4)
df.to_csv("processed_data\WHR2023_cleaned.csv", index=False)

数据分析

接下来进行数据分析,并将所有代码保存到名为 data_analysis.py 的文件中。可用以下终端命令创建该文件:

$ code data_analysis.py

我们也可以使用 echo 命令创建该 Python 文件,并在其他代码编辑器中打开。

$ echo "" > data_analysis.py

在下方代码块中,我们将:

  1. 根据用户提供的输入加载清洗后的数据集。
  2. 生成数据摘要、数据信息以及 DataFrame 的前 5 行,并保存到 TXT 文件。
  3. 绘制以下可视化:
    • 幸福分数的分布
    • 按幸福分数排名前 20 的国家
    • 幸福分数与人均 GDP 的关系
    • 幸福分数与社会支持的关系
    • 相关性热力图
  4. 将所有可视化保存到“figures”文件夹。

文件:data_analysis.py

import io
import sys

import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

# Check if the data location argument is provided
if len(sys.argv) != 2:
    print("Usage: python data_analysis.py <data_location>")
    sys.exit(1)

# Load the clean dataset (step 1)
df = pd.read_csv(sys.argv[1])

# Data summary (step 2)
print("Data Summary:")
summary = df.describe()
data_head = df.head()
print(summary)
print(data_head)

# Collecting data information
buffer = io.StringIO()
df.info(buf=buffer)
info = buffer.getvalue()

## Write metrics to file
with open("processed_data/summary.txt", "w") as outfile:
    f"\n## Data Summary\n\n{summary}\n\n## Data Info\n\n{info}\n\n## Dataframe\n\n{data_head}"

print("Data summary saved in processed_data folder!")

# Distribution of Happiness Score (step 3)
plt.figure(figsize=(10, 6))
sns.displot(df["Happiness Score"])
plt.title("Distribution of Happiness Score")
plt.xlabel("Happiness Score")
plt.ylabel("Frequency")
plt.savefig("figures/happiness_score_distribution.png")

# Top 20 countries by Happiness Score
top_20_countries = df.nlargest(20, "Happiness Score")
plt.figure(figsize=(10, 6))
sns.barplot(x="Country", y="Happiness Score", data=top_20_countries)
plt.title("Top 20 Countries by Happiness Score")
plt.xlabel("Country")
plt.ylabel("Happiness Score")
plt.xticks(rotation=90)
plt.savefig("figures/top_20_countries_by_happiness_score.png")

# Scatter plot of Happiness Score vs GDP per Capita
plt.figure(figsize=(10, 6))
sns.scatterplot(x="GDP per Capita", y="Happiness Score", data=df)
plt.title("Happiness Score vs GDP per Capita")
plt.xlabel("GDP per Capita")
plt.ylabel("Happiness Score")
plt.savefig("figures/happiness_score_vs_gdp_per_capita.png")

# Visualize the relationship between Happiness Score and Social Support
plt.figure(figsize=(10, 6))
plt.scatter(x="Social Support", y="Happiness Score", data=df)
plt.xlabel("Social Support")
plt.ylabel("Happiness Score")
plt.title("Relationship between Social Support and Happiness Score")
plt.savefig("figures/social_support_happiness_relationship.png")

# Heatmap of correlations between variables
corr_matrix = df.drop("Country", axis=1).corr()
plt.figure(figsize=(12, 10))
sns.heatmap(corr_matrix, annot=True, cmap="coolwarm", square=True)
plt.title("Correlation Heatmap")
plt.savefig("figures/correlation_heatmap.png")
print("Visualizations saved to figures folder!")

创建 Makefile

在创建 Makefile 之前,我们需要先设置 requirements.txt 文件,以便在新机器上安装所有必需的 Python 包。该文件内容如下:

pandas
numpy
seaborn
matplotlib
black

我们的 Makefile 由变量、依赖、目标与动作组成——我们将学习这些构建块。创建名为 Makefile 的文件,并为各目标添加动作:

  • install:升级 pip 版本,并使用 requirements.txt 安装所有必要的 Python 包。
  • format:使用 Black 工具格式化所有 Python 文件。
  • process:运行 `data_processing.py`,以原始数据路径作为变量和依赖。
  • analyze:运行 `data_analysis.py`,以处理后数据路径作为变量和依赖。
  • clean:删除运行 Python 文件时生成的所有文件与图像。
  • all:按序运行所有有效目标。

文件:Makefile

RAW_DATA_PATH = "raw_data/WHR2023.csv"
PROCESSED_DATA = "processed_data/WHR2023_cleaned.csv"

install:
    pip install --upgrade pip &&\
        pip install -r requirements.txt

format:
        black *.py --line-length 88

process: ./raw_data/WHR2023.csv
        python data_processing.py $(RAW_DATA_PATH)

analyze: ./processed_data/WHR2023_cleaned.csv
        python data_analysis.py $(PROCESSED_DATA)

clean:
        rm -f processed_data/* **/*.png

all: install format process analyze

要执行目标,我们将在终端中使用 make 工具并提供目标名称。

$ make format

Black 脚本已成功运行。

running make command in terminal

我们来尝试运行数据处理的 Python 脚本。

需要注意的是,在运行 process 目标前,它会检查目标依赖。在此例中,它会检查原始数据文件是否存在;若不存在,命令将不会启动。

$ make process

如您所见,操作非常简单。

running make command

您甚至可以在 make 命令中提供额外参数来覆盖现有变量。

在我们的例子中,我们更改了原始数据的路径。

$ make process RAW_DATA_PATH="WHR2022.csv"

Python 脚本已使用不同的输入参数运行。

running make command with input argument

为实现整个流程自动化,我们将使用 all 作为目标,它会依次执行安装、格式化、处理与分析等目标。

$ make all

make 命令已安装 Python 包、格式化代码、处理数据,并保存了摘要与可视化。

terminal output

data visualization on World Happiness dataset 2023

运行 make all 命令后,您的项目目录应如下所示:

Project files and folders

认识 GitHub Actions

虽然 Make 在本地开发环境中的自动化表现出色,但 GitHub Actions 提供了一种云端替代方案。

GitHub Actions 通常用于 CI/CD,允许开发者直接从 GitHub 编译、构建、测试并将应用部署到生产环境。

例如,我们可以创建一个自定义工作流程,并基于 push 或 pull request 等特定事件触发。该工作流程将运行 shell 脚本与 Python 脚本,或使用预构建的 Actions。自定义工作流程是一个 YML 文件,通常易于理解,也便于开始编写自定义的运行与动作。

核心组成

我们来了解 GitHub Actions 的核心组件:

  • Workflows(工作流程):使用 YML 文件定义,指定应执行的步骤及其执行条件。
  • Events(事件):触发工作流程运行的特定活动,如 push、pull_request、schedule 等。
  • Runners(运行器):在触发时执行工作流程的机器。GitHub 提供 Linux、Windows 和 macOS 的托管免费运行器。
  • Jobs(作业):在同一运行器上执行的一组步骤。默认并行运行,也可配置为顺序执行。
  • Steps(步骤):运行命令或 Actions 的任务,类似于 Makefile 的动作。
  • Actions(动作):GitHub Actions 平台上的自定义应用,用于执行复杂但高频的任务。可选的 Actions 很多,多由开源社区维护。

GitHub Actions 入门

本节我们将学习使用 GitHub Actions,并尝试复用此前在 Makefile 中使用的命令。

要将代码转移到 GitHub,我们需要将项目文件夹转换为 Git 仓库:

$ git init

然后在 GitHub 上创建一个新仓库并复制其 URL。

Creating the new GitHub repository

在终端中输入以下命令以:

  1. 添加远程仓库链接。
  2. 从远程仓库拉取文件。
  3. 添加并提交所有文件并附上提交消息。
  4. 将本地 master 推送到远程 main 分支。
$ git remote add github https://github.com/kingabzpro/Makefile-Actions.git
$ git pull github main
$ git add . 
$ git commit -m "adding all the files"
$ git push github master:main

另外说明:如果您需要快速回顾 GitHub 或 Git,请查看这篇面向初学者的教程 GitHub and Git。它将帮助您使用 Git CLI 工具为数据科学项目做版本管理并与团队共享。

继续我们的课程——可以看到,所有文件都已成功转移至远程仓库。

Pushing the files to remote repository

接下来,我们将创建一个 GitHub Action 工作流程。首先进入仓库 kingabzpro/Makefile-Actions 的 Actions 选项卡。然后点击蓝色文字 “set up a workflow yourself.”

getting started with GitHub actions

随后将跳转至 YML 文件页面,我们会在其中编写用于设置环境和执行命令的全部代码。我们将:

  1. 提供工作流程名称。
  2. 设置触发事件:当代码推送到 main 分支或对 main 分支发起 pull request 时执行。
  3. 使用最新的 Ubuntu Linux 机器设置一个作业。
  4. 在步骤部分使用 checkout v3 动作,帮助工作流程访问仓库。
  5. 安装所需的 Python 包、格式化代码,并使用变量运行两个 Python 脚本。
name: Data Processing and Analysis

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Install Packages
        run: |
          pip install --upgrade pip
          pip install -r requirements.txt
      - name: Format
        run: black *.py --line-length 88
      - name: Data Processing
        env:
          RAW_DATA_DIR: "./raw_data/WHR2023.csv"
        run: python data_processing.py $RAW_DATA_DIR
      - name: Data Analysis
        env:
          CLEAN_DATA_DIR: "./processed_data/WHR2023_cleaned.csv"
        run: python data_analysis.py $CLEAN_DATA_DIR

提交工作流程文件并附上消息后,工作流程将自动开始运行。

running the workflow

完成所有步骤至少需要 20 秒。

要查看日志,我们先点击工作流程运行记录,再点 Build 按钮,然后逐一点击各作业以查看日志。

Build logs of GitHub Actions

如果您觉得这部分有趣,那么您一定会喜欢这篇 机器学习 CI/CD 新手指南,其中涵盖了构建和自动化机器学习工作流程的基础知识。

将 GitHub Actions 与 MakeFile 结合

为简化并标准化 GitHub Action 工作流程,开发者会在工作流程文件中使用 Make 命令。本节我们将学习如何用 Make 命令简化工作流程代码,并学习使用连续机器学习(CML)Action。

开始之前,我们需要从远程仓库拉取工作流程文件。

$ git pull

使用 CML

Continuous Machine Learning(CML)是 iterative.ai 的开源库,使我们能够在数据科学项目中实现持续集成。

在本项目中,我们将使用 iterative/setup-cml GitHub Action,在工作流程中调用 CML 函数,借助图形与数据统计生成分析报告。

报告将被创建并附加到我们的 GitHub 提交,供团队在合并前审阅与批准。

我们会修改 Makefile,新增一个目标 “summary”。注意:

  • 该目标是多行脚本,会将 summary.txt 中的文本数据转移到 report.md。
  • 随后逐一添加各图的标题及用于在 report.md 中显示图像的 Markdown 代码。
  • 最后,我们将使用 cml 工具创建数据分析报告,并将其显示在提交评论下方。

想象一下若在 GitHub 工作流程文件中加入这么多行,阅读与修改都会很困难。相反,我们将使用 make summary。

文件:Makefile

summary: ./processed_data/summary.txt
    echo "# Data Summary" > report.md
    cat ./processed_data/summary.txt >> report.md
   
    echo '\n# Data Analysis' >> report.md
    echo '\n## Correlation Heatmap' >> report.md
    echo '![Correlation Heatmap](./figures/correlation_heatmap.png)' >> report.md

    echo '\n## Happiness Score Distribution' >> report.md
    echo '![Happiness Score Distribution](./figures/happiness_score_distribution.png)' >> report.md

    echo '\n## Happiness Score vs GDP per Capita' >> report.md
    echo '![Happiness Score vs GDP per Capita](./figures/happiness_score_vs_gdp_per_capita.png)' >> report.md
   
    echo '\n## Social Support vs Happiness Relationship' >> report.md
    echo '![Social Support Happiness Relationship](./figures/social_support_happiness_relationship.png)' >> report.md
   
    echo '\n## Top 20 Countries by Happiness Score' >> report.md
    echo '![Top 20 Countries by Happiness Score](./figures/top_20_countries_by_happiness_score.png)' >> report.md
   
    cml comment create report.md

Editing the workflow file using make command.

修改 GiHub Action 文件

现在我们来编辑 main.yml 文件,它位于 .github/workflows 目录中。

我们将把所有命令与 Python 脚本替换为 make 命令,并为 CML Action 授予权限,以便在提交评论中创建数据报告。另外,别忘了在运行中加入 iterative/setup-cml@v3 Action。

name: Data Processing and Analysis

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

permissions: write-all

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: iterative/setup-cml@v3
      - name: Install Packages
        run: make install
      - name: Format
        run: make format
      - name: Data Processing
        env:
          RAW_DATA_DIR: "./raw_data/WHR2023.csv"
        run: make process RAW_DATA_PATH=$RAW_DATA_DIR
      - name: Data Analysis
        env:
          CLEAN_DATA_DIR: "./processed_data/WHR2023_cleaned.csv"
        run: make analyze PROCESSED_DATA=$CLEAN_DATA_DIR
      - name: Data Summary
        env:
          REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: make summary

运行工作流程

要执行工作流程,我们只需提交所有更改并与远程 main 分支同步。

$ git commit -am "makefile and github action combo"
$ git push github master:main

running GitHub action workflow

该工作流程运行耗时 32 秒——我们的报告已准备好供审阅。为查看报告,我们进入构建摘要,下拉点击 Data Summary 选项卡,然后点击评论链接(如下图第 20 行所示):

workflow successfully finished

如您所见,数据摘要与数据可视化已附加到我们的提交中。

Automated analytical report generated using the GitHub Actions "CML".

该项目已发布在 kingabzpro/Makefile-Actions,当您遇到问题时可将其作为参考。该仓库为公开仓库,直接 fork 并亲自体验吧!

想获得 GitHub 认证吗?请查看我们关于不同 GitHub 认证的综合指南!

结语

本教程重点介绍了使用 Makefile 与 GitHub Actions 来实现数据分析报告的自动化生成。

我们还学习了如何构建并运行工作流程,以及如何在 GitHub 工作流程中使用 Make 工具以优化与简化自动化过程。不必编写多行代码,只需使用 make summary 便可生成一份将附加到提交中的数据分析报告。

如果您想系统掌握数据科学的 CI/CD 实践,建议学习本课程:CI/CD for Machine Learning。课程涵盖 CI/CD、GitHub Actions、数据版本控制,以及模型超参数优化与评估自动化的基础知识。

Abid Ali Awan

作者

Abid Ali Awan

作为一名持证的数据科学家,我热衷于利用前沿技术打造创新的机器学习应用。凭借在语音识别、数据分析与报告、MLOps、对话式人工智能以及自然语言处理方面的扎实背景,我不断打磨构建智能系统的能力,力求带来切实影响。除技术专长外,我也擅长沟通,能够将复杂概念提炼为清晰、简明的表述。因此,我成为数据科学领域备受关注的博主,与不断壮大的数据专业人士社区分享洞见与实践经验。目前,我专注于内容创作与编辑,借助大语言模型打造有力且吸引人的内容,帮助企业与个人更好地发挥数据价值。

主题