Modele i platformy AI
Wzorce projektowe w Pythonie dla inżynierów AI i LLM: Praktyczny przewodnik
Jako inżynierowie AI, tworzenie czystego, wydajnego i łatwego w utrzymaniu kodu jest kluczowe, szczególnie przy budowaniu złożonych systemów.
Wzorce projektowe to ponownie wykorzystywane rozwiązania problemów w projektowaniu oprogramowania. Dla inżynierów AI i dużych modeli językowych (LLM), wzorce projektowe pomagają budować solidne, skalowalne i łatwe w utrzymaniu systemy, które efektywnie obsługują złożone przepływy pracy. Artykuł ten zagłębia się w wzorce projektowe w Pythonie, koncentrując się na ich istotności w AI i systemach opartych na LLM. Wyjaśnię każdy wzorzec z praktycznymi przypadkami użycia i przykładami kodu w Pythonie.
Przejdźmy do niektórych kluczowych wzorców projektowych, które są szczególnie przydatne w kontekście AI i uczenia maszynowego, wraz z przykładami w Pythonie.
Dlaczego wzorce projektowe są ważne dla inżynierów AI
Systemy AI często obejmują:
- Skomplikowaną tworzenie obiektów (np. ładowanie modeli, przetwarzanie danych).
- Zarządzanie interakcjami między komponentami (np. inferencja modelu, aktualizacje w czasie rzeczywistym).
- Obsługę skalowalności, utrzymania i elastyczności w przypadku zmieniających się wymagań.
Wzorce projektowe rozwiązują te wyzwania, zapewniając klarowną strukturę i redukując naprawy ad hoc. Mogą być podzielone na trzy główne kategorie:
- Wzorce kreacyjne: Koncentrują się na tworzeniu obiektów. (Singleton, Factory, Builder)
- Wzorce strukturalne: Organizują relacje między obiektami. (Adapter, Dekorator)
- Wzorce behawioralne: Zarządzają komunikacją między obiektami. (Strategy, Observer)
1. Wzorzec Singleton
Wzorzec Singleton zapewnia, że klasa ma tylko jedną instancję i zapewnia globalny punkt dostępu do tej instancji. Jest to szczególnie cenne w przepływach pracy AI, gdzie udostępnione zasoby, takie jak ustawienia konfiguracyjne, systemy logowania lub instancje modeli, muszą być spójnie zarządzane bez redundancji.
Kiedy używać
- Zarządzanie ustawieniami globalnymi (np. hiperparametrami modelu).
- Udostępnianie zasobów w wielu wątkach lub procesach (np. pamięć GPU).
- Gwarantowanie spójnego dostępu do pojedynczego silnika inferencji lub połączenia z bazą danych.
Implementacja
Oto, jak zaimplementować wzorzec Singleton w Pythonie, aby zarządzać konfiguracjami modelu AI:
class ModelConfig:
"""
Klasa Singleton do zarządzania globalnymi konfiguracjami modelu.
"""
_instance = None # Zmienna klasy do przechowywania instancji Singleton
def __new__(cls, *args, **kwargs):
if not cls._instance:
# Utwórz nową instancję, jeśli nie istnieje
cls._instance = super().__new__(cls)
cls._instance.settings = {} # Zainicjuj słownik konfiguracji
return cls._instance
def set(self, key, value):
"""
Ustaw parametr konfiguracyjny.
"""
self.settings[key] = value
def get(self, key):
"""
Pobierz wartość konfiguracji.
"""
return self.settings.get(key)
# Przykład użycia
config1 = ModelConfig()
config1.set("model_name", "GPT-4")
config1.set("batch_size", 32)
# Dostęp do tej samej instancji
config2 = ModelConfig()
print(config2.get("model_name")) # Wyjście: GPT-4
print(config2.get("batch_size")) # Wyjście: 32
print(config1 is config2) # Wyjście: True (oba są tą samą instancją)
Wyjaśnienie
- Metoda __new__: Zapewnia, że utworzona jest tylko jedna instancja klasy. Jeśli instancja już istnieje, zwraca istniejącą.
- Stan współdzielony: Zarówno
config1, jak iconfig2wskazują na tę samą instancję, zapewniając globalny dostęp do konfiguracji. - Przypadek użycia AI: Użyj tego wzorca, aby zarządzać globalnymi ustawieniami, takimi jak ścieżki do zestawów danych, konfiguracje logowania lub zmienne środowiskowe.
2. Wzorzec Factory
Wzorzec Factory zapewnia sposób delegowania tworzenia obiektów do podklas lub dedykowanych metod fabrycznych. W systemach AI jest to idealny do tworzenia dynamicznie różnych typów modeli, loaderów danych lub potoków na podstawie kontekstu.
Kiedy używać
- Tworzenie modeli dynamicznie na podstawie wejścia użytkownika lub wymagań zadania.
- Zarządzanie złożoną logiką tworzenia obiektów (np. wieloetapowe potoki przetwarzania danych).
- Odłączanie instancji obiektu od reszty systemu w celu poprawy elastyczności.
Implementacja
Zbudujmy fabrykę do tworzenia modeli dla różnych zadań AI, takich jak klasyfikacja tekstu, podsumowanie i tłumaczenie:
class BaseModel:
"""
Klasa bazowa dla modeli AI.
"""
def predict(self, data):
raise NotImplementedError("Podklasy muszą zaimplementować metodę `predict`")
class TextClassificationModel(BaseModel):
def predict(self, data):
return f"Klasyfikacja tekstu: {data}"
class SummarizationModel(BaseModel):
def predict(self, data):
return f"Podsumowanie tekstu: {data}"
class TranslationModel(BaseModel):
def predict(self, data):
return f"Tłumaczenie tekstu: {data}"
class ModelFactory:
"""
Klasa fabryki do tworzenia modeli AI dynamicznie.
"""
@staticmethod
def create_model(task_type):
"""
Metoda fabryki do tworzenia modeli na podstawie typu zadania.
"""
task_mapping = {
"klasyfikacja": TextClassificationModel,
"podsumowanie": SummarizationModel,
"tłumaczenie": TranslationModel,
}
model_class = task_mapping.get(task_type)
if not model_class:
raise ValueError(f"Nieznany typ zadania: {task_type}")
return model_class()
# Przykład użycia
task = "klasyfikacja"
model = ModelFactory.create_model(task)
print(model.predict("AI zmieni świat!"))
# Wyjście: Klasyfikacja tekstu: AI zmieni świat!
Wyjaśnienie
- Klasa bazowa: Klasa
BaseModeldefiniuje interfejs (predict), który wszystkie podklasy muszą zaimplementować, zapewniając spójność. - Logika fabryki: Klasa
ModelFactorydynamicznie wybiera odpowiednią klasę na podstawie typu zadania i tworzy instancję. - Rozszerzalność: Dodanie nowego typu modelu jest proste — wystarczy zaimplementować nową podklasę i zaktualizować mapowanie fabryki.
Przypadek użycia AI
Wyobraź sobie, że projektujesz system, który wybiera inny model LLM (np. BERT, GPT lub T5) w zależności od zadania. Wzorzec Factory ułatwia rozszerzanie systemu o nowe modele bez modyfikowania istniejącego kodu.
3. Wzorzec Builder
Wzorzec Builder oddziela konstrukcję złożonego obiektu od jego reprezentacji. Jest przydatny, gdy obiekt wymaga wielu kroków do zainicjowania lub skonfigurowania.
Kiedy używać
- Budowanie wieloetapowych potoków (np. przetwarzanie danych).
- Zarządzanie konfiguracjami doświadczeń lub treningu modelu.
- Tworzenie obiektów, które wymagają wielu parametrów, zapewniając czytelność i utrzymanie.
Implementacja
Oto, jak użyć wzorca Builder do utworzenia potoku przetwarzania danych:
class DataPipeline: """ Klasa budująca do konstruowania potoku przetwarzania danych. """ def __init__(self): self.steps = [] def add_step(self, step_function): """ Dodaj krok przetwarzania do potoku. """ self.steps.append(step_function) return self # Zwróć siebie, aby umożliwić łańcuchowanie metod def run(self, data): """ Wykonaj wszystkie kroki w potoku. """ for step in self.steps: data = step(data) return data # Przykład użycia pipeline = DataPipeline() pipeline.add_step(lambda x: x.strip()) # Krok 1: Usuń białe znaki pipeline.add_step(lambda x: x.lower()) # Krok 2: Zamień na małe litery pipeline.add_step(lambda x: x.replace(".", "")) # Krok 3: Usuń kropki processed_data = pipeline.run(" Witaj Świecie. ") print(processed_data) # Wyjście: witaj świecie
Wyjaśnienie
- Metody łańcuchowe: Metoda
add_steppozwala na łańcuchowanie, aby zdefiniować potok w czytelny i kompaktowy sposób. - Wykonanie kroków: Potok przetwarza dane, wykonując je sekwencyjnie.
- Przypadek użycia AI: Użyj wzorca Builder, aby utworzyć złożone, wielokrotnie używalne potoki przetwarzania danych lub ustawienia treningu modelu.
4. Wzorzec Strategy
Wzorzec Strategy definiuje rodzinę wymiennych algorytmów, kapsułkując każdy z nich i pozwalając na zmianę zachowania dynamicznie w czasie wykonywania. Jest to szczególnie przydatne w systemach AI, gdzie ten sam proces (np. inferencja lub przetwarzanie danych) może wymagać różnych podejść w zależności od kontekstu.
Kiedy używać
- Przełączanie między różnymi strategiami inferencji (np. przetwarzanie batchowe a strumieniowe).
- Stosowanie różnych technik przetwarzania danych dynamicznie.
- Wybór strategii zarządzania zasobami w zależności od dostępnej infrastruktury.
Implementacja
Zastosujmy wzorzec Strategy, aby zaimplementować dwie różne strategie inferencji dla modelu AI:
class InferenceStrategy:
"""
Klasa bazowa dla strategii inferencji.
"""
def infer(self, model, data):
raise NotImplementedError("Podklasy muszą zaimplementować metodę `infer`")
class BatchInference(InferenceStrategy):
"""
Strategia dla inferencji batchowej.
"""
def infer(self, model, data):
print("Wykonuję inferencję batchową...")
return [model.predict(item) for item in data]
class StreamInference(InferenceStrategy):
"""
Strategia dla inferencji strumieniowej.
"""
def infer(self, model, data):
print("Wykonuję inferencję strumieniową...")
results = []
for item in data:
results.append(model.predict(item))
return results
class InferenceContext:
"""
Klasa kontekstu do przełączania między strategiami inferencji dynamicznie.
"""
def __init__(self, strategy: InferenceStrategy):
self.strategy = strategy
def set_strategy(self, strategy: InferenceStrategy):
"""
Zmień strategię inferencji dynamicznie.
"""
self.strategy = strategy
def infer(self, model, data):
"""
Deleguj inferencję do wybranej strategii.
"""
return self.strategy.infer(model, data)
# Klasa modelu
class MockModel:
def predict(self, input_data):
return f"Przewidziane: {input_data}"
# Przykład użycia
model = MockModel()
data = ["przykład1", "przykład2", "przykład3"]
context = InferenceContext(BatchInference())
print(context.infer(model, data))
# Wyjście:
# Wykonuję inferencję batchową...
# ["Przewidziane: przykład1", "Przewidziane: przykład2", "Przewidziane: przykład3"]
# Przełącz na inferencję strumieniową
context.set_strategy(StreamInference())
print(context.infer(model, data))
# Wyjście:
# Wykonuję inferencję strumieniową...
# ["Przewidziane: przykład1", "Przewidziane: przykład2", "Przewidziane: przykład3"]
Wyjaśnienie
- Klasa strategii: Klasa
InferenceStrategydefiniuje interfejs, który wszystkie strategie muszą spełniać. - Strategie konkretnych: Każda strategia (np.
BatchInference,StreamInference) implementuje logikę specyficzną dla tego podejścia. - Przełączanie dynamiczne: Klasa
InferenceContextpozwala na zmianę strategii w czasie wykonywania, oferując elastyczność dla różnych przypadków użycia.
Kiedy używać
- Przełączaj między inferencją batchową dla przetwarzania offline a inferencją strumieniową dla aplikacji w czasie rzeczywistym.
- Dostosuj techniki przetwarzania danych lub wstępnego przetwarzania dynamicznie w zależności od zadania lub formatu wejścia.
5. Wzorzec Observer
Wzorzec Observer ustanawia relację jeden-do-wielu między obiektami. Gdy jeden obiekt (podmiot) zmienia stan, wszystkie jego zależne (obserwatorzy) są automatycznie powiadamiani. Jest to szczególnie przydatne w systemach AI do monitorowania w czasie rzeczywistym, obsługi zdarzeń lub synchronizacji danych.
Kiedy używać
- Monitorowanie metryk, takich jak dokładność lub strata podczas treningu modelu.
- Aktualizacje w czasie rzeczywistym dla pulpitu nawigacyjnego lub logów.
- Zarządzanie zależnościami między komponentami w złożonych przepływach pracy.
Implementacja
Zastosujmy wzorzec Observer, aby monitorować wydajność modelu AI w czasie rzeczywistym.
class Subject:
"""
Klasa bazowa dla podmiotów obserwowanych.
"""
def __init__(self):
self._observers = []
def attach(self, observer):
"""
Dołącz obserwatora do podmiotu.
"""
self._observers.append(observer)
def detach(self, observer):
"""
Odłącz obserwatora od podmiotu.
"""
self._observers.remove(observer)
def notify(self, data):
"""
Powiadom wszystkich obserwatorów o zmianie stanu.
"""
for observer in self._observers:
observer.update(data)
class ModelMonitor(Subject):
"""
Podmiot, który monitoruje metryki wydajności modelu.
"""
def update_metrics(self, metric_name, value):
"""
Symuluj aktualizację metryki wydajności i powiadom obserwatorów.
"""
print(f"Zaktualizowano {metric_name}: {value}")
self.notify({metric_name: value})
class Observer:
"""
Klasa bazowa dla obserwatorów.
"""
def update(self, data):
raise NotImplementedError("Podklasy muszą zaimplementować metodę `update`")
class LoggerObserver(Observer):
"""
Obserwator, który loguje metryki.
"""
def update(self, data):
print(f"Logowanie metryki: {data}")
class AlertObserver(Observer):
"""
Obserwator, który wywołuje alerty, jeśli progi są przekroczone.
"""
def __init__(self, threshold):
self.threshold = threshold
def update(self, data):
for metric, value in data.items():
if value > self.threshold:
print(f"ALERT: {metric} przekroczył próg z wartością {value}")
# Przykład użycia
monitor = ModelMonitor()
logger = LoggerObserver()
alert = AlertObserver(threshold=90)
monitor.attach(logger)
monitor.attach(alert)
# Symuluj aktualizację metryk
monitor.update_metrics("dokładność", 85) # Loguje metrykę
monitor.update_metrics("dokładność", 95) # Loguje i wywołuje alert
- Podmiot: Zarządza listą obserwatorów i powiadamia ich, gdy stan się zmienia. W tym przykładzie klasa
ModelMonitorśledzi metryki. - Obserwatorzy: Wykonują określone akcje, gdy są powiadamiani. Na przykład
LoggerObserverloguje metryki, aAlertObserverwywołuje alerty, jeśli próg jest przekroczony. - Projekt rozłączny: Obserwatorzy i podmioty są słabo powiązane, co sprawia, że system jest modułowy i rozszerzalny.
Jak wzorce projektowe różnią się dla inżynierów AI w porównaniu z tradycyjnymi inżynierami
Wzorce projektowe, choć powszechnie stosowane, przyjmują unikalne cechy, gdy są wdrożone w inżynierii AI w porównaniu z tradycyjnym inżynierią oprogramowania. Różnica leży w wyzwaniach, celach i przepływach pracy wewnętrznych dla systemów AI, które często wymagają, aby wzorce były dostosowane lub rozszerzane poza ich konwencjonalne zastosowania.
1. Tworzenie obiektów: statyczne vs. dynamiczne potrzeby
- Inżynieria tradycyjna: Wzorce tworzenia obiektów, takie jak Factory lub Singleton, są często używane do zarządzania konfiguracjami, połączeniami z bazą danych lub stanami sesji użytkownika. Są one zwykle statyczne i dobrze zdefiniowane podczas projektowania systemu.
- Inżynieria AI: Tworzenie obiektów często obejmuje dynamiczne przepływy pracy, takie jak:
- Tworzenie modeli na żądanie w zależności od wejścia użytkownika lub wymagań systemu.
- Ładowanie różnych konfiguracji modelu dla zadań, takich jak tłumaczenie, podsumowanie lub klasyfikacja.
- Instancjonowanie wielu potoków przetwarzania danych, które różnią się w zależności od cech zestawu danych (np. tabularne vs. niestrukturyzowane teksty).
Przykład: W AI wzorzec Factory może dynamicznie generować głęboki model uczący się na podstawie typu zadania i ograniczeń sprzętowych, podczas gdy w systemach tradycyjnych może po prostu generować składnik interfejsu użytkownika.
2. Ograniczenia wydajności
- Inżynieria tradycyjna: Wzorce projektowe są zwykle optymalizowane pod kątem opóźnień i przepływności w aplikacjach, takich jak serwery sieciowe, zapytania do bazy danych lub renderowanie interfejsu użytkownika.
- Inżynieria AI: Wymagania dotyczące wydajności w AI sięgają opóźnień inferencji modelu, wykorzystania GPU/TPU i optymalizacji pamięci. Wzorce muszą uwzględniać:
- Buforowanie wyników pośrednich, aby zmniejszyć redundancję obliczeń (wzorzec Dekorator lub Proxy).
- Dynamiczne przełączanie algorytmów (wzorzec Strategy), aby balansować opóźnienia i dokładność w zależności od obciążenia systemu lub ograniczeń czasu rzeczywistego.
3. Charakterystyka danych
- Inżynieria tradycyjna: Wzorce często operują na strukturach wejścia-wyjścia (np. formularze, odpowiedzi API REST).
- Inżynieria AI: Wzorce muszą obsługiwać zmienność danych zarówno pod względem struktury, jak i skali, w tym:
- Dane strumieniowe dla systemów w czasie rzeczywistym.
- Dane wielomodalne (np. tekst, obrazy, filmy) wymagające potoków z elastycznymi krokami przetwarzania.
- Duże zestawy danych, które wymagają wydajnych potoków wstępnego przetwarzania, często wykorzystujących wzorce, takie jak Builder lub Pipeline.
4. Eksperymentowanie vs. stabilność
- Inżynieria tradycyjna: Nacisk kładziony jest na budowanie stabilnych, przewidywalnych systemów, gdzie wzorce zapewniają spójną wydajność i niezawodność.
- Inżynieria AI: Przepływy pracy AI są często eksperymentalne i obejmują:
- Iterowanie nad różnymi architekturami modeli lub technikami wstępnego przetwarzania.
- Dynamiczne aktualizowanie składników systemu (np. ponowne trenowanie modeli, zmiana algorytmów).
- Rozszerzanie istniejących przepływów pracy bez naruszania potoków produkcyjnych, często wykorzystując rozszerzalne wzorce, takie jak Dekorator lub Factory.
Przykład: Fabryka w AI może nie tylko zainicjować model, ale również dołączyć wcześniej załadowane wagi, skonfigurować optymalizatory i połączyć wywołania treningu — wszystko dynamicznie.












