TTK
Loading...
Searching...
No Matches
TopologicalDimensionReduction.cpp
Go to the documentation of this file.
2
3#ifdef TTK_ENABLE_TORCH
4
5using namespace torch::indexing;
6
7ttk::TopologicalDimensionReduction::TopologicalDimensionReduction(
8 bool useCUDA,
9 bool deterministic,
10 int seed,
11 int numberOfComponents,
12 int epochs,
13 double learningRate,
14 OPTIMIZER optimizer,
15 REGUL method,
16 MODEL modelType,
17 const std::string &architecture,
18 const std::string &activation,
19 int batchSize,
20 bool batchNormalization,
21 double regCoefficient,
22 bool inputIsImages,
23 bool preOptimize,
24 int preOptimizeEpochs)
25 : NumberOfComponents(numberOfComponents), Epochs(epochs),
26 LearningRate(learningRate), Optimizer(optimizer), Method(method),
27 ModelType(modelType), InputIsImages(inputIsImages),
28 Architecture(architecture), Activation(activation), BatchSize(batchSize),
29 BatchNormalization(batchNormalization), RegCoefficient(regCoefficient),
30 PreOptimize(preOptimize), PreOptimizeEpochs(preOptimizeEpochs) {
31 // inherited from Debug: prefix will be printed at the beginning of every msg
32 this->setDebugMsgPrefix("TopologicalDimensionReduction");
33
34 if(torch::cuda::is_available() && useCUDA && !deterministic)
35 device = torch::kCUDA;
36
37 if(deterministic) {
38 at::manual_seed(seed);
39 at::globalContext().setDeterministicFillUninitializedMemory(true);
40 at::globalContext().setDeterministicAlgorithms(true, true);
41 } else {
42 at::globalContext().setDeterministicFillUninitializedMemory(false);
43 at::globalContext().setDeterministicAlgorithms(false, false);
44 }
45}
46
47int ttk::TopologicalDimensionReduction::initializeModel(int inputSize,
48 int inputDimension) {
49 if((!InputIsImages && !AutoEncoder::isStringValid(Architecture))
50 || (InputIsImages
51 && !ConvolutionalAutoEncoder::isStringValid(Architecture))) {
52 printErr("Invalid string for layers description.");
53 return 1;
54 }
55 if(ModelType == MODEL::AUTOENCODER) {
56 if(!InputIsImages)
57 model = std::make_unique<AutoEncoder>(inputDimension, NumberOfComponents,
58 Architecture, Activation,
59 BatchNormalization);
60 else
61 model = std::make_unique<ConvolutionalAutoEncoder>(
62 sqrt(inputDimension), NumberOfComponents, Architecture,
63 BatchNormalization);
64 } else if(ModelType == MODEL::AUTODECODER)
65 model = std::make_unique<AutoDecoder>(inputDimension, inputSize,
66 NumberOfComponents, Architecture,
67 Activation, false);
68 else if(ModelType == MODEL::DIRECT)
69 model = std::make_unique<DirectOptimization>(inputSize, NumberOfComponents);
70 model->to(device);
71 return 0;
72}
73
74void ttk::TopologicalDimensionReduction::initializeOptimizer() {
75 if(Optimizer == OPTIMIZER::ADAM)
76 torchOptimizer = std::make_unique<torch::optim::Adam>(
77 model->parameters(), /*lr=*/LearningRate);
78 else if(Optimizer == OPTIMIZER::SGD)
79 torchOptimizer = std::make_unique<torch::optim::SGD>(
80 model->parameters(), /*lr=*/LearningRate);
81 else if(Optimizer == OPTIMIZER::LBFGS)
82 torchOptimizer = std::make_unique<torch::optim::LBFGS>(
83 model->parameters(), /*lr=*/LearningRate);
84}
85
86int ttk::TopologicalDimensionReduction::execute(
87 std::vector<std::vector<double>> &outputEmbedding,
88 const std::vector<double> &inputMatrix,
89 size_t n) {
90 Timer tm{};
91 printMsg("Initialization", 0., tm.getElapsedTime());
92
93 const int inputSize = n;
94 const int inputRawDimension = inputMatrix.size() / n;
95 const int inputDimension
96 = inputRawDimension - PreOptimize * NumberOfComponents;
97 if(!InputIsImages)
98 this->printMsg("input dimension: " + std::to_string(inputDimension), 0.0,
99 tm.getElapsedTime());
100 else
101 this->printMsg(
102 "input dimension: " + std::to_string(inputDimension) + " = "
103 + std::to_string(static_cast<int>(sqrt(inputDimension))) + " x "
104 + std::to_string(static_cast<int>(sqrt(inputDimension))) + " images",
105 .0, tm.getElapsedTime());
106 this->printMsg("output dimension: " + std::to_string(NumberOfComponents), 0.0,
107 tm.getElapsedTime());
108 this->printMsg(
109 "#elements: " + std::to_string(inputSize), 0.0, tm.getElapsedTime());
110
111#ifndef TTK_ENABLE_CGAL
112 printWrn("TTK not compiled with CGAL enabled: this backend could be slow.");
113#endif
114
115 if(initializeModel(inputSize, inputDimension))
116 return 1;
117 initializeOptimizer();
118
119 const torch::Tensor rawInput
120 = torch::from_blob(const_cast<double *>(inputMatrix.data()),
121 {inputSize, inputRawDimension}, torch::kFloat64)
122 .to(torch::kFloat32)
123 .to(device);
124 const torch::Tensor input
125 = rawInput.index({Slice(), Slice(None, inputDimension)});
126
127 rpd::PointCloud points(inputSize, std::vector<double>(inputDimension));
128 for(int i = 0; i < inputSize; ++i) {
129 for(int j = 0; j < inputDimension; ++j)
130 points[i][j] = inputMatrix[inputRawDimension * i + j];
131 }
132
133 if(PreOptimize) {
134 preOptimize(input, rawInput.index({Slice(), Slice(inputDimension, None)}));
135 initializeOptimizer();
136 }
137
138 if(Method == REGUL::NO_REGUL) {
139 printMsg("Starting optimization", 0., tm.getElapsedTime());
140 optimizeSimple(input);
141 } else {
142 printMsg("Computing input persistence", 0., tm.getElapsedTime());
143 topologicalLossContainer
144 = std::make_unique<TopologicalLoss>(input, points, Method);
145 printMsg("Starting optimization", 0., tm.getElapsedTime());
146 optimize(input);
147 }
148
149 const torch::Tensor latent = model->encode(input).cpu();
150 for(int i = 0; i < inputSize; ++i) {
151 for(int j = 0; j < NumberOfComponents; ++j)
152 outputEmbedding[j][i] = latent[i][j].item<double>();
153 }
154
155 printMsg("Complete", 1., tm.getElapsedTime());
156 return 0;
157}
158
159void ttk::TopologicalDimensionReduction::optimizeSimple(
160 const torch::Tensor &input) const {
161 int epoch = 0;
162
163 auto closure = [&] {
164 TensorIndex indices = Slice();
165 if(BatchSize > 0)
166 indices
167 = torch::randint(input.size(0), {BatchSize}, torch::kInt).to(device);
168
169 // step initialization
170 torchOptimizer->zero_grad();
171 const torch::Tensor prediction = model->forward(input.index(indices));
172
173 // loss and optimizer step
174 const torch::Tensor loss
175 = torch::mse_loss(prediction, input.index(indices));
176 loss.backward();
177
178 // IO
179 printLoss(epoch, Epochs, loss.item<double>());
180
181 return loss;
182 };
183
184 for(; epoch < Epochs; ++epoch)
185 torchOptimizer->step(closure);
186}
187
188void ttk::TopologicalDimensionReduction::optimize(
189 const torch::Tensor &input) const {
190 int epoch = 0;
191
192 auto closure = [&] {
193 // step initialization
194 torchOptimizer->zero_grad();
195 const torch::Tensor latent = model->encode(input);
196 const torch::Tensor prediction = model->decode(latent);
197
198 // loss and optimizer step
199 const torch::Tensor topologicalLoss
200 = RegCoefficient * topologicalLossContainer->computeLoss(latent);
201 const torch::Tensor reconstructionLoss = torch::mse_loss(prediction, input);
202 const torch::Tensor loss = reconstructionLoss + topologicalLoss;
203 loss.backward();
204
205 // IO
206 printLoss(epoch, Epochs, loss.item<double>());
207
208 return loss;
209 };
210
211 for(; epoch < Epochs; ++epoch)
212 torchOptimizer->step(closure);
213}
214
215void ttk::TopologicalDimensionReduction::preOptimize(
216 const torch::Tensor &input, const torch::Tensor &target) const {
217 int epoch = 0;
218
219 auto closure = [&] {
220 // step initialization
221 torchOptimizer->zero_grad();
222 const torch::Tensor latent = model->encode(input);
223 const torch::Tensor prediction = model->decode(latent);
224
225 // loss and optimizer step
226 const torch::Tensor loss
227 = torch::mse_loss(latent, target) + torch::mse_loss(prediction, input);
228 loss.backward();
229
230 // IO
231 printLoss(epoch, PreOptimizeEpochs, loss.item<double>());
232
233 return loss;
234 };
235
236 for(; epoch < PreOptimizeEpochs; ++epoch)
237 torchOptimizer->step(closure);
238}
239
240void ttk::TopologicalDimensionReduction::printLoss(int epoch,
241 int maxEpoch,
242 double loss) const {
243 if(epoch % std::max(1, maxEpoch / 10) == 0)
244 printMsg(
245 "Loss at epoch " + std::to_string(epoch) + ": " + std::to_string(loss),
246 static_cast<double>(epoch) / maxEpoch, -1, -1, debug::LineMode::REPLACE);
247 else if(epoch == maxEpoch - 1)
248 printMsg("Final loss value: " + std::to_string(loss), 1.);
249}
250
251#endif
int printErr(const std::string &msg, const debug::LineMode &lineMode=debug::LineMode::NEW, std::ostream &stream=std::cerr) const
Definition Debug.h:149
std::vector< std::vector< value_t > > PointCloud
printMsg(debug::output::BOLD+" | | | | | . \\ | | (__| | / __/| |_| / __/| (_) |"+debug::output::ENDCOLOR, debug::Priority::PERFORMANCE, debug::LineMode::NEW, stream)