1658212962
Hướng dẫn triển khai một hệ thống liên tục thu thập dữ liệu và liên tục đào tạo lại các mô hình Học máy.
Triển khai mô hình Học máy (ML) được đào tạo không phải là phần cuối của quy trình ML. Thay vào đó, nó nên được coi là một cột mốc đầu tiên trong khi các kỹ sư ML phải duy trì hiệu suất của mô hình theo thời gian. Dữ liệu chuỗi thời gian hoặc dữ liệu được sử dụng cho các hệ thống khuyến nghị là những ví dụ điển hình để minh họa quan điểm này; Nếu những người mẫu này không được đào tạo liên tục, họ có thể mất khả năng dự đoán các sự kiện / xu hướng mới.
Trong bài viết này, tôi sẽ giải thích cách thiết lập một hệ thống có thể tự động thu thập đánh giá của khách hàng từ một trang thương mại điện tử , đào tạo lại mô hình NLP khi có dữ liệu mới đến và lưu trữ hiệu quả các phiên bản mô hình theo thời gian.
Tổng quan về kiến trúc
Kiến trúc được tạo thành từ:
Sau đây là các bước cấp cao cho kiến trúc này:
Thư mục dự án
Trước khi bắt đầu, bạn nên có một cái nhìn tổng quan về thư mục dự án. dags
, logs
và là data
các plugins
thư mục sẽ được gắn vào vùng chứa Airflow Docker của chúng tôi.
dags
├── __init__.py
├── automated_ml_dag.py
├── clean_archived_dag.py
└── src
logs
data
├── archived
└── staging
plugins
build-images.sh
docker-compose.yaml
airflow.Dockerfile
mlflow.Dockerfile
start-main.sh
Bước 1 - Thiết lập Docker Containers
Trong bước này, chúng tôi xây dựng một môi trường Docker nhiều vùng chứa:
Để bắt đầu, tôi tạo hai Dockerfiles được gọi airflow.Dockerfile
và mlflow.Dockerfile
để xây dựng các hình ảnh tùy chỉnh cho nhu cầu của dự án.
FROM apache/airflow:2.3.2
USER root
# Set timezone for image
RUN apt-get install -yq tzdata && \
ln -fs /usr/share/zoneinfo/Asia/Saigon /etc/localtime && \
dpkg-reconfigure -f noninteractive tzdata
ENV TZ="Asia/Saigon"
USER airflow
# Install Scrapy
RUN pip install scrapy psycopg2-binary mlflow scikit-learn nltk boto3
Sau đó, tôi bắt đầu xây dựng hình ảnh bằng tập lệnh bash:
FROM python:3.7.9
# Set timezone for image
RUN apt-get install -yq tzdata && \
ln -fs /usr/share/zoneinfo/Asia/Saigon /etc/localtime && \
dpkg-reconfigure -f noninteractive tzdata
ENV TZ="Asia/Saigon"
# Install
RUN pip install --upgrade pip setuptools wheel
RUN pip install mlflow psycopg2
Sau đó, tôi bắt đầu xây dựng hình ảnh bằng tập lệnh bash:
#!/bin/bash
source 'env.list'
docker build -f airflow.Dockerfile --rm -t $AIRFLOW_IMAGE_NAME .
docker build -f mlflow.Dockerfile --rm -t $MLFLOW_IMAGE_NAME .
Đối với docker-compose.yaml
, chúng ta có thể tham khảo từ một mẫu do Apache Airflow cung cấp tại https://airflow.apache.org/docs/apache-airflow/2.3.2/docker-compose.yaml .
Trong dự án, tôi tùy chỉnh bằng cách thêm một số biến trong airflow-common-env và bao gồm hình ảnh mlflow trong các dịch vụ:
environment:
&airflow-common-env
RDS_USER: ${RDS_USER}
RDS_PASSWORD: ${RDS_PASSWORD}
RDS_HOSTNAME: ${RDS_HOSTNAME}
RDS_NAME: ${RDS_NAME}
MLFLOW_BACKEND_STORE_URI: ${MLFLOW_BACKEND_STORE_URI}
MLFLOW_DEFAULT_ARTIFACT_ROOT: ${MLFLOW_DEFAULT_ARTIFACT_ROOT}
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
mlflow:
image: ${MLFLOW_IMAGE_NAME}
container_name: mlflow
ports:
- "5000:5000"
command: |
mlflow server --backend-store-uri ${MLFLOW_BACKEND_STORE_URI} --default-artifact-root ${MLFLOW_DEFAULT_ARTIFACT_ROOT} --host "0.0.0.0"
depends_on:
<<: *airflow-common-depends-on
airflow-init:
condition: service_completed_successfully
Cuối cùng, hãy chạy đoạn mã dưới đây để bắt đầu các vùng chứa:
docker-compose --env-file env.list up airflow-init
docker-compose --env-file env.list up -d
Lưu ý: Đảm bảo một
env.list
tệp lưu trữ các biến môi trường nằm trong thư mục để chạy thành công lệnh trên.
Trong Docker Desktop, chúng ta sẽ thấy các tín hiệu màu xanh lục của vùng chứa sau khi khởi động thành công.
Bước 2— Thiết kế một con nhện đang thu thập thông tin
Các bài đánh giá thô từ trang web như sau:
Để thu thập đánh giá định kỳ, tôi sử dụng scrapy
thư viện để thu thập thông tin kết quả thông qua phương pháp API.
Mục đích của việc cạo trong trường hợp này là để thực hành, tôi chọn không hiển thị công khai API trang web. Tuy nhiên, kịch bản dưới đây có thể được tham khảo để xây dựng một con nhện cho mục đích của riêng bạn.
import scrapy
import json
import random
from datetime import datetime
class CrawlSpider(scrapy.Spider):
name = 'quotes'
list_match_ids = ['11013350', '11013420', '11013434', '11027829', '11013491']
match_id = random.choice(list_match_ids)
offset = 0
scrape_time = datetime.now()
# product settings
product_page_scrape = 2 #must be more than 1
product_incremented = 20
product_api = 'https://******/******/v4/search/search_items?by=pop&limit={product_incremented}&match_id={match_id}&newest={offset}&order=desc&page_type=search&scenario=PAGE_CATEGORY&version=2'
# review settings
review_page_scrape = 1
review_incremented = 5
review_api = 'https://******/******/v2/item/get_ratings?filter=0&flag=1&itemid={itemid}&limit={review_incremented}&offset={offset}&shopid={shopid}&type={type}'
def __init__(self, db):
self.db = db
def start_requests(self):
# Loop product page
for offset in range(0, self.product_incremented * self.product_page_scrape, self.product_incremented):
yield scrapy.Request(url=self.product_api.format(
product_incremented=self.product_incremented,
match_id=self.match_id,
offset=offset),
callback=self.parse)
def parse(self, response):
resp = json.loads(response.body)
items = resp.get("items")
rating_list = ['1', '2', '3', '4', '5']
for rating_type in rating_list:
for item in items:
offset = 0
item_basic = item.get('item_basic')
itemid = item_basic.get('itemid')
shopid = item_basic.get('shopid')
product = item_basic.get('name')
while offset < (self.review_incremented * self.review_page_scrape):
yield response.follow(self.review_api.format(
itemid=itemid,
shopid=shopid,
review_incremented=self.review_incremented,
offset=offset,
type=rating_type),
callback=self.get_rating,
meta = {
'product': product,
'itemid': itemid,
'shopid': shopid
})
offset += self.review_incremented
def get_rating(self, response):
product = response.meta.get('product')
itemid = response.meta.get('itemid')
shopid = response.meta.get('shopid')
resp = json.loads(response.body)
ratings = resp.get('data').get('ratings')
records = []
for rating in ratings:
comment = rating.get('comment')
if len(comment) <= 5: # don't insert if too few characters
continue
userid = rating.get('userid')
ctime = rating.get('ctime')
orderid = rating.get('orderid')
rating = rating.get('rating_star')
record = (userid, product, itemid, shopid, ctime, comment, orderid, rating, self.scrape_time)
records.append(record)
self.db.insert_record(records)
Để lưu trữ kết quả đã thu thập vào cơ sở dữ liệu, chúng tôi cũng cần xây dựng lớp Postgres để trình thu thập dữ liệu của chúng tôi có thể gửi kết quả cóp nhặt tới AWS RDS.
import psycopg2
import pandas as pd
import os
class PostgresPipeline:
def __init__(self):
self.db_user = os.environ.get('RDS_USER' ,'')
self.db_password = os.environ.get('RDS_PASSWORD', '')
self.db_host = os.environ.get('RDS_HOSTNAME', '')
self.db_name = os.environ.get('RDS_NAME', '')
self.db_schema = 'dev'
self.db_table = 'ecommerce_reviews'
self.port = 5432
def create_conn(self):
self.client = psycopg2.connect(
user=self.db_user,
password = self.db_password,
host = self.db_host,
database = self.db_name,
port = self.port)
self.curr = self.client.cursor()
print("Successfully connected to database!")
def close_conn(self):
self.client.close()
print("Successfully closed connection to database!")
def create_table(self):
self.curr.execute(f"""
CREATE TABLE IF NOT EXISTS {self.db_schema}.{self.db_table} (
userid text,
product text,
itemid text,
shopid decimal,
ctime integer,
comment text,
orderid text,
rating integer,
time_scrape timestamp without time zone,
PRIMARY KEY (orderid)
)
"""
)
self.client.commit()
def insert_record(self, item):
query = """
INSERT INTO {db_schema}.{db_table} VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
ON CONFLICT (orderid)
DO NOTHING
""".format(db_schema=self.db_schema, db_table=self.db_table)
self.curr.executemany(query, item)
self.client.commit()
def load_data(self, path):
query = """
SELECT * FROM {db_schema}.{db_table}
""".format(db_schema=self.db_schema, db_table=self.db_table)
df = pd.read_sql(query, self.client)
df.to_csv(path, index=False)
Cuối cùng, chúng ta tạo một hàm để tích hợp Scrapy spider với cơ sở dữ liệu.
from scrapy.crawler import CrawlerProcess
from src.spiders.crawl_spider import CrawlSpider
from src.database.postgres_class import PostgresPipeline
def crawl_data():
db=PostgresPipeline()
db.create_conn()
db.create_table()
process = CrawlerProcess()
process.crawl(CrawlSpider, db=db)
process.start()
db.close_conn()
Mỗi khi quá trình chạy, dữ liệu cóp nhặt được tải lên cơ sở dữ liệu AWS RDS của chúng tôi
Bước 3 - Đường ống ML
Đầu tiên, một đường dẫn cần được xác định về quá trình đào tạo diễn ra như thế nào.
Mục đích của bài viết này không tập trung vào khả năng dự đoán của mô hình; Vì vậy, tôi sử dụng Quy trình NLP cơ bản trong trường hợp này: Mã hóa dữ liệu thô, áp dụng TF-IDF và sử dụng GridSearch để tìm các tham số tốt nhất cho thuật toán (Tôi sử dụng RandomForestClassifier cho dự án này).
import re
import pandas as pd
import pandas as pd
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
from nltk.stem.wordnet import WordNetLemmatizer
from nltk.tokenize import word_tokenize
from nltk.stem.wordnet import WordNetLemmatizer
from sklearn.base import BaseEstimator
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.pipeline import Pipeline
from sklearn.metrics import classification_report
from sklearn.feature_extraction.text import TfidfTransformer, CountVectorizer
from sklearn.ensemble import RandomForestClassifier
def tokenize(text):
stop_words = stopwords.words("english")
lemmatizer = WordNetLemmatizer()
# Remove punctuation characters
text = re.sub(r"[^a-zA-Z0-9]", " ", text.lower())
# Tokenize text
tokens = word_tokenize(text)
# Don't keep texts that are too short
tokens = [word for word in tokens if len(word) > 2]
# Lemmatize verbs and remove stop words
tokens = [lemmatizer.lemmatize(word) for word in tokens if word not in stop_words]
return tokens
def group_categories(rating):
if rating in (1,2):
category = 0
elif rating == 3:
category = 1
elif rating in (4, 5):
category = 2
return category
def preprocessing(df, test_size=0.20, random_state=42):
# Remove nan rows
df = df[df['comment'].notnull()]
# Split train, test
X = df['comment']
y = df['rating']
y = y.apply(lambda rating: group_categories(rating))
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = test_size, random_state = random_state)
return X_train, X_test, y_train, y_test
class ClfSwitcher(BaseEstimator):
def __init__(
self,
estimator = RandomForestClassifier(),
):
"""
A Custom BaseEstimator that can switch between classifiers.
:param estimator: sklearn object - The classifier
"""
self.estimator = estimator
def fit(self, X, y=None, **kwargs):
self.estimator.fit(X, y)
return self
def predict(self, X, y=None):
return self.estimator.predict(X)
def predict_proba(self, X):
return self.estimator.predict_proba(X)
def score(self, X, y):
return self.estimator.score(X, y)
def build_model():
"""Return a defined pipeline with gridsearch used to train model"""
# Add starting verb feature
pipeline = Pipeline([
('text_pipeline', Pipeline ([
('vect', CountVectorizer(tokenizer=tokenize)),
('tfidf', TfidfTransformer())
])),
('clf', ClfSwitcher())
])
# Set parameters
parameters = [
{
'clf__estimator': [RandomForestClassifier()],
'text_pipeline__vect__ngram_range': [(1,1), (1,2)],
'text_pipeline__tfidf__smooth_idf': [True, False],
'clf__estimator__n_estimators' : [200],
'clf__estimator__min_samples_split': [2, 5]
}
]
# Create gridsearch for pipeline
model = GridSearchCV(pipeline, param_grid=parameters, verbose=4, cv=5)
return model
def evaluate_model(model, X_test, y_test):
"""Step to evaluate and print results of fitted model"""
print(" Model best params: ", model.best_params_)
# Predict
y_pred = model.predict(X_test)
# Get returned metrics report
report_dict = classification_report(y_test, y_pred, output_dict=True,zero_division=0)
return report_dict
Quá trình hoàn tất, nhưng chúng ta cần thiết kế một quy trình mẹ để ghi lại các chỉ số, tham số và các phiên bản mô hình được đào tạo. Đây là lúc MLFlow đến để giúp chúng tôi. Với MLFlow, chúng tôi có thể dễ dàng theo dõi các phiên bản được đào tạo trong suốt vòng đời ML.
Trong dự án này, bất cứ khi nào một mô hình được đào tạo, các tham số và chỉ số được tải lên AWS RDS và các mô hình đã đào tạo được lưu trữ trong AWS S3.
Ngoài ra, để tránh tăng kích thước lưu trữ, chỉ các phiên bản mô hình được đào tạo vượt qua mô hình được triển khai hiện tại; ví dụ về điểm chính xác, được tải lên và thay thế điểm hiện tại.
import nltk
import pandas as pd
import mlflow
import mlflow.sklearn
import os
from src.model.nlp_preprocessing import preprocessing, build_model, evaluate_model
from mlflow.tracking import MlflowClient
from mlflow.entities import ViewType
def train_model(datasource, experiment_name, registered_model, random_state=42):
# Download nltk packages
nltk.download('punkt')
nltk.download('stopwords')
nltk.download('wordnet')
nltk.download('omw-1.4')
nltk.download('averaged_perceptron_tagger')
nltk.download('vader_lexicon')
# Get tracking uri and artifact store
backend_store_uri = os.environ.get("MLFLOW_BACKEND_STORE_URI", "")
artifact_root = os.environ.get("MLFLOW_DEFAULT_ARTIFACT_ROOT", "")
# Set tracking uri and model schema
mlflow.set_tracking_uri(backend_store_uri)
try:
mlflow.create_experiment(experiment_name, artifact_root)
except Exception:
print("An experiment with same name has been created!")
mlflow.set_experiment(experiment_name)
client = MlflowClient()
experiment = client.get_experiment_by_name(experiment_name)
list_runs = mlflow.list_run_infos(experiment.experiment_id, run_view_type=ViewType.ACTIVE_ONLY, order_by=['metric.accuracy DESC'])
# Load dataset
df = pd.read_csv(datasource)
# Train, test split
X_train, X_test, y_train, y_test = preprocessing(df, random_state=random_state)
# Start process
with mlflow.start_run():
model = build_model()
model.fit(X_train, y_train)
report_dict= evaluate_model(model, X_test, y_test)
# Get params
model_params = model.best_params_
model_name = type(model_params['clf__estimator']).__name__
smooth_idf = model_params['text_pipeline__tfidf__smooth_idf']
ngram_range = model_params['text_pipeline__vect__ngram_range']
n_estimator = model_params['clf__estimator__n_estimators']
mlflow.log_param("model_name", model_name)
mlflow.log_param("smooth_idf", smooth_idf)
mlflow.log_param("ngram_range", ngram_range)
mlflow.log_param("n_estimator ", n_estimator)
mlflow.log_param("samples ", df.shape[0])
# Get metrics
accuracy = report_dict['accuracy']
avg_precision = report_dict['weighted avg']['precision']
avg_recall = report_dict['weighted avg']['recall']
avg_f1 = report_dict['weighted avg']['f1-score']
avg_support = report_dict['weighted avg']['support']
negative_f1 = report_dict['0']['f1-score']
neutral_f1 = report_dict['1']['f1-score']
positive_f1 = report_dict['2']['f1-score']
mlflow.log_metric("accuracy", accuracy)
mlflow.log_metric("avg_precision", avg_precision)
mlflow.log_metric("avg_recall", avg_recall)
mlflow.log_metric("avg_f1", avg_f1)
mlflow.log_metric("avg_support", avg_support)
mlflow.log_metric("negative_f1", negative_f1)
mlflow.log_metric("neutral_f1", neutral_f1)
mlflow.log_metric("positive_f1", positive_f1)
# Print
print(" Trained successfully!")
print(" Model %s (n_estimator=%s, ngram_range=%s, smooth_idf=%s):" % (model_name, n_estimator, ngram_range, smooth_idf))
print(" accuracy: %s" % accuracy)
print(" avg_precision: %s" % avg_precision)
print(" avg_recall: %s" % avg_recall)
print(" avg_f1: %s" % avg_f1)
print(" avg_support: %s" % avg_support)
print(" negative_f1: %s" % negative_f1)
print(" neutral_f1: %s" % neutral_f1)
print(" positive_f1: %s" % positive_f1)
# Register the model
if len(list_runs) == 0:
print("First time running, use default threshold!")
threshold = 0.5
else:
run_id = list_runs[0].run_id
threshold = client.get_metric_history(run_id, 'accuracy')[0].value
# If higher than threshold -> deploy model
if accuracy > threshold:
print("New model has higher accuracy than that of the current model, model will be deployed shortly!")
mlflow.sklearn.log_model(model, "model", registered_model_name=registered_model)
print("Model has been deployed successfully to S3!")
else:
print("Model is NOT deployed because accuracy is not higher than current model or is too low!")
print("Current model accuracy: ", threshold)
Bước 4 - Tạo Quy trình làm việc DAGs
Khi đến bước này, dự án của chúng ta đã gần hoàn thành. Sau khi thiết kế các phần nhỏ hơn, tất cả những gì chúng ta cần làm bây giờ là tập hợp tất cả lại và biến nó thành một quy trình đầy đủ.
Ví dụ: tôi tạo một DAG luồng không khí (nằm trong ./dags
thư mục) tự động hóa quy trình vào mỗi Thứ Hai, Thứ Tư và Thứ Sáu lúc 8 giờ sáng theo giờ UTC và chỉ vào thứ Sáu, mô hình được đào tạo lại.
from airflow import DAG
from airflow.utils.task_group import TaskGroup
from airflow.operators.python_operator import PythonOperator
from airflow.operators.bash_operator import BashOperator
from airflow.operators.weekday import BranchDayOfWeekOperator
from airflow.operators.empty import EmptyOperator
from src.crawl_data import crawl_data
from src.load_reviews import load_reviews
from src.train_model import train_model
import pendulum
with DAG(
'automated_ml',
description='DAGS to crawl data periodically and train model',
schedule_interval='0 8 * * 1,3,5', # 8 a.m UTC every Mon, Wed, Fri
start_date=pendulum.datetime(2022, 7, 10, tz="UTC"),
catchup=False,
tags=['automated_ml']
) as dag:
# [START]
task_crawl = PythonOperator(
task_id='crawl_data',
python_callable=crawl_data,
dag=dag
)
branch = BranchDayOfWeekOperator(
task_id="on_friday_continue",
follow_task_ids_if_true="load_data",
follow_task_ids_if_false="empty",
week_day="Friday",
)
task_empty = EmptyOperator(
task_id="empty",
dag=dag
)
with TaskGroup(group_id='train_model_group') as train_model_group:
task_staging = PythonOperator(
task_id='load_data',
python_callable=load_reviews,
op_kwargs={'path': 'data/staging/items.csv'},
dag=dag
)
task_train = PythonOperator(
task_id='train_model',
python_callable=train_model,
op_kwargs={
'datasource': 'data/staging/items.csv',
'experiment_name': 'NLP Experiment',
'registered_model': 'NLP Model'
},
dag=dag
)
bash_command = """
mv /opt/airflow/data/staging/items.csv /opt/airflow/data/archived/items_$(date '+%Y-%m-%d').csv
"""
task_archive = BashOperator(
task_id="archived_data",
bash_command=bash_command,
dag=dag
)
task_staging >> task_train >> task_archive
# [END]
# [FLOW]
task_crawl >> branch >> [train_model_group , task_empty]
Trong giao diện người dùng luồng không khí, đoạn mã trên có thể được dịch sang Biểu đồ này. Đối với train_model_group, chúng ta có 3 nhiệm vụ phụ:
data/staging
thư mục.data/staging
thư mục sang data/archived
thư mục để theo dõi đầu vào đào tạo.Nhật ký đào tạo cũng có thể được theo dõi trong Giao diện người dùng luồng không khí:
Để kiểm tra giao diện người dùng MLFlow, hãy điều hướng đến {EC2 Public IP}: 5000 hoặc localhost: 5000 nếu bạn đang chạy trong máy cục bộ.
Trong ví dụ dưới đây, chúng ta có thể thấy rằng chỉ những mô hình có độ chính xác cao hơn mô hình hiện tại mới được triển khai ( cột Mô hình ).
Cũng dễ dàng theo dõi ID của mô hình đã triển khai từ MLFlow UI và tải xuống mô hình được đào tạo từ AWS S3.
🙌🙌 Đây là phần cuối của bài viết và tôi hy vọng bạn thích nó.
Nếu bạn thấy bài viết này có nhiều thông tin, hãy like và theo dõi tôi để được thông báo về những bài viết sắp tới của tôi ✌️
Liên kết: https://medium.com/faun/how-i-built-an-automated-retraining-machine-learning-model-6a6032f37bb9
#machinelearning
1658212962
Hướng dẫn triển khai một hệ thống liên tục thu thập dữ liệu và liên tục đào tạo lại các mô hình Học máy.
Triển khai mô hình Học máy (ML) được đào tạo không phải là phần cuối của quy trình ML. Thay vào đó, nó nên được coi là một cột mốc đầu tiên trong khi các kỹ sư ML phải duy trì hiệu suất của mô hình theo thời gian. Dữ liệu chuỗi thời gian hoặc dữ liệu được sử dụng cho các hệ thống khuyến nghị là những ví dụ điển hình để minh họa quan điểm này; Nếu những người mẫu này không được đào tạo liên tục, họ có thể mất khả năng dự đoán các sự kiện / xu hướng mới.
Trong bài viết này, tôi sẽ giải thích cách thiết lập một hệ thống có thể tự động thu thập đánh giá của khách hàng từ một trang thương mại điện tử , đào tạo lại mô hình NLP khi có dữ liệu mới đến và lưu trữ hiệu quả các phiên bản mô hình theo thời gian.
Tổng quan về kiến trúc
Kiến trúc được tạo thành từ:
Sau đây là các bước cấp cao cho kiến trúc này:
Thư mục dự án
Trước khi bắt đầu, bạn nên có một cái nhìn tổng quan về thư mục dự án. dags
, logs
và là data
các plugins
thư mục sẽ được gắn vào vùng chứa Airflow Docker của chúng tôi.
dags
├── __init__.py
├── automated_ml_dag.py
├── clean_archived_dag.py
└── src
logs
data
├── archived
└── staging
plugins
build-images.sh
docker-compose.yaml
airflow.Dockerfile
mlflow.Dockerfile
start-main.sh
Bước 1 - Thiết lập Docker Containers
Trong bước này, chúng tôi xây dựng một môi trường Docker nhiều vùng chứa:
Để bắt đầu, tôi tạo hai Dockerfiles được gọi airflow.Dockerfile
và mlflow.Dockerfile
để xây dựng các hình ảnh tùy chỉnh cho nhu cầu của dự án.
FROM apache/airflow:2.3.2
USER root
# Set timezone for image
RUN apt-get install -yq tzdata && \
ln -fs /usr/share/zoneinfo/Asia/Saigon /etc/localtime && \
dpkg-reconfigure -f noninteractive tzdata
ENV TZ="Asia/Saigon"
USER airflow
# Install Scrapy
RUN pip install scrapy psycopg2-binary mlflow scikit-learn nltk boto3
Sau đó, tôi bắt đầu xây dựng hình ảnh bằng tập lệnh bash:
FROM python:3.7.9
# Set timezone for image
RUN apt-get install -yq tzdata && \
ln -fs /usr/share/zoneinfo/Asia/Saigon /etc/localtime && \
dpkg-reconfigure -f noninteractive tzdata
ENV TZ="Asia/Saigon"
# Install
RUN pip install --upgrade pip setuptools wheel
RUN pip install mlflow psycopg2
Sau đó, tôi bắt đầu xây dựng hình ảnh bằng tập lệnh bash:
#!/bin/bash
source 'env.list'
docker build -f airflow.Dockerfile --rm -t $AIRFLOW_IMAGE_NAME .
docker build -f mlflow.Dockerfile --rm -t $MLFLOW_IMAGE_NAME .
Đối với docker-compose.yaml
, chúng ta có thể tham khảo từ một mẫu do Apache Airflow cung cấp tại https://airflow.apache.org/docs/apache-airflow/2.3.2/docker-compose.yaml .
Trong dự án, tôi tùy chỉnh bằng cách thêm một số biến trong airflow-common-env và bao gồm hình ảnh mlflow trong các dịch vụ:
environment:
&airflow-common-env
RDS_USER: ${RDS_USER}
RDS_PASSWORD: ${RDS_PASSWORD}
RDS_HOSTNAME: ${RDS_HOSTNAME}
RDS_NAME: ${RDS_NAME}
MLFLOW_BACKEND_STORE_URI: ${MLFLOW_BACKEND_STORE_URI}
MLFLOW_DEFAULT_ARTIFACT_ROOT: ${MLFLOW_DEFAULT_ARTIFACT_ROOT}
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
mlflow:
image: ${MLFLOW_IMAGE_NAME}
container_name: mlflow
ports:
- "5000:5000"
command: |
mlflow server --backend-store-uri ${MLFLOW_BACKEND_STORE_URI} --default-artifact-root ${MLFLOW_DEFAULT_ARTIFACT_ROOT} --host "0.0.0.0"
depends_on:
<<: *airflow-common-depends-on
airflow-init:
condition: service_completed_successfully
Cuối cùng, hãy chạy đoạn mã dưới đây để bắt đầu các vùng chứa:
docker-compose --env-file env.list up airflow-init
docker-compose --env-file env.list up -d
Lưu ý: Đảm bảo một
env.list
tệp lưu trữ các biến môi trường nằm trong thư mục để chạy thành công lệnh trên.
Trong Docker Desktop, chúng ta sẽ thấy các tín hiệu màu xanh lục của vùng chứa sau khi khởi động thành công.
Bước 2— Thiết kế một con nhện đang thu thập thông tin
Các bài đánh giá thô từ trang web như sau:
Để thu thập đánh giá định kỳ, tôi sử dụng scrapy
thư viện để thu thập thông tin kết quả thông qua phương pháp API.
Mục đích của việc cạo trong trường hợp này là để thực hành, tôi chọn không hiển thị công khai API trang web. Tuy nhiên, kịch bản dưới đây có thể được tham khảo để xây dựng một con nhện cho mục đích của riêng bạn.
import scrapy
import json
import random
from datetime import datetime
class CrawlSpider(scrapy.Spider):
name = 'quotes'
list_match_ids = ['11013350', '11013420', '11013434', '11027829', '11013491']
match_id = random.choice(list_match_ids)
offset = 0
scrape_time = datetime.now()
# product settings
product_page_scrape = 2 #must be more than 1
product_incremented = 20
product_api = 'https://******/******/v4/search/search_items?by=pop&limit={product_incremented}&match_id={match_id}&newest={offset}&order=desc&page_type=search&scenario=PAGE_CATEGORY&version=2'
# review settings
review_page_scrape = 1
review_incremented = 5
review_api = 'https://******/******/v2/item/get_ratings?filter=0&flag=1&itemid={itemid}&limit={review_incremented}&offset={offset}&shopid={shopid}&type={type}'
def __init__(self, db):
self.db = db
def start_requests(self):
# Loop product page
for offset in range(0, self.product_incremented * self.product_page_scrape, self.product_incremented):
yield scrapy.Request(url=self.product_api.format(
product_incremented=self.product_incremented,
match_id=self.match_id,
offset=offset),
callback=self.parse)
def parse(self, response):
resp = json.loads(response.body)
items = resp.get("items")
rating_list = ['1', '2', '3', '4', '5']
for rating_type in rating_list:
for item in items:
offset = 0
item_basic = item.get('item_basic')
itemid = item_basic.get('itemid')
shopid = item_basic.get('shopid')
product = item_basic.get('name')
while offset < (self.review_incremented * self.review_page_scrape):
yield response.follow(self.review_api.format(
itemid=itemid,
shopid=shopid,
review_incremented=self.review_incremented,
offset=offset,
type=rating_type),
callback=self.get_rating,
meta = {
'product': product,
'itemid': itemid,
'shopid': shopid
})
offset += self.review_incremented
def get_rating(self, response):
product = response.meta.get('product')
itemid = response.meta.get('itemid')
shopid = response.meta.get('shopid')
resp = json.loads(response.body)
ratings = resp.get('data').get('ratings')
records = []
for rating in ratings:
comment = rating.get('comment')
if len(comment) <= 5: # don't insert if too few characters
continue
userid = rating.get('userid')
ctime = rating.get('ctime')
orderid = rating.get('orderid')
rating = rating.get('rating_star')
record = (userid, product, itemid, shopid, ctime, comment, orderid, rating, self.scrape_time)
records.append(record)
self.db.insert_record(records)
Để lưu trữ kết quả đã thu thập vào cơ sở dữ liệu, chúng tôi cũng cần xây dựng lớp Postgres để trình thu thập dữ liệu của chúng tôi có thể gửi kết quả cóp nhặt tới AWS RDS.
import psycopg2
import pandas as pd
import os
class PostgresPipeline:
def __init__(self):
self.db_user = os.environ.get('RDS_USER' ,'')
self.db_password = os.environ.get('RDS_PASSWORD', '')
self.db_host = os.environ.get('RDS_HOSTNAME', '')
self.db_name = os.environ.get('RDS_NAME', '')
self.db_schema = 'dev'
self.db_table = 'ecommerce_reviews'
self.port = 5432
def create_conn(self):
self.client = psycopg2.connect(
user=self.db_user,
password = self.db_password,
host = self.db_host,
database = self.db_name,
port = self.port)
self.curr = self.client.cursor()
print("Successfully connected to database!")
def close_conn(self):
self.client.close()
print("Successfully closed connection to database!")
def create_table(self):
self.curr.execute(f"""
CREATE TABLE IF NOT EXISTS {self.db_schema}.{self.db_table} (
userid text,
product text,
itemid text,
shopid decimal,
ctime integer,
comment text,
orderid text,
rating integer,
time_scrape timestamp without time zone,
PRIMARY KEY (orderid)
)
"""
)
self.client.commit()
def insert_record(self, item):
query = """
INSERT INTO {db_schema}.{db_table} VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
ON CONFLICT (orderid)
DO NOTHING
""".format(db_schema=self.db_schema, db_table=self.db_table)
self.curr.executemany(query, item)
self.client.commit()
def load_data(self, path):
query = """
SELECT * FROM {db_schema}.{db_table}
""".format(db_schema=self.db_schema, db_table=self.db_table)
df = pd.read_sql(query, self.client)
df.to_csv(path, index=False)
Cuối cùng, chúng ta tạo một hàm để tích hợp Scrapy spider với cơ sở dữ liệu.
from scrapy.crawler import CrawlerProcess
from src.spiders.crawl_spider import CrawlSpider
from src.database.postgres_class import PostgresPipeline
def crawl_data():
db=PostgresPipeline()
db.create_conn()
db.create_table()
process = CrawlerProcess()
process.crawl(CrawlSpider, db=db)
process.start()
db.close_conn()
Mỗi khi quá trình chạy, dữ liệu cóp nhặt được tải lên cơ sở dữ liệu AWS RDS của chúng tôi
Bước 3 - Đường ống ML
Đầu tiên, một đường dẫn cần được xác định về quá trình đào tạo diễn ra như thế nào.
Mục đích của bài viết này không tập trung vào khả năng dự đoán của mô hình; Vì vậy, tôi sử dụng Quy trình NLP cơ bản trong trường hợp này: Mã hóa dữ liệu thô, áp dụng TF-IDF và sử dụng GridSearch để tìm các tham số tốt nhất cho thuật toán (Tôi sử dụng RandomForestClassifier cho dự án này).
import re
import pandas as pd
import pandas as pd
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
from nltk.stem.wordnet import WordNetLemmatizer
from nltk.tokenize import word_tokenize
from nltk.stem.wordnet import WordNetLemmatizer
from sklearn.base import BaseEstimator
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.pipeline import Pipeline
from sklearn.metrics import classification_report
from sklearn.feature_extraction.text import TfidfTransformer, CountVectorizer
from sklearn.ensemble import RandomForestClassifier
def tokenize(text):
stop_words = stopwords.words("english")
lemmatizer = WordNetLemmatizer()
# Remove punctuation characters
text = re.sub(r"[^a-zA-Z0-9]", " ", text.lower())
# Tokenize text
tokens = word_tokenize(text)
# Don't keep texts that are too short
tokens = [word for word in tokens if len(word) > 2]
# Lemmatize verbs and remove stop words
tokens = [lemmatizer.lemmatize(word) for word in tokens if word not in stop_words]
return tokens
def group_categories(rating):
if rating in (1,2):
category = 0
elif rating == 3:
category = 1
elif rating in (4, 5):
category = 2
return category
def preprocessing(df, test_size=0.20, random_state=42):
# Remove nan rows
df = df[df['comment'].notnull()]
# Split train, test
X = df['comment']
y = df['rating']
y = y.apply(lambda rating: group_categories(rating))
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = test_size, random_state = random_state)
return X_train, X_test, y_train, y_test
class ClfSwitcher(BaseEstimator):
def __init__(
self,
estimator = RandomForestClassifier(),
):
"""
A Custom BaseEstimator that can switch between classifiers.
:param estimator: sklearn object - The classifier
"""
self.estimator = estimator
def fit(self, X, y=None, **kwargs):
self.estimator.fit(X, y)
return self
def predict(self, X, y=None):
return self.estimator.predict(X)
def predict_proba(self, X):
return self.estimator.predict_proba(X)
def score(self, X, y):
return self.estimator.score(X, y)
def build_model():
"""Return a defined pipeline with gridsearch used to train model"""
# Add starting verb feature
pipeline = Pipeline([
('text_pipeline', Pipeline ([
('vect', CountVectorizer(tokenizer=tokenize)),
('tfidf', TfidfTransformer())
])),
('clf', ClfSwitcher())
])
# Set parameters
parameters = [
{
'clf__estimator': [RandomForestClassifier()],
'text_pipeline__vect__ngram_range': [(1,1), (1,2)],
'text_pipeline__tfidf__smooth_idf': [True, False],
'clf__estimator__n_estimators' : [200],
'clf__estimator__min_samples_split': [2, 5]
}
]
# Create gridsearch for pipeline
model = GridSearchCV(pipeline, param_grid=parameters, verbose=4, cv=5)
return model
def evaluate_model(model, X_test, y_test):
"""Step to evaluate and print results of fitted model"""
print(" Model best params: ", model.best_params_)
# Predict
y_pred = model.predict(X_test)
# Get returned metrics report
report_dict = classification_report(y_test, y_pred, output_dict=True,zero_division=0)
return report_dict
Quá trình hoàn tất, nhưng chúng ta cần thiết kế một quy trình mẹ để ghi lại các chỉ số, tham số và các phiên bản mô hình được đào tạo. Đây là lúc MLFlow đến để giúp chúng tôi. Với MLFlow, chúng tôi có thể dễ dàng theo dõi các phiên bản được đào tạo trong suốt vòng đời ML.
Trong dự án này, bất cứ khi nào một mô hình được đào tạo, các tham số và chỉ số được tải lên AWS RDS và các mô hình đã đào tạo được lưu trữ trong AWS S3.
Ngoài ra, để tránh tăng kích thước lưu trữ, chỉ các phiên bản mô hình được đào tạo vượt qua mô hình được triển khai hiện tại; ví dụ về điểm chính xác, được tải lên và thay thế điểm hiện tại.
import nltk
import pandas as pd
import mlflow
import mlflow.sklearn
import os
from src.model.nlp_preprocessing import preprocessing, build_model, evaluate_model
from mlflow.tracking import MlflowClient
from mlflow.entities import ViewType
def train_model(datasource, experiment_name, registered_model, random_state=42):
# Download nltk packages
nltk.download('punkt')
nltk.download('stopwords')
nltk.download('wordnet')
nltk.download('omw-1.4')
nltk.download('averaged_perceptron_tagger')
nltk.download('vader_lexicon')
# Get tracking uri and artifact store
backend_store_uri = os.environ.get("MLFLOW_BACKEND_STORE_URI", "")
artifact_root = os.environ.get("MLFLOW_DEFAULT_ARTIFACT_ROOT", "")
# Set tracking uri and model schema
mlflow.set_tracking_uri(backend_store_uri)
try:
mlflow.create_experiment(experiment_name, artifact_root)
except Exception:
print("An experiment with same name has been created!")
mlflow.set_experiment(experiment_name)
client = MlflowClient()
experiment = client.get_experiment_by_name(experiment_name)
list_runs = mlflow.list_run_infos(experiment.experiment_id, run_view_type=ViewType.ACTIVE_ONLY, order_by=['metric.accuracy DESC'])
# Load dataset
df = pd.read_csv(datasource)
# Train, test split
X_train, X_test, y_train, y_test = preprocessing(df, random_state=random_state)
# Start process
with mlflow.start_run():
model = build_model()
model.fit(X_train, y_train)
report_dict= evaluate_model(model, X_test, y_test)
# Get params
model_params = model.best_params_
model_name = type(model_params['clf__estimator']).__name__
smooth_idf = model_params['text_pipeline__tfidf__smooth_idf']
ngram_range = model_params['text_pipeline__vect__ngram_range']
n_estimator = model_params['clf__estimator__n_estimators']
mlflow.log_param("model_name", model_name)
mlflow.log_param("smooth_idf", smooth_idf)
mlflow.log_param("ngram_range", ngram_range)
mlflow.log_param("n_estimator ", n_estimator)
mlflow.log_param("samples ", df.shape[0])
# Get metrics
accuracy = report_dict['accuracy']
avg_precision = report_dict['weighted avg']['precision']
avg_recall = report_dict['weighted avg']['recall']
avg_f1 = report_dict['weighted avg']['f1-score']
avg_support = report_dict['weighted avg']['support']
negative_f1 = report_dict['0']['f1-score']
neutral_f1 = report_dict['1']['f1-score']
positive_f1 = report_dict['2']['f1-score']
mlflow.log_metric("accuracy", accuracy)
mlflow.log_metric("avg_precision", avg_precision)
mlflow.log_metric("avg_recall", avg_recall)
mlflow.log_metric("avg_f1", avg_f1)
mlflow.log_metric("avg_support", avg_support)
mlflow.log_metric("negative_f1", negative_f1)
mlflow.log_metric("neutral_f1", neutral_f1)
mlflow.log_metric("positive_f1", positive_f1)
# Print
print(" Trained successfully!")
print(" Model %s (n_estimator=%s, ngram_range=%s, smooth_idf=%s):" % (model_name, n_estimator, ngram_range, smooth_idf))
print(" accuracy: %s" % accuracy)
print(" avg_precision: %s" % avg_precision)
print(" avg_recall: %s" % avg_recall)
print(" avg_f1: %s" % avg_f1)
print(" avg_support: %s" % avg_support)
print(" negative_f1: %s" % negative_f1)
print(" neutral_f1: %s" % neutral_f1)
print(" positive_f1: %s" % positive_f1)
# Register the model
if len(list_runs) == 0:
print("First time running, use default threshold!")
threshold = 0.5
else:
run_id = list_runs[0].run_id
threshold = client.get_metric_history(run_id, 'accuracy')[0].value
# If higher than threshold -> deploy model
if accuracy > threshold:
print("New model has higher accuracy than that of the current model, model will be deployed shortly!")
mlflow.sklearn.log_model(model, "model", registered_model_name=registered_model)
print("Model has been deployed successfully to S3!")
else:
print("Model is NOT deployed because accuracy is not higher than current model or is too low!")
print("Current model accuracy: ", threshold)
Bước 4 - Tạo Quy trình làm việc DAGs
Khi đến bước này, dự án của chúng ta đã gần hoàn thành. Sau khi thiết kế các phần nhỏ hơn, tất cả những gì chúng ta cần làm bây giờ là tập hợp tất cả lại và biến nó thành một quy trình đầy đủ.
Ví dụ: tôi tạo một DAG luồng không khí (nằm trong ./dags
thư mục) tự động hóa quy trình vào mỗi Thứ Hai, Thứ Tư và Thứ Sáu lúc 8 giờ sáng theo giờ UTC và chỉ vào thứ Sáu, mô hình được đào tạo lại.
from airflow import DAG
from airflow.utils.task_group import TaskGroup
from airflow.operators.python_operator import PythonOperator
from airflow.operators.bash_operator import BashOperator
from airflow.operators.weekday import BranchDayOfWeekOperator
from airflow.operators.empty import EmptyOperator
from src.crawl_data import crawl_data
from src.load_reviews import load_reviews
from src.train_model import train_model
import pendulum
with DAG(
'automated_ml',
description='DAGS to crawl data periodically and train model',
schedule_interval='0 8 * * 1,3,5', # 8 a.m UTC every Mon, Wed, Fri
start_date=pendulum.datetime(2022, 7, 10, tz="UTC"),
catchup=False,
tags=['automated_ml']
) as dag:
# [START]
task_crawl = PythonOperator(
task_id='crawl_data',
python_callable=crawl_data,
dag=dag
)
branch = BranchDayOfWeekOperator(
task_id="on_friday_continue",
follow_task_ids_if_true="load_data",
follow_task_ids_if_false="empty",
week_day="Friday",
)
task_empty = EmptyOperator(
task_id="empty",
dag=dag
)
with TaskGroup(group_id='train_model_group') as train_model_group:
task_staging = PythonOperator(
task_id='load_data',
python_callable=load_reviews,
op_kwargs={'path': 'data/staging/items.csv'},
dag=dag
)
task_train = PythonOperator(
task_id='train_model',
python_callable=train_model,
op_kwargs={
'datasource': 'data/staging/items.csv',
'experiment_name': 'NLP Experiment',
'registered_model': 'NLP Model'
},
dag=dag
)
bash_command = """
mv /opt/airflow/data/staging/items.csv /opt/airflow/data/archived/items_$(date '+%Y-%m-%d').csv
"""
task_archive = BashOperator(
task_id="archived_data",
bash_command=bash_command,
dag=dag
)
task_staging >> task_train >> task_archive
# [END]
# [FLOW]
task_crawl >> branch >> [train_model_group , task_empty]
Trong giao diện người dùng luồng không khí, đoạn mã trên có thể được dịch sang Biểu đồ này. Đối với train_model_group, chúng ta có 3 nhiệm vụ phụ:
data/staging
thư mục.data/staging
thư mục sang data/archived
thư mục để theo dõi đầu vào đào tạo.Nhật ký đào tạo cũng có thể được theo dõi trong Giao diện người dùng luồng không khí:
Để kiểm tra giao diện người dùng MLFlow, hãy điều hướng đến {EC2 Public IP}: 5000 hoặc localhost: 5000 nếu bạn đang chạy trong máy cục bộ.
Trong ví dụ dưới đây, chúng ta có thể thấy rằng chỉ những mô hình có độ chính xác cao hơn mô hình hiện tại mới được triển khai ( cột Mô hình ).
Cũng dễ dàng theo dõi ID của mô hình đã triển khai từ MLFlow UI và tải xuống mô hình được đào tạo từ AWS S3.
🙌🙌 Đây là phần cuối của bài viết và tôi hy vọng bạn thích nó.
Nếu bạn thấy bài viết này có nhiều thông tin, hãy like và theo dõi tôi để được thông báo về những bài viết sắp tới của tôi ✌️
Liên kết: https://medium.com/faun/how-i-built-an-automated-retraining-machine-learning-model-6a6032f37bb9
#machinelearning
1635215499
35.000+ Tủ locker, tủ nhân viên cao cấp chính hãng l Nam Thuy Corp
Việc trang bị tủ locker cho các trường học là điều vô cùng cần thiết để giúp học sinh có ý thức và trách nhiệm hơn trong việc bảo quản tài sản cá nhân.
Website: https://namthuycorp.com/danh-muc-san-pham/tu-locker/
#tủ_sắt_locker #locker #tu_sat_locker #tu_locker #tủ_locker_sắt #tủ_nhân_viên #tu_locker_sat #tủ_locker_giá_rẻ #tu_locker_gia_re #tủ_cá_nhân_locker #tủ_sắt_nhiều_ngăn #tủ_đựng_đồ_nhân_viên
CÔNG TY TNHH QUỐC TẾ NAM THỦY
Công ty thành viên trực thuộc Nam Thủy Group
Địa chỉ: SH02-22, Sari Town, KĐT Sala, 10 Mai Chí Thọ,
Phường An Lợi Đông, Quận 2, TP. Hồ Chí Minh
Điện thoại: (028) 62700527 Hotline: 0909 420 804
Email: info@namthuycorp.com
1636358771
Tủ iLocker mang nhiều đặc điểm nổi bật, cùng các phương thức bảo mật khác nhau như Fingerprint, RFID, Face ID hoặc QR code được xem là giải pháp lưu trữ tối ưu của Smart Locker
#tủ_locker #tủ_sắt_locker #locker #tu_sat_locker #tu_locker #tủ_locker_sắt #tủ_nhân_viên #tu_locker_sat #tủ_locker_giá rẻ #tu_locker_gia_re #tủ_cá_nhân_locker #tủ_sắt_nhiều_ngăn #tủ_đựng_đồ_nhân_viên
Website:
1636956996
Tủ locker dòng W900 là sản phẩm có thể xếp chồng lên được 3 tầng cho 1 cột, có thể kết hợp nhiều cột tủ lại với nhau theo mong muốn (không giới hạn số lượng cột tủ)
#tủ_locker #tủ_sắt_locker #locker #tu_sat_locker #tu_locker #tủ_locker_sắt #tủ_nhân_viên #tu_locker_sat #tủ_locker_giá rẻ #tu_locker_gia_re #tủ_cá_nhân_locker #tủ_sắt_nhiều_ngăn #tủ_đựng_đồ_nhân_viên
Website:
1637049082
Hệ thống tủ locker trong trường học giúp cho các học sinh sinh viên có một môi trường học tập hiện đại, thoải mái và an toàn hơn.
#tủ_locker #tủ_sắt_locker #locker #tu_sat_locker #tu_locker #tủ_locker_sắt #tủ_nhân_viên #tu_locker_sat #tủ_locker_giá rẻ #tu_locker_gia_re #tủ_cá_nhân_locker #tủ_sắt_nhiều_ngăn #tủ_đựng_đồ_nhân_viên
Website: