File size: 5,717 Bytes
8136541
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import pandas as pd
import numpy as np
import torch
from torch.utils.data import TensorDataset, DataLoader
from sklearn.preprocessing import MinMaxScaler
from pathlib import Path
import os

# Definição do Modelo LSTM com PyTorch
class Net(torch.nn.Module):
    def __init__(self, input_size, hidden_size, output_size, num_layers=1):
        super(Net, self).__init__()
        self.lstm = torch.nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)
        self.linear = torch.nn.Linear(hidden_size, output_size)

    def forward(self, x):
        lstm_out, _ = self.lstm(x)
        # Usamos apenas a saída do último passo de tempo para a previsão
        last_time_step_out = lstm_out[:, -1, :]
        out = self.linear(last_time_step_out)
        return out

def create_sliding_windows(data, sequence_length, prediction_length):
    """Cria janelas deslizantes para problemas de séries temporais."""
    xs, ys = [], []
    for i in range(len(data) - sequence_length - prediction_length + 1):
        x = data[i:(i + sequence_length)]
        y = data[(i + sequence_length):(i + sequence_length + prediction_length), -1] # A última coluna é 'P_kW'
        xs.append(x)
        ys.append(y)
    return np.array(xs), np.array(ys)

def load_data(client_id: int, sequence_length=60, prediction_length=30, batch_size=32):
    """

    Carrega os dados para um cliente específico, processa e retorna DataLoaders.

    NOVA VERSÃO: Concatena todos os percursos e divide em 80% para treino e 20% para teste.

    """
    data_dir = Path(f"data/client_{client_id}")
    
    # 1. Carrega todos os percursos em uma lista de DataFrames
    all_routes_df = [pd.read_csv(f) for f in data_dir.glob("*.csv")]

    if not all_routes_df:
        raise FileNotFoundError(f"Nenhum arquivo CSV encontrado para o cliente {client_id} no diretório {data_dir}")

    # 2. Concatena TODOS os percursos em um único DataFrame
    combined_df = pd.concat(all_routes_df, ignore_index=True)

    feature_columns = ['vehicle_speed', 'engine_rpm', 'accel_x', 'accel_y', 'P_kW', 'dt']
    processed_df = combined_df[feature_columns].dropna()

    # 3. Divide o DataFrame combinado em 80% para treino e 20% para teste
    split_index = int(len(processed_df) * 0.8)
    train_df = processed_df.iloc[:split_index]
    test_df = processed_df.iloc[split_index:]

    # 4. AJUSTA O SCALER APENAS NOS DADOS DE TREINO (ESSENCIAL!)
    scaler = MinMaxScaler()
    scaler.fit(train_df)

    # 5. TRANSFORMA AMBOS OS CONJUNTOS COM O SCALER AJUSTADO
    train_scaled = scaler.transform(train_df)
    test_scaled = scaler.transform(test_df)

    # 6. CRIA JANELAS SEPARADAMENTE PARA TREINO E TESTE
    X_train, y_train = create_sliding_windows(train_scaled, sequence_length, prediction_length)
    X_test, y_test = create_sliding_windows(test_scaled, sequence_length, prediction_length)
    
    # Garante que os conjuntos não sejam vazios
    if len(X_train) == 0 or len(X_test) == 0:
        raise ValueError(f"A divisão de dados para o cliente {client_id} resultou em um conjunto de treino ou teste vazio. Verifique a quantidade de dados.")

    # Conversão para tensores PyTorch
    X_train_tensor = torch.from_numpy(X_train).float()
    y_train_tensor = torch.from_numpy(y_train).float()
    X_test_tensor = torch.from_numpy(X_test).float()
    y_test_tensor = torch.from_numpy(y_test).float()

    # Criação de DataLoaders
    train_dataset = TensorDataset(X_train_tensor, y_train_tensor)
    test_dataset = TensorDataset(X_test_tensor, y_test_tensor)
    
    trainloader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
    testloader = DataLoader(test_dataset, batch_size=batch_size)
    
    num_features = X_train_tensor.shape[2]

    return trainloader, testloader, num_features


def train(net, trainloader, epochs, device):
    """Treina e retorna a perda média por amostra (float)."""
    criterion = torch.nn.MSELoss(reduction="mean")  # perda média por saída
    optimizer = torch.optim.Adam(net.parameters(), lr=1e-5)
    net.to(device)
    net.train()

    total_loss_sum = 0.0
    total_samples = 0

    for _ in range(epochs):
        for sequences, labels in trainloader:
            sequences, labels = sequences.to(device), labels.to(device)
            optimizer.zero_grad()
            outputs = net(sequences)
            loss = criterion(outputs, labels)  # média por amostra no batch
            loss.backward()
            torch.nn.utils.clip_grad_norm_(net.parameters(), max_norm=1.0)
            optimizer.step()

            batch_size = sequences.size(0)
            total_loss_sum += loss.item() * batch_size  # soma por amostra
            total_samples += batch_size

    if total_samples == 0:
        return 0.0
    return total_loss_sum / total_samples  # média por amostra

# --- substitua a função test ---
def test(net, testloader, device):
    """Avalia e retorna (avg_loss_per_sample, num_examples)."""
    criterion = torch.nn.MSELoss(reduction="mean")
    net.to(device)
    net.eval()
    total_loss_sum = 0.0
    total_samples = 0
    with torch.no_grad():
        for sequences, labels in testloader:
            sequences, labels = sequences.to(device), labels.to(device)
            outputs = net(sequences)
            loss = criterion(outputs, labels)
            batch_size = sequences.size(0)
            total_loss_sum += loss.item() * batch_size
            total_samples += batch_size

    if total_samples == 0:
        return 0.0, 0
    avg_loss = total_loss_sum / total_samples
    return avg_loss, total_samples