Faza 02 · lecția 08
Ingineria și selecția caracteristicilor
Scopul lecției: Tip: Construire Limbaje: Python Cerințe preliminare: Faza 1 (Statistică pentru ML, Algebră liniară), Faza 2, lecțiile 1–7 Durată: ~90 de minute
Versiunea curentă AlexBred.com: primele 100 de lecții ale programului în limba română.
Cuprinsul lecției
- Obiective de învățare
- Problema
- Conceptul
- Pipeline-ul caracteristicilor
- Caracteristici numerice
- Caracteristici categoriale
- Caracteristici textuale
- Valori lipsă
- Interacțiunea caracteristicilor
- Selecția caracteristicilor
- Construiți
- Pasul 1: transformări numerice de la zero
- Pasul 2: codificare categorială de la zero
- Pasul 3: caracteristici textuale de la zero
- Pasul 4: imputarea valorilor lipsă de la zero
- Pasul 5: selecția caracteristicilor de la zero
- Pasul 6: pipeline complet și demonstrație
- Folosiți
- Livrați
- Exerciții
- Termeni-cheie
- Lecturi suplimentare
O caracteristică bună poate valora cât o mie de puncte de date.
Tip: Construire Limbaje: Python Cerințe preliminare: Faza 1 (Statistică pentru ML, Algebră liniară), Faza 2, lecțiile 1–7 Durată: ~90 de minute
Obiective de învățare
- Să implementați transformări numerice — standardizare, scalare min–max, transformare logaritmică și discretizare — și să explicați când este potrivită fiecare
- Să construiți codificări one-hot, ordinale și bazate pe țintă pentru caracteristici categoriale și să identificați riscul de scurgere a datelor la codificarea prin țintă
- Să construiți de la zero un vectorizator TF-IDF și să explicați când poate depăși numărarea brută a cuvintelor în clasificarea textului
- Să aplicați selecția caracteristicilor prin metode de filtrare — prag de varianță, corelație și informație mutuală — pentru a reduce dimensionalitatea
Problema
Aveți un set de date. Alegeți un algoritm și îl antrenați. Rezultatele sunt mediocre. Încercați un algoritm mai sofisticat, dar rezultatele rămân mediocre. Petreceți o săptămână reglând hiperparametrii și obțineți doar o îmbunătățire marginală.
Apoi cineva transformă datele brute în caracteristici mai potrivite, iar o regresie logistică simplă depășește ansamblul gradient boosting pe care l-ați reglat.
Acest lucru se întâmplă frecvent. În ML clasic, reprezentarea datelor poate conta cel puțin la fel de mult ca alegerea algoritmului. Un model pentru prețul locuințelor care primește suprafața și numărul de dormitoare pornește de la un semnal mai util decât unul care primește adresa ca șir brut, indiferent de sofisticarea algoritmului. Modelul poate lucra numai cu informația și reprezentarea pe care i le furnizați.
Ingineria caracteristicilor transformă datele brute în reprezentări care fac tiparele mai accesibile modelului. Selecția caracteristicilor elimină caracteristici irelevante, redundante sau prea zgomotoase pentru obiectivul dat. Împreună pot avea un impact foarte mare în proiectele ML clasice, dar valoarea lor trebuie măsurată pe validare, în cadrul pipeline-ului complet.
Conceptul
Pipeline-ul caracteristicilor
Toate etapele care învață statistici, categorii, vocabular sau selecții trebuie ajustate numai pe datele de antrenare din fiecare partiție de validare, apoi aplicate fără reajustare datelor de validare și testare.
Caracteristici numerice
Numerele brute necesită adesea transformări înainte de modelare:
Scalare: aduce caracteristicile la scări comparabile, astfel încât unitățile numerice să nu domine neintenționat algoritmii bazați pe distanță, precum K-Means și KNN, sau modelele sensibile la scară și regularizare, precum multe configurații SVM. Scalarea min–max ajustează valorile de antrenare la [0, 1]. Standardizarea z-score scade media de antrenare și împarte la abaterea standard de antrenare.
Notă tehnică: Datele noi pot ajunge în afara intervalului [0, 1] stabilit de
MinMaxScaler, iar setul transformat nu va avea întotdeauna exact media 0 și abaterea 1. Parametrii se învață din train; relevanța scalării depinde de model și de semnificația unităților.
Transformare logaritmică: comprimă distribuțiile asimetrice la dreapta, precum venitul, populația sau numărul de cuvinte, și poate transforma unele relații multiplicative în relații aditive. Domeniul trebuie tratat explicit: log(1+x) necesită x > −1, iar valori mai mici sau semnate pot necesita o altă transformare.
Discretizare: transformă valori continue în intervale categoriale. Poate fi utilă când relația dintre caracteristică și țintă este neliniară, dar aproximativ în trepte, de exemplu pentru grupe de vârstă. Limitele intervalelor se învață din train, iar discretizarea pierde informație și introduce discontinuități.
Caracteristici polinomiale: creează termeni precum x^2, x^3 și x1*x2. Permit modelelor liniare să surprindă relații neliniare în intrările originale, cu prețul creșterii dimensionalității și al unui risc mai mare de supraînvățare.
Caracteristici categoriale
Multe modele necesită intrări numerice, deci categoriile trebuie codificate.
Codificare one-hot: creează o coloană binară pentru fiecare categorie cunoscută. „culoare = roșu/albastru/verde” devine trei coloane: este_roșu, este_albastru și este_verde. Funcționează bine pentru cardinalitate mică sau moderată, dar poate produce foarte multe coloane la cardinalitate mare. Stabiliți și comportamentul pentru categorii necunoscute.
Codificare ordinală: asociază fiecare categorie cu un întreg, de exemplu roșu=0, albastru=1, verde=2. Dacă ordinea nu are sens real, codurile introduc o relație artificială. Acest lucru afectează și arborii, deoarece o împărțire numerică grupează categoriile după pragul impus de coduri; codificarea este justificată direct atunci când categoriile sunt cu adevărat ordinale sau când estimatorul tratează explicit categoriile.
Codificare prin țintă: înlocuiește fiecare categorie cu o estimare netezită a mediei țintei pentru acea categorie. Este puternică, dar riscantă: pentru rândurile de antrenare trebuie folosită o schemă out-of-fold sau cross-fitting, astfel încât valoarea rândului să nu-și codifice propria țintă. Codificarea finală se ajustează numai pe train și se aplică pe test, cu o regulă pentru categorii necunoscute.
Caracteristici textuale
Vectorizator de numărare: numără aparițiile fiecărui termen într-un document. „pisica stă pe covor lângă pisică” devine {pisică: 2, stă: 1, pe: 1, covor: 1, lângă: 1} după tokenizarea și normalizarea alese.
TF-IDF: frecvența termenului înmulțită cu frecvența inversă a documentelor. Termenii sunt ponderați după cât de distinctivi sunt în corpus; cuvintele prezente în foarte multe documente primesc o pondere IDF mai mică.
TF(cuvânt, document) = numărul aparițiilor cuvântului în document / numărul total de cuvinte din document
IDF(cuvânt) = log(numărul total de documente / numărul documentelor care conțin cuvântul)
TF-IDF = TF * IDF
Aceasta este o formulă didactică. Implementările diferă prin tokenizare, netezirea IDF, adăugarea unei constante, TF subliniar și normalizarea vectorilor. TF-IDF poate depăși numărătorile brute când termenii foarte frecvenți sunt puțin discriminativi, dar nu există o garanție universală; comparați reprezentările prin validare.
Valori lipsă
Datele reale au goluri. Strategiile includ:
- Eliminarea rândurilor: numai când pierderea de date și mecanismul lipsei sunt acceptabile; „rar și aleatoriu” trebuie verificat, nu presupus
- Imputarea prin medie sau mediană: simplă; mediana este mai robustă la valori extreme, însă ambele modifică distribuția și reduc artificial varianța
- Imputarea prin mod: pentru caracteristici categoriale, cu tratarea explicită a situațiilor fără valori observate
- Coloană-indicator: adăugați o coloană binară precum „această_valoare_lipsea” înainte de imputare; lipsa poate fi ea însăși informativă
- Propagare înainte sau înapoi: pentru serii temporale, respectând ordinea și informația disponibilă la momentul predicției; propagarea înapoi poate introduce informație din viitor
Strategia de imputare trebuie ajustată pe train în fiecare partiție de validare și aplicată ulterior fără a consulta distribuția setului de testare.
Interacțiunea caracteristicilor
Uneori relația se află în combinația dintre variabile. Înălțimea și greutatea separat pot fi mai puțin informative decât IMC = greutate / înălțime^2 pentru anumite obiective. Interacțiunile multiplică spațiul caracteristicilor, așa că folosiți cunoașterea domeniului, regularizarea și validarea pentru a le alege.
Selecția caracteristicilor
Mai multe caracteristici nu înseamnă întotdeauna performanță mai bună. Caracteristicile irelevante sau redundante pot crește costul, varianța și oportunitățile de supraînvățare.
Metode de filtrare, independente de estimator:
- Corelație: identifică perechi cu dependență liniară puternică; eliminarea uneia este o alegere dependentă de obiectiv și nu surprinde redundanța neliniară
- Informație mutuală: estimează câtă dependență există între o caracteristică și țintă; estimarea poate fi zgomotoasă și dependentă de discretizare sau de estimator
- Prag de varianță: elimină caracteristici aproape constante; rezultatul depinde de scară și nu folosește ținta
Metode embedded și wrapper:
- Regularizare L1, de exemplu Lasso pentru regresie sau logistică penalizată: metodă embedded care poate aduce ponderi exact la zero; nu identifică garantat „adevăratele” caracteristici și poate alege arbitrar între predictori corelați
- Eliminare recursivă a caracteristicilor: metodă wrapper care antrenează modelul, elimină caracteristicile cu importanță mică și repetă
De ce contează selecția: într-un regim cu puține observații și multe caracteristici zgomotoase, un model cu zece caracteristici utile poate generaliza mai bine decât același model cu încă 90 irelevante. Diferența depinde însă de estimator, regularizare, dimensiunea eșantionului și procedura de selecție. Selecția trebuie inclusă în fiecare fold de validare pentru a evita o estimare optimistă.
feature-scaling
Construiți
Pasul 1: transformări numerice de la zero
import math
def min_max_scale(values):
min_val = min(values)
max_val = max(values)
if max_val == min_val:
return [0.0] * len(values)
return [(v - min_val) / (max_val - min_val) for v in values]
def standardize(values):
n = len(values)
mean = sum(values) / n
variance = sum((v - mean) ** 2 for v in values) / n
std = math.sqrt(variance) if variance > 0 else 1.0
return [(v - mean) / std for v in values]
def log_transform(values):
return [math.log(v + 1) for v in values]
def bin_values(values, n_bins=5):
min_val = min(values)
max_val = max(values)
bin_width = (max_val - min_val) / n_bins
if bin_width == 0:
return [0] * len(values)
result = []
for v in values:
bin_idx = int((v - min_val) / bin_width)
bin_idx = min(bin_idx, n_bins - 1)
result.append(bin_idx)
return result
def polynomial_features(row, degree=2):
n = len(row)
result = list(row)
if degree >= 2:
for i in range(n):
result.append(row[i] ** 2)
for i in range(n):
for j in range(i + 1, n):
result.append(row[i] * row[j])
return result
Notă tehnică: Funcțiile de scalare și discretizare nu tratează listele vide și nu separă
fitdetransform.log_transformnecesită valori mai mari decât −1,bin_valuesnecesităn_bins > 0, iarpolynomial_featuresgenerează numai termeni până la gradul 2 chiar dacă primițidegree > 2. Folosiți statisticile învățate din train pentru datele ulterioare.
Pasul 2: codificare categorială de la zero
def one_hot_encode(values):
categories = sorted(set(values))
cat_to_idx = {cat: i for i, cat in enumerate(categories)}
n_cats = len(categories)
encoded = []
for v in values:
row = [0] * n_cats
row[cat_to_idx[v]] = 1
encoded.append(row)
return encoded, categories
def label_encode(values):
categories = sorted(set(values))
cat_to_int = {cat: i for i, cat in enumerate(categories)}
return [cat_to_int[v] for v in values], cat_to_int
def target_encode(feature_values, target_values, smoothing=10):
global_mean = sum(target_values) / len(target_values)
category_stats = {}
for feat, target in zip(feature_values, target_values):
if feat not in category_stats:
category_stats[feat] = {"sum": 0.0, "count": 0}
category_stats[feat]["sum"] += target
category_stats[feat]["count"] += 1
encoding = {}
for cat, stats in category_stats.items():
cat_mean = stats["sum"] / stats["count"]
weight = stats["count"] / (stats["count"] + smoothing)
encoding[cat] = weight * cat_mean + (1 - weight) * global_mean
return [encoding[v] for v in feature_values], encoding
Notă tehnică: Aceste funcții combină ajustarea și transformarea și nu definesc comportamentul pentru categorii noi. Mai important,
target_encodecodifică fiecare rând de antrenare cu o statistică ce include propria țintă; netezirea reduce, dar nu elimină scurgerea și supraînvățarea. Pentru train folosiți cross-fitting, iar pentru categorii noi folosiți o valoare de rezervă, de regulă media globală învățată.
Pasul 3: caracteristici textuale de la zero
def count_vectorize(documents):
vocab = {}
idx = 0
for doc in documents:
for word in doc.lower().split():
if word not in vocab:
vocab[word] = idx
idx += 1
vectors = []
for doc in documents:
vec = [0] * len(vocab)
for word in doc.lower().split():
vec[vocab[word]] += 1
vectors.append(vec)
return vectors, vocab
def tfidf(documents):
n_docs = len(documents)
vocab = {}
idx = 0
for doc in documents:
for word in doc.lower().split():
if word not in vocab:
vocab[word] = idx
idx += 1
doc_freq = {}
for doc in documents:
seen = set()
for word in doc.lower().split():
if word not in seen:
doc_freq[word] = doc_freq.get(word, 0) + 1
seen.add(word)
vectors = []
for doc in documents:
words = doc.lower().split()
word_count = len(words)
tf_map = {}
for word in words:
tf_map[word] = tf_map.get(word, 0) + 1
vec = [0.0] * len(vocab)
for word, count in tf_map.items():
tf = count / word_count
idf = math.log(n_docs / doc_freq[word])
vec[vocab[word]] = tf * idf
vectors.append(vec)
return vectors, vocab
Notă tehnică: Tokenizarea prin
lower().split()păstrează punctuația și este doar demonstrativă. Pentru un document gol,tfidfîntoarce un vector nul deoarece bucla interioară nu se execută; nu împarte la zero. Implementarea nu netezește IDF, nu adaugă constanta folosită de multe biblioteci și nu normalizează vectorii. Vocabularul și IDF se învață numai pe train; termenii necunoscuți din date noi trebuie ignorați sau tratați printr-o politică stabilită.
Pasul 4: imputarea valorilor lipsă de la zero
def impute_mean(values):
present = [v for v in values if v is not None]
if not present:
return [0.0] * len(values), 0.0
mean = sum(present) / len(present)
return [v if v is not None else mean for v in values], mean
def impute_median(values):
present = sorted(v for v in values if v is not None)
if not present:
return [0.0] * len(values), 0.0
n = len(present)
if n % 2 == 0:
median = (present[n // 2 - 1] + present[n // 2]) / 2
else:
median = present[n // 2]
return [v if v is not None else median for v in values], median
def impute_mode(values):
present = [v for v in values if v is not None]
if not present:
return values, None
counts = {}
for v in present:
counts[v] = counts.get(v, 0) + 1
mode = max(counts, key=counts.get)
return [v if v is not None else mode for v in values], mode
def add_missing_indicator(values):
return [0 if v is not None else 1 for v in values]
Funcțiile întorc atât valorile imputate, cât și statistica de completare, pentru ca aceasta să poată fi reutilizată. Totuși, interfața nu oferă o funcție transform separată, iar ramura fără nicio valoare observată alege convenții diferite: zero pentru numeric și valori None neschimbate pentru mod. Într-un pipeline de producție definiți explicit politica și învățați-o din train.
Pasul 5: selecția caracteristicilor de la zero
def correlation(x, y):
n = len(x)
mean_x = sum(x) / n
mean_y = sum(y) / n
cov = sum((xi - mean_x) * (yi - mean_y) for xi, yi in zip(x, y)) / n
std_x = math.sqrt(sum((xi - mean_x) ** 2 for xi in x) / n)
std_y = math.sqrt(sum((yi - mean_y) ** 2 for yi in y) / n)
if std_x == 0 or std_y == 0:
return 0.0
return cov / (std_x * std_y)
def mutual_information(feature, target, n_bins=10):
feat_min = min(feature)
feat_max = max(feature)
bin_width = (feat_max - feat_min) / n_bins if feat_max != feat_min else 1.0
feat_binned = [
min(int((f - feat_min) / bin_width), n_bins - 1) for f in feature
]
n = len(feature)
target_classes = sorted(set(target))
feat_bins = sorted(set(feat_binned))
p_feat = {}
for b in feat_bins:
p_feat[b] = feat_binned.count(b) / n
p_target = {}
for t in target_classes:
p_target[t] = target.count(t) / n
mi = 0.0
for b in feat_bins:
for t in target_classes:
joint_count = sum(
1 for fb, tv in zip(feat_binned, target) if fb == b and tv == t
)
p_joint = joint_count / n
if p_joint > 0:
mi += p_joint * math.log(p_joint / (p_feat[b] * p_target[t]))
return mi
def variance_threshold(features, threshold=0.01):
n_features = len(features[0])
n_samples = len(features)
selected = []
for j in range(n_features):
col = [features[i][j] for i in range(n_samples)]
mean = sum(col) / n_samples
var = sum((v - mean) ** 2 for v in col) / n_samples
if var >= threshold:
selected.append(j)
return selected
def remove_correlated(features, threshold=0.9):
n_features = len(features[0])
n_samples = len(features)
to_remove = set()
for i in range(n_features):
if i in to_remove:
continue
col_i = [features[r][i] for r in range(n_samples)]
for j in range(i + 1, n_features):
if j in to_remove:
continue
col_j = [features[r][j] for r in range(n_samples)]
corr = abs(correlation(col_i, col_j))
if corr >= threshold:
to_remove.add(j)
return [i for i in range(n_features) if i not in to_remove]
Notă tehnică: Codul presupune intrări nevide, rectangulare și cu lungimi compatibile. Estimarea informației mutuale este una empirică după discretizare egală și poate fi puternic influențată de
n_binsși de eșantioane mici.remove_correlatedeste greedy și dependentă de ordinea coloanelor: elimină automat coloana cu indice mai mare fără să compare utilitatea pentru țintă.
Pasul 6: pipeline complet și demonstrație
import random
def make_housing_data(n=200, seed=42):
random.seed(seed)
data = []
for _ in range(n):
sqft = random.uniform(500, 5000)
bedrooms = random.choice([1, 2, 3, 4, 5])
age = random.uniform(0, 50)
neighborhood = random.choice(["downtown", "suburbs", "rural"])
has_pool = random.choice([True, False])
sqft_with_missing = sqft if random.random() > 0.05 else None
age_with_missing = age if random.random() > 0.08 else None
price = (
50 * sqft
+ 20000 * bedrooms
- 1000 * age
+ (50000 if neighborhood == "downtown" else 10000 if neighborhood == "suburbs" else 0)
+ (15000 if has_pool else 0)
+ random.gauss(0, 20000)
)
data.append({
"sqft": sqft_with_missing,
"bedrooms": bedrooms,
"age": age_with_missing,
"neighborhood": neighborhood,
"has_pool": has_pool,
"price": price,
})
return data
if __name__ == "__main__":
data = make_housing_data(200)
print("=== Raw Data Sample ===")
for row in data[:3]:
print(f" {row}")
sqft_raw = [d["sqft"] for d in data]
age_raw = [d["age"] for d in data]
prices = [d["price"] for d in data]
print("\n=== Missing Value Handling ===")
sqft_missing = sum(1 for v in sqft_raw if v is None)
age_missing = sum(1 for v in age_raw if v is None)
print(f" sqft missing: {sqft_missing}/{len(sqft_raw)}")
print(f" age missing: {age_missing}/{len(age_raw)}")
sqft_indicator = add_missing_indicator(sqft_raw)
age_indicator = add_missing_indicator(age_raw)
sqft_imputed, sqft_fill = impute_median(sqft_raw)
age_imputed, age_fill = impute_mean(age_raw)
print(f" sqft filled with median: {sqft_fill:.0f}")
print(f" age filled with mean: {age_fill:.1f}")
print("\n=== Numerical Transforms ===")
sqft_scaled = standardize(sqft_imputed)
age_scaled = min_max_scale(age_imputed)
sqft_log = log_transform(sqft_imputed)
age_binned = bin_values(age_imputed, n_bins=5)
print(f" sqft standardized: mean={sum(sqft_scaled)/len(sqft_scaled):.4f}, std={math.sqrt(sum(v**2 for v in sqft_scaled)/len(sqft_scaled)):.4f}")
print(f" age min-max: [{min(age_scaled):.2f}, {max(age_scaled):.2f}]")
print(f" age bins: {sorted(set(age_binned))}")
print("\n=== Categorical Encoding ===")
neighborhoods = [d["neighborhood"] for d in data]
ohe, ohe_cats = one_hot_encode(neighborhoods)
print(f" One-hot categories: {ohe_cats}")
print(f" Sample encoding: {neighborhoods[0]} -> {ohe[0]}")
le, le_map = label_encode(neighborhoods)
print(f" Label encoding map: {le_map}")
te, te_map = target_encode(neighborhoods, prices, smoothing=10)
print(f" Target encoding: {({k: round(v) for k, v in te_map.items()})}")
print("\n=== Text Features ===")
descriptions = [
"large modern house with pool",
"small cozy cottage near downtown",
"spacious family home with large yard",
"modern apartment downtown with view",
"rustic cabin in rural area",
]
cv, cv_vocab = count_vectorize(descriptions)
print(f" Vocabulary size: {len(cv_vocab)}")
print(f" Doc 0 non-zero features: {sum(1 for v in cv[0] if v > 0)}")
tf, tf_vocab = tfidf(descriptions)
print(f" TF-IDF vocabulary size: {len(tf_vocab)}")
top_words = sorted(tf_vocab.keys(), key=lambda w: tf[0][tf_vocab[w]], reverse=True)[:3]
print(f" Doc 0 top TF-IDF words: {top_words}")
print("\n=== Polynomial Features ===")
sample_row = [sqft_scaled[0], age_scaled[0]]
poly = polynomial_features(sample_row, degree=2)
print(f" Input: {[round(v, 4) for v in sample_row]}")
print(f" Polynomial: {[round(v, 4) for v in poly]}")
print(f" Features: [x1, x2, x1^2, x2^2, x1*x2]")
print("\n=== Feature Selection ===")
feature_matrix = [
[sqft_scaled[i], age_scaled[i], float(sqft_indicator[i]), float(age_indicator[i])]
+ ohe[i]
for i in range(len(data))
]
print(f" Total features: {len(feature_matrix[0])}")
surviving_var = variance_threshold(feature_matrix, threshold=0.01)
print(f" After variance threshold (0.01): {len(surviving_var)} features kept")
surviving_corr = remove_correlated(feature_matrix, threshold=0.9)
print(f" After correlation filter (0.9): {len(surviving_corr)} features kept")
binary_prices = [1 if p > sum(prices) / len(prices) else 0 for p in prices]
print("\n Mutual information with target:")
feature_names = ["sqft", "age", "sqft_missing", "age_missing"] + [f"neigh_{c}" for c in ohe_cats]
for j in range(len(feature_matrix[0])):
col = [feature_matrix[i][j] for i in range(len(feature_matrix))]
mi = mutual_information(col, binary_prices, n_bins=10)
print(f" {feature_names[j]}: MI={mi:.4f}")
print("\n Correlation with price:")
for j in range(len(feature_matrix[0])):
col = [feature_matrix[i][j] for i in range(len(feature_matrix))]
corr = correlation(col, prices)
print(f" {feature_names[j]}: r={corr:.4f}")
Notă tehnică: Demonstrația este descriptivă și procesează întregul set simultan. Nu o folosiți ca șablon de evaluare: împărțiți mai întâi datele, apoi ajustați imputarea, scalarea, categoriile, target encoding și selecția numai în train sau în foldurile interne. În plus, calcularea
binary_pricesfolosește media țintei pe toate datele și ar produce scurgere dacă ar intra într-o evaluare predictivă.
Folosiți
În scikit-learn, aceste transformări pot fi compuse în pipeline-uri:
from sklearn.preprocessing import StandardScaler, OneHotEncoder, PolynomialFeatures
from sklearn.impute import SimpleImputer
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.feature_selection import mutual_info_classif, VarianceThreshold
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
numeric_pipe = Pipeline([
("imputer", SimpleImputer(strategy="median")),
("scaler", StandardScaler()),
])
categorical_pipe = Pipeline([
("encoder", OneHotEncoder(sparse_output=False)),
])
preprocessor = ColumnTransformer([
("num", numeric_pipe, ["sqft", "age"]),
("cat", categorical_pipe, ["neighborhood"]),
])
Versiunile de la zero arată mecanica de bază a transformărilor. Implementările din bibliotecă adaugă tratarea cazurilor-limită, matrici rare, separarea fit/transform, compunerea pipeline-urilor și opțiuni suplimentare. Formulele nu sunt întotdeauna identice: de exemplu, TfidfVectorizer folosește implicit IDF netezit, un termen constant și normalizare L2.
Notă tehnică:
OneHotEncoder(sparse_output=False)produce o matrice densă, potențial costisitoare la cardinalitate mare, și implicit ridică eroare pentru categorii necunoscute. Pentru producție luați în calcul ieșirea rară și o politică precumhandle_unknown="ignore"sau gruparea categoriilor rare. Pipeline-ul prezentat este doar preprocesorul; pentru validare fără scurgere, includeți și estimatorul final în acelașiPipeline.
Livrați
Această lecție produce:
outputs/prompt-feature-engineer.md— un prompt pentru proiectarea sistematică a caracteristicilor din date brute
Exerciții
- Adăugați scalarea robustă, folosind mediana și intervalul intercuartilic în locul mediei și abaterii standard. Comparați-o cu standardizarea pe date cu valori extreme, ajustând ambele transformări numai pe train.
- Implementați target encoding leave-one-out: pentru fiecare rând de antrenare, calculați media țintei fără propria valoare. Arătați cum reduce supraînvățarea față de codificarea naivă. Comparați și cu cross-fitting pe mai multe folduri, care separă complet rândurile ce produc statistica de cele transformate.
- Construiți un pipeline automat de selecție care combină pragul de varianță, filtrarea corelațiilor și clasarea prin informație mutuală. Aplicați-l setului de locuințe și comparați regresia liniară cu toate caracteristicile față de cele selectate. Includeți întregul preprocessing și selecția în fiecare fold de validare, fără a consulta testul.
Termeni-cheie
| Termen | Cum i se spune | Ce înseamnă de fapt |
|---|---|---|
| Ingineria caracteristicilor | „Crearea unor coloane noi” | Transformarea datelor brute în reprezentări care expun modelului tipare relevante |
| Standardizare | „Aducerea la normal” | Scăderea mediei de train și împărțirea la abaterea standard de train; valorile de train au media 0 și varianță unitară dacă abaterea este nenulă și se folosește convenția corespunzătoare |
| Codificare one-hot | „Crearea variabilelor dummy” | Câte o coloană binară pentru fiecare categorie cunoscută; un rând are de regulă un 1, iar categoriile necunoscute urmează politica encoderului |
| Codificare prin țintă | „Folosirea răspunsului pentru codificare” | Înlocuirea categoriei cu o estimare netezită bazată pe țintă, calculată out-of-fold pentru datele de antrenare |
| TF-IDF | „Numărători sofisticate de cuvinte” | Frecvența termenului înmulțită cu frecvența inversă a documentelor, eventual cu netezire și normalizare |
| Imputare | „Completarea golurilor” | Înlocuirea valorilor lipsă prin statistici sau predicții învățate numai din datele de antrenare |
| Selecția caracteristicilor | „Eliminarea coloanelor slabe” | Alegerea unui subset după relevanță, redundanță, stabilitate și cost, evaluat fără scurgere |
| Informație mutuală | „Cât ne spune un lucru despre altul” | Măsură a dependenței dintre variabile, echivalentă cu reducerea așteptată a incertitudinii despre Y prin observarea lui X |
| Scurgerea datelor | „Trișare accidentală” | Folosirea la ajustare a unei informații care nu ar fi disponibilă la momentul predicției, ceea ce produce rezultate prea optimiste |
Lecturi suplimentare
- Feature Engineering and Selection (Max Kuhn & Kjell Johnson) — carte online gratuită despre ansamblul metodelor de inginerie a caracteristicilor
- scikit-learn Preprocessing Guide — referință practică pentru transformările standard
- Target Encoding Done Right (Micci-Barreca, 2001) — lucrarea originală despre codificarea prin țintă cu netezire
Sursă: Feature Engineering & Selection — original
Navigare: înapoi: 02.07 — Învățare nesupravegheată · Faza 2 — Bazele învățării automate · Catalog complet · în continuare: 02.09 — Evaluarea modelelor.