Skip to content

Commit

Permalink
Add Microsoft.Extensions.ML integration package. (#3827)
Browse files Browse the repository at this point in the history
* Add Microsoft.Extensions.ML integration package.

This package makes it easier to use ML.NET with app models that support Microsoft.Extensions - i.e. ASP.NET and Azure Functions.

Specifically it contains functionality for:

- Dependency Injection
- Pooling PredictionEngines
- Reloading models when the file or URI has changed
- Hooking ML.NET logging to Microsoft.Extensions.Logging

Fix #3239

* Add XML doc comments.
Format the code so lines aren't so long.
Remove unnecessary IPredictionEnginePoolBuilder interface, and just use a regular class.

* Respond to PR feedback.

* PR feedback

Rename MLContextOptions to MLOptions. Make MLContext lazy loaded, so callers can provide their own.

Use the loaded model in the test.
  • Loading branch information
eerhardt authored Jun 13, 2019
1 parent 23332c1 commit 40609d5
Show file tree
Hide file tree
Showing 23 changed files with 1,452 additions and 0 deletions.
37 changes: 37 additions & 0 deletions Microsoft.ML.sln
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.ML.FastTree", "Mi
pkg\Microsoft.ML.FastTree\Microsoft.ML.FastTree.symbols.nupkgproj = pkg\Microsoft.ML.FastTree\Microsoft.ML.FastTree.symbols.nupkgproj
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ML", "src\Microsoft.Extensions.ML\Microsoft.Extensions.ML.csproj", "{D6741C37-B5E6-4050-BCBA-9715809EA15B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ML.Tests", "test\Microsoft.Extensions.ML.Tests\Microsoft.Extensions.ML.Tests.csproj", "{21CAD3A1-5E1F-42C1-BB73-46B6E67F4206}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Extensions.ML", "Microsoft.Extensions.ML", "{AE4F7569-26F3-4160-8A8B-7A57D0DA3350}"
ProjectSection(SolutionItems) = preProject
pkg\Microsoft.Extensions.ML\Microsoft.Extensions.ML.nupkgproj = pkg\Microsoft.Extensions.ML\Microsoft.Extensions.ML.nupkgproj
pkg\Microsoft.Extensions.ML\Microsoft.Extensions.ML.symbols.nupkgproj = pkg\Microsoft.Extensions.ML\Microsoft.Extensions.ML.symbols.nupkgproj
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.StableApi", "tools-local\Microsoft.ML.StableApi\Microsoft.ML.StableApi.csproj", "{F308DC6B-7E59-40D7-A581-834E8CD99CFE}"
EndProject
Global
Expand Down Expand Up @@ -970,6 +980,30 @@ Global
{E02DA82D-3FEE-4C60-BD80-9EC3C3448DFC}.Release-Intrinsics|Any CPU.Build.0 = Release-Intrinsics|Any CPU
{E02DA82D-3FEE-4C60-BD80-9EC3C3448DFC}.Release-netfx|Any CPU.ActiveCfg = Release-netfx|Any CPU
{E02DA82D-3FEE-4C60-BD80-9EC3C3448DFC}.Release-netfx|Any CPU.Build.0 = Release-netfx|Any CPU
{D6741C37-B5E6-4050-BCBA-9715809EA15B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D6741C37-B5E6-4050-BCBA-9715809EA15B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D6741C37-B5E6-4050-BCBA-9715809EA15B}.Debug-Intrinsics|Any CPU.ActiveCfg = Debug-Intrinsics|Any CPU
{D6741C37-B5E6-4050-BCBA-9715809EA15B}.Debug-Intrinsics|Any CPU.Build.0 = Debug-Intrinsics|Any CPU
{D6741C37-B5E6-4050-BCBA-9715809EA15B}.Debug-netfx|Any CPU.ActiveCfg = Debug-netfx|Any CPU
{D6741C37-B5E6-4050-BCBA-9715809EA15B}.Debug-netfx|Any CPU.Build.0 = Debug-netfx|Any CPU
{D6741C37-B5E6-4050-BCBA-9715809EA15B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D6741C37-B5E6-4050-BCBA-9715809EA15B}.Release|Any CPU.Build.0 = Release|Any CPU
{D6741C37-B5E6-4050-BCBA-9715809EA15B}.Release-Intrinsics|Any CPU.ActiveCfg = Release-Intrinsics|Any CPU
{D6741C37-B5E6-4050-BCBA-9715809EA15B}.Release-Intrinsics|Any CPU.Build.0 = Release-Intrinsics|Any CPU
{D6741C37-B5E6-4050-BCBA-9715809EA15B}.Release-netfx|Any CPU.ActiveCfg = Release-netfx|Any CPU
{D6741C37-B5E6-4050-BCBA-9715809EA15B}.Release-netfx|Any CPU.Build.0 = Release-netfx|Any CPU
{21CAD3A1-5E1F-42C1-BB73-46B6E67F4206}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{21CAD3A1-5E1F-42C1-BB73-46B6E67F4206}.Debug|Any CPU.Build.0 = Debug|Any CPU
{21CAD3A1-5E1F-42C1-BB73-46B6E67F4206}.Debug-Intrinsics|Any CPU.ActiveCfg = Debug-Intrinsics|Any CPU
{21CAD3A1-5E1F-42C1-BB73-46B6E67F4206}.Debug-Intrinsics|Any CPU.Build.0 = Debug-Intrinsics|Any CPU
{21CAD3A1-5E1F-42C1-BB73-46B6E67F4206}.Debug-netfx|Any CPU.ActiveCfg = Debug-netfx|Any CPU
{21CAD3A1-5E1F-42C1-BB73-46B6E67F4206}.Debug-netfx|Any CPU.Build.0 = Debug-netfx|Any CPU
{21CAD3A1-5E1F-42C1-BB73-46B6E67F4206}.Release|Any CPU.ActiveCfg = Release|Any CPU
{21CAD3A1-5E1F-42C1-BB73-46B6E67F4206}.Release|Any CPU.Build.0 = Release|Any CPU
{21CAD3A1-5E1F-42C1-BB73-46B6E67F4206}.Release-Intrinsics|Any CPU.ActiveCfg = Release-Intrinsics|Any CPU
{21CAD3A1-5E1F-42C1-BB73-46B6E67F4206}.Release-Intrinsics|Any CPU.Build.0 = Release-Intrinsics|Any CPU
{21CAD3A1-5E1F-42C1-BB73-46B6E67F4206}.Release-netfx|Any CPU.ActiveCfg = Release-netfx|Any CPU
{21CAD3A1-5E1F-42C1-BB73-46B6E67F4206}.Release-netfx|Any CPU.Build.0 = Release-netfx|Any CPU
{F308DC6B-7E59-40D7-A581-834E8CD99CFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F308DC6B-7E59-40D7-A581-834E8CD99CFE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F308DC6B-7E59-40D7-A581-834E8CD99CFE}.Debug-Intrinsics|Any CPU.ActiveCfg = Debug-Intrinsics|Any CPU
Expand Down Expand Up @@ -1069,6 +1103,9 @@ Global
{AD7058C9-5608-49A8-BE23-58C33A74EE91} = {D3D38B03-B557-484D-8348-8BADEE4DF592}
{E02DA82D-3FEE-4C60-BD80-9EC3C3448DFC} = {09EADF06-BE25-4228-AB53-95AE3E15B530}
{B1B3F284-FA3D-4D76-A712-FF04495D244B} = {D3D38B03-B557-484D-8348-8BADEE4DF592}
{D6741C37-B5E6-4050-BCBA-9715809EA15B} = {09EADF06-BE25-4228-AB53-95AE3E15B530}
{21CAD3A1-5E1F-42C1-BB73-46B6E67F4206} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4}
{AE4F7569-26F3-4160-8A8B-7A57D0DA3350} = {D3D38B03-B557-484D-8348-8BADEE4DF592}
{F308DC6B-7E59-40D7-A581-834E8CD99CFE} = {7F13E156-3EBA-4021-84A5-CD56BA72F99E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
Expand Down
1 change: 1 addition & 0 deletions build/Dependencies.props
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<PropertyGroup>
<GoogleProtobufPackageVersion>3.5.1</GoogleProtobufPackageVersion>
<LightGBMPackageVersion>2.2.3</LightGBMPackageVersion>
<MicrosoftExtensionsPackageVersion>2.1.0</MicrosoftExtensionsPackageVersion>
<MicrosoftMLOnnxRuntimePackageVersion>0.3.0</MicrosoftMLOnnxRuntimePackageVersion>
<MlNetMklDepsPackageVersion>0.0.0.9</MlNetMklDepsPackageVersion>
<ParquetDotNetPackageVersion>2.1.3</ParquetDotNetPackageVersion>
Expand Down
16 changes: 16 additions & 0 deletions pkg/Microsoft.Extensions.ML/Microsoft.Extensions.ML.nupkgproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk" DefaultTargets="Pack">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageDescription>An integration package for ML.NET models on scalable web apps and services.</PackageDescription>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="$(MicrosoftExtensionsPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="$(MicrosoftExtensionsPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Options" Version="$(MicrosoftExtensionsPackageVersion)" />
<ProjectReference Include="..\Microsoft.ML\Microsoft.ML.nupkgproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<Project DefaultTargets="Pack">

<Import Project="Microsoft.Extensions.ML.nupkgproj" />

</Project>
213 changes: 213 additions & 0 deletions src/Microsoft.Extensions.ML/Builder/BuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.Extensions.ML
{
/// <summary>
/// Extension methods for <see cref="PredictionEnginePoolBuilder{TData, TPrediction}"/>.
/// </summary>
public static class BuilderExtensions
{
/// <summary>
/// Adds the model at the specified location to the builder.
/// </summary>
/// <param name="builder">The builder to which to add the model.</param>
/// <param name="uri">The location of the model.</param>
/// <returns>
/// The updated <see cref="PredictionEnginePoolBuilder{TData, TPrediction}"/>.
/// </returns>
public static PredictionEnginePoolBuilder<TData, TPrediction> FromUri<TData, TPrediction>(
this PredictionEnginePoolBuilder<TData, TPrediction> builder, string uri)
where TData : class
where TPrediction : class, new()
{
return builder.FromUri(string.Empty, new Uri(uri));
}

/// <summary>
/// Adds the named model at the specified location to the builder.
/// </summary>
/// <param name="builder">The builder to which to add the model.</param>
/// <param name="modelName">
/// The name of the model which allows for uniquely identifying the model when
/// multiple models have the same <typeparamref name="TData"/> and
/// <typeparamref name="TPrediction"/> types.
/// </param>
/// <param name="uri">The location of the model.</param>
/// <returns>
/// The updated <see cref="PredictionEnginePoolBuilder{TData, TPrediction}"/>.
/// </returns>
public static PredictionEnginePoolBuilder<TData, TPrediction> FromUri<TData, TPrediction>(
this PredictionEnginePoolBuilder<TData, TPrediction> builder, string modelName, string uri)
where TData : class
where TPrediction : class, new()
{
return builder.FromUri(modelName, new Uri(uri));
}

/// <summary>
/// Adds the named model at the specified location to the builder.
/// </summary>
/// <param name="builder">The builder to which to add the model.</param>
/// <param name="modelName">
/// The name of the model which allows for uniquely identifying the model when
/// multiple models have the same <typeparamref name="TData"/> and
/// <typeparamref name="TPrediction"/> types.
/// </param>
/// <param name="uri">The location of the model.</param>
/// <returns>
/// The updated <see cref="PredictionEnginePoolBuilder{TData, TPrediction}"/>.
/// </returns>
public static PredictionEnginePoolBuilder<TData, TPrediction> FromUri<TData, TPrediction>(
this PredictionEnginePoolBuilder<TData, TPrediction> builder, string modelName, Uri uri)
where TData : class where TPrediction : class, new()
{
return builder.FromUri(modelName, uri, TimeSpan.FromMinutes(5));
}

/// <summary>
/// Adds the model at the specified location to the builder.
/// </summary>
/// <param name="builder">The builder to which to add the model.</param>
/// <param name="uri">The location of the model.</param>
/// <param name="period">
/// How often to query if the model has been updated at the specified location.
/// </param>
/// <returns>
/// The updated <see cref="PredictionEnginePoolBuilder{TData, TPrediction}"/>.
/// </returns>
public static PredictionEnginePoolBuilder<TData, TPrediction> FromUri<TData, TPrediction>(
this PredictionEnginePoolBuilder<TData, TPrediction> builder, string uri, TimeSpan period)
where TData : class where TPrediction : class, new()
{
return builder.FromUri(string.Empty, new Uri(uri), period);
}

/// <summary>
/// Adds the named model at the specified location to the builder.
/// </summary>
/// <param name="builder">The builder to which to add the model.</param>
/// <param name="modelName">
/// The name of the model which allows for uniquely identifying the model when
/// multiple models have the same <typeparamref name="TData"/> and
/// <typeparamref name="TPrediction"/> types.
/// </param>
/// <param name="uri">The location of the model.</param>
/// <param name="period">
/// How often to query if the model has been updated at the specified location.
/// </param>
/// <returns>
/// The updated <see cref="PredictionEnginePoolBuilder{TData, TPrediction}"/>.
/// </returns>
public static PredictionEnginePoolBuilder<TData, TPrediction> FromUri<TData, TPrediction>(
this PredictionEnginePoolBuilder<TData, TPrediction> builder, string modelName, string uri, TimeSpan period)
where TData : class
where TPrediction : class, new()
{
return builder.FromUri(modelName, new Uri(uri), period);
}

/// <summary>
/// Adds the named model at the specified location to the builder.
/// </summary>
/// <param name="builder">The builder to which to add the model.</param>
/// <param name="modelName">
/// The name of the model which allows for uniquely identifying the model when
/// multiple models have the same <typeparamref name="TData"/> and
/// <typeparamref name="TPrediction"/> types.
/// </param>
/// <param name="uri">The location of the model.</param>
/// <param name="period">
/// How often to query if the model has been updated at the specified location.
/// </param>
/// <returns>
/// The updated <see cref="PredictionEnginePoolBuilder{TData, TPrediction}"/>.
/// </returns>
public static PredictionEnginePoolBuilder<TData, TPrediction> FromUri<TData, TPrediction>(
this PredictionEnginePoolBuilder<TData, TPrediction> builder, string modelName, Uri uri, TimeSpan period)
where TData : class
where TPrediction : class, new()
{
builder.Services.AddTransient<UriModelLoader, UriModelLoader>();
builder.Services.AddOptions<PredictionEnginePoolOptions<TData, TPrediction>>(modelName)
.Configure<UriModelLoader>((opt, loader) =>
{
loader.Start(uri, period);
opt.ModelLoader = loader;
});
return builder;
}

/// <summary>
/// Adds the model at the specified file to the builder.
/// </summary>
/// <param name="builder">The builder to which to add the model.</param>
/// <param name="filePath">The location of the model.</param>
/// <returns>
/// The updated <see cref="PredictionEnginePoolBuilder{TData, TPrediction}"/>.
/// </returns>
public static PredictionEnginePoolBuilder<TData, TPrediction> FromFile<TData, TPrediction>(
this PredictionEnginePoolBuilder<TData, TPrediction> builder, string filePath)
where TData : class
where TPrediction : class, new()
{
return builder.FromFile(string.Empty, filePath, true);
}

/// <summary>
/// Adds the model at the specified file to the builder.
/// </summary>
/// <param name="builder">The builder to which to add the model.</param>
/// <param name="modelName">
/// The name of the model which allows for uniquely identifying the model when
/// multiple models have the same <typeparamref name="TData"/> and
/// <typeparamref name="TPrediction"/> types.
/// </param>
/// <param name="filePath">The location of the model.</param>
/// <returns>
/// The updated <see cref="PredictionEnginePoolBuilder{TData, TPrediction}"/>.
/// </returns>
public static PredictionEnginePoolBuilder<TData, TPrediction> FromFile<TData, TPrediction>(
this PredictionEnginePoolBuilder<TData, TPrediction> builder, string modelName, string filePath)
where TData : class
where TPrediction : class, new()
{
return builder.FromFile(modelName, filePath, true);
}

/// <summary>
/// Adds the model at the specified file to the builder.
/// </summary>
/// <param name="builder">The builder to which to add the model.</param>
/// <param name="modelName">
/// The name of the model which allows for uniquely identifying the model when
/// multiple models have the same <typeparamref name="TData"/> and
/// <typeparamref name="TPrediction"/> types.
/// </param>
/// <param name="filePath">The location of the model.</param>
/// <param name="watchForChanges">
/// Whether to watch for changes to the file path and update the model when the file is changed or not.
/// </param>
/// <returns>
/// The updated <see cref="PredictionEnginePoolBuilder{TData, TPrediction}"/>.
/// </returns>
public static PredictionEnginePoolBuilder<TData, TPrediction> FromFile<TData, TPrediction>(
this PredictionEnginePoolBuilder<TData, TPrediction> builder, string modelName, string filePath, bool watchForChanges)
where TData : class
where TPrediction : class, new()
{
builder.Services.AddTransient<FileModelLoader, FileModelLoader>();
builder.Services.AddOptions<PredictionEnginePoolOptions<TData, TPrediction>>(modelName)
.Configure<FileModelLoader>((options, loader) =>
{
loader.Start(filePath, watchForChanges);
options.ModelLoader = loader;
});
return builder;
}
}
}
33 changes: 33 additions & 0 deletions src/Microsoft.Extensions.ML/Builder/PredictionEnginePoolBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.ML;

namespace Microsoft.Extensions.ML
{
/// <summary>
/// A class that provides the mechanisms to configure a pool
/// of ML.NET <see cref="PredictionEngine{TData, TPrediction}"/> objects.
/// </summary>
public class PredictionEnginePoolBuilder<TData, TPrediction>
where TData : class
where TPrediction : class, new()
{
/// <summary>
/// Initializes a new instance of <see cref="PredictionEnginePoolBuilder{TData, TPrediction}"/>.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param>
public PredictionEnginePoolBuilder(IServiceCollection services)
{
Services = services ?? throw new ArgumentException(nameof(services));
}

/// <summary>
/// The <see cref="IServiceCollection"/> to add services to.
/// </summary>
public IServiceCollection Services { get; private set; }
}
}
Loading

0 comments on commit 40609d5

Please sign in to comment.