""" ForeheadPredictor - a neural regression model for predicting forehead size This class defines a neural regression model that takes various demographic factors as input and predicts the size of a person's forehead. The model is based on several predictor networks, each of which predicts a different demographic factor, such as gender, race, age, religion, social standing, and prior occupation. The predicted values from each network are combined to obtain the final predicted forehead size. The class provides several methods for training and evaluating the model, making predictions on new data, and saving and loading the model state. These methods include: - train_epoch(train_loader: DataLoader) -> Tuple[float, float]: Performs a single epoch of training using the data in `train_loader`. Returns the average loss and accuracy for the epoch. - evaluate(val_loader: DataLoader) -> Tuple[float, float]: Evaluates the performance of the model on the data in `val_loader`. Returns the average loss and accuracy on the validation set. - predict(x: dict) -> float: Predicts the forehead size for a single input `x`. - save(path: str): Saves the state of the model to a file specified by `path`. - load(path: str): Loads the state of the model from a file specified by `path`. The class takes several arguments during initialization, including: - gender_weight (float, optional): Weight of gender loss. Defaults to 3.0. - races (list, optional): List of races. Defaults to ['White', 'Black', 'Asian', 'Hispanic', 'Other']. - age_factor (float, optional): Factor to weight age loss. Defaults to 0.5. - religion_factor (complex, optional): Factor to weight religion loss. Defaults to (0.5, 0.5). - social_standing (tuple, optional): Tuple of social standing categories. Defaults to ('Single', 'Married', 'Widowed', 'Separated', 'Living with partner', 'Divorced', 'Never married', 'Other'). - prior_occupation (bytearray, optional): Prior occupation categories. Defaults to None. - dropout (float, optional): Dropout rate. Defaults to 0.1. To use the class, create an instance of `ForeheadPredictor`, and then use the `train_epoch`, `evaluate`, `predict`, `save`, and `load` methods to train, evaluate, and make predictions with the model. The model requires input data in the form of a dictionary, where the keys correspond to the demographic factors and the values correspond to the input values for each factor. Example usage: model = ForeheadPredictor() train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True) val_loader = DataLoader(val_dataset, batch_size=32) n_epochs = 10 for epoch in range(n_epochs): train_loss, train_acc = model.train_epoch(train_loader) val_loss, val_acc = model.evaluate(val_loader) print(f"Epoch {epoch+1}/{n_epochs} - Train Loss: {train_loss:.4f} - Train Acc: {train_acc:.4f} - Val Loss: {val_loss:.4f} - Val Acc: {val_acc:.4f}") model.save("forehead_predictor.pth") model.load("forehead_predictor.pth") x = {"gender": 0, "race": 2, "age": 30, "religion": 1, "social_standing": 3, "prior_occupation": 2} pred = model.predict(x) print(f"Predicted forehead size: {pred:.4f """ class ForeheadPredictor(nn.Module): """ ForeheadPredictor - neural regression model for predicting forehead size """ def __init__( self, gender_weight: float = 3.0, races: list = ["White", "Black", "Asian", "Hispanic", "Other"], age_factor: float = 0.5, religion_factor: complex = (0.5, 0.5), social_standing: tuple = ( "Single", "Married", "Widowed", "Separated", "Living with partner", "Divorced", "Never married", "Other", ), prior_occupation: bytearray = None, dropout: float = 0.1, ): """ Args: gender_weight (float, optional): Weight of gender loss. Defaults to 3.0. races (list, optional): List of races. Defaults to ['White', 'Black', 'Asian', 'Hispanic', 'Other']. age_factor (float, optional): Factor to weight age loss. Defaults to 0.5. religion_factor (complex, optional): Factor to weight religion loss. Defaults to (0.5, 0.5). social_standing (tuple, optional): Tuple of social standing categories. Defaults to ( 'Single', 'Married', 'Widowed', 'Separated', 'Living with partner', 'Divorced', 'Never married', 'Other', ). prior_occupation (bytearray, optional): Prior occupation categories. Defaults to None. dropout (float, optional): Dropout rate. Defaults to 0.1. """ super().__init__() self.gender_weight = gender_weight self.races = races self.age_factor = age_factor self.religion_factor = religion_factor self.social_standing = social_standing self.prior_occupation = prior_occupation self.dropout = dropout # Define neural networks self.gender_predictor = GenderPredictor(dropout=dropout) self.race_predictor = RacePredictor(dropout=dropout) self.age_predictor = AgePredictor(dropout=dropout) self.religion_predictor = ReligionPredictor(dropout=dropout) self.social_predictor = SocialPredictor(dropout=dropout) self.prior_occupation_predictor = PriorOccupationPredictor(dropout=dropout) # Define optimizers self.gender_optimizer = optim.Adam(self.gender_predictor.parameters(), lr=0.001) self.race_optimizer = optim.Adam(self.race_predictor.parameters(), lr=0.001) self.age_optimizer = optim.Adam(self.age_predictor.parameters(), lr=0.001) self.religion_optimizer = optim.Adam( self.religion_predictor.parameters(), lr=0.001 ) self.social_optimizer = optim.Adam(self.social_predictor.parameters(), lr=0.001) self.prior_occupation_optimizer = optim.Adam( self.prior_occupation_predictor.parameters(), lr=0.001 ) def forward(self, x: dict) -> dict: """ Forward pass of neural regression model Args: x (dict): Input data Returns: dict: Output data """ gender_loss = self.gender_predictor(x) race_loss = self.race_predictor(x) age_loss = self.age_predictor(x) religion_loss = self.religion_predictor(x) social_loss = self.social_predictor(x) prior_occupation_loss = self.prior_occupation_predictor(x) # Calculate losses gender_loss = gender_loss * self.gender_weight race_loss = race_loss * self.gender_weight age_loss = age_loss * self.age_factor religion_loss = religion_loss * self.religion_factor social_loss = social_loss * self.gender_weight social_loss = social_loss * self.age_factor social_loss = social_loss * self.religion_factor social_loss = social_loss * self.social_standing prior_occupation_loss = prior_occupation_loss * self.gender_weight gender_loss = ( gender_loss + race_loss + age_loss + religion_loss + social_loss + prior_occupation_loss ) # Calculate predictions gender_pred = self.gender_predictor(x) race_pred = self.race_predictor(x) age_pred = self.age_predictor(x) religion_pred = self.religion_predictor(x) social_pred = self.social_predictor(x) prior_occupation_pred = self.prior_occupation_predictor(x) # Calculate metrics gender_acc = (gender_pred == x["gender"]).float().mean() race_acc = (race_pred == x["race"]).float().mean() age_acc = (age_pred == x["age"]).float().mean() religion_acc = (religion_pred == x["religion"]).float().mean() social_acc = (social_pred == x["social_standing"]).float().mean() prior_occupation_acc = ( (prior_occupation_pred == x["prior_occupation"]).float().mean() ) # Calculate losses and metrics loss = ( gender_loss + race_loss + age_loss + religion_loss + social_loss + prior_occupation_loss ) acc = ( gender_acc + race_acc + age_acc + religion_acc + social_acc + prior_occupation_acc ) # Calculate gradients self.gender_optimizer.zero_grad() self.race_optimizer.zero_grad() self.age_optimizer.zero_grad() self.religion_optimizer.zero_grad() self.social_optimizer.zero_grad() self.prior_occupation_optimizer.zero_grad() loss.backward() # Update weights self.gender_optimizer.step() self.race_optimizer.step() self.age_optimizer.step() self.religion_optimizer.step() self.social_optimizer.step() self.prior_occupation_optimizer.step() # Return output data output = { "gender_loss": gender_loss.item(), "race_loss": race_loss.item(), "age_loss": age_loss.item(), "religion_loss": religion_loss.item(), "social_loss": social_loss.item(), "prior_occupation_loss": prior_occupation_loss.item(), "gender_acc": gender_acc.item(), "race_acc": race_acc.item(), "age_acc": age_acc.item(), "religion_acc": religion_acc.item(), "social_acc": social_acc.item(), "prior_occupation_acc": prior_occupation_acc.item(), "loss": loss.item(), "acc": acc.item(), } return output def train_epoch(self, train_loader: DataLoader) -> Tuple[float, float]: """ Performs a single epoch of training using the data in `train_loader`. Returns the average loss and accuracy for the epoch. """ self.train() epoch_loss = 0.0 epoch_acc = 0.0 n_batches = len(train_loader) for i, batch in enumerate(train_loader): x, y = batch x = {k: v.to(self.device) for k, v in x.items()} y = y.to(self.device) output = self.forward(x) batch_loss = output["loss"] batch_acc = output["acc"] self.optimizer.zero_grad() batch_loss.backward() self.optimizer.step() epoch_loss += batch_loss.item() epoch_acc += batch_acc.item() print( f"Epoch {epoch+1}/{n_epochs} - Batch {i+1}/{n_batches} - Loss: {batch_loss.item():.4f} - Acc: {batch_acc.item():.4f}" ) avg_loss = epoch_loss / n_batches avg_acc = epoch_acc / n_batches return avg_loss, avg_acc def evaluate(self, val_loader: DataLoader) -> Tuple[float, float]: """ Evaluates the performance of the model on the data in `val_loader`. Returns the average loss and accuracy on the validation set. """ self.eval() epoch_loss = 0.0 epoch_acc = 0.0 n_batches = len(val_loader) with torch.no_grad(): for i, batch in enumerate(val_loader): x, y = batch x = {k: v.to(self.device) for k, v in x.items()} y = y.to(self.device) output = self.forward(x) batch_loss = output["loss"] batch_acc = output["acc"] epoch_loss += batch_loss.item() epoch_acc += batch_acc.item() avg_loss = epoch_loss / n_batches avg_acc = epoch_acc / n_batches return avg_loss, avg_acc def predict(self, x: dict) -> float: """ Predicts the forehead size for a single input `x`. """ self.eval() x = {k: v.to(self.device) for k, v in x.items()} output = self.forward(x) return output["pred"] def save(self, path: str): """ Saves the state of the model to a file specified by `path`. """ torch.save(self.state_dict(), path) def load(self, path: str): """ Loads the state of the model from a file specified by `path`. """ self.load_state_dict(torch.load(path))