Tối ưu hóa Class trong Python với Class Method – P2

Trong phần trước chúng ta đã tìm hiểu về việc sử dụng Class Method trong các dự án liên quan tới data, và lần này chúng ta sẽ tiếp tục với  :

2. Các hàm khởi tạo thay thế cho Model Wrapper

Bây giờ chúng ta hãy xem một ví dụ về học máy. Giả sử bạn có một Class Model có tên MyXGBModel, đây là một Class bao bọc trên thư viện XGBoost phổ biến. Nó nhận một số tham số, khởi tạo một Model và có thể xử lý một số nhiệm vụ train, đánh giá và các tác vụ modeling thông thường khác.

import xgboost

class MyXGBModel:
    def __init__(self, learning_rate=0.1, n_estimators=100, max_depth=3):
        self.learning_rate = learning_rate
        self.n_estimators = n_estimators
        self.max_depth = max_depth
        self.model = self._create_model()

    def _create_model(self):
        model = xgboost.XGBClassifier(learning_rate=self.learning_rate,
                                      n_estimators=self.n_estimators,
                                      max_depth=self.max_depth)
        return model

model = MyXGBModel(learning_rate=0.05, n_estimators=200, max_depth=5)

Thông thường, để khởi tạo Model này, bạn cần đẩy vào một loạt tham số như ví dụ trên. Ngoài ra, chúng ta có thể đẩy các biến này vào trong một parameter dictionary như sau :

params = {'learning_rate': 0.05, 'n_estimators': 200, 'max_depth': 5}
model_from_dict = MyXGBModel(**params)

Nhưng điều gì sẽ xảy ra nếu chúng ta có các tham số này trong tệp cấu hình JSON thay vì một dictionary trong bộ nhớ ? Chà, có một cách là tải tệp JSON và tạo một parameter và đẩy nó vào một model như sau :

import json

with open('config.json', 'r') as file:
    params = json.load(file)
  
model_from_dict = MyXGBModel(**params)

Một lần nữa thì tôi phải nói là… nó hơi đần. Đần ở chỗ nó dựa vào các phần code xuất hiện bên ngoài Class của bạn. Vì vậy hãy định nghĩa mọt Class Method để hợp lý hóa quy trình và cải thiện nó

class MyXGBModel:
    def __init__(self, learning_rate=0.1, n_estimators=100, max_depth=3):
        ...

    def _create_model(self):
        ...

    @classmethod
    def from_config_file(cls, file_path):
        import json
        with open(file_path, 'r') as file:
            config = json.load(file)
        return cls(**config)

model_from_config = MyXGBModel.from_config_file('config.json')

Nhìn ngon lành hơn nhiều rồi đấy nhỉ.

Lưu ý rằng bằng cách sử dụng một Class Method, chúng ta có thể đặt tất cả các tham số của mình cùng một lúc từ một tệp, thay vì đặt từng tham số một. Hàm tạo thay thế trực tiếp phân loại các tham số từ tệp cấu hình, loại bỏ sự cần thiết của các đoạn code bên ngoài Class (như đoạn code tôi đã ví dụ ở trên). Việc triển khai mới sạch hơn, đơn giản hơn, dễ bảo trì hơn và dễ hiểu hơn đối với những người tham gia dự án sau này.

Chúng ta có thể tiến thêm một bước nữa về kỹ thuật này và tạo ra các phương pháp tiện lợi để tải các Model được cấu hình sẵn cho các tình huống cụ thể nhằm đơn giản hóa quá trình khởi tạo Model. Hãy xem cách chúng ta có thể thực hiện điều đó trong phần tiếp theo.

3. Các Model được cấu hình sẵn

Khái niệm khởi tạo Model từ tệp cấu hình trước đây có thể dễ dàng được khái quát hóa (hoặc tôi nên nói là chuyên biệt) để bạn có thể nhanh chóng khởi tạo Model với các cài đặt được xác định trước cho các trường hợp cụ thể. Giả sử chúng ta muốn xác định Model “quick start” cho các bước lặp nhanh và cả Model “high accuracy” (có độ chính xác cao) cho các tác vụ phức tạp hơn. Chúng ta có thể sử dụng Class Method để cung cấp các tùy chọn được cấu hình sẵn sau:

class MyXGBoostModel:
    def __init__(self, learning_rate=0.1, n_estimators=100, max_depth=3):
        ...

    def _create_model(self):
        ...

    @classmethod
    def from_config_file(cls, file_path):
        ...

    @classmethod
    def quick_start(cls):
        default_params = {'learning_rate': 0.05, 'n_estimators': 100, 'max_depth': 4}
        return cls(**default_params)

    @classmethod
    def high_accuracy(cls):
        high_acc_params = {'learning_rate': 0.01, 'n_estimators': 500, 'max_depth': 10}
        return cls(**high_acc_params)

quick_start_model = MyXGBoostModel.quick_start()
high_accuracy_model = MyXGBoostModel.high_accuracy()

Bạn có thể thấy các Class Method cung cấp một cách tinh tế để khởi tạo các Model bằng cách sử dụng các cài đặt được cấu hình sẵn. Bằng cách xác định các phương thức Class cụ thể cho từng cài đặt được cấu hình sẵn, chúng ta có thể nhanh chóng đưa chúng vào các đoạn code mà không cần phải chỉ định từng tham số theo cách thủ công. Một lần nữa, điều này làm giảm đáng kể số lượng mã soạn sẵn mà chúng ta cần viết, làm cho mã sạch hơn, dễ đọc hơn và dễ bảo trì hơn (tôi đã sử dụng các từ sạch, dễ đọc và có thể bảo trì được bao nhiêu lần rồi từ part 1 tới giờ ???).

Bây giờ, với bối cảnh này, chúng ta có thể tưởng tượng dễ dàng hơn. Giả sử bạn đoán đó là nút xoay cài sẵn của máy ảnh! Chức năng mà các Class Method cung cấp ở đây rất giống với cấu hình cài sẵn của máy ảnh kỹ thuật số cho các loại ảnh khác nhau, ví dụ: phong cảnh, chân dung, chế độ ban đêm, v.v. Bạn luôn có tùy chọn đặt khẩu độ và tốc độ màn trập theo cách thủ công cho ảnh tùy chỉnh , nhưng các cấu hình đặt trước có thể giúp bạn ít phải điều chỉnh bằng tay hơn, phù hợp với trường hợp sử dụng cụ thể của bạn. Không cần phải nói, trong thực tế, các tùy chọn này chỉ cung cấp cho chúng ta điểm khởi đầu cho việc khám phá Model trước khi thực hiện tìm kiếm lưới (grid search) và điều chỉnh siêu tham số thích hợp để tìm ra bộ tham số tối ưu cho Model để dự đoán.

4. Cấu hình Dev và Prod cho việc kết nối cơ sở dữ liệu

Cuối cùng, hãy xem xét trường hợp sử dụng thực tế khác của các Class Method : Tạo một Class kết nối cơ sở dữ liệu.

Tôi phải trình bày điều này kèm theo 1 note nho nhỏ là tôi không phải là 1 pro DevOps engineer và code tôi trình bày ở đây chỉ là basic, không phải là cách an toàn nhất để thiết lập kết nối cơ sở dữ liệu. Nếu bạn định sử dụng mẫu thiết kế này trong công việc của mình, hãy đảm bảo đã tham khảo ý kiến của Dev team để điều chính sao cho phù hợp với tiêu chuẩn của dự án, công ty của bạn.

Và giờ thì hãy xem cách chúng ta có thể tận dụng các Class Method để tạo một chương trình kết nối cơ sở dữ liệu điển hình với các cấu hình được xác định trước cho cả môi trường phát triển (Dev env) và môi trường Chính thức (Prod env)

Việc thiết lập và quản lý trình kết nối cơ sở dữ liệu luôn có thể gây khó chịu (ít nhất là một chút) đặc biệt là khi bạn đang làm việc với nhiều môi trường (Dev, Staging, Prod…) Mỗi môi trường có các cài đặt riêng và việc chuyển đổi giữa chúng, đặc biệt là bên trong notebook có thể dẫn đến nhiều lỗi và sự không nhất quán, đôi khi tạo ra những sai sót không đáng có mà chắc hẳn công ty nào cũng mắc phải. Và nếu bạn đang xử lí dữ liệu nhạy cảm, việc quản lý thông tin xác thực cơ sở dữ liệu của bạn cần được chăm sóc đặc biệt vì mục đích bảo mật.

Một giải pháp tinh tế với các Class Method sẽ liên quan đến việc xác định các phương thức Class riêng biệt cho từng chế độ (Dev và Prod) sử dụng các biến môi trường để khởi động trình kết nối các cài đặt riêng của chúng. Ngoài tính nhất quán mà phương pháp này mang lại, nó còn nâng cao khả năng đọc và độ bền của code của bạn (điều mà tôi đã nói rất nhiều lần trong bài viết này)

import os
from getpass import getpass

class DatabaseConnector:
    def __init__(self, account, user, password):
        self.account = account
        self.user = user
        self._password = getpass("enter the DB Password:")  # Keep password private
        self._connection = None

    @classmethod
    def development_config(cls):
        return cls(
            account=os.environ.get("DEV_DB_ACCOUNT"),
            user=os.environ.get("DEV_DB_USER"),
            password=os.environ.get("DEV_DB_PASSWORD") or getpass("Enter Dev DB Password: ")
        )

    @classmethod
    def production_config(cls):
        return cls(
            account=os.environ.get("PROD_DB_ACCOUNT"),
            user=os.environ.get("PROD_DB_USER"),
            password=os.environ.get("PROD_DB_PASSWORD") or getpass("Enter Prod DB Password: ")
        )

    @property
    def params(self):
        # Exclude password from the public parameters
        return {
            "account": self.account,
            "user": self.user
        }

    def initialize_connection(self):
        if not self._connection:
	    # define create_database_connection based on your specific database (e.g. postgres, snowflake, redshift, etc)
            self._connection = create_database_connection(self.account, self.user, self._password)

    def query_data(self, query):
        if not self._connection:
            raise Exception("Database connection not initialized")
        return execute_query(self._connection, query)

Tuyệt vời, bây giờ để sử dụng trình kết nối cơ sở dữ liệu kì công này của tôi, chúng ta chỉ cần khởi tạo Class bằng các instance như sau :


# Dev Database
dev_db_conn = DatabaseConnector.development_config()
dev_db_conn.initialize_connection()
dev_data = dev_db_conn.query_data("SELECT * FROM dev_table_name")

# Prod Database
prod_db_conn = DatabaseConnector.production_config()
prod_db_conn.initialize_connection()
prod_data = prod_db_connector.query_data("SELECT * FROM prod_table_name")

Chỉ là ví dụ vậy thôi chứ hạn chế dùng query_data nhé 😉 Dễ gặp lỗi SQL INJECTION lắm đó. (Tôi sẽ viết về nó một ngày nào đó nha)

Bây giờ, bước duy nhất bạn cần thực hiện trước khi chạy những dòng này là lưu trữ tên người dùng và mật khẩu cơ sở dữ liệu thích hợp dưới dạng biến môi trường một lần. Hãy để Class này xử lý phần còn lại mãi mãi.

Như  tiêu đề của phần này, chúng ta đã sử dụng Class Method như một công tắc để chuyển đổi giữa môi trường dev và prod mà không cần phải cấu hình lại toàn bộ mạch kết nối cơ sở dữ liệu! Tôi hy vọng bạn tận hưởng thêm thời gian tiết kiệm được bằng cách tránh tất cả các đoạn code mà bạn cần phải viết để truy cập dữ liệu của mình.

Okay, hi vọng bạn hiểu thêm nhiều điều về Class Method, giờ thì xin chào và hẹn gặp lại.

Add a Comment

Scroll Up