in simulation/decai/simulation/contract/incentive/prediction_market.py [0:0]
def process_contribution(self):
"""
Reward Phase:
Process the next data contribution.
"""
assert self.remaining_bounty_rounds > 0, "The market has ended."
if self.state == MarketPhase.REWARD_RESTART:
self._next_data_index = 0
self._logger.debug("Remaining bounty rounds: %s", self.remaining_bounty_rounds)
self._scores = defaultdict(float)
if self._reset_model_during_reward_phase:
# The paper implies that we should not retrain the model and instead only train once.
# The problem there is that a contributor is affected by bad contributions
# between them and the last counted contribution after bad contributions are filtered out.
self.model.reset_model()
if self.prev_acc is None:
# XXX This evaluation can be expensive and likely won't work in Ethereum.
# We need to find a more efficient way to do this or let a contributor proved they did it.
self.prev_acc = self.model.evaluate(self.test_data, self.test_labels)
self.original_acc = self.prev_acc
self._logger.debug("Accuracy: %0.2f%%", self.prev_acc * 100)
elif not self._reset_model_during_reward_phase:
# When calculating rewards, the score, the same accuracy for the initial model should be used.
self.prev_acc = self.original_acc
self._num_market_contributions: Dict[Address, int] = Counter()
self._worst_contribution: Optional[_Contribution] = None
self._worst_contributor: Optional[Address] = None
self._min_score = math.inf
self.state = MarketPhase.REWARD
else:
assert self.state == MarketPhase.REWARD
contribution = self._market_data[self._next_data_index]
self._num_market_contributions[contribution.contributor_address] += 1
self.model.update(contribution.data, contribution.classification)
if not self._reset_model_during_reward_phase and contribution.accuracy is None:
# XXX Potentially expensive gas cost.
contribution.accuracy = self.model.evaluate(self.test_data, self.test_labels)
self._next_data_index += 1
iterated_through_all_contributions = self._next_data_index >= self.get_num_contributions_in_market()
if iterated_through_all_contributions \
or not self._group_contributions \
or self._market_data[self._next_data_index].contributor_address != contribution.contributor_address:
# Need to compute score.
if self._reset_model_during_reward_phase:
# XXX Potentially expensive gas cost.
acc = self.model.evaluate(self.test_data, self.test_labels)
else:
acc = contribution.accuracy
score_change = acc - self.prev_acc
if self._group_contributions:
new_score = self._scores[contribution.contributor_address] = \
self._scores[contribution.contributor_address] + score_change
else:
new_score = contribution.score = score_change
if new_score < self._min_score:
self._min_score = new_score
if self._group_contributions:
self._worst_contributor = contribution.contributor_address
else:
self._worst_contribution = contribution
elif self._group_contributions and self._worst_contributor == contribution.contributor_address:
# Their score increased, they might not be the worst anymore.
# Optimize: use a heap.
self._worst_contributor, self._min_score = min(self._scores.items(), key=lambda x: x[1])
self.prev_acc = acc
if iterated_through_all_contributions:
# Find min score and remove that address from the list.
self._logger.debug("Minimum score: %.2f", self._min_score)
if self._min_score < 0:
if self._group_contributions:
num_rounds = self._market_balances[self._worst_contributor] / -self._min_score
else:
num_rounds = self._worst_contribution.balance / -self._min_score
if num_rounds > self.remaining_bounty_rounds:
num_rounds = self.remaining_bounty_rounds
self._logger.debug("Will simulate %.2f rounds.", num_rounds)
self.remaining_bounty_rounds -= num_rounds
if self.remaining_bounty_rounds == 0:
self._end_reward_phase(num_rounds)
else:
if self._group_contributions:
participants_to_remove = set()
for participant, score in self._scores.items():
self._logger.debug("Score for \"%s\": %.2f", participant, score)
self._market_balances[participant] += score * num_rounds
if self._market_balances[participant] < self._num_market_contributions[participant]:
# They don't have enough left to stake next time.
participants_to_remove.add(participant)
self._market_data: List[_Contribution] = list(
filter(lambda c: c.contributor_address not in participants_to_remove,
self._market_data))
else:
for contribution in self._market_data:
contribution.balance += contribution.score * num_rounds
if contribution.balance < 1:
# Contribution is going to get kicked out.
self._market_balances[contribution.contributor_address] += contribution.balance
self._market_data: List[_Contribution] = \
list(filter(lambda c: c.balance >= 1, self._market_data))
if self.get_num_contributions_in_market() == 0:
self.state = MarketPhase.REWARD_COLLECT
self.remaining_bounty_rounds = 0
self.reward_phase_end_time_s = self._time()
else:
self.state = MarketPhase.REWARD_RESTART
else:
num_rounds = self.remaining_bounty_rounds
self.remaining_bounty_rounds = 0
self._end_reward_phase(num_rounds)