From theory to production โ 10 lessons covering algorithms, neural networks, transformers, and real-world deployment.
Artificial Intelligence is the broadest field โ any technique that enables machines to mimic human intelligence. Machine Learning is a subset of AI where systems learn from data without being explicitly programmed. Deep Learning is a subset of ML using neural networks with many layers.
applications = {
"Classification": ["Spam detection", "Medical diagnosis", "Sentiment analysis"],
"Regression": ["House pricing", "Stock prediction", "Demand forecasting"],
"Clustering": ["Customer segmentation", "Anomaly detection", "Document grouping"],
"Generation": ["Image synthesis", "Text generation", "Music composition"],
"RL": ["Game AI", "Robotics", "Autonomous driving"]
}
for task, examples in applications.items():
print(f"{task}: {', '.join(examples)}")import numpy as np
a = np.array([1, 2, 3, 4, 5])
print(a.mean(), a.std(), a.max())
X = np.random.randn(100, 5)
y = X @ np.array([1, 2, 3, 4, 5]) + np.random.randn(100) * 0.1
X_centered = X - X.mean(axis=0)import pandas as pd
df = pd.read_csv("housing.csv")
print(df.head())
print(df.describe())
df.dropna(inplace=True)
df["price_per_sqft"] = df["price"] / df["sqft"]
df.groupby("neighborhood")["price"].agg(["mean", "median", "count"])import matplotlib.pyplot as plt
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
axes[0].scatter(df["sqft"], df["price"], alpha=0.3, s=10)
axes[0].set_xlabel("Square Feet")
axes[0].set_ylabel("Price")
axes[1].hist(df["price"], bins=50, edgecolor="black")
plt.tight_layout()
plt.savefig("eda_plots.png", dpi=150)
plt.show()from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score
import numpy as np
X = np.random.randn(1000, 3)
y = X @ [1.5, -2.0, 3.0] + np.random.randn(1000) * 0.5
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
model = LinearRegression()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
print(f"RMSE: {np.sqrt(mean_squared_error(y_test, y_pred)):.4f}")
print(f"R2: {r2_score(y_test, y_pred):.4f}")Pipeline to chain transforms and models to prevent data leakage.Prediction: ลท = wโxโ + wโxโ + ... + wโxโ + b = wยทX + b
Goal: find weights w and bias b that minimize the error between predictions and actual values.
import numpy as np
def compute_cost(X, y, w, b):
m = len(y)
predictions = X @ w + b
cost = (1 / (2 * m)) * np.sum((predictions - y) ** 2)
return costdef gradient_descent(X, y, w, b, learning_rate, epochs):
m = len(y)
cost_history = []
for epoch in range(epochs):
predictions = X @ w + b
dw = (1 / m) * (X.T @ (predictions - y))
db = (1 / m) * np.sum(predictions - y)
w -= learning_rate * dw
b -= learning_rate * db
cost = compute_cost(X, y, w, b)
cost_history.append(cost)
if epoch % 100 == 0:
print(f"Epoch {epoch}: Cost = {cost:.6f}")
return w, b, cost_history
np.random.seed(42)
X = 2 * np.random.randn(100, 1)
y = 4 + 3 * X.squeeze() + np.random.randn(100) * 0.5
w = np.zeros(X.shape[1])
b = 0
w, b, costs = gradient_descent(X, y, w, b, learning_rate=0.1, epochs=1000)
print(f"Learned: w = {w[0]:.4f}, b = {b:.4f}")from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
y_true = np.array([3, -0.5, 2, 7])
y_pred = np.array([2.5, 0.0, 2, 8])
mae = mean_absolute_error(y_true, y_pred)
mse = mean_squared_error(y_true, y_pred)
rmse = np.sqrt(mse)
r2 = r2_score(y_true, y_pred)
print(f"MAE: {mae:.4f}")
print(f"MSE: {mse:.4f}")
print(f"RMSE: {rmse:.4f}")
print(f"R2: {r2:.4f}")Despite the name, it's a classification algorithm. Uses sigmoid function to map predictions to probabilities:
import numpy as np
def sigmoid(z):
return 1 / (1 + np.exp(-np.clip(z, -500, 500)))
z = np.linspace(-10, 10, 100)
probs = sigmoid(z)
def predict(X, w, b, threshold=0.5):
return (sigmoid(X @ w + b) >= threshold).astype(int)from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.metrics import classification_report, confusion_matrix
data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(data.data, data.target, test_size=0.2, random_state=42)
pipe = Pipeline([("scaler", StandardScaler()), ("model", LogisticRegression(max_iter=10000))])
pipe.fit(X_train, y_train)
y_pred = pipe.predict(X_test)
print(classification_report(y_test, y_pred))
print(confusion_matrix(y_test, y_pred))from sklearn.svm import SVC
svm_linear = SVC(kernel="linear", C=1.0)
svm_linear.fit(X_train, y_train)
print(f"Accuracy: {svm_linear.score(X_test, y_test):.4f}")
svm_rbf = SVC(kernel="rbf", C=1.0, gamma="scale")
svm_rbf.fit(X_train, y_train)
print(f"Accuracy: {svm_rbf.score(X_test, y_test):.4f}")from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
dt = DecisionTreeClassifier(max_depth=5, random_state=42)
dt.fit(X_train, y_train)
print(f"Tree Accuracy: {dt.score(X_test, y_test):.4f}")
rf = RandomForestClassifier(n_estimators=100, max_depth=10, random_state=42, n_jobs=-1)
rf.fit(X_train, y_train)
print(f"Forest Accuracy: {rf.score(X_test, y_test):.4f}")
importances = rf.feature_importances_
top_idx = np.argsort(importances)[::-1][:5]
for i in top_idx:
print(f" {data.feature_names[i]}: {importances[i]:.4f}")import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
X, y_true = make_blobs(n_samples=300, centers=4, cluster_std=0.60, random_state=42)
kmeans = KMeans(n_clusters=4, init="k-means++", n_init=10, random_state=42)
kmeans.fit(X)
labels = kmeans.labels_
centroids = kmeans.cluster_centers_
print(f"Inertia (SSE): {kmeans.inertia_:.2f}")
inertias = []
K_range = range(1, 11)
for k in K_range:
km = KMeans(n_clusters=k, random_state=42, n_init=10)
km.fit(X)
inertias.append(km.inertia_)
plt.plot(K_range, inertias, "bo-")
plt.xlabel("Number of Clusters (k)")
plt.ylabel("Inertia")
plt.title("Elbow Method")
plt.savefig("elbow.png")from sklearn.cluster import DBSCAN
from sklearn.preprocessing import StandardScaler
X_scaled = StandardScaler().fit_transform(X)
dbscan = DBSCAN(eps=0.3, min_samples=5)
dbscan.fit(X_scaled)
labels = dbscan.labels_
n_clusters = len(set(labels)) - (1 if -1 in labels else 0)
n_noise = list(labels).count(-1)
print(f"Clusters found: {n_clusters}")
print(f"Noise points: {n_noise}")from sklearn.cluster import AgglomerativeClustering
from scipy.cluster.hierarchy import dendrogram, linkage
agg = AgglomerativeClustering(n_clusters=4, linkage="ward")
labels = agg.fit_predict(X)
Z = linkage(X, method="ward")
plt.figure(figsize=(12, 5))
dendrogram(Z, truncate_mode="lastp", p=30)
plt.title("Hierarchical Clustering Dendrogram")
plt.savefig("dendrogram.png")from sklearn.metrics import silhouette_score, calinski_harabasz_score, davies_bouldin_score
sil = silhouette_score(X, labels)
ch = calinski_harabasz_score(X, labels)
db = davies_bouldin_score(X, labels)
print(f"Silhouette: {sil:.4f}")
print(f"Calinski-Harabasz: {ch:.1f}")
print(f"Davies-Bouldin: {db:.4f}")A single neuron computes: z = wโxโ + wโxโ + ... + wโxโ + b, then applies an activation function: a = ฯ(z).
import numpy as np
import matplotlib.pyplot as plt
z = np.linspace(-5, 5, 200)
def sigmoid(z): return 1 / (1 + np.exp(-z))
def relu(z): return np.maximum(0, z)
def tanh(z): return np.tanh(z)
def leaky_relu(z, alpha=0.01): return np.where(z > 0, z, alpha * z)
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
axes[0,0].plot(z, sigmoid(z)); axes[0,0].set_title("Sigmoid")
axes[0,1].plot(z, relu(z)); axes[0,1].set_title("ReLU")
axes[1,0].plot(z, tanh(z)); axes[1,0].set_title("Tanh")
axes[1,1].plot(z, leaky_relu(z)); axes[1,1].set_title("Leaky ReLU")
plt.savefig("activations.png")import numpy as np
def forward_pass(X, weights, biases):
z1 = X @ weights["W1"] + biases["b1"]
a1 = np.maximum(0, z1)
z2 = a1 @ weights["W2"] + biases["b2"]
a2 = 1 / (1 + np.exp(-z2))
return {"z1": z1, "a1": a1, "z2": z2, "a2": a2}
np.random.seed(42)
n_features, n_hidden, n_output = 10, 4, 1
weights = {"W1": np.random.randn(n_features, n_hidden) * np.sqrt(2/n_features), "W2": np.random.randn(n_hidden, n_output) * np.sqrt(2/n_hidden)}
biases = {"b1": np.zeros(n_hidden), "b2": np.zeros(n_output)}
X_dummy = np.random.randn(5, n_features)
output = forward_pass(X_dummy, weights, biases)
print(f"Predictions shape: {output['a2'].shape}")import torch
import torch.nn as nn
import torch.optim as optim
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
X, y = make_classification(n_samples=1000, n_features=20, n_informative=10, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
X_train_t = torch.FloatTensor(X_train)
y_train_t = torch.FloatTensor(y_train).unsqueeze(1)
X_test_t = torch.FloatTensor(X_test)
y_test_t = torch.FloatTensor(y_test).unsqueeze(1)
class NeuralNet(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(nn.Linear(20, 64), nn.ReLU(), nn.Dropout(0.3), nn.Linear(64, 32), nn.ReLU(), nn.Dropout(0.2), nn.Linear(32, 1), nn.Sigmoid())
def forward(self, x): return self.net(x)
model = NeuralNet()
criterion = nn.BCELoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
for epoch in range(100):
model.train()
optimizer.zero_grad()
output = model(X_train_t)
loss = criterion(output, y_train_t)
loss.backward()
optimizer.step()
if (epoch + 1) % 20 == 0:
model.eval()
with torch.no_grad():
test_output = model(X_test_t)
preds = (test_output > 0.5).float()
acc = (preds == y_test_t).float().mean()
print(f"Epoch {epoch+1}: Loss={loss:.4f}, Acc={acc:.4f}")โL/โw = โL/โa ยท โa/โz ยท โz/โw. PyTorch's loss.backward() computes all gradients automatically.import torch
import torch.nn as nn
conv = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, stride=1, padding=1)
x = torch.randn(1, 3, 224, 224)
out = conv(x)
print(f"Input: {x.shape}")
print(f"Output: {out.shape}")
pool = nn.MaxPool2d(kernel_size=2, stride=2)
out_pooled = pool(out)
print(f"Pooled: {out_pooled.shape}")class CNN(nn.Module):
def __init__(self, num_classes=10):
super().__init__()
self.features = nn.Sequential(
nn.Conv2d(3, 32, 3, padding=1), nn.BatchNorm2d(32), nn.ReLU(),
nn.Conv2d(32, 32, 3, padding=1), nn.BatchNorm2d(32), nn.ReLU(),
nn.MaxPool2d(2), nn.Dropout2d(0.25),
nn.Conv2d(32, 64, 3, padding=1), nn.BatchNorm2d(64), nn.ReLU(),
nn.Conv2d(64, 64, 3, padding=1), nn.BatchNorm2d(64), nn.ReLU(),
nn.MaxPool2d(2), nn.Dropout2d(0.25),
nn.Conv2d(64, 128, 3, padding=1), nn.BatchNorm2d(128), nn.ReLU(),
nn.MaxPool2d(2), nn.Dropout2d(0.25))
self.classifier = nn.Sequential(nn.Flatten(), nn.Linear(128 * 28 * 28, 256), nn.ReLU(), nn.Dropout(0.5), nn.Linear(256, num_classes))
def forward(self, x): return self.classifier(self.features(x))import torchvision.models as models
model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V2)
for param in model.parameters():
param.requires_grad = False
num_classes = 5
model.fc = nn.Sequential(nn.Linear(model.fc.in_features, 256), nn.ReLU(), nn.Dropout(0.3), nn.Linear(256, num_classes))
optimizer = torch.optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=0.001)import torch, torch.nn as nn, math
class SelfAttention(nn.Module):
def __init__(self, embed_dim, num_heads):
super().__init__()
self.num_heads = num_heads
self.head_dim = embed_dim // num_heads
self.scale = math.sqrt(self.head_dim)
self.qkv = nn.Linear(embed_dim, 3 * embed_dim)
self.out_proj = nn.Linear(embed_dim, embed_dim)
def forward(self, x):
B, T, C = x.shape
qkv = self.qkv(x).reshape(B, T, 3, self.num_heads, self.head_dim)
q, k, v = qkv.permute(2, 0, 3, 1, 4).unbind(0)
attn = (q @ k.transpose(-2, -1)) / self.scale
attn = torch.softmax(attn, dim=-1)
out = (attn @ v).transpose(1, 2).reshape(B, T, C)
return self.out_proj(out)
attn = SelfAttention(512, 8)
x = torch.randn(2, 10, 512)
print(attn(x).shape)def positional_encoding(seq_len, d_model):
pe = torch.zeros(seq_len, d_model)
position = torch.arange(0, seq_len).unsqueeze(1).float()
div_term = torch.exp(torch.arange(0, d_model, 2).float() * -(math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
return pe
pe = positional_encoding(100, 512)
print(pe.shape)from transformers import AutoTokenizer, AutoModelForSequenceClassification, TrainingArguments, Trainer
from datasets import load_dataset
dataset = load_dataset("imdb")
model_name = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=2)
def tokenize(batch):
return tokenizer(batch["text"], padding=True, truncation=True, max_length=512)
dataset = dataset.map(tokenize, batched=True)
args = TrainingArguments(output_dir="./results", num_train_epochs=3, per_device_train_batch_size=16, per_device_eval_batch_size=64, eval_strategy="epoch", learning_rate=2e-5, weight_decay=0.01, fp16=True)
trainer = Trainer(model=model, args=args, train_dataset=dataset["train"].shuffle(seed=42).select(range(5000)), eval_dataset=dataset["test"].shuffle(seed=42).select(range(1000)))
trainer.train()import joblib, torch
joblib.dump(model, "model.joblib")
loaded_model = joblib.load("model.joblib")
torch.save(model.state_dict(), "model.pt")
model = MyModel()
model.load_state_dict(torch.load("model.pt"))
model.eval()from fastapi import FastAPI
from pydantic import BaseModel
import joblib, numpy as np
app = FastAPI()
model = joblib.load("model.joblib")
class PredictionRequest(BaseModel):
features: list[float]
class PredictionResponse(BaseModel):
prediction: float
confidence: float | None = None
@app.post("/predict", response_model=PredictionResponse)
def predict(req: PredictionRequest):
X = np.array(req.features).reshape(1, -1)
pred = model.predict(X)[0]
proba = getattr(model, "predict_proba", lambda X: [[None]])(X)[0]
return PredictionResponse(prediction=float(pred), confidence=float(max(proba)) if proba[0] is not None else None)
@app.get("/health")
def health():
return {"status": "ok", "model": type(model).__name__}FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY model.joblib .
COPY api.py .
EXPOSE 8000
CMD ["uvicorn", "api:app", "--host", "0.0.0.0", "--port", "8000"]docker build -t ml-api .
docker run -p 8000:8000 ml-api
curl -X POST http://localhost:8000/predict -H "Content-Type: application/json" -d '{"features": [5.1, 3.5, 1.4, 0.2]}'import logging
from datetime import datetime
logging.basicConfig(filename="model_monitor.log", level=logging.INFO)
def log_prediction(features, prediction, latency_ms):
logging.info({"timestamp": datetime.utcnow().isoformat(), "features": features, "prediction": prediction, "latency_ms": latency_ms})
def detect_drift(reference_data, new_data, threshold=0.05):
from scipy.stats import ks_2samp
drift_detected = False
for col in reference_data.columns:
stat, p_value = ks_2samp(reference_data[col], new_data[col])
if p_value < threshold:
print(f"DRIFT: {col} (p={p_value:.4f})")
drift_detected = True
return drift_detectedimport pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline
from sklearn.model_selection import cross_val_score
df = pd.read_csv("spam.csv", encoding="latin-1")
df = df[["v1", "v2"]].rename(columns={"v1": "label", "v2": "text"})
df["label"] = df["label"].map({"ham": 0, "spam": 1})
pipeline = Pipeline([("tfidf", TfidfVectorizer(max_features=5000, ngram_range=(1, 2))), ("clf", MultinomialNB(alpha=0.1))])
scores = cross_val_score(pipeline, df["text"], df["label"], cv=5, scoring="f1")
print(f"F1: {scores.mean():.4f} +/- {scores.std():.4f}")
pipeline.fit(df["text"], df["label"])
tests = ["Congratulations! You won a free iPhone. Click here!", "Hey, are we still meeting for lunch tomorrow?", "URGENT: Your account has been compromised. Send password now."]
for text in tests:
pred = pipeline.predict([text])[0]
proba = pipeline.predict_proba([text])[0][1]
print(f"{'SPAM' if pred else 'HAM '}: {proba:.2%} | {text[:50]}")import pandas as pd, numpy as np
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_absolute_error, r2_score
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
df = pd.read_csv("housing.csv")
numeric = ["sqft", "bedrooms", "bathrooms", "age"]
categorical = ["neighborhood", "condition"]
X = df[numeric + categorical]
y = df["price"]
preprocessor = ColumnTransformer([("num", StandardScaler(), numeric), ("cat", OneHotEncoder(handle_unknown="ignore"), categorical)])
pipeline = Pipeline([("prep", preprocessor), ("model", GradientBoostingRegressor(n_estimators=200, max_depth=5, learning_rate=0.1, random_state=42))])
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
print(f"MAE: ${mean_absolute_error(y_test, y_pred):,.0f}")
print(f"R2: {r2_score(y_test, y_pred):.4f}")import torch, torch.nn as nn, torch.optim as optim, torchvision, torchvision.transforms as transforms
transform = transforms.Compose([transforms.RandomHorizontalFlip(), transforms.RandomCrop(32, padding=4), transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2470, 0.2435, 0.2616))])
trainset = torchvision.datasets.CIFAR10(root="./data", train=True, download=True, transform=transform)
testset = torchvision.datasets.CIFAR10(root="./data", train=False, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=128, shuffle=True, num_workers=2)
testloader = torch.utils.data.DataLoader(testset, batch_size=128)
model = torchvision.models.resnet18(weights=None)
model.conv1 = nn.Conv2d(3, 64, 3, 1, 1, bias=False)
model.maxpool = nn.Identity()
model.fc = nn.Linear(512, 10)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.AdamW(model.parameters(), lr=0.001, weight_decay=0.01)
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=50)
for epoch in range(50):
model.train()
for images, labels in trainloader:
images, labels = images.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
scheduler.step()
model.eval()
correct, total = 0, 0
with torch.no_grad():
for images, labels in testloader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
_, predicted = outputs.max(1)
total += labels.size(0)
correct += predicted.eq(labels).sum().item()
print(f"Epoch {epoch+1}: Accuracy = {100.*correct/total:.2f}%")from langchain_community.llms import Ollama
from langchain.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
llm = Ollama(model="llama3.2:1b")
template = """You are a helpful AI assistant. Answer clearly and concisely. If you don't know, say so. Context: {context} Question: {question} Answer:"""
prompt = ChatPromptTemplate.from_template(template)
chain = ({"context": RunnablePassthrough(), "question": RunnablePassthrough()} | prompt | llm | StrOutputParser())
print("Chatbot ready! Type 'quit' to exit.")
while True:
question = input("\nYou: ")
if question.lower() in ["quit", "exit", "q"]: break
response = chain.invoke({"context": "", "question": question})
print(f"Bot: {response}")