diff --git a/BotSharp.NLP.UnitTest/DefaultTaggerTest.cs b/BotSharp.NLP.UnitTest/DefaultTaggerTest.cs index 7f2d2f53..ac2aef22 100644 --- a/BotSharp.NLP.UnitTest/DefaultTaggerTest.cs +++ b/BotSharp.NLP.UnitTest/DefaultTaggerTest.cs @@ -14,22 +14,15 @@ namespace BotSharp.NLP.UnitTest [TestMethod] public void TagInCoNLL2000() { - var sentences = new CoNLLReader() - .Read(new ReaderOptions - { - DataDir = AppDomain.CurrentDomain.BaseDirectory, - FileName = "conll2000_chunking_train" - }); + var tokenizer = new TokenizerFactory(new TokenizationOptions { }, SupportedLanguage.English); + var tokens = tokenizer.Tokenize("How are you doing?"); - var tagger = new TaggerFactory(); + var tagger = new TaggerFactory(new TagOptions + { + Tag = "NN" + }, SupportedLanguage.English); - // tokenize - - tagger.Tag(null, - new TagOptions - { - - }); + tagger.Tag(new Sentence { Words = tokens }); } } -} +} \ No newline at end of file diff --git a/BotSharp.NLP.UnitTest/NGramTaggerTest.cs b/BotSharp.NLP.UnitTest/NGramTaggerTest.cs new file mode 100644 index 00000000..befaa773 --- /dev/null +++ b/BotSharp.NLP.UnitTest/NGramTaggerTest.cs @@ -0,0 +1,46 @@ +using BotSharp.NLP.Corpus; +using BotSharp.NLP.Tag; +using BotSharp.NLP.Tokenize; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace BotSharp.NLP.UnitTest +{ + [TestClass] + public class NGramTaggerTest + { + [TestMethod] + public void TagInCoNLL2000() + { + // tokenization + var tokenizer = new TokenizerFactory(new TokenizationOptions + { + Pattern = RegexTokenizer.WORD_PUNC + }, SupportedLanguage.English); + + var tokens = tokenizer.Tokenize("How are you doing?"); + + // get training corpus + string corpusDir = Environment.GetEnvironmentVariable("BOTSHARP_CORPUS_PATH", EnvironmentVariableTarget.User); + var sentences = 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.UnitTest/RegexStemmerTest.cs b/BotSharp.NLP.UnitTest/RegexStemmerTest.cs index 9cc42c5f..8bf0fc98 100644 --- a/BotSharp.NLP.UnitTest/RegexStemmerTest.cs +++ b/BotSharp.NLP.UnitTest/RegexStemmerTest.cs @@ -12,13 +12,12 @@ namespace BotSharp.NLP.UnitTest [TestMethod] public void StemInDefault() { - var stemmer = new StemmerFactory(); + var stemmer = new StemmerFactory(new StemOptions + { + Pattern = RegexStemmer.DEFAULT + }, SupportedLanguage.English); - var stem = stemmer.Stem("doing", - new StemOptions - { - Pattern = RegexStemmer.DEFAULT - }); + var stem = stemmer.Stem("doing"); Assert.IsTrue(stem == "do"); } diff --git a/BotSharp.NLP.UnitTest/RegexTokenizerTest.cs b/BotSharp.NLP.UnitTest/RegexTokenizerTest.cs index dbcc94c6..01b57429 100644 --- a/BotSharp.NLP.UnitTest/RegexTokenizerTest.cs +++ b/BotSharp.NLP.UnitTest/RegexTokenizerTest.cs @@ -9,13 +9,12 @@ namespace BotSharp.NLP.UnitTest [TestMethod] public void TokenizeInWhiteSpace() { - var tokenizer = new TokenizerFactory(); + var tokenizer = new TokenizerFactory(new TokenizationOptions + { + Pattern = RegexTokenizer.WHITE_SPACE + }, SupportedLanguage.English); - var tokens = tokenizer.Tokenize("Chop into pieces, isn't it?", - new TokenizationOptions - { - Pattern = RegexTokenizer.WHITE_SPACE - }); + var tokens = tokenizer.Tokenize("Chop into pieces, isn't it?"); Assert.IsTrue(tokens[0].Start == 0); Assert.IsTrue(tokens[0].Text == "Chop"); @@ -36,13 +35,12 @@ namespace BotSharp.NLP.UnitTest [TestMethod] public void TokenizeInWordPunctuation() { - var tokenizer = new TokenizerFactory(); + var tokenizer = new TokenizerFactory(new TokenizationOptions + { + Pattern = RegexTokenizer.WORD_PUNC + }, SupportedLanguage.English); - var tokens = tokenizer.Tokenize("Chop into pieces, isn't it?", - new TokenizationOptions - { - Pattern = RegexTokenizer.WORD_PUNC - }); + var tokens = tokenizer.Tokenize("Chop into pieces, isn't it?"); Assert.IsTrue(tokens[0].Start == 0); Assert.IsTrue(tokens[0].Text == "Chop"); @@ -75,17 +73,16 @@ namespace BotSharp.NLP.UnitTest [TestMethod] public void TokenizeInBlankLine() { - var tokenizer = new TokenizerFactory(); + var tokenizer = new TokenizerFactory(new TokenizationOptions + { + Pattern = RegexTokenizer.BLANK_LINE + }, SupportedLanguage.English); var tokens = tokenizer.Tokenize(@"Chop into pieces, isn't -it?", - new TokenizationOptions - { - Pattern = RegexTokenizer.BLANK_LINE - }); +it?"); Assert.IsTrue(tokens[0].Start == 0); Assert.IsTrue(tokens[0].Text == "Chop into pieces,"); diff --git a/BotSharp.NLP/Corpus/CoNLL/README.md b/BotSharp.NLP/Corpus/CoNLL/README.md index a5e05833..a1727e90 100644 --- a/BotSharp.NLP/Corpus/CoNLL/README.md +++ b/BotSharp.NLP/Corpus/CoNLL/README.md @@ -1 +1,2 @@ conll2000_chunking is downloaded from https://www.clips.uantwerpen.be/conll2000/chunking/ +The train and test data consist of three columns separated by spaces. Each word has been put on a separate line and there is an empty line after each sentence. The first column contains the current word, the second its part-of-speech tag as derived by the Brill tagger and the third its chunk tag as derived from the WSJ corpus. \ No newline at end of file diff --git a/BotSharp.NLP/Corpus/ConllReader.cs b/BotSharp.NLP/Corpus/ConllReader.cs index 3a5754c4..ef971f4f 100644 --- a/BotSharp.NLP/Corpus/ConllReader.cs +++ b/BotSharp.NLP/Corpus/ConllReader.cs @@ -1,4 +1,5 @@ -using System; +using BotSharp.NLP.Tokenize; +using System; using System.Collections.Generic; using System.IO; using System.Text; @@ -17,9 +18,36 @@ namespace BotSharp.NLP.Corpus { public List Read(ReaderOptions options) { - string corpus = File.ReadAllText(Path.Combine(options.DataDir, "conll2000_chunking_train.txt")); + var sentences = new List(); + using(StreamReader reader = new StreamReader(Path.Combine(options.DataDir, options.FileName))) + { + string line = reader.ReadLine(); + var sentence = new Sentence { Words = new List { } }; - return null; + while (!reader.EndOfStream) + { + if (String.IsNullOrEmpty(line)) + { + sentences.Add(sentence); + sentence = new Sentence { Words = new List { } }; + } + else + { + var columns = line.Split(' '); + + sentence.Words.Add(new Token + { + Text = columns[0], + Pos = columns[1] + }); + } + + line = reader.ReadLine(); + } + + } + + return sentences; } } } diff --git a/BotSharp.NLP/Stem/IStemmer.cs b/BotSharp.NLP/Stem/IStemmer.cs index afd5ca55..6441039f 100644 --- a/BotSharp.NLP/Stem/IStemmer.cs +++ b/BotSharp.NLP/Stem/IStemmer.cs @@ -12,11 +12,6 @@ namespace BotSharp.NLP.Stem /// public interface IStemmer { - /// - /// Language - /// - SupportedLanguage Lang { get; set; } - /// /// Strip affixes from the token and return the stem. /// diff --git a/BotSharp.NLP/Stem/RegexStemmer.cs b/BotSharp.NLP/Stem/RegexStemmer.cs index 23034418..d33c4fb7 100644 --- a/BotSharp.NLP/Stem/RegexStemmer.cs +++ b/BotSharp.NLP/Stem/RegexStemmer.cs @@ -15,8 +15,6 @@ namespace BotSharp.NLP.Stem { public const string DEFAULT = "ing$|s$|e$|able$"; - public SupportedLanguage Lang { get; set; } - private Regex _regex; public string Stem(string word, StemOptions options) diff --git a/BotSharp.NLP/Stem/StemmerFactory.cs b/BotSharp.NLP/Stem/StemmerFactory.cs index a94899ed..be7b4f70 100644 --- a/BotSharp.NLP/Stem/StemmerFactory.cs +++ b/BotSharp.NLP/Stem/StemmerFactory.cs @@ -14,16 +14,22 @@ namespace BotSharp.NLP.Stem /// public class StemmerFactory where IStem : IStemmer, new() { + private SupportedLanguage _lang { get; set; } + private IStem _stemmer; - public StemmerFactory() + private StemOptions _options; + + public StemmerFactory(StemOptions options, SupportedLanguage lang) { + _lang = lang; + _options = options; _stemmer = new IStem(); } - public string Stem(string word, StemOptions options) + public string Stem(string word) { - return _stemmer.Stem(word, options); + return _stemmer.Stem(word, _options); } } } diff --git a/BotSharp.NLP/Tag/DefaultTagger.cs b/BotSharp.NLP/Tag/DefaultTagger.cs index 33bdcb72..48abb43c 100644 --- a/BotSharp.NLP/Tag/DefaultTagger.cs +++ b/BotSharp.NLP/Tag/DefaultTagger.cs @@ -12,16 +12,14 @@ namespace BotSharp.NLP.Tag /// public class DefaultTagger : ITagger { - public SupportedLanguage Lang { get; set; } - public void Tag(Sentence sentence, TagOptions options) { - throw new NotImplementedException(); + } public void Train(List sentences, TagOptions options) { - throw new NotImplementedException(); + } } } diff --git a/BotSharp.NLP/Tag/ITagger.cs b/BotSharp.NLP/Tag/ITagger.cs index e204e666..71aa4ffb 100644 --- a/BotSharp.NLP/Tag/ITagger.cs +++ b/BotSharp.NLP/Tag/ITagger.cs @@ -11,11 +11,6 @@ namespace BotSharp.NLP.Tag /// public interface ITagger { - /// - /// Language - /// - SupportedLanguage Lang { get; set; } - /// /// /// diff --git a/BotSharp.NLP/Tag/NGramTagger.cs b/BotSharp.NLP/Tag/NGramTagger.cs index b386e932..42940869 100644 --- a/BotSharp.NLP/Tag/NGramTagger.cs +++ b/BotSharp.NLP/Tag/NGramTagger.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; using BotSharp.NLP.Tokenize; @@ -11,16 +12,55 @@ namespace BotSharp.NLP.Tag /// public class NGramTagger : ITagger { - public SupportedLanguage Lang { get; set; } + public Dictionary ContextMapping { get; set; } public void Tag(Sentence sentence, TagOptions options) { - throw new NotImplementedException(); + // need training to generate model + if(ContextMapping == null) + { + var cache = new List>(); + var contextTag = new List>(); + + ContextMapping = new Dictionary(); + + options.Corpus.ForEach(sent => + { + // Supplementary place + for (int ngram = 1; ngram < options.NGram; ngram++) + { + sent.Words.Insert(0, new Token { Text = "NIL", Pos = options.Tag, Start = (ngram - 1) * 3 }); + } + + int pos = options.NGram - 1; + for(pos = 1; pos < sent.Words.Count; pos++) + { + Token pre = sent.Words[pos - 1]; + Token cur = sent.Words[pos]; + + cache.Add(new Tuple($"{pre.Pos} {cur.Text}", cur.Pos));// Dictionary.Add($"{pre.Pos} {cur.Text}", cur.Pos); + } + }); + + var results = (from c in cache + group c by c.Item1 into g + select new { g.Key, Count = g.Count() }).ToList(); + + results.ForEach(x => + { + int count = cache.Count(c => c.Item1 == x.Key); + }); + } } public void Train(List sentences, TagOptions options) { throw new NotImplementedException(); } + + private class NGramFreq + { + public string Key { get; set; } + } } } diff --git a/BotSharp.NLP/Tag/TagOptions.cs b/BotSharp.NLP/Tag/TagOptions.cs index 2f6c7ea9..d33a395d 100644 --- a/BotSharp.NLP/Tag/TagOptions.cs +++ b/BotSharp.NLP/Tag/TagOptions.cs @@ -16,5 +16,15 @@ namespace BotSharp.NLP.Tag /// Used in DefaultTagger /// public string Tag { get; set; } + + /// + /// N-Gram number + /// + public int NGram { get; set; } + + /// + /// Tagged corpus used for training a model + /// + public List Corpus { get; set; } } } diff --git a/BotSharp.NLP/Tag/TaggerFactory.cs b/BotSharp.NLP/Tag/TaggerFactory.cs index 9b563d1c..1eba88f9 100644 --- a/BotSharp.NLP/Tag/TaggerFactory.cs +++ b/BotSharp.NLP/Tag/TaggerFactory.cs @@ -6,16 +6,22 @@ namespace BotSharp.NLP.Tag { public class TaggerFactory where ITag : ITagger, new() { + private SupportedLanguage _lang; + private ITag _tagger; - public TaggerFactory() + private TagOptions _options; + + public TaggerFactory(TagOptions options, SupportedLanguage lang) { + _lang = lang; + _options = options; _tagger = new ITag(); } - public void Tag(Sentence sentence, TagOptions options) + public void Tag(Sentence sentence) { - _tagger.Tag(sentence, options); + _tagger.Tag(sentence, _options); } } } diff --git a/BotSharp.NLP/Tokenize/ITokenizer.cs b/BotSharp.NLP/Tokenize/ITokenizer.cs index 1d761851..ca12b008 100644 --- a/BotSharp.NLP/Tokenize/ITokenizer.cs +++ b/BotSharp.NLP/Tokenize/ITokenizer.cs @@ -10,17 +10,12 @@ namespace BotSharp.NLP.Tokenize /// public interface ITokenizer { - /// - /// Language - /// - SupportedLanguage Lang { get; set; } - /// /// Tokenize /// /// input sentence /// Options such as: regex expression /// - Token[] Tokenize(string sentence, TokenizationOptions options); + List Tokenize(string sentence, TokenizationOptions options); } } diff --git a/BotSharp.NLP/Tokenize/RegexTokenizer.cs b/BotSharp.NLP/Tokenize/RegexTokenizer.cs index 791710d7..43cd2a2f 100644 --- a/BotSharp.NLP/Tokenize/RegexTokenizer.cs +++ b/BotSharp.NLP/Tokenize/RegexTokenizer.cs @@ -11,8 +11,6 @@ namespace BotSharp.NLP.Tokenize /// public class RegexTokenizer : ITokenizer { - public SupportedLanguage Lang { get; set; } - /// /// Tokenize a text into a sequence of alphabetic and non-alphabetic characters /// @@ -34,7 +32,7 @@ namespace BotSharp.NLP.Tokenize private Regex _regex; - public Token[] Tokenize(string sentence, TokenizationOptions options) + public List Tokenize(string sentence, TokenizationOptions options) { _regex = new Regex(options.Pattern); @@ -65,7 +63,7 @@ namespace BotSharp.NLP.Tokenize } } - return tokens.ToArray(); + return tokens.ToList(); } else { @@ -73,7 +71,7 @@ namespace BotSharp.NLP.Tokenize { Text = x.Value, Start = x.Index - }).ToArray(); + }).ToList(); } } } diff --git a/BotSharp.NLP/Tokenize/Token.cs b/BotSharp.NLP/Tokenize/Token.cs index 43d8dd91..35f71d5e 100644 --- a/BotSharp.NLP/Tokenize/Token.cs +++ b/BotSharp.NLP/Tokenize/Token.cs @@ -55,5 +55,10 @@ namespace BotSharp.NLP.Tokenize return Start + Text.Length - 1; } } + + public override string ToString() + { + return $"{Text} {Start} {Pos}"; + } } } diff --git a/BotSharp.NLP/Tokenize/TokenizerFactory.cs b/BotSharp.NLP/Tokenize/TokenizerFactory.cs index 78a2a416..3d841ce0 100644 --- a/BotSharp.NLP/Tokenize/TokenizerFactory.cs +++ b/BotSharp.NLP/Tokenize/TokenizerFactory.cs @@ -12,16 +12,22 @@ namespace BotSharp.NLP.Tokenize /// public class TokenizerFactory where ITokenize : ITokenizer, new() { + private SupportedLanguage _lang; + private ITokenize _tokenizer; - public TokenizerFactory() + private TokenizationOptions _options; + + public TokenizerFactory(TokenizationOptions options, SupportedLanguage lang) { + _lang = lang; + _options = options; _tokenizer = new ITokenize(); } - public Token[] Tokenize(string sentence, TokenizationOptions options) + public List Tokenize(string sentence) { - return _tokenizer.Tokenize(sentence, options); + return _tokenizer.Tokenize(sentence, _options); } } }