← Назад к вопросам

Как работает machine learning под капотом?

2.0 Middle🔥 141 комментариев
#Python Core#Soft Skills

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как работает Machine Learning под капотом

Machine Learning (МЛ) — это дисциплина, которая позволяет компьютерам учиться на примерах без явного программирования каждого правила. Вот как это происходит.

1. Основной цикл обучения

Все МЛ алгоритмы следуют одной схеме:

# 1. Подготовка данных
X_train = [[2, 3], [4, 5], [6, 7], ...]  # Признаки (features)
y_train = [1, 1, 0, ...]                  # Ответы (labels)

# 2. Инициализация модели со случайными весами
weights = [0.1, 0.2]  # w1, w2
bias = 0.5             # b

# 3. Цикл обучения (эпохи)
for epoch in range(100):
    # Прямой проход (Forward pass)
    predictions = []
    for x in X_train:
        y_pred = x[0] * weights[0] + x[1] * weights[1] + bias
        predictions.append(y_pred)
    
    # Вычисляем ошибку (Loss function)
    loss = sum((y_true - y_pred) ** 2 for y_true, y_pred in zip(y_train, predictions)) / len(y_train)
    
    # Градиентный спуск (Backward pass)
    # Вычисляем производные (градиенты)
    dw1 = sum(2 * (y_pred - y_true) * x[0] for x, y_true, y_pred in zip(X_train, y_train, predictions)) / len(y_train)
    dw2 = sum(2 * (y_pred - y_true) * x[1] for x, y_true, y_pred in zip(X_train, y_train, predictions)) / len(y_train)
    db = sum(2 * (y_pred - y_true) for y_true, y_pred in zip(y_train, predictions)) / len(y_train)
    
    # Обновляем веса (Update weights)
    learning_rate = 0.01
    weights[0] -= learning_rate * dw1
    weights[1] -= learning_rate * dw2
    bias -= learning_rate * db
    
    if epoch % 10 == 0:
        print(f"Epoch {epoch}, Loss: {loss}")

# 4. Предсказание на новых данных
new_x = [3, 4]
prediction = new_x[0] * weights[0] + new_x[1] * weights[1] + bias

2. Градиентный спуск визуально

Oшибка (Loss) по осям весов:

        Loss
         /\
        /  \
       /    \  <- Локальный минимум
      /      \
  ___/________\___
      w1, w2

На каждой итерации делаем маленький шаг вниз (вдоль градиента).
Цель: достичь локального минимума где Loss наименьшее.
# Пример: функция потерь и градиентный спуск
import numpy as np
import matplotlib.pyplot as plt

def loss_function(w):
    """Квадратичная функция потерь"""
    return (w - 5) ** 2

def gradient(w):
    """Производная"""
    return 2 * (w - 5)

# Начинаем с w=0
w = 0
learning_rate = 0.1
history = [w]

for _ in range(50):
    grad = gradient(w)
    w = w - learning_rate * grad  # Шаг в направлении минимума
    history.append(w)
    print(f"w={w:.3f}, loss={loss_function(w):.3f}")

# После 50 итераций w ≈ 5 (минимум функции)
print(f"\nНайденное значение: w={w:.3f}")
print(f"Истинный минимум: w=5")

Вывод:

w=0.400, loss=21.160
w=0.720, loss=18.506
w=0.976, loss=16.105
...
w=4.999, loss=0.000

3. Нейронная сеть (Neural Network)

Это граф из нейронов и слоев:

# Простая нейронная сеть (2 входа -> 1 выход)

import numpy as np

class SimpleNeuralNetwork:
    def __init__(self, input_size, hidden_size, output_size):
        # Инициализируем веса случайно
        self.W1 = np.random.randn(input_size, hidden_size) * 0.01
        self.b1 = np.zeros((1, hidden_size))
        self.W2 = np.random.randn(hidden_size, output_size) * 0.01
        self.b2 = np.zeros((1, output_size))
    
    def relu(self, x):
        """Функция активации ReLU"""
        return np.maximum(0, x)
    
    def relu_derivative(self, x):
        return (x > 0).astype(float)
    
    def sigmoid(self, x):
        """Функция активации Sigmoid"""
        return 1 / (1 + np.exp(-np.clip(x, -500, 500)))
    
    def forward(self, X):
        """Прямой проход (Forward pass)"""
        # Скрытый слой
        self.z1 = np.dot(X, self.W1) + self.b1
        self.a1 = self.relu(self.z1)  # Активация
        
        # Выходной слой
        self.z2 = np.dot(self.a1, self.W2) + self.b2
        self.a2 = self.sigmoid(self.z2)  # Вероятность
        
        return self.a2
    
    def backward(self, X, y, learning_rate=0.01):
        """Обратный проход (Backward pass) - backpropagation"""
        m = X.shape[0]
        
        # Ошибка на выходе
        dz2 = self.a2 - y
        dW2 = np.dot(self.a1.T, dz2) / m
        db2 = np.sum(dz2, axis=0, keepdims=True) / m
        
        # Ошибка на скрытом слое
        da1 = np.dot(dz2, self.W2.T)
        dz1 = da1 * self.relu_derivative(self.z1)
        dW1 = np.dot(X.T, dz1) / m
        db1 = np.sum(dz1, axis=0, keepdims=True) / m
        
        # Обновляем веса
        self.W1 -= learning_rate * dW1
        self.b1 -= learning_rate * db1
        self.W2 -= learning_rate * dW2
        self.b2 -= learning_rate * db2
    
    def train(self, X, y, epochs=100, learning_rate=0.01):
        for epoch in range(epochs):
            # Прямой проход
            output = self.forward(X)
            
            # Вычисляем ошибку
            loss = -np.mean(y * np.log(output + 1e-8) + (1-y) * np.log(1-output + 1e-8))
            
            # Обратный проход
            self.backward(X, y, learning_rate)
            
            if epoch % 10 == 0:
                print(f"Epoch {epoch}, Loss: {loss:.4f}")
    
    def predict(self, X):
        return (self.forward(X) > 0.5).astype(int)

# Использование
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([[0], [1], [1], [0]])  # XOR задача

model = SimpleNeuralNetwork(input_size=2, hidden_size=4, output_size=1)
model.train(X, y, epochs=100)

print("\nПредсказания:")
for x, y_true in zip(X, y):
    pred = model.predict(x.reshape(1, -1))[0, 0]
    print(f"Input: {x}, True: {y_true[0]}, Predicted: {pred}")

4. Основные концепции

Overfitting vs Underfitting

# Overfitting: модель слишком сложная, запомнила всё
# Train loss = 0.01, Test loss = 0.80

# Underfitting: модель слишком простая
# Train loss = 0.50, Test loss = 0.50

# Golden sweet spot:
# Train loss = 0.10, Test loss = 0.12

# Регуляризация (L2, L1) помогает избежать overfitting:
class RegularizedNN:
    def compute_loss(self, output, y, lambda_reg=0.01):
        # Loss + регуляризация
        ce_loss = -np.mean(y * np.log(output))
        l2_loss = lambda_reg * np.sum(self.W1**2 + self.W2**2)
        return ce_loss + l2_loss

Батчи (Batch Processing)

# Вместо обновления весов на всех примерах сразу,
# обновляем на батче (подмножество)

def train_with_batches(X, y, batch_size=32, epochs=10):
    for epoch in range(epochs):
        # Перемешиваем данные
        indices = np.random.permutation(len(X))
        X_shuffled = X[indices]
        y_shuffled = y[indices]
        
        # Итерируем по батчам
        for i in range(0, len(X), batch_size):
            X_batch = X_shuffled[i:i+batch_size]
            y_batch = y_shuffled[i:i+batch_size]
            
            # Обучаем на батче
            model.forward(X_batch)
            model.backward(X_batch, y_batch)

5. Популярные алгоритмы

Linear Regression (Линейная регрессия)

from sklearn.linear_model import LinearRegression

# Предсказываем непрерывное значение
X = [[1], [2], [3], [4], [5]]
y = [2, 4, 5, 4, 6]

model = LinearRegression()
model.fit(X, y)
prediction = model.predict([[6]])  # [7.2]

Decision Trees (Деревья решений)

from sklearn.tree import DecisionTreeClassifier

# Классификация с помощью дерева
X = [[0, 0], [1, 1], [0, 1], [1, 0]]
y = [0, 1, 1, 0]

model = DecisionTreeClassifier()
model.fit(X, y)
prediction = model.predict([[0.5, 0.5]])  # [1]

K-Means (Кластеризация)

from sklearn.cluster import KMeans

# Группируем данные в K групп
X = [[1, 1], [2, 2], [10, 10], [11, 11]]

model = KMeans(n_clusters=2)
model.fit(X)
print(model.labels_)  # [0, 0, 1, 1]

6. Практический пример: классификатор цифр

from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neural_network import MLPClassifier
from sklearn.metrics import accuracy_score, confusion_matrix

# Загружаем данные (изображения цифр 0-9)
digits = load_digits()
X, y = digits.data, digits.target

# Нормализуем данные
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# Разделяем на train и test
X_train, X_test, y_train, y_test = train_test_split(
    X_scaled, y, test_size=0.2, random_state=42
)

# Обучаем нейронную сеть
model = MLPClassifier(
    hidden_layer_sizes=(128, 64, 32),  # 3 скрытых слоя
    learning_rate_init=0.001,
    batch_size=32,
    max_iter=500,
    random_state=42
)

model.fit(X_train, y_train)

# Оцениваем
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"Точность: {accuracy:.2%}")  # ~97%

print(confusion_matrix(y_test, y_pred))

7. Типичные ошибки в МЛ

# Ошибка 1: Не нормализировать данные
# Плохо
model.fit(X, y)  # X может быть в разных масштабах

# Хорошо
from sklearn.preprocessing import StandardScaler
X_scaled = StandardScaler().fit_transform(X)
model.fit(X_scaled, y)

# Ошибка 2: Использовать одни же данные для train и test
# Плохо
model.fit(X, y)
accuracy = model.score(X, y)  # Переобучение!

# Хорошо
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
model.fit(X_train, y_train)
accuracy = model.score(X_test, y_test)

# Ошибка 3: Неправильный выбор метрики
# Плохо: использовать accuracy для imbalanced dataset
y = [0, 0, 0, 0, 0, 0, 0, 0, 0, 1]  # 90% класс 0

# Хорошо: использовать F1-score
from sklearn.metrics import f1_score
f1 = f1_score(y_test, y_pred)

Заключение

Machine Learning под капотом — это:

  1. Функция потерь — мера того, насколько модель ошибается
  2. Градиентный спуск — алгоритм нахождения оптимальных весов
  3. Нейронные сети — универсальные аппроксиматоры функций
  4. Backpropagation — эффективный способ вычисления градиентов
  5. Регуляризация — предотвращение переобучения
  6. Батчи и эпохи — стратегия обучения

Это как обучение человека: делаем ошибку, учимся на ошибке, улучшаемся!