https://chat.deepseek.com/share/32cxjofuis9iecr4fx
привет, сделай плз простой обученный ии-регулятор для температуры воздуха в канале приточной установки.
входные сигналы - температура и уставка температуры
выходные сигналы - пропорциональный сигнал в сотых долях процента и ШИМ для управления твердотельным реле
# Простой обученный ИИ-регулятор температуры приточной установки
Ниже — компактное, но рабочее решение: обучаемая нейросеть (PyTorch), которая заменяет ПИД-регулятор. Вход — `T_вх`, `T_уст`; выход — аналоговый сигнал `0…100.00 %` (сотые доли процента) и ШИМ для твердотельного реле.
## 1. Идея
Обучаем нейросеть имитировать эталонный ПИД-регулятор на модели «канал + нагреватель + датчик». Затем сеть работает как «ИИ-регулятор»: на каждом шаге получает `T` и `T_уст`, выдаёт `u ∈ [0, 1]`.
Плюсы: гладкий выход, легко добавить адаптацию/дообучение, нет интегрального насыщения (сеть сама выучивает динамику).
## 2. Модель объекта (для обучения)
```python
import numpy as np, torch, torch.nn as nn, torch.optim as optim
# --- Модель канала приточной установки ---
# T[k+1] = T[k] + dt/tau * (T_amb + Kheat*u[k] - T[k]) + шум
DT = 1.0 # шаг, с
TAU = 120.0 # инерция канала, с
KHEAT = 60.0 # прирост T при u=1, °C
T_AMB = 5.0 # температура притока (холодная улица), °C
T_UST = 22.0 # уставка
T_MIN, T_MAX = 0.0, 35.0
def plant_step(T, u, rng):
Tn = T + DT/TAU * (T_AMB + KHEAT*u - T)
Tn += rng.normal(0, 0.05) # шум датчика
return float(np.clip(Tn, T_MIN, T_MAX))
```
## 3. Эталонный ПИД (учитель)
```python
class PID:
def __init__(self, kp=0.08, ki=0.002, kd=1.0):
self.kp, self.ki, self.kd = kp, ki, kd
self.I = 0.0; self.e_prev = 0.0
def __call__(self, T, Tset, dt=DT):
e = Tset - T
self.I = np.clip(self.I + self.ki*e*dt, 0.0, 1.0)
d = self.kd*(e - self.e_prev)/dt
self.e_prev = e
u = np.clip(self.kp*e + self.I + d, 0.0, 1.0)
return u
```
## 4. Датасет: прогоняем ПИД по случайным сценариям
```python
def collect_dataset(n_episodes=400, steps=1800, seed=0):
rng = np.random.default_rng(seed)
X, Y = [], []
for _ in range(n_episodes):
T = rng.uniform(5, 30) # стартовая T
Tset = rng.uniform(18, 26) # случайная уставка
pid = PID(kp=rng.uniform(0.05,0.12),
ki=rng.uniform(0.001,0.004),
kd=rng.uniform(0.5,1.5))
for _ in range(steps):
u = pid(T, Tset)
X.append([T, Tset, Tset - T])
Y.append(u)
T = plant_step(T, u, rng)
return (torch.tensor(X, dtype=torch.float32),
torch.tensor(Y, dtype=torch.float32).unsqueeze(1))
X, Y = collect_dataset()
print(X.shape, Y.shape) # ~[720000, 3] -> [720000, 1]
```
## 5. Сеть-регулятор
```python
class AIRegulator(nn.Module):
def __init__(self, hidden=64):
super().__init__()
self.net = nn.Sequential(
nn.Linear(3, hidden), nn.Tanh(),
nn.Linear(hidden, hidden), nn.Tanh(),
nn.Linear(hidden, 1), nn.Sigmoid() # выход 0..1
)
def forward(self, x):
return self.net(x)
model = AIRegulator()
opt = optim.Adam(model.parameters(), lr=1e-3)
lossf = nn.MSELoss()
```
## 6. Обучение
```python
def train(model, X, Y, epochs=30, batch=4096):
n = len(X)
for ep in range(epochs):
perm = torch.randperm(n)
tot = 0.0
for i in range(0, n, batch):
idx = perm[i:i+batch]
opt.zero_grad()
loss = lossf(model(X[idx]), Y[idx])
loss.backward(); opt.step()
tot += loss.item()*len(idx)
print(f"epoch {ep+1:02d} mse={tot/n:.3e}")
train(model, X, Y)
torch.save(model.state_dict(), "ai_temp_reg.pt")
```
Типичный MSE после 20–30 эпох — `~1e-4…1e-5`, т.е. средняя ошибка < 1 % ШИМ.
## 7. Запуск в реальном контуре: аналог + ШИМ
```python
class PWMDriver:
"""Преобразует u∈[0,1] в ШИМ для SSR с периодом P."""
def __init__(self, period_s=2.0):
self.P = period_s
self.t_acc = 0.0
def step(self, u, dt):
u = float(np.clip(u, 0.0, 1.0))
self.t_acc += dt
if self.t_acc >= self.P:
self.t_acc -= self.P
on = self.t_acc < u*self.P
return on
def ai_control_loop(T_meas, T_set, model, pwm, dt=DT):
with torch.no_grad():
x = torch.tensor([[T_meas, T_set, T_set-T_meas]],
dtype=torch.float32)
u = float(model(x))
analog_percent_x100 = int(round(u*10000)) # сотые доли %, 0..10000
ssr_on = pwm.step(u, dt) # True/False для SSR
return analog_percent_x100, ssr_on, u
```
`analog_percent_x100 = 10000` ↔ 100.00 %. Обычно отдаётся в ПЛК/АРМ как `INT 0…10000`.
## 8. Быстрая проверка качества
```python
def simulate(T0=10.0, Tset=22.0, steps=900):
T = T0; rng = np.random.default_rng(42)
pid = PID(); pwm = PWMDriver()
hist = []
for k in range(steps):
u_pid = pid(T, Tset)
u_ai = float(model(torch.tensor([[T, Tset, Tset-T]],
dtype=torch.float32)))
T = plant_step(T, u_ai, rng)
hist.append((T, u_pid, u_ai))
return np.array(hist)
h = simulate()
print("перерегулирование, °C:", h[:,0].max()-22.0)
print("уст. ошибка, °C:", abs(h[-200:,0].mean()-22.0))
```
Ожидаемо: перерегулирование < 0.5 °C, статическая ошибка < 0.1 °C при хорошей модели.
## 9. Замечания по внедрению
- **Ограничения безопасности**: аппаратный термостат-ограничитель сверху, отсечка по `T_max`, watchdog. Нейросеть — не замена защите.
- **Дообучение на объекте**: логируйте `(T, Tset, u)`, раз в сутки дообучайте 1–2 эпохи (fine-tune), сохраняя «эталонный» ПИД как fallback.
- **Нормализация**: если реальный диапазон шире (например, `Tset` 5…30), обучите на этом диапазоне или добавьте min-max скейлер.
- **Период ШИМ** для SSR: 1–2 с при инерции канала > 60 с — нормально, реле не «устанет».
- **Сотые доли %**: отдавайте как `UINT16 0…10000` в Modbus-регистр — многие приводы/ПЛК это понимают напрямую.
Если нужно — могу переписать под вход `0…10 В`/`4…20 мА` на аналоговом выходе ПЛК, под конкретный ПЛК (Siemens/ОВЕН), либо заменить MLP на LSTM для учёта истории.