Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/Interfaces/IFeatureAware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,25 @@ public interface IFeatureAware
/// </summary>
IEnumerable<int> GetActiveFeatureIndices();

/// <summary>
/// Sets the active feature indices for this model.
/// </summary>
void SetActiveFeatureIndices(IEnumerable<int> featureIndices);

/// <summary>
/// Checks if a specific feature is used by this model.
/// </summary>
bool IsFeatureUsed(int featureIndex);
}

/// <summary>
/// Interface for models that can provide feature importance scores.
/// </summary>
/// <typeparam name="T">The numeric type used for feature importance scores.</typeparam>
public interface IFeatureImportance<T>
{
/// <summary>
/// Gets the feature importance scores.
/// </summary>
Dictionary<string, T> GetFeatureImportance();
}
4 changes: 2 additions & 2 deletions src/Interfaces/IFullModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ namespace AiDotNet.Interfaces;
/// - Loaded quickly when needed to make predictions
/// - Possibly updated with new data periodically
/// </remarks>
public interface IFullModel<T, TInput, TOutput> : IModel<TInput, TOutput, ModelMetaData<T>>,
IModelSerializer, IParameterizable<T, TInput, TOutput>, IFeatureAware, ICloneable<IFullModel<T, TInput, TOutput>>
public interface IFullModel<T, TInput, TOutput> : IModel<TInput, TOutput, ModelMetaData<T>>,
IModelSerializer, IParameterizable<T, TInput, TOutput>, IFeatureAware, IFeatureImportance<T>, ICloneable<IFullModel<T, TInput, TOutput>>
{
}
45 changes: 45 additions & 0 deletions src/Models/NeuralNetworkModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,51 @@ public IEnumerable<int> GetActiveFeatureIndices()
return Enumerable.Range(0, FeatureCount);
}

/// <summary>
/// Sets the parameters for this model.
/// </summary>
/// <param name="parameters">A vector containing the model parameters.</param>
public void SetParameters(Vector<T> parameters)
{
if (Network == null)
{
throw new InvalidOperationException("Network has not been initialized.");
}

Network.SetParameters(parameters);
}

/// <summary>
/// Sets the active feature indices for this model.
/// </summary>
/// <param name="featureIndices">The indices of features to activate.</param>
public void SetActiveFeatureIndices(IEnumerable<int> featureIndices)
{
// Neural networks typically don't support feature masking after training
throw new NotSupportedException("Neural networks do not support setting active features after network construction.");
}

/// <summary>
/// Gets the feature importance scores as a dictionary.
/// </summary>
/// <returns>A dictionary mapping feature names to their importance scores.</returns>
/// <exception cref="NotSupportedException">
/// This method is not supported for neural networks. Feature importance in neural networks
/// requires specialized techniques like gradient-based attribution or permutation importance.
/// </exception>
public Dictionary<string, T> GetFeatureImportance()
{
// Neural network feature importance requires specialized techniques like:
// - Gradient-based attribution methods (e.g., Integrated Gradients, SHAP)
// - Permutation importance
// - Layer-wise relevance propagation
// These are complex to implement correctly and beyond the scope of this basic method.
throw new NotSupportedException(
"Feature importance is not supported for neural networks through this method. " +
"Neural networks require specialized techniques like gradient-based attribution, " +
"permutation importance, or SHAP values to properly assess feature importance.");
}

/// <summary>
/// Creates a deep copy of this model.
/// </summary>
Expand Down
94 changes: 90 additions & 4 deletions src/Models/VectorModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace AiDotNet.Models;
/// - It supports genetic algorithm operations for optimization
///
/// For example, if predicting house prices, the model might learn that:
/// price = 50,000 × bedrooms + 100 × square_feet + 20,000 × bathrooms
/// price = 50,000 � bedrooms + 100 � square_feet + 20,000 � bathrooms
///
/// This is one of the simplest and most interpretable machine learning models,
/// making it a good starting point for many problems.
Expand Down Expand Up @@ -54,7 +54,23 @@ public class VectorModel<T> : IFullModel<T, Matrix<T>, Vector<T>>
/// </para>
/// </remarks>
public Vector<T> Coefficients { get; }


/// <summary>
/// Gets or sets the feature names.
/// </summary>
/// <value>
/// An array of feature names. If not set, feature indices will be used as names.
/// </value>
/// <remarks>
/// <para><b>For Beginners:</b> This allows you to give meaningful names to your features.
///
/// Instead of having features referred to as "Feature_0", "Feature_1", etc.,
/// you can use descriptive names like "bedrooms", "bathrooms", "square_feet".
/// This makes the model's feature importance output more readable.
/// </para>
/// </remarks>
public string[]? FeatureNames { get; set; }

/// <summary>
/// The numeric operations provider used for mathematical operations on type T.
/// </summary>
Expand Down Expand Up @@ -219,10 +235,10 @@ public bool IsFeatureUsed(int featureIndex)
/// - Throws an error if the input has the wrong number of features
///
/// This is the core of how a linear model works - it's just a weighted sum:
/// prediction = (input1 × coefficient1) + (input2 × coefficient2) + ...
/// prediction = (input1 � coefficient1) + (input2 � coefficient2) + ...
///
/// For example, with coefficients [50000, 100, 20000] and input [3, 1500, 2],
/// the prediction would be: 3×50000 + 1500×100 + 2×20000 = 350,000
/// the prediction would be: 3�50000 + 1500�100 + 2�20000 = 350,000
/// </para>
/// </remarks>
public T Evaluate(Vector<T> input)
Expand Down Expand Up @@ -745,6 +761,76 @@ public IEnumerable<int> GetActiveFeatureIndices()
}
}

/// <summary>
/// Sets the parameters for this model.
/// </summary>
/// <param name="parameters">A vector containing the model parameters.</param>
/// <exception cref="ArgumentException">Thrown when the parameters vector has an incorrect length.</exception>
/// <remarks>
/// <para><b>For Beginners:</b> This method updates the model's coefficients directly.
/// The parameters vector should match the number of features in the model.
/// </para>
/// </remarks>
public void SetParameters(Vector<T> parameters)
{
if (parameters.Length != Coefficients.Length)
{
throw new ArgumentException($"Expected {Coefficients.Length} parameters, but got {parameters.Length}", nameof(parameters));
}

for (int i = 0; i < Coefficients.Length; i++)
{
Coefficients[i] = parameters[i];
}
}

/// <summary>
/// Sets the active feature indices for this model.
/// </summary>
/// <param name="featureIndices">The indices of features to activate.</param>
/// <remarks>
/// <para><b>For Beginners:</b> This method selectively activates only certain features
/// by setting all other feature coefficients to zero.
/// </para>
/// </remarks>
public void SetActiveFeatureIndices(IEnumerable<int> featureIndices)
{
var activeSet = new HashSet<int>(featureIndices);

for (int i = 0; i < Coefficients.Length; i++)
{
if (!activeSet.Contains(i))
{
Coefficients[i] = _numOps.Zero;
}
}
}

/// <summary>
/// Gets the feature importance scores as a dictionary.
/// </summary>
/// <returns>A dictionary mapping feature names to their importance scores.</returns>
/// <remarks>
/// <para><b>For Beginners:</b> This method returns the absolute values of coefficients
/// as feature importance scores. Features with larger absolute coefficients are more important.
/// If FeatureNames is set, those names will be used; otherwise, default names like "Feature_0" are used.
/// </para>
/// </remarks>
public Dictionary<string, T> GetFeatureImportance()
{
var result = new Dictionary<string, T>();

for (int i = 0; i < Coefficients.Length; i++)
{
string featureName = FeatureNames != null && i < FeatureNames.Length
? FeatureNames[i]
: $"Feature_{i}";
result[featureName] = _numOps.Abs(Coefficients[i]);
}

return result;
}

/// <summary>
/// Creates a deep copy of this model.
/// </summary>
Expand Down
72 changes: 72 additions & 0 deletions src/Regression/DecisionTreeAsyncRegressionBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ public abstract class AsyncDecisionTreeRegressionBase<T> : IAsyncTreeBasedModel<
/// </summary>
protected Random Random => new(Options.Seed ?? Environment.TickCount);

/// <summary>
/// Gets or sets the feature names.
/// </summary>
/// <value>
/// An array of feature names. If not set, feature indices will be used as names.
/// </value>
public string[]? FeatureNames { get; set; }

/// <summary>
/// Initializes a new instance of the AsyncDecisionTreeRegressionBase class.
/// </summary>
Expand Down Expand Up @@ -520,6 +528,70 @@ public virtual bool IsFeatureUsed(int featureIndex)
return IsFeatureUsedInSubtree(Root, featureIndex);
}

/// <summary>
/// Sets the parameters for this model.
/// </summary>
/// <param name="parameters">A vector containing the model parameters.</param>
public virtual void SetParameters(Vector<T> parameters)
{
throw new NotSupportedException("Decision trees do not support direct parameter setting. Use WithParameters to create a new model with different parameters.");
}

/// <summary>
/// Sets the active feature indices for this model.
/// </summary>
/// <param name="featureIndices">The indices of features to activate.</param>
public virtual void SetActiveFeatureIndices(IEnumerable<int> featureIndices)
{
throw new NotSupportedException("Decision trees do not support setting active features after training. Features are selected during tree construction.");
}

/// <summary>
/// Gets the feature importance scores as a dictionary.
/// </summary>
/// <returns>A dictionary mapping feature names to their importance scores.</returns>
public virtual Dictionary<string, T> GetFeatureImportance()
{
if (Root == null)
{
return new Dictionary<string, T>();
}

var importanceScores = new Dictionary<int, T>();
CalculateFeatureImportanceRecursive(Root, importanceScores);

var result = new Dictionary<string, T>();
foreach (var kvp in importanceScores)
{
string featureName = FeatureNames != null && kvp.Key < FeatureNames.Length
? FeatureNames[kvp.Key]
: $"Feature_{kvp.Key}";
result[featureName] = kvp.Value;
}

return result;
}

private void CalculateFeatureImportanceRecursive(DecisionTreeNode<T>? node, Dictionary<int, T> importanceScores)
{
if (node == null || node.IsLeaf)
return;

if (!importanceScores.ContainsKey(node.FeatureIndex))
{
importanceScores[node.FeatureIndex] = NumOps.Zero;
}

Comment thread
ooples marked this conversation as resolved.
// NOTE: This is a simple count-based approach to feature importance.
// It increments the score for each time a feature is used to split a node,
// but does NOT account for the quality of the split (e.g., reduction in impurity or error).
// This limitation means the importance scores may not reflect the true predictive power of each feature.
importanceScores[node.FeatureIndex] = NumOps.Add(importanceScores[node.FeatureIndex], NumOps.One);

CalculateFeatureImportanceRecursive(node.Left, importanceScores);
CalculateFeatureImportanceRecursive(node.Right, importanceScores);
}

/// <summary>
/// Creates a deep copy of the decision tree model.
/// </summary>
Expand Down
Loading
Loading