Skip to content

Commit e10224b

Browse files
ooplesclaude
andauthored
fix(us-if-003): implement ifullmodel interfaces in predictionmodelresult (#245)
* fix(us-if-003): implement ifullmodel interfaces in predictionmodelresult - add constructor accepting IFullModel parameter - implement Train method throwing InvalidOperationException - implement GetParameters, SetParameters, ParameterCount methods - implement WithParameters method - implement GetActiveFeatureIndices, SetActiveFeatureIndices methods - implement IsFeatureUsed method - implement GetFeatureImportance method - implement DeepCopy and Clone methods - all methods delegate to underlying Model property 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * fix(us-if-003): implement train delegation in predictionmodelresult Update PredictionModelResult.Train() to delegate training to the inner model instead of throwing an exception. This completes the IFullModel implementation and allows the model to be trained through the wrapper. Changes: - Modified Train() method to check for null Model and delegate to Model.Train() - Updated XML documentation to reflect the delegation behavior - Ensures consistency with acceptance criteria for US-IF-003 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * refactor(us-if-003): enforce immutability in predictionmodelresult PredictionModelResult represents an already-trained model snapshot and should not allow mutation. This aligns with the architectural vision that users must use PredictionModelBuilder to create and train models. Changes: 1. Remove redundant IPredictiveModel interface (IFullModel is superset) 2. Revert Train() to throw InvalidOperationException (was delegating) 3. SetParameters() now throws InvalidOperationException (was delegating) 4. SetActiveFeatureIndices() now throws exception (was delegating) 5. Update IPredictionModelBuilder and PredictionModelBuilder to use PredictionModelResult<T, TInput, TOutput> directly Rationale: - PredictionModelResult is a snapshot with OptimizationResult and metadata - Retraining/mutation would invalidate the optimization results - Users can still: Predict(), GetParameters(), Clone(), DeepCopy(), Save/Load - Mutation is prevented: Train(), SetParameters(), SetActiveFeatureIndices() - Even DeepCopy() returns PredictionModelResult, so copies can't be retrained This enforces correct usage: use PredictionModelBuilder for training. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * fix(us-nf-009): ensure optimizationresult.bestsolution references correct model instance **Changes:** - WithParameters(): deep-copy OptimizationResult and update BestSolution to reference newModel - Clone(): delegate to WithParameters for consistency - DeepCopy(): update clonedOptimizationResult.BestSolution to reference clonedModel - All methods now preserve BiasDetector and FairnessEvaluator components **Rationale:** Previously WithParameters() and Clone() reused the existing OptimizationResult instance, which meant OptimizationResult.BestSolution still pointed to the old model instead of the new model with updated parameters. This created inconsistent metadata. Now all three methods (WithParameters, Clone, DeepCopy) ensure that OptimizationResult.BestSolution always references the correct model instance. Addresses code review feedback on PR #245. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * fix(testconsole): update examples to use predictionmodelresult instead of ipredictivemodel **Changes:** - TimeSeriesExample.cs: fix pattern matching to check model.Model instead of model - EnhancedTimeSeriesExample.cs: - Add using AiDotNet.Models.Results - Change return types from IPredictiveModel to PredictionModelResult - Update EvaluateModel parameter type to PredictionModelResult - RegressionExample.cs: update model property access to use model.Model - EnhancedRegressionExample.cs: update model property access to use model.Model **Rationale:** These testconsole examples were broken by removing IPredictiveModel from PredictionModelResult's interface declaration (PR #245). The examples now correctly work with PredictionModelResult and access the wrapped model via the Model property when needed. Fixes build errors introduced by PR #245 architectural changes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> --------- Co-authored-by: Claude <[email protected]>
1 parent 30130f3 commit e10224b

File tree

7 files changed

+255
-32
lines changed

7 files changed

+255
-32
lines changed

src/Interfaces/IPredictionModelBuilder.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using AiDotNet.Models.Results;
2+
13
namespace AiDotNet.Interfaces;
24

35
/// <summary>
@@ -200,7 +202,7 @@ public interface IPredictionModelBuilder<T, TInput, TOutput>
200202
/// <param name="x">The input features matrix, where each row is a data point and each column is a feature.</param>
201203
/// <param name="y">The target values vector that the model will learn to predict.</param>
202204
/// <returns>A trained predictive model ready to make predictions.</returns>
203-
IPredictiveModel<T, TInput, TOutput> Build(TInput x, TOutput y);
205+
PredictionModelResult<T, TInput, TOutput> Build(TInput x, TOutput y);
204206

205207
/// <summary>
206208
/// Uses a trained model to make predictions on new data.
@@ -216,7 +218,7 @@ public interface IPredictionModelBuilder<T, TInput, TOutput>
216218
/// <param name="newData">The new input data to make predictions for.</param>
217219
/// <param name="model">The trained model to use for making predictions.</param>
218220
/// <returns>A vector of predicted values.</returns>
219-
TOutput Predict(TInput newData, IPredictiveModel<T, TInput, TOutput> model);
221+
TOutput Predict(TInput newData, PredictionModelResult<T, TInput, TOutput> model);
220222

221223
/// <summary>
222224
/// Saves a trained model to a file.
@@ -230,7 +232,7 @@ public interface IPredictionModelBuilder<T, TInput, TOutput>
230232
/// </remarks>
231233
/// <param name="model">The trained model to save.</param>
232234
/// <param name="filePath">The file path where the model should be saved.</param>
233-
void SaveModel(IPredictiveModel<T, TInput, TOutput> model, string filePath);
235+
void SaveModel(PredictionModelResult<T, TInput, TOutput> model, string filePath);
234236

235237
/// <summary>
236238
/// Loads a previously saved model from a file.
@@ -244,7 +246,7 @@ public interface IPredictionModelBuilder<T, TInput, TOutput>
244246
/// </remarks>
245247
/// <param name="filePath">The file path where the model is stored.</param>
246248
/// <returns>The loaded predictive model.</returns>
247-
IPredictiveModel<T, TInput, TOutput> LoadModel(string filePath);
249+
PredictionModelResult<T, TInput, TOutput> LoadModel(string filePath);
248250

249251
/// <summary>
250252
/// Converts a trained model into a byte array for storage or transmission.
@@ -266,7 +268,7 @@ public interface IPredictionModelBuilder<T, TInput, TOutput>
266268
/// </remarks>
267269
/// <param name="model">The trained model to serialize.</param>
268270
/// <returns>A byte array containing the serialized model data.</returns>
269-
byte[] SerializeModel(IPredictiveModel<T, TInput, TOutput> model);
271+
byte[] SerializeModel(PredictionModelResult<T, TInput, TOutput> model);
270272

271273
/// <summary>
272274
/// Reconstructs a model from a previously serialized byte array.
@@ -286,7 +288,7 @@ public interface IPredictionModelBuilder<T, TInput, TOutput>
286288
/// </remarks>
287289
/// <param name="modelData">The byte array containing the serialized model data.</param>
288290
/// <returns>The reconstructed predictive model.</returns>
289-
IPredictiveModel<T, TInput, TOutput> DeserializeModel(byte[] modelData);
291+
PredictionModelResult<T, TInput, TOutput> DeserializeModel(byte[] modelData);
290292

291293
/// <summary>
292294
/// Configures the bias detector component for ethical AI evaluation.

src/Models/Results/PredictionModelResult.cs

Lines changed: 227 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ namespace AiDotNet.Models.Results;
4141
/// </remarks>
4242
/// <typeparam name="T">The numeric type used for calculations, typically float or double.</typeparam>
4343
[Serializable]
44-
public class PredictionModelResult<T, TInput, TOutput> : IPredictiveModel<T, TInput, TOutput>
44+
public class PredictionModelResult<T, TInput, TOutput> : IFullModel<T, TInput, TOutput>
4545
{
4646
/// <summary>
4747
/// Gets or sets the underlying model used for making predictions.
@@ -183,28 +183,43 @@ public class PredictionModelResult<T, TInput, TOutput> : IPredictiveModel<T, TIn
183183
/// <param name="normalizationInfo">The normalization information used to preprocess input data and postprocess predictions.</param>
184184
/// <remarks>
185185
/// <para>
186-
/// This constructor creates a new PredictionModelResult instance with the specified model, optimization results, and
187-
/// normalization information. It also initializes the ModelMetadata property by calling the GetModelMetadata method on
188-
/// the provided model. This constructor is typically used when a new model has been trained and needs to be packaged
186+
/// This constructor creates a new PredictionModelResult instance with the specified model, optimization results, and
187+
/// normalization information. It also initializes the ModelMetadata property by calling the GetModelMetadata method on
188+
/// the provided model. This constructor is typically used when a new model has been trained and needs to be packaged
189189
/// with all the necessary information for making predictions and for later serialization.
190190
/// </para>
191191
/// <para><b>For Beginners:</b> This constructor creates a new prediction model result with all the necessary components.
192-
///
192+
///
193193
/// When creating a new PredictionModelResult:
194194
/// - You provide the trained model that will make predictions
195195
/// - You provide the optimization results that describe how the model was created
196196
/// - You provide the normalization information needed to process data
197197
/// - The constructor automatically extracts metadata from the model
198-
///
198+
///
199199
/// This constructor is typically used when:
200200
/// - You've just finished training a model
201201
/// - You want to package it with all the information needed to use it
202202
/// - You plan to save it for later use or deploy it in an application
203-
///
203+
///
204204
/// For example, after training a house price prediction model, you would use this constructor
205205
/// to create a complete package that can be saved and used for making predictions.
206206
/// </para>
207207
/// </remarks>
208+
public PredictionModelResult(IFullModel<T, TInput, TOutput> model,
209+
OptimizationResult<T, TInput, TOutput> optimizationResult,
210+
NormalizationInfo<T, TInput, TOutput> normalizationInfo)
211+
{
212+
Model = model;
213+
OptimizationResult = optimizationResult;
214+
NormalizationInfo = normalizationInfo;
215+
ModelMetaData = model?.GetModelMetadata() ?? new();
216+
}
217+
218+
/// <summary>
219+
/// Initializes a new instance of the PredictionModelResult class with optimization results and normalization information.
220+
/// </summary>
221+
/// <param name="optimizationResult">The results of the optimization process that created the model.</param>
222+
/// <param name="normalizationInfo">The normalization information used to preprocess input data and postprocess predictions.</param>
208223
public PredictionModelResult(OptimizationResult<T, TInput, TOutput> optimizationResult,
209224
NormalizationInfo<T, TInput, TOutput> normalizationInfo,
210225
IBiasDetector<T>? biasDetector = null,
@@ -328,6 +343,211 @@ public TOutput Predict(TInput newData)
328343
return NormalizationInfo.Normalizer.Denormalize(normalizedPredictions, NormalizationInfo.YParams);
329344
}
330345

346+
/// <summary>
347+
/// Training is not supported on PredictionModelResult. Use PredictionModelBuilder to create and train new models.
348+
/// </summary>
349+
/// <param name="input">Input training data (not used).</param>
350+
/// <param name="expectedOutput">Expected output values (not used).</param>
351+
/// <exception cref="InvalidOperationException">Always thrown - PredictionModelResult represents an already-trained model and cannot be retrained.</exception>
352+
/// <remarks>
353+
/// PredictionModelResult is a snapshot of a trained model with its optimization results and metadata.
354+
/// Retraining would invalidate the OptimizationResult and metadata.
355+
/// To train a new model or retrain with different data, use PredictionModelBuilder.Build() instead.
356+
/// </remarks>
357+
public void Train(TInput input, TOutput expectedOutput)
358+
{
359+
throw new InvalidOperationException(
360+
"PredictionModelResult represents an already-trained model and cannot be retrained. " +
361+
"The OptimizationResult and metadata reflect the original training process. " +
362+
"To train a new model, use PredictionModelBuilder.Build() instead.");
363+
}
364+
365+
/// <summary>
366+
/// Gets the parameters of the underlying model.
367+
/// </summary>
368+
/// <returns>A vector containing the model parameters.</returns>
369+
/// <exception cref="InvalidOperationException">Thrown when the Model is not initialized.</exception>
370+
public Vector<T> GetParameters()
371+
{
372+
if (Model == null)
373+
{
374+
throw new InvalidOperationException("Model is not initialized.");
375+
}
376+
377+
return Model.GetParameters();
378+
}
379+
380+
/// <summary>
381+
/// Setting parameters is not supported on PredictionModelResult.
382+
/// </summary>
383+
/// <param name="parameters">The parameter vector (not used).</param>
384+
/// <exception cref="InvalidOperationException">Always thrown - PredictionModelResult parameters cannot be modified.</exception>
385+
/// <remarks>
386+
/// Modifying parameters would invalidate the OptimizationResult which reflects the optimized parameter values.
387+
/// To create a model with different parameters, use PredictionModelBuilder with custom initial parameters.
388+
/// </remarks>
389+
public void SetParameters(Vector<T> parameters)
390+
{
391+
throw new InvalidOperationException(
392+
"PredictionModelResult parameters cannot be modified. " +
393+
"The current parameters reflect the optimized solution from the training process. " +
394+
"To create a model with different parameters, use PredictionModelBuilder.");
395+
}
396+
397+
/// <summary>
398+
/// Gets the number of parameters in the underlying model.
399+
/// </summary>
400+
public int ParameterCount
401+
{
402+
get
403+
{
404+
if (Model == null)
405+
{
406+
return 0;
407+
}
408+
409+
return Model.ParameterCount;
410+
}
411+
}
412+
413+
/// <summary>
414+
/// Creates a new instance with the specified parameters.
415+
/// </summary>
416+
/// <param name="parameters">The parameter vector to use.</param>
417+
/// <returns>A new PredictionModelResult with updated parameters.</returns>
418+
/// <exception cref="InvalidOperationException">Thrown when the Model is not initialized.</exception>
419+
public IFullModel<T, TInput, TOutput> WithParameters(Vector<T> parameters)
420+
{
421+
if (Model == null)
422+
{
423+
throw new InvalidOperationException("Model is not initialized.");
424+
}
425+
426+
var newModel = Model.WithParameters(parameters);
427+
428+
// Deep-copy OptimizationResult and update its BestSolution to reference newModel
429+
// This ensures metadata consistency - BestSolution should always point to the current model
430+
var updatedOptimizationResult = OptimizationResult.DeepCopy();
431+
updatedOptimizationResult.BestSolution = newModel;
432+
433+
// Create new result with updated optimization result
434+
// Use constructor that preserves BiasDetector and FairnessEvaluator
435+
return new PredictionModelResult<T, TInput, TOutput>(
436+
updatedOptimizationResult,
437+
NormalizationInfo,
438+
BiasDetector,
439+
FairnessEvaluator);
440+
}
441+
442+
/// <summary>
443+
/// Gets the indices of features that are actively used by the underlying model.
444+
/// </summary>
445+
/// <returns>An enumerable of active feature indices.</returns>
446+
/// <exception cref="InvalidOperationException">Thrown when the Model is not initialized.</exception>
447+
public IEnumerable<int> GetActiveFeatureIndices()
448+
{
449+
if (Model == null)
450+
{
451+
throw new InvalidOperationException("Model is not initialized.");
452+
}
453+
454+
return Model.GetActiveFeatureIndices();
455+
}
456+
457+
/// <summary>
458+
/// Setting active feature indices is not supported on PredictionModelResult.
459+
/// </summary>
460+
/// <param name="featureIndices">The feature indices (not used).</param>
461+
/// <exception cref="InvalidOperationException">Always thrown - PredictionModelResult feature configuration cannot be modified.</exception>
462+
/// <remarks>
463+
/// Changing active features would invalidate the trained model and optimization results.
464+
/// To train a model with different features, use PredictionModelBuilder with the desired feature configuration.
465+
/// </remarks>
466+
public void SetActiveFeatureIndices(IEnumerable<int> featureIndices)
467+
{
468+
throw new InvalidOperationException(
469+
"PredictionModelResult active features cannot be modified. " +
470+
"The model was trained with a specific feature set. " +
471+
"To use different features, train a new model using PredictionModelBuilder.");
472+
}
473+
474+
/// <summary>
475+
/// Checks if a specific feature is used by the underlying model.
476+
/// </summary>
477+
/// <param name="featureIndex">The index of the feature to check.</param>
478+
/// <returns>True if the feature is used, false otherwise.</returns>
479+
/// <exception cref="InvalidOperationException">Thrown when the Model is not initialized.</exception>
480+
public bool IsFeatureUsed(int featureIndex)
481+
{
482+
if (Model == null)
483+
{
484+
throw new InvalidOperationException("Model is not initialized.");
485+
}
486+
487+
return Model.IsFeatureUsed(featureIndex);
488+
}
489+
490+
/// <summary>
491+
/// Gets the feature importance scores from the underlying model.
492+
/// </summary>
493+
/// <returns>A dictionary mapping feature names to importance scores.</returns>
494+
/// <exception cref="InvalidOperationException">Thrown when the Model is not initialized.</exception>
495+
public Dictionary<string, T> GetFeatureImportance()
496+
{
497+
if (Model == null)
498+
{
499+
throw new InvalidOperationException("Model is not initialized.");
500+
}
501+
502+
return Model.GetFeatureImportance();
503+
}
504+
505+
/// <summary>
506+
/// Creates a deep copy of this PredictionModelResult.
507+
/// </summary>
508+
/// <returns>A new PredictionModelResult instance that is a deep copy of this one.</returns>
509+
public IFullModel<T, TInput, TOutput> DeepCopy()
510+
{
511+
if (Model == null)
512+
{
513+
throw new InvalidOperationException("Cannot deep copy PredictionModelResult with null Model.");
514+
}
515+
516+
var clonedModel = Model.DeepCopy();
517+
var clonedOptimizationResult = OptimizationResult.DeepCopy();
518+
519+
// Update OptimizationResult.BestSolution to reference the cloned model
520+
// This ensures metadata consistency across the deep copy
521+
clonedOptimizationResult.BestSolution = clonedModel;
522+
523+
var clonedNormalizationInfo = NormalizationInfo.DeepCopy();
524+
525+
// Use constructor that preserves BiasDetector and FairnessEvaluator
526+
return new PredictionModelResult<T, TInput, TOutput>(
527+
clonedOptimizationResult,
528+
clonedNormalizationInfo,
529+
BiasDetector,
530+
FairnessEvaluator);
531+
}
532+
533+
/// <summary>
534+
/// Creates a shallow copy of this PredictionModelResult.
535+
/// </summary>
536+
/// <returns>A new PredictionModelResult instance that is a shallow copy of this one.</returns>
537+
/// <remarks>
538+
/// This method delegates to WithParameters to ensure consistency in how OptimizationResult is handled.
539+
/// The cloned instance will have a new model with the same parameters and updated OptimizationResult metadata.
540+
/// </remarks>
541+
public IFullModel<T, TInput, TOutput> Clone()
542+
{
543+
if (Model == null)
544+
{
545+
throw new InvalidOperationException("Cannot clone PredictionModelResult with null Model.");
546+
}
547+
548+
return WithParameters(Model.GetParameters());
549+
}
550+
331551
/// <summary>
332552
/// Serializes the model to a byte array.
333553
/// </summary>

src/PredictionModelBuilder.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ public IPredictionModelBuilder<T, TInput, TOutput> ConfigureOutlierRemoval(IOutl
205205
/// The input matrix 'x' contains your features (like house size, number of bedrooms, etc. if predicting house prices),
206206
/// and the vector 'y' contains the known answers (actual house prices) for those examples.
207207
/// </remarks>
208-
public IPredictiveModel<T, TInput, TOutput> Build(TInput x, TOutput y)
208+
public PredictionModelResult<T, TInput, TOutput> Build(TInput x, TOutput y)
209209
{
210210
var convertedX = ConversionsHelper.ConvertToMatrix<T, TInput>(x);
211211
var convertedY = ConversionsHelper.ConvertToVector<T, TOutput>(y);
@@ -253,7 +253,7 @@ public IPredictiveModel<T, TInput, TOutput> Build(TInput x, TOutput y)
253253
///
254254
/// The input matrix should have the same number of columns (features) as the data you used to train the model.
255255
/// </remarks>
256-
public TOutput Predict(TInput newData, IPredictiveModel<T, TInput, TOutput> modelResult)
256+
public TOutput Predict(TInput newData, PredictionModelResult<T, TInput, TOutput> modelResult)
257257
{
258258
return modelResult.Predict(newData);
259259
}
@@ -271,7 +271,7 @@ public TOutput Predict(TInput newData, IPredictiveModel<T, TInput, TOutput> mode
271271
/// Think of it like saving a document in a word processor - you can close the program and come back later
272272
/// to continue where you left off.
273273
/// </remarks>
274-
public void SaveModel(IPredictiveModel<T, TInput, TOutput> modelResult, string filePath)
274+
public void SaveModel(PredictionModelResult<T, TInput, TOutput> modelResult, string filePath)
275275
{
276276
File.WriteAllBytes(filePath, SerializeModel(modelResult));
277277
}
@@ -288,7 +288,7 @@ public void SaveModel(IPredictiveModel<T, TInput, TOutput> modelResult, string f
288288
/// This is useful when you want to use your model in different applications or at different times
289289
/// without the time and computational cost of retraining.
290290
/// </remarks>
291-
public IPredictiveModel<T, TInput, TOutput> LoadModel(string filePath)
291+
public PredictionModelResult<T, TInput, TOutput> LoadModel(string filePath)
292292
{
293293
byte[] modelData = File.ReadAllBytes(filePath);
294294
return DeserializeModel(modelData);
@@ -306,7 +306,7 @@ public IPredictiveModel<T, TInput, TOutput> LoadModel(string filePath)
306306
/// You might use this directly if you want to store the model in a database or send it over a network,
307307
/// rather than saving it to a file.
308308
/// </remarks>
309-
public byte[] SerializeModel(IPredictiveModel<T, TInput, TOutput> modelResult)
309+
public byte[] SerializeModel(PredictionModelResult<T, TInput, TOutput> modelResult)
310310
{
311311
return modelResult.Serialize();
312312
}
@@ -323,7 +323,7 @@ public byte[] SerializeModel(IPredictiveModel<T, TInput, TOutput> modelResult)
323323
///
324324
/// You might use this directly if you retrieved a serialized model from a database or received it over a network.
325325
/// </remarks>
326-
public IPredictiveModel<T, TInput, TOutput> DeserializeModel(byte[] modelData)
326+
public PredictionModelResult<T, TInput, TOutput> DeserializeModel(byte[] modelData)
327327
{
328328
var result = new PredictionModelResult<T, TInput, TOutput>();
329329
result.Deserialize(modelData);

testconsole/Examples/EnhancedRegressionExample.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,11 +176,11 @@ public void RunExample()
176176
Console.WriteLine($"\nBest model based on R² score: {bestModelName}");
177177

178178
// 8. Analyze feature importance (for the best model)
179-
if (bestModel is IPredictiveModel<double, Matrix<double>, Vector<double>> predictiveModel)
179+
if (bestModel.Model != null)
180180
{
181-
if (predictiveModel.GetType().GetProperty("Coefficients") != null)
181+
if (bestModel.Model.GetType().GetProperty("Coefficients") != null)
182182
{
183-
var coefficients = predictiveModel.GetType().GetProperty("Coefficients")?.GetValue(predictiveModel);
183+
var coefficients = bestModel.Model.GetType().GetProperty("Coefficients")?.GetValue(bestModel.Model);
184184

185185
if (coefficients is Vector<double> coefVector)
186186
{

0 commit comments

Comments
 (0)