mastodon.gamedev.place is one of the many independent Mastodon servers you can use to participate in the fediverse.
Mastodon server focused on game development and related topics.

Server stats:

5.1K
active users

#select

0 posts0 participants0 posts today

Inheritance in Python: la chiave per scrivere codice pulito e collaborativo nel Machine Learning

Molte persone che si avvicinano al machine learning non hanno un forte background in ingegneria del software, e quando devono lavorare su un prodotto reale, il loro codice può risultare disordinato e difficile da gestire. Per questo motivo, raccomando sempre vivamente di imparare a usare le coding best practices, che ti permetteranno di lavorare senza problemi all’interno di un team e di migliorare il livello del progetto su cui stai lavorando. Oggi voglio parlare dell’inheritance di Python e mostrare alcuni semplici esempi di come utilizzarla nel campo del machine learning.

Nello sviluppo software e in altri ambiti dell’informatica, il technical debt (noto anche come design debt o code debt) rappresenta il costo implicito di future rielaborazioni dovuto a una soluzione che privilegia la rapidità rispetto a un design a lungo termine.

Se sei interessato a saperne di più sui design patterns, potresti trovare utili alcuni dei miei articoli precedenti.

Python Inheritance


L’Inheritance non è solo un concetto di Python, ma un concetto generale nell’Object Oriented Programming. Quindi, in questo tutorial, dovremo lavorare con classei e oggetti, che rappresentano un paradigma di sviluppo non molto utilizzato in Python rispetto ad altri linguaggi come Java.

Nell’OOP, possiamo definire una classe generale che rappresenta qualcosa nel mondo, ad esempio una Persona, che definiamo semplicemente con un nome, un cognome e un’età nel seguente modo.

class Person:
def __init__(self, name, surname, age):
self.name = name
self.surname = surname
self.age = age

def __str__(self):
return f"Name: {self.name}, surname: {self.surname}, age: {self.age}"

def grow(self):
self.age +=1

In questa classe, abbiamo definito un semplice costruttore (init). Poi abbiamo definito il method str, che si occuperà di stampare l’oggetto nel modo che desideriamo. Infine, abbiamo il method grow() per rendere la persona di un anno più vecchia.

Ora possiamo instanziare un oggetto e utilizzare questa classe.

person = Person("Marcello", "Politi", 28)
person.grow()
print(person)

# output wiil be
# Name: Marcello, surname: Politi, age: 29

E se volessimo definire un particolare tipo di persona, ad esempio un operaio? Possiamo fare la stessa cosa di prima, ma aggiungiamo un’altra variabile di input per aggiungere il suo stipendio.

class Worker:
def __init__(self, name, surname, age, salary):
self.name = name
self.surname = surname
self.age = age
self.salary = salary

def __str__(self):
return f"Name: {self.name}, surname: {self.surname}, age: {self.age}, salary: {self.salary}"

def grow(self):
self.age +=1

Tutto qui. Ma è questo il modo migliore per implementarlo? Vedete che la maggior parte del codice del lavoratore è uguale a quello della persona, perché un lavoratore è una persona particolare e condivide molte cose in comune con una persona.

Quello che possiamo fare è dire a Python che il lavoratore deve ereditare tutto da Persona, e poi aggiungere manualmente tutte le cose di cui abbiamo bisogno, che una persona generica non ha.

class Worker(Person):
def __init__(self, name, surname, age, salary):
super().__init__(name, surname, age)
self.salary = salary

def __str__(self):
text = super().__str__()
return text + f",salary: {self.salary}"

Nella classe worker, il costruttore richiama il costruttore della classe person sfruttando la parola chiave super() e poi aggiunge anche la variabile salary.

La stessa cosa avviene quando si definisce il metodo str. Utilizziamo lo stesso testo restituito da Person usando la parola chiave super e aggiungiamo il salario quando stampiamo l’oggetto.

Ereditarietà nel Machine Learning


Non ci sono regole su quando usare l’ereditarietà nell’machine learning . Non so a quale progetto stiate lavorando, né come sia il vostro codice. Voglio solo sottolineare il fatto che dovreste adottare un paradigma OOP nel vostro codice. Tuttavia, vediamo alcuni esempi di utilizzo dell’ereditarietà.

Definire un BaseModel


Sviluppiamo una classe di base per il modello di ML, definita da alcune variabili standard. Questa classe avrà un metodo per caricare i dati, uno per addestrare, un altro per valutare e uno per preelaborare i dati. Tuttavia, ogni modello specifico preelaborerà i dati in modo diverso, quindi le sottoclassi che erediteranno il modello di base dovranno riscrivere il metodo di preelaborazione.
Attenzione, il modello BaseMLModel stesso eredita la classe ABC. Questo è un modo per dire a Python che questa classe è una classe astratta e non deve essere usata, ma è solo un modello per costruire sottoclassi.

Lo stesso vale per il metodo preprocess_train_data, che è contrassegnato come @abstactmethod. Ciò significa che le sottoclassi devono reimplementare questo metodo.

Guardate questo video per saperne di più su classi e metodi astratti:

youtube.com/embed/UDmJGvM-OUw?…

from abc import ABC, abstractmethod
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_iris
import numpy as np

class BaseMLModel(ABC):
def __init__(self, test_size=0.2, random_state=42):
self.model = None # This will be set in subclasses
self.test_size = test_size
self.random_state = random_state
self.X_train = None
self.X_test = None
self.y_train = None
self.y_test = None

def load_data(self, X, y):
self.X_train, self.X_test, self.y_train, self.y_test = train_test_split(
X, y, test_size=self.test_size, random_state=self.random_state
)

@abstractmethod
def preprocess_train_data(self):
"""Each model can define custom preprocessing for training data."""
pass

def train(self):
self.X_train, self.y_train = self.preprocess_train_data()
self.model.fit(self.X_train, self.y_train)

def evaluate(self):
predictions = self.model.predict(self.X_test)
return accuracy_score(self.y_test, predictions)

Vediamo ora come ereditare da questa classe. Per prima cosa, possiamo implementare un LogisticRegressionModel. Che avrà il suo algoritmo di preelaborazione.

class LogisticRegressionModel(BaseMLModel):
def __init__(self, **kwargs):
super().__init__()
self.model = LogisticRegression(**kwargs)

def preprocess_train_data(self):
#Standardize features for Logistic Regression
mean = self.X_train.mean(axis=0)
std = self.X_train.std(axis=0)
X_train_scaled = (self.X_train - mean) / std
return X_train_scaled, self.y_train

Poi possiamo definire tutte le sottoclassi che vogliamo. Qui ne definisco una per una Random Forest.

class RandomForestModel(BaseMLModel):
def __init__(self, n_important_features=2, **kwargs):
super().__init__()
self.model = RandomForestClassifier(**kwargs)
self.n_important_features = n_important_features

def preprocess_train_data(self):
#Select top `n_important_features` features based on variance
feature_variances = np.var(self.X_train, axis=0)
top_features_indices = np.argsort(feature_variances)[-self.n_important_features:]
X_train_selected = self.X_train[:, top_features_indices]
return X_train_selected, self.y_train

Ora usiamo tutto nella funzione main.

if __name__ == "__main__":
# Load dataset
data = load_iris()
X, y = data.data, data.target

# Logistic Regression
log_reg_model = LogisticRegressionModel(max_iter=200)
log_reg_model.load_data(X, y)
log_reg_model.train()
print(f"Logistic Regression Accuracy: {log_reg_model.evaluate()}")

# Random Forest
rf_model = RandomForestModel(n_estimators=100, n_important_features=3)
rf_model.load_data(X, y)
rf_model.train()
print(f"Random Forest Accuracy: {rf_model.evaluate()}")

Conclusioni


Uno dei principali vantaggi dell’ereditarietà di Python nei progetti ML è nella progettazione di codici modulari, mantenibili e scalabili. L’ereditarietà aiuta a evitare codice ridondante, scrivendo la logica comune in una classe base, come BaseMLModel, riducendo quindi la duplicazione del codice. L’inheritance rende anche facile incapsulare comportamenti comuni in una classe base, permettendo alle subclasses di definire dettagli specifici.

Il principale vantaggio, a mio avviso, è che una codebase ben organizzata e orientata agli oggetti consente a più sviluppatori all’interno di un team di lavorare indipendentemente su parti separate. Nel nostro esempio, un ingegnere capo potrebbe definire il modello base, e poi ogni sviluppatore potrebbe concentrarsi su un singolo algoritmo e scrivere la subclass.
Prima di immergerti in design patterns complessi, concentrati sull’utilizzo delle best practices nell’OOP. Farlo ti renderà un programmatore migliore rispetto a molti altri nel campo dell’AI!

L'articolo Inheritance in Python: la chiave per scrivere codice pulito e collaborativo nel Machine Learning proviene da il blog della sicurezza informatica.

il blog della sicurezza informatica · Scopriamo le differenze tra un algoritmo di Machine Learning e Deep LearningScopri le differenze tra Machine Learning e Deep Learning, due metodi per implementare algoritmi di AI. Esplora come funzionano, le capacità e le caratteristiche di ciascuno, e l'importanza di scegliere l'approccio giusto per i tuoi casi d'uso specifici.

Inheritance in Python: la chiave per scrivere codice pulito e collaborativo nel Machine Learnin

Molte persone che si avvicinano al machine learning non hanno un forte background in ingegneria del software, e quando devono lavorare su un prodotto reale, il loro codice può risultare disordinato e difficile da gestire. Per questo motivo, raccomando sempre vivamente di imparare a usare le coding best practices, che ti permetteranno di lavorare senza problemi all’interno di un team e di migliorare il livello del progetto su cui stai lavorando. Oggi voglio parlare dell’inheritance di Python e mostrare alcuni semplici esempi di come utilizzarla nel campo del machine learning.

Nello sviluppo software e in altri ambiti dell’informatica, il technical debt (noto anche come design debt o code debt) rappresenta il costo implicito di future rielaborazioni dovuto a una soluzione che privilegia la rapidità rispetto a un design a lungo termine.

Se sei interessato a saperne di più sui design patterns, potresti trovare utili alcuni dei miei articoli precedenti.

Python Inheritance


L’Inheritance non è solo un concetto di Python, ma un concetto generale nell’Object Oriented Programming. Quindi, in questo tutorial, dovremo lavorare con classei e oggetti, che rappresentano un paradigma di sviluppo non molto utilizzato in Python rispetto ad altri linguaggi come Java.

Nell’OOP, possiamo definire una classe generale che rappresenta qualcosa nel mondo, ad esempio una Persona, che definiamo semplicemente con un nome, un cognome e un’età nel seguente modo.

class Person:
def __init__(self, name, surname, age):
self.name = name
self.surname = surname
self.age = age

def __str__(self):
return f"Name: {self.name}, surname: {self.surname}, age: {self.age}"

def grow(self):
self.age +=1

In questa classe, abbiamo definito un semplice costruttore (init). Poi abbiamo definito il method str, che si occuperà di stampare l’oggetto nel modo che desideriamo. Infine, abbiamo il method grow() per rendere la persona di un anno più vecchia.

Ora possiamo instanziare un oggetto e utilizzare questa classe.

person = Person("Marcello", "Politi", 28)
person.grow()
print(person)

# output wiil be
# Name: Marcello, surname: Politi, age: 29

E se volessimo definire un particolare tipo di persona, ad esempio un operaio? Possiamo fare la stessa cosa di prima, ma aggiungiamo un’altra variabile di input per aggiungere il suo stipendio.

class Worker:
def __init__(self, name, surname, age, salary):
self.name = name
self.surname = surname
self.age = age
self.salary = salary

def __str__(self):
return f"Name: {self.name}, surname: {self.surname}, age: {self.age}, salary: {self.salary}"

def grow(self):
self.age +=1

Tutto qui. Ma è questo il modo migliore per implementarlo? Vedete che la maggior parte del codice del lavoratore è uguale a quello della persona, perché un lavoratore è una persona particolare e condivide molte cose in comune con una persona.

Quello che possiamo fare è dire a Python che il lavoratore deve ereditare tutto da Persona, e poi aggiungere manualmente tutte le cose di cui abbiamo bisogno, che una persona generica non ha.

class Worker(Person):
def __init__(self, name, surname, age, salary):
super().__init__(name, surname, age)
self.salary = salary

def __str__(self):
text = super().__str__()
return text + f",salary: {self.salary}"

Nella classe worker, il costruttore richiama il costruttore della classe person sfruttando la parola chiave super() e poi aggiunge anche la variabile salary.

La stessa cosa avviene quando si definisce il metodo str. Utilizziamo lo stesso testo restituito da Person usando la parola chiave super e aggiungiamo il salario quando stampiamo l’oggetto.

Ereditarietà nel Machine Learning


Non ci sono regole su quando usare l’ereditarietà nell’machine learning . Non so a quale progetto stiate lavorando, né come sia il vostro codice. Voglio solo sottolineare il fatto che dovreste adottare un paradigma OOP nel vostro codice. Tuttavia, vediamo alcuni esempi di utilizzo dell’ereditarietà.

Definire un BaseModel


Sviluppiamo una classe di base per il modello di ML, definita da alcune variabili standard. Questa classe avrà un metodo per caricare i dati, uno per addestrare, un altro per valutare e uno per preelaborare i dati. Tuttavia, ogni modello specifico preelaborerà i dati in modo diverso, quindi le sottoclassi che erediteranno il modello di base dovranno riscrivere il metodo di preelaborazione.
Attenzione, il modello BaseMLModel stesso eredita la classe ABC. Questo è un modo per dire a Python che questa classe è una classe astratta e non deve essere usata, ma è solo un modello per costruire sottoclassi.

Lo stesso vale per il metodo preprocess_train_data, che è contrassegnato come @abstactmethod. Ciò significa che le sottoclassi devono reimplementare questo metodo.

Guardate questo video per saperne di più su classi e metodi astratti:

youtube.com/embed/UDmJGvM-OUw?…

from abc import ABC, abstractmethod
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_iris
import numpy as np

class BaseMLModel(ABC):
def __init__(self, test_size=0.2, random_state=42):
self.model = None # This will be set in subclasses
self.test_size = test_size
self.random_state = random_state
self.X_train = None
self.X_test = None
self.y_train = None
self.y_test = None

def load_data(self, X, y):
self.X_train, self.X_test, self.y_train, self.y_test = train_test_split(
X, y, test_size=self.test_size, random_state=self.random_state
)

@abstractmethod
def preprocess_train_data(self):
"""Each model can define custom preprocessing for training data."""
pass

def train(self):
self.X_train, self.y_train = self.preprocess_train_data()
self.model.fit(self.X_train, self.y_train)

def evaluate(self):
predictions = self.model.predict(self.X_test)
return accuracy_score(self.y_test, predictions)

Vediamo ora come ereditare da questa classe. Per prima cosa, possiamo implementare un LogisticRegressionModel. Che avrà il suo algoritmo di preelaborazione.

class LogisticRegressionModel(BaseMLModel):
def __init__(self, **kwargs):
super().__init__()
self.model = LogisticRegression(**kwargs)

def preprocess_train_data(self):
#Standardize features for Logistic Regression
mean = self.X_train.mean(axis=0)
std = self.X_train.std(axis=0)
X_train_scaled = (self.X_train - mean) / std
return X_train_scaled, self.y_train

Poi possiamo definire tutte le sottoclassi che vogliamo. Qui ne definisco una per una Random Forest.

class RandomForestModel(BaseMLModel):
def __init__(self, n_important_features=2, **kwargs):
super().__init__()
self.model = RandomForestClassifier(**kwargs)
self.n_important_features = n_important_features

def preprocess_train_data(self):
#Select top `n_important_features` features based on variance
feature_variances = np.var(self.X_train, axis=0)
top_features_indices = np.argsort(feature_variances)[-self.n_important_features:]
X_train_selected = self.X_train[:, top_features_indices]
return X_train_selected, self.y_train

Ora usiamo tutto nella funzione main.

if __name__ == "__main__":
# Load dataset
data = load_iris()
X, y = data.data, data.target

# Logistic Regression
log_reg_model = LogisticRegressionModel(max_iter=200)
log_reg_model.load_data(X, y)
log_reg_model.train()
print(f"Logistic Regression Accuracy: {log_reg_model.evaluate()}")

# Random Forest
rf_model = RandomForestModel(n_estimators=100, n_important_features=3)
rf_model.load_data(X, y)
rf_model.train()
print(f"Random Forest Accuracy: {rf_model.evaluate()}")

Conclusioni


Uno dei principali vantaggi dell’ereditarietà di Python nei progetti ML è nella progettazione di codici modulari, mantenibili e scalabili. L’ereditarietà aiuta a evitare codice ridondante, scrivendo la logica comune in una classe base, come BaseMLModel, riducendo quindi la duplicazione del codice. L’inheritance rende anche facile incapsulare comportamenti comuni in una classe base, permettendo alle subclasses di definire dettagli specifici.

Il principale vantaggio, a mio avviso, è che una codebase ben organizzata e orientata agli oggetti consente a più sviluppatori all’interno di un team di lavorare indipendentemente su parti separate. Nel nostro esempio, un ingegnere capo potrebbe definire il modello base, e poi ogni sviluppatore potrebbe concentrarsi su un singolo algoritmo e scrivere la subclass.
Prima di immergerti in design patterns complessi, concentrati sull’utilizzo delle best practices nell’OOP. Farlo ti renderà un programmatore migliore rispetto a molti altri nel campo dell’AI!

L'articolo Inheritance in Python: la chiave per scrivere codice pulito e collaborativo nel Machine Learnin proviene da il blog della sicurezza informatica.

il blog della sicurezza informatica · Scopriamo le differenze tra un algoritmo di Machine Learning e Deep LearningScopri le differenze tra Machine Learning e Deep Learning, due metodi per implementare algoritmi di AI. Esplora come funzionano, le capacità e le caratteristiche di ciascuno, e l'importanza di scegliere l'approccio giusto per i tuoi casi d'uso specifici.

First change since #swad 0.2 will actually be a (huge?) improvement to my #poser lib. So far, it was hardwired to use the good old #POSIX #select call. This is perfectly fine for handling around up to 100 (or at least less than 1000, YMMV) clients.

Some #select implementations offer defining the upper limit for checked file descriptors. Added support for that.

POSIX also specifies #poll, which has very similar #scalability issues, but slightly different. Added support for this as well.

And then, I went on to add support for the #Linux-specific #epoll and #BSD-specific #kqueue (#FreeBSD, #NetBSD, #OpenBSD, ...) which are both designed to *solve* any scalability issues 🥳

A little thing that slightly annoyed me about kqueue was that there's no support for temporarily changing the signal mask, so I had to do the silly dance shown in the screenshot. OTOH, it offers changing event filters and getting events in a single call, which I might try to even further optimize ... 😎

Replied in thread

@toomanysecrets It's kind of funny the talk is all about #epoll although the text then correctly tells that BSD's (originally #FreeBSD but adopted by the others as well) #kqueue was the first async event interface without the scalability issues of #select and #poll. 🙈

Talking of these, I'd say the "Before epoll" paragraph is, at least partially, wrong. It just omits select/poll. Both are specified in #POSIX (and therefore available on *many* systems), and both offer the same async model, with the drawback of having scalability limitations. As a rule of thumb, they're fine for a few hundred concurrent clients, but not more.

The really wasteful "forking model" (which is kind of classic Unix, really forking one process per client) predates both.

It's really a shame there is no common standard for the modern, really scalable APIs. 😞 We have #epoll, #kqueue, there's "IO completion ports" in Windows, etc. I tend to still use classic #select a lot, if more than a few hundred concurrent (!) clients is nothing you could ever expect for your software, it's still fine.

Integrating #xcb in a #select / #poll based #event loop, next chapter 🤯

Testing in a remote session with xrdp, I observed the first tooltip rendering only the frame → obviously an unnoticed (queued by xcb) expose event.

Tried to add code checking after xcb_flush() whether more bytes were read, and, indeed, this fixed it. Oh my. 🤪

github.com/Zirias/xmoji/commit

I really like xcb so far, but this eagerness to read and queue stuff without a way to disable that behavior is more or less braindead and should really be fixed. Any real-life application will do other things than just interacting with #X11, so running your own event loop *is* a common scenario. And I refuse to do the stupid workaround seen a lot which dedicates a thread to xcb_wait_for_events() and friends.

GitHubX11Adapter: Add another check for queued events · Zirias/xmoji@e7044fbIt seems in rare cases, even xcb_flush() can trigger reads, catch this as well.
Replied in thread

Adventures with #X11 #programming using #xcb: I now have to admit it's *really* hard to *correctly* integrate xcb with a typical generic event loop working on file descriptors (e.g. with #select or #poll). My issue of an occassionally undrawn window was probably caused by not getting that correct.

Xcb more or less enforces its own asynchronous model, getting a "cookie" for each request and using that later to check for the reply. This works great if talking to the X server is all you do, but otherwise, it's a pain. Xcb also demuxes events from replies, and while there's a function to check only for *queued* events, there's no such function for replies, so checking for them could always trigger another read from the socket, possibly queueing new events. 🤯

Many projects resort to dedicating a thread to xcb and using its blocking API from there to solve this issue. I think integrating into a generic event loop is still *possible* though, and this is what I finally came up with (commented implementation because it's just too complex):
github.com/Zirias/xmoji/blob/d

If you're an xcb expert and spot an error there, please let me know 😂

This code requires two things to work reliably:

- Never ever use one of xcb's reply functions, instead use the AWAIT() macro to get a callback when the reply arrives
- Never use an xcb wait_for or poll_for function, if error-handling for a request is needed, use one of the overloads of CHECK()

GitHubxmoji/src/bin/xmoji/x11adapter.c at d5fcf301c0cb65b5a33e1310b298c01bcd682f47 · Zirias/xmojiContribute to Zirias/xmoji development by creating an account on GitHub.