文章目录
一、实验跟踪(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.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_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
这个示例包括了两个作业:
- test 作业:检出代码、设置Python环境、安装Python应用程序的依赖项、运行pytest进行单元测试。
- 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或其他工具。