Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(SentimentAnalyzer): Improve performance #3

Merged
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
10 changes: 8 additions & 2 deletions Cognitive-Library/CognitiveLibrary.sln
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28307.329
# Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomVision", "CustomVision\CustomVision.csproj", "{5197E68A-CC01-4048-AD8A-EBF806507633}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SentimentAnalyzer", "SentimentAnalyzer\SentimentAnalyzer.csproj", "{7F54E5D3-408B-49F2-AFDF-3CC2FDD8C5B6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SentimentAnalyzer.Tests", "SentimentAnalyzer.Tests\SentimentAnalyzer.Tests.csproj", "{4BB7CCDE-C145-4342-BD23-31F8E1955499}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -21,6 +23,10 @@ Global
{7F54E5D3-408B-49F2-AFDF-3CC2FDD8C5B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7F54E5D3-408B-49F2-AFDF-3CC2FDD8C5B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7F54E5D3-408B-49F2-AFDF-3CC2FDD8C5B6}.Release|Any CPU.Build.0 = Release|Any CPU
{4BB7CCDE-C145-4342-BD23-31F8E1955499}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4BB7CCDE-C145-4342-BD23-31F8E1955499}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4BB7CCDE-C145-4342-BD23-31F8E1955499}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4BB7CCDE-C145-4342-BD23-31F8E1955499}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
57 changes: 57 additions & 0 deletions Cognitive-Library/SentimentAnalyzer.Tests/Benchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;

namespace SentimentAnalyzer.Tests
{
public class Benchmarks
{
private readonly ITestOutputHelper output;

public Benchmarks(ITestOutputHelper output)
{
this.output = output;
}

/// <summary>
/// Use IMDB dataset of 50k reviews to benchmark analysis.
/// See: https://github.com/nas5w/imdb-data
/// </summary>
/// <returns></returns>
[Fact]
public async Task Loop_Of_50000_IMDB_Reviews()
{
var assembly = Assembly.GetExecutingAssembly();
var stream = assembly.GetManifestResourceStream("SentimentAnalyzer.Tests.reviews.json");

Assert.NotNull(stream);

var reviews = await JsonSerializer.DeserializeAsync<Review[]>(stream);
Assert.Equal(50000, reviews.Length);

var stopWatch = new Stopwatch();
stopWatch.Start();

for(var i = 0; i < reviews.Length; i++)
{
Sentiments.Predict(reviews[i].t);
}

stopWatch.Stop();

Assert.True(stopWatch.ElapsedMilliseconds > 0);
output.WriteLine($"Loop duration: {stopWatch.ElapsedMilliseconds}ms");
}
}

public class Review
{
public string t { get; set; }
public int s { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<None Remove="reviews.json" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="reviews.json" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="System.Text.Json" Version="5.0.1" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
<PackageReference Include="coverlet.collector" Version="1.2.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\SentimentAnalyzer\SentimentAnalyzer.csproj" />
</ItemGroup>

</Project>
1 change: 1 addition & 0 deletions Cognitive-Library/SentimentAnalyzer.Tests/reviews.json

Large diffs are not rendered by default.

18 changes: 16 additions & 2 deletions Cognitive-Library/SentimentAnalyzer/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
# SentimentAnalyzer - On device (offline) Sentiment Analysis for .NET Standard apps
# SentimentAnalyzer - On-device (offline) Sentiment Analysis for .NET Standard apps

This is the library which can be used to consume the model which I created using ML.NET. For further inforamtion, you can always have a look at my [blog](https://www.arafattehsin.com/blog/sentimentanalyzer-ondevice-machine-learning/)
This is a library which can be used to consume a model which I created using ML.NET.

For further information, you can always have a look at my [blog](https://www.arafattehsin.com/blog/sentimentanalyzer-ondevice-machine-learning/)

## Quick Start

```c#
using SentimentAnalyzer;

var sentiment = Sentiments.Predict("some string");
```

`Predict` returns a `SentimentPrediction` which contains:

- `Prediction` (`bool`) - `true` is Positive sentiment, `false` is Negative sentiment.
- `Score` (`float`) - A score representing the model's accuracy
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.ML" Version="1.5.4" />
<PackageReference Include="Microsoft.ML" Version="1.5.5" />
</ItemGroup>

</Project>
49 changes: 35 additions & 14 deletions Cognitive-Library/SentimentAnalyzer/Sentiments.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,47 @@

namespace SentimentAnalyzer
{
public class Sentiments
/// <summary>
/// Thread-safe singleton to manage initialization of ML training model
/// to improve runtime performance.
/// </summary>
public sealed class Sentiments
{
public static SentimentPrediction Predict(string text)
private static readonly Sentiments instance = new Sentiments();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be [ThreadStatic] and initialized once per thread

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, addressed in #5

private readonly PredictionEngine<Sentiment, SentimentPrediction> predictionEngine;

/// <summary>
/// Explicit static constructor to tell C# compiler
/// not to mark type as beforefieldinit
/// </summary>
static Sentiments()
{
//Create MLContext to be shared across the model creation workflow objects
//Set a random seed for repeatable/deterministic results across multiple trainings.
var assembly = Assembly.GetExecutingAssembly();
var stream = assembly.GetManifestResourceStream("SentimentAnalyzer.MLModels.SentimentModel.zip");
}

var mlContext = new MLContext(seed: 1);
Sentiment statement = new Sentiment { Col0 = text };
ITransformer trainedModel = mlContext.Model.Load(stream, out var modelInputSchema);
private Sentiments()
{
var assembly = Assembly.GetExecutingAssembly();
using (var stream = assembly.GetManifestResourceStream("SentimentAnalyzer.MLModels.SentimentModel.zip"))
{
var mlContext = new MLContext();
arafattehsin marked this conversation as resolved.
Show resolved Hide resolved
ITransformer trainedModel = mlContext.Model.Load(stream, out var modelInputSchema);

// Create prediction engine related to the loaded trained model
var predEngine = mlContext.Model.CreatePredictionEngine<Sentiment, SentimentPrediction>(trainedModel);
// Create prediction engine related to the loaded trained model
predictionEngine = mlContext.Model.CreatePredictionEngine<Sentiment, SentimentPrediction>(trainedModel);

stream.Close();
stream.Close();
}
}

//Score
return predEngine.Predict(statement);
/// <summary>
/// Perform a single prediction of the given text
arafattehsin marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
public static SentimentPrediction Predict(string text)
{
var statement = new Sentiment { Col0 = text };
return instance.predictionEngine.Predict(statement);
}

}
Expand Down