Kunstmatige intelligentie
Ontwerppatronen in Python voor AI- en LLM-ingenieurs: een praktische gids

Als AI-ingenieurs is het essentieel om schone, efficiënte en onderhoudsvriendelijke code te schrijven, vooral bij het bouwen van complexe systemen.
Ontwerppatronen zijn herbruikbare oplossingen voor veelvoorkomende problemen in softwareontwerp. Voor AI- en large language model (LLM)-ingenieurs helpen ontwerppatronen bij het bouwen van robuuste, schaalbare en onderhoudsvriendelijke systemen die complexe workflows efficiënt aanpakken. Dit artikel duikt in ontwerppatronen in Python, met de focus op hun relevantie in AI- en LLM-gebaseerde systemen. Ik zal elk patroon uitleggen met praktische AI-gebruiksvoorbeelden en Python-codevoorbeelden.
Latens enkele sleutelontwerppatronen verkennen die bijzonder nuttig zijn in AI- en machine learning-contexten, samen met Python-voorbeelden.
Waarom ontwerppatronen ertoe doen voor AI-ingenieurs
AI-systemen omvatten vaak:
- Complex objectcreatie (bijv. modellen laden, datapipeline-voorbewerking).
- Beheer van interacties tussen componenten (bijv. modelinferentie, real-time-updates).
- Aanpakken van schaalbaarheid, onderhoud en flexibiliteit voor veranderende vereisten.
Ontwerppatronen lossen deze uitdagingen op door een duidelijke structuur te bieden en ad-hoc-oplossingen te verminderen. Ze vallen in drie hoofdcategorieën:
- Creational Patterns: Focus op objectcreatie. (Singleton, Factory, Builder)
- Structural Patterns: Organiseren van de relaties tussen objecten. (Adapter, Decorator)
- Behavioral Patterns: Beheersen van de communicatie tussen objecten. (Strategy, Observer)
1. Singleton-patroon
Het Singleton-patroon zorgt ervoor dat een klasse slechts één instantie heeft en biedt een globale toegangspunt tot die instantie. Dit is vooral waardevol in AI-workflows waar gedeelde resources, zoals configuraties, logsystemen of modelinstanties, consistent moeten worden beheerd zonder redundantie.
Wanneer te gebruiken
- Beheer van globale configuraties (bijv. modelhyperparameters).
- Gedeelde resources delen over meerdere threads of processen (bijv. GPU-geheugen).
- Garanderen van consistente toegang tot één inference-engine of databaseverbinding.
Implementatie
Hier is hoe u het Singleton-patroon in Python kunt implementeren om configuraties voor een AI-model te beheren:
class ModelConfig:
"""
Een Singleton-klasse voor het beheren van globale modelconfiguraties.
"""
_instance = None # Klassenvariabele om de Singleton-instantie op te slaan
<p>def __new__(cls, *args, **kwargs):
if not cls._instance:
# Maak een nieuwe instantie als er nog geen bestaat
cls._instance = super().__new__(cls)
cls._instance.settings = {} # Initialiseer configuratiedictionary
return cls._instance</p>
<p>def set(self, key, value):
"""
Stel een configuratiesleutel-waarde-paar in.
"""
self.settings[key] = value</p>
<p>def get(self, key):
"""
Haal een configuratiewaarde op met de sleutel.
"""
return self.settings.get(key)</p>
<p># Gebruiksvoorbeeld
config1 = ModelConfig()
config1.set("model_name", "GPT-4")
config1.set("batch_size", 32)</p>
<p># Toegang tot dezelfde instantie
config2 = ModelConfig()
print(config2.get("model_name")) # Uitvoer: GPT-4
print(config2.get("batch_size")) # Uitvoer: 32
print(config1 is config2) # Uitvoer: True (beiden zijn dezelfde instantie)</p>
Uitleg
- De
__new__-methode: Deze zorgt ervoor dat er slechts één instantie van de klasse wordt gemaakt. Als een instantie al bestaat, wordt de bestaande instantie geretourneerd. - Gedeelde staat: Zowel
config1alsconfig2verwijzen naar dezelfde instantie, waardoor alle configuraties wereldwijd toegankelijk en consistent zijn. - AI-gebruiksvoorbeeld: Gebruik dit patroon om globale instellingen te beheren, zoals paden naar datasets, logconfiguraties of omgevingsvariabelen.
2. Factory-patroon
Het Factory-patroon biedt een manier om de creatie van objecten te delegeren aan subklassen of speciale fabrieksmethoden. In AI-systemen is dit patroon ideaal voor het dynamisch maken van verschillende soorten modellen, dataladers of pipelines op basis van context.
Wanneer te gebruiken
- Dynamisch maken van modellen op basis van gebruikersinvoer of taakvereisten.
- Beheer van complexe objectcreatielogica (bijv. meerdere stappen in een datapipeline).
- Loskoppelen van objectinstantiatie van de rest van het systeem om flexibiliteit te verbeteren.
Implementatie
Hier is hoe u een Factory kunt bouwen voor het maken van modellen voor verschillende AI-taken, zoals tekstclassificatie, samenvatting en vertaling:
class BaseModel:
"""
Abstracte basisklasse voor AI-modellen.
"""
def predict(self, data):
raise NotImplementedError("Subklassen moeten de `predict`-methode implementeren")
<p>class TextClassificationModel(BaseModel):
def predict(self, data):
return f"Tekst classificeren: {data}"</p>
<p>class SummarizationModel(BaseModel):
def predict(self, data):
return f"Tekst samenvatten: {data}"</p>
<p>class TranslationModel(BaseModel):
def predict(self, data):
return f"Tekst vertalen: {data}"</p>
<p>class ModelFactory:
"""
Fabrieksklasse om AI-modellen dynamisch te maken.
"""
@staticmethod
def create_model(task_type):
"""
Fabrieksmethode om modellen te maken op basis van de taaktype.
"""
task_mapping = {
"classificatie": TextClassificationModel,
"samenvatting": SummarizationModel,
"vertaling": TranslationModel,
}
model_class = task_mapping.get(task_type)
if not model_class:
raise ValueError(f"Onbekend taaktype: {task_type}")
return model_class()</p>
<p># Gebruiksvoorbeeld
taak = "classificatie"
model = ModelFactory.create_model(taak)
print(model.predict("AI zal de wereld veranderen!"))
# Uitvoer: Tekst classificeren: AI zal de wereld veranderen!</p>
Uitleg
- Abstracte basisklasse: De
BaseModel-klasse definieert de interface (predict) die alle subklassen moeten implementeren, waardoor consistentie wordt gewaarborgd. - Fabrieklogica: De
ModelFactoryselecteert dynamisch de juiste klasse op basis van het taaktype en maakt een instantie. - Uitbreidbaarheid: Het toevoegen van een nieuw modeltype is eenvoudig: implementeer een nieuwe subklasse en werk de fabrieksmapping bij.
AI-gebruiksvoorbeeld
Stel dat u een systeem ontwerpt dat een ander LLM (bijv. BERT, GPT of T5) selecteert op basis van de taak. Het Factory-patroon maakt het eenvoudig om het systeem uit te breiden als nieuwe modellen beschikbaar komen zonder bestaande code te wijzigen.
3. Builder-patroon
Het Builder-patroon scheidt de constructie van een complex object van zijn weergave. Het is handig wanneer een object meerdere stappen vereist om te initialiseren of te configureren.
Wanneer te gebruiken
- Bouwen van meerdere stappen in een datapipeline (bijv. datapreprocessing).
- Beheer van configuraties voor experimenten of modeltraining.
- Maken van objecten die veel parameters vereisen, waarbij leesbaarheid en onderhoud worden gewaarborgd.
Implementatie
Hier is hoe u het Builder-patroon kunt gebruiken om een datapreprocessing-pipeline te maken:
class DataPipeline:
"""
Builder-klasse voor het maken van een datapreprocessing-pipeline.
"""
def __init__(self):
self.stappen = []
<p>def add_step(self, step_function):
"""
Voeg een preprocessing-stap toe aan de pipeline.
"""
self.stappen.append(step_function)
return self # Retourneer zelf voor method chaining</p>
<p>def run(self, data):
"""
Voer alle stappen in de pipeline uit.
"""
for stap in self.stappen:
data = stap(data)
return data</p>
<p># Gebruiksvoorbeeld
pipeline = DataPipeline()
pipeline.add_step(lambda x: x.strip()) # Stap 1: verwijder witruimte
pipeline.add_step(lambda x: x.lower()) # Stap 2: converteer naar kleine letters
pipeline.add_step(lambda x: x.replace(".", "")) # Stap 3: verwijder punten</p>
<p>verwerkte_data = pipeline.run(" Hallo Wereld. ")
print(verwerkte_data) # Uitvoer: hallo wereld</p>
Uitleg
- Gekoppelde methoden: De
add_step-methode staat method chaining toe voor een intuïtieve en compacte syntax bij het definiëren van pipelines. - Stap-voor-stap-uitvoering: De pipeline verwerkt data door deze door elke stap in sequentie te voeren.
- AI-gebruiksvoorbeeld: Gebruik het Builder-patroon om complexe, herbruikbare datapreprocessing-pipelines of modeltrainingsopstellingen te maken.
4. Strategy-patroon
Het Strategy-patroon definieert een familie van uitwisselbare algoritmen, waarbij elk algoritme wordt ingekapseld en het gedrag kan veranderen op runtime. Dit is vooral handig in AI-systemen waar hetzelfde proces (bijv. inferentie of dataprocessing) verschillende benaderingen kan vereisen op basis van de context.
Wanneer te gebruiken
- Wisselen tussen verschillende inferentiestrategieën (bijv. batchverwerking vs. streaming).
- Toepassen van verschillende dataprocessingsTechnieken dynamisch.
- Kiezen van resourcebeheerstrategieën op basis van beschikbare infrastructuur.
Implementatie
Hier is hoe u het Strategy-patroon kunt gebruiken om twee verschillende inferentiestrategieën voor een AI-model te implementeren: batchinferentie en streaming-inferentie.
<p>class InferenceStrategy:
"""
Abstracte basisklasse voor inferentiestrategieën.
"""
def infer(self, model, data):
raise NotImplementedError("Subklassen moeten de `infer`-methode implementeren")</p>
<p>class BatchInference(InferenceStrategy):
"""
Strategie voor batch-inferentie.
"""
def infer(self, model, data):
print("Batch-inferentie uitvoeren...")
return [model.predict(item) for item in data]</p>
<p>class StreamInference(InferenceStrategy):
"""
Strategie voor streaming-inferentie.
"""
def infer(self, model, data):
print("Streaming-inferentie uitvoeren...")
resultaten = []
for item in data:
resultaten.append(model.predict(item))
return resultaten</p>
<p>class InferenceContext:
"""
Contextklasse om tussen inferentiestrategieën te schakelen.
"""
def __init__(self, strategy: InferenceStrategy):
self.strategy = strategy</p>
<p>def set_strategy(self, strategy: InferenceStrategy):
"""
Verander de inferentiestrategie dynamisch.
"""
self.strategy = strategy</p>
<p>def infer(self, model, data):
"""
Delegeer inferentie naar de geselecteerde strategie.
"""
return self.strategy.infer(model, data)</p>
<p># Mock Model Klasse
class MockModel:
def predict(self, input_data):
return f"Voorspeld: {input_data}"</p>
<p># Gebruiksvoorbeeld
model = MockModel()
data = ["voorbeeld1", "voorbeeld2", "voorbeeld3"]</p>
<p>context = InferenceContext(BatchInference())
print(context.infer(model, data))
# Uitvoer:
# Batch-inferentie uitvoeren...
# ['Voorspeld: voorbeeld1', 'Voorspeld: voorbeeld2', 'Voorspeld: voorbeeld3']</p>
<p># Schakel over naar streaming-inferentie
context.set_strategy(StreamInference())
print(context.infer(model, data))
# Uitvoer:
# Streaming-inferentie uitvoeren...
# ['Voorspeld: voorbeeld1', 'Voorspeld: voorbeeld2', 'Voorspeld: voorbeeld3']</p>
Uitleg
- Abstracte strategieklasse: De
InferenceStrategydefinieert de interface die alle strategieën moeten volgen. - Concrete strategieën: Elke strategie (bijv.
BatchInference,StreamInference) implementeert de logica specifiek voor die benadering. - Dynamisch schakelen: De
InferenceContextstaat het schakelen tussen strategieën op runtime toe, waardoor flexibiliteit voor verschillende use cases wordt geboden.
Wanneer te gebruiken
- Wissel tussen batch-inferentie voor offline-verwerking en streaming-inferentie voor real-time-toepassingen.
- Pas datapreprocessing- of voorbewerkingstechnieken dynamisch aan op basis van de taak of invoerformaat.
5. Observer-patroon
Het Observer-patroon vestigt een een-op-veel-relatie tussen objecten. Wanneer een object (het onderwerp) van staat verandert, worden alle afhankelijke objecten (observatoren) automatisch op de hoogte gesteld. Dit is vooral handig in AI-systemen voor real-time-monitoring, gebeurtenisbeheer of gegevenssynchronisatie.
Wanneer te gebruiken
- Monitor metrics zoals nauwkeurigheid of verlies tijdens modeltraining.
- Real-time-updates voor dashboards of logs.
- Beheer van afhankelijkheden tussen componenten in complexe workflows.
Implementatie
Hier is hoe u het Observer-patroon kunt gebruiken om de prestaties van een AI-model in real-time te monitoren.
class Onderwerp:
"""
Basisklasse voor onderwerpen die worden geobserveerd.
"""
def __init__(self):
self._observatoren = []
<p>def attach(self, observer):
"""
Koppel een observer aan het onderwerp.
"""
self._observatoren.append(observer)</p>
<p>def detach(self, observer):
"""
Ontkoppel een observer van het onderwerp.
"""
self._observatoren.remove(observer)</p>
<p>def notify(self, data):
"""
Stel alle observatoren op de hoogte van een verandering in staat.
"""
for observer in self._observatoren:
observer.update(data)</p>
<p>class ModelMonitor(Onderwerp):
"""
Onderwerp dat modelprestatie-metrics monitort.
"""
def update_metrics(self, metric_name, value):
"""
Simuleer het bijwerken van een prestatie-metric en stel observatoren op de hoogte.
"""
print(f"{metric_name} bijgewerkt: {value}")
self.notify({metric_name: value})</p>
<p>class Observer:
"""
Basisklasse voor observatoren.
"""
def update(self, data):
raise NotImplementedError("Subklassen moeten de `update`-methode implementeren")</p>
<p>class LoggerObserver(Observer):
"""
Observer om metrics te loggen.
"""
def update(self, data):
print(f"Loggen van metric: {data}")</p>
<p>class AlertObserver(Observer):
"""
Observer om alerts te activeren als drempelwaarden worden overschreden.
"""
def __init__(self, threshold):
self.threshold = threshold</p>
<p>def update(self, data):
for metric, value in data.items():
if value > self.threshold:
print(f"ALERT: {metric} heeft drempelwaarde overschreden met waarde {value}")</p>
<p># Gebruiksvoorbeeld
monitor = ModelMonitor()
logger = LoggerObserver()
alert = AlertObserver(threshold=90)</p>
<p>monitor.attach(logger)
monitor.attach(alert)</p>
<p># Simuleer metric-updates
monitor.update_metrics("nauwkeurigheid", 85) # Logt de metric
monitor.update_metrics("nauwkeurigheid", 95) # Logt en activeert alert</p>
- Onderwerp: Beheert een lijst van observatoren en stelt hen op de hoogte wanneer de staat verandert. In dit voorbeeld monitort de
ModelMonitor-klasse metrics. - Observatoren: Voeren specifieke acties uit wanneer ze worden opgeroepen. De
LoggerObserverlogt metrics, terwijl deAlertObserveralerts activeert als een drempelwaarde wordt overschreden. - Losgekoppelde ontwerp: Observatoren en onderwerpen zijn losgekoppeld, waardoor het systeem modulair en uitbreidbaar is.
Hoe ontwerppatronen verschillen voor AI-ingenieurs vs. traditionele ingenieurs
Ontwerppatronen, hoewel universeel toepasbaar, nemen unieke kenmerken aan wanneer ze in AI-engineering worden geïmplementeerd in vergelijking met traditionele software-engineering. Het verschil ligt in de uitdagingen, doelen en workflows die inherent zijn aan AI-systemen, die vaak vereisen dat patronen worden aangepast of uitgebreid buiten hun conventionele toepassingen.
1. Objectcreatie: statisch vs. dynamische behoeften
- Traditionele engineering: Objectcreatiepatronen zoals Factory of Singleton worden vaak gebruikt om configuraties, databaseverbindingen of gebruikersessies te beheren. Deze zijn meestal statisch en goed gedefinieerd tijdens systeemontwerp.
- AI-engineering: Objectcreatie omvat vaak dynamische workflows, zoals:
- Modellen maken op basis van gebruikersinvoer of systeemeisen.
- Modelconfiguraties laden voor taken zoals vertaling, samenvatting of classificatie.
- Meerdere dataprocessing-pipelines instantiëren die variëren op basis van datasetkenmerken (bijv. tabulair vs. ongestructureerde tekst).
Voorbeeld: In AI kan een Factory-patroon dynamisch een diep leermodel genereren op basis van de taak en hardwarebeperkingen, terwijl in traditionele systemen het een gebruikersinterfacecomponent zou kunnen genereren.
2. Prestatiebeperkingen
- Traditionele engineering: Ontwerppatronen worden meestal geoptimaliseerd voor latentie en doorvoer in toepassingen zoals web servers, databasequeries of UI-weergave.
- AI-engineering: Prestatievereisten in AI omvatten modelinferentielatentie, GPU/TPU-gebruik en geheugenoptimalisatie. Patronen moeten rekening houden met:
- Tussenresultaten in cache opslaan om redundante berekeningen te verminderen (Decorator- of Proxy-patronen).
- Algoritmen dynamisch wisselen (Strategy-patroon) om latentie en nauwkeurigheid te balanceren op basis van systeembezetting of real-time-eisen.
3. Data-georiënteerde aard
- Traditionele engineering: Patronen werken vaak met vaste invoer-uitvoerstructuren (bijv. formulieren, REST API-antwoorden).
- AI-engineering: Patronen moeten gegevensvariabiliteit in zowel structuur als schaal aanpakken, waaronder:
- Streaming-gegevens voor real-time-systemen.
- Multimodale gegevens (bijv. tekst, afbeeldingen, video’s) die flexibele verwerkingstappen vereisen.
- Grote datasets die efficiënte preprocessing- en augmentatie-pipelines nodig hebben, vaak met behulp van patronen zoals Builder of Pipeline.
4. Experimentatie vs. stabiliteit
- Traditionele engineering: De focus ligt op het bouwen van stabiele, voorspelbare systemen waarbij patronen consistentie en betrouwbaarheid garanderen.
- AI-engineering: AI-workflows zijn vaak experimenteel en omvatten:
- Itereren over verschillende modelarchitecturen of datapreprocessingtechnieken.
- Dynamisch bijwerken van systeemcomponenten (bijv. modellen opnieuw trainen, algoritmen wisselen).
- Bestaande workflows uitbreiden zonder productie-pipelines te breken, vaak met behulp van uitbreidbare patronen zoals Decorator of Factory.
Voorbeeld: Een Factory in AI kan niet alleen een model instantiëren, maar ook vooraf geladen gewichten toevoegen, optimizers configureren en trainingscallbacks koppelen, allemaal dynamisch.












