Files
temperature-based-fertility…/code/new_realtime/utils/dataset_creation.py
T
Alex Blank 50cf43b9fe added code
2025-05-19 11:11:04 +02:00

236 lines
9.2 KiB
Python

import os
import lmdb
import numpy as np
import pandas as pd
import torch
from sklearn.preprocessing import StandardScaler, MinMaxScaler
import joblib
from utils.lmdb_utils import load_from_lmdb, get_lmdb_keys
def get_features(cycle: dict,
feature_config: dict) -> dict:
"""
Computes the feature for a given cycle defined by the feature_config
:param cycle: cycle data as dictionary
:param feature_config: feature config as dictionary
:return: dict with feature according feature config
"""
features = {}
feature_sets = feature_config["feature_sets"]
for feature_set in feature_sets:
for feature_def in feature_config[feature_set]:
if feature_set not in features:
features[feature_set] = {}
# skip features that are marked as ignored
if "ignored_features" in feature_config and feature_def["name"] in feature_config["ignored_features"]:
continue
feature_return = feature_def["fn"](cycle=cycle)
if isinstance(feature_return, dict):
if len(feature_return) > 1:
for feature_name, feature in feature_return.items():
features[feature_set][f"{feature_def['name']}_{feature_name}"] = feature
else:
features[feature_set][feature_def["name"]] = list(feature_return.values())[0]
else:
features[feature_set][feature_def["name"]] = feature_return
# check, if all features have same length
if feature_set in features and len(features[feature_set]) > 0 and isinstance(
list(features[feature_set].values())[0], np.ndarray):
feature_lengths = [len(x) for x in features[feature_set].values()]
if len(set(feature_lengths)) > 1:
raise ValueError(f"Feature set {feature_set} has features of different lengths: {feature_lengths}")
return features
def save_scalers(scalers: dict,
scaler_dir: str):
if not os.path.exists(scaler_dir):
os.makedirs(scaler_dir)
for feature_type in scalers:
for feature_name in scalers[feature_type]:
joblib.dump(scalers[feature_type][feature_name],
f"{scaler_dir}/{feature_name}.pkl")
def load_scalers(scaler_dir: str) -> dict:
scalers = dict()
for scaler_file in os.listdir(scaler_dir):
if scaler_file.endswith(".pkl"):
feature_name = scaler_file.replace(".pkl", "")
scalers[feature_name] = joblib.load(f"{scaler_dir}/{scaler_file}")
return scalers
def get_scalers_for_model(model_configuration: dict):
"""
Get the scalers for the features of a model configuration
Args:
model_configuration: configuration of the model
Returns:
scalers: scalers for the model
"""
scaler_dir = os.path.join(model_configuration["feature_config"]["dataset_dir"], "scalers")
scalers = load_scalers(scaler_dir)
return scalers
def get_feature_values(feature_type: str | None,
feature_name: str,
env: lmdb.Environment,
keys: list = None) -> list:
"""
Get the values of a feature from the lmdb dataset for all keys
:param feature_type: type of feature, can be None, then the first feature with the given name will be used
:param feature_name: feature name to extract
:param env: lmdb environment
:param keys: keys to extract the feature from, if None, all keys will be used / fetched from the database
:return: list with feature values
"""
if keys is None:
keys = get_lmdb_keys(env)
feature_values = []
for key in keys:
data = load_from_lmdb(env, key)
if feature_type is None:
for feature_type, feature_data in data.items():
if feature_name in feature_data:
feature_values.append(feature_data[feature_name])
break
else:
for current_feature_name, feature_data in data[feature_type].items():
# catch sub features that have been prefixed with the feature name
if current_feature_name.startswith(feature_name):
feature_values.append(feature_data)
break
return feature_values
def train_scalers(feature_type: str,
feature_name: str,
scaler_type,
sample,
env) -> dict:
scalers = dict()
all_features = sample[feature_type]
individual_feature_names = list()
for individual_feature_name in all_features:
if individual_feature_name.startswith(feature_name) and not individual_feature_name.endswith("_scaled"):
individual_feature_names.append(individual_feature_name)
for individual_feature_name in individual_feature_names:
feature_values = get_feature_values(feature_type, individual_feature_name, env)
if len(feature_values) == 0:
raise ValueError(f"No feature values found for feature {individual_feature_name}")
if isinstance(feature_values[0], list) or isinstance(feature_values[0], np.ndarray):
features_reshaped = np.concatenate(feature_values).reshape(-1, 1)
else:
features_reshaped = np.array(feature_values).reshape(-1, 1)
del feature_values
if scaler_type is not None:
scaler = scaler_type()
scaler.fit(features_reshaped)
else:
scaler = None
scalers[individual_feature_name] = scaler
return scalers
def scale_item(data: dict,
scalers: dict):
data_format = dict()
for feature_set in data:
if feature_set not in data_format:
data_format[feature_set] = dict()
for feature_name in data[feature_set]:
if feature_name.endswith("_scaled"):
continue
data_format[feature_set][feature_name] = data[feature_set][feature_name]
for feature_set in data_format:
for feature_name in data_format[feature_set]:
feature_values = data[feature_set][feature_name]
if scalers[feature_name] is not None:
if isinstance(feature_values, list) or isinstance(feature_values, np.ndarray):
scaled_feature_values = scalers[feature_name].transform(
feature_values.reshape(-1, 1)).flatten()
else:
scaled_feature_values = scalers[feature_name].transform(
np.array(feature_values).reshape(-1, 1)).flatten()
else:
scaled_feature_values = feature_values
data[feature_set][f"{feature_name}_scaled"] = scaled_feature_values
return data
def inverse_scale_feature(input_feature: np.ndarray | int | float | torch.Tensor,
feature_names: np.ndarray | list | str,
scalers: dict) -> np.ndarray:
"""
Inverse scales the input features, can handle single and multi feature input
Args:
input_feature: input feature as a numpy array
feature_names: names of input features as reference for scalers
scalers: dict of feature scalers
Returns:
scaled input features as numpy array
"""
if isinstance(feature_names, str):
feature_names = [feature_names]
if isinstance(input_feature, np.ndarray) or isinstance(input_feature, torch.Tensor):
input_dim = input_feature.shape[1] if len(input_feature.shape) > 1 else 1
elif isinstance(input_feature, float) or isinstance(input_feature, int):
# handle case, where input is scalar
input_scaled = scalers[feature_names[0]].inverse_transform([[input_feature]])[0][0]
return input_scaled
elif isinstance(input_feature, list):
input_feature = np.array(input_feature)
input_dim = input_feature.shape[1] if len(input_feature.shape) > 1 else 1
else:
raise ValueError(f"Unsupported input type: {type(input_feature)}")
# reshape, if input is one dimensional
if input_dim == 1:
input_feature = input_feature.reshape(-1, 1)
input_scaled = input_feature.copy()
for i in range(input_dim):
input_scaled[:, i] = scalers[feature_names[i]].inverse_transform(np.array([input_scaled[:, i]]))
return input_scaled
def combine_features(cycles: list[dict], feature_config: dict) -> dict:
combined_features = dict()
for feature_set in feature_config["feature_sets"]:
combined_features[feature_set] = dict()
if feature_set not in cycles[0]:
continue
features_in_set = cycles[0][feature_set].keys()
for feature in features_in_set:
feature_values = [cycles[i][feature_set][feature] for i in range(len(cycles))]
if isinstance(feature_values[0], dict):
for key in feature_values[0].keys():
feature_array = np.concatenate([feature_values[i][key] for i in range(len(feature_values))])
combined_features[feature_set][key] = feature_array
else:
feature_array = np.concatenate(feature_values)
combined_features[feature_set][feature] = feature_array
return combined_features