Update metrics - #321
Conversation
for more information, see https://pre-commit.ci
There was a problem hiding this comment.
Code Review
This pull request refactors several evaluation metrics (including MRR, HitRate, and NegativeLogLikelihood) to support averaging on a per-trip basis instead of per-batch, introduces a new MeanRank metric, and updates the basket dataset to calculate sample counts more efficiently. The review feedback highlights critical TensorFlow type mismatch issues in the MRR and MeanRank metrics that could lead to runtime errors, and suggests a more Pythonic implementation for the new get_n_samples method.
| else: | ||
| self.mrr.assign(self.mrr + tf.reduce_sum(mean_rank)) | ||
| self.n_evals.assign(self.n_evals + tf.cast(tf.shape(y_true)[0], tf.float32)) | ||
| self.mrr.assign(self.mrr + tf.reduce_sum(tf.cast(1 / item_ranks, dtype=tf.float32))) | ||
| self.n_evals.assign(self.n_evals + tf.shape(y_true)[0]) |
There was a problem hiding this comment.
Using tf.shape(y_true)[0] directly without casting will raise a TypeError in TensorFlow due to type mismatch between the float32 variable self.n_evals and the int32/int64 tensor returned by tf.shape. Additionally, using assign_add is more efficient and standard for updating metric variables.
| else: | |
| self.mrr.assign(self.mrr + tf.reduce_sum(mean_rank)) | |
| self.n_evals.assign(self.n_evals + tf.cast(tf.shape(y_true)[0], tf.float32)) | |
| self.mrr.assign(self.mrr + tf.reduce_sum(tf.cast(1 / item_ranks, dtype=tf.float32))) | |
| self.n_evals.assign(self.n_evals + tf.shape(y_true)[0]) | |
| else: | |
| self.mrr.assign_add(tf.reduce_sum(tf.cast(1 / item_ranks, dtype=tf.float32))) | |
| self.n_evals.assign_add(tf.cast(tf.shape(y_true)[0], self.n_evals.dtype)) |
| if batch is not None and self.average_on_trip: | ||
| int_batch = tf.cast(batch, tf.int32) | ||
| self.mr.assign(self.mr + tf.reduce_sum(tf.math.segment_mean(float_rank, int_batch))) | ||
| self.n_evals.assign(self.n_evals + tf.reduce_max(batch) + 1) | ||
| else: | ||
| self.mr.assign(self.mr + tf.reduce_sum(float_rank)) | ||
| self.n_evals.assign(self.n_evals + tf.cast(tf.shape(y_true)[0], tf.float32)) |
There was a problem hiding this comment.
The current implementation of MeanRank under average_on_trip has several issues:
tf.math.segment_meanrequires sorted segment IDs, which is not guaranteed for arbitrary batches.tf.reduce_max(batch) + 1assumes batch indices are 0-indexed and contiguous, which is fragile and prone to bugs.- Adding
tf.reduce_max(batch)(int32) directly toself.n_evals(float32) will raise aTypeErrorin TensorFlow.
Using tf.unique and tf.math.unsorted_segment_mean with assign_add is much more robust, consistent with other metrics in this file, and avoids type mismatch errors.
if batch is not None and self.average_on_trip:
unique_trips, segment_ids = tf.unique(batch)
trip_means = tf.math.unsorted_segment_mean(
float_rank, segment_ids, tf.shape(unique_trips)[0]
)
self.mr.assign_add(tf.reduce_sum(trip_means))
self.n_evals.assign_add(tf.cast(tf.shape(unique_trips)[0], self.n_evals.dtype))
else:
self.mr.assign_add(tf.reduce_sum(float_rank))
self.n_evals.assign_add(tf.cast(tf.shape(y_true)[0], self.n_evals.dtype))| def get_n_samples(self) -> int: | ||
| """Return the numbers of samples/transations of the TripDataset.""" | ||
| n_samples = 0 | ||
| for trip in self.trips: | ||
| n_samples += len(trip.purchases) | ||
|
|
||
| return n_samples |
There was a problem hiding this comment.
The get_n_samples method can be written more pythonically and efficiently using a generator expression with sum(). Also, corrected typos in the docstring ('numbers' -> 'number', 'transations' -> 'transactions').
| def get_n_samples(self) -> int: | |
| """Return the numbers of samples/transations of the TripDataset.""" | |
| n_samples = 0 | |
| for trip in self.trips: | |
| n_samples += len(trip.purchases) | |
| return n_samples | |
| def get_n_samples(self) -> int: | |
| """Return the number of samples/transactions of the TripDataset.""" | |
| return sum(len(trip.purchases) for trip in self.trips) |
Coverage Report for Python 3.12
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Coverage Report for Python 3.9
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Coverage Report for Python 3.11
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description of the goal of the PR
Description:
Changes this PR introduces (fill it before implementation)
Checklist before requesting a review