Skip to content

New Examples and Classifier Changes #66

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
80 changes: 80 additions & 0 deletions examples/football_project/football_classes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import enum
import math

# This graph will determine which free agents a team can sign based on their available cap space
# and which players they should sign based on their needs and other player factors

class LeagueRules(enum.Enum): # Fixed the base class
POSTION_LIST = ["QB", "RB", "WR", "TE"]
MIN_PLAYER_AT_POSITION = {
"QB": 1,
"RB": 1,
"WR": 2,
"TE": 1,
}
SALARY_CAP = 2000000

class FootballPlayer:
def __init__(self, name, position, expected_salary, madden_rating, available=True):
self.name = name
self.position = position
self.expected_salary = expected_salary
self.madden_rating = madden_rating
self.available = available
self.player_value = (self.madden_rating / self.expected_salary) * 100000

def get_availability(self):
return self.available

class FootballTeam:
def __init__(self, team_name):
self.team_name = team_name
self.roster = {}
for position in LeagueRules.POSTION_LIST.value:
self.roster[position] = []
self.cap_space = LeagueRules.SALARY_CAP.value

def can_sign_player(self, player):
if player.expected_salary <= self.cap_space and player.get_availability():
return True
return False

# A team wants to sign a player if the player can provide more value than the lowest value player at thier position
def check_if_team_wants_players(self, player):
lowest_rostered_value_at_pos = self.get_lowest_value_at_position(player.position)
if player.player_value > lowest_rostered_value_at_pos:
# print(f"Team {self.team_name} wants player {player.name} with value {player.player_value}. Lowest value is {lowest_rostered_value_at_pos}.")
return True
return False

def get_lowest_value_at_position(self, position):
min_value = math.inf
for player in self.roster[position]:
if player.player_value < min_value:
min_value = player.player_value

return min_value

def sign_player(self, player):
if self.can_sign_player(player):
#self.roster.append(player)
self.roster[player.position].append(player)
self.cap_space -= player.expected_salary
player.available = False
return True
return False

def get_available_players(self):
return [player for player in self.roster if player.get_availability()]

def get_cap_space(self):
return self.cap_space

def get_num_player_needed(self, position):
current_qbs = [player for player in self.roster[position]]
num_players_needed = LeagueRules.MIN_PLAYER_AT_POSITION.value[position] - len(current_qbs)
return num_players_needed

def get_remaining_cap_space(self):
total_salary = sum(player.expected_salary for player in self.roster)
return self.cap_space - total_salary
221 changes: 221 additions & 0 deletions examples/football_project/player_signings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
import numba
import pyreason as pr
import networkx as nx
import football_classes as fc
import random
from pprint import pprint
import matplotlib.pyplot as plt # type: ignore

# Generates random names
import names

def generate_random_player_at_position(position):
full_name = names.get_full_name()
name_with_underscore = full_name.replace(" ", "_")
return fc.FootballPlayer(
name=name_with_underscore,
position=position,
expected_salary=random.randint(100000, 500000),
madden_rating=random.randint(60, 99),
)

def add_rostered_fact(team, player):
fact_string = f"rostered({team.team_name} , {player.name})"
pr.add_fact(pr.Fact(fact_string, 'rostered_fact', 0, 1))

def add_not_rostered_fact(team, player):
fact_string = f"~rostered({team.team_name} , {player.name})"
pr.add_fact(pr.Fact(fact_string, 'rostered_fact', 0, 1))

def try_to_sign_player(team, player):
if team.can_sign_player(player):
team.sign_player(player)
add_rostered_fact(team, player)
print(f"{team.team_name} signed {player.name} for ${player.expected_salary}")
else:
add_not_rostered_fact(team, player)

# Operating at timestep 1
def add_interested_fact(team, player):
fact_string = f"interested({team.team_name} , {player.name})"
pr.add_fact(pr.Fact(fact_string, 'interested_fact', 0, 1))

def add_not_interested_fact(team, player):
fact_string = f"~interested({team.team_name} , {player.name})"
pr.add_fact(pr.Fact(fact_string, 'interested_fact', 0, 1))

def determine_team_interest():
for team in team_list:
for player in all_player_list:
if team.check_if_team_wants_players(player):
add_interested_fact(team, player)
else:
add_not_interested_fact(team, player)

def simulate_draft():
# print("Available Players:")
# for player in all_players:
# print(f"Name: {player.name}, Position: {player.position}, "
# f"Expected Salary: ${player.expected_salary:,}, Madden Rating: {player.madden_rating}")
for team in team_list:
# Loop through the available players and try to sign them
for player in qb_list:
if team.get_num_player_needed("QB") > 0:
try_to_sign_player(team, player)
else:
add_not_rostered_fact(team, player)
for player in rb_list:
if team.get_num_player_needed("RB") > 0:
try_to_sign_player(team, player)
else:
add_not_rostered_fact(team, player)
for player in wr_list:
if team.get_num_player_needed("WR") > 0:
try_to_sign_player(team, player)
else:
add_not_rostered_fact(team, player)
for player in te_list:
if team.get_num_player_needed("TE") > 0:
try_to_sign_player(team, player)
else:
add_not_rostered_fact(team, player)

# Print the final rosters and cap space for each team
print("\nFinal Rosters and Cap Space:")
for team in team_list:
print(f"\n{team.team_name} Roster:")
for position, players in team.roster.items():
for player in players:
print(f" Name: {player.name}, Position: {player.position}, Value: {player.player_value}")
print(f"Cap Space Remaining: ${team.get_cap_space():,}")


qb_list = []
rb_list = []
wr_list = []
te_list = []
all_player_list = []

for val in range(20):
qb_list.append(generate_random_player_at_position("QB"))
rb_list.append(generate_random_player_at_position("RB"))
wr_list.append(generate_random_player_at_position("WR"))
te_list.append(generate_random_player_at_position("TE"))

all_player_list.extend(qb_list)
all_player_list.extend(rb_list)
all_player_list.extend(wr_list)
all_player_list.extend(te_list)

team_list = []
team_list.append(fc.FootballTeam("Pittsburgh_Steelers"))
team_list.append(fc.FootballTeam("Baltamore_Ravens"))
team_list.append(fc.FootballTeam("Dallas_Cowboys"))
team_list.append(fc.FootballTeam("Chicago_Bears"))



# Create a Directed graph
g = nx.DiGraph()

for player in all_player_list:
g.add_node(player.name, name=player.name, position=player.position, expected_salary=player.expected_salary, madden_rating=player.madden_rating, player_value=player.player_value)
for team in team_list:
g.add_node(team.team_name)

# Make a picture of the graph
# nx.draw(g, with_labels=True)
# plt.savefig("football_graph.png")
# plt.show()

pr.settings.verbose = True # Print info to screen
pr.settings.atom_trace = True # Print the trace of the atoms

# Load all the files into pyreason
pr.load_graph(g)


@numba.njit
def demanded_player_annotation_fn(annotations, weights):
"""
Calculate the value of a player based on their attributes.
"""
# Calculate the value based on the weights
# print("Annotations: ", annotations)
# print("Annotations[0]: ", annotations[0])
num_interested_teams = len(annotations[0])
print("Interested Teams: ", num_interested_teams)
if num_interested_teams > 3:
upper_bound = 1
lower_bound = 1
else:
upper_bound = 0
lower_bound = 0
return lower_bound, upper_bound

pr.add_annotation_function(demanded_player_annotation_fn)

# These functions add facts about the players based on how they are drafted and the teams that want them
simulate_draft()
determine_team_interest()

# Rule to check if a player is a free agent
pr.add_rule(pr.Rule('rostered_player(x) <-1 rostered(y, x)', 'rostered_player_rule'))
pr.add_rule(pr.Rule('free_agent(x) <-1 ~rostered(y,x)', 'free_agent_rule'))
pr.add_rule(pr.Rule('make_offer(y,x) <-1 free_agent(x), interested(y,x)', 'make_offer_rule'))
pr.add_rule(pr.Rule('demanded_player(x) : demanded_player_annotation_fn <-1 make_offer(a,x)', 'demanded_player(x)'))
pr.add_rule(pr.Rule('highly_demanded_player(x) <-1 demanded_player(x): [1,1]', 'highly_demanded_player_rule'))

#pr.add_rule(pr.Rule('team_interested_in_player(x, y) <-1 '))

interpretation = pr.reason(timesteps=4)
interpretation_dict = interpretation.get_dict()
# print("Interpretation Dictionary:")
# pprint(interpretation_dict)

# Display the changes in the interpretation for each timestep
print("========================== Rostered Players ==========================")
rostered_player_df = pr.filter_and_sort_nodes(interpretation, ['rostered_player'])
for t, df in enumerate(rostered_player_df):
print(f'TIMESTEP - {t}')
print(df)
print()

print("========================== Free Agents ==========================")
free_agent_df = pr.filter_and_sort_nodes(interpretation, ['free_agent'])
for t, df in enumerate(free_agent_df):
print(f'TIMESTEP - {t}')
print(df)
print()


print("========================== Team offers ==========================")
team_offer_df = pr.filter_and_sort_edges(interpretation, ['make_offer'])
for t, df in enumerate(team_offer_df):
print(f'TIMESTEP - {t}')
print(df)
print()

print("========================== Demanded Player ==========================")
team_offer_df = pr.filter_and_sort_nodes(interpretation, ['demanded_player'])
for t, df in enumerate(team_offer_df):
print(f'TIMESTEP - {t}')
print(df)
print()

print("========================== Hot Commodities ==========================")
hot_commodity_df = pr.filter_and_sort_nodes(interpretation, ['highly_demanded_player'])
for t, df in enumerate(hot_commodity_df):
print(f'TIMESTEP - {t}')
print(df)
for index, row in df.iterrows():
node_name = row['component']
player_value = g.nodes[node_name]["player_value"]
madden_rating = g.nodes[node_name]["madden_rating"]
expected_salary = g.nodes[node_name]["expected_salary"]
print(f"Node: {node_name}, Salary: {expected_salary}, Madden Rating: {madden_rating}, Value: {player_value}")


# Iterate over the "component" column in hot_commodity_df and print everything we know about the corresponding node

pr.save_rule_trace(interpretation)
Binary file added examples/image_classifier_two/images/fish_1.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/image_classifier_two/images/fish_2.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/image_classifier_two/images/shark_1.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/image_classifier_two/images/shark_2.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/image_classifier_two/images/shark_3.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
47 changes: 44 additions & 3 deletions pyreason/scripts/learning/classification/classifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,60 @@ def get_class_facts(self, t1: int, t2: int) -> List[Fact]:
fact = Fact(f'{c}({self.identifier})', name=f'{self.identifier}-{c}-fact', start_time=t1, end_time=t2)
facts.append(fact)
return facts


def forward(self, x, t1: int = 0, t2: int = 0) -> Tuple[torch.Tensor, torch.Tensor, List[Fact]]:
# A user may want to restrict the number of classe so that the classifier only returns facts for a subset of classes.
# This is useful for large models like CLIP, where the model has 4000 classes.
# We will set the new possible classes to be limited to the class names given to the classifier.
def update_classes_and_probs_for_filter(self, probabilities) -> torch.Tensor:
# Get the index-to-label mapping from the model config
id2label = self.model.config.id2label

# Get the indices of the allowed labels, stripping everything after the comma
allowed_indices = [
i for i, label in id2label.items()
if label.split(",")[0].strip().lower() in [name.lower() for name in self.class_names]
]

# Normalize the probabilities based only on the allowed classes
filtered_probs = torch.zeros_like(probabilities)
filtered_probs[allowed_indices] = probabilities[allowed_indices]
filtered_probs = filtered_probs / filtered_probs.sum()

# Because we are filtering the probabilities, we need to update the class labels to only include the allowed classes.
# We also update the class names so they are ordered by the probabilities.
top_labels = []
top_probs, top_indices = filtered_probs.topk(len(self.class_names))
for prob, idx in zip(top_probs, top_indices):
label = id2label[idx.item()].split(",")[0]
print(f"{label}: {prob.item():.4f}")
top_labels.append(label)
self.class_names = top_labels
return top_probs

def forward(self, x, t1: int = 0, t2: int = 0, limit_classification_output_classes = False) -> Tuple[torch.Tensor, torch.Tensor, List[Fact]]:
"""
Forward pass of the model
:param x: Input tensor
:param t1: Start time for the facts
:param t2: End time for the facts
:return: Output tensor
"""
output = self.model(x)

# Convert logits to probabilities assuming a multi-class classification.
try:
output = self.model(x)
except AttributeError as e:
print(f"Error during model forward pass: {e}")
try:
output = self.model(**x).logits
except Exception as e:
print(f"Error during model forward pass with kwargs: {e}")

probabilities = F.softmax(output, dim=1).squeeze()

if limit_classification_output_classes:
probabilities = self.update_classes_and_probs_for_filter(probabilities)

opts = self.interface_options

# Prepare threshold tensor.
Expand Down
Loading
Loading