From 0d017f1e863c7809190b9ea7fe1ccabefee6f557 Mon Sep 17 00:00:00 2001 From: Wenbo Cao <104199@smsassist.com> Date: Thu, 31 Aug 2023 09:52:09 -0500 Subject: [PATCH 1/3] Add DialoguePrediction --- .../BotSharp.Plugin.RoutingSpeeder.csproj | 6 + .../Providers/DialogueClassifier.cs | 144 ++++++++++++++++++ .../Models/DialoguePredictionModel.cs | 13 ++ .../Providers/fastTextEmbeddingProvider.cs | 70 +++++++++ .../RoutingConversationHook.cs | 16 ++ .../RoutingSpeederPlugin.cs | 8 + .../Settings/classifierSetting.cs | 21 +++ .../Settings/fastTextSetting.cs | 7 + .../Settings/routerSpeedSettings.cs | 11 ++ .../Settings/trainingParams.cs | 13 ++ 10 files changed, 309 insertions(+) create mode 100644 src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/DialogueClassifier.cs create mode 100644 src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/Models/DialoguePredictionModel.cs create mode 100644 src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/fastTextEmbeddingProvider.cs create mode 100644 src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/classifierSetting.cs create mode 100644 src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/fastTextSetting.cs create mode 100644 src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/routerSpeedSettings.cs create mode 100644 src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/trainingParams.cs diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/BotSharp.Plugin.RoutingSpeeder.csproj b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/BotSharp.Plugin.RoutingSpeeder.csproj index 79a54b27..e0a8e7af 100644 --- a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/BotSharp.Plugin.RoutingSpeeder.csproj +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/BotSharp.Plugin.RoutingSpeeder.csproj @@ -11,4 +11,10 @@ + + + + + + diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/DialogueClassifier.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/DialogueClassifier.cs new file mode 100644 index 00000000..788eb641 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/DialogueClassifier.cs @@ -0,0 +1,144 @@ +using System; +using System.IO; +using System.Text; +using System.Collections.Generic; +using Tensorflow; +using static Tensorflow.KerasApi; +using Tensorflow.Keras.Engine; +using Tensorflow.NumPy; +using static Tensorflow.Binding; +using Tensorflow.Keras.Callbacks; +using System.Text.RegularExpressions; +using BotSharp.Plugin.RoutingSpeeder.Settings; +using BotSharp.Abstraction.MLTasks; +using BotSharp.Plugin.RoutingSpeeder.Providers.Models; +using Microsoft.Extensions.DependencyInjection; +using System.Linq; +using Tensorflow.Keras; + +namespace BotSharp.Plugin.RoutingSpeeder.Providers; + +public class DialogueClassifier +{ + private readonly IServiceProvider _services; + Model _model; + public Model model => _model; + private bool _isModelReady; + public bool isModelReady => _isModelReady; + private classifierSetting _settings; + + public DialogueClassifier(IServiceProvider services, classifierSetting settings) + { + _services = services; + _settings = settings; + } + + private void Reset() + { + keras.backend.clear_session(); + _isModelReady = false; + } + + private void Build() + { + if (_isModelReady) + { + return; + } + + var layers = new List + { + keras.layers.InputLayer((300), name: "Input"), + keras.layers.Dense(256, activation:"relu"), + keras.layers.Dense(256, activation:"relu"), + keras.layers.Dense(_settings.labelMappingDict.Count, activation: keras.activations.Softmax) + }; + _model = keras.Sequential(layers); + +#if DEBUG + Console.WriteLine(); + _model.summary(); +#endif + _isModelReady = true; + } + + private void Fit(NDArray x, NDArray y, TrainingParams trainingParams) + { + // release more memory + var vector = _services.GetRequiredService(); + // vector.UnloadModel(); + + _model.compile(optimizer: keras.optimizers.Adam(trainingParams.LearningRate), + loss: keras.losses.SparseCategoricalCrossentropy(), + metrics: new[] { "accuracy" } + ); + + CallbackParams callback_parameters = new CallbackParams + { + Model = _model, + Epochs = trainingParams.Epochs, + Verbose = 1, + Steps = 10 + }; + + ICallback earlyStop = new EarlyStopping(callback_parameters, "accuracy"); + + var callbacks = new List() { earlyStop }; + + var weights = LoadWeights(); + + _model.fit(x, y, + batch_size: trainingParams.BatchSize, + epochs: trainingParams.Epochs, + callbacks: callbacks, + // validation_split: 0.1f, + shuffle: true); + + _model.save_weights(weights); + + _isModelReady = true; + } + + public string LoadWeights() + { + var weightsFile = Path.Combine(_settings.MODEL_DIR, $"wo-dialogue-classifier.h5"); + if (File.Exists(weightsFile)) + { + _model.load_weights(weightsFile); + Console.WriteLine($"Successfully load the weights!"); + } + else + { + Console.WriteLine("No available weights."); + } + return weightsFile; + } + + public (NDArray x, NDArray y) Vectorize(List items) + { + var x = np.zeros((items.Count, 300), dtype: np.float32); + var y = np.zeros((items.Count, 1), dtype: np.float32); + + var vector = _services.GetRequiredService(); + + for (int i = 0; i < items.Count; i++) + { + x[i] = vector.GetVector(TextClean(items[i].text)); + if (_settings.labelMappingDict.ContainsKey(items[i].label)) + { + y[i] = _settings.labelMappingDict[items[i].label]; + } + } + return (x, y); + } + public string TextClean(string text) + { + // Remove punctuation + // Remove digits + // To lowercase + var processedText = Regex.Replace(text, "[AB0-9]", " "); + processedText = string.Join("", processedText.Select(c => char.IsPunctuation(c) ? ' ' : c).ToList()); + processedText = processedText.Replace(" ", " ").ToLower(); + return processedText; + } +} diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/Models/DialoguePredictionModel.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/Models/DialoguePredictionModel.cs new file mode 100644 index 00000000..4641b9cd --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/Models/DialoguePredictionModel.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BotSharp.Plugin.RoutingSpeeder.Providers.Models; + +public class DialoguePredictionModel +{ + public int Id { get; set; } + public string text { get; set; } + public string? label { get; set; } + public string? prediction { get; set; } +} diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/fastTextEmbeddingProvider.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/fastTextEmbeddingProvider.cs new file mode 100644 index 00000000..79925418 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/fastTextEmbeddingProvider.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime; +using System.Text; +using System.Text.RegularExpressions; +using BotSharp.Abstraction.MLTasks; +using BotSharp.Plugin.RoutingSpeeder.Settings; +using FastText.NetWrapper; + +namespace BotSharp.Plugin.RoutingSpeeder.Providers; + +public class fastTextEmbeddingProvider : ITextEmbedding +{ + private FastTextWrapper _fastText; + private readonly fastTextSetting _settings; + + public int Dimension + { + get + { + if (!_fastText.IsModelReady()) + { + _fastText.LoadModel(_settings.ModelPath); + } + return _fastText.GetModelDimension(); + } + } + + public fastTextEmbeddingProvider(fastTextSetting settings) + { + _settings = settings; + + } + + public float[] GetVector(string text) + { + LoadModel(); + return _fastText.GetSentenceVector(text); + } + + public List GetVectors(List texts) + { + LoadModel(); + var vectors = new List(); + for (int i = 0; i < texts.Count; i++) + { + vectors.Add(GetVector(texts[i])); + } + return vectors; + } + + private void LoadModel() + { + if (_fastText == null) + { + if (!File.Exists(_settings.ModelPath)) + { + throw new FileNotFoundException($"Can't load pre-trained word vectors from {_settings.ModelPath}.\n Try to download from https://fasttext.cc/docs/en/english-vectors.html."); + } + + _fastText = new FastTextWrapper(); + + if (!_fastText.IsModelReady()) + { + _fastText.LoadModel(_settings.ModelPath); + } + } + } +} diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingConversationHook.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingConversationHook.cs index 50d74fe8..bc37e117 100644 --- a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingConversationHook.cs +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingConversationHook.cs @@ -1,13 +1,29 @@ using BotSharp.Abstraction.Conversations; using BotSharp.Abstraction.Conversations.Models; +using BotSharp.Abstraction.MLTasks; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Linq; using System.Threading.Tasks; +using FastText.NetWrapper; +using BotSharp.Plugin.RoutingSpeeder.Settings; namespace BotSharp.Plugin.RoutingSpeeder; public class RoutingConversationHook: ConversationHookBase { + private readonly IServiceProvider _services; + private routerSpeedSettings _settings; + public RoutingConversationHook(IServiceProvider service, routerSpeedSettings settings) + { + _services = service; + _settings = settings; + } public override async Task BeforeCompletion(RoleDialogModel message) { + var embedding = _services.GetServices() + .FirstOrDefault(x => x.GetType().FullName.EndsWith(_settings.TextEmbedding)); + // Utilize local discriminative model to predict intent message.Content = "response content"; message.StopCompletion = true; diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingSpeederPlugin.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingSpeederPlugin.cs index 28c856ba..bcb91335 100644 --- a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingSpeederPlugin.cs +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingSpeederPlugin.cs @@ -1,5 +1,8 @@ using BotSharp.Abstraction.Conversations; +using BotSharp.Abstraction.MLTasks; using BotSharp.Abstraction.Plugins; +using BotSharp.Plugin.RoutingSpeeder.Settings; +using BotSharp.Plugin.RoutingSpeeder.Providers; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -9,6 +12,11 @@ public class RoutingSpeederPlugin : IBotSharpPlugin { public void RegisterDI(IServiceCollection services, IConfiguration config) { + var settings = new routerSpeedSettings(); + config.Bind("routerSpeed", settings); + services.AddSingleton(x => settings); + services.AddSingleton(x => settings.fastText); services.AddScoped(); + services.AddSingleton(); } } diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/classifierSetting.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/classifierSetting.cs new file mode 100644 index 00000000..2fe13237 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/classifierSetting.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BotSharp.Plugin.RoutingSpeeder.Settings; + +public class classifierSetting +{ + + public Dictionary labelMappingDict { get; set; } = new Dictionary() + { + {"goodbye", 0f}, + {"greeting", 1f}, + {"other", 2f}, + {"wo-followup", 3f}, + {"wo-identifer", 4f}, + {"wo-scheduler", 5} + }; + public string RAW_DATA_DIR { get; set; } = "C:\\new_wenbocao\\one_brain\\WebStarter\\data\\raw_data"; + public string MODEL_DIR { get; set; } = "C:\\new_wenbocao\\one_brain\\WebStarter\\data\\models"; +} diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/fastTextSetting.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/fastTextSetting.cs new file mode 100644 index 00000000..e5554402 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/fastTextSetting.cs @@ -0,0 +1,7 @@ + +namespace BotSharp.Plugin.RoutingSpeeder.Settings; + +public class fastTextSetting +{ + public string ModelPath { get; set; } +} diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/routerSpeedSettings.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/routerSpeedSettings.cs new file mode 100644 index 00000000..300ec9e0 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/routerSpeedSettings.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BotSharp.Plugin.RoutingSpeeder.Settings; + +public class routerSpeedSettings +{ + public fastTextSetting fastText { get; set; } + public string TextEmbedding { get; set; } +} diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/trainingParams.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/trainingParams.cs new file mode 100644 index 00000000..fcf15ce3 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/trainingParams.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BotSharp.Plugin.RoutingSpeeder.Settings; + +public class TrainingParams +{ + public int ClientId { get; set; } + public int Epochs { get; set; } = 10; + public int BatchSize { get; set; } = 16; + public float LearningRate { get; set; } = 1.0e-4f; +} From 890df8743a86ffd7016614f21923681751f3cfd5 Mon Sep 17 00:00:00 2001 From: Wenbo Cao <104199@smsassist.com> Date: Thu, 31 Aug 2023 10:48:59 -0500 Subject: [PATCH 2/3] Add intent classifier in routing speeder. --- .../Settings}/KnowledgeBaseSettings.cs | 2 +- .../Plugins/Knowledges/KnowledgeBasePlugin.cs | 1 + .../Knowledges/Services/KnowledgeService.cs | 2 +- .../Controllers/KnowledgeController.cs | 2 +- .../BotSharp.Plugin.RoutingSpeeder.csproj | 6 +- ...logueClassifier.cs => IntentClassifier.cs} | 31 ++++---- .../Models/TrainingParams.cs} | 2 +- .../Providers/fastTextEmbeddingProvider.cs | 70 ------------------- .../RoutingConversationHook.cs | 11 +-- .../RoutingSpeederPlugin.cs | 10 +-- .../Settings/classifierSetting.cs | 15 ++-- .../Settings/fastTextSetting.cs | 7 -- .../Settings/routerSpeedSettings.cs | 4 +- src/WebStarter/appsettings.json | 13 ++-- 14 files changed, 53 insertions(+), 123 deletions(-) rename src/Infrastructure/{BotSharp.Core/Plugins/Knowledges => BotSharp.Abstraction/Knowledges/Settings}/KnowledgeBaseSettings.cs (81%) rename src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/{DialogueClassifier.cs => IntentClassifier.cs} (79%) rename src/Plugins/BotSharp.Plugin.RoutingSpeeder/{Settings/trainingParams.cs => Providers/Models/TrainingParams.cs} (82%) delete mode 100644 src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/fastTextEmbeddingProvider.cs delete mode 100644 src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/fastTextSetting.cs diff --git a/src/Infrastructure/BotSharp.Core/Plugins/Knowledges/KnowledgeBaseSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Settings/KnowledgeBaseSettings.cs similarity index 81% rename from src/Infrastructure/BotSharp.Core/Plugins/Knowledges/KnowledgeBaseSettings.cs rename to src/Infrastructure/BotSharp.Abstraction/Knowledges/Settings/KnowledgeBaseSettings.cs index 0cb08a48..97f7f55c 100644 --- a/src/Infrastructure/BotSharp.Core/Plugins/Knowledges/KnowledgeBaseSettings.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Settings/KnowledgeBaseSettings.cs @@ -1,4 +1,4 @@ -namespace BotSharp.Core.Plugins.Knowledges; +namespace BotSharp.Abstraction.Knowledges.Settings; public class KnowledgeBaseSettings { diff --git a/src/Infrastructure/BotSharp.Core/Plugins/Knowledges/KnowledgeBasePlugin.cs b/src/Infrastructure/BotSharp.Core/Plugins/Knowledges/KnowledgeBasePlugin.cs index e0c74e83..8f862ca4 100644 --- a/src/Infrastructure/BotSharp.Core/Plugins/Knowledges/KnowledgeBasePlugin.cs +++ b/src/Infrastructure/BotSharp.Core/Plugins/Knowledges/KnowledgeBasePlugin.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Knowledges.Settings; using BotSharp.Core.Plugins.Knowledges.Services; using Microsoft.Extensions.Configuration; diff --git a/src/Infrastructure/BotSharp.Core/Plugins/Knowledges/Services/KnowledgeService.cs b/src/Infrastructure/BotSharp.Core/Plugins/Knowledges/Services/KnowledgeService.cs index 9ed87e6a..3a627ec9 100644 --- a/src/Infrastructure/BotSharp.Core/Plugins/Knowledges/Services/KnowledgeService.cs +++ b/src/Infrastructure/BotSharp.Core/Plugins/Knowledges/Services/KnowledgeService.cs @@ -1,7 +1,7 @@ using BotSharp.Abstraction.Knowledges.Models; +using BotSharp.Abstraction.Knowledges.Settings; using BotSharp.Abstraction.MLTasks; using BotSharp.Abstraction.VectorStorage; -using System.Text.Json; namespace BotSharp.Core.Plugins.Knowledges.Services; diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeController.cs index 3824f476..bea5597b 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeController.cs @@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Http; using UglyToad.PdfPig.Content; using UglyToad.PdfPig; using BotSharp.Core.Plugins.Knowledges; - +using BotSharp.Abstraction.Knowledges.Settings; namespace BotSharp.OpenAPI.Controllers; diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/BotSharp.Plugin.RoutingSpeeder.csproj b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/BotSharp.Plugin.RoutingSpeeder.csproj index e0a8e7af..fb145e8d 100644 --- a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/BotSharp.Plugin.RoutingSpeeder.csproj +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/BotSharp.Plugin.RoutingSpeeder.csproj @@ -8,13 +8,11 @@ - + - - - + diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/DialogueClassifier.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs similarity index 79% rename from src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/DialogueClassifier.cs rename to src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs index 788eb641..ea74f54d 100644 --- a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/DialogueClassifier.cs +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs @@ -15,19 +15,20 @@ using BotSharp.Plugin.RoutingSpeeder.Providers.Models; using Microsoft.Extensions.DependencyInjection; using System.Linq; using Tensorflow.Keras; +using BotSharp.Abstraction.Knowledges.Settings; namespace BotSharp.Plugin.RoutingSpeeder.Providers; -public class DialogueClassifier +public class IntentClassifier { private readonly IServiceProvider _services; Model _model; public Model model => _model; private bool _isModelReady; public bool isModelReady => _isModelReady; - private classifierSetting _settings; + private ClassifierSetting _settings; - public DialogueClassifier(IServiceProvider services, classifierSetting settings) + public IntentClassifier(IServiceProvider services, ClassifierSetting settings) { _services = services; _settings = settings; @@ -51,7 +52,7 @@ public class DialogueClassifier keras.layers.InputLayer((300), name: "Input"), keras.layers.Dense(256, activation:"relu"), keras.layers.Dense(256, activation:"relu"), - keras.layers.Dense(_settings.labelMappingDict.Count, activation: keras.activations.Softmax) + keras.layers.Dense(_settings.LabelMappingDict.Count, activation: keras.activations.Softmax) }; _model = keras.Sequential(layers); @@ -64,10 +65,6 @@ public class DialogueClassifier private void Fit(NDArray x, NDArray y, TrainingParams trainingParams) { - // release more memory - var vector = _services.GetRequiredService(); - // vector.UnloadModel(); - _model.compile(optimizer: keras.optimizers.Adam(trainingParams.LearningRate), loss: keras.losses.SparseCategoricalCrossentropy(), metrics: new[] { "accuracy" } @@ -101,7 +98,7 @@ public class DialogueClassifier public string LoadWeights() { - var weightsFile = Path.Combine(_settings.MODEL_DIR, $"wo-dialogue-classifier.h5"); + var weightsFile = Path.Combine(_settings.MODEL_DIR, $"intent-classifier.h5"); if (File.Exists(weightsFile)) { _model.load_weights(weightsFile); @@ -116,7 +113,7 @@ public class DialogueClassifier public (NDArray x, NDArray y) Vectorize(List items) { - var x = np.zeros((items.Count, 300), dtype: np.float32); + var x = np.zeros((items.Count, vector.Dimension), dtype: np.float32); var y = np.zeros((items.Count, 1), dtype: np.float32); var vector = _services.GetRequiredService(); @@ -124,13 +121,23 @@ public class DialogueClassifier for (int i = 0; i < items.Count; i++) { x[i] = vector.GetVector(TextClean(items[i].text)); - if (_settings.labelMappingDict.ContainsKey(items[i].label)) + if (_settings.LabelMappingDict.ContainsKey(items[i].label)) { - y[i] = _settings.labelMappingDict[items[i].label]; + y[i] = _settings.LabelMappingDict[items[i].label]; } } return (x, y); } + + public float[] GetTextEmbedding(string text) + { + var knowledgeSettings = _services.GetRequiredService(); + var embedding = _services.GetServices() + .FirstOrDefault(x => x.GetType().FullName.EndsWith(knowledgeSettings.TextEmbedding)); + + return embedding.GetVector(text); + } + public string TextClean(string text) { // Remove punctuation diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/trainingParams.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/Models/TrainingParams.cs similarity index 82% rename from src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/trainingParams.cs rename to src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/Models/TrainingParams.cs index fcf15ce3..f3c822ac 100644 --- a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/trainingParams.cs +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/Models/TrainingParams.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Text; -namespace BotSharp.Plugin.RoutingSpeeder.Settings; +namespace BotSharp.Plugin.RoutingSpeeder.Providers.Models; public class TrainingParams { diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/fastTextEmbeddingProvider.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/fastTextEmbeddingProvider.cs deleted file mode 100644 index 79925418..00000000 --- a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/fastTextEmbeddingProvider.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Runtime; -using System.Text; -using System.Text.RegularExpressions; -using BotSharp.Abstraction.MLTasks; -using BotSharp.Plugin.RoutingSpeeder.Settings; -using FastText.NetWrapper; - -namespace BotSharp.Plugin.RoutingSpeeder.Providers; - -public class fastTextEmbeddingProvider : ITextEmbedding -{ - private FastTextWrapper _fastText; - private readonly fastTextSetting _settings; - - public int Dimension - { - get - { - if (!_fastText.IsModelReady()) - { - _fastText.LoadModel(_settings.ModelPath); - } - return _fastText.GetModelDimension(); - } - } - - public fastTextEmbeddingProvider(fastTextSetting settings) - { - _settings = settings; - - } - - public float[] GetVector(string text) - { - LoadModel(); - return _fastText.GetSentenceVector(text); - } - - public List GetVectors(List texts) - { - LoadModel(); - var vectors = new List(); - for (int i = 0; i < texts.Count; i++) - { - vectors.Add(GetVector(texts[i])); - } - return vectors; - } - - private void LoadModel() - { - if (_fastText == null) - { - if (!File.Exists(_settings.ModelPath)) - { - throw new FileNotFoundException($"Can't load pre-trained word vectors from {_settings.ModelPath}.\n Try to download from https://fasttext.cc/docs/en/english-vectors.html."); - } - - _fastText = new FastTextWrapper(); - - if (!_fastText.IsModelReady()) - { - _fastText.LoadModel(_settings.ModelPath); - } - } - } -} diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingConversationHook.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingConversationHook.cs index 2307c079..e742f169 100644 --- a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingConversationHook.cs +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingConversationHook.cs @@ -7,24 +7,25 @@ using Microsoft.Extensions.DependencyInjection; using System; using System.Linq; using System.Threading.Tasks; -using FastText.NetWrapper; using BotSharp.Plugin.RoutingSpeeder.Settings; +using BotSharp.Abstraction.Templating; +using BotSharp.Plugin.RoutingSpeeder.Providers; namespace BotSharp.Plugin.RoutingSpeeder; public class RoutingConversationHook: ConversationHookBase { private readonly IServiceProvider _services; - private routerSpeedSettings _settings; - public RoutingConversationHook(IServiceProvider service, routerSpeedSettings settings) + private RouterSpeederSettings _settings; + public RoutingConversationHook(IServiceProvider service, RouterSpeederSettings settings) { _services = service; _settings = settings; } public override async Task BeforeCompletion(RoleDialogModel message) { - var embedding = _services.GetServices() - .FirstOrDefault(x => x.GetType().FullName.EndsWith(_settings.TextEmbedding)); + var intentClassifier = _services.GetRequiredService(); + var vector = intentClassifier.GetTextEmbedding(message.Content); // Utilize local discriminative model to predict intent message.IntentName = "greeting"; diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingSpeederPlugin.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingSpeederPlugin.cs index bcb91335..c3dac3a2 100644 --- a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingSpeederPlugin.cs +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingSpeederPlugin.cs @@ -12,11 +12,13 @@ public class RoutingSpeederPlugin : IBotSharpPlugin { public void RegisterDI(IServiceCollection services, IConfiguration config) { - var settings = new routerSpeedSettings(); - config.Bind("routerSpeed", settings); + var settings = new RouterSpeederSettings(); + config.Bind("RouterSpeeder", settings); services.AddSingleton(x => settings); - services.AddSingleton(x => settings.fastText); + + services.AddSingleton(); + services.AddScoped(); - services.AddSingleton(); + services.AddSingleton(); } } diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/classifierSetting.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/classifierSetting.cs index 2fe13237..e6352694 100644 --- a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/classifierSetting.cs +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/classifierSetting.cs @@ -4,18 +4,15 @@ using System.Text; namespace BotSharp.Plugin.RoutingSpeeder.Settings; -public class classifierSetting +public class ClassifierSetting { - - public Dictionary labelMappingDict { get; set; } = new Dictionary() + public Dictionary LabelMappingDict { get; set; } = new Dictionary() { {"goodbye", 0f}, {"greeting", 1f}, - {"other", 2f}, - {"wo-followup", 3f}, - {"wo-identifer", 4f}, - {"wo-scheduler", 5} + {"other", 2f} }; - public string RAW_DATA_DIR { get; set; } = "C:\\new_wenbocao\\one_brain\\WebStarter\\data\\raw_data"; - public string MODEL_DIR { get; set; } = "C:\\new_wenbocao\\one_brain\\WebStarter\\data\\models"; + + public string RAW_DATA_DIR { get; set; } = ""; + public string MODEL_DIR { get; set; } = ""; } diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/fastTextSetting.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/fastTextSetting.cs deleted file mode 100644 index e5554402..00000000 --- a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/fastTextSetting.cs +++ /dev/null @@ -1,7 +0,0 @@ - -namespace BotSharp.Plugin.RoutingSpeeder.Settings; - -public class fastTextSetting -{ - public string ModelPath { get; set; } -} diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/routerSpeedSettings.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/routerSpeedSettings.cs index 300ec9e0..fbedf581 100644 --- a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/routerSpeedSettings.cs +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/routerSpeedSettings.cs @@ -4,8 +4,6 @@ using System.Text; namespace BotSharp.Plugin.RoutingSpeeder.Settings; -public class routerSpeedSettings +public class RouterSpeederSettings { - public fastTextSetting fastText { get; set; } - public string TextEmbedding { get; set; } } diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index 42ef6319..e409738f 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -47,11 +47,14 @@ } }, - "MetaAi": { - "fastText": { - "ModelPath": "crawl-300d-2M-subword.bin" - } - }, + "MetaAi": { + "fastText": { + "ModelPath": "crawl-300d-2M-subword.bin" + } + }, + + "RoutingSpeeder": { + }, "MetaMessenger": { "Endpoint": "https://graph.facebook.com", From 7762627f8254d885b1eeea0ef1765c2920c682e2 Mon Sep 17 00:00:00 2001 From: Wenbo Cao <104199@smsassist.com> Date: Thu, 31 Aug 2023 17:00:31 -0500 Subject: [PATCH 3/3] Add intent classifier --- .../Templating/ResponseTemplateService.cs | 15 ++- .../Providers/IntentClassifier.cs | 108 ++++++++++++++++-- .../RoutingConversationHook.cs | 6 +- .../Settings/classifierSetting.cs | 4 +- 4 files changed, 121 insertions(+), 12 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core/Templating/ResponseTemplateService.cs b/src/Infrastructure/BotSharp.Core/Templating/ResponseTemplateService.cs index b7be790a..ef2c23ba 100644 --- a/src/Infrastructure/BotSharp.Core/Templating/ResponseTemplateService.cs +++ b/src/Infrastructure/BotSharp.Core/Templating/ResponseTemplateService.cs @@ -46,6 +46,10 @@ public class ResponseTemplateService : IResponseTemplateService // Find response template var agentService = _services.GetRequiredService(); var dir = Path.Combine(agentService.GetAgentDataDir(agentId), "responses"); + if (!Directory.Exists(dir)) + { + return string.Empty; + } var responses = Directory.GetFiles(dir) .Where(f => f.Split(Path.DirectorySeparatorChar).Last().Split('.')[1] == message.IntentName) .ToList(); @@ -62,8 +66,15 @@ public class ResponseTemplateService : IResponseTemplateService // Convert args and execute data to dictionary var dict = new Dictionary(); - ExtractArgs(JsonSerializer.Deserialize(message.FunctionArgs), dict); - ExtractExecuteData(message.ExecutionData, dict); + if (!string.IsNullOrEmpty(message.FunctionArgs)) + { + ExtractArgs(JsonSerializer.Deserialize(message.FunctionArgs), dict); + } + + if (message.ExecutionData != null) + { + ExtractExecuteData(message.ExecutionData, dict); + } var text = render.Render(template, dict); diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs index ea74f54d..382b3a98 100644 --- a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs @@ -16,6 +16,10 @@ using Microsoft.Extensions.DependencyInjection; using System.Linq; using Tensorflow.Keras; using BotSharp.Abstraction.Knowledges.Settings; +using System.Numerics; +using Newtonsoft.Json; +using Tensorflow.Keras.Layers; +using BotSharp.Abstraction.Agents; namespace BotSharp.Plugin.RoutingSpeeder.Providers; @@ -47,12 +51,14 @@ public class IntentClassifier return; } + var vector = _services.GetRequiredService(); + var layers = new List { - keras.layers.InputLayer((300), name: "Input"), + keras.layers.InputLayer((vector.Dimension), name: "Input"), keras.layers.Dense(256, activation:"relu"), keras.layers.Dense(256, activation:"relu"), - keras.layers.Dense(_settings.LabelMappingDict.Count, activation: keras.activations.Softmax) + keras.layers.Dense(GetLabels().Length, activation: keras.activations.Softmax) }; _model = keras.Sequential(layers); @@ -98,10 +104,13 @@ public class IntentClassifier public string LoadWeights() { - var weightsFile = Path.Combine(_settings.MODEL_DIR, $"intent-classifier.h5"); + var agentService = _services.CreateScope().ServiceProvider.GetRequiredService(); + + var weightsFile = Path.Combine(agentService.GetDataDir(), _settings.MODEL_DIR, $"intent-classifier.h5"); if (File.Exists(weightsFile)) { _model.load_weights(weightsFile); + _isModelReady = true; Console.WriteLine($"Successfully load the weights!"); } else @@ -113,11 +122,11 @@ public class IntentClassifier public (NDArray x, NDArray y) Vectorize(List items) { + var vector = _services.GetRequiredService(); + var x = np.zeros((items.Count, vector.Dimension), dtype: np.float32); var y = np.zeros((items.Count, 1), dtype: np.float32); - var vector = _services.GetRequiredService(); - for (int i = 0; i < items.Count; i++) { x[i] = vector.GetVector(TextClean(items[i].text)); @@ -129,13 +138,65 @@ public class IntentClassifier return (x, y); } - public float[] GetTextEmbedding(string text) + public NDArray GetTextEmbedding(string text) { var knowledgeSettings = _services.GetRequiredService(); var embedding = _services.GetServices() .FirstOrDefault(x => x.GetType().FullName.EndsWith(knowledgeSettings.TextEmbedding)); - return embedding.GetVector(text); + var x = np.zeros((1, embedding.Dimension), dtype: np.float32); + x[0] = embedding.GetVector(text); + return x; + } + + public (NDArray, NDArray) PrepareLoadData() + { + var agentService = _services.CreateScope().ServiceProvider.GetRequiredService(); + string rootDirectory = Path.Combine(agentService.GetDataDir(), _settings.RAW_DATA_DIR); + + + if (!Directory.Exists(rootDirectory)) + { + throw new Exception($"No training data found! Please put training data in this path: {rootDirectory}"); + } + + var vector = _services.GetRequiredService(); + + + var vectorList = new List(); + + var labelList = new List(); + foreach (var filePath in GetFiles()) + { + var texts = File.ReadAllLines(filePath, Encoding.UTF8).Select(x => TextClean(x)).ToList(); + vectorList.AddRange(vector.GetVectors(texts)); + string fileName = Path.GetFileNameWithoutExtension(filePath); + labelList.AddRange(Enumerable.Repeat(fileName, texts.Count).ToList()); + } + + var uniqueLabelList = labelList.Distinct().ToList(); + + var x = np.zeros((vectorList.Count, vector.Dimension), dtype: np.float32); + var y = np.zeros((vectorList.Count, 1), dtype: np.float32); + + for (int i = 0; i < vectorList.Count; i++) + { + x[i] = vectorList[i]; + y[i] = (float)uniqueLabelList.IndexOf(labelList[i]); + } + return (x, y); + } + + public string[] GetFiles() + { + var agentService = _services.CreateScope().ServiceProvider.GetRequiredService(); + string rootDirectory = Path.Combine(agentService.GetDataDir(), _settings.RAW_DATA_DIR); + return Directory.GetFiles(rootDirectory).OrderBy(x => x).ToArray(); + } + + public string[] GetLabels() + { + return GetFiles().Select(x => Path.GetFileNameWithoutExtension(x)).ToArray(); } public string TextClean(string text) @@ -148,4 +209,37 @@ public class IntentClassifier processedText = processedText.Replace(" ", " ").ToLower(); return processedText; } + + public string Predict(NDArray vector) + { + if (!_isModelReady) + { + InitClassifer(); + } + + var prob = _model.predict(vector); + var probLabel = tf.arg_max(prob, -1).numpy(); + // var prediction = _settings.LabelMappingDict.First(x => x.Value == probLabel[0]).Key; + + var prediction = GetLabels()[probLabel[0]]; + // var prediction = GetLabels().Where((x, i) => i == probLabel[0]).First(); + + return prediction; + } + public void InitClassifer() + { + Reset(); + Build(); + LoadWeights(); + } + + public void Train() + { + var trainingParams = new TrainingParams(); + Reset(); + Build(); + (var x, var y) = PrepareLoadData(); + Fit(x, y, trainingParams); + + } } diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingConversationHook.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingConversationHook.cs index e742f169..fbb63acc 100644 --- a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingConversationHook.cs +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingConversationHook.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using BotSharp.Plugin.RoutingSpeeder.Settings; using BotSharp.Abstraction.Templating; using BotSharp.Plugin.RoutingSpeeder.Providers; +using System.Runtime.InteropServices; namespace BotSharp.Plugin.RoutingSpeeder; @@ -27,8 +28,11 @@ public class RoutingConversationHook: ConversationHookBase var intentClassifier = _services.GetRequiredService(); var vector = intentClassifier.GetTextEmbedding(message.Content); + // intentClassifier.Train(); // Utilize local discriminative model to predict intent - message.IntentName = "greeting"; + var predText = intentClassifier.Predict(vector); + + message.IntentName = predText; // Render by template var templateService = _services.GetRequiredService(); diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/classifierSetting.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/classifierSetting.cs index e6352694..d051f2e7 100644 --- a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/classifierSetting.cs +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/classifierSetting.cs @@ -13,6 +13,6 @@ public class ClassifierSetting {"other", 2f} }; - public string RAW_DATA_DIR { get; set; } = ""; - public string MODEL_DIR { get; set; } = ""; + public string RAW_DATA_DIR { get; set; } = "raw_data"; + public string MODEL_DIR { get; set; } = "models"; }