I am working on a code library containing data structures and algorithms for solving parameter optimization problems. A parameter optimization problem is a problem of the form: given a vector of parameters P, and a loss function L(P), find the vector components of P such that L(P) is minimized. The implementation of the parameters and the loss function are specific to the problem, but they can all be represented as floating point data types. So far I have an abstract base class called Parameters like so:
public abstract class Parameters
{
/// <summary>
/// Maps the names of the parameters to the values.
/// </summary>
public IImmutableDictionary<string, double> Params { get; }
public Parameters(IImmutableDictionary<string, double> parameters)
{
Params = parameters;
}
/// <summary>
/// Returns a string representation of the parameters.
/// </summary>
public override string ToString()
{
string parametersStr = $"{String.Join('\n', Params.Select(p => $"{p.Key} = {p.Value}"))}";
return parametersStr;
}
}
I also have a definition of a loss function like so:
/// <summary>
/// Calculates a fitness score for the given parameters.
/// <returns>The fitness of the parameters (higher values are better).</returns>
public delegate double FitnessFunction(Parameters parameters);
I can then define the contract for an IParameterOptimizer like so:
/// <summary>
/// Represents an object capable of optimizing parameters.
/// </summary>
public interface IParameterOptimizer
{
/// <summary>
/// Optimizes parameters using the given fitness function.
/// </summary>
/// <returns>A list of 2-tuples of parameters and fitness scores.</returns>
public Task<IList<(Parameters, double)>> OptimizeParameters(FitnessFunction fitnessFunction);
}
So an object implementing IParameterOptimizer can return an ordered list of tuples mapping parameters to loss values, with more fit values appearing later in the list.
Now consider as a concrete example, a SalesParameters class, and a MaximizeProfit fitness function like so:
public class SalesParameters : Parameters
{
public int ManufacturedUnits { get; }
public double Price { get; }
public double MarketingCost { get; }
public SalesParameters(int units, double price, double marketing) :
base(new Dictionary<string, double>() {
{ "ManufacturedUnits", (double)ManufacturedUnits },
{ "Price", Price },
{ "MarketingCost", MarketingCost },
}.ToImmutableDictionary())
{
ManufacturedUnits = units;
Price = price;
MarketingCost = marketing;
}
}
public double MaximizeProfit(SalesParameters p)
{
return (p.ManufacturedUnits * p.Price) - p.MarketingCost;
}
The consumer of my library could then call one of my many implementations of IParameterOptimizer to find SalesParameters that will maximize their profits.
My question is - is this a sensible design? I find the Parameters dictionary mapping parameter names to values to be necessary to allow the optimization algorithms to know the type of operations that can be performed on the parameters (addition, subtraction, etc.). But it seems a little confusing and cumbersome for the user of the library to need to subclass Parameters and set the Params dictionary. Still, I can't think of a better solution as I would like to avoid using reflection.
IList<(Parameters, double)
? In optimization, the goal is usually to find one (optimal or suboptimal)Parameters
instance - what is your idea of generating many? Different local optima? A sequence of intermediate optima? Please clarify.IImmutableDictionary
. For the current case, it looks overcomplicated - a simple, specificToString()
implementation in each derivation ofParameters
would IMHO be simpler and sufficient. But maybe you left something out where theIImmutableDictionary
becomes more useful?