文章目录
- 一、实验跟踪(Experiental Tracking)
- 1. MLflow
- (1)实验跟踪
- (2)超参数优化
- Hyperopt
- 集成Hyperopt和MLflow
- (3)模型注册
- 2. Weight & Bias
- 二、模型部署
- 1. Web服务部署
- 2. Docker
- Docker Compose
- Kubernetes
- 三、最佳实践
- 1. 单元测试
- 2. Terraform
- 3. CI/CD
- CI
- CD
一、实验跟踪(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.01mlflow.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 xgbfrom hyperopt import fmin, tpe, hp, STATUS_OK, Trials
from hyperopt.pyll import scopetrain = 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 Flaskapp = 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 LinearRegressionapp = 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_imageports:- "5000:5000"service2:build:context: ./service2depends_on:- service1environment:- 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.pydef add(x, y):return x + y
我们将为这个函数编写一个单元测试,测试函数直接使用assert语句来检查条件是否为真,测试函数的名称以test_开头:
# test_my_math.pyfrom my_math import adddef test_add_positive_numbers():assert add(2, 3) == 5def test_add_negative_numbers():assert add(-2, -3) == -5def 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.regionami_id = var.ami_idinstance_type = var.instance_typekey_name = var.key_namesubnet_id = var.subnet_idsecurity_group_names = var.security_group_namesinstance_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.tfvars
或 terraform 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_idinstance_type = var.instance_typekey_name = var.key_namesubnet_id = var.subnet_idsecurity_group_names = var.security_group_namestags = {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: CIon:push:branches:- mainjobs:test:runs-on: ubuntu-lateststeps:- name: Checkout Repositoryuses: actions/checkout@v2- name: Setup Pythonuses: actions/setup-python@v2with:python-version: 3.9- name: Install Dependenciesrun: |python -m pip install --upgrade pippip install -r requirements.txt- name: Run Tests with Pytestrun: pytestterraform-validation:runs-on: ubuntu-lateststeps:- name: Checkout Repositoryuses: actions/checkout@v2- name: Setup Terraformuses: hashicorp/setup-terraform@v2with:terraform_version: 1.0.0- name: Initialize Terraformrun: terraform init- name: Validate Terraform Configurationrun: terraform validate
这个示例包括了两个作业:
- test 作业:检出代码、设置Python环境、安装Python应用程序的依赖项、运行pytest进行单元测试。
- terraform-validation 作业:检出代码、设置Terraform环境、初始化Terraform、验证Terraform配置的语法和静态错误。
CD
CD的主要目标是将通过CI验证的代码部署到生产环境或其他目标环境。CD的YAML文件可能包含部署步骤、发布到服务器或云服务的命令等。在CD中,可能包括以下步骤:检出代码、设置部署环境、执行部署命令。以下是一个示例
name: CDon:workflow_run:workflows: ["CI"]types:- completedjobs:deploy:runs-on: ubuntu-lateststeps:- name: Checkout Repositoryuses: actions/checkout@v2- name: Setup Pythonuses: actions/setup-python@v2with:python-version: 3.9- name: Install Dependenciesrun: pip install -r requirements.txt- name: Configure AWS Credentialsuses: aws-actions/configure-aws-credentials@v1with: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 Terraformrun: |cd terraformterraform initterraform apply -auto-approve- name: Deploy Python Application to Lambdarun: |# 在这里添加将 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或其他工具。