diff --git a/BotSharp.NLP.UnitTest/NGramTaggerTest.cs b/BotSharp.NLP.UnitTest/NGramTaggerTest.cs index befaa773..80e6b977 100644 --- a/BotSharp.NLP.UnitTest/NGramTaggerTest.cs +++ b/BotSharp.NLP.UnitTest/NGramTaggerTest.cs @@ -13,7 +13,7 @@ namespace BotSharp.NLP.UnitTest public class NGramTaggerTest { [TestMethod] - public void TagInCoNLL2000() + public void UniGramInCoNLL2000() { // tokenization var tokenizer = new TokenizerFactory(new TokenizationOptions @@ -21,26 +21,88 @@ namespace BotSharp.NLP.UnitTest Pattern = RegexTokenizer.WORD_PUNC }, SupportedLanguage.English); - var tokens = tokenizer.Tokenize("How are you doing?"); + var tokens = tokenizer.Tokenize("Chancellor of the Exchequer Nigel Lawson's restated commitment"); + // test tag + var tagger = new TaggerFactory(new TagOptions + { + NGram = 1, + Tag = "NN", + Corpus = GetTaggedCorpus() + }, SupportedLanguage.English); + + tagger.Tag(new Sentence { Words = tokens }); + + Assert.IsTrue(tokens[0].Pos == "NNP"); + Assert.IsTrue(tokens[1].Pos == "IN"); + Assert.IsTrue(tokens[2].Pos == "DT"); + Assert.IsTrue(tokens[3].Pos == "NNP"); + } + + [TestMethod] + public void BiGramInCoNLL2000() + { + // tokenization + var tokenizer = new TokenizerFactory(new TokenizationOptions + { + Pattern = RegexTokenizer.WORD_PUNC + }, SupportedLanguage.English); + + var tokens = tokenizer.Tokenize("Chancellor of the Exchequer Nigel Lawson's restated commitment"); + + // test tag + var tagger = new TaggerFactory(new TagOptions + { + NGram = 2, + Tag = "NN", + Corpus = GetTaggedCorpus() + }, SupportedLanguage.English); + + tagger.Tag(new Sentence { Words = tokens }); + + Assert.IsTrue(tokens[0].Pos == "NNP"); + Assert.IsTrue(tokens[1].Pos == "IN"); + Assert.IsTrue(tokens[2].Pos == "DT"); + Assert.IsTrue(tokens[3].Pos == "NNP"); + } + + [TestMethod] + public void TriGramInCoNLL2000() + { + // tokenization + var tokenizer = new TokenizerFactory(new TokenizationOptions + { + Pattern = RegexTokenizer.WORD_PUNC + }, SupportedLanguage.English); + + var tokens = tokenizer.Tokenize("Chancellor of the Exchequer Nigel Lawson's restated commitment"); + + // test tag + var tagger = new TaggerFactory(new TagOptions + { + NGram = 3, + Tag = "NN", + Corpus = GetTaggedCorpus() + }, SupportedLanguage.English); + + tagger.Tag(new Sentence { Words = tokens }); + + Assert.IsTrue(tokens[0].Pos == "NNP"); + Assert.IsTrue(tokens[1].Pos == "IN"); + Assert.IsTrue(tokens[2].Pos == "DT"); + Assert.IsTrue(tokens[3].Pos == "NNP"); + } + + private List GetTaggedCorpus() + { // get training corpus string corpusDir = Environment.GetEnvironmentVariable("BOTSHARP_CORPUS_PATH", EnvironmentVariableTarget.User); - var sentences = new CoNLLReader() + return new CoNLLReader() .Read(new ReaderOptions { DataDir = Path.Combine(corpusDir, "CoNLL"), FileName = "conll2000_chunking_train.txt" }); - - // start tag - var tagger = new TaggerFactory(new TagOptions - { - NGram = 2, - Tag = "NN", - Corpus = sentences - }, SupportedLanguage.English); - - tagger.Tag(new Sentence { Words = tokens }); } } } diff --git a/BotSharp.NLP/Tag/NGramTagger.cs b/BotSharp.NLP/Tag/NGramTagger.cs index c42ca99f..f9b3e38b 100644 --- a/BotSharp.NLP/Tag/NGramTagger.cs +++ b/BotSharp.NLP/Tag/NGramTagger.cs @@ -21,64 +21,92 @@ namespace BotSharp.NLP.Tag { Train(options.Corpus, options); } + + Fill(sentence, options); + + for (int pos = options.NGram - 1; pos < sentence.Words.Count; pos++) + { + sentence.Words[pos].Pos = _contextMapping.FirstOrDefault(x => x.Context == GetContext(pos, sentence.Words, options))?.Tag; + + // set default tag + if(sentence.Words[pos].Pos == null) + { + sentence.Words[pos].Pos = options.Tag; + } + } + + for(int pos = 0; pos < options.NGram - 1; pos++) + { + sentence.Words.RemoveAt(0); + } } public void Train(List sentences, TagOptions options) { - _contextMapping = new List(); + var cache = new List(); for (int idx = 0; idx < options.Corpus.Count; idx++) { var sent = options.Corpus[idx]; - for (int ngram = 1; ngram < options.NGram; ngram++) - { - sent.Words.Insert(0, new Token { Text = "NIL", Pos = options.Tag, Start = (ngram - 1) * 3 }); - } + Fill(sent, options); - int pos = options.NGram - 1; - for (pos = 1; pos < sent.Words.Count; pos++) + for (int pos = options.NGram - 1; pos < sent.Words.Count; pos++) { var freq = new NGramFreq { - PrecedingTokens = new List { sent.Words[pos - 1] }, - Token = sent.Words[pos], - Count = 0 + Context = GetContext(pos, sent.Words, options), + Tag = sent.Words[pos].Pos, + Count = 1 }; - _contextMapping.Add(freq); + cache.Add(freq); } } - /*var results = (from c in cache - group c by c.Item1 into g - select new { g.Key, Count = g.Count() }).ToList();*/ + _contextMapping = (from c in cache + group c by new { c.Context, c.Tag } into g + select new NGramFreq + { + Context = g.Key.Context, + Tag = g.Key.Tag, + Count = g.Count() + }).OrderByDescending(x => x.Count) + .ToList(); + } + + private string GetContext(int pos, List words, TagOptions options) + { + string context = words[pos].Text; + for (int ngram = options.NGram - 1; ngram > 0; ngram--) + { + context = words[pos - ngram].Pos + " " + context; + } + + return context; + } + + private void Fill(Sentence sent, TagOptions options) + { + for (int ngram = 1; ngram < options.NGram; ngram++) + { + sent.Words.Insert(0, new Token { Text = "NIL", Pos = options.Tag, Start = (ngram - 1) * 3 }); + } } private class NGramFreq { - /// - /// Tokens prior current token - /// - public List PrecedingTokens { get; set; } - /// /// Current token tag /// - public Token Token { get; set; } + public string Tag { get; set; } /// /// Occurence frequency /// public int Count { get; set; } - public string Context - { - get - { - return $"{PrecedingTokens.First().Pos} {Token.Text} {Token.Pos}"; - } - } + public string Context { get; set; } } } }