forked from YuweiMao-NU/AIMicrostructurePrediction
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtrain.py
280 lines (222 loc) · 10.6 KB
/
train.py
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
import numpy as np
import scipy.io
import torch
import joblib
import torch.nn as nn
import torch.optim as optim
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
from torch.utils.data import Dataset, DataLoader
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_absolute_percentage_error
import json
import joblib
import time
import pandas as pd
import torch
from torch import nn
# torch.set_default_tensor_type(torch.DoubleTensor)
class EncoderDecoderRNN(nn.Module):
def __init__(self, input_size, hidden_size, num_layers, dropout, param_size):
super().__init__()
self.input_size = input_size
self.hidden_size = hidden_size
self.num_layers = num_layers
self.dropout = dropout
self.param_size = param_size
# Define the encoder LSTM
self.encoder = nn.LSTM(input_size *2,
hidden_size,
num_layers,
dropout=dropout,
batch_first=True)
# Define the decoder LSTM
self.decoder = nn.LSTM(input_size,
hidden_size,
num_layers,
dropout=dropout,
batch_first=True)
# Define the output layer
self.param_embedding = nn.Linear(param_size, input_size)
self.output = nn.Linear(hidden_size, input_size)
def init_hidden(self, batch_size):
# Initialize hidden state and cell state with zeros
h_0 = torch.zeros(self.num_layers, batch_size, self.hidden_size)
c_0 = torch.zeros(self.num_layers, batch_size, self.hidden_size)
return (h_0, c_0)
def forward(self, x, processing_params, target=None, iftrain=True, max_length=9):
batch_size = x.size(0)
# Concatenate the processing parameters to each time step of the input sequence
processing_params = self.param_embedding(processing_params)
processing_params = processing_params.unsqueeze(1).repeat(1, x.size(1), 1)
# print(x.size(), processing_params.size())
encoder_input = torch.cat([x, processing_params], dim=2)
# Encode the input sequence
encoder_hidden = self.init_hidden(batch_size)
# print(encoder_input.size())
encoder_output, encoder_hidden = self.encoder(encoder_input, encoder_hidden)
# Prepare decoder input (starting with the last encoder output)
decoder_input = x[:, -1:, :] # Start with the last input from the initial sequence
decoder_hidden = encoder_hidden
# Initialize the output sequence
output_seq = torch.zeros(batch_size, max_length, self.input_size)
# Decode the output sequence
for i in range(max_length):
decoder_output, decoder_hidden = self.decoder(decoder_input, decoder_hidden)
output = self.output(decoder_output.squeeze(1))
# output = constrain(output)
output_seq[:, i, :] = output
# Use teacher forcing or the model's own predictions as the next input
if iftrain and target is not None and np.random.random() < p_teacher_forcing:
decoder_input = target[:, i:i+1, :]
else:
decoder_input = output.unsqueeze(1)
return output_seq
# Define Dataset
class TimeSeriesDataset(Dataset):
def __init__(self, x, target, param):
self.x = x
self.target = target
self.param = param
def __len__(self):
return self.x.shape[0]
def __getitem__(self, index):
return self.x[index], self.target[index], self.param[index]
def random_split_param_filenames():
interval = [0, 0.25, 0.5, 0.75, 1]
filename_list = []
for t1 in interval:
for t2 in interval:
for t3 in interval:
for t4 in interval:
for t5 in interval:
filename = [t1, t2, t3, t4, t5]
filename_list.append(filename)
train_filenames, test_filenames = train_test_split(filename_list, test_size=0.3, random_state=0)
train_filenames, val_filenames = train_test_split(train_filenames, test_size=0.2, random_state=0)
return train_filenames, val_filenames, test_filenames
def load_data(filename_list):
inputs = []
param_list = []
for filename in filename_list:
[t1, t2, t3, t4, t5] = filename
# param_rep = onehot_encode(filename).reshape(1, -1)
param_rep = np.array([t1, t2, t3, t4, t5]).reshape(1, -1)
# filename = str(t1) + str(t2) + str(t3) + str(t4) + str(t5)
filename = str(t1) + '_' + str(t2) + '_' + str(t3) + '_' + str(t4) + '_' + str(t5)
odf = np.loadtxt('ODF_new/' + filename + '.csv', delimiter=',').transpose()
# print(tmp.shape)
inputs.append(odf)
param_list.append(param_rep[0])
inputs, param_list = np.array(inputs), np.array(param_list)
return inputs, param_list
def constrain(odfs):
mat = scipy.io.loadmat('../Copper_Properties.mat')
p = mat['stiffness']
q = mat['volumefraction']
q = torch.tensor(q, dtype=torch.double) # convert to PyTorch tensor
output_odf_list = torch.zeros_like(odfs)
for i in range(odfs.size()[0]):
odf = odfs[i]
odf = torch.maximum(odf, torch.tensor(0.0))
odf = odf.type(torch.DoubleTensor) # convert to PyTorch tensor
volfrac = torch.matmul(q, odf) + 1e-8
out_odf = odf/volfrac
output_odf_list[i] = out_odf # convert back to NumPy array
return output_odf_list
def data_normalized(target_data, scaler=None):
num_samples, seq_length, num_features = target_data.shape
reshaped_data = target_data.reshape(num_samples * seq_length, num_features)
# Step 2: Normalize using StandardScaler
if not scaler:
scaler = StandardScaler()
reshaped_data_normalized = scaler.fit_transform(reshaped_data)
else:
reshaped_data_normalized = scaler.transform(reshaped_data)
# Step 3: Reshape back to the original shape
target_data_normalized = reshaped_data_normalized.reshape(num_samples, seq_length, num_features)
return target_data_normalized, scaler
if __name__ == '__main__':
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
train_filenames, val_filenames, test_filenames = random_split_param_filenames()
print('train file number: ', len(train_filenames), 'val file number:', len(val_filenames), 'test file number: ', len(test_filenames))
filenames = {"train_filenames":train_filenames, "val_filenames":val_filenames, "test_filenames":test_filenames}
filenames = json.dumps(filenames)
with open("filenames.json", "w") as outfile:
outfile.write(filenames)
batch_size = 16
input_size = 76
param_size = 5
hidden_size = 128
num_layers = 2
dropout = 0.1
sequence_length = 3 # Using the first H time steps
future_length = 11 - sequence_length # Predict the next F time steps
num_epochs = 1000
learning_rate = 1e-4
p_teacher_forcing = 0.2
model_name = 'autoencoder_new3.model'
# Initialize the model
model = EncoderDecoderRNN(input_size, hidden_size, num_layers, dropout, param_size)
# model = model.to(device)
# define optimizer
optimizer = optim.Adam(model.parameters())
full_data_list, param_list = load_data(train_filenames)
input_data_list = full_data_list[:, :sequence_length, :] # Shape: (100, 2, 76)
# target_data_list takes the next 9 time steps
target_data_list = full_data_list[:, sequence_length:, :] # Shape: (100, 9, 76)
print(input_data_list.shape, param_list.shape)
#
input_data_list, input_scaler = data_normalized(input_data_list, scaler=None)
joblib.dump(input_scaler, 'scaler.pkl')
# target_data_list = target_data_list.to(device)
input_data_list = torch.Tensor(input_data_list)
target_data_list = torch.Tensor(target_data_list)
param_list = torch.Tensor(param_list)
traindata = TimeSeriesDataset(input_data_list, target_data_list, param_list)
train_loader = DataLoader(traindata, batch_size=batch_size, shuffle=True)
# load val data
val_input_data_list, val_param_list = load_data(val_filenames)
val_input_data_list, val_target_data_list = val_input_data_list[:, :sequence_length, :], val_input_data_list[:, sequence_length:, :] # Shape: (100, 2, 76)
val_input_data_list, _ = data_normalized(val_input_data_list, scaler=input_scaler)
val_input_data_list = torch.Tensor(val_input_data_list)
val_target_data_list = torch.Tensor(val_target_data_list)
val_param_list = torch.Tensor(val_param_list)
valdata = TimeSeriesDataset(val_input_data_list, val_target_data_list, val_param_list)
val_loader = DataLoader(valdata, batch_size=batch_size, shuffle=False)
# Initialize the model
model = EncoderDecoderRNN(input_size, hidden_size, num_layers, dropout, param_size)
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
# Training loop
for epoch in range(num_epochs):
model.train()
total_train_loss = 0
for i, (input_data, target_data, param) in enumerate(train_loader):
optimizer.zero_grad()
input_data, target_data, param = input_data, target_data, param
output = model(input_data, param, target_data, iftrain=True, max_length=future_length)
loss = criterion(output, target_data)
if torch.isnan(loss).any():
print(f"Skipping step {i} due to NaN loss")
continue
loss.backward()
# Gradient clipping to avoid exploding gradients
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=5.0)
optimizer.step()
total_train_loss += loss.item() * input_data.size(0)
avg_train_loss = total_train_loss / len(train_loader.dataset)
print(f'Epoch [{epoch + 1}/{num_epochs}], Train Loss: {avg_train_loss}')
# Validation loop
model.eval()
total_val_loss = 0
with torch.no_grad():
for i, (input_data, target_data, param) in enumerate(val_loader):
input_data, target_data, param = input_data, target_data, param
output = model(input_data, param, iftrain=False, max_length=future_length)
val_loss = criterion(output, target_data)
total_val_loss += val_loss.item() * input_data.size(0)
avg_val_loss = total_val_loss / len(val_loader.dataset)
print(f'Epoch [{epoch + 1}/{num_epochs}], Validation Loss: {avg_val_loss}')
print("Training complete.")
torch.save(model.state_dict(), model_name)