439.39K

Практическая_работа_3 (2)

1.

Практическая работа №3
Синтез нейросетевого стационарного линейного
фильтра
Дисциплина «Нейронные сети»
PyTorch • линейный слой • FIR-фильтр
Аль-Днинави Асра
Ассистент - ВШУКС и ВШВЭ
Эл. Почта: aldninavi_ah@spbstu.ru

2.

ПРАКТИЧЕСКАЯ РАБОТА №3
Цели и задачи работы
• Связать цифровую обработку сигналов с обучением нейронных сетей.
• Показать, что веса nn.Linear соответствуют коэффициентам FIR-фильтра.
• Обучить модель подавлять шум по известному чистому сигналу.
• Оценить MSE, изменение SNR и частотную характеристику.
Нейронные сети • Практическая работа №3
2

3.

ТЕОРИЯ
Постановка задачи
• Известен эталон s[n]. Наблюдаем сигнал x[n] = s[n] + v[n].
• Шум v[n] имеет нулевое среднее и стандартное отклонение σ.
• Модель получает M отсчётов и оценивает чистое значение s[n].
Зашумлённое окно
x[n], …, x[n−M+1]
→
Линейный слой
M входов, 1 выход
→
Восстановленный
сигнал ŝ[n]
Для обучения нужны пары «зашумлённое окно / чистый отсчёт».
Нейронные сети • Практическая работа №3
3

4.

ТЕОРИЯ
FIR-фильтр и линейная нейронная сеть
ŝ[n] = ∑ₖ₌₀ᴹ⁻¹ wₖ x[n − k]
ŷ = wᵀu + b
u = [x[n], x[n−1], …, x[n−M+1]]ᵀ
При bias=False: b = 0, веса сети совпадают с FIR-коэффициентами.
M означает число коэффициентов. Порядок FIR-фильтра равен M−1.
Нейронные сети • Практическая работа №3
4

5.

ТЕОРИЯ
Стационарность, смещение и обучение
Основной режим: обучение off-line, затем фиксация весов.
Фиксированные коэффициенты задают линейный стационарный FIR-фильтр.
bias=True добавляет постоянный член. Такая модель является аффинной.
Вариант 14: веса обновляются по одному отсчёту. Это адаптивный режим.
Наличие DC-компоненты само по себе не требует bias=True: для её передачи важна сумма весов.
Нейронные сети • Практическая работа №3
5

6.

ПРАКТИКА
Среда и общие параметры
import numpy as np
import torch
from torch import nn, optim
from torch.utils.data import DataLoader, TensorDataset
from scipy.signal import freqz
import matplotlib.pyplot as plt
np.random.seed(42)
torch.manual_seed(42)
N, M = 10000, 20
sigma, epochs, batch_size = 0.30, 60, 128
Все этапы выполняются по порядку в одном ноутбуке Google Colab.
Нейронные сети • Практическая работа №3
6

7.

ПРАКТИКА
Шаг 1. Генерация сигнала
n = np.arange(N)
s=(
np.sin(2 * np.pi * 0.01 * n)
+ 0.5 * np.sin(2 * np.pi * 0.03 * n)
)
v = sigma * np.random.randn(N)
x=s+v
# Частоты заданы в циклах / отсчёт
# Физическая частота F = f * Fs
s — чистый сигнал, v — шум, x — зашумлённый сигнал.
Нейронные сети • Практическая работа №3
7

8.

ПРАКТИКА
Шаг 1. График исходных сигналов
plt.figure(figsize=(12, 4))
plt.plot(n[:300], x[:300], color="gray",
alpha=0.65, label="Noisy signal")
plt.plot(n[:300], s[:300], color="blue",
linewidth=2, label="Clean signal")
plt.xlabel("Sample index")
plt.ylabel("Amplitude")
plt.title("Clean and noisy signals")
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()
Перед обучением проверьте форму эталона и уровень добавленного шума.
Нейронные сети • Практическая работа №3
8

9.

ПРАКТИКА
Чистый и зашумлённый сигналы
2,5
2
1,5
0,5
0
-0,5
0
5
10
15
20
25
30
35
40
45
50
55
60
65
70
75
80
85
90
95
100
105
110
115
120
125
130
135
140
145
150
155
160
165
170
175
180
185
190
195
200
205
210
215
220
225
230
235
240
245
250
255
260
265
270
275
280
285
290
295
Amplitude
1
-1
-1,5
-2
-2,5
Sample index
Clean signal
Noisy signal
Демонстрационный расчёт: M = 20, σ = 0.30, f₁ = 0.01, f₂ = 0.03.
Нейронные сети • Практическая работа №3
9

10.

ПРАКТИКА
Шаг 2. Скользящее окно
def make_windows(x_part, s_part, M):
X = np.lib.stride_tricks.sliding_window_view(
x_part, M
)[:, ::-1].copy()
y = s_part[M - 1:, None].copy()
return (
torch.tensor(X, dtype=torch.float32),
torch.tensor(y, dtype=torch.float32)
)
Каждое окно: [x[n], x[n−1], …, x[n−M+1]]. Цель: s[n].
Нейронные сети • Практическая работа №3
10

11.

ПРАКТИКА
Шаг 3. Разделение данных
split = int(0.7 * N)
Xtr, ytr = make_windows(x[:split], s[:split], M)
Xte, yte = make_windows(x[split:], s[split:], M)
loader = DataLoader(
TensorDataset(Xtr, ytr),
batch_size=batch_size, shuffle=True
)
print("Training windows:", tuple(Xtr.shape))
print("Test windows:", tuple(Xte.shape))
Ожидаемые размеры: train (6981, 20), test (2981, 20). Общих отсчётов нет.
Нейронные сети • Практическая работа №3
11

12.

ПРАКТИКА
Шаг 4. Линейная модель
class LinearFilterNet(nn.Module):
def __init__(self, M, bias=False):
super().__init__()
self.linear = nn.Linear(M, 1, bias=bias)
def forward(self, x):
return self.linear(x)
model = LinearFilterNet(M, bias=False)
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=0.01)
Один линейный слой без активации. Число входов равно M.
Нейронные сети • Практическая работа №3
12

13.

ПРАКТИКА
Шаг 5. Цикл обучения
train_loss, test_loss = [], []
for epoch in range(epochs):
model.train()
for xb, yb in loader:
optimizer.zero_grad()
loss = criterion(model(xb), yb)
loss.backward()
optimizer.step()
model.eval()
with torch.no_grad():
train_loss.append(
criterion(model(Xtr), ytr).item()
)
test_loss.append(
criterion(model(Xte), yte).item()
)
Этот блок и следующий блок добавьте в одну ячейку Colab.
Нейронные сети • Практическая работа №3
13

14.

ПРАКТИКА
Шаг 5. Контроль обучения
if epoch == 0 or (epoch + 1) % 10 == 0:
print(
f"Epoch {epoch + 1:02d}/{epochs} | "
f"Train MSE = {train_loss[-1]:.6f} | "
f"Test MSE = {test_loss[-1]:.6f}"
)
Продолжение цикла for epoch: сохраните отступ в 4 пробела. Та же ячейка.
Нейронные сети • Практическая работа №3
14

15.

ПРАКТИКА
Шаг 6. График функции потерь
plt.figure(figsize=(10, 4))
plt.plot(train_loss, label="Train MSE")
plt.plot(test_loss, label="Test MSE")
plt.xlabel("Epoch index (0-based)")
plt.ylabel("MSE")
plt.yscale("log")
plt.title("Training and test error")
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()
После быстрого снижения MSE возможны колебания из-за обучения по мини-пакетам.
Нейронные сети • Практическая работа №3
15

16.

РЕЗУЛЬТАТЫ
Динамика MSE: фактический запуск
После 60 эпох: Train MSE = 0.026249, Test MSE = 0.025755.
Нейронные сети • Практическая работа №3
16

17.

ПРАКТИКА
Шаг 7. Восстановление и метрики
with torch.no_grad():
pred = model(Xte).cpu().numpy().ravel()
ref = yte.cpu().numpy().ravel()
inp = x[split + M - 1:]
mse_in = np.mean((inp - ref) ** 2)
mse_out = np.mean((pred - ref) ** 2)
power = np.mean(ref ** 2)
snr_in = 10 * np.log10(power / mse_in)
snr_out = 10 * np.log10(power / mse_out)
delta_snr = snr_out - snr_in
Оценка проводится на одном и том же тестовом интервале.
Нейронные сети • Практическая работа №3
17

18.

ПРАКТИКА
Шаг 7. Вывод MSE и SNR
print("\n========== TEST RESULTS ==========")
print(f"Input MSE = {mse_in:.6f}")
print(f"Output MSE = {mse_out:.6f}")
print(f"Input SNR = {snr_in:.2f} dB")
print(f"Output SNR = {snr_out:.2f} dB")
print(f"Delta SNR = {delta_snr:.2f} dB")
В данном запуске: MSE 0.091184 / 0.025755; SNR 8.36 / 13.85 дБ.
Нейронные сети • Практическая работа №3
18

19.

ПРАКТИКА
Шаг 7. График восстановленного сигнала
test_n = n[split + M - 1:]
L = min(300, len(ref))
plt.figure(figsize=(12, 5))
plt.plot(test_n[:L], inp[:L], color="gray",
alpha=0.5, label="Noisy input")
plt.plot(test_n[:L], ref[:L], color="blue",
linewidth=2, label="Clean reference")
plt.plot(test_n[:L], pred[:L], color="red",
linewidth=1.8, label="Filtered output")
plt.xlabel("Sample index")
plt.ylabel("Amplitude")
Продолжение оформления графика приведено на следующем слайде.
Нейронные сети • Практическая работа №3
19

20.

ПРАКТИКА
Шаг 7. Подпись и вывод графика
plt.title(
f"Signal restoration | MSE = {mse_out:.4f} | "
f"Delta SNR = {delta_snr:.2f} dB"
)
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()
Добавьте к предыдущему блоку в ту же ячейку, затем выполните её.
Нейронные сети • Практическая работа №3
20

21.

РЕЗУЛЬТАТЫ
Восстановленный сигнал
Фактический запуск: MSE = 0.025755; SNR 8.36 / 13.85 дБ; ΔSNR = 5.49 дБ.
Нейронные сети • Практическая работа №3
21

22.

ПРАКТИКА
Шаг 8. Коэффициенты и АЧХ
weights = (
model.linear.weight.detach().cpu().numpy().ravel()
)
print("Filter coefficients:")
print(np.array2string(weights, precision=5))
print(f"\nNumber of coefficients: {M}")
print(f"FIR filter order: {M - 1}")
f, H = freqz(weights, worN=1024, fs=1.0)
20 коэффициентов, порядок 19. Первый вес относится к текущему отсчёту.
Нейронные сети • Практическая работа №3
22

23.

ПРАКТИКА
Шаг 8. Построение АЧХ
plt.figure(figsize=(10, 4))
plt.plot(f, np.abs(H), label="Learned filter")
plt.axvline(0.01, color="green", linestyle="--",
label="Signal frequency 0.01")
plt.axvline(0.03, color="orange", linestyle="--",
label="Signal frequency 0.03")
plt.xlabel("Frequency, cycles/sample")
plt.ylabel("|H(f)|")
plt.title("Frequency response of the learned filter")
plt.xlim(0, 0.5)
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()
Линии 0.01 и 0.03 относятся к основному примеру. Измените их по варианту.
Нейронные сети • Практическая работа №3
23

24.

РЕЗУЛЬТАТЫ
АЧХ обученного фильтра
Коэффициент передачи: |H(0.01)| = 0.9288; |H(0.03)| = 1.0055.
Нейронные сети • Практическая работа №3
24

25.

ПРАКТИКА
Шаг 9. Количественный анализ
print("\n========== ANALYSIS ==========")
print(f"Input MSE = {mse_in:.6f}")
print(f"Output MSE = {mse_out:.6f}")
print(f"Delta SNR = {delta_snr:.2f} dB")
if mse_out < mse_in:
reduction = 100 * (1 - mse_out / mse_in)
print(f"MSE reduction = {reduction:.2f}%")
print("Filtering reduced the reconstruction error.")
else:
print("Filtering did not reduce the reconstruction error.")
В фактическом запуске средний квадрат ошибки уменьшился на 71.76%.
Нейронные сети • Практическая работа №3
25

26.

ПРАКТИКА
Шаг 9. Усиление и фазовый сдвиг
signal_frequencies = np.array([0.01, 0.03])
H_signal = np.exp(
-2j * np.pi
* signal_frequencies[:, None]
* np.arange(M)[None, :]
) @ weights
for frequency, response in zip(signal_frequencies, H_signal):
print(
f"f = {frequency:.3f} cycles/sample | "
f"Gain = {abs(response):.4f} | "
f"Phase = {np.angle(response, deg=True):.2f} degrees"
)
Gain показывает изменение амплитуды. Phase показывает фазовый сдвиг.
Нейронные сети • Практическая работа №3
26

27.

РЕЗУЛЬТАТЫ
Анализ результатов
MSE до фильтрации: 0.091184. После: 0.025755. Снижение: 71.76%.
SNR до фильтрации: 8.36 дБ. После: 13.85 дБ. Прирост: 5.49 дБ.
На полезных частотах усиление близко к 1: 0.9288 и 1.0055.
Выход следует за эталоном, но сохраняет остаточную ошибку и искажения.
Это результаты одного запуска. Для проверки обобщения используйте независимый шум.
Нейронные сети • Практическая работа №3
27

28.

ИНДИВИДУАЛЬНЫЕ ЗАДАНИЯ
Варианты 1- 4. Низкочастотные сигналы
№
Сигнал, частоты
M
σ
Особенность
1
sin: f₁ = 0.02
15
0.25
bias=False
2
sin: 0.015 + 0.025
25
0.35
Амплитуды 1 и 0.5
3
sin: f₁ = 0.005
10
0.40
Оценка искажений
4
sin: f₁ = 0.03, DC = +0.8
30
0.20
bias=True, сравнить
с bias=False
Названия групп описывают сигнал. Селективные свойства обученного фильтра подтвердите по АЧХ.
Нейронные сети • Практическая работа №3
28

29.

ИНДИВИДУАЛЬНЫЕ ЗАДАНИЯ
Варианты 5 - 7. Среднечастотные сигналы
№
Сигнал, частоты
M
σ
Задача анализа
5
sin: f₁ = 0.15
20
0.30
Усиление при f₁
6
sin: f₁ = 0.12, f₂ = 0.18
40
0.22
Сохранение двух частот
7
sin: f₁ = 0.22
35
0.28
Шум вне полезной области
Для двух синусоид используйте амплитуды 1 и 0.5. Частоты заданы в циклах / отсчёт.
Нейронные сети • Практическая работа №3
29

30.

ИНДИВИДУАЛЬНЫЕ ЗАДАНИЯ
Варианты 8 - 10. Сложные сигналы
№
Сигнал
M
σ
Уточнение параметров
8
sin: 0.01, 0.02, 0.04
50
0.15
Амплитуды: 1, 0.5, 0.25
9
Пилообразный сигнал
24
0.30
f₀ = 0.01, амплитуда 1
10
Меандр после фильтрации
16
0.25
f₀ = 0.01, амплитуда ±1
сглаживание: среднее по 5
Для вариантов 9 - 10 недостающие параметры заданы как единые условия. Шум добавляется после подготовки
эталона.
Нейронные сети • Практическая работа №3
30

31.

ИНДИВИДУАЛЬНЫЕ ЗАДАНИЯ
Варианты 11 - 14. Изменение режима
№
Сигнал / режим
M
σ
Изменение кода
11
sin: f₁ = 0.02, прогноз
20
0.10
Цель s[n+1]
12
sin: f₁ = 0.025, L2
45
0.50
Adam, weight_decay=0.001
13
sin: f₁ = 0.01, f₂ = 0.05
12
0.20
SGD и Adam, lr=0.01
14
sin: f₁ = 0.02, online
20
0.20
SGD, batch_size=1
shuffle=False, lr=0.001
В вариантах 12 и 14 параметры регуляризации, частота и lr уточнены для воспроизводимости.
Нейронные сети • Практическая работа №3
31

32.

ПРАКТИКА
Модификации для вариантов 11–14
# 11: прогноз на один шаг, окна без последнего отсчёта
X, y = make_windows(x_part[:-1], s_part[1:], M)
# 12: L2-регуляризация в Adam
optimizer = optim.Adam(model.parameters(),
lr=0.01, weight_decay=1e-3)
# 13: сравнить два независимых обучения с тем же seed
optimizer = optim.SGD(model.parameters(), lr=0.01)
# 14: последовательные отсчёты, один проход
epochs = 1
loader = DataLoader(TensorDataset(Xtr, ytr),
batch_size=1, shuffle=False)
optimizer = optim.SGD(model.parameters(), lr=0.001)
При MSE и batch_size=1 SGD даёт w ← w + 2ηe·u, то есть обновление типа LMS.
Нейронные сети • Практическая работа №3
32

33.

ПРАКТИКА
Порядок выполнения работы
1. Выбрать вариант и сформировать чистый сигнал и шум.
2. Разделить последовательность и сформировать окна.
3. Создать линейную модель и задать оптимизатор.
4. Обучить модель и сохранить MSE по эпохам.
5. Восстановить сигнал, рассчитать MSE и ΔSNR.
6. Извлечь веса, построить АЧХ и сформулировать вывод.
В варианте 14 стройте ошибку по шагам или среднюю MSE по блокам. После обучения оцените test с
фиксированными весами.
Нейронные сети • Практическая работа №3
33

34.

ДОМАШНЕЕ ЗАДАНИЕ
Содержание отчёта
1. Цель работы, номер варианта и все параметры эксперимента.
2. Модифицированный исходный код с пояснением структуры окон.
3. Графики Train MSE и Test MSE.
4. Чистый, зашумлённый и восстановленный сигналы на одном графике.
5. Коэффициенты фильтра, АЧХ, MSE и изменение SNR.
6. Вывод о подавлении шума, искажениях и селективности.
Нейронные сети • Практическая работа №3
34

35.

ПРОВЕРКА ПОНИМАНИЯ
Вопросы для защиты
1. Почему nn.Linear без смещения соответствует FIR-фильтру?
2. Что означают M и порядок фильтра M−1?
3. Почему в модели нет нелинейной функции активации?
4. Как правильно выровнять окно и целевой отсчёт?
5. Почему test не участвует в обновлении весов?
6. Что показывают MSE, ΔSNR и АЧХ?
7. Чем стационарный фильтр отличается от адаптивного LMS?
Нейронные сети • Практическая работа №3
35

36.

Практическая работа №3
Спасибо
за внимание!
English     Русский Правила