Skip to content

Commit

Permalink
[back] update references to "score_max" in Solidago, TournesolInput
Browse files Browse the repository at this point in the history
… and inconsitencies API (#1966)
  • Loading branch information
amatissart authored May 2, 2024
1 parent 4efa27d commit 91efbfb
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 58 deletions.
6 changes: 5 additions & 1 deletion backend/ml/inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ def get_comparisons(self, criteria=None, user_id=None) -> pd.DataFrame:

values = scores_queryset.values(
"score",
"score_max",
"criteria",
"weight",
entity_a=F("comparison__entity_1_id"),
Expand All @@ -78,7 +79,9 @@ def get_comparisons(self, criteria=None, user_id=None) -> pd.DataFrame:
)
if len(values) > 0:
dtf = pd.DataFrame(values)
return dtf[["user_id", "entity_a", "entity_b", "criteria", "score", "weight"]]
return dtf[
["user_id", "entity_a", "entity_b", "criteria", "score", "score_max", "weight"]
]

return pd.DataFrame(
columns=[
Expand All @@ -87,6 +90,7 @@ def get_comparisons(self, criteria=None, user_id=None) -> pd.DataFrame:
"entity_b",
"criteria",
"score",
"score_max",
"weight",
]
)
Expand Down
1 change: 1 addition & 0 deletions backend/tournesol/serializers/inconsistencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class ScoreInconsistencySerializer(Serializer):
entity_1_rating = serializers.FloatField()
entity_2_rating = serializers.FloatField()
comparison_score = serializers.FloatField()
comparison_score_max = serializers.IntegerField()
expected_comparison_score = serializers.FloatField()


Expand Down
1 change: 1 addition & 0 deletions backend/tournesol/tests/test_api_inconsistencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ def test_response_format(self):
self.assertEqual(results["entity_1_rating"], rating_1_score)
self.assertEqual(results["entity_2_rating"], rating_2_score)
self.assertEqual(results["comparison_score"], comparison_score)
self.assertEqual(results["comparison_score_max"], 10)
self.assertGreater(results["expected_comparison_score"], 0)
self.assertLess(results["expected_comparison_score"], 1)

Expand Down
2 changes: 1 addition & 1 deletion backend/tournesol/tests/test_exports.py
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,7 @@ def test_use_public_export_as_ml_input(self):
self.assertEqual(len(comparisons_df), 1)
self.assertEqual(
list(comparisons_df.columns),
["user_id", "entity_a", "entity_b", "criteria", "score", "weight"],
["user_id", "entity_a", "entity_b", "criteria", "score", "score_max", "weight"],
)

self.assertEqual(len(rating_properties), 2)
Expand Down
48 changes: 29 additions & 19 deletions backend/tournesol/views/inconsistencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
ScoreInconsistenciesFilterSerializer,
ScoreInconsistenciesSerializer,
)
from tournesol.utils.constants import COMPARISON_MAX
from tournesol.views.mixins.poll import PollScopedViewMixin


Expand Down Expand Up @@ -214,6 +213,7 @@ def get(self, request, *args, **kwargs):
"comparison__entity_2__uid",
"criteria",
"score",
"score_max",
)
)

Expand All @@ -229,16 +229,18 @@ def get(self, request, *args, **kwargs):
)
)

response = self._list_inconsistent_comparisons(contributor_comparisons_criteria,
ratings,
filters["inconsistency_threshold"],
poll.criterias_list)
response = self._list_inconsistent_comparisons(
contributor_comparisons_criteria,
ratings,
filters["inconsistency_threshold"],
poll.criterias_list,
)

return Response(ScoreInconsistenciesSerializer(response).data)

@staticmethod
def _list_inconsistent_comparisons( # pylint: disable=too-many-locals
criteria_comparisons: list,
criteria_comparisons: list[dict],
criteria_ratings: list,
threshold: float,
criteria_list: list
Expand All @@ -265,6 +267,7 @@ def _list_inconsistent_comparisons( # pylint: disable=too-many-locals
entity_2 = comparison_criteria["comparison__entity_2__uid"]
criteria = comparison_criteria["criteria"]
comparison_score = comparison_criteria["score"]
comparison_score_max = comparison_criteria["score_max"]

try:
rating_1 = ratings_map[(entity_1, criteria)]
Expand All @@ -278,6 +281,7 @@ def _list_inconsistent_comparisons( # pylint: disable=too-many-locals
rating_1["score"],
rating_2["score"],
comparison_score,
comparison_score_max,
uncertainty,
)

Expand All @@ -296,6 +300,7 @@ def _list_inconsistent_comparisons( # pylint: disable=too-many-locals
"entity_1_rating": rating_1["score"],
"entity_2_rating": rating_2["score"],
"comparison_score": comparison_score,
"comparison_score_max": comparison_score_max,
"expected_comparison_score": ideal_comparison_score,
}
)
Expand All @@ -322,10 +327,13 @@ def _list_inconsistent_comparisons( # pylint: disable=too-many-locals
return response

@staticmethod
def _calculate_inconsistency(entity_1_calculated_rating,
entity_2_calculated_rating,
comparison_score,
uncertainty) -> float:
def _calculate_inconsistency(
entity_1_calculated_rating,
entity_2_calculated_rating,
comparison_score,
comparison_score_max,
uncertainty,
) -> tuple[float, float]:
"""
Calculate the inconsistency between the comparison
criteria score and the general rating of the entity.
Expand Down Expand Up @@ -361,18 +369,20 @@ def _calculate_inconsistency(entity_1_calculated_rating,

base_rating_difference = entity_2_calculated_rating - entity_1_calculated_rating

def inconsistency_calculation(rating_diff):
return abs(comparison_score - COMPARISON_MAX * rating_diff / sqrt(rating_diff**2 + 1))
def inconsistency_calculation(rating_diff) -> float:
return abs(
comparison_score - comparison_score_max * rating_diff / sqrt(rating_diff**2 + 1)
)

min_rating_difference = base_rating_difference - uncertainty
max_rating_difference = base_rating_difference + uncertainty

if comparison_score <= -COMPARISON_MAX:
if comparison_score <= -comparison_score_max:
min_inconsistency = inconsistency_calculation(min_rating_difference)
elif comparison_score >= COMPARISON_MAX:
elif comparison_score >= comparison_score_max:
min_inconsistency = inconsistency_calculation(max_rating_difference)
else:
root = comparison_score / sqrt(COMPARISON_MAX ** 2 - comparison_score ** 2)
root = comparison_score / sqrt(comparison_score_max**2 - comparison_score**2)
if max_rating_difference < root:
# The inconsistency is decreasing with the rating_difference
min_inconsistency = inconsistency_calculation(max_rating_difference)
Expand All @@ -381,12 +391,12 @@ def inconsistency_calculation(rating_diff):
min_inconsistency = inconsistency_calculation(min_rating_difference)
else:
# The root is a possible value for the rating_difference
min_inconsistency = 0
min_inconsistency = 0.0

# Comparison imprecision of 0.5, because comparisons scores are on integers, not floats
inconsistency = max(min_inconsistency - 0.5, 0)

expected_comparison_score = \
COMPARISON_MAX * base_rating_difference / sqrt(base_rating_difference**2 + 1)
expected_comparison_score = (
comparison_score_max * base_rating_difference / sqrt(base_rating_difference**2 + 1)
)

return inconsistency, expected_comparison_score
6 changes: 5 additions & 1 deletion solidago/src/solidago/pipeline/inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def get_comparisons(
* `entity_b`: int or str
* `criteria`: str
* `score`: float
* `score_max`: int
* `weight`: float
"""
raise NotImplementedError
Expand Down Expand Up @@ -103,7 +104,10 @@ def get_comparisons(self, criteria=None, user_id=None) -> pd.DataFrame:
if user_id is not None:
dtf = dtf[dtf.user_id == user_id]
dtf["weight"] = 1
return dtf[["user_id", "entity_a", "entity_b", "criteria", "score", "weight"]]
if "score_max" not in dtf:
# For compatibility with older datasets
dtf["score_max"] = 10
return dtf[["user_id", "entity_a", "entity_b", "criteria", "score", "score_max", "weight"]]

@cached_property
def ratings_properties(self):
Expand Down
14 changes: 11 additions & 3 deletions solidago/src/solidago/pipeline/legacy2023/individual_scores.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,37 @@
from solidago.pipeline import TournesolInput
from .parameters import PipelineParameters


def get_individual_scores(
input: TournesolInput,
criteria: str,
parameters: PipelineParameters,
single_user_id: Optional[int] = None,
) -> pd.DataFrame:
comparisons_df = input.get_comparisons(criteria=criteria, user_id=single_user_id)
# Legacy pipeline assumes all comparisons use the same 'score_max'
score_max_series = comparisons_df.pop("score_max")
if score_max_series.nunique() > 1:
raise RuntimeError(
"Legacy pipeline does not support multiple 'score_max' in comparisons. "
f"Found {dict(score_max_series.value_counts())}"
)

initial_contributor_scores = input.get_individual_scores(
criteria=criteria, user_id=single_user_id
)
if initial_contributor_scores is not None:
initial_contributor_scores = initial_contributor_scores.groupby("user_id")

individual_scores = []
for (user_id, user_comparisons) in comparisons_df.groupby("user_id"):
for user_id, user_comparisons in comparisons_df.groupby("user_id"):
if initial_contributor_scores is None:
initial_entity_scores = None
else:
try:
contributor_score_df = initial_contributor_scores.get_group(user_id)
initial_entity_scores = pd.Series(
data=contributor_score_df.raw_score,
index=contributor_score_df.entity
data=contributor_score_df.raw_score, index=contributor_score_df.entity
)
except KeyError:
initial_entity_scores = None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,18 +216,18 @@ def hessian_diagonal_element(
score_diff = solution[coordinate] - solution[coordinate_bis]
result += self.cumulant_generating_function_second_derivative(score_diff)
return result


class UniformGBT(GeneralizedBradleyTerry):

def __init__(
self,
prior_std_dev: float=7,
comparison_max: float=10,
convergence_error: float=1e-5,
cumulant_generating_function_error: float=1e-5,
self,
prior_std_dev: float = 7,
convergence_error: float = 1e-5,
cumulant_generating_function_error: float = 1e-5,
):
"""
Parameters
----------
initialization: dict[int, float]
Expand All @@ -236,61 +236,47 @@ def __init__(
tolerated error
"""
super().__init__(prior_std_dev, convergence_error)
self.comparison_max = comparison_max
self.cumulant_generating_function_error = cumulant_generating_function_error

@cached_property
def cumulant_generating_function_derivative(self) -> Callable[[npt.NDArray], npt.NDArray]:
""" For.
Parameters
----------
score_diff: float
Score difference
Returns
-------
out: float
"""
tolerance = self.cumulant_generating_function_error

@njit
def f(score_diff: npt.NDArray):
return np.where(
np.abs(score_diff) < tolerance,
score_diff / 3,
1/ np.tanh(score_diff) - 1 / score_diff
1 / np.tanh(score_diff) - 1 / score_diff,
)

return f


def cumulant_generating_function_second_derivative(self, score_diff: float) -> float:
""" We estimate uncertainty by the flatness of the negative log likelihood,
"""We estimate uncertainty by the flatness of the negative log likelihood,
which is directly given by the second derivative of the cumulant generating function.
Parameters
----------
score_diff: float
Score difference
Returns
-------
out: float
"""
if np.abs(score_diff) < self.cumulant_generating_function_error:
return (1/3) - (score_diff**2 / 15)
return 1 - (1 / np.tanh(score_diff)**2) + (1 / score_diff**2)
return (1 / 3) - (score_diff**2 / 15)
return 1 - (1 / np.tanh(score_diff) ** 2) + (1 / score_diff**2)

def to_json(self):
return type(self).__name__, dict(
prior_std_dev=self.prior_std_dev,
comparison_max=self.comparison_max,
convergence_error=self.convergence_error,
cumulant_generating_function_error=self.cumulant_generating_function_error,
)

def __str__(self):
prop_names = ["prior_std_dev", "convergence_error", "comparison_max",
"cumulant_generating_function_error"]
prop_names = ["prior_std_dev", "convergence_error", "cumulant_generating_function_error"]
prop = ", ".join([f"{p}={getattr(self, p)}" for p in prop_names])
return f"{type(self).__name__}({prop})"

0 comments on commit 91efbfb

Please sign in to comment.