Coverage for encodermap/loss_functions/loss_functions.py: 69%

267 statements  

« prev     ^ index     » next       coverage.py v7.1.0, created at 2023-02-07 11:05 +0000

1# -*- coding: utf-8 -*- 

2# encodermap/loss_functions/loss_functions.py 

3################################################################################ 

4# Encodermap: A python library for dimensionality reduction. 

5# 

6# Copyright 2019-2022 University of Konstanz and the Authors 

7# 

8# Authors: 

9# Kevin Sawade, Tobias Lemke 

10# 

11# Encodermap is free software: you can redistribute it and/or modify 

12# it under the terms of the GNU Lesser General Public License as 

13# published by the Free Software Foundation, either version 2.1 

14# of the License, or (at your option) any later version. 

15# This package is distributed in the hope that it will be useful to other 

16# researches. IT DOES NOT COME WITH ANY WARRANTY WHATSOEVER; without even the 

17# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 

18# See the GNU Lesser General Public License for more details. 

19# 

20# See <http://www.gnu.org/licenses/>. 

21################################################################################ 

22"""Loss functions for encodermap 

23 

24ToDo: 

25 * Debug Autograph for distance cost 

26 * WARNING: AutoGraph could not transform <function sigmoid_loss at 0x00000264AB761040> and will run it as-is. 

27 * Please report this to the TensorFlow team. When filing the bug, set the verbosity to 10 (on Linux, `export AUTOGRAPH_VERBOSITY=10`) and attach the full output. 

28 * Cause: module 'gast' has no attribute 'Index' 

29 * To silence this warning, decorate the function with @tf.autograph.experimental.do_not_convert 

30 

31""" 

32############################################################################## 

33# Imports 

34############################################################################## 

35 

36import tensorflow as tf 

37import tensorflow.keras.backend as K 

38 

39from ..encodermap_tf1.misc import distance_cost 

40from ..misc.distances import ( 

41 pairwise_dist, 

42 pairwise_dist_periodic, 

43 periodic_distance, 

44 sigmoid, 

45) 

46from ..parameters.parameters import ADCParameters, Parameters, ParametersFramework 

47 

48############################################################################## 

49# Globals 

50############################################################################## 

51 

52__all__ = [ 

53 "reconstruction_loss", 

54 "auto_loss", 

55 "center_loss", 

56 "regularization_loss", 

57 "loss_combinator", 

58 "distance_loss", 

59 "cartesian_loss", 

60 "cartesian_distance_loss", 

61 "angle_loss", 

62 "dihedral_loss", 

63] 

64 

65############################################################################## 

66# Functions for tf.cond 

67# Don't know if this is really faster than logging every step to tensorboard 

68############################################################################## 

69 

70 

71def _do_nothing(*args): 

72 """This function does nothing. One of the functions provided to tf.cond.""" 

73 pass 

74 

75 

76def _summary_cost(name, cost): 

77 """This functions logs a scalar to a name. One of the functions provided to tf.cond.""" 

78 tf.summary.scalar(name, cost) 

79 

80 

81############################################################################## 

82# Legacy Code to make some tests 

83############################################################################## 

84 

85 

86def old_distance_loss(model, parameters=None): 

87 # choose parameters 

88 if parameters is None: 88 ↛ 91line 88 didn't jump to line 91, because the condition on line 88 was never false

89 p = ParametersFramework(Parameters.defaults) 

90 else: 

91 p = parameters 

92 # check Layers 

93 if len(model.layers) == 2: 93 ↛ 95line 93 didn't jump to line 95, because the condition on line 93 was never true

94 # sequential API 

95 latent = model.encoder 

96 else: 

97 # functional API 

98 latent = model.encoder 

99 

100 # closure 

101 def loss(y_true, y_pred=None, step=None): 

102 loss.name = "distance_loss" 

103 y_pred = latent(y_true, training=True) 

104 # print(f'For testing: Loss latent of model to test em.encodermap_tf1.misc: {y_pred}') 

105 if p.distance_cost_scale is not None: 105 ↛ 112line 105 didn't jump to line 112, because the condition on line 105 was never false

106 dist_cost = distance_cost( 

107 y_true, y_pred, *p.dist_sig_parameters, p.periodicity 

108 ) 

109 if p.distance_cost_scale != 0: 109 ↛ 113line 109 didn't jump to line 113, because the condition on line 109 was never false

110 dist_cost *= p.distance_cost_scale 

111 else: 

112 dist_cost = 0 

113 tf.summary.scalar("Distance Cost", dist_cost) 

114 return dist_cost 

115 

116 return loss 

117 

118 

119############################################################################## 

120# Public Functions 

121############################################################################## 

122 

123 

124def basic_loss_combinator(*losses): 

125 """Calculates the sum of a list of losses and returns a combined loss. 

126 

127 The basic loss combinator does not write to summary. Can be used for debugging. 

128 

129 """ 

130 

131 def loss(y_true, y_pred=None): 

132 return sum([loss(y_true, y_pred) for loss in losses]) 

133 

134 return loss 

135 

136 

137def loss_combinator(*losses): 

138 """Calculates the sum of a list of losses and returns a combined loss. 

139 

140 Args: 

141 *losses: Variable length argument list of loss functions. 

142 

143 Returns: 

144 function: A combined loss function that can be used in custom training or with model.fit() 

145 

146 Example: 

147 >>> import encodermap as em 

148 >>> from encodermap import loss_functions 

149 >>> import tensorflow as tf 

150 >>> import numpy as np 

151 >>> tf.random.set_seed(1) # fix random state to pass doctest :) 

152 

153 >>> model = tf.keras.Sequential([ 

154 ... tf.keras.layers.Dense(100, kernel_regularizer=tf.keras.regularizers.l2(), activation='relu'), 

155 ... tf.keras.layers.Dense(2, kernel_regularizer=tf.keras.regularizers.l2(), activation='relu'), 

156 ... tf.keras.layers.Dense(100, kernel_regularizer=tf.keras.regularizers.l2(), activation='relu') 

157 ... ]) 

158 

159 >>> # Set up losses and bundle them using the loss combinator 

160 >>> auto_loss = loss_functions.auto_loss(model) 

161 >>> reg_loss = loss_functions.regularization_loss(model) 

162 >>> loss = loss_functions.loss_combinator(auto_loss, reg_loss) 

163 

164 >>> # Compile model, model.fit() usually takes a tuple of (data, classes) but in 

165 >>> # regression learning the data needs to be provided twice. That's why we use fit(data, data) 

166 >>> model.compile(tf.keras.optimizers.Adam(), loss=loss) 

167 >>> data = np.random.random((100, 100)) 

168 >>> history = model.fit(data, data, verbose=0) 

169 >>> tf.random.set_seed(None) # reset seed 

170 

171 >>> # This weird contraption is also there to make the output predictable and pass tests 

172 >>> # Somehow the tf.random.seed(1) does not work here. :( 

173 >>> loss = history.history['loss'][0] 

174 >>> print(loss) # doctest: +SKIP 

175 {'loss': array([2.6])} 

176 >>> print(type(loss)) 

177 <class 'float'> 

178 

179 """ 

180 

181 def combined_loss_func(y_true, y_pred=None): 

182 cost = sum([loss(y_true, y_pred) for loss in losses]) 

183 tf.summary.scalar("Combined Cost", cost) 

184 return cost 

185 

186 return combined_loss_func 

187 

188 

189def distance_loss(model, parameters=None, callback=None): 

190 """Encodermap distance_loss 

191 

192 Transforms space using sigmoid function first proposed by sketch-map. 

193 

194 Args: 

195 model (tf.keras.Model): A model you want to use the loss function on. 

196 parameters (Union[encodermap.Parameters, None], optional): The parameters. If None is 

197 provided default values (check them with print(em.Parameters.defaults_description())) 

198 are used. Defaults to None. 

199 

200 Note: 

201 If the model contains two layers. The first layer will be assumed to be the decoder. 

202 If the model contains more layers, one layer needs to be named 'latent' (case insensitive). 

203 

204 Raises: 

205 Exception: When no bottleneck/latent layer can be found in the model. 

206 

207 Returns: 

208 function: A loss function. 

209 

210 References:: 

211 

212 @article{ceriotti2011simplifying, 

213 title={Simplifying the representation of complex free-energy landscapes using sketch-map}, 

214 author={Ceriotti, Michele and Tribello, Gareth A and Parrinello, Michele}, 

215 journal={Proceedings of the National Academy of Sciences}, 

216 volume={108}, 

217 number={32}, 

218 pages={13023--13028}, 

219 year={2011}, 

220 publisher={National Acad Sciences} 

221 } 

222 

223 """ 

224 # choose parameters 

225 if parameters is None: 

226 p = ParametersFramework(Parameters.defaults) 

227 else: 

228 p = parameters 

229 

230 # check Layers 

231 if len(model.layers) == 2: 

232 # sequential API 

233 latent = model.encoder 

234 else: 

235 # functional API 

236 latent = model.encoder 

237 # functional API without multiple models to be removed 

238 # layer_index = [layer.name.lower() for layer in model.layers].index('latent') 

239 # latent = tf.keras.Model(inputs=model.inputs, outputs=model.layers[layer_index].output) 

240 

241 if callback is None: 241 ↛ 244line 241 didn't jump to line 244, because the condition on line 241 was never false

242 write_bool = K.constant(False, "bool", name="log_bool") 

243 else: 

244 write_bool = callback.log_bool 

245 

246 # define dist loss 

247 dist_loss = sigmoid_loss(p) 

248 

249 # closure 

250 def distance_loss_func(y_true, y_pred=None): 

251 """y_true can be whatever input you like, dihedrals, angles, pairwise dist, contact maps. That will be 

252 transformed with Sketchmap's sigmoid function, as will the output of the latent layer of the autoencoder. 

253 the difference of these two will result in a loss function.""" 

254 distance_loss_func.name = "distance_loss" 

255 y_pred = latent(y_true, training=True) 

256 # functional model gives a tuple 

257 if isinstance(y_true, tuple): 257 ↛ 258line 257 didn't jump to line 258, because the condition on line 257 was never true

258 y_true = tf.concat(y_true, axis=1) 

259 if p.distance_cost_scale is not None: 259 ↛ 264line 259 didn't jump to line 264, because the condition on line 259 was never false

260 dist_cost = dist_loss(y_true, y_pred) 

261 if p.distance_cost_scale != 0: 261 ↛ 265line 261 didn't jump to line 265, because the condition on line 261 was never false

262 dist_cost *= p.distance_cost_scale 

263 else: 

264 dist_cost = 0 

265 tf.cond( 

266 write_bool, 

267 true_fn=lambda: _summary_cost("Distance Cost", dist_cost), 

268 false_fn=lambda: _do_nothing(), 

269 name="Cost", 

270 ) 

271 return dist_cost 

272 

273 return distance_loss_func 

274 

275 

276def sigmoid_loss(parameters=None, periodicity_overwrite=None): 

277 """Sigmoid loss closure for use in distance cost and cartesian distance cost. 

278 

279 Outer function prepares callable sigmoid. Sigmoid can then be called with just y_true and y_pred. 

280 

281 Args: 

282 parameters (Union[encodermap.Parameters, None], optional): The parameters. If None is 

283 provided default values (check them with print(em.Parameters.defaults_description())) 

284 are used. Defaults to None. 

285 periodicity overwrite(Union[float, None]), optional): Cartesian distance cost is always non-periodic. 

286 To make sure no periodicity is applied to the data, set periodicity_overwrite to float('inf'). If 

287 None is provided the periodicity of the parameters class (default 2*pi) will be used. 

288 Defaults to None. 

289 

290 Returns: 

291 function: A function that takes y_true and y_pred. Both need to be of the same shape. 

292 

293 """ 

294 if parameters is None: 

295 p = ParametersFramework(Parameters.defaults) 

296 else: 

297 p = parameters 

298 

299 if periodicity_overwrite is not None: 

300 periodicity = periodicity_overwrite 

301 else: 

302 periodicity = p.periodicity 

303 

304 # @tf.autograph.experimental.do_not_convert 

305 def sigmoid_loss_func(y_true, y_pred): 

306 r_h = y_true 

307 r_l = y_pred 

308 if periodicity == float("inf"): 

309 dist_h = pairwise_dist(r_h) 

310 else: 

311 dist_h = pairwise_dist_periodic(r_h, periodicity) 

312 dist_l = pairwise_dist(r_l) 

313 

314 sig_h = sigmoid(*p.dist_sig_parameters[:3])(dist_h) 

315 sig_l = sigmoid(*p.dist_sig_parameters[3:])(dist_l) 

316 

317 cost = tf.reduce_mean(tf.square(sig_h - sig_l)) 

318 return cost 

319 

320 return sigmoid_loss_func 

321 

322 

323def center_loss(model, parameters=None, callback=None): 

324 """Encodermap center_loss 

325 

326 Use in custom training loops or in model.fit() training. 

327 

328 Args: 

329 model (tf.keras.Model): A model you want to use the loss function on. 

330 parameters (Union[encodermap.Parameters, None], optional): The parameters. If None is 

331 provided default values (check them with print(em.Parameters.defaults_description())) 

332 are used. Defaults to None. 

333 

334 Note: 

335 If the model contains two layers. The first layer will be assumed to be the decoder. 

336 If the model contains more layers, one layer needs to be named 'latent' (case insensitive). 

337 

338 Raises: 

339 Exception: When no bottleneck/latent layer can be found in the model. 

340 

341 Returns: 

342 function: A loss function. 

343 

344 """ 

345 # choose parameters 

346 if parameters is None: 

347 p = ParametersFramework(Parameters.defaults) 

348 else: 

349 p = parameters 

350 # check Layers 

351 if len(model.layers) == 2: 

352 # sequential API 

353 latent = model.encoder 

354 else: 

355 # functional API 

356 latent = model.encoder 

357 

358 if callback is None: 358 ↛ 361line 358 didn't jump to line 361, because the condition on line 358 was never false

359 write_bool = K.constant(False, "bool", name="log_bool") 

360 else: 

361 write_bool = callback.log_bool 

362 

363 # closure 

364 def center_loss_func(y_true, y_pred=None): 

365 """y_true will not be used in this loss function. y_pred can be supplied, but if None will be taken from the 

366 latent layer. This loss function tries to center the points in the latent layer.""" 

367 center_loss_func.name = "center_loss" 

368 y_pred = latent(y_true, training=True) 

369 # functional model gives a tuple 

370 if isinstance(y_true, tuple): 370 ↛ 371line 370 didn't jump to line 371, because the condition on line 370 was never true

371 y_true = tf.concat(y_true, axis=1) 

372 # center cost 

373 # this is still a bit finicky 

374 # needs to have tf.GradentTape() context manager to watch a single layer 

375 if p.center_cost_scale is not None: 375 ↛ 385line 375 didn't jump to line 385, because the condition on line 375 was never false

376 # custom model dep code 

377 # center_cost = tf.reduce_mean(tf.square(model.get_layer('Encoder')(y_true))) 

378 # sequential model 

379 # get the output according to https://keras.io/getting_started/faq/#how-can-i-obtain-the-output-of-an-intermediate-layer-feature-extraction 

380 # this can be omitted by implementing a custom metric. Todo. 

381 center_cost = tf.reduce_mean(tf.square(y_pred)) 

382 if p.center_cost_scale != 0: 382 ↛ 386line 382 didn't jump to line 386, because the condition on line 382 was never false

383 center_cost *= p.center_cost_scale 

384 else: 

385 center_cost = 0 

386 tf.cond( 

387 write_bool, 

388 true_fn=lambda: _summary_cost("Center Cost", center_cost), 

389 false_fn=lambda: _do_nothing(), 

390 name="Cost", 

391 ) 

392 return center_cost 

393 

394 return center_loss_func 

395 

396 

397def regularization_loss(model, parameters=None, callback=None): 

398 """Regularization loss of arbitrary tf.keras.Model 

399 

400 Use in custom training loops or in model.fit() training. 

401 Loss is obtained as tf.math.add_n(model.losses) 

402 

403 Args: 

404 model (tf.keras.Model): A model you want to use the loss function on. 

405 

406 Returns: 

407 function: A loss function. 

408 

409 """ 

410 if parameters is None: 

411 p = ParametersFramework(Parameters.defaults) 

412 else: 

413 p = parameters 

414 

415 if callback is None: 415 ↛ 418line 415 didn't jump to line 418, because the condition on line 415 was never false

416 write_bool = K.constant(False, "bool", name="log_bool") 

417 else: 

418 write_bool = callback.log_bool 

419 

420 def regularization_loss_func(y_true=None, y_pred=None): 

421 """y_true and y_pred will not be considered here, because the regularization loss is accessed via model.losses.""" 

422 regularization_loss.name = "regularization_loss" 

423 reg_loss = tf.math.add_n(model.losses) 

424 tf.cond( 

425 write_bool, 

426 true_fn=lambda: _summary_cost("Regularization Cost", reg_loss), 

427 false_fn=lambda: _do_nothing(), 

428 name="Cost", 

429 ) 

430 return reg_loss 

431 

432 return regularization_loss_func 

433 

434 

435def reconstruction_loss(model): 

436 """Simple Autoencoder recosntruction loss. 

437 

438 Use in custom training loops or in model.fit training. 

439 

440 Args: 

441 model (tf.keras.Model): A model you want to use the loss function on. 

442 

443 Returns: 

444 function: A loss function to be used in custom training or model.fit. 

445 Function takes the following arguments: 

446 y_true (tf.Tensor): The true tensor. 

447 y_pred (tf.Tensor, optional): The output tensor. If not supplied 

448 the model will be called to get this tensor. Defaults to None. 

449 step (int): A step for tensorboard callbacks. Defaults to None. 

450 

451 Examples: 

452 >>> import tensorflow as tf 

453 >>> import encodermap as em 

454 >>> from encodermap import loss_functions 

455 >>> model = tf.keras.Model() 

456 >>> loss = loss_functions.reconstruction_loss(model) 

457 >>> x = tf.random.normal(shape=(10, 10)) 

458 >>> loss(x, x).numpy() 

459 0.0 

460 

461 """ 

462 

463 def reconstruction_loss_func(y_true, y_pred=None): 

464 # if y_pred is None, this function is used in custom training 

465 # and should use model to get the output 

466 if y_pred is None: 466 ↛ 467line 466 didn't jump to line 467, because the condition on line 466 was never true

467 y_pred = model(y_true) 

468 # calculate error 

469 reconstruction_error = tf.reduce_mean(tf.square(tf.subtract(y_pred, y_true))) 

470 return reconstruction_error 

471 

472 return reconstruction_loss_func 

473 

474 

475def auto_loss(model, parameters=None, callback=None): 

476 """Encodermap auto_loss. 

477 

478 Use in custom training loops or in model.fit() training. 

479 

480 Args: 

481 model (tf.keras.Model): A model you want to use the loss function on. 

482 parameters (Union[encodermap.Parameters, None], optional): The parameters. If None is 

483 provided default values (check them with print(em.Parameters.defaults_description())) 

484 are used. Defaults to None. 

485 

486 Returns: 

487 function: A loss function. 

488 

489 """ 

490 if parameters is None: 

491 p = ParametersFramework(Parameters.defaults) 

492 else: 

493 p = parameters 

494 

495 if callback is None: 495 ↛ 498line 495 didn't jump to line 498, because the condition on line 495 was never false

496 write_bool = K.constant(False, "bool", name="log_bool") 

497 else: 

498 write_bool = callback.log_bool 

499 

500 def auto_loss_func(y_true, y_pred=None): 

501 """y_true is complete model input, y_pred is complete model output. Because here it is not intended to unpack 

502 the output into dihedrals and angles, y_pred can be None and will be directly taken from the model.""" 

503 auto_loss_func.name = "auto_loss" 

504 

505 if y_pred is None: 

506 y_pred = model(y_true) 

507 if p.auto_cost_scale is not None: 507 ↛ 527line 507 didn't jump to line 527, because the condition on line 507 was never false

508 if p.auto_cost_variant == "mean_square": 508 ↛ 509line 508 didn't jump to line 509, because the condition on line 508 was never true

509 auto_cost = tf.reduce_mean( 

510 tf.square(periodic_distance(y_true, y_pred, p.periodicity)) 

511 ) 

512 elif p.auto_cost_variant == "mean_abs": 512 ↛ 516line 512 didn't jump to line 516, because the condition on line 512 was never false

513 auto_cost = tf.reduce_mean( 

514 tf.abs(periodic_distance(y_true, y_pred, p.periodicity)) 

515 ) 

516 elif p.auto_cost_variant == "mean_norm": 

517 auto_cost = tf.reduce_mean( 

518 tf.norm(periodic_distance(y_true, y_pred, p.periodicity), axis=1) 

519 ) 

520 else: 

521 raise ValueError( 

522 "auto_cost_variant {} not available".format(p.auto_cost_variant) 

523 ) 

524 if p.auto_cost_scale != 0: 524 ↛ 528line 524 didn't jump to line 528, because the condition on line 524 was never false

525 auto_cost *= p.auto_cost_scale 

526 else: 

527 auto_cost = 0 

528 tf.cond( 

529 write_bool, 

530 true_fn=lambda: _summary_cost("Auto Cost", auto_cost), 

531 false_fn=lambda: _do_nothing(), 

532 name="Cost", 

533 ) 

534 return auto_cost 

535 

536 return auto_loss_func 

537 

538 

539def dihedral_loss(model, parameters=None, callback=None): 

540 """Encodermap dihedral loss. Calculates distances between true and predicted dihedral angles. 

541 

542 Respects periodicity in a [-a, a] interval if the provided parameters have a periodicity of 2 * a. 

543 

544 Note: 

545 The interval should be (-a, a], but due to floating point precision we can't make this 

546 distinction here. 

547 

548 Args: 

549 model (tf.keras.Model): The model to use the loss function on. 

550 parameters (Union[encodermap.ADCParameters, None], optional): The parameters. If None is 

551 provided default values (check them with print(em.ADCParameters.defaults_description())) 

552 are used. Defaults to None. 

553 

554 Returns: 

555 function: A loss function. Can be used in either custom training or model.fit(). 

556 

557 """ 

558 if parameters is None: 558 ↛ 559line 558 didn't jump to line 559, because the condition on line 558 was never true

559 p = ParametersFramework(ADCParameters.defaults) 

560 else: 

561 p = parameters 

562 

563 if callback is None: 563 ↛ 566line 563 didn't jump to line 566, because the condition on line 563 was never false

564 write_bool = K.constant(False, "bool", name="log_bool") 

565 else: 

566 write_bool = callback.log_bool 

567 

568 # closure 

569 def dihedral_loss_func(y_pred, y_true=None): 

570 """y_pred should be model input dihedrals, y_true should be model output dihedrals.""" 

571 dihedral_loss_func.name = "dihedral_loss" 

572 if p.dihedral_cost_scale is not None: 572 ↛ 593line 572 didn't jump to line 593, because the condition on line 572 was never false

573 if p.dihedral_cost_variant == "mean_square": 573 ↛ 574line 573 didn't jump to line 574, because the condition on line 573 was never true

574 dihedral_cost = tf.reduce_mean( 

575 tf.square(periodic_distance(y_true, y_pred, p.periodicity)) 

576 ) 

577 elif p.dihedral_cost_variant == "mean_abs": 577 ↛ 581line 577 didn't jump to line 581, because the condition on line 577 was never false

578 dihedral_cost = tf.reduce_mean( 

579 tf.abs(periodic_distance(y_true, y_pred, p.periodicity)) 

580 ) 

581 elif p.dihedral_cost_variant == "mean_norm": 

582 dihedral_cost = tf.reduce_mean( 

583 tf.norm(periodic_distance(y_true, y_pred, p.periodicity), axis=1) 

584 ) 

585 else: 

586 raise ValueError( 

587 "dihedral_cost_variant {} not available".format(p.auto_cost_variant) 

588 ) 

589 dihedral_cost /= p.dihedral_cost_reference 

590 if p.dihedral_cost_scale != 0: 590 ↛ 594line 590 didn't jump to line 594, because the condition on line 590 was never false

591 dihedral_cost *= p.dihedral_cost_scale 

592 else: 

593 dihedral_cost = 0 

594 tf.cond( 

595 write_bool, 

596 true_fn=lambda: _summary_cost("Dihedral Cost", dihedral_cost), 

597 false_fn=lambda: _do_nothing(), 

598 name="Cost", 

599 ) 

600 return dihedral_cost 

601 

602 return dihedral_loss_func 

603 

604 

605def side_dihedral_loss(model, parameters=None, callback=None): 

606 """Encodermap sidechain dihedral loss. Calculates distances between true and predicted sidechain dihedral angles. 

607 

608 Respects periodicity in a [-a, a] interval if the provided parameters have a periodicity of 2 * a. 

609 

610 Note: 

611 The interval should be (-a, a], but due to floating point precision we can't make this 

612 distinction here. 

613 

614 Args: 

615 model (tf.keras.Model): The model to use the loss function on. 

616 parameters (Union[encodermap.ADCParameters, None], optional): The parameters. If None is 

617 provided default values (check them with print(em.ADCParameters.defaults_description())) 

618 are used. Defaults to None. 

619 

620 Returns: 

621 function: A loss function. Can be used in either custom training or model.fit(). 

622 

623 """ 

624 if parameters is None: 624 ↛ 625line 624 didn't jump to line 625, because the condition on line 624 was never true

625 p = ParametersFramework(ADCParameters.defaults) 

626 else: 

627 p = parameters 

628 

629 if callback is None: 629 ↛ 632line 629 didn't jump to line 632, because the condition on line 629 was never false

630 write_bool = K.constant(False, "bool", name="log_bool") 

631 else: 

632 write_bool = callback.log_bool 

633 

634 # closure 

635 def side_dihedral_loss_func(y_pred, y_true=None): 

636 """y_pred should be model input side dihedrals, y_true should be model output side dihedrals.""" 

637 side_dihedral_loss_func.name = "side_dihedral_loss" 

638 if p.side_dihedral_cost_scale is not None: 

639 if p.side_dihedral_cost_variant == "mean_square": 

640 side_dihedral_cost = tf.reduce_mean( 

641 tf.square(periodic_distance(y_true, y_pred, p.periodicity)) 

642 ) 

643 elif p.side_dihedral_cost_variant == "mean_abs": 

644 side_dihedral_cost = tf.reduce_mean( 

645 tf.abs(periodic_distance(y_true, y_pred, p.periodicity)) 

646 ) 

647 elif p.side_dihedral_cost_variant == "mean_norm": 

648 side_dihedral_cost = tf.reduce_mean( 

649 tf.norm(periodic_distance(y_true, y_pred, p.periodicity), axis=1) 

650 ) 

651 else: 

652 raise ValueError( 

653 "dihedral_cost_variant {} not available".format(p.auto_cost_variant) 

654 ) 

655 side_dihedral_cost /= p.side_dihedral_cost_reference 

656 if p.side_dihedral_cost_scale != 0: 

657 side_dihedral_cost *= p.side_dihedral_cost_scale 

658 else: 

659 side_dihedral_cost = 0 

660 tf.cond( 

661 write_bool, 

662 true_fn=lambda: _summary_cost( 

663 "Sidechain Dihedral Cost", side_dihedral_cost 

664 ), 

665 false_fn=lambda: _do_nothing(), 

666 name="Cost", 

667 ) 

668 return side_dihedral_cost 

669 

670 return side_dihedral_loss_func 

671 

672 

673def angle_loss(model, parameters=None, callback=None): 

674 """Encodermap angle loss. Calculates distances between true and predicted angles. 

675 

676 Respects periodicity in a [-a, a] interval if the provided parameters have a periodicity of 2 * a. 

677 

678 Note: 

679 The interval should be (-a, a], but due to floating point precision we can't make this 

680 distinction here. 

681 

682 Args: 

683 model (tf.keras.Model): The model to use the loss function on. 

684 parameters (Union[encodermap.ADCParameters, None], optional): The parameters. If None is 

685 provided default values (check them with print(em.ADCParameters.defaults_description())) 

686 are used. Defaults to None. 

687 

688 Returns: 

689 function: A loss function. Can be used in either custom training or model.fit(). 

690 

691 """ 

692 if parameters is None: 692 ↛ 693line 692 didn't jump to line 693, because the condition on line 692 was never true

693 p = ParametersFramework(ADCParameters.defaults) 

694 else: 

695 p = parameters 

696 

697 if callback is None: 697 ↛ 700line 697 didn't jump to line 700, because the condition on line 697 was never false

698 write_bool = K.constant(False, "bool", name="log_bool") 

699 else: 

700 write_bool = callback.log_bool 

701 

702 # closure 

703 def angle_loss_func(y_pred, y_true=None): 

704 """y_true should be input angles. y_pred should be output angles (either from mean input angles or, when 

705 ADCParameters.use_backbone_angles == True, directly from model output).""" 

706 angle_loss_func.name = "angle_loss" 

707 if p.angle_cost_scale is not None: 707 ↛ 728line 707 didn't jump to line 728, because the condition on line 707 was never false

708 if p.angle_cost_variant == "mean_square": 708 ↛ 709line 708 didn't jump to line 709, because the condition on line 708 was never true

709 angle_cost = tf.reduce_mean( 

710 tf.square(periodic_distance(y_true, y_pred, p.periodicity)) 

711 ) 

712 elif p.angle_cost_variant == "mean_abs": 712 ↛ 716line 712 didn't jump to line 716, because the condition on line 712 was never false

713 angle_cost = tf.reduce_mean( 

714 tf.abs(periodic_distance(y_true, y_pred, p.periodicity)) 

715 ) 

716 elif p.angle_cost_variant == "mean_norm": 

717 angle_cost = tf.reduce_mean( 

718 tf.norm(periodic_distance(y_true, y_pred, p.periodicity), axis=1) 

719 ) 

720 else: 

721 raise ValueError( 

722 "angle_cost_variant {} not available".format(p.auto_cost_variant) 

723 ) 

724 angle_cost /= p.angle_cost_reference 

725 if p.angle_cost_scale != 0: 725 ↛ 726line 725 didn't jump to line 726, because the condition on line 725 was never true

726 angle_cost *= p.angle_cost_scale 

727 else: 

728 angle_cost = 0 

729 tf.cond( 

730 write_bool, 

731 true_fn=lambda: _summary_cost("Angle Cost", angle_cost), 

732 false_fn=lambda: _do_nothing(), 

733 name="Cost", 

734 ) 

735 return angle_cost 

736 

737 return angle_loss_func 

738 

739 

740def cartesian_distance_loss(model, parameters=None, callback=None): 

741 """Encodermap cartesian distance loss. Calculates sigmoid-weighted distances between pairwise cartesians and latent. 

742 

743 Uses sketch-map's sigmoid function to transform the high-dimensional space of the input and the 

744 low-dimensional space of latent. 

745 

746 Make sure to provide the pairwise cartesian distances. The output of the latent will be compared to the input. 

747 

748 Note: 

749 If the model contains two layers. The first layer will be assumed to be the decoder. 

750 If the model contains more layers, one layer needs to be named 'latent' (case insensitive). 

751 

752 Args: 

753 model (tf.keras.Model): The model to use the loss function on. 

754 parameters (Union[encodermap.ADCParameters, None], optional): The parameters. If None is 

755 provided default values (check them with print(em.ADCParameters.defaults_description())) 

756 are used. Defaults to None. 

757 

758 Returns: 

759 function: A loss function. Can be used in either custom training or model.fit(). 

760 

761 """ 

762 if parameters is None: 762 ↛ 763line 762 didn't jump to line 763, because the condition on line 762 was never true

763 p = ParametersFramework(ADCParameters.defaults) 

764 else: 

765 p = parameters 

766 

767 if callback is None: 767 ↛ 770line 767 didn't jump to line 770, because the condition on line 767 was never false

768 write_bool = K.constant(False, "bool", name="log_bool") 

769 else: 

770 write_bool = callback.log_bool 

771 

772 dist_loss = sigmoid_loss(p, periodicity_overwrite=float("inf")) 

773 

774 def cartesian_distance_loss_func(y_true, y_pred): 

775 """y_true can be whatever input you like, dihedrals, angles, pairwise dist, contact maps. That will be 

776 transformed with Sketchmap's sigmoid function, as will the output of the latent layer of the autoencoder. 

777 the difference of these two will result in a loss function.""" 

778 cartesian_distance_loss_func.name = "cartesian_distance_loss" 

779 if p.cartesian_distance_cost_scale is not None: 779 ↛ 784line 779 didn't jump to line 784, because the condition on line 779 was never false

780 dist_cost = dist_loss(y_true, y_pred) 

781 if p.distance_cost_scale != 0: 781 ↛ 785line 781 didn't jump to line 785, because the condition on line 781 was never false

782 dist_cost *= p.cartesian_distance_cost_scale 

783 else: 

784 dist_cost = 0 

785 tf.cond( 

786 write_bool, 

787 true_fn=lambda: _summary_cost("Cartesian Distance Cost", dist_cost), 

788 false_fn=lambda: _do_nothing(), 

789 name="Cost", 

790 ) 

791 return dist_cost 

792 

793 return cartesian_distance_loss_func 

794 

795 

796def cartesian_loss( 

797 model, 

798 scale_callback=None, 

799 parameters=None, 

800 log_callback=None, 

801 print_current_scale=False, 

802): 

803 """Encodermap cartesian distance loss. Calculates sigmoid-weighted distances between pairwise cartesians and latent. 

804 

805 Uses sketch-map's sigmoid function to transform the high-dimensional space of the input and the 

806 high-dimensional space of the output. 

807 

808 Adjustments to this cost_function via the soft_start parameter need to be made via a callback that re-compiles the 

809 model during training. For this, the soft_start parameters of the outer function will be used. 

810 It must be either 0 or 1, indexing the 1st or 2nd element of the cartesian_cost_scale_soft_start 

811 tuple. The callback should also be provided when model.fit is executed. 

812 

813 Three cases are possible: 

814 * Case 1: step < cartesian_cost_scale_soft_start[0]: cost_scale = 0 

815 * Case 2: cartesian_cost_scale_soft_start[0] <= step <= cartesian_cost_scale_soft_start[1]: 

816 cost_scale = p.cartesian_cost_scale / (cartesian_cost_scale_soft_start[1] - cartesian_cost_scale_soft_start[0]) * step 

817 * Case 3: cartesian_cost_scale_soft_start[1] < step: cost_scale = p.cartesian_cost_scale 

818 

819 Make sure to provide the pairwise cartesian distances. This function will be adjusted as training increases via a 

820 callback. See encodermap.callbacks.callbacks.IncreaseCartesianCost for more info. 

821 

822 Args: 

823 model (tf.keras.Model): The model to use the loss function on. 

824 parameters (Union[encodermap.ADCParameters, None], optional): The parameters. If None is 

825 provided default values (check them with print(em.ADCParameters.defaults_description())) 

826 are used. Defaults to None. 

827 soft_start (Union[int, None], optional): How to scale the cartesian loss. The ADCParameters class contains a 

828 two-tuple of integers. These integers can be used to scale this loss function. If soft_start is 0, the 

829 first value of ADCParameters.cartesian_cost_scale_soft_start will be used, if it is 1, the second. 

830 if it is None, or both values of ADCParameters.cartesian_cost_scale_soft_start are None, the cost will 

831 not be scaled. Defaults to None. 

832 print_current_scale (bool, optional): Whether to print the current scale. Is used in unittesting. Defaults to False. 

833 

834 Raises: 

835 Exception: When no bottleneck/latent layer can be found in the model. 

836 Exception: When soft_start is greater than 1 and can't index the two-tuple. 

837 

838 Returns: 

839 function: A loss function. Can be used in either custom training or model.fit(). 

840 

841 """ 

842 if parameters is None: 842 ↛ 843line 842 didn't jump to line 843, because the condition on line 842 was never true

843 p = ParametersFramework(ADCParameters.defaults) 

844 else: 

845 p = parameters 

846 if scale_callback is not None: 

847 current_scale_callback = scale_callback.current_cartesian_cost_scale 

848 else: 

849 current_scale_callback = K.constant( 

850 p.cartesian_cost_scale, dtype="float32", name="current_cartesian_cost_scale" 

851 ) 

852 

853 if print_current_scale: 

854 print(current_scale_callback) 

855 

856 if log_callback is None: 856 ↛ 859line 856 didn't jump to line 859, because the condition on line 856 was never false

857 write_bool = K.constant(False, "bool", name="log_bool") 

858 else: 

859 write_bool = log_callback.log_bool 

860 

861 def cartesian_loss_func(y_true, y_pred=None): 

862 """y_true should be pairwise distances of input cartesians, 

863 y_pred should be pairwise distances of back-mapped output cartesians.""" 

864 scale = current_scale_callback 

865 if p.cartesian_cost_variant == "mean_square": 865 ↛ 866line 865 didn't jump to line 866, because the condition on line 865 was never true

866 cartesian_cost = tf.reduce_mean(tf.square(y_true - y_pred)) 

867 elif p.cartesian_cost_variant == "mean_abs": 867 ↛ 869line 867 didn't jump to line 869, because the condition on line 867 was never false

868 cartesian_cost = tf.reduce_mean(tf.abs(y_true - y_pred)) 

869 elif p.cartesian_cost_variant == "mean_norm": 

870 cartesian_cost = tf.reduce_mean(tf.norm(y_true - y_pred, axis=1)) 

871 else: 

872 raise ValueError( 

873 "cartesian_cost_variant {} not available".format( 

874 p.dihedral_to_cartesian_cost_variant 

875 ) 

876 ) 

877 cartesian_cost /= p.cartesian_cost_reference 

878 tf.cond( 

879 write_bool, 

880 true_fn=lambda: _summary_cost( 

881 "Cartesian Cost before scaling", cartesian_cost 

882 ), 

883 false_fn=lambda: _do_nothing(), 

884 name="Cost", 

885 ) 

886 tf.cond( 

887 write_bool, 

888 true_fn=lambda: _summary_cost("Cartesian Cost current scaling", scale), 

889 false_fn=lambda: _do_nothing(), 

890 name="Cost", 

891 ) 

892 cartesian_cost *= scale 

893 tf.cond( 

894 write_bool, 

895 true_fn=lambda: _summary_cost( 

896 "Cartesian Cost after scaling", cartesian_cost 

897 ), 

898 false_fn=lambda: _do_nothing(), 

899 name="Cost", 

900 ) 

901 return cartesian_cost 

902 

903 return cartesian_loss_func