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] 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"; }