18+
реклама
18+
Бургер менюБургер меню

Александр Иванов – Python в аудио-спецэффектах. Как работают нейросети изнутри. (страница 5)

18

error = neuron.train_step(x, y_true, learning_rate=0.5)

total_error += abs(error)

if epoch % 50 == 0 or epoch < 10:

x_example, y_example = train_data[0]

y_pred_example = neuron.forward(x_example)

print(f"{epoch:5d} | {neuron.weight:+.3f} | {neuron.bias:+.3f} | "

f"{x_example:.2f} | {y_pred_example:.4f} | {y_example:.1f} | "

f"{abs(y_pred_example - y_example):.4f}")

# Проверяем обученный нейрон

print("\nРезультаты после обучения:")

test_data = [0.12, 0.30, 0.65, 0.90, 0.20, 0.85]

for x in test_data:

pred = neuron.forward(x)

label = "ГРОМКИЙ" if pred > 0.5 else "ТИХИЙ"

print(f" Громкость: {x:.2f} -> {pred:.4f} ({label})")

Запустите этот код. Вы увидите, как в начале обучения нейрон выдаёт случайные предсказания — примерно 0.5 для любого входа. Постепенно вес и смещение подстраиваются. К концу обучения нейрон уверенно отличает тихие звуки от громких: для значений около 0.2 предсказание близко к нулю, для значений около 0.8 — близко к единице.

Разберём ключевые моменты. Сигмоидная функция активации превращает любое число в значение между нулём и единицей. Большое положительное число на входе даёт значение, близкое к единице. Большое отрицательное — близкое к нулю. Это удобно для бинарной классификации: мы интерпретируем выход как вероятность того, что вход принадлежит классу «1».

Обучение происходит через корректировку веса и смещения. Мы вычисляем ошибку — разницу между предсказанием и правильным ответом. Затем вычисляем градиенты — насколько нужно изменить параметры, чтобы ошибка уменьшилась. И делаем маленький шаг в направлении, противоположном градиенту. Это называется градиентным спуском.

От нейрона к сети

Один нейрон может решать только очень простые задачи — разделять данные прямой линией. В реальном мире границы между классами гораздо сложнее. Граница между речью и музыкой, между эмоцией радости и эмоцией грусти, между чистым голосом и зашумлённым — всё это нелинейные, запутанные границы, которые один нейрон не может описать.

Решение — объединить нейроны в сеть. В полносвязной сети нейроны организованы в слои. Каждый нейрон первого слоя получает входные данные. Каждый нейрон второго слоя получает выходы всех нейронов первого слоя. Каждый нейрон третьего — выходы второго. И так далее. Между слоями применяются нелинейные функции активации, и именно эти нелинейности позволяют сети описывать сложные границы.

Обучение сети происходит так же, как обучение одного нейрона, но с одним усложнением: ошибку нужно распространить от выходного слоя к входному через все промежуточные слои. Это называется обратным распространением ошибки. Мы вычисляем, как ошибка на выходе зависит от каждого веса в сети, и обновляем все веса одновременно в направлении уменьшения ошибки.

Классификация звуков: речь, музыка, шум

Давайте применим полносвязную сеть к реальной задаче классификации звуков. Мы научим сеть отличать речь от музыки и шума по набору признаков, извлечённых из аудиофрагментов. Для этого мы воспользуемся PyTorch — фреймворком, который берёт на себя вычисление градиентов и обновление параметров.

python

import torch

import torch.nn as nn

import torch.optim as optim

import librosa

import numpy as np

def extract_features(y, sr, frame_size=2048, hop_length=512):

"""

Извлекает признаки из аудиосигнала для подачи в нейросеть.

Возвращает список векторов признаков для каждого фрейма.

"""

features = []

# Разбиваем на фреймы

num_frames = (len(y) - frame_size) // hop_length + 1

for i in range(num_frames):

start = i * hop_length

frame = y[start:start + frame_size]

# Признак 1: RMS энергия (средняя громкость)

rms = np.sqrt(np.mean(frame ** 2))

# Признак 2: Zero-crossing rate (частота пересечений нуля)

zcr = np.sum(np.abs(np.diff(np.sign(frame)))) / (2 * len(frame))

# Признак 3: Спектральный центроид (центр тяжести спектра)

spectrum = np.abs(np.fft.fft(frame * np.hamming(len(frame))))

freqs = np.fft.fftfreq(len(frame), 1 / sr)

positive_freqs = freqs[:len(freqs)//2]

positive_spectrum = spectrum[:len(spectrum)//2]

if np.sum(positive_spectrum) > 0:

centroid = np.sum(positive_freqs * positive_spectrum) / np.sum(positive_spectrum)

else:

centroid = 0

# Признак 4: Спектральный спад (частота, ниже которой 85% энергии)

cumsum = np.cumsum(positive_spectrum ** 2)

if cumsum[-1] > 0:

rolloff_idx = np.where(cumsum >= 0.85 * cumsum[-1])[0][0]

rolloff = positive_freqs[rolloff_idx]

else:

rolloff = 0

features.append([rms, zcr, centroid / 1000, rolloff / 1000])

return np.array(features, dtype=np.float32)

class SoundClassifier(nn.Module):

"""