一、实验跟踪(Experiental Tracking)

在搭建机器学习模型的过程中我们会进行多次试验; 每次 实验中我们会得到与 机器学习模型关联的任何文件:包括模型本身、包版本、超参数等。我们需要跟踪机器学习实验的所有相关信息; 实验跟踪有助于再现性、组织和优化我们的训练过程。

常见的实验跟踪工具包括MLflow和Weight & Bias

1. MLflow

MLflow是一个机器学习生命周期的开源平台,它主要针对以下几个方面来对实验进行追踪,分别是Tracking、Models、Model registry、Projects。

MLflow 将每次实验作为一次run,并跟踪可能影响模型及其结果的任何变量; 例如:参数、指标、元数据、模型本身…MLflow 还会自动记录每次运行的额外信息,例如:源代码、Git 提交、开始和结束时间以及作者。

要在本地运行 MLflow UI,我们使用以下命令,在此命令中,我们使用 SQLite 后端以及当前运行存储库中的文件 mlflow.db:

mlflow ui --backend-store-uri sqlite:///mlflow.db

(1)实验跟踪

在实验跟踪中,我们首先需要配置跟踪 URI 和当前实验名称

import mlflow
mlflow.set_tracking_uri("sqlite:///mlflow.db")
mlflow.set_experiment("nyc-taxi-experiment")


# 加载数据集和模型
x_train = ...
y_train = ...

之后初始化mlflow的运行并使用三个 mlflow 命令跟踪相关信息:

  • set_tag 用于元数据标签
  • log_param 用于记录模型参数
  • log_metric 用于记录模型指标
  • log_model 用于记录模型
  • log_artifact 用于记录模型相关的方法
with mlflow.start_run():
    mlflow.set_tag("developer","Qfl3x")
    
    mlflow.log_param("train-data-path", "data/green_tripdata_2021-01.parquet")
    mlflow.log_param("val-data-path", "data/green_tripdata_2021-02.parquet")
    
    alpha = 0.01
    mlflow.log_param("alpha", alpha)
    lr = Lasso(alpha)
    lr.fit(X_train, y_train)
    
    y_pred = lr.predict(X_val)
    rmse = mean_squared_error(y_val, y_pred, squared=False)
    mlflow.log_metric("rmse", rmse)
    mlflow.sklearn.log_model(lr , artifact_path="models_mlflow")
    mlflow.log_artifact("vectorizer.pkl", artifact_path="extra_artifacts")

我们也可以使用autolog()来自动记录参数,例如

mlflow.autolog()
mlflow.xgboost.autolog()

(2)超参数优化

Hyperopt

Hyperopt是一个用于优化机器学习模型超参数的Python库。它通过搜索超参数空间来最大化或最小化指定的目标函数。以下是Hyperopt可以做的一些主要功能:

  • 超参数优化: Hyperopt主要用于优化机器学习模型的超参数。这些超参数包括学习率、层数、节点数等。通过搜索超参数空间,Hyperopt试图找到最优的超参数组合,从而提高模型性能。
  • 搜索算法: Hyperopt支持不同的搜索算法,包括随机搜索、贝叶斯优化等。这些算法帮助在超参数空间中高效地搜索,以找到最佳的超参数配置。
  • 目标函数最小化/最大化: 用户可以定义一个目标函数,该函数返回模型在给定超参数配置下的性能指标。Hyperopt根据用户的选择来最小化或最大化这个目标函数。
  • 并行优化: Hyperopt支持并行优化,允许同时评估多个超参数组合,从而加速搜索过程。
  • 分布式计算: Hyperopt可以与分布式计算框架(如Dask)一起使用,以便在大规模数据集和计算资源上进行高效的超参数优化。
  • 自定义搜索空间: 用户可以定义自己感兴趣的超参数搜索空间,并使用Hyperopt进行搜索。这使得Hyperopt非常灵活,可以适应各种不同类型的模型和超参数设置。
集成Hyperopt和MLflow

通过将 hyperopt 优化目标包装在 with mlflow.start_run() 块中,我们可以跟踪 hyperopt 运行的每个优化运行。然后我们记录 hyperopt 传递的参数以及指标,如下所示:

import xgboost as xgb

from hyperopt import fmin, tpe, hp, STATUS_OK, Trials
from hyperopt.pyll import scope

train = xgb.DMatrix(X_train, label=y_train)
valid = xgb.DMatrix(X_val, label=y_val)

def objective(params):
    with mlflow.start_run():
        mlflow.set_tag("model", "xgboost")
        mlflow.log_params(params)
        booster = xgb.train(
            params=params,
            dtrain=train,
            num_boost_round=1000,
            evals=[(valid, 'validation')],
            early_stopping_rounds=50
        )
        y_pred = booster.predict(valid)
        rmse = mean_squared_error(y_val, y_pred, squared=False)
        mlflow.log_metric("rmse", rmse)

    return {'loss': rmse, 'status': STATUS_OK}

search_space = {
    'max_depth': scope.int(hp.quniform('max_depth', 4, 100, 1)),
    'learning_rate': hp.loguniform('learning_rate', -3, 0),
    'reg_alpha': hp.loguniform('reg_alpha', -5, -1),
    'reg_lambda': hp.loguniform('reg_lambda', -6, -1),
    'min_child_weight': hp.loguniform('min_child_weight', -1, 3),
    'objective': 'reg:linear',
    'seed': 42
}

best_result = fmin(
    fn=objective,
    space=search_space,
    algo=tpe.suggest,
    max_evals=50,
    trials=Trials()
)

在以上代码中,我们定义了搜索空间和运行优化器的目标。 我们使用 mlflow.start_run() 将训练和验证块包装在内部,并使用 log_params 记录使用的参数,并使用 log_metric 验证 RMSE。

(3)模型注册

我们可以使用mlflow的load_model方法来加载我们保存的模型

logged_model = 'runs:/{Model UUID in MLflow}/models' 
xgboost_model = mlflow.xgboost.load_model(logged_model)

模型注册

mlflow.set_tracking_uri(MLFLOW_TRACKING_URI)

run_id = run_id 
model_uri = f"runs:/{run_id}/models"
mlflow.register_model(model_uri=model_uri, name="model-name")

模型转换staging

from mlflow.tracking import MlflowClient
MLFLOW_TRACKING_URI = "sqlite:///mlflow.db"
client = MlflowClient(tracking_uri=MLFLOW_TRACKING_URI)

model_version = 4
new_stage = "Staging"
client.transition_model_version_stage(
    name=model_name,
    version=model_version,
    stage=new_stage,
    archive_existing_versions=False
)

2. Weight & Bias

二、模型部署

1. Web服务部署

Web 服务是一种用于在电子设备之间进行通信的方法。Web服务中有一些方法我们可以使用它来解决我们的问题。

  • GET:GET是一种用于检索文件的方法,例如当我们在google中搜索猫图像时,我们实际上是使用GET方法请求猫图像。
  • POST:POST 是 Web 服务中使用的第二种常用方法。 例如,在注册过程中,当我们提交姓名、用户名、密码等时,我们会将数据发布到使用网络服务的服务器。 (请注意,没有指定数据的去向)
  • PUT:PUT 与 POST 相同,但我们指定数据的去向。
  • DELETE:DELETE是一种用于请求从服务器删除某些数据的方法。

Python中的Flask库、Django库都可以来搭建web框架,这里以Flask举例。

from flask import Flask

app = Flask('ping') # give an identity to your web service

@app.route('/ping', methods=['GET']) # use decorator to add Flask's functionality to our function
def ping():
    return 'PONG'

if __name__ == '__main__':
   app.run(debug=True, host='0.0.0.0', port=9696) # run the code in local machine with the debugging mode true and port 9696

对于一个机器学习模型,我们可以通过加载它的模型文件来搭建web服务进行预测

from flask import Flask, render_template, request
import numpy as np
from sklearn.linear_model import LinearRegression

app = Flask(__name__)

# 生成一些虚构的训练数据
X_train = np.array([[1], [2], [3], [4], [5]])
y_train = np.array([2, 4, 5, 4, 5])

# 训练线性回归模型
model = LinearRegression()
model.fit(X_train, y_train)

@app.route('/')
def home():
    return render_template('index.html')

@app.route('/predict', methods=['POST'])
def predict():
    if request.method == 'POST':
        try:
            input_data = float(request.form['input_data'])
            input_data = np.array([[input_data]])

            # 使用训练好的模型进行预测
            prediction = model.predict(input_data)[0]

            return render_template('index.html', prediction=prediction)
        except ValueError:
            return render_template('index.html', error="请输入有效的数值")

if __name__ == '__main__':
    app.run(debug=True)

我们使用模板来优化我们的页面

<!DOCTYPE html>
<html>
<head>
    <title>线性回归预测</title>
</head>
<body>
    <h1>线性回归预测</h1>
    <form action="/predict" method="post">
        <label for="input_data">输入数据:</label>
        <input type="text" name="input_data" id="input_data" placeholder="请输入数值">
        <button type="submit">预测</button>
    </form>

    {% if prediction %}
        <p>预测结果: {{ prediction }}</p>
    {% endif %}

    {% if error %}
        <p style="color: red;">{{ error }}</p>
    {% endif %}
</body>
</html>

2. Docker

Docker是一种容器化服务。使用 Docker可以将所有项目打包为您想要的系统,并在任何系统机器上运行它。

首先我们需要编写DockerFile来创建镜像

# 使用基础镜像
FROM python:3.8

# 设置工作目录
WORKDIR /app

# 复制应用程序的依赖文件到工作目录
COPY requirements.txt .

# 安装应用程序的依赖
RUN pip install --no-cache-dir -r requirements.txt

# 复制当前目录中的所有文件到工作目录
COPY . .

# 暴露应用程序运行的端口
EXPOSE 5000

# 启动应用程序
CMD ["python", "app.py"]

之后创建并运行镜像启动web服务后,我们可以发送请求来获取预测结果。

docker build -t your-image-name .
docker run -p 5000:5000 your-image-name

Docker Compose

Docker Compose是一个用于定义和运行多容器Docker应用程序的工具。通过一个简单的YAML文件,可以配置应用程序的服务、网络和卷,并使用docker-compose命令启动、停止和管理整个应用程序的生命周期。

在Docker Compose中,我们需要定义以下内容:

  • 版本:Docker Compose文件的版本号通常在文件的顶部指定。版本号影响可以使用的Compose功能。
  • 服务(services):定义您要在Compose中运行的各个服务。每个服务都包括服务的名称、使用的镜像、端口映射等信息。
    • 环境变量:可以在Compose文件中定义服务的环境变量,这些变量将传递给容器。
    • 端口:指定端口号用于容器间访问
    • 网络:Docker Compose会为定义的服务创建默认网络,服务可以通过服务名称相互访问。
    • 卷:使用卷可以在容器之间共享数据。在Compose文件中,可以定义卷并将其分配给服务。
    • 构建:如果您的应用程序需要自定义镜像,可以在Compose文件中定义build部分,指定Dockerfile的路径。
    • 依赖关系(depends_on):如果一个服务依赖于另一个服务,您可以使用depends_on来定义这些依赖关系。这并不意味着服务一定会在另一个完全启动之后才启动,但可以确保它们的启动顺序。
version: '3'
services:
  service1:
    image: service1_image
    ports:
      - "5000:5000"

  service2:
    build:
      context: ./service2
    depends_on:
      - service1
    environment:
      - MODEL_NAME=model.pkl  # Add the environment variable

以上是一个简单的示例,service1 将通过 HTTP 提供输出数据,并在端口 5000 上监听。service2 依赖于 service1,并可以访问 service1 提供的数据并运行机器学习模型来预测该输出。

Kubernetes

Kubernetes 是一个用于自动部署、扩展和操作容器化应用程序的开源平台。它提供了一个可移植、可扩展且易于管理的容器编排解决方案。我们可以在Kubernetes部署我们的Docker容器。

Kubernetes 的核心概念:

  • Pod(容器组)Pod 是 Kubernetes 中最小的部署单元,它包含一个或多个容器,并共享相同的网络和存储空间。通常,一个 Pod 包含一个主容器,以及可能的辅助容器(sidecar),共同协同完成某个任务。
  • Service(服务):Service 定义了一组 Pod 的逻辑集合,并提供一个稳定的网络端点,以便其他应用程序可以访问这组 Pod。它充当了负载均衡器,可以将请求分发给 Pod 组中的任何一个。
  • ReplicaSet(副本集):ReplicaSet 确保指定数量的 Pod 副本在任何时候都在运行。如果有 Pod 发生故障或被删除,ReplicaSet 会启动新的 Pod 来替代。ReplicaSet 通常与 Deployment 结合使用,Deployment 提供了对 ReplicaSet 的声明性定义,可以轻松实现应用程序的滚动更新。
  • Deployment(部署):Deployment 提供了一种声明性的方式来定义应用程序的部署规范。它允许你指定 Pod 的副本数、更新策略等,从而简化了应用程序的管理。Deployment 控制 ReplicaSet,并且可以实现滚动更新、回滚等操作。

三、最佳实践

1. 单元测试

在Python中,单元测试是一种测试方法,用于验证程序的各个部分是否按照预期工作。pytest是Python中一种流行的测试框架,它简化了单元测试的编写和执行。以下是一个简单的实例

假设有一个简单的函数,对两个数进行加法:

# my_math.py

def add(x, y):
    return x + y

我们将为这个函数编写一个单元测试,测试函数直接使用assert语句来检查条件是否为真,测试函数的名称以test_开头:

# test_my_math.py

from my_math import add

def test_add_positive_numbers():
    assert add(2, 3) == 5

def test_add_negative_numbers():
    assert add(-2, -3) == -5

def test_add_mixed_numbers():
    assert add(2, -3) == -1

要运行这些测试,只需在命令行中执行,pytest将自动查找以test_开头的文件和函数,并执行这些测试。如果所有测试通过,你将看到一个简洁的输出。如果有测试失败,pytest将提供详细的错误信息,帮助你识别问题所在。

pytest

2. Terraform

参考:Terraform学习

Terraform 是一个开源的基础设施即代码(Infrastructure as Code,IaC)工具。它允许开发人员使用声明性的配置语言定义基础设施,然后通过命令行工具将该配置部署到各种云提供商(如AWS、Azure、Google Cloud)和本地基础设施中。Terraform 的核心思想是将基础设施的定义与实际的基础设施状态保持同步,实现可重复、可管理的基础设施管理。

Terraform 的核心概念

  • Provider: 提供商,指定了 Terraform 将要使用的云服务提供商或基础设施平台(如 AWS、Azure、Google Cloud)。
  • Resource: 资源,表示基础设施中的可管理对象,如虚拟机、存储桶等。
  • State: 状态,是 Terraform 记录当前基础设施状态的文件,用于跟踪已创建的资源。
  • Module: 模块,是一个可重用的 Terraform 配置单元,允许将代码模块化以便复用。
  • Variable:变量,是在 Terraform 配置中定义的参数,用于传递值到模块或配置文件。变量可以在配置中引用,也可以从外部源(如变量文件或环境变量)获取值。

Terraform 配置文件的扩展名通常为 .tf。配置文件可以包含 Terraform 命令、Provider 配置、资源定义、变量和输出等。下面是一个Terraform的文件结构的示例

my_terraform_project/
|-- main.tf
|-- variable.tf
|-- vars/
|   |-- dev.tfvars
|   |-- prod.tfvars
|-- modules/
|   |-- ec2-instance/
|       |-- main.tf
|       |-- variables.tf
|       |-- outputs.tf

variable.tf 文件定义了全局变量,这些变量将在主 Terraform 配置文件 main.tf 中被引用。这使得在整个项目中可以共享这些变量,而不仅仅是在特定于环境的变量文件中。下面是variable.tf 的例子

variable "region" {
  description = "AWS region"
  type        = string
}

variable "ami_id" {
  description = "AMI ID for the EC2 instance"
  type        = string
}

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
}

variable "key_name" {
  description = "Key pair name for SSH access"
  type        = string
}

variable "subnet_id" {
  description = "Subnet ID for the EC2 instance"
  type        = string
}

variable "security_group_names" {
  description = "List of security group names to associate with the EC2 instance"
  type        = list(string)
}

variable "instance_name" {
  description = "Name tag for the EC2 instance"
  type        = string
}


# 输出定义
output "stream_name" {
  value = aws_kinesis_stream.example_stream.name
}

main.tf 文件是主配置文件,用于调用 EC2 实例模块。

provider "aws" {
  region = var.region
}

module "my_ec2_instance" {
  source      = "./modules/ec2-instance"
  region      = var.region
  ami_id      = var.ami_id
  instance_type = var.instance_type
  key_name    = var.key_name
  subnet_id   = var.subnet_id
  security_group_names = var.security_group_names
  instance_name = var.instance_name
}

output "my_instance_id" {
  value = module.my_ec2_instance.instance_id
}

output "my_instance_public_ip" {
  value = module.my_ec2_instance.public_ip
}

vars 文件夹包含了 dev.tfvars 和 prod.tfvars,分别代表了开发和生产环境的变量。通过使用不同的变量文件,你可以在不同的环境中使用相同的 Terraform 模块,使用 terraform apply -var-file=vars/dev.tfvarsterraform apply -var-file=vars/prod.tfvars 这样的命令来指定特定的环境变量文件。

vars/dev.tfvars 文件:

region = "us-east-1"
ami_id = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
key_name = "dev-keypair"
subnet_id = "subnet-0123456789abcdef0"
security_group_names = ["dev-security-group"]
instance_name = "DevEC2Instance"

vars/prod.tfvars 文件:

region = "us-west-2"
ami_id = "ami-0123456789abcdef0"
instance_type = "t2.large"
key_name = "prod-keypair"
subnet_id = "subnet-0123456789abcdef1"
security_group_names = ["prod-security-group"]
instance_name = "ProdEC2Instance"

modules 文件夹包含 EC2 实例模块,具有自己的 main.tf、variables.tf 和 outputs.tf 文件。使用模块有助于提高 Terraform 代码的可维护性、可读性和可复用性。

module的main.tf 文件:

provider "aws" {
  region = var.region
}

resource "aws_instance" "ec2_instance" {
  ami           = var.ami_id
  instance_type = var.instance_type
  key_name      = var.key_name

  subnet_id = var.subnet_id

  security_group_names = var.security_group_names

  tags = {
    Name = var.instance_name
  }
}

module的variables.tf 文件:

variable "region" {
  description = "AWS region"
}

variable "ami_id" {
  description = "AMI ID for the EC2 instance"
}

variable "instance_type" {
  description = "EC2 instance type"
}

variable "key_name" {
  description = "Key pair name for SSH access"
}

variable "subnet_id" {
  description = "Subnet ID for the EC2 instance"
}

variable "security_group_names" {
  type        = list(string)
  description = "List of security group names to associate with the EC2 instance"
}

variable "instance_name" {
  description = "Name tag for the EC2 instance"
}

module的outputs.tf 文件:

output "instance_id" {
  value = aws_instance.ec2_instance.id
}

output "public_ip" {
  value = aws_instance.ec2_instance.public_ip
}

常用基础命令

  • terraform init :初始化一个包含Terraform代码的工作目录。
  • terraform plan :查看并创建变更计划。
  • terraform apply :生成并执行计划。
  • terraform destroy :销毁并回收所有Terraform管理的基础设施资源。

3. CI/CD

在 DevOps 领域,持续集成 (CI) 和持续部署 (CD) 在确保以结构化且高效的方式开发、测试、打包和交付软件应用程序方面发挥着关键作用。

  • 持续集成(Continuous Integration):持续集成是一种开发实践,其目标是将团队成员的代码集成到主干(主要代码库或分支)中,以便快速发现和解决潜在的代码集成问题。CI 的核心思想是频繁地将代码合并到共享存储库中,并在每次合并时运行自动化测试,以确保新的更改不会破坏现有的代码功能。CI 有助于降低集成问题的风险,并促使团队更频繁地交付高质量的软件。
  • 持续部署(Continuous Deployment):持续部署是在通过持续集成验证代码后,自动将代码部署到生产环境的实践。持续部署通过自动化构建、测试和部署流程,加速软件交付,降低发布的风险,并提高整体的交付效率。

GitHub Actions:对于存储库的每次新提交或代码更改,它将自动触发构建、测试和部署我们的服务的作业。

CI

GitHub Actions中的CI的主要目标是确保新的代码变更能够顺利地集成到主代码库,并且通过运行测试和其他验证步骤来确保代码质量。在CI中,通常会包括以下步骤:检出代码、设置环境、运行测试。我们需要编写YAML文件来实现CI过程,需要包含以下关键内容:

  • 触发器(Triggers): 指定何时运行工作流程。这通常包括push事件、pull请求事件或定时触发。例如在main分支上的push或pull请求时触发工作流程。
  • 作业(Jobs): 定义一个或多个作业,每个作业运行在一个独立的虚拟环境中。例如:有一个名为test的作业,它运行在ubuntu-latest虚拟环境中,包含了一些步骤(Steps),指定义在作业中执行的一系列操作,比如检出仓库、设置环境和运行测试。
  • 环境变量(Environment Variables): 设置工作流程中需要使用的环境变量,例如密钥、配置信息等。

下面是一个CI的例子

name: CI

on:
  push:
    branches:
      - main

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout Repository
      uses: actions/checkout@v2

    - name: Setup Python
      uses: actions/setup-python@v2
      with:
        python-version: 3.9

    - name: Install Dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt

    - name: Run Tests with Pytest
      run: pytest

  terraform-validation:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout Repository
      uses: actions/checkout@v2

    - name: Setup Terraform
      uses: hashicorp/setup-terraform@v2
      with:
        terraform_version: 1.0.0

    - name: Initialize Terraform
      run: terraform init

    - name: Validate Terraform Configuration
      run: terraform validate

这个示例包括了两个作业:

  1. test 作业:检出代码、设置Python环境、安装Python应用程序的依赖项、运行pytest进行单元测试。
  2. terraform-validation 作业:检出代码、设置Terraform环境、初始化Terraform、验证Terraform配置的语法和静态错误。

CD

CD的主要目标是将通过CI验证的代码部署到生产环境或其他目标环境。CD的YAML文件可能包含部署步骤、发布到服务器或云服务的命令等。在CD中,可能包括以下步骤:检出代码、设置部署环境、执行部署命令。以下是一个示例

name: CD

on:
  workflow_run:
    workflows: ["CI"]
    types:
      - completed

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout Repository
      uses: actions/checkout@v2

    - name: Setup Python
      uses: actions/setup-python@v2
      with:
        python-version: 3.9

    - name: Install Dependencies
      run: pip install -r requirements.txt

    - name: Configure AWS Credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: us-east-1  # 替换为你的AWS区域

    - name: Deploy Infrastructure with Terraform
      run: |
        cd terraform
        terraform init
        terraform apply -auto-approve

    - name: Deploy Python Application to Lambda
      run: |
        # 在这里添加将 Python 应用程序部署到 Lambda 的命令
        # 你可能需要使用 AWS CLI 或其他工具进行部署

上述示例包含了以下关键步骤:

  • 检出代码: 使用 actions/checkout 动作从版本控制系统中检出代码。
  • 设置Python环境: 使用 actions/setup-python 动作设置Python环境,并指定Python版本。
  • 安装依赖: 使用 pip install 安装Python应用程序的依赖项。
  • 配置AWS凭据: 使用 aws-actions/configure-aws-credentials 动作配置AWS凭据,以便后续步骤可以访问AWS服务。
  • 使用Terraform部署基础设施: 进入Terraform目录,运行terraform init和terraform apply -auto-approve来部署基础设施。
  • 部署Python应用程序到Lambda: 在这一步中,你需要添加将Python应用程序部署到AWS Lambda的命令。这可能涉及使用AWS CLI或其他工具。
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐