Intergate NBClassifier into NLP pipeline.

This commit is contained in:
Oceania2018 2018-09-12 15:31:20 -05:00
parent 347cd5cd9c
commit 30f0eea87c
8 changed files with 137 additions and 50 deletions

View file

@ -10,5 +10,7 @@ namespace BotSharp.Algorithm.Bayes
public List<Probability> LabelDist { get; set; } public List<Probability> LabelDist { get; set; }
public Dictionary<string, double> CondProbDictionary { get; set; } public Dictionary<string, double> CondProbDictionary { get; set; }
public List<String> Values { get; set; }
} }
} }

View file

@ -22,25 +22,22 @@ namespace BotSharp.Core.Engines.BotSharp
public async Task<bool> Train(Agent agent, NlpDoc doc, PipeModel meta) public async Task<bool> Train(Agent agent, NlpDoc doc, PipeModel meta)
{ {
meta.Model = "classification-nb.model"; meta.Model = "classification-nb.model";
string modelFileName = Path.Combine(Settings.ModelDir, meta.Model); string modelFileName = Path.Combine(Settings.ModelDir, meta.Model);
var encoder = new OneHotEncoder(); var options = new ClassifyOptions
encoder.Sentences = doc.Sentences.Select(x => new NLP.Sentence {
ModelFilePath = modelFileName
};
var classifier = new ClassifierFactory<NaiveBayesClassifier, SentenceFeatureExtractor>(options, SupportedLanguage.English);
var sentences = doc.Sentences.Select(x => new Sentence
{ {
Label = x.Intent.Label, Label = x.Intent.Label,
Text = x.Text, Text = x.Text,
Words = x.Tokens Words = x.Tokens
}).ToList(); }).ToList();
encoder.EncodeAll();
var options = new ClassifyOptions classifier.Train(sentences);
{
TrainingCorpusDir = Path.Combine(Configuration.GetValue<String>("MachineLearning:dataDir"), "Text Classification", "cooking.stackexchange")
};
var classifier = new ClassifierFactory<NaiveBayesClassifier, SentenceFeatureExtractor>(options, SupportedLanguage.English);
classifier.Train(encoder.Sentences);
Console.WriteLine($"Saved model to {modelFileName}"); Console.WriteLine($"Saved model to {modelFileName}");
meta.Meta = new JObject(); meta.Meta = new JObject();
@ -51,6 +48,28 @@ namespace BotSharp.Core.Engines.BotSharp
public async Task<bool> Predict(Agent agent, NlpDoc doc, PipeModel meta) public async Task<bool> Predict(Agent agent, NlpDoc doc, PipeModel meta)
{ {
var options = new ClassifyOptions
{
ModelFilePath = Path.Combine(Settings.ModelDir, meta.Model)
};
var classifier = new ClassifierFactory<NaiveBayesClassifier, SentenceFeatureExtractor>(options, SupportedLanguage.English);
var sentence = doc.Sentences.Select(s => new Sentence
{
Text = s.Text,
Words = s.Tokens
}).First();
var result = classifier.Classify(sentence);
doc.Sentences[0].Intent = new TextClassificationResult
{
Classifier = "BotSharpNBayesClassifier",
Label = result.First().Item1,
Confidence = (decimal)result.First().Item2
};
return true; return true;
} }
} }

View file

@ -36,17 +36,14 @@ namespace BotSharp.NLP.UnitTest
sentences.Shuffle(); sentences.Shuffle();
var encoder = new OneHotEncoder();
encoder.Sentences = sentences;
encoder.EncodeAll();
var options = new ClassifyOptions var options = new ClassifyOptions
{ {
ModelFilePath = Path.Combine(Configuration.GetValue<String>("MachineLearning:dataDir"), "Text Classification", "cooking.stackexchange", "nb.model"),
TrainingCorpusDir = Path.Combine(Configuration.GetValue<String>("MachineLearning:dataDir"), "Text Classification", "cooking.stackexchange") TrainingCorpusDir = Path.Combine(Configuration.GetValue<String>("MachineLearning:dataDir"), "Text Classification", "cooking.stackexchange")
}; };
var classifier = new ClassifierFactory<NaiveBayesClassifier, SentenceFeatureExtractor>(options, SupportedLanguage.English); var classifier = new ClassifierFactory<NaiveBayesClassifier, SentenceFeatureExtractor>(options, SupportedLanguage.English);
var dataset = sentences.Split(0.7M); var dataset = sentences.Split(1M);
classifier.Train(dataset.Item1); classifier.Train(dataset.Item1);
int correct = 0; int correct = 0;

View file

@ -29,20 +29,20 @@ namespace BotSharp.NLP.Classify
public void Train(List<Sentence> sentences) public void Train(List<Sentence> sentences)
{ {
var vectors = new List<Tuple<string, double[]>>(); _classifier.Train(sentences, _options);
_classifier.SaveModel(_options);
var sents = sentences.Select(x => new Tuple<string, double[]>(x.Label, x.Vector)).ToList();
_classifier.Train(sents, new double[] { 0, 1 }, _options);
} }
public List<Tuple<string, double>> Classify(Sentence sentence) public List<Tuple<string, double>> Classify(Sentence sentence)
{ {
var options = new ClassifyOptions var options = new ClassifyOptions
{ {
ModelFilePath = _options.ModelFilePath
}; };
var classes = _classifier.Classify(sentence.Vector, options); _classifier.LoadModel(options);
var classes = _classifier.Classify(sentence, options);
return classes.OrderByDescending(x => x.Item2).ToList(); return classes.OrderByDescending(x => x.Item2).ToList();
} }

View file

@ -10,16 +10,20 @@ namespace BotSharp.NLP.Classify
/// <summary> /// <summary>
/// Training by feature vector /// Training by feature vector
/// </summary> /// </summary>
/// <param name="featureSets"></param> /// <param name="sentences"></param>
/// <param name="options"></param> /// <param name="options"></param>
void Train(List<Tuple<string, double[]>> featureSets, double[] values, ClassifyOptions options); void Train(List<Sentence> sentences, ClassifyOptions options);
/// <summary> /// <summary>
/// Predict by feature vector /// Predict by feature vector
/// </summary> /// </summary>
/// <param name="features"></param> /// <param name="sentence"></param>
/// <param name="options"></param> /// <param name="options"></param>
/// <returns></returns> /// <returns></returns>
List<Tuple<string, double>> Classify(double[] features, ClassifyOptions options); List<Tuple<string, double>> Classify(Sentence sentence, ClassifyOptions options);
String SaveModel(ClassifyOptions options);
Object LoadModel(ClassifyOptions options);
} }
} }

View file

@ -22,6 +22,8 @@ using BotSharp.Algorithm.Estimators;
using BotSharp.Algorithm.Extensions; using BotSharp.Algorithm.Extensions;
using BotSharp.Algorithm.Features; using BotSharp.Algorithm.Features;
using BotSharp.Algorithm.Statistics; using BotSharp.Algorithm.Statistics;
using BotSharp.NLP.Txt2Vec;
using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -46,14 +48,24 @@ namespace BotSharp.NLP.Classify
private Dictionary<string, double> condProbDictionary = new Dictionary<string, double>(); private Dictionary<string, double> condProbDictionary = new Dictionary<string, double>();
public void Train(List<Tuple<string, double[]>> featureSets, double[] values, ClassifyOptions options) private List<string> words;
private double[] features = new double[] { 0, 1 };
public void Train(List<Sentence> sentences, ClassifyOptions options)
{ {
var encoder = new OneHotEncoder();
encoder.Sentences = sentences;
words = encoder.EncodeAll();
var featureSets = sentences.Select(x => new Tuple<string, double[]>(x.Label, x.Vector)).ToList();
labelDist = featureSets.GroupBy(x => x.Item1) labelDist = featureSets.GroupBy(x => x.Item1)
.Select(x => new Probability .Select(x => new Probability
{ {
Value = x.Key, Value = x.Key,
Freq = x.Count() Freq = x.Count()
}) })
.OrderBy(x => x.Value)
.ToList(); .ToList();
nb.LabelDist = labelDist; nb.LabelDist = labelDist;
@ -70,30 +82,27 @@ namespace BotSharp.NLP.Classify
{ {
for (int x = 0; x < featureCount; x++) for (int x = 0; x < featureCount; x++)
{ {
for (int v = 0; v < values.Length; v++) for (int v = 0; v < features.Length; v++)
{ {
string key = $"{label.Value} f{x} {values[v]}"; string key = $"{label.Value} f{x} {features[v]}";
condProbDictionary[key] = nb.CalCondProb(x, label.Value, values[v]); condProbDictionary[key] = nb.CalCondProb(x, label.Value, features[v]);
} }
} }
}); });
// save the model
var model = new MultinomiaNaiveBayesModel
{
LabelDist = labelDist,
CondProbDictionary = condProbDictionary
};
} }
public List<Tuple<string, double>> Classify(double[] features, ClassifyOptions options) public List<Tuple<string, double>> Classify(Sentence sentence, ClassifyOptions options)
{ {
var encoder = new OneHotEncoder();
encoder.Words = words;
encoder.Encode(sentence);
var results = new List<Tuple<string, double>>(); var results = new List<Tuple<string, double>>();
// calculate prop // calculate prop
labelDist.ForEach(lf => labelDist.ForEach(lf =>
{ {
var prob = nb.CalPosteriorProb(lf.Value, features, lf.Prob, condProbDictionary); var prob = nb.CalPosteriorProb(lf.Value, sentence.Vector, lf.Prob, condProbDictionary);
results.Add(new Tuple<string, double>(lf.Value, prob)); results.Add(new Tuple<string, double>(lf.Value, prob));
}); });
@ -105,6 +114,47 @@ namespace BotSharp.NLP.Classify
return results; return results;
} }
public string SaveModel(ClassifyOptions options)
{
// save the model
var model = new MultinomiaNaiveBayesModel
{
LabelDist = labelDist,
CondProbDictionary = condProbDictionary,
Values = words
};
//save the file
using (var bw = new BinaryWriter(new FileStream(options.ModelFilePath, FileMode.Create)))
{
var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(model));
bw.Write(bytes);
}
return options.ModelFilePath;
}
public Object LoadModel(ClassifyOptions options)
{
string json = String.Empty;
//read the file
using (var br = new BinaryReader(new FileStream(options.ModelFilePath, FileMode.Open)))
{
byte[] bytes = br.ReadBytes((int)br.BaseStream.Length);
json = Encoding.UTF8.GetString(bytes);
}
var model = JsonConvert.DeserializeObject<MultinomiaNaiveBayesModel>(json);
labelDist = model.LabelDist;
condProbDictionary = model.CondProbDictionary;
words = model.Values;
return model;
}
} }
public class FeaturesWithLabel public class FeaturesWithLabel

View file

@ -48,12 +48,12 @@ namespace BotSharp.NLP.Classify
return Prediction.PredictLabelsProbability(options.Model, scaled); return Prediction.PredictLabelsProbability(options.Model, scaled);
} }
public void Train(List<Tuple<string, double[]>> featureSets, double[] values, ClassifyOptions options) public void Train(List<Sentence> sentences, ClassifyOptions options)
{ {
// SVMClassifierTrain(featureSets, options); // SVMClassifierTrain(featureSets, options);
} }
public List<Tuple<string, double>> Classify(double[] features, ClassifyOptions options) public List<Tuple<string, double>> Classify(Sentence sentence, ClassifyOptions options)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@ -154,5 +154,15 @@ namespace BotSharp.NLP.Classify
return labeledFeatureSet; return labeledFeatureSet;
} }
public string SaveModel(ClassifyOptions options)
{
throw new NotImplementedException();
}
object IClassifier.LoadModel(ClassifyOptions options)
{
throw new NotImplementedException();
}
} }
} }

View file

@ -15,17 +15,17 @@ namespace BotSharp.NLP.Txt2Vec
{ {
public List<Sentence> Sentences { get; set; } public List<Sentence> Sentences { get; set; }
private List<string> words; public List<string> Words { get; set; }
public void Encode(Sentence sentence) public void Encode(Sentence sentence)
{ {
InitDictionary(); InitDictionary();
var vector = words.Select(x => 0D).ToArray(); var vector = Words.Select(x => 0D).ToArray();
sentence.Words.ForEach(w => sentence.Words.ForEach(w =>
{ {
int index = words.IndexOf(w.Text.ToLower()); int index = Words.IndexOf(w.Text.ToLower());
if(index > 0) if(index > 0)
{ {
vector[index] = 1; vector[index] = 1;
@ -35,24 +35,29 @@ namespace BotSharp.NLP.Txt2Vec
sentence.Vector = vector; sentence.Vector = vector;
} }
public void EncodeAll() public List<string> EncodeAll()
{ {
InitDictionary(); InitDictionary();
Sentences.ForEach(sent => Encode(sent)); Sentences.ForEach(sent => Encode(sent));
//Parallel.ForEach(Sentences, sent => Encode(sent)); //Parallel.ForEach(Sentences, sent => Encode(sent));
return Words;
} }
private void InitDictionary() private List<string> InitDictionary()
{ {
if (words == null) if (Words == null)
{ {
words = new List<string>(); Words = new List<string>();
Sentences.ForEach(x => Sentences.ForEach(x =>
{ {
words.AddRange(x.Words.Where(w => w.IsAlpha).Select(w => w.Text.ToLower())); Words.AddRange(x.Words.Where(w => w.IsAlpha).Select(w => w.Text.ToLower()));
}); });
words = words.Distinct().OrderBy(x => x).ToList(); Words = Words.Distinct().OrderBy(x => x).ToList();
} }
return Words;
} }
} }
} }