diff --git a/BotSharp.MachineLearning.UnitTest/BotSharp.MachineLearning.UnitTest.csproj b/BotSharp.MachineLearning.UnitTest/BotSharp.MachineLearning.UnitTest.csproj new file mode 100644 index 00000000..57109843 --- /dev/null +++ b/BotSharp.MachineLearning.UnitTest/BotSharp.MachineLearning.UnitTest.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp2.1 + + false + + + + + + + + + + + + + diff --git a/BotSharp.MachineLearning.UnitTest/DecoderTest.cs b/BotSharp.MachineLearning.UnitTest/DecoderTest.cs new file mode 100644 index 00000000..225bbea0 --- /dev/null +++ b/BotSharp.MachineLearning.UnitTest/DecoderTest.cs @@ -0,0 +1,287 @@ +using BotSharp.MachineLearning.CRFLite; +using BotSharp.MachineLearning.CRFLite.Decoder; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Threading.Tasks; + +namespace BotSharp.MachineLearning.UnitTest +{ + [TestClass] + public class DecoderTest + { + [TestMethod] + public void TestDecode() + { + var encoder = new CRFDecoder(); + bool bRet = Decode(new DecoderOptions + { + + }); + } + + object rdLocker = new object(); + + bool Decode(DecoderOptions options) + { + var parallelOption = new ParallelOptions(); + var watch = Stopwatch.StartNew(); + if (File.Exists(options.strInputFileName) == false) + { + //Logger.WriteLine("FAILED: Open {0} file failed.", options.strInputFileName); + return false; + } + + if (File.Exists(options.strModelFileName) == false) + { + //Logger.WriteLine("FAILED: Open {0} file failed.", options.strModelFileName); + return false; + } + + var sr = new StreamReader(options.strInputFileName); + StreamWriter sw = null, swSeg = null; + + if (options.strOutputFileName != null && options.strOutputFileName.Length > 0) + { + sw = new StreamWriter(options.strOutputFileName); + } + if (options.strOutputSegFileName != null && options.strOutputSegFileName.Length > 0) + { + swSeg = new StreamWriter(options.strOutputSegFileName); + } + + //Create CRFSharp wrapper instance. It's a global instance + var crfWrapper = new CRFDecoder(); + + //Load encoded model from file + //Logger.WriteLine("Loading model from {0}", options.strModelFileName); + crfWrapper.LoadModel(options.strModelFileName); + + var queueRecords = new ConcurrentQueue>>(); + var queueSegRecords = new ConcurrentQueue>>(); + + parallelOption.MaxDegreeOfParallelism = options.thread; + Parallel.For(0, options.thread, parallelOption, t => + { + + //Create decoder tagger instance. If the running environment is multi-threads, each thread needs a separated instance + var tagger = crfWrapper.CreateTagger(options.nBest, options.maxword); + tagger.set_vlevel(options.probLevel); + + //Initialize result + var crf_out = new crf_seg_out[options.nBest]; + for (var i = 0; i < options.nBest; i++) + { + crf_out[i] = new crf_seg_out(tagger.crf_max_word_num); + } + + var inbuf = new List>(); + while (true) + { + lock (rdLocker) + { + if (ReadRecord(inbuf, sr) == false) + { + break; + } + + queueRecords.Enqueue(inbuf); + queueSegRecords.Enqueue(inbuf); + } + + //Call CRFSharp wrapper to predict given string's tags + if (swSeg != null) + { + crfWrapper.Segment(crf_out, tagger, inbuf); + } + else + { + crfWrapper.Segment((CRFTermOut[])crf_out, (DecoderTagger)tagger, inbuf); + } + + List> peek = null; + //Save segmented tagged result into file + if (swSeg != null) + { + var rstList = ConvertCRFTermOutToStringList(inbuf, crf_out); + while (peek != inbuf) + { + queueSegRecords.TryPeek(out peek); + } + for (int index = 0; index < rstList.Count; index++) + { + var item = rstList[index]; + swSeg.WriteLine(item); + } + queueSegRecords.TryDequeue(out peek); + peek = null; + } + + //Save raw tagged result (with probability) into file + if (sw != null) + { + while (peek != inbuf) + { + queueRecords.TryPeek(out peek); + } + OutputRawResultToFile(inbuf, crf_out, tagger, sw); + queueRecords.TryDequeue(out peek); + + } + } + }); + + + sr.Close(); + + if (sw != null) + { + sw.Close(); + } + if (swSeg != null) + { + swSeg.Close(); + } + watch.Stop(); + //Logger.WriteLine("Elapsed: {0} ms", watch.ElapsedMilliseconds); + + return true; + } + + private bool ReadRecord(List> inbuf, StreamReader sr) + { + inbuf.Clear(); + + while (true) + { + var strLine = sr.ReadLine(); + if (strLine == null) + { + //At the end of current file + if (inbuf.Count == 0) + { + return false; + } + else + { + return true; + } + } + strLine = strLine.Trim(); + if (strLine.Length == 0) + { + return true; + } + + //Read feature set for each record + var items = strLine.Split(new char[] { '\t' }); + inbuf.Add(new List()); + for (int index = 0; index < items.Length; index++) + { + var item = items[index]; + inbuf[inbuf.Count - 1].Add(item); + } + } + } + + //Output raw result with probability + private void OutputRawResultToFile(List> inbuf, CRFTermOut[] crf_out, SegDecoderTagger tagger, StreamWriter sw) + { + for (var k = 0; k < crf_out.Length; k++) + { + if (crf_out[k] == null) + { + //No more result + break; + } + + var sb = new StringBuilder(); + + var crf_seg_out = crf_out[k]; + //Show the entire sequence probability + //For each token + for (var i = 0; i < inbuf.Count; i++) + { + //Show all features + for (var j = 0; j < inbuf[i].Count; j++) + { + sb.Append(inbuf[i][j]); + sb.Append("\t"); + } + + //Show the best result and its probability + sb.Append(crf_seg_out.result_[i]); + + if (tagger.vlevel_ > 1) + { + sb.Append("\t"); + sb.Append(crf_seg_out.weight_[i]); + + //Show the probability of all tags + sb.Append("\t"); + for (var j = 0; j < tagger.ysize_; j++) + { + sb.Append(tagger.yname(j)); + sb.Append("/"); + sb.Append(tagger.prob(i, j)); + + if (j < tagger.ysize_ - 1) + { + sb.Append("\t"); + } + } + } + sb.AppendLine(); + } + if (tagger.vlevel_ > 0) + { + sw.WriteLine("#{0}", crf_seg_out.prob); + } + sw.WriteLine(sb.ToString().Trim()); + sw.WriteLine(); + } + } + + //Convert CRFSharp output format to string list + private List ConvertCRFTermOutToStringList(List> inbuf, crf_seg_out[] crf_out) + { + var sb = new StringBuilder(); + for (var i = 0; i < inbuf.Count; i++) + { + sb.Append(inbuf[i][0]); + } + + var strText = sb.ToString(); + var rstList = new List(); + for (var i = 0; i < crf_out.Length; i++) + { + if (crf_out[i] == null) + { + //No more result + break; + } + + sb.Clear(); + var crf_term_out = crf_out[i]; + for (var j = 0; j < crf_term_out.Count; j++) + { + var str = strText.Substring(crf_term_out.tokenList[j].offset, crf_term_out.tokenList[j].length); + var strNE = crf_term_out.tokenList[j].strTag; + + sb.Append(str); + if (strNE.Length > 0) + { + sb.Append("[" + strNE + "]"); + } + sb.Append(" "); + } + rstList.Add(sb.ToString().Trim()); + } + + return rstList; + } + } +} diff --git a/BotSharp.MachineLearning.UnitTest/EncoderTest.cs b/BotSharp.MachineLearning.UnitTest/EncoderTest.cs new file mode 100644 index 00000000..1958e760 --- /dev/null +++ b/BotSharp.MachineLearning.UnitTest/EncoderTest.cs @@ -0,0 +1,22 @@ +using BotSharp.MachineLearning.CRFLite; +using BotSharp.MachineLearning.CRFLite.Encoder; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace BotSharp.MachineLearning.UnitTest +{ + [TestClass] + public class EncoderTest + { + [TestMethod] + public void TestEncode() + { + var encoder = new CRFEncoder(); + bool bRet = encoder.Learn(new EncoderOptions + { + TrainingCorpusFileName = "", + TemplateFileName = "", + ModelFileName = "" + }); + } + } +} diff --git a/BotSharp.MachineLearning/BotSharp.MachineLearning.csproj b/BotSharp.MachineLearning/BotSharp.MachineLearning.csproj index d838d0ee..a5c52f3f 100644 --- a/BotSharp.MachineLearning/BotSharp.MachineLearning.csproj +++ b/BotSharp.MachineLearning/BotSharp.MachineLearning.csproj @@ -9,4 +9,10 @@ + + + ..\..\..\..\..\..\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.netcore.app\2.1.0\ref\netcoreapp2.1\System.ComponentModel.Annotations.dll + + + diff --git a/BotSharp.MachineLearning/CRFLite/BaseModel.cs b/BotSharp.MachineLearning/CRFLite/BaseModel.cs new file mode 100644 index 00000000..e20925f3 --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/BaseModel.cs @@ -0,0 +1,154 @@ +using System.Collections.Generic; +using System.Text; + +namespace BotSharp.MachineLearning.CRFLite +{ + public class BaseModel + { + public long maxid_; + public double cost_factor_; + + public List unigram_templs_; + public List bigram_templs_; + + //Labeling tag list + public List y_; + public uint ysize() { return (uint)y_.Count; } + + //The dimension training corpus + public uint xsize_; + + //Feature set value array + public double[] alpha_; + + public BaseModel() + { + cost_factor_ = 1.0; + } + + //获取类别i的字符表示 + public string y(int i) { return y_[i]; } + + public long feature_size() { return maxid_; } + + public StringBuilder apply_rule(string p, int pos, StringBuilder resultContainer, Tagger tagger) + { + resultContainer.Clear(); + for (var i = 0; i < p.Length; i++) + { + if (p[i] == '%') + { + i++; + if (p[i] == 'x') + { + i++; + var res = get_index(p, pos, i, tagger); + i = res.idx; + if (res.value == null) + { + return null; + } + resultContainer.Append(res.value); + } + else + { + return null; + } + } + else + { + resultContainer.Append(p[i]); + } + } + return resultContainer; + } + + Index get_index(string p, int pos, int i, Tagger tagger) + { + if (p[i] != '[') + { + return new Index(null, i); + } + i++; + var isInRow = true; + var col = 0; + var row = 0; + var neg = 1; + + if (p[i] == '-') + { + neg = -1; + i++; + } + + for (; i < p.Length; i++) + { + var c = p[i]; + if (isInRow) + { + if (c >= '0' && c <= '9') + { + row = 10 * row + (c - '0'); + } + else if (c == ',') + { + isInRow = false; + } + else + { + return new Index(null, i); + } + } + else + { + if (c >= '0' && c <= '9') + { + col = 10 * col + (c - '0'); + } + else if (c == ']') + { + break; + } + else + { + return new Index(null, i); + } + } + } + + row *= neg; + + if (col < 0 || col >= xsize_) + { + return new Index(null, i); + } + var idx = pos + row; + if (idx < 0) + { + return new Index("_B-" + (-idx).ToString(), i); ; + } + if (idx >= tagger.word_num) + { + return new Index("_B+" + (idx - tagger.word_num + 1).ToString(), i); + } + + return new Index(tagger.x_[idx][col], i); + + } + + private struct Index + { + public int idx; + public string value; + + /// + /// Initializes a new instance of the class. + /// + public Index(string value, int idx) + { + this.idx = idx; + this.value = value; + } + } + } +} diff --git a/BotSharp.MachineLearning/CRFLite/CRFDecoder.cs b/BotSharp.MachineLearning/CRFLite/CRFDecoder.cs new file mode 100644 index 00000000..931ba99e --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/CRFDecoder.cs @@ -0,0 +1,144 @@ +using BotSharp.MachineLearning.CRFLite.Decoder; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace BotSharp.MachineLearning.CRFLite +{ + public class CRFDecoder + { + ModelReader _modelReader; + + /// + /// Load encoded model from file + /// + /// + /// The model path. + /// + /// + public void LoadModel(string modelFilename) + { + _modelReader = new ModelReader(modelFilename); + _modelReader.LoadModel(); + } + + /// + /// Loads an encoded model using the specified delegate. + /// Using this overload you can read the model e.g. + /// from network, zipped archives or other locations, as you wish. + /// + /// + /// Allows reading the model from arbitrary formats and sources. + /// + /// + /// The model file name, as used by the given + /// for file resolution. + /// + /// + public void LoadModel(Func modelLoader, string modelFilename) + { + this._modelReader = new ModelReader(modelLoader, modelFilename); + _modelReader.LoadModel(); + } + + public SegDecoderTagger CreateTagger(int nbest, int this_crf_max_word_num = BaseUtils.DEFAULT_CRF_MAX_WORD_NUM) + { + if (_modelReader == null) + { + return null; + } + + var tagger = new SegDecoderTagger(nbest, this_crf_max_word_num); + tagger.init_by_model(_modelReader); + + return tagger; + } + + //Segment given text + public int Segment(crf_seg_out[] pout, //segment result + SegDecoderTagger tagger, //Tagger per thread + List> inbuf //feature set for segment + ) + { + var ret = 0; + if (inbuf.Count == 0) + { + //Empty input string + return BaseUtils.ERROR_SUCCESS; + } + + ret = tagger.reset(); + if (ret < 0) + { + return ret; + } + + ret = tagger.add(inbuf); + if (ret < 0) + { + return ret; + } + + //parse + ret = tagger.parse(); + if (ret < 0) + { + return ret; + } + + //wrap result + ret = tagger.output(pout); + if (ret < 0) + { + return ret; + } + + return BaseUtils.ERROR_SUCCESS; + } + + + + //Segment given text + public int Segment(CRFTermOut[] pout, //segment result + DecoderTagger tagger, //Tagger per thread + List> inbuf //feature set for segment + ) + { + var ret = 0; + if (inbuf.Count == 0) + { + //Empty input string + return BaseUtils.ERROR_SUCCESS; + } + + ret = tagger.reset(); + if (ret < 0) + { + return ret; + } + + ret = tagger.add(inbuf); + if (ret < 0) + { + return ret; + } + + //parse + ret = tagger.parse(); + if (ret < 0) + { + return ret; + } + + //wrap result + ret = tagger.output(pout); + if (ret < 0) + { + return ret; + } + + return BaseUtils.ERROR_SUCCESS; + } + } +} diff --git a/BotSharp.MachineLearning/CRFLite/CRFEncoder.cs b/BotSharp.MachineLearning/CRFLite/CRFEncoder.cs new file mode 100644 index 00000000..fb228bb8 --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/CRFEncoder.cs @@ -0,0 +1,317 @@ +using BotSharp.MachineLearning.CRFLite.Encoder; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace BotSharp.MachineLearning.CRFLite +{ + public class CRFEncoder + { + public enum REG_TYPE { L1, L2 }; + + //encoding CRF model from training corpus + public bool Learn(EncoderOptions args) + { + if (args.MinDifference <= 0.0) + { + return false; + } + + if (args.CostFactor < 0.0) + { + return false; + } + + if (args.ThreadsNum <= 0) + { + return false; + } + + if (args.HugeLexMemLoad > 0) + { + } + + var modelWriter = new ModelWriter(args.ThreadsNum, args.CostFactor, + args.HugeLexMemLoad, args.RetrainModelFileName); + + if (modelWriter.Open(args.TemplateFileName, args.TrainingCorpusFileName) == false) + { + return false; + } + + var xList = modelWriter.ReadAllRecords(); + + + modelWriter.Shrink(xList, args.MinFeatureFreq); + + if (!modelWriter.SaveModelMetaData(args.ModelFileName)) + { + return false; + } + else + { + } + + if (!modelWriter.BuildFeatureSetIntoIndex(args.ModelFileName, args.SlotUsageRateThreshold, args.DebugLevel)) + { + return false; + } + else + { + } + + if (xList.Length == 0) + { + return false; + } + + var orthant = false; + if (args.RegType == REG_TYPE.L1) + { + orthant = true; + } + if (runCRF(xList, modelWriter, orthant, args) == false) + { + } + + modelWriter.SaveFeatureWeight(args.ModelFileName, args.BVQ); + + return true; + } + + bool runCRF(EncoderTagger[] x, ModelWriter modelWriter, bool orthant, EncoderOptions args) + { + var old_obj = double.MaxValue; + var converge = 0; + var lbfgs = new LBFGS(args.ThreadsNum); + lbfgs.expected = new double[modelWriter.feature_size() + 1]; + + var processList = new List(); + var parallelOption = new ParallelOptions(); + parallelOption.MaxDegreeOfParallelism = args.ThreadsNum; + + //Initialize encoding threads + for (var i = 0; i < args.ThreadsNum; i++) + { + var thread = new CRFEncoderThread(); + thread.start_i = i; + thread.thread_num = args.ThreadsNum; + thread.x = x; + thread.lbfgs = lbfgs; + thread.Init(); + processList.Add(thread); + } + + //Statistic term and result tags frequency + var termNum = 0; + int[] yfreq; + yfreq = new int[modelWriter.y_.Count]; + for (int index = 0; index < x.Length; index++) + { + var tagger = x[index]; + termNum += tagger.word_num; + for (var j = 0; j < tagger.word_num; j++) + { + yfreq[tagger.answer_[j]]++; + } + } + + //Iterative training + var startDT = DateTime.Now; + var dMinErrRecord = 1.0; + for (var itr = 0; itr < args.MaxIteration; ++itr) + { + //Clear result container + lbfgs.obj = 0.0f; + lbfgs.err = 0; + lbfgs.zeroone = 0; + + Array.Clear(lbfgs.expected, 0, lbfgs.expected.Length); + + var threadList = new List(); + for (var i = 0; i < args.ThreadsNum; i++) + { + var thread = new Thread(processList[i].Run); + thread.Start(); + threadList.Add(thread); + } + + int[,] merr; + merr = new int[modelWriter.y_.Count, modelWriter.y_.Count]; + for (var i = 0; i < args.ThreadsNum; ++i) + { + threadList[i].Join(); + lbfgs.obj += processList[i].obj; + lbfgs.err += processList[i].err; + lbfgs.zeroone += processList[i].zeroone; + + //Calculate error + for (var j = 0; j < modelWriter.y_.Count; j++) + { + for (var k = 0; k < modelWriter.y_.Count; k++) + { + merr[j, k] += processList[i].merr[j, k]; + } + } + } + + long num_nonzero = 0; + var fsize = modelWriter.feature_size(); + var alpha = modelWriter.alpha_; + if (orthant == true) + { + //L1 regularization + Parallel.For(1, fsize + 1, parallelOption, () => 0, (k, loop, subtotal) => + { + subtotal += Math.Abs(alpha[k] / modelWriter.cost_factor_); + if (alpha[k] != 0.0) + { + Interlocked.Increment(ref num_nonzero); + } + return subtotal; + }, + (subtotal) => // lock free accumulator + { + double initialValue; + double newValue; + do + { + initialValue = lbfgs.obj; // read current value + newValue = initialValue + subtotal; //calculate new value + } + while (initialValue != Interlocked.CompareExchange(ref lbfgs.obj, newValue, initialValue)); + }); + } + else + { + //L2 regularization + num_nonzero = fsize; + Parallel.For(1, fsize + 1, parallelOption, () => 0, (k, loop, subtotal) => + { + subtotal += (alpha[k] * alpha[k] / (2.0 * modelWriter.cost_factor_)); + lbfgs.expected[k] += (alpha[k] / modelWriter.cost_factor_); + return subtotal; + }, + (subtotal) => // lock free accumulator + { + double initialValue; + double newValue; + do + { + initialValue = lbfgs.obj; // read current value + newValue = initialValue + subtotal; //calculate new value + } + while (initialValue != Interlocked.CompareExchange(ref lbfgs.obj, newValue, initialValue)); + }); + } + + //Show each iteration result + var diff = (itr == 0 ? 1.0f : Math.Abs(old_obj - lbfgs.obj) / old_obj); + old_obj = lbfgs.obj; + + ShowEvaluation(x.Length, modelWriter, lbfgs, termNum, itr, merr, yfreq, diff, startDT, num_nonzero, args); + if (diff < args.MinDifference) + { + converge++; + } + else + { + converge = 0; + } + if (itr > args.MaxIteration || converge == 3) + { + break; // 3 is ad-hoc + } + + if (args.DebugLevel > 0 && (double)lbfgs.zeroone / (double)x.Length < dMinErrRecord) + { + var cc = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Red; + Console.Write("[Debug Mode] "); + Console.ForegroundColor = cc; + + //Save current best feature weight into file + dMinErrRecord = (double)lbfgs.zeroone / (double)x.Length; + modelWriter.SaveFeatureWeight("feature_weight_tmp", false); + } + + int iret; + iret = lbfgs.optimize(alpha, modelWriter.cost_factor_, orthant); + if (iret <= 0) + { + return false; + } + } + + return true; + } + + private static void ShowEvaluation(int recordNum, ModelWriter feature_index, LBFGS lbfgs, int termNum, int itr, int[,] merr, int[] yfreq, double diff, DateTime startDT, long nonzero_feature_num, EncoderOptions args) + { + var ts = DateTime.Now - startDT; + + if (args.DebugLevel > 1) + { + for (var i = 0; i < feature_index.y_.Count; i++) + { + var total_merr = 0; + var sdict = new SortedDictionary>(); + for (var j = 0; j < feature_index.y_.Count; j++) + { + total_merr += merr[i, j]; + var v = (double)merr[i, j] / (double)yfreq[i]; + if (v > 0.0001) + { + if (sdict.ContainsKey(v) == false) + { + sdict.Add(v, new List()); + } + sdict[v].Add(feature_index.y_[j]); + } + } + var vet = (double)total_merr / (double)yfreq[i]; + vet = vet * 100.0F; + + Console.ForegroundColor = ConsoleColor.Green; + Console.Write("{0} ", feature_index.y_[i]); + Console.ResetColor(); + Console.Write("[FR={0}, TE=", yfreq[i]); + Console.ForegroundColor = ConsoleColor.Yellow; + Console.Write("{0:0.00}%", vet); + Console.ResetColor(); + Console.WriteLine("]"); + + var n = 0; + foreach (var pair in sdict.Reverse()) + { + for (int index = 0; index < pair.Value.Count; index++) + { + var item = pair.Value[index]; + n += item.Length + 1 + 7; + if (n > 80) + { + //only show data in one line, more data in tail will not be show. + break; + } + Console.Write("{0}:", item); + Console.ForegroundColor = ConsoleColor.Red; + Console.Write("{0:0.00}% ", pair.Key * 100); + Console.ResetColor(); + } + if (n > 80) + { + break; + } + } + Console.WriteLine(); + } + } + + var act_feature_rate = (double)(nonzero_feature_num) / (double)(feature_index.feature_size()) * 100.0; + //Logger.WriteLine("iter={0} terr={1:0.00000} serr={2:0.00000} diff={3:0.000000} fsize={4}({5:0.00}% act)", itr, 1.0 * lbfgs.err / termNum, 1.0 * lbfgs.zeroone / recordNum, diff, feature_index.feature_size(), act_feature_rate); + //Logger.WriteLine("Time span: {0}, Aver. time span per iter: {1}", ts, new TimeSpan(0, 0, (int)(ts.TotalSeconds / (itr + 1)))); + } + } +} diff --git a/BotSharp.MachineLearning/CRFLite/CRFSharpHelper.cs b/BotSharp.MachineLearning/CRFLite/CRFSharpHelper.cs new file mode 100644 index 00000000..674133a3 --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/CRFSharpHelper.cs @@ -0,0 +1,41 @@ +using BotSharp.MachineLearning.CRFLite.Decoder; +using System; +using System.Collections.Generic; +using System.Text; + +namespace BotSharp.MachineLearning.CRFLite +{ + public class SegToken + { + public int offset; + public int length; + public string strTag; //CRF对应于term组合后的Tag字符串 + public double fWeight; //对应属性id的概率值,或者得分 + }; + + public class crf_seg_out : CRFTermOut + { + //Segmented token by merging raw CRF model output + public int termTotalLength; // the total term length in character + public List tokenList; + + public int Count + { + get { return tokenList.Count; } + } + + public void Clear() + { + termTotalLength = 0; + tokenList.Clear(); + } + + public crf_seg_out(int max_word_num = BaseUtils.DEFAULT_CRF_MAX_WORD_NUM): + base(max_word_num) + { + termTotalLength = 0; + tokenList = new List(); + } + }; + +} diff --git a/BotSharp.MachineLearning/CRFLite/Decoder/CRFTermOut.cs b/BotSharp.MachineLearning/CRFLite/Decoder/CRFTermOut.cs new file mode 100644 index 00000000..69468a43 --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Decoder/CRFTermOut.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Text; + +namespace BotSharp.MachineLearning.CRFLite.Decoder +{ + public class CRFTermOut + { + //Sequence label probability + public double prob; + + //Raw CRF model output + public string[] result_; + public double[] weight_; + + public CRFTermOut(int max_word_num = BaseUtils.DEFAULT_CRF_MAX_WORD_NUM) + { + prob = 0; + result_ = new string[max_word_num]; + weight_ = new double[max_word_num]; + } + } +} diff --git a/BotSharp.MachineLearning/CRFLite/Decoder/DecoderOptions.cs b/BotSharp.MachineLearning/CRFLite/Decoder/DecoderOptions.cs new file mode 100644 index 00000000..4bf694e9 --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Decoder/DecoderOptions.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; + +namespace BotSharp.MachineLearning.CRFLite.Decoder +{ + public class DecoderOptions + { + [Required] + public string strModelFileName; + [Required] + public string strInputFileName; + public string strOutputFileName; + public string strOutputSegFileName; + public int nBest; + public int thread; + public int probLevel; + public int maxword; + + public DecoderOptions() + { + thread = 1; + nBest = 1; + probLevel = 0; + maxword = 100; + } + } +} diff --git a/BotSharp.MachineLearning/CRFLite/Decoder/DecoderTagger.cs b/BotSharp.MachineLearning/CRFLite/Decoder/DecoderTagger.cs new file mode 100644 index 00000000..581fa235 --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Decoder/DecoderTagger.cs @@ -0,0 +1,471 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Text; + +namespace BotSharp.MachineLearning.CRFLite.Decoder +{ + public class DecoderTagger : Tagger + { + private readonly Pool _buildersPool = + new Pool(p => new StringBuilder(100), b => b.Clear()); + + public int forward_backward_stat; //前向后向过程运行状态,0为未运行,1为已经运行 + + //概率计算函数 + double toprob(Node n, double Z) + { + return Math.Exp(n.alpha + n.beta - n.cost - Z); + } + + //To get the fastest decoded result, please set vlevel=0 and nbest=1, since it only outputs 1-best result without probability (forward-backward and A* aren't performed, only run viterbi) + public int vlevel_; //Need to calculate probability 0 - no need to calculate, 1 - calculate sequence label probability, 2 - calculate both sequence label and individual entity probability + protected int nbest_; //output top N-best result + //CrfModel model; + ModelReader featureIndex; + + Node node(int i, int j) + { + return node_[i, j]; + } + + Heap heap_queue; //Using min-heap to get next result, it's only used when nbest > 1 + public int crf_max_word_num; + + public DecoderTagger(int nbest, int this_crf_max_word_num = BaseUtils.DEFAULT_CRF_MAX_WORD_NUM) + { + crf_max_word_num = this_crf_max_word_num; + vlevel_ = 0; + nbest_ = nbest; + cost_ = 0.0; + Z_ = 0; + + ysize_ = 0; + word_num = 0; + heap_queue = null; + node_ = null; + x_ = null; + result_ = null; + } + + public void InitializeFeatureCache() + { + feature_cache_ = new List(); + var feature_cache_every_row_size = 0; + if (featureIndex.unigram_templs_.Count > featureIndex.bigram_templs_.Count) + { + feature_cache_every_row_size = featureIndex.unigram_templs_.Count + 1; + } + else + { + feature_cache_every_row_size = featureIndex.bigram_templs_.Count + 1; + } + for (var i = 0; i < crf_max_word_num * 2; i++) + { + var features = new long[feature_cache_every_row_size]; + for (var j = 0; j < feature_cache_every_row_size; j++) + { + features[j] = -1; + } + feature_cache_.Add(features); + } + } + + //获取序列的词数 + public short get_word_num() + { + return word_num; + } + + public double prob(int i, int j) + { + return toprob(node_[i, j], Z_); + } + + //Get the probability of the i-th word's best result + public double prob(int i) + { + return toprob(node_[i, result_[i]], Z_); + } + + //Get entire sequence probability + public double prob() + { + return Math.Exp(-cost_ - Z_); + } + + //Get the string of i-th tag + public string yname(int i) { return featureIndex.y(i); } + + //设置vlevel + public void set_vlevel(int vlevel_value) + { + vlevel_ = vlevel_value; + } + + //使用模型初始化tag,必须先使用该函数初始化才能使用add和parse + //正常返回为0, 错误返回<0 + public int init_by_model(ModelReader model_p) + { + featureIndex = model_p; + ysize_ = (short)model_p.ysize(); + + if (nbest_ > 1) + { + //Only allocate heap when nbest is more than 1 + heap_queue = BaseUtils.heap_init((int)(crf_max_word_num * ysize_ * ysize_)); + } + + //Initialize feature set cache according unigram and bigram templates + InitializeFeatureCache(); + + node_ = new Node[crf_max_word_num, ysize_]; + result_ = new short[crf_max_word_num]; + + //Create node and path cache + for (short cur = 0; cur < crf_max_word_num; cur++) + { + for (short i = 0; i < ysize_; i++) + { + var n = new Node(); + node_[cur, i] = n; + + n.lpathList = new List(); + n.rpathList = new List(); + n.x = cur; + n.y = i; + } + } + + for (var cur = 1; cur < crf_max_word_num; cur++) + { + for (var j = 0; j < ysize_; ++j) + { + for (var i = 0; i < ysize_; ++i) + { + var p = new Path(); + p.add(node_[cur - 1, j], node_[cur, i]); + } + } + } + + return BaseUtils.ERROR_SUCCESS; + } + + public int initNbest() + { + var k = (int)word_num - 1; + for (var i = 0; i < ysize_; ++i) + { + var eos = BaseUtils.allc_from_heap(heap_queue); + eos.node = node_[k, i]; + eos.fx = -node_[k, i].bestCost; + eos.gx = -node_[k, i].cost; + eos.next = null; + if (BaseUtils.heap_insert(eos, heap_queue) < 0) + { + return BaseUtils.ERROR_INSERT_HEAP_FAILED; + } + } + return BaseUtils.ERROR_SUCCESS; + } + + public int next() + { + while (!BaseUtils.is_heap_empty(heap_queue)) + { + var top = BaseUtils.heap_delete_min(heap_queue); + var rnode = top.node; + + if (rnode.x == 0) + { + for (var n = top; n != null; n = n.next) + { + result_[n.node.x] = n.node.y; + } + cost_ = top.gx; + return 0; + } + + for (int index = 0; index < rnode.lpathList.Count; index++) + { + var p = rnode.lpathList[index]; + var n = BaseUtils.allc_from_heap(heap_queue); + var x_num = (rnode.x) - 1; + n.node = p.lnode; + n.gx = -p.lnode.cost - p.cost + top.gx; + n.fx = -p.lnode.bestCost - p.cost + top.gx; + // | h(x) | | g(x) | + n.next = top; + if (BaseUtils.heap_insert(n, heap_queue) < 0) + { + return BaseUtils.ERROR_INSERT_HEAP_FAILED; + } + } + } + return 0; + } + + public int reset() + { + word_num = 0; + Z_ = cost_ = 0.0; + + BaseUtils.heap_reset(heap_queue); + return BaseUtils.ERROR_SUCCESS; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + int buildLattice() + { + //Generate feature ids for all nodes and paths + RebuildFeatures(); + + for (int i = 0; i < word_num; ++i) + { + for (int j = 0; j < ysize_; ++j) + { + var currentNode = node_[i, j]; + calcCost(currentNode); + for (int index = 0; index < currentNode.lpathList.Count; ++index) + { + var p = currentNode.lpathList[index]; + calcCost(p); + } + } + } + + return BaseUtils.ERROR_SUCCESS; + } + + public int add(List> row_p) + { + x_ = row_p; + word_num = (short)x_.Count; + + return BaseUtils.ERROR_SUCCESS; + } + + + public int termbuf_build(CRFTermOut term_buf) + { + if (vlevel_ > 0) + { + //Calcuate the sequence label probability + term_buf.prob = prob(); + } + + var this_word_num = get_word_num(); + + for (var i = 0; i < this_word_num; ++i) + { + term_buf.result_[i] = yname(result_[i]); + switch (vlevel_) + { + case 0: + term_buf.weight_[i] = 0.0; + break; + case 2: + term_buf.weight_[i] = prob(i); + break; + } + } + return BaseUtils.ERROR_SUCCESS; + } + + //Label input string. The result is saved as result [] + //If nbest > 1, get nbest result by "next" + //Returen value: Successed - 0, Failed < 0 + public int parse() + { + var ret = 0; + //no word need to be labeled + if (word_num == 0) + { + return BaseUtils.ERROR_SUCCESS; + } + + //building feature set + ret = buildFeatures(); + if (ret < 0) + { + return ret; + } + + + ret = buildLattice(); + if (ret < 0) + { + return ret; + } + + //4.forward-backward when we need to calcuate probability + if (vlevel_ > 0) + { + forwardbackward(); + } + + + //5.using viterbi to search best result path + ret = viterbi(); + if (ret < 0) + { + return ret; + } + + //6.initNbest + // 求nbest(n>1)时的数据结构初始化,此后可以调用next()来获取nbest结果 + if (nbest_ > 1) + { + //如果只求1-best,不需要使用initNbest()和next()获取结果 + ret = initNbest(); + if (ret < 0) + { + return ret; + } + + } + + return BaseUtils.ERROR_SUCCESS; + } + + + public int buildFeatures() + { + if (word_num <= 0) + { + return BaseUtils.ERROR_INVALIDATED_PARAMETER; + } + using (var v = _buildersPool.GetOrCreate()) + { + var builder = v.Item; + var id = 0; + var feature_cache_row_size = 0; + var feature_cache_size = 0; + for (var cur = 0; cur < word_num; cur++) + { + feature_cache_row_size = 0; + for (int index = 0; index < featureIndex.unigram_templs_.Count; index++) + { + var templ = featureIndex.unigram_templs_[index]; + var res = featureIndex.apply_rule(templ, cur, builder, this); + if (res == null) + { + return BaseUtils.ERROR_EMPTY_FEATURE; + } + id = featureIndex.get_id(res.ToString()); + if (id != -1) + { + feature_cache_[feature_cache_size][feature_cache_row_size] = id; + feature_cache_row_size++; + } + } + feature_cache_[feature_cache_size][feature_cache_row_size] = -1; + feature_cache_size++; + } + + for (var cur = 0; cur < word_num; cur++) + { + feature_cache_row_size = 0; + for (int index = 0; index < featureIndex.bigram_templs_.Count; index++) + { + var templ = featureIndex.bigram_templs_[index]; + var strFeature = featureIndex.apply_rule(templ, cur, builder, this); + if (strFeature == null) + { + return BaseUtils.ERROR_EMPTY_FEATURE; + } + + id = featureIndex.get_id(strFeature.ToString()); + if (id != -1) + { + feature_cache_[feature_cache_size][feature_cache_row_size] = id; + feature_cache_row_size++; + } + } + feature_cache_[feature_cache_size][feature_cache_row_size] = -1; + feature_cache_size++; + } + + return BaseUtils.ERROR_SUCCESS; + } + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void calcCost(Node n) + { + double c = 0; + var f = feature_cache_[n.fid]; + + for (int i = 0; i < f.Length; ++i) + { + int fCurrent = (int)f[i]; + if (fCurrent == -1) + break; + c += featureIndex.GetAlpha(fCurrent + n.y); + } + + n.cost = featureIndex.cost_factor_ * c; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void calcCost(Path p) + { + double c = 0; + long[] f = feature_cache_[p.fid]; + for (int i = 0; i < f.Length; ++i) + { + int fCurrent = (int)f[i]; + if (fCurrent == -1) + break; + c += featureIndex.GetAlpha((fCurrent + p.lnode.y * ysize_ + p.rnode.y)); + } + + p.cost = featureIndex.cost_factor_ * c; + } + + + public int output(CRFTermOut[] pout) + { + var n = 0; + var ret = 0; + + if (nbest_ == 1) + { + //If only best result and no need probability, "next" is not to be used + ret = termbuf_build(pout[0]); + if (ret < 0) + { + return ret; + } + } + else + { + //Fill the n best result + var iNBest = nbest_; + if (pout.Length < iNBest) + { + iNBest = pout.Length; + } + + for (n = 0; n < iNBest; ++n) + { + ret = next(); + if (ret < 0) + { + break; + } + + ret = termbuf_build(pout[n]); + if (ret < 0) + { + return ret; + } + } + } + + return BaseUtils.ERROR_SUCCESS; + } + } +} diff --git a/BotSharp.MachineLearning/CRFLite/Decoder/ModelReader.cs b/BotSharp.MachineLearning/CRFLite/Decoder/ModelReader.cs new file mode 100644 index 00000000..d20a44be --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Decoder/ModelReader.cs @@ -0,0 +1,251 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using BotSharp.MachineLearning.CRFLite.Utils; + +namespace BotSharp.MachineLearning.CRFLite.Decoder +{ + public class ModelReader : BaseModel + { + private readonly Func modelLoader = null; + + public uint version; //模型版本号,读取模型时读入 + private CRFLite.Utils.DoubleArrayTrieSearch da; //特征集合 + + /// + /// Returns the model path. + /// + public string ModelPath { get; private set; } + + /// + /// Creates a new + /// that will load the model from the file system, + /// using the given . + /// + /// + /// Path to the model. + /// + public ModelReader(string modelPath) : + this(GetStreamFromFileSystem, modelPath) + { + + } + + /// + /// Creates a new + /// that will load the model from the file system, + /// using the given . + /// + /// + /// A delegate capable of resolving + /// the given + /// into a stream with the model file. + /// + /// + /// Path to the model. + /// + public ModelReader(Func modelLoader, + string modelPath) + { + this.modelLoader = modelLoader; + this.ModelPath = modelPath; + } + + /// + /// Loads the model into memory. + /// + public void LoadModel() + { + //Load model meta data + LoadMetadata(); + + //Load all feature set data + LoadFeatureSet(); + + //Load all features alpha data + LoadFeatureWeights(); + } + + //获取key对应的特征id + public virtual int get_id(string str) + { + return da.SearchByPerfectMatch(str); + } + + public virtual double GetAlpha(long index) + { + return alpha_[index]; + } + + /// + /// The default model loading strategy - + /// load files from the file system. + /// + /// + /// Model file path. + /// + /// A stream containing the requested file. + /// + private static Stream GetStreamFromFileSystem(string path) + { + path.ThrowIfNotExists(); + return File.OpenRead(path); + } + + /// + /// Provides access to the metadata stream. + /// + /// + /// A instance + /// that points to the model metadata file. + /// + private Stream GetMetadataStream() + { + string path = ModelPath.ToMetadataModelName(); + + return modelLoader(path); + } + + /// + /// Provides access to the feature set stream. + /// + /// + /// A instance + /// that allows accessing the model feature set file. + /// + private Stream GetFeatureSetStream() + { + string path = ModelPath.ToFeatureSetFileName(); + + return modelLoader(path); + } + + /// + /// Provides access to the feature set stream. + /// + /// + /// A instance + /// that allows accessing the model feature weight file. + /// + private Stream GetFeatureWeightStream() + { + string path = ModelPath.ToFeatureWeightFileName(); + + return modelLoader(path); + } + + private void LoadMetadata() + { + using (Stream metadataStream = GetMetadataStream()) + { + var sr = new StreamReader(metadataStream); + string strLine; + + //读入版本号 + strLine = sr.ReadLine(); + version = uint.Parse(strLine.Split(':')[1].Trim()); + + //读入cost_factor + strLine = sr.ReadLine(); + cost_factor_ = double.Parse(strLine.Split(':')[1].Trim()); + + //读入maxid + strLine = sr.ReadLine(); + maxid_ = long.Parse(strLine.Split(':')[1].Trim()); + + //读入xsize + strLine = sr.ReadLine(); + xsize_ = uint.Parse(strLine.Split(':')[1].Trim()); + + //读入空行 + strLine = sr.ReadLine(); + + //读入待标注的标签 + y_ = new List(); + while (true) + { + strLine = sr.ReadLine(); + if (strLine.Length == 0) + { + break; + } + y_.Add(strLine); + } + + //读入unigram和bigram模板 + unigram_templs_ = new List(); + bigram_templs_ = new List(); + while (sr.EndOfStream == false) + { + strLine = sr.ReadLine(); + if (strLine.Length == 0) + { + break; + } + if (strLine[0] == 'U') + { + unigram_templs_.Add(strLine); + } + if (strLine[0] == 'B') + { + bigram_templs_.Add(strLine); + } + } + sr.Close(); + } + } + + private void LoadFeatureSet() + { + Stream featureSetStream = GetFeatureSetStream(); + da = new DoubleArrayTrieSearch(); + da.Load(featureSetStream); + } + + private void LoadFeatureWeights() + { + //feature weight array + alpha_ = new double[maxid_ + 1]; + + using (Stream featureWeightStream = GetFeatureWeightStream()) + { + //Load all features alpha data + var sr_alpha = new StreamReader(featureWeightStream); + var br_alpha = new BinaryReader(sr_alpha.BaseStream); + + //Get VQ Size + int vqSize = br_alpha.ReadInt32(); + + if (vqSize > 0) + { + //This is a VQ model, we need to get code book at first + List vqCodeBook = new List(); + for (int i = 0; i < vqSize; i++) + { + vqCodeBook.Add(br_alpha.ReadDouble()); + } + + //Load weights + for (long i = 0; i < maxid_; i++) + { + int vqIdx = br_alpha.ReadByte(); + alpha_[i] = vqCodeBook[vqIdx]; + } + } + else + { + //This is a normal model + for (long i = 0; i < maxid_; i++) + { + alpha_[i] = br_alpha.ReadSingle(); + } + } + + br_alpha.Close(); + } + } + + } +} diff --git a/BotSharp.MachineLearning/CRFLite/Decoder/ModelReaderExtensions.cs b/BotSharp.MachineLearning/CRFLite/Decoder/ModelReaderExtensions.cs new file mode 100644 index 00000000..fa4602e8 --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Decoder/ModelReaderExtensions.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace BotSharp.MachineLearning.CRFLite.Decoder +{ + internal static class ModelReaderExtensions + { + private static readonly string featureFileNameExtension = ".feature"; + private static readonly string weightFileNameExtension = ".alpha"; + + internal static string ToMetadataModelName(this string modelName) + { + return modelName; + } + + internal static string ToFeatureSetFileName(this string modelName) + { + return String.Concat(modelName, featureFileNameExtension); + } + + internal static string ToFeatureWeightFileName(this string modelName) + { + return String.Concat(modelName, weightFileNameExtension); + } + + internal static void ThrowIfNotExists(this string fileName) + { + if (String.IsNullOrWhiteSpace(fileName)) + throw new ArgumentNullException("fileName", + "Please specify a valid model path"); + + if (!File.Exists(fileName)) + throw new FileNotFoundException("fileName", + "Please specify a valid model path"); + } + } +} diff --git a/BotSharp.MachineLearning/CRFLite/Encoder/CRFEncoderThread.cs b/BotSharp.MachineLearning/CRFLite/Encoder/CRFEncoderThread.cs new file mode 100644 index 00000000..dfd04c3d --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Encoder/CRFEncoderThread.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; + +namespace BotSharp.MachineLearning.CRFLite.Encoder +{ + public class CRFEncoderThread + { + public EncoderTagger[] x; + public int start_i; + public int thread_num; + public int zeroone; + public int err; + public double obj; + public Node[,] node_; + short[] result_; + public short max_xsize_; + public LBFGS lbfgs; + public int[,] merr; + + public void Init() + { + if (x.Length == 0) + { + return; + } + + var ysize_ = x[0].ysize_; + max_xsize_ = 0; + for (var i = start_i; i < x.Length; i += thread_num) + { + if (max_xsize_ < x[i].word_num) + { + max_xsize_ = x[i].word_num; + } + } + + result_ = new short[max_xsize_]; + node_ = new Node[max_xsize_, ysize_]; + for (var i = 0; i < max_xsize_; i++) + { + for (var j = 0; j < ysize_; j++) + { + node_[i, j] = new Node(); + node_[i, j].x = (short)i; + node_[i, j].y = (short)j; + node_[i, j].lpathList = new List(ysize_); + node_[i, j].rpathList = new List(ysize_); + } + } + + for (short cur = 1; cur < max_xsize_; ++cur) + { + for (short j = 0; j < ysize_; ++j) + { + for (short i = 0; i < ysize_; ++i) + { + var path = new Path(); + path.fid = -1; + path.cost = 0.0; + path.add(node_[cur - 1, j], node_[cur, i]); + } + } + } + + merr = new int[ysize_, ysize_]; + } + + public void Run() + { + //Initialize thread self data structure + obj = 0.0f; + err = zeroone = 0; + //expected.Clear(); + Array.Clear(merr, 0, merr.Length); + for (var i = start_i; i < x.Length; i += thread_num) + { + x[i].Init(result_, node_); + obj += x[i].gradient(lbfgs.expected); + var error_num = x[i].eval(merr); + err += error_num; + if (error_num > 0) + { + ++zeroone; + } + } + } + + } +} diff --git a/BotSharp.MachineLearning/CRFLite/Encoder/DefaultFeatureLexicalDict.cs b/BotSharp.MachineLearning/CRFLite/Encoder/DefaultFeatureLexicalDict.cs new file mode 100644 index 00000000..797d27da --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Encoder/DefaultFeatureLexicalDict.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace BotSharp.MachineLearning.CRFLite.Encoder +{ + public class DefaultFeatureLexicalDict : IFeatureLexicalDict + { + CRFLite.Utils.BTreeDictionary featureset_dict_; + long maxid_; + Object thisLock = new object(); + ParallelOptions parallelOption; + + public DefaultFeatureLexicalDict(int thread_num) + { + featureset_dict_ = new CRFLite.Utils.BTreeDictionary(StringComparer.Ordinal, 128); + maxid_ = 0; + parallelOption = new ParallelOptions(); + parallelOption.MaxDegreeOfParallelism = thread_num; + } + + public void Clear() + { + featureset_dict_.Clear(); + featureset_dict_ = null; + } + + public long Size + { + get + { + return featureset_dict_.Count; + } + } + + public void Shrink(int freq) + { + var i = 0; + while (i < featureset_dict_.Count) + { + if (featureset_dict_.ValueList[i].Value < freq) + { + //If the feature's frequency is less than specific frequency, drop the feature. + featureset_dict_.RemoveAt(i); + } + else + { + i++; + } + } + } + + public void GenerateLexicalIdList(out IList keyList, out IList valList) + { + keyList = featureset_dict_.KeyList; + var fixArrayValue = new int[Size]; + valList = fixArrayValue; + + Parallel.For(0, featureset_dict_.ValueList.Count, parallelOption, i => + { + fixArrayValue[i] = (int)featureset_dict_.ValueList[i].Key; + }); + + } + + public long RegenerateFeatureId(CRFLite.Utils.BTreeDictionary old2new, long ysize) + { + long new_maxid = 0; + //Regenerate new feature id and create feature ids mapping + foreach (var it in featureset_dict_) + { + var strFeature = it.Key; + //Regenerate new feature id + old2new.Add(it.Value.Key, new_maxid); + it.Value.Key = new_maxid; + + var addValue = (strFeature[0] == 'U' ? ysize : ysize * ysize); + new_maxid += addValue; + } + + return new_maxid; + } + + //Get feature id from feature set by feature string + //If feature string is not existed in the set, generate a new id and return it + private long GetId(string key) + { + FeatureIdPair pair; + if (featureset_dict_.TryGetValue(key, out pair) == true) + { + return pair.Key; + } + + return BaseUtils.ERROR_INVALIDATED_FEATURE; + } + + public long GetOrAddId(string key) + { + FeatureIdPair pair; + if (featureset_dict_.TryGetValue(key, out pair) == true && pair != null) + { + //Find its feature id + System.Threading.Interlocked.Increment(ref pair.Value); + } + else + { + lock (thisLock) + { + if (featureset_dict_.TryGetValue(key, out pair) == true) + { + System.Threading.Interlocked.Increment(ref pair.Value); + } + else + { + var oldValue = Interlocked.Increment(ref maxid_) - 1; + pair = new FeatureIdPair(oldValue, 1); + featureset_dict_.Add(key, pair); + } + } + } + return pair.Key; + } + } +} diff --git a/BotSharp.MachineLearning/CRFLite/Encoder/EncoderOptions.cs b/BotSharp.MachineLearning/CRFLite/Encoder/EncoderOptions.cs new file mode 100644 index 00000000..9fa61c70 --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Encoder/EncoderOptions.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Text; + +namespace BotSharp.MachineLearning.CRFLite.Encoder +{ + public class EncoderOptions + { + /// + /// Maximum iteration + /// + public int MaxIteration { get; set; } + + /// + /// Minimum feature frequency, if one feature's frequency is less than this value, the feature will be dropped. + /// + public int MinFeatureFreq = 2; + + /// + /// Minimum diff value, when diff less than the value consecutive 3 times, the process will be ended. + /// + public double MinDifference; + + /// + /// The maximum slot usage rate threshold when building feature set. + /// + public double SlotUsageRateThreshold { get; set; } + + /// + /// The amount of threads used to train model. + /// + public int ThreadsNum { get; set; } + + /// + /// Regularization type + /// + public CRFEncoder.REG_TYPE RegType { get; set; } + + /// + /// Template file name + /// + [Required] + public string TemplateFileName { get; set; } + + /// + /// Training corpus file name + /// + [Required] + public string TrainingCorpusFileName { get; set; } + + /// + /// Encoded model file name + /// + [Required] + public string ModelFileName { get; set; } + + /// + /// The model file name for re-training + /// + public string RetrainModelFileName { get; set; } + + /// + /// Debug level + /// + public int DebugLevel { get; set; } + + /// + /// + /// + public uint HugeLexMemLoad = 0; + + /// + /// cost factor, too big or small value may lead encoded model over tune or under tune + /// + public double CostFactor { get; set; } + + /// + /// If we build vector quantization model for feature weights + /// + public bool BVQ { get; set; } + + public EncoderOptions() + { + MaxIteration = 100; + MinFeatureFreq = 2; + MinDifference = 0.0001; + SlotUsageRateThreshold = 0.95; + ThreadsNum = 1; + RegType = CRFEncoder.REG_TYPE.L2; + CostFactor = 1.0; + } + } +} diff --git a/BotSharp.MachineLearning/CRFLite/Encoder/EncoderTagger.cs b/BotSharp.MachineLearning/CRFLite/Encoder/EncoderTagger.cs new file mode 100644 index 00000000..b43f74af --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Encoder/EncoderTagger.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Linq; +using System.Text; +using System.IO; +using System.Security.Cryptography; + +namespace BotSharp.MachineLearning.CRFLite.Encoder +{ + public class EncoderTagger : Tagger + { + public ModelWriter feature_index_; + public short[] answer_; + + public int eval(int[,] merr) + { + var err = 0; + for (var i = 0; i < word_num; ++i) + { + if (answer_[i] != result_[i]) + { + ++err; + merr[answer_[i], result_[i]]++; + } + } + return err; + } + + public EncoderTagger(ModelWriter modelWriter) + { + feature_index_ = modelWriter; + ysize_ = (short)feature_index_.ysize(); + } + + public bool GenerateFeature(List> recordList) + { + word_num = (short)recordList.Count; + if (word_num == 0) + { + return false; + } + + //Try to find each record's answer tag + var x_num = 0; + var xsize = (int)feature_index_.xsize_; + answer_ = new short[word_num]; + for (int index = 0; index < recordList.Count; index++) + { + var record = recordList[index]; +//get result tag's index and fill answer + for (short k = 0; k < ysize_; ++k) + { + if (feature_index_.y(k) == record[xsize]) + { + answer_[x_num] = k; + break; + } + } + x_num++; + } + + //Build record feature set + x_ = recordList; + Z_ = 0.0; + feature_cache_ = new List(); + feature_index_.BuildFeatures(this); + x_ = null; + + return true; + } + + private void LockFreeAdd(double[] expected, long exp_offset, double addValue) + { + double initialValue; + double newValue; + do + { + initialValue = expected[exp_offset]; // read current value + newValue = initialValue + addValue; //calculate new value + } + while (initialValue != Interlocked.CompareExchange(ref expected[exp_offset], newValue, initialValue)); + } + + private void calcExpectation(int x, int y, double[] expected) + { + var n = node_[x, y]; + var c = Math.Exp(n.alpha + n.beta - n.cost - Z_); + var offset = y + 1; //since expected array is based on 1 + for (int index = 0; index < feature_cache_[n.fid].Length; index++) + { + var item = feature_cache_[n.fid][index]; + LockFreeAdd(expected, item + offset, c); + } + + for (int index = 0; index < n.lpathList.Count; index++) + { + var p = n.lpathList[index]; + c = Math.Exp(p.lnode.alpha + p.cost + p.rnode.beta - Z_); + offset = p.lnode.y * ysize_ + p.rnode.y + 1; //since expected array is based on 1 + for (int i = 0; i < feature_cache_[p.fid].Length; i++) + { + var item = feature_cache_[p.fid][i]; + LockFreeAdd(expected, item + offset, c); + } + } + } + + public double gradient(double[] expected) + { + buildLattice(); + forwardbackward(); + var s = 0.0; + + for (var i = 0; i < word_num; ++i) + { + for (var j = 0; j < ysize_; ++j) + { + calcExpectation(i, j, expected); + } + } + + for (var i = 0; i < word_num; ++i) + { + var answer_val = answer_[i]; + var answer_Node = node_[i, answer_val]; + var offset = answer_val + 1; //since expected array is based on 1 + for (int index = 0; index < feature_cache_[answer_Node.fid].Length; index++) + { + var fid = feature_cache_[answer_Node.fid][index]; + LockFreeAdd(expected, fid + offset, -1.0f); + } + s += answer_Node.cost; // UNIGRAM cost + + + for (int index = 0; index < answer_Node.lpathList.Count; index++) + { + var lpath = answer_Node.lpathList[index]; + if (lpath.lnode.y == answer_[lpath.lnode.x]) + { + offset = lpath.lnode.y * ysize_ + lpath.rnode.y + 1; + for (int index1 = 0; index1 < feature_cache_[lpath.fid].Length; index1++) + { + var fid = feature_cache_[lpath.fid][index1]; + LockFreeAdd(expected, fid + offset, -1.0f); + } + + s += lpath.cost; // BIGRAM COST + break; + } + } + } + + viterbi(); // call for eval() + return Z_ - s; + } + + public void Init(short[] result, Node[,] node) + { + result_ = result; + node_ = node; + } + + + + + public void buildLattice() + { + RebuildFeatures(); + for (var i = 0; i < word_num; ++i) + { + for (var j = 0; j < ysize_; ++j) + { + var node_i_j = node_[i, j]; + node_i_j.cost = calcCost(node_i_j.fid, j); + for (int index = 0; index < node_i_j.lpathList.Count; index++) + { + var p = node_i_j.lpathList[index]; + var offset = p.lnode.y * ysize_ + p.rnode.y; + p.cost = calcCost(p.fid, offset); + } + } + } + } + + public double calcCost(int featureListIdx, int offset) + { + double c = 0.0f; + offset++; //since alpha_ array is based on 1 + for (int index = 0; index < feature_cache_[featureListIdx].Length; index++) + { + var fid = feature_cache_[featureListIdx][index]; + c += feature_index_.alpha_[fid + offset]; + } + return feature_index_.cost_factor_ * c; + } + } +} diff --git a/BotSharp.MachineLearning/CRFLite/Encoder/FeatureIdPair.cs b/BotSharp.MachineLearning/CRFLite/Encoder/FeatureIdPair.cs new file mode 100644 index 00000000..b266f098 --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Encoder/FeatureIdPair.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace BotSharp.MachineLearning.CRFLite.Encoder +{ + public sealed class FeatureIdPair + { + public long Key; + public int Value; + + public FeatureIdPair(long key, int value) + { + Key = key; + Value = value; + } + } +} diff --git a/BotSharp.MachineLearning/CRFLite/Encoder/FeatureItem.cs b/BotSharp.MachineLearning/CRFLite/Encoder/FeatureItem.cs new file mode 100644 index 00000000..6f665d13 --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Encoder/FeatureItem.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace BotSharp.MachineLearning.CRFLite.Encoder +{ + public sealed class FeatureItem : IComparable + { + public string strFeature; + public FeatureIdPair feaIdPair; + + public FeatureItem(string s, FeatureIdPair item) + { + strFeature = s; + feaIdPair = item; + } + + public int CompareTo(FeatureItem fi) + { + return StringComparer.Ordinal.Compare(strFeature, fi.strFeature); + } + } +} diff --git a/BotSharp.MachineLearning/CRFLite/Encoder/HugeFeatureLexicalDict.cs b/BotSharp.MachineLearning/CRFLite/Encoder/HugeFeatureLexicalDict.cs new file mode 100644 index 00000000..700f44ec --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Encoder/HugeFeatureLexicalDict.cs @@ -0,0 +1,305 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace BotSharp.MachineLearning.CRFLite.Encoder +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public class MEMORYSTATUSEX + { + public uint dwLength; + public uint dwMemoryLoad; + public ulong ullTotalPhys; + public ulong ullAvailPhys; + public ulong ullTotalPageFile; + public ulong ullAvailPageFile; + public ulong ullTotalVirtual; + public ulong ullAvailVirtual; + public ulong ullAvailExtendedVirtual; + public MEMORYSTATUSEX() + { + this.dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUSEX)); + } + } + + public sealed class FeatureFreq : IComparable + { + public string strFeature; + public long value; + + public int CompareTo(FeatureFreq fi) + { + return StringComparer.Ordinal.Compare(strFeature, fi.strFeature); + } + } + + public sealed class HugeFeatureLexicalDict : IFeatureLexicalDict + { + CRFLite.Utils.VarBigArray arrayFeatureFreq; + long arrayFeatureFreqSize; + uint SHRINK_AVALI_MEM_LOAD; + CRFLite.Utils.MD5 md5; + ParallelOptions parallelOption; + + [return: MarshalAs(UnmanagedType.Bool)] + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + static extern bool GlobalMemoryStatusEx([In, Out] MEMORYSTATUSEX lpBuffer); + + public HugeFeatureLexicalDict(int thread_num, uint shrinkMemLoad) + { + SHRINK_AVALI_MEM_LOAD = shrinkMemLoad; + arrayFeatureFreq = new CRFLite.Utils.VarBigArray(1024 * 1024); + arrayFeatureFreqSize = 0; + md5 = new CRFLite.Utils.MD5(); + parallelOption = new ParallelOptions(); + parallelOption.MaxDegreeOfParallelism = thread_num; + } + + public void Clear() + { + arrayFeatureFreq.Clear(); + arrayFeatureFreq = null; + } + + + public CRFLite.Utils.VarBigArray featureFreq + { + get + { + return arrayFeatureFreq; + } + } + + public long Size + { + get + { + return arrayFeatureFreqSize; + } + } + + private long ParallelMerge(long startIndex, long endIndex, int freq) + { + var sizePerThread = (endIndex - startIndex + 1) / parallelOption.MaxDegreeOfParallelism; + //Fistly, merge items in each block by parallel + Parallel.For(0, parallelOption.MaxDegreeOfParallelism, parallelOption, i => + { + Merge(startIndex + i * sizePerThread, startIndex + (i + 1) * sizePerThread - 1, 0); + }); + + //Secondly, merge all items + return Merge(startIndex, endIndex, freq); + } + + private void ForceCollectMemory() + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + } + + //Merge same items in sorted list + private long Merge(long startIndex, long endIndex, int freq) + { + var newEndIndex = startIndex; + + //Try to find first not null item + while ((arrayFeatureFreq[startIndex] == null) && + startIndex <= endIndex) + { + startIndex++; + } + arrayFeatureFreq[newEndIndex] = arrayFeatureFreq[startIndex]; + for (var i = startIndex + 1; i <= endIndex; i++) + { + if (arrayFeatureFreq[i] == null) + { + continue; + } + + if (arrayFeatureFreq[newEndIndex].strFeature == arrayFeatureFreq[i].strFeature) + { + //two same items, sum their value up + arrayFeatureFreq[newEndIndex].value += arrayFeatureFreq[i].value; + arrayFeatureFreq[i] = null; + } + else + { + //two different items + if (arrayFeatureFreq[newEndIndex].value >= freq) + { + newEndIndex++; + } + + arrayFeatureFreq[newEndIndex] = arrayFeatureFreq[i]; + if (newEndIndex < i) + { + arrayFeatureFreq[i] = null; + } + } + } + + return newEndIndex; + } + + //Generate feature string and its id list + public void GenerateLexicalIdList(out IList keyList, out IList valList) + { + var fixArrayKey = new CRFLite.Utils.FixedBigArray(Size, 0); + keyList = fixArrayKey; + + var fixArrayValue = new CRFLite.Utils.FixedBigArray(Size, 0); + valList = fixArrayValue; + Parallel.For(0, arrayFeatureFreqSize, parallelOption, i => + { + fixArrayKey[i] = arrayFeatureFreq[i].strFeature; + fixArrayValue[i] = (int)(arrayFeatureFreq[i].value); + }); + } + + Object thisLock = new object(); + //Generate feature id by NGram rules + public long RegenerateFeatureId(CRFLite.Utils.BTreeDictionary old2new, long ysize) + { + long maxid_ = 0; + Parallel.For(0, arrayFeatureFreqSize, parallelOption, i => + { + //Generate new feature id + var addValue = (arrayFeatureFreq[i].strFeature[0] == 'U' ? ysize : ysize * ysize); + var oldValue = maxid_; + while (System.Threading.Interlocked.CompareExchange(ref maxid_, oldValue + addValue, oldValue) != oldValue) + { + oldValue = maxid_; + } + + //Create existed and new feature ids mapping + lock (thisLock) + { + old2new.Add( + GetId(arrayFeatureFreq[i].strFeature), + oldValue); + } + + arrayFeatureFreq[i].value = oldValue; + }); + return maxid_; + } + + //Shrink entire list + public void Shrink(int freq) + { + var newEndIndex = Shrink(0, arrayFeatureFreqSize - 1, freq); + arrayFeatureFreqSize = newEndIndex + 1; + } + + //Shrink item list + private long Shrink(long startIndex, long endIndex, int freq) + { + Console.Write("Sorting..."); + arrayFeatureFreq.Sort(startIndex, endIndex - startIndex + 1, parallelOption.MaxDegreeOfParallelism); + Console.Write("Merging..."); + + var newEndIndex = ParallelMerge(startIndex, endIndex, freq); + sortedEndIndex = newEndIndex; + + Console.WriteLine("Done!"); + ForceCollectMemory(); + + return newEndIndex; + } + + //Get feature string id + private long GetId(string strFeature) + { + var rawbytes = Encoding.UTF8.GetBytes(strFeature); + + lock (thisLock) + { + return md5.Compute64BitHash(rawbytes); + } + } + + private long sortedEndIndex = 0; + private int ShrinkingLock = 0; + private int AddLock = 0; + //Add the feature string into list and get feature string id + public long GetOrAddId(string strFeature) + { + while (ShrinkingLock == 1) { Thread.Sleep(5000); } + + //add item-adding lock + Interlocked.Increment(ref AddLock); + + var newFFItem = new FeatureFreq(); + newFFItem.strFeature = strFeature; + newFFItem.value = 1; + if (sortedEndIndex > 0) + { + var ff = arrayFeatureFreq.BinarySearch(0, sortedEndIndex, newFFItem); + if (ff != null) + { + Interlocked.Increment(ref ff.value); + //free item-adding lock + Interlocked.Decrement(ref AddLock); + return GetId(strFeature); + } + } + + var oldValue = Interlocked.Increment(ref arrayFeatureFreqSize) - 1; + arrayFeatureFreq[oldValue] = newFFItem; + + //free item-adding lock + Interlocked.Decrement(ref AddLock); + + //Check whether shrink process should be started + uint memoryLoad = 0; + if (oldValue % 10000000 == 0) + { + var msex = new MEMORYSTATUSEX(); + GlobalMemoryStatusEx(msex); + memoryLoad = msex.dwMemoryLoad; + } + + if (memoryLoad >= SHRINK_AVALI_MEM_LOAD) + { + if (Interlocked.CompareExchange(ref ShrinkingLock, 1, 0) == 0) + { + //Double check whether shrink should be started + var msex = new MEMORYSTATUSEX(); + GlobalMemoryStatusEx(msex); + if (msex.dwMemoryLoad >= SHRINK_AVALI_MEM_LOAD) + { + while (AddLock != 0) { Thread.Sleep(1000); } + + var startDT = DateTime.Now; + Console.WriteLine("Begin to shrink [Feature Size: {0}]...", arrayFeatureFreqSize); + var newArrayFeatureFreqSize = Shrink(0, arrayFeatureFreqSize - 1, 0) + 1; + + GlobalMemoryStatusEx(msex); + if (msex.dwMemoryLoad >= SHRINK_AVALI_MEM_LOAD - 1) + { + //Still have enough available memory, raise shrink threshold + SHRINK_AVALI_MEM_LOAD = msex.dwMemoryLoad + 1; + if (SHRINK_AVALI_MEM_LOAD >= 100) + { + //if use more than 100% memory, the performance will extremely reduce + SHRINK_AVALI_MEM_LOAD = 100; + } + } + + arrayFeatureFreqSize = newArrayFeatureFreqSize; + var ts = DateTime.Now - startDT; + Console.WriteLine("Shrink has been done!"); + Console.WriteLine("[Feature Size:{0}, TimeSpan:{1}, Next Shrink Rate:{2}%]", arrayFeatureFreqSize, ts, SHRINK_AVALI_MEM_LOAD); + } + + Interlocked.Decrement(ref ShrinkingLock); + } + } + return GetId(strFeature); + } + } +} diff --git a/BotSharp.MachineLearning/CRFLite/Encoder/IFeatureLexicalDict.cs b/BotSharp.MachineLearning/CRFLite/Encoder/IFeatureLexicalDict.cs new file mode 100644 index 00000000..10efa8a3 --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Encoder/IFeatureLexicalDict.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace BotSharp.MachineLearning.CRFLite.Encoder +{ + public interface IFeatureLexicalDict + { + void Shrink(int freq); + long GetOrAddId(string strFeature); + long RegenerateFeatureId(CRFLite.Utils.BTreeDictionary old2new, long ysize); + void GenerateLexicalIdList(out IList fea, out IList val); + void Clear(); + + long Size + { + get; + } + } +} diff --git a/BotSharp.MachineLearning/CRFLite/Encoder/LBFGS.cs b/BotSharp.MachineLearning/CRFLite/Encoder/LBFGS.cs new file mode 100644 index 00000000..0c6f8995 --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Encoder/LBFGS.cs @@ -0,0 +1,349 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace BotSharp.MachineLearning.CRFLite.Encoder +{ + public class LBFGS + { + double [] diag; + CRFLite.Utils.FixedBigArray w; + Mcsrch mcsrch_; + long nfev, point, npt, iter, info, ispt, iypt; + int iflag_; + double stp; + public int zeroone; + public int err; + public double obj; + + public double[] expected; + public double[] v; + public double[] xi; + + private ParallelOptions parallelOption; + + public LBFGS(int thread_num) + { + iflag_ = 0; nfev = 0; + point = 0; npt = 0; iter = 0; info = 0; + ispt = 0; iypt = 0; + stp = 0.0; + mcsrch_ = new Mcsrch(thread_num); + + parallelOption = new ParallelOptions(); + parallelOption.MaxDegreeOfParallelism = thread_num; + } + + private double ddot_(long size, CRFLite.Utils.FixedBigArray dx, long dx_idx, CRFLite.Utils.FixedBigArray dy, long dy_idx) + { + double ret = 0.0f; + Parallel.For(0, size, parallelOption, () => 0, (i, loop, subtotal) => + { + subtotal += dx[i + dx_idx] * dy[i + dy_idx]; + return subtotal; + }, + (subtotal) => // lock free accumulator + { + double initialValue; + double newValue; + do + { + initialValue = ret; // read current value + newValue = initialValue + subtotal; //calculate new value + } + while (initialValue != Interlocked.CompareExchange(ref ret, newValue, initialValue)); + }); + return ret; + } + + + private double ddot_(long size, double[] dx, long dx_idx, double[] dy, long dy_idx) + { + double ret = 0.0f; + Parallel.For(0, size, parallelOption, () => 0, (i, loop, subtotal) => + { + subtotal += dx[i + dx_idx] * dy[i + dy_idx]; + return subtotal; + }, + (subtotal) => // lock free accumulator + { + double initialValue; + double newValue; + do + { + initialValue = ret; // read current value + newValue = initialValue + subtotal; //calculate new value + } + while (initialValue != Interlocked.CompareExchange(ref ret, newValue, initialValue)); + }); + return ret; + } + void pseudo_gradient(double[] x, double C) + { + var size = expected.LongLength - 1; + Parallel.For(1, size + 1, parallelOption, i => + { + if (x[i] == 0) + { + if (expected[i] + C < 0) + { + v[i] = (expected[i] + C); + } + else if (expected[i] - C > 0) + { + v[i] = (expected[i] - C); + } + else + { + v[i] = 0; + } + } + else + { + v[i] = (expected[i] + C * sigma(x[i])); + } + }); + } + double sigma(double x) + { + if (x > 0) return 1.0; + else if (x < 0) return -1.0; + return 0.0; + } + + public int optimize(double[] x, double C, bool orthant) + { + const long msize = 5; + var size = x.LongLength - 1; + if (w == null || w.LongLength == 0) + { + iflag_ = 0; + w = new CRFLite.Utils.FixedBigArray(size * (2 * msize + 1) + 2 * msize, 1); + diag = new double[size + 1]; + if (orthant == true) + { + xi = new double[size + 1]; + v = new double[size + 1]; + } + } + + if (orthant == true) + { + pseudo_gradient(x, C); + } + else + { + v = expected; + } + + lbfgs_optimize(msize, x, orthant, C); + if (iflag_ < 0) + { + Console.WriteLine("routine stops with unexpected error"); + return -1; + } + + return iflag_; + } + + void lbfgs_optimize(long msize, double[] x, bool orthant, double C) + { + var size = x.LongLength - 1; + var yy = 0.0; + var ys = 0.0; + long bound = 0; + long cp = 0; + var bExit = false; + + // initialization + if (iflag_ == 0) + { + point = 0; + ispt = size + (msize << 1); + iypt = ispt + size * msize; + + Parallel.For(1, size + 1, parallelOption, i => + { + diag[i] = 1.0f; + w[ispt + i] = -v[i]; + w[i] = expected[i]; + }); + + if (orthant == true) + { + Parallel.For(1, size + 1, parallelOption, i => + { + xi[i] = (x[i] != 0 ? sigma(x[i]) : sigma(-v[i])); + }); + } + + //第一次试探步长 + stp = 1.0f / Math.Sqrt(ddot_(size, v, 1, v, 1)); + + ++iter; + info = 0; + nfev = 0; + } + + // MAIN ITERATION LOOP + bExit = LineSearchAndUpdateStepGradient(msize, x, orthant); + while (bExit == false) + { + ++iter; + info = 0; + + if (orthant == true) + { + Parallel.For(1, size + 1, parallelOption, i => + { + xi[i] = (x[i] != 0 ? sigma(x[i]) : sigma(-v[i])); + }); + } + + if (iter > size) + { + bound = size; + } + + // COMPUTE -H*G USING THE FORMULA GIVEN IN: Nocedal, J. 1980, + // "Updating quasi-Newton matrices with limited storage", + // Mathematics of Computation, Vol.24, No.151, pp. 773-782. + ys = ddot_(size, w, iypt + npt + 1, w, ispt + npt + 1); + yy = ddot_(size, w, iypt + npt + 1, w, iypt + npt + 1); + + var r_ys_yy = ys / yy; + Parallel.For(1, size + 1, parallelOption, i => + { + diag[i] = r_ys_yy; + w[i] = -v[i]; + }); + + cp = point; + if (point == 0) + { + cp = msize; + } + + w[size + cp] = (1.0 / ys); + //回退次数 + bound = Math.Min(iter - 1, msize); + cp = point; + for (var i = 1; i <= bound; ++i) + { + --cp; + if (cp == -1) cp = msize - 1; + var sq = ddot_(size, w, ispt + cp * size + 1, w, 1); + var inmc = size + msize + cp + 1; + var iycn = iypt + cp * size; + w[inmc] = (w[size + cp + 1] * sq); + var d = -w[inmc]; + + Parallel.For(1, size + 1, parallelOption, j => + { + w[j] = (w[j] + d * w[iycn + j]); + }); + } + + Parallel.For(1, size + 1, parallelOption, i => + { + w[i] = (diag[i] * w[i]); + }); + + for (var i = 1; i <= bound; ++i) + { + var yr = ddot_(size, w, iypt + cp * size + 1, w, 1); + var beta = w[size + cp + 1] * yr; + var inmc = size + msize + cp + 1; + beta = w[inmc] - beta; + var iscn = ispt + cp * size; + + Parallel.For(1, size + 1, parallelOption, j => + { + w[j] = (w[j] + beta * w[iscn + j]); + }); + + ++cp; + if (cp == msize) + { + cp = 0; + } + } + + if (orthant == true) + { + Parallel.For(1, size + 1, parallelOption, i => + { + w[i] = (sigma(w[i]) == sigma(-v[i]) ? w[i] : 0); + }); + } + + + // STORE THE NEW SEARCH DIRECTION + var offset = ispt + point * size; + Parallel.For(1, size + 1, parallelOption, i => + { + w[offset + i] = w[i]; + w[i] = expected[i]; + }); + + stp = 1.0f; + nfev = 0; + bExit = LineSearchAndUpdateStepGradient(msize, x, orthant); + } + } + + private bool LineSearchAndUpdateStepGradient(long msize, double[] x, bool orthant) + { + var size = x.LongLength - 1; + var bExit = false; + mcsrch_.mcsrch(x, obj, v, w, ispt + point * size, + ref stp, ref info, ref nfev, diag); + if (info == -1) + { + if (orthant == true) + { + Parallel.For(1, size + 1, parallelOption, i => + { + x[i] = (sigma(x[i]) == sigma(xi[i]) ? x[i] : 0); + }); + } + + + iflag_ = 1; // next value + bExit = true; + } + else if (info != 1) + { + //MCSRCH error, please see error code in info + iflag_ = -1; + bExit = true; + } + else + { + // COMPUTE THE NEW STEP AND GRADIENT CHANGE + npt = point * size; + Parallel.For(1, size + 1, parallelOption, i => + { + w[ispt + npt + i] = (stp * w[ispt + npt + i]); + w[iypt + npt + i] = expected[i] - w[i]; + }); + + ++point; + if (point == msize) + { + point = 0; + } + + var gnorm = Math.Sqrt(ddot_(size, v, 1, v, 1)); + var xnorm = Math.Max(1.0, Math.Sqrt(ddot_(size, x, 1, x, 1))); + if (gnorm / xnorm <= BaseUtils.eps) + { + iflag_ = 0; // OK terminated + bExit = true; + } + } + + return bExit; + } + } +} diff --git a/BotSharp.MachineLearning/CRFLite/Encoder/Mcsrch.cs b/BotSharp.MachineLearning/CRFLite/Encoder/Mcsrch.cs new file mode 100644 index 00000000..886f3c16 --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Encoder/Mcsrch.cs @@ -0,0 +1,461 @@ +using BotSharp.MachineLearning.CRFLite.Utils; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace BotSharp.MachineLearning.CRFLite.Encoder +{ + class Mcsrch + { + private int infoc; + private bool stage1, brackt; + private double dginit; + private double width, width1; + private double fx, dgx, fy, dgy; + private double finit; + private double dgtest; + private double stx, sty; + private double stmin, stmax; + + private ParallelOptions parallelOption; + + public Mcsrch(int thread_num) + { + infoc = 0; + stage1 = false; + brackt = false; + finit = 0.0; + dginit = 0.0; + dgtest = 0.0; + width = 0.0; + width1 = 0.0; + stx = 0.0; + fx = 0.0; + dgx = 0.0; + sty = 0.0; + fy = 0.0; + dgy = 0.0; + stmin = 0.0; + stmax = 0.0; + + parallelOption = new ParallelOptions(); + parallelOption.MaxDegreeOfParallelism = thread_num; + } + + + + void mcstep(ref double stx, ref double fx, ref double dx, + ref double sty, ref double fy, ref double dy, + ref double stp, double fp, double dp, + ref bool brackt, + double stpmin, double stpmax, + ref int info) + { + var bound = true; + double p, q, d3, r, stpq, stpc, stpf; + double gamma; + double s; + double d1, d2; + double theta; + info = 0; + + if (brackt == true && ((stp <= Math.Min(stx, sty) || stp >= Math.Max(stx, sty)) || + dx * (stp - stx) >= 0.0 || stpmax < stpmin)) + { + return; + } + + var sgnd = dp * (dx / Math.Abs(dx)); + if (fp > fx) + { + info = 1; + bound = true; + theta = (fx - fp) * 3 / (stp - stx) + dx + dp; + d1 = Math.Abs(theta); + d2 = Math.Abs(dx); + d1 = Math.Max(d1, d2); + d2 = Math.Abs(dp); + s = Math.Max(d1, d2); + d1 = theta / s; + gamma = s * Math.Sqrt(d1 * d1 - dx / s * (dp / s)); + if (stp < stx) + { + gamma = -gamma; + } + p = gamma - dx + theta; + q = gamma - dx + gamma + dp; + r = p / q; + stpc = stx + r * (stp - stx); + stpq = stx + dx / ((fx - fp) / + (stp - stx) + dx) / 2 * (stp - stx); + d1 = stpc - stx; + d2 = stpq - stx; + if (Math.Abs(d1) < Math.Abs(d2)) + { + stpf = stpc; + } + else + { + stpf = stpc + (stpq - stpc) / 2; + } + brackt = true; + } + else if (sgnd < 0.0) + { + info = 2; + bound = false; + theta = (fx - fp) * 3 / (stp - stx) + dx + dp; + d1 = Math.Abs(theta); + d2 = Math.Abs(dx); + d1 = Math.Max(d1, d2); + d2 = Math.Abs(dp); + s = Math.Max(d1, d2); + d1 = theta / s; + gamma = s * Math.Sqrt(d1 * d1 - dx / s * (dp / s)); + if (stp > stx) + { + gamma = -gamma; + } + p = gamma - dp + theta; + q = gamma - dp + gamma + dx; + r = p / q; + stpc = stp + r * (stx - stp); + stpq = stp + dp / (dp - dx) * (stx - stp); + + d1 = stpc - stp; + d2 = stpq - stp; + if (Math.Abs(d1) > Math.Abs(d2)) + { + stpf = stpc; + } + else + { + stpf = stpq; + } + brackt = true; + } + else if (Math.Abs(dp) < Math.Abs(dx)) + { + info = 3; + bound = true; + theta = (fx - fp) * 3 / (stp - stx) + dx + dp; + d1 = Math.Abs(theta); + d2 = Math.Abs(dx); + d1 = Math.Max(d1, d2); + d2 = Math.Abs(dp); + s = Math.Max(d1, d2); + d3 = theta / s; + d1 = 0.0f; + d2 = d3 * d3 - dx / s * (dp / s); + gamma = s * Math.Sqrt((Math.Max(d1, d2))); + if (stp > stx) + { + gamma = -gamma; + } + p = gamma - dp + theta; + q = gamma + (dx - dp) + gamma; + r = p / q; + if (r < 0.0 && gamma != 0.0) + { + stpc = stp + r * (stx - stp); + } + else if (stp > stx) + { + stpc = stpmax; + } + else + { + stpc = stpmin; + } + stpq = stp + dp / (dp - dx) * (stx - stp); + if (brackt == true) + { + d1 = stp - stpc; + d2 = stp - stpq; + if (Math.Abs(d1) < Math.Abs(d2)) + { + stpf = stpc; + } + else + { + stpf = stpq; + } + } + else + { + d1 = stp - stpc; + d2 = stp - stpq; + if (Math.Abs(d1) > Math.Abs(d2)) + { + stpf = stpc; + } + else + { + stpf = stpq; + } + } + } + else + { + info = 4; + bound = false; + if (brackt == true) + { + theta = (fp - fy) * 3 / (sty - stp) + dy + dp; + d1 = Math.Abs(theta); + d2 = Math.Abs(dy); + d1 = Math.Max(d1, d2); + d2 = Math.Abs(dp); + s = Math.Max(d1, d2); + d1 = theta / s; + gamma = s * Math.Sqrt(d1 * d1 - dy / s * (dp / s)); + if (stp > sty) + { + gamma = -gamma; + } + p = gamma - dp + theta; + q = gamma - dp + gamma + dy; + r = p / q; + stpc = stp + r * (sty - stp); + stpf = stpc; + } + else if (stp > stx) + { + stpf = stpmax; + } + else + { + stpf = stpmin; + } + } + + if (fp > fx) + { + sty = stp; + fy = fp; + dy = dp; + } + else + { + if (sgnd < 0.0) + { + sty = stx; + fy = fx; + dy = dx; + } + stx = stp; + fx = fp; + dx = dp; + } + + stpf = Math.Min(stpmax, stpf); + stpf = Math.Max(stpmin, stpf); + stp = stpf; + if (brackt == true && bound) + { + if (sty > stx) + { + d1 = stx + (sty - stx) * 0.66; + stp = Math.Min(d1, stp); + } + else + { + d1 = stx + (sty - stx) * 0.66; + stp = Math.Max(d1, stp); + } + } + + return; + } + + + + const double lb3_1_gtol = 0.9; + const double xtol = 1e-16; + const double lb3_1_stpmin = 1e-20; + const double lb3_1_stpmax = 1e20; + const double ftol = 1e-4; + const double p5 = 0.5; + const double p66 = 0.66; + const double xtrapf = 4.0; + const int maxfev = 20; + + private double ddot_(long size, double[] dx, long dx_idx, FixedBigArray dy, long dy_idx) + { + double ret = 0.0f; + Parallel.For(0, size, parallelOption, () => 0, (i, loop, subtotal) => + { + subtotal += dx[i + dx_idx] * dy[i + dy_idx]; + return subtotal; + }, + (subtotal) => // lock free accumulator + { + double initialValue; + double newValue; + do + { + initialValue = ret; // read current value + newValue = initialValue + subtotal; //calculate new value + } + while (initialValue != Interlocked.CompareExchange(ref ret, newValue, initialValue)); + }); + return ret; + } + + public void mcsrch(double[] x, double f, double[] g, FixedBigArray s, long s_idx, + ref double stp, ref long info, ref long nfev, double[] wa) + { + var size = x.LongLength - 1; + /* Parameter adjustments */ + if (info == -1) + { + info = 0; + nfev++; + + var dg = ddot_(size, g, 1, s, s_idx + 1); + var ftest1 = finit + stp * dgtest; + + if (brackt && ((stp <= stmin || stp >= stmax) || infoc == 0)) + { + info = 6; + Console.WriteLine("MCSRCH warning: Rounding errors prevent further progress.There may not be a step which satisfies the sufficient decrease and curvature conditions. Tolerances may be too small."); + Console.WriteLine("bracket: {0}, stp:{1}, stmin:{2}, stmax:{3}, infoc:{4}", brackt, stp, stmin, stmax, infoc); + } + if (stp == lb3_1_stpmax && f <= ftest1 && dg <= dgtest) + { + info = 5; + Console.WriteLine("MCSRCH warning: The step is too large."); + } + if (stp == lb3_1_stpmin && (f > ftest1 || dg >= dgtest)) + { + info = 4; + Console.WriteLine("MCSRCH warning: The step is too small."); + Console.WriteLine("stp:{0}, lb3_1_stpmin:{1}, f:{2}, ftest1:{3}, dg:{4}, dgtest:{5}", stp, lb3_1_stpmin, f, ftest1, dg, dgtest); + } + if (nfev >= maxfev) + { + info = 3; + Console.WriteLine("MCSRCH warning: More than {0} function evaluations were required at the present iteration.", maxfev); + } + if (brackt && stmax - stmin <= xtol * stmax) + { + info = 2; + Console.WriteLine("MCSRCH warning: Relative width of the interval of uncertainty is at most xtol."); + } + if (f <= ftest1 && Math.Abs(dg) <= lb3_1_gtol * (-dginit)) + { + info = 1; + } + + if (info != 0) + { + return; + } + + if (stage1 && f <= ftest1 && dg >= Math.Min(ftol, lb3_1_gtol) * dginit) + { + stage1 = false; + } + + if (stage1 && f <= fx && f > ftest1) + { + var fm = f - stp * dgtest; + var fxm = fx - stx * dgtest; + var fym = fy - sty * dgtest; + var dgm = dg - dgtest; + var dgxm = dgx - dgtest; + var dgym = dgy - dgtest; + mcstep(ref stx, ref fxm, ref dgxm, ref sty, ref fym, ref dgym, ref stp, fm, dgm, ref brackt, + stmin, stmax, ref infoc); + fx = fxm + stx * dgtest; + fy = fym + sty * dgtest; + dgx = dgxm + dgtest; + dgy = dgym + dgtest; + } + else + { + mcstep(ref stx, ref fx, ref dgx, ref sty, ref fy, ref dgy, ref stp, f, dg, ref brackt, + stmin, stmax, ref infoc); + } + + if (brackt) + { + var d1 = 0.0; + d1 = sty - stx; + if (Math.Abs(d1) >= p66 * width1) + { + stp = stx + p5 * (sty - stx); + } + width1 = width; + d1 = sty - stx; + width = Math.Abs(d1); + } + } + else + { + infoc = 1; + if (size <= 0 || stp <= 0.0) + { + return; + } + + dginit = ddot_(size, g, 1, s, s_idx + 1); + if (dginit >= 0.0) + { + return; + } + + brackt = false; + stage1 = true; + nfev = 0; + finit = f; + dgtest = ftol * dginit; + width = lb3_1_stpmax - lb3_1_stpmin; + width1 = width / p5; + + Parallel.For(1, size + 1, parallelOption, i => + { + wa[i] = x[i]; + } + ); + + stx = 0.0; + fx = finit; + dgx = dginit; + sty = 0.0; + fy = finit; + dgy = dginit; + } + + if (brackt) + { + stmin = Math.Min(stx, sty); + stmax = Math.Max(stx, sty); + } + else + { + stmin = stx; + stmax = stp + xtrapf * (stp - stx); + } + + stp = Math.Max(stp, lb3_1_stpmin); + stp = Math.Min(stp, lb3_1_stpmax); + + if ((brackt && ((stp <= stmin || stp >= stmax) || + nfev >= maxfev - 1 || infoc == 0)) || + (brackt && (stmax - stmin <= xtol * stmax))) + { + stp = stx; + } + + var stp_t = stp; + Parallel.For(1, size + 1, parallelOption, i => + { + x[i] = (wa[i] + stp_t * s[s_idx + i]); + }); + + info = -1; + } + + } +} diff --git a/BotSharp.MachineLearning/CRFLite/Encoder/ModelWriter.cs b/BotSharp.MachineLearning/CRFLite/Encoder/ModelWriter.cs new file mode 100644 index 00000000..15479d0d --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Encoder/ModelWriter.cs @@ -0,0 +1,405 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using BotSharp.MachineLearning.CRFLite.Decoder; + +namespace BotSharp.MachineLearning.CRFLite.Encoder +{ + public class ModelWriter : BaseModel + { + private readonly string modelFileName; + + private readonly Pool _buildersPool = + new Pool(p => new StringBuilder(100), b => b.Clear()); + + + int thread_num_; + public IFeatureLexicalDict featureLexicalDict; + List>> trainCorpusList; + ParallelOptions parallelOption = new ParallelOptions(); + + public ModelWriter(int thread_num, double cost_factor, + uint hugeLexShrinkMemLoad, string modelFileName) + { + cost_factor_ = cost_factor; + maxid_ = 0; + thread_num_ = thread_num; + this.modelFileName = modelFileName; + parallelOption.MaxDegreeOfParallelism = thread_num; + + if (hugeLexShrinkMemLoad > 0) + { + featureLexicalDict = new HugeFeatureLexicalDict(thread_num_, hugeLexShrinkMemLoad); + } + else + { + featureLexicalDict = new DefaultFeatureLexicalDict(thread_num_); + } + } + + //Regenerate feature id and shrink features with lower frequency + public void Shrink(EncoderTagger[] xList, int freq) + { + var old2new = new CRFLite.Utils.BTreeDictionary(); + featureLexicalDict.Shrink(freq); + maxid_ = featureLexicalDict.RegenerateFeatureId(old2new, y_.Count); + var feature_count = xList.Length; + + //Update feature ids + Parallel.For(0, feature_count, parallelOption, i => + { + for (var j = 0; j < xList[i].feature_cache_.Count; j++) + { + var newfs = new List(); + long rstValue = 0; + for (int index = 0; index < xList[i].feature_cache_[j].Length; index++) + { + var v = xList[i].feature_cache_[j][index]; + if (old2new.TryGetValue(v, out rstValue) == true) + { + newfs.Add(rstValue); + } + } + xList[i].feature_cache_[j] = newfs.ToArray(); + } + }); + } + + // Load all records and generate features + public EncoderTagger[] ReadAllRecords() + { + var arrayEncoderTagger = new EncoderTagger[trainCorpusList.Count]; + var arrayEncoderTaggerSize = 0; + + //Generate each record features + Parallel.For(0, trainCorpusList.Count, parallelOption, i => + { + var _x = new EncoderTagger(this); + if (_x.GenerateFeature(trainCorpusList[i]) == false) + { + } + else + { + var oldValue = Interlocked.Increment(ref arrayEncoderTaggerSize) - 1; + arrayEncoderTagger[oldValue] = _x; + + if (oldValue % 10000 == 0) + { + //Show current progress on console + Console.Write("{0}...", oldValue); + } + } + }); + + trainCorpusList.Clear(); + trainCorpusList = null; + + Console.WriteLine(); + return arrayEncoderTagger; + } + + //Open and check training and template file + public bool Open(string strTemplateFileName, string strTrainCorpusFileName) + { + return OpenTemplateFile(strTemplateFileName) && OpenTrainCorpusFile(strTrainCorpusFileName); + } + + //Build feature set into indexed data + public bool BuildFeatureSetIntoIndex(string filename, double max_slot_usage_rate_threshold, int debugLevel) + { + IList keyList; + IList valList; + featureLexicalDict.GenerateLexicalIdList(out keyList, out valList); + + if (debugLevel > 0) + { + var filename_featureset_raw_format = filename + ".feature.raw_text"; + var sw = new StreamWriter(filename_featureset_raw_format); + // save feature and its id into lists in raw format + for (var i = 0; i < keyList.Count; i++) + { + sw.WriteLine("{0}\t{1}", keyList[i], valList[i]); + } + sw.Close(); + } + + //Build feature index + var filename_featureset = filename + ".feature"; + var da = new CRFLite.Utils.DoubleArrayTrieBuilder(thread_num_); + if (da.build(keyList, valList, max_slot_usage_rate_threshold) == false) + { + return false; + } + //Save indexed feature set into file + da.save(filename_featureset); + + if (string.IsNullOrWhiteSpace(modelFileName)) + { + //Clean up all data + featureLexicalDict.Clear(); + featureLexicalDict = null; + keyList = null; + valList = null; + + GC.Collect(); + + //Create weight matrix + alpha_ = new double[feature_size() + 1]; + } + else + { + //Create weight matrix + alpha_ = new double[feature_size() + 1]; + var modelReader = new ModelReader(this.modelFileName); + modelReader.LoadModel(); + + if (modelReader.y_.Count == y_.Count) + { + for (var i = 0; i < keyList.Count; i++) + { + var index = modelReader.get_id(keyList[i]); + if (index < 0) + { + continue; + } + var size = (keyList[i][0] == 'U' ? y_.Count : y_.Count * y_.Count); + for (var j = 0; j < size; j++) + { + alpha_[valList[i] + j + 1] = modelReader.GetAlpha(index + j); + } + } + } + else + { + } + + //Clean up all data + featureLexicalDict.Clear(); + featureLexicalDict = null; + keyList = null; + valList = null; + + GC.Collect(); + } + + return true; + } + + //Save model meta data into file + public bool SaveModelMetaData(string filename) + { + var tofs = new StreamWriter(filename); + + // header + tofs.WriteLine("version: " + BaseUtils.MODEL_TYPE_NORM); + tofs.WriteLine("cost-factor: " + cost_factor_); + tofs.WriteLine("maxid: " + maxid_); + tofs.WriteLine("xsize: " + xsize_); + + tofs.WriteLine(); + + // y + for (var i = 0; i < y_.Count; ++i) + { + tofs.WriteLine(y_[i]); + } + tofs.WriteLine(); + + // template + for (var i = 0; i < unigram_templs_.Count; ++i) + { + tofs.WriteLine(unigram_templs_[i]); + } + for (var i = 0; i < bigram_templs_.Count; ++i) + { + tofs.WriteLine(bigram_templs_[i]); + } + + tofs.Close(); + + return true; + } + + /// + /// Save feature weights into file + /// + /// + /// + /// + public void SaveFeatureWeight(string filename, bool bVQ) + { + var filename_alpha = filename + ".alpha"; + var tofs = new StreamWriter(filename_alpha, false); + var bw = new BinaryWriter(tofs.BaseStream); + + if (bVQ == true) + { + //Build code book + CRFLite.Utils.VectorQuantization vq = new CRFLite.Utils.VectorQuantization(); + for (long i = 1; i <= maxid_; i++) + { + vq.Add(alpha_[i]); + } + + int vqSize = 256; + double distortion = vq.BuildCodebook(vqSize); + + //VQ size + bw.Write(vqSize); + + //Save VQ codebook into file + for (int j = 0; j < vqSize; j++) + { + bw.Write(vq.CodeBook[j]); + } + + //Save weights + for (long i = 1; i <= maxid_; ++i) + { + bw.Write((byte)vq.ComputeVQ(alpha_[i])); + } + } + else + { + bw.Write(0); + //Save weights + for (long i = 1; i <= maxid_; ++i) + { + bw.Write((float)alpha_[i]); + } + } + + bw.Close(); + } + + bool OpenTemplateFile(string filename) + { + var ifs = new StreamReader(filename); + unigram_templs_ = new List(); + bigram_templs_ = new List(); + while (ifs.EndOfStream == false) + { + var line = ifs.ReadLine(); + if (line.Length == 0 || line[0] == '#') + { + continue; + } + if (line[0] == 'U') + { + unigram_templs_.Add(line); + } + else if (line[0] == 'B') + { + bigram_templs_.Add(line); + } + else + { + } + } + ifs.Close(); + return true; + } + + bool OpenTrainCorpusFile(string strTrainingCorpusFileName) + { + var ifs = new StreamReader(strTrainingCorpusFileName); + y_ = new List(); + trainCorpusList = new List>>(); + var hashCand = new HashSet(); + var recordList = new List>(); + + var last_xsize = -1; + while (ifs.EndOfStream == false) + { + var line = ifs.ReadLine(); + if (line.Length == 0 || line[0] == ' ' || line[0] == '\t') + { + //Current record is finished, save it into the list + if (recordList.Count > 0) + { + trainCorpusList.Add(recordList); + recordList = new List>(); + } + continue; + } + + var items = line.Split('\t'); + var size = items.Length; + if (last_xsize >= 0 && last_xsize != size) + { + return false; + } + last_xsize = size; + xsize_ = (uint)(size - 1); + recordList.Add(new List(items)); + + if (hashCand.Contains(items[items.Length - 1]) == false) + { + hashCand.Add(items[items.Length - 1]); + y_.Add(items[items.Length - 1]); + } + } + ifs.Close(); + + return true; + } + + //Get feature id from feature set by feature string + //If feature string is not existed in the set, generate a new id and return it + public bool BuildFeatures(EncoderTagger tagger) + { + var feature = new List(); + using (var v = _buildersPool.GetOrCreate()) + { + var localBuilder = v.Item; + //tagger.feature_id_ = tagger.feature_cache_.Count; + for (var cur = 0; cur < tagger.word_num; ++cur) + { + for (int index = 0; index < unigram_templs_.Count; index++) + { + var it = unigram_templs_[index]; + var strFeature = apply_rule(it, cur, localBuilder, tagger); + if (strFeature == null) + { + } + else + { + var id = featureLexicalDict.GetOrAddId(strFeature.ToString()); + feature.Add(id); + } + } + tagger.feature_cache_.Add(feature.ToArray()); + feature.Clear(); + } + + for (var cur = 1; cur < tagger.word_num; ++cur) + { + for (int index = 0; index < bigram_templs_.Count; index++) + { + var it = bigram_templs_[index]; + var strFeature = apply_rule(it, cur, localBuilder, tagger); + if (strFeature == null) + { + } + else + { + var id = featureLexicalDict.GetOrAddId(strFeature.ToString()); + feature.Add(id); + } + } + + tagger.feature_cache_.Add(feature.ToArray()); + feature.Clear(); + + } + + } + + return true; + } + + } +} diff --git a/BotSharp.MachineLearning/CRFLite/IGenerateFeature.cs b/BotSharp.MachineLearning/CRFLite/IGenerateFeature.cs new file mode 100644 index 00000000..87748d0d --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/IGenerateFeature.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace BotSharp.MachineLearning.CRFLite +{ + public interface IGenerateFeature + { + bool Initialize(); + List> GenerateFeature(string strText); + } +} diff --git a/BotSharp.MachineLearning/CRFLite/Node.cs b/BotSharp.MachineLearning/CRFLite/Node.cs new file mode 100644 index 00000000..a17c47f2 --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Node.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace BotSharp.MachineLearning.CRFLite +{ + public class Node + { + public int fid; + public short x; + public short y; + public double alpha; + public double beta; + public double cost; + public double bestCost; + public Node prev; + + public List lpathList; + public List rpathList; + } +} diff --git a/BotSharp.MachineLearning/CRFLite/Path.cs b/BotSharp.MachineLearning/CRFLite/Path.cs new file mode 100644 index 00000000..7e003f63 --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Path.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace BotSharp.MachineLearning.CRFLite +{ + public class Path + { + public int fid; + public Node rnode; + public Node lnode; + public double cost; + + public Path() + { + rnode = null; + lnode = null; + cost = 0; + } + + public void add(Node _lnode, Node _rnode) + { + lnode = _lnode; + rnode = _rnode; + + lnode.rpathList.Add(this); + rnode.lpathList.Add(this); + } + } +} diff --git a/BotSharp.MachineLearning/CRFLite/Pool.cs b/BotSharp.MachineLearning/CRFLite/Pool.cs new file mode 100644 index 00000000..1f66ccef --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Pool.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Concurrent; +using System.Threading; + +namespace BotSharp.MachineLearning.CRFLite +{ + /// + /// Represents general purpose pool that has no restrictions (e.g. grows if it's required) + /// + /// + internal sealed class Pool + { + private int _totalCount; + private readonly ConcurrentStack _container = new ConcurrentStack(); + private readonly Func, T> _creator; + private readonly Action _cleaner; + + /// + /// Initializes a new instance of the class. + /// + public Pool(Func, T> creator, Action cleaner = null) + { + _creator = creator; + _cleaner = cleaner; + } + + /// + /// Gets item from pool or creates a new item + /// + /// + public PoolItem GetOrCreate() + { + T item; + if (_container.TryPop(out item)) + { + return new PoolItem(item, _cleaner, this); + } + var newItem = _creator(this); + if (newItem == null) + { + throw new ApplicationException("Unable to create new pool item"); + } + Interlocked.Increment(ref _totalCount); + return new PoolItem(newItem, _cleaner, this); + } + + /// + /// Returns amount of free items in the bag + /// + public int FreeCount { get { return _container.Count; } } + + /// + /// Returns amount items created by pool + /// + public int TotalCount { get { return _totalCount; } } + + private void Return(T item) + { + _container.Push(item); + } + + /// + /// Pool item that is return when pool request is processed + /// + /// + internal struct PoolItem : IDisposable + { + /// + /// Pooled item + /// + public readonly T1 Item; + private readonly Pool _owner; + private readonly Action _cleaner; + + /// + /// Creates a new pool item + /// + /// + /// + /// + internal PoolItem(T1 item, Action cleaner, Pool owner) + { + Item = item; + _cleaner = cleaner; + _owner = owner; + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + _cleaner?.Invoke(Item); + _owner.Return(Item); + } + } + } +} \ No newline at end of file diff --git a/BotSharp.MachineLearning/CRFLite/SegDecoderTagger.cs b/BotSharp.MachineLearning/CRFLite/SegDecoderTagger.cs new file mode 100644 index 00000000..d623dc97 --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/SegDecoderTagger.cs @@ -0,0 +1,131 @@ +using BotSharp.MachineLearning.CRFLite.Decoder; +using System; +using System.Collections.Generic; +using System.Text; + +namespace BotSharp.MachineLearning.CRFLite +{ + public class SegDecoderTagger : DecoderTagger + { + public SegDecoderTagger(int nbest, int this_crf_max_word_num = BaseUtils.DEFAULT_CRF_MAX_WORD_NUM) + : base(nbest, this_crf_max_word_num) + { + crf_max_word_num = this_crf_max_word_num; + } + + int seg_termbuf_build(crf_seg_out term_buf) + { + term_buf.Clear(); + + //build raw result at first + var iRet = termbuf_build(term_buf); + if (iRet != BaseUtils.ERROR_SUCCESS) + { + return iRet; + } + + //Then build token result + var term_len = 0; + var weight = 0.0; + var num = 0; + for (var i = 0; i < x_.Count; i++) + { + //Adding the length of current token + var strTag = term_buf.result_[i]; + term_len += x_[i][0].Length; + weight += term_buf.weight_[i]; + num++; + + //Check if current term is the end of a token + if ((strTag.StartsWith("B_") == false && + strTag.StartsWith("M_") == false) || + i == x_.Count - 1) + { + var tkn = new SegToken(); + tkn.length = term_len; + tkn.offset = term_buf.termTotalLength; + + var spos = strTag.IndexOf('_'); + if (spos < 0) + { + if (strTag == "NOR") + { + tkn.strTag = ""; + } + else + { + tkn.strTag = strTag; + } + } + else + { + tkn.strTag = strTag.Substring(spos + 1); + } + + term_buf.termTotalLength += term_len; + //Calculate each token's weight + switch (vlevel_) + { + case 0: + tkn.fWeight = 0.0; + break; + case 2: + tkn.fWeight = weight / num; + weight = 0.0; + num = 0; + break; + } + + term_buf.tokenList.Add(tkn); + term_len = 0; + } + } + + + return BaseUtils.ERROR_SUCCESS; + } + + + public int output(crf_seg_out[] pout) + { + var n = 0; + var ret = 0; + + if (nbest_ == 1) + { + //If only best result and no need probability, "next" is not to be used + ret = seg_termbuf_build(pout[0]); + if (ret < 0) + { + return ret; + } + } + else + { + //Fill the n best result + var iNBest = nbest_; + if (pout.Length < iNBest) + { + iNBest = pout.Length; + } + + for (n = 0; n < iNBest; ++n) + { + ret = next(); + if (ret < 0) + { + break; + } + + ret = seg_termbuf_build(pout[n]); + if (ret < 0) + { + return ret; + } + } + } + + return BaseUtils.ERROR_SUCCESS; + } + } +} diff --git a/BotSharp.MachineLearning/CRFLite/Tagger.cs b/BotSharp.MachineLearning/CRFLite/Tagger.cs new file mode 100644 index 00000000..94f81824 --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Tagger.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; + +namespace BotSharp.MachineLearning.CRFLite +{ + public class Tagger + { + public List> x_; + public Node[,] node_; //Node matrix + public short ysize_; + public short word_num; //the number of tokens need to be labeled + public double Z_; //概率值 + public double cost_; //The path cost + public short[] result_; + public List feature_cache_; + + //Calculate the cost of each path. It's used for finding the best or N-best result + public int viterbi() + { + var bestc = double.MinValue; + Node bestNode = null; + + for (var i = 0; i < word_num; ++i) + { + for (var j = 0; j < ysize_; ++j) + { + bestc = double.MinValue; + bestNode = null; + + var node_i_j = node_[i, j]; + + for (int index = 0; index < node_i_j.lpathList.Count; ++index) + { + var p = node_i_j.lpathList[index]; + var cost = p.lnode.bestCost + p.cost + node_i_j.cost; + if (cost > bestc) + { + bestc = cost; + bestNode = p.lnode; + } + } + + node_i_j.prev = bestNode; + node_i_j.bestCost = bestNode != null ? bestc : node_i_j.cost; + } + } + + bestc = double.MinValue; + bestNode = null; + + var s = (short)(word_num - 1); + for (short j = 0; j < ysize_; ++j) + { + if (bestc < node_[s, j].bestCost) + { + bestNode = node_[s, j]; + bestc = node_[s, j].bestCost; + } + } + + var n = bestNode; + while (n != null) + { + result_[n.x] = n.y; + n = n.prev; + } + + cost_ = -node_[s, result_[s]].bestCost; + + return BaseUtils.ERROR_SUCCESS; + } + + private void calcAlpha(int m, int n) + { + var nd = node_[m, n]; + nd.alpha = 0.0; + + var i = 0; + for (int index = 0; index < nd.lpathList.Count; index++) + { + var p = nd.lpathList[index]; + nd.alpha = BaseUtils.logsumexp(nd.alpha, p.cost + p.lnode.alpha, (i == 0)); + i++; + } + nd.alpha += nd.cost; + } + + private void calcBeta(int m, int n) + { + var nd = node_[m, n]; + nd.beta = 0.0f; + if (m + 1 < word_num) + { + var i = 0; + for (int index = 0; index < nd.rpathList.Count; index++) + { + var p = nd.rpathList[index]; + nd.beta = BaseUtils.logsumexp(nd.beta, p.cost + p.rnode.beta, (i == 0)); + i++; + } + } + nd.beta += nd.cost; + } + + public void forwardbackward() + { + for (int i = 0, k = word_num - 1; i < word_num; ++i, --k) + { + for (var j = 0; j < ysize_; ++j) + { + calcAlpha(i, j); + calcBeta(k, j); + } + } + + Z_ = 0.0; + for (var j = 0; j < ysize_; ++j) + { + Z_ = BaseUtils.logsumexp(Z_, node_[0, j].beta, j == 0); + } + } + + + //Assign feature ids to node and path + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int RebuildFeatures() + { + var fid = 0; + for (short cur = 0; cur < word_num; ++cur) + { + for (short i = 0; i < ysize_; ++i) + { + node_[cur, i].fid = fid; + if (cur > 0) + { + Node previousNode = node_[cur - 1, i]; + for (int index = 0; index < previousNode.rpathList.Count; ++index) + { + Path path = previousNode.rpathList[index]; + path.fid = fid + word_num - 1; + } + } + } + + ++fid; + } + + return 0; + } + } +} diff --git a/BotSharp.MachineLearning/CRFLite/Utils.cs b/BotSharp.MachineLearning/CRFLite/Utils.cs new file mode 100644 index 00000000..90d85fb8 --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Utils.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace BotSharp.MachineLearning.CRFLite +{ + public class QueueElement + { + public Node node; + public QueueElement next; + public double fx; + public double gx; + }; + + public class Heap + { + public int capacity; + public int elem_size; //size of elem_list + public int size; // size of elem_ptr_list + public List elem_ptr_list; + public List elem_list; + }; + + public class BaseUtils + { + public const double eps = 1e-7; + + + public const int MINUS_LOG_EPSILON = 13; + public const int DEFAULT_CRF_MAX_WORD_NUM = 100; + + public const int MODEL_TYPE_NORM = 100; + + + public const int ERROR_INVALIDATED_FEATURE = -8; + public const int ERROR_HEAP_SIZE_TOO_BIG = -7; + public const int ERROR_INSERT_HEAP_FAILED = -6; + public const int ERROR_EMPTY_FEATURE = -5; + public const int ERROR_INVALIDATED_PARAMETER = -4; + public const int ERROR_WRONG_STATUS = -3; + public const int ERROR_TOO_LONG_WORD = -2; + public const int ERROR_UNKNOWN = -1; + public const int ERROR_SUCCESS = 0; + + public static Heap heap_init(int max_size) + { + Heap H; + + H = new Heap(); + H.capacity = max_size; + H.size = 0; + H.elem_size = 0; + + H.elem_ptr_list = new List(max_size + 1); + H.elem_list = new List(max_size + 1); + + for (var z = 0; z < max_size; z++) + { + H.elem_list.Add(new QueueElement()); + H.elem_ptr_list.Add(null); + } + H.elem_list[0].fx = double.MinValue; + H.elem_ptr_list.Add(H.elem_list[0]); + + return H; + } + + public static QueueElement allc_from_heap(Heap H) + { + if (H.elem_size >= H.capacity) + { + return null; + } + else + { + return H.elem_list[++H.elem_size]; + } + } + + public static int heap_insert(QueueElement qe, Heap H) + { + if (H.size >= H.capacity) + { + return BaseUtils.ERROR_HEAP_SIZE_TOO_BIG; + } + var i = ++H.size; + while (i != 1 && H.elem_ptr_list[i / 2].fx > qe.fx) + { + H.elem_ptr_list[i] = H.elem_ptr_list[i / 2]; //此时i还没有进行i/2操作 + i /= 2; + } + H.elem_ptr_list[i] = qe; + return 0; + } + + public static QueueElement heap_delete_min(Heap H) + { + var min_elem = H.elem_ptr_list[1]; //堆是从第1号元素开始的 + var last_elem = H.elem_ptr_list[H.size--]; + int i = 1, ci = 2; + while (ci <= H.size) + { + if (ci < H.size && H.elem_ptr_list[ci].fx > H.elem_ptr_list[ci + 1].fx) + { + ci++; + } + if (last_elem.fx <= H.elem_ptr_list[ci].fx) + { + break; + } + H.elem_ptr_list[i] = H.elem_ptr_list[ci]; + i = ci; + ci *= 2; + } + H.elem_ptr_list[i] = last_elem; + return min_elem; + } + + public static bool is_heap_empty(Heap H) + { + return H.size == 0; + } + + public static void heap_reset(Heap H) + { + if (H != null) + { + H.size = 0; + H.elem_size = 0; + } + } + + public static double logsumexp(double x, double y, bool flg) + { + if (flg) + { + return y; // init mode + } + double vmin; + double vmax; + if (x > y) + { + vmin = y; + vmax = x; + } + else + { + vmin = x; + vmax = y; + } + + if (vmax > vmin + MINUS_LOG_EPSILON) + { + return vmax; + } + return vmax + Math.Log(Math.Exp(vmin - vmax) + 1.0); + } + } +} diff --git a/BotSharp.MachineLearning/CRFLite/Utils/BTreeDictionary.cs b/BotSharp.MachineLearning/CRFLite/Utils/BTreeDictionary.cs new file mode 100644 index 00000000..8895ba04 --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Utils/BTreeDictionary.cs @@ -0,0 +1,1337 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace BotSharp.MachineLearning.CRFLite.Utils +{ + /// + /// Represents a generic interface of an ordered collection. + /// + /// The type of elements in the collection. + public interface ISortedCollection : ICollection + { + /// + /// Gets the comparer used to order items in the collection. + /// + IComparer Comparer + { + get; + } + + /// + /// Get all items equal to or greater than the specified value, starting with the lowest index and moving forwards. + /// + IEnumerable WhereGreaterOrEqual(T value); + + /// + /// Get all items less than or equal to the specified value, starting with the highest index and moving backwards. + /// + IEnumerable WhereLessOrEqualBackwards(T value); + + /// + /// Gets the index of the first item greater than the specified value. + /// /// + int FirstIndexWhereGreaterThan(T value); + + /// + /// Gets the index of the last item less than the specified key. + /// + int LastIndexWhereLessThan(T value); + + /// + /// Gets the item at the specified index. + /// + T At(int index); + + /// + /// Removes the item at the specified index. + /// + void RemoveAt(int index); + + /// + /// Get all items starting at the index, and moving forward. + /// + IEnumerable ForwardFromIndex(int index); + + /// + /// Get all items starting at the index, and moving backward. + /// + IEnumerable BackwardFromIndex(int index); + } + + /// + /// Represents a generic interface of ordered key/value pairs. + /// + /// The type of the key. + /// The type of the value. + public interface ISortedDictionary : IDictionary + { + /// + /// Get all items having a key equal to or greater than the specified key, starting with the lowest index and moving forwards. + /// + IEnumerable> WhereGreaterOrEqual(TKey key); + + /// + /// Get all items less than or equal to the specified value, starting with the highest index and moving backwards. + /// + IEnumerable> WhereLessOrEqualBackwards(TKey keyUpperBound); + + /// + /// Gets the sorted collection of keys. + /// + new ISortedCollection Keys + { + get; + } + + /// + /// Gets the item at the specified index. + /// + KeyValuePair At(int index); + + /// + /// Removes the item at the specified index. + /// + void RemoveAt(int index); + + /// + /// Sets the value at the specified index. + /// + /// + /// + void SetValueAt(int index, TValue value); + + /// + /// Get all items starting at the index, and moving forward. + /// + IEnumerable> ForwardFromIndex(int index); + + /// + /// Get all items starting at the index, and moving backward. + /// + IEnumerable> BackwardFromIndex(int index); + } + + /// + /// An O(log N) implementation of the ISortedDictionary interface. + /// + /// The type for the sorted keys. + /// The type for the associated values. + public class BTreeDictionary : ISortedDictionary + { + #region Fields + + Node root; + readonly Node first; + readonly KeyCollection keys; + readonly ValueCollection values; + readonly IComparer keyComparer; + + private void ObjectInvariant() + { + + } + + #endregion + + #region Construction + + /// + /// Initializes a new BTreeDictionary instance optimized for the specified node capacity. + /// + /// The capacity in keys for each node in the tree structure. + public BTreeDictionary(int nodeCapacity = 128) + : this(Comparer.Default, nodeCapacity) + { + + } + + /// + /// Initializes a new BTreeDictionary instance. + /// + /// The comparer for ordering keys in the structure. + /// The capacity in keys for each node in the tree structure. + public BTreeDictionary(IComparer keyComparer, int nodeCapacity) + { + + + this.keyComparer = keyComparer; + this.first = new Node(nodeCapacity); + this.root = this.first; + + this.keys = new KeyCollection(this); + this.values = new ValueCollection(this); + } + + #endregion + + #region Properties + + /// + /// Gets the value associated with the specified key. An arbitrary value will be chosen if key is a duplicate. + /// + /// The key for which to retrieve the associated value. + /// The value associated with the key. + public TValue this[TKey key] + { + get + { + TValue result; + if (this.TryGetValue(key, out result)) + return result; + throw new InvalidOperationException("error"); + } + set + { + + Node leaf; + int pos; + if (Node.Find(root, key, KeyComparer, 0, out leaf, out pos)) + leaf.SetValue(pos, value); + else + { + Node.Insert(key, ref leaf, ref pos, ref root); + leaf.SetValue(pos, value); + } + } + } + + /// + /// Gets the number of key value pairs in the dictionary. + /// + public int Count + { + get + { + + return this.root.TotalCount; + } + } + + /// + /// Gets the key comparer. + /// + public IComparer KeyComparer + { + get + { + + return this.keyComparer; + } + } + + /// + /// Gets the collection of keys in the dictionary. + /// + public ISortedCollection Keys + { + get + { + + return this.keys; + } + } + + public IList KeyList + { + get + { + return this.keys; + } + } + + /// + /// Gets the collection of values in the dictionary. + /// + public ICollection Values + { + get + { + + return this.values; + } + } + + public IList ValueList + { + get + { + return this.values; + } + } + + /// + /// Gets or sets indication whether this dictionary is readonly or mutable. + /// + public bool IsReadOnly + { + get; + set; + } + + + #endregion + + #region Methods + + /// + /// Gets indication of whether the dictionary contains an entry for the specified key. + /// + /// The key. + /// True if the dictionary contains the key; otherwise, false. + public bool ContainsKey(TKey key) + { + Node leaf; + int pos; + var found = Node.Find(root, key, KeyComparer, 0, out leaf, out pos); + return found; + } + + /// + /// Tries to get the value for the specified key. An arbitrary value will be chosen if key is a duplicate. + /// + /// The key for which to try to get the value. + /// The value found for the specified key, or a default value if not found. + /// True if the value was found; otherwise, false. + public bool TryGetValue(TKey key, out TValue value) + { + try + { + Node leaf; + int pos; + var found = Node.Find(root, key, KeyComparer, 0, out leaf, out pos); + value = found ? leaf.GetValue(pos) : default(TValue); + return found; + } + catch (System.Exception) + { + value = default(TValue); + return false; + } + + } + + /// + /// Adds the specified key and value to the dictionary. + /// + /// The key to add. + /// The value to associate with the key. + public void Add(TKey key, TValue value) + { + + Node leaf; + int pos; + var found = Node.Find(root, key, KeyComparer, 0, out leaf, out pos); + if (found) + { + //The key is already in the dictionary, throw exception out + throw new InvalidOperationException("The key is already in the dictionary"); + } + + Node.Insert(key, ref leaf, ref pos, ref root); + leaf.SetValue(pos, value); + } + + /// + /// Gets the key value pair at the specified index. + /// + /// The index at which to get the key value pair. + /// The key value pair at the specified index. + public KeyValuePair At(int index) + { + + var leaf = Node.LeafAt(root, ref index); + return new KeyValuePair(leaf.GetKey(index), leaf.GetValue(index)); + } + + /// + /// Clears the dictionary of all items. + /// + public void Clear() + { + + Node.Clear(first); + root = first; + } + + /// + /// Remove the key and associated value from the dictionary. + /// + /// The key to remove. + /// True if the key was removed; otherwise, false if key was not found. + public bool Remove(TKey key) + { + + Node leaf; + int pos; + if (!Node.Find(root, key, KeyComparer, 0, out leaf, out pos)) + return false; + + Node.Remove(leaf, pos, ref root); + return true; + } + + /// + /// Removes the key and associated value from the dictionary at the specified index. + /// + /// The index at which to remove the key value pair. + public void RemoveAt(int index) + { + + var leaf = Node.LeafAt(root, ref index); + Node.Remove(leaf, index, ref root); + } + + + /// + /// Get all items starting at the index, and moving forward. + /// + public IEnumerable> ForwardFromIndex(int index) + { + + var node = Node.LeafAt(root, ref index); + return Node.ForwardFromIndex(node, index); + } + + /// + /// Get all items starting at the index, and moving backward. + /// + public IEnumerable> BackwardFromIndex(int index) + { + + var node = Node.LeafAt(root, ref index); + return Node.BackwardFromIndex(node, index); + } + + /// + /// Sets the value at the specified index, leaving the key unchanged. + /// + /// The index at which to set the value. + /// The value to associate at the specified index. + public void SetValueAt(int index, TValue value) + { + + var leaf = Node.LeafAt(root, ref index); + leaf.SetValue(index, value); + } + + /// + /// Gets an enumerator of key value pairs for the entire collection in sorted ascending order. + /// + /// + public IEnumerator> GetEnumerator() + { + + return Node.ForwardFromIndex(first, 0).GetEnumerator(); + } + + /// + /// Gets an enumerator of key value pairs in ascending order by key, for all items with a key + /// equal or greater than the specified key lower bound. + /// + /// The value at which to start returning keys. + /// All key value pairs having a key equal or greater than the lower bound, in key ascending order. + public IEnumerable> WhereGreaterOrEqual(TKey keyLowerBound) + { + + Node leaf; + int leafPos; + Node.Find(root, keyLowerBound, KeyComparer, 0, out leaf, out leafPos); + return Node.ForwardFromIndex(leaf, leafPos); + } + + /// + /// Gets an enumerator of key value pairs in descending order by key, for all items with a key + /// less than or equal than the specified key upper bound. + /// + /// The value at which to start returning keys. + /// All key value pairs having a key equal or less than the upper bound, in key descending order. + public IEnumerable> WhereLessOrEqualBackwards(TKey keyUpperBound) + { + + Node leaf; + int leafPos; + var found = Node.Find(root, keyUpperBound, KeyComparer, 0, out leaf, out leafPos); + if (!found) + --leafPos; + return Node.BackwardFromIndex(leaf, leafPos); + } + + /// + /// Copy the entire dictionary to the specified array, starting at the specified array index. + /// + /// The array into which to copy. + /// The index at which to start copying. + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + foreach (var item in this) + array[arrayIndex++] = item; + } + + #endregion + + #region Implementation - Nested Types + + sealed class Node + { + #region Fields + + readonly TKey[] keys; + readonly TValue[] values; + readonly Node[] nodes; + + int nodeCount; + int totalCount; + + Node parent; + Node next; + Node prev; + private void ObjectInvariant() + { + + } + + #endregion + + #region Construction + + /// + /// Initialize the first node in the BTree structure. + /// + public Node(int nodeCapacity) + : this(nodeCapacity, true) + { + } + + #endregion + + #region Properties + + public int TotalCount + { + get + { + return this.totalCount; + } + } + + public bool IsRoot + { + get + { + return this.parent == null; + } + } + + public bool IsLeaf + { + get + { + return nodes == null; + } + } + + public int NodeCount + { + get + { + return this.nodeCount; + } + } + + #endregion + + #region Methods + + /// + /// Gets the key at the specified position. + /// + public TKey GetKey(int pos) + { + return this.keys[pos]; + } + + /// + /// Gets the value at the specified position. + /// + public TValue GetValue(int pos) + { + return this.values[pos]; + } + + /// + /// Sets the value at the specified position. + /// + public void SetValue(int pos, TValue value) + { + this.values[pos] = value; + } + + /// + /// Get the leaf node at the specified index in the tree defined by the specified root. + /// + public static Node LeafAt(Node root, ref int pos) + { + + int nodeIndex = 0; + while (true) + { + if (root.nodes == null) + { + return root; + } + + var node = root.nodes[nodeIndex]; + if (pos < node.totalCount) + { + root = node; + nodeIndex = 0; + } + else + { + pos -= node.totalCount; + ++nodeIndex; + } + } + } + + /// + /// Find the node and index in the tree defined by the specified root. + /// + public static bool Find(Node root, TKey key, IComparer keyComparer, int duplicatesBias, out Node leaf, out int pos) + { + + pos = Array.BinarySearch(root.keys, 0, root.nodeCount, key, keyComparer); + while (root.nodes != null) + { + if (pos >= 0) + { + if (duplicatesBias != 0) + MoveToDuplicatesBoundary(key, keyComparer, duplicatesBias, ref root, ref pos); + + // Found an exact match. Move down one level. + root = root.nodes[pos]; + } + else + { + // No exact match. Find greatest lower bound. + pos = ~pos; + if (pos > 0) + --pos; + root = root.nodes[pos]; + } + + pos = Array.BinarySearch(root.keys, 0, root.nodeCount, key, keyComparer); + } + + leaf = root; + if (pos < 0) + { + pos = ~pos; + return false; + } + + if (duplicatesBias != 0) + MoveToDuplicatesBoundary(key, keyComparer, duplicatesBias, ref leaf, ref pos); + + return true; + } + + /// + /// Insert a new key into the leaf node at the specified position. + /// + public static void Insert(TKey key, ref Node leaf, ref int pos, ref Node root) + { + // Make sure there is space for the new key. + if (EnsureSpace(leaf, ref root) && pos > leaf.nodeCount) + { + pos -= leaf.nodeCount; + leaf = leaf.next; + } + + // Insert the key. + int moveCount = leaf.nodeCount - pos; + Array.Copy(leaf.keys, pos, leaf.keys, pos + 1, moveCount); + leaf.keys[pos] = key; + ++leaf.nodeCount; + EnsureParentKey(leaf, pos); + + // Insert space for the value. Caller is responsible for filling in value. + Array.Copy(leaf.values, pos, leaf.values, pos + 1, moveCount); + + // Update total counts. + for (var node = leaf; node != null; node = node.parent) + ++node.totalCount; + } + + /// + /// Remove the item from the node at the specified position. + /// + public static bool Remove(Node leaf, int pos, ref Node root) + { + + // Update total counts. + for (var node = leaf; node != null; node = node.parent) + --node.totalCount; + + // Remove the key and value from the node. + --leaf.nodeCount; + Array.Copy(leaf.keys, pos + 1, leaf.keys, pos, leaf.nodeCount - pos); + Array.Copy(leaf.values, pos + 1, leaf.values, pos, leaf.nodeCount - pos); + leaf.keys[leaf.nodeCount] = default(TKey); + leaf.values[leaf.nodeCount] = default(TValue); + + // Make sure parent keys index correctly into this node. + if (leaf.nodeCount > 0) + EnsureParentKey(leaf, pos); + + // Merge this node with others if it is below the node capacity threshold. + Merge(leaf, ref root); + return true; + } + + /// + /// Get an ascending enumerable for the collection, starting an the index in the specified leaf node. + /// + public static IEnumerable> ForwardFromIndex(Node leaf, int pos) + { + + while (leaf != null) + { + while (pos < leaf.nodeCount) + { + yield return new KeyValuePair(leaf.GetKey(pos), leaf.GetValue(pos)); + ++pos; + } + pos -= leaf.nodeCount; + leaf = leaf.next; + } + } + + /// + /// Get a descending enumerable, starting at the index in the specified leaf node. + /// + public static IEnumerable> BackwardFromIndex(Node leaf, int pos) + { + + if (pos == -1) + { + // Handle special case to start moving in the previous node. + leaf = leaf.prev; + if (leaf != null) + pos = leaf.nodeCount - 1; + else + pos = 0; + } + else if (pos == leaf.NodeCount) + { + // Handle special case to start moving in the next node. + if (leaf.next == null) + --pos; + else + { + leaf = leaf.next; + pos = 0; + } + } + + // Loop thru collection, yielding each value in sequence. + while (leaf != null) + { + while (pos >= 0) + { + yield return new KeyValuePair(leaf.GetKey(pos), leaf.GetValue(pos)); + --pos; + } + leaf = leaf.prev; + if (leaf != null) + pos += leaf.nodeCount; + } + } + + /// + /// Clear all keys and values from the specified node. + /// + public static void Clear(Node firstNode) + { + + int clearCount = firstNode.nodeCount; + + Array.Clear(firstNode.keys, 0, clearCount); + Array.Clear(firstNode.values, 0, clearCount); + firstNode.nodeCount = 0; + firstNode.totalCount = 0; + + firstNode.parent = null; + firstNode.next = null; + } + + /// + /// Get the index relative to the root node, for the position in the specified leaf. + /// + public static int GetRootIndex(Node leaf, int pos) + { + var node = leaf; + var rootIndex = pos; + while (node.parent != null) + { + int nodePos = Array.IndexOf(node.parent.nodes, node, 0, node.parent.nodeCount); + for (int i = 0; i < nodePos; ++i) + rootIndex += node.parent.nodes[i].totalCount; + node = node.parent; + } + return rootIndex; + } + + #endregion + + #region Implementation + + Node(int nodeCapacity, bool leaf) + { + this.keys = new TKey[nodeCapacity]; + + if (leaf) + { + this.values = new TValue[nodeCapacity]; + this.nodes = null; + } + else + { + this.values = null; + this.nodes = new Node[nodeCapacity]; + } + + this.nodeCount = 0; + this.totalCount = 0; + this.parent = null; + this.next = null; + this.prev = null; + } + + /// + /// (Assumes: key is a duplicate in node at pos) Move to the side on the range of duplicates, + /// as indicated by the sign of duplicatesBias. + /// + /// + /// + /// + /// + /// + static void MoveToDuplicatesBoundary(TKey key, IComparer keyComparer, int duplicatesBias, ref Node node, ref int pos) + { + // Technically, we could adjust the binary search to perform most of this step, but duplicates + // are usually unexpected.. algorithm is still O(log N), because scan include at most a scan thru two nodes + // worth of keys, for each level. + // Also, the binary search option would still need the ugliness of the special case for moving into the + // previous node; it would only be a little faster, on average, assuming large numbers of duplicates were common. + + if (duplicatesBias < 0) + { + // Move backward over duplicates. + while (pos > 0 && 0 == keyComparer.Compare(node.keys[pos - 1], key)) + --pos; + + // Special case: duplicates can span backwards into the previous node because the parent + // key pivot might be in the center for the duplicates. + if (pos == 0 && node.prev != null) + { + var prev = node.prev; + var prevPos = prev.NodeCount; + while (prevPos > 0 && 0 == keyComparer.Compare(prev.keys[prevPos - 1], key)) + { + --prevPos; + } + if (prevPos < prev.NodeCount) + { + node = prev; + pos = prevPos; + } + } + } + else + { + // Move forward over duplicates. + while (pos < node.NodeCount - 1 && 0 == keyComparer.Compare(node.keys[pos + 1], key)) + ++pos; + } + } + + static bool EnsureSpace(Node node, ref Node root) + { + if (node.nodeCount < node.keys.Length) + return false; + + EnsureParent(node, ref root); + EnsureSpace(node.parent, ref root); + + var sibling = new Node(node.keys.Length, node.nodes == null); + sibling.next = node.next; + sibling.prev = node; + sibling.parent = node.parent; + + if (node.next != null) + node.next.prev = sibling; + node.next = sibling; + + int pos = Array.IndexOf(node.parent.nodes, node, 0, node.parent.nodeCount); + int siblingPos = pos + 1; + + Array.Copy(node.parent.keys, siblingPos, node.parent.keys, siblingPos + 1, node.parent.nodeCount - siblingPos); + Array.Copy(node.parent.nodes, siblingPos, node.parent.nodes, siblingPos + 1, node.parent.nodeCount - siblingPos); + ++node.parent.nodeCount; + node.parent.nodes[siblingPos] = sibling; + + int half = node.nodeCount / 2; + int halfCount = node.nodeCount - half; + Move(node, half, sibling, 0, halfCount); + return true; + } + + static void Move(Node source, int sourceIndex, Node target, int targetIndex, int moveCount) + { + Move(source.keys, sourceIndex, source.nodeCount, target.keys, targetIndex, target.nodeCount, moveCount); + if (source.values != null) + Move(source.values, sourceIndex, source.nodeCount, target.values, targetIndex, target.nodeCount, moveCount); + + int totalMoveCount; + if (source.nodes == null) + { + totalMoveCount = moveCount; + } + else + { + Move(source.nodes, sourceIndex, source.nodeCount, target.nodes, targetIndex, target.nodeCount, moveCount); + totalMoveCount = 0; + for (int i = 0; i < moveCount; ++i) + { + var child = target.nodes[targetIndex + i]; + child.parent = target; + totalMoveCount += child.totalCount; + } + } + + source.nodeCount -= moveCount; + target.nodeCount += moveCount; + + var sn = source; + var tn = target; + while (sn != null && sn != tn) + { + sn.totalCount -= totalMoveCount; + tn.totalCount += totalMoveCount; + sn = sn.parent; + tn = tn.parent; + } + + EnsureParentKey(source, sourceIndex); + EnsureParentKey(target, targetIndex); + } + + static void Move(TItem[] source, int sourceIndex, int sourceTotal, TItem[] target, int targetIndex, int targetTotal, int count) + { + Array.Copy(target, targetIndex, target, targetIndex + count, targetTotal - targetIndex); + Array.Copy(source, sourceIndex, target, targetIndex, count); + Array.Copy(source, sourceIndex + count, source, sourceIndex, sourceTotal - sourceIndex - count); + Array.Clear(source, sourceTotal - count, count); + } + + static void EnsureParent(Node node, ref Node root) + { + if (node.parent != null) + return; + + var parent = new Node(node.keys.Length, false); + parent.totalCount = node.totalCount; + parent.nodeCount = 1; + parent.keys[0] = node.keys[0]; + parent.nodes[0] = node; + + node.parent = parent; + root = parent; + } + + static void EnsureParentKey(Node node, int pos) + { + while (pos == 0 && node.parent != null) + { + pos = Array.IndexOf(node.parent.nodes, node, 0, node.parent.nodeCount); + node.parent.keys[pos] = node.keys[0]; + node = node.parent; + } + } + + static void Merge(Node node, ref Node root) + { + if (node.nodeCount == 0) + { + // Handle special case: Empty node. + if (node.parent == null) + return; + + // Remove the node from the parent nodes. + int pos = Array.IndexOf(node.parent.nodes, node, 0, node.parent.nodeCount); + --node.parent.nodeCount; + Array.Copy(node.parent.keys, pos + 1, node.parent.keys, pos, node.parent.nodeCount - pos); + Array.Copy(node.parent.nodes, pos + 1, node.parent.nodes, pos, node.parent.nodeCount - pos); + node.parent.keys[node.parent.nodeCount] = default(TKey); + node.parent.nodes[node.parent.nodeCount] = null; + + // Make sure parent (of the parent) keys link down correctly. + if (node.parent.nodeCount > 0) + EnsureParentKey(node.parent, pos); + + // Delete the node from the next/prev linked list. + node.prev.next = node.next; + if (node.next != null) + node.next.prev = node.prev; + + // Merge the parent node. + Merge(node.parent, ref root); + return; + } + + if (node.next == null) + { + if (node.parent == null && node.nodeCount == 1 && node.nodes != null) + { + root = node.nodes[0]; + root.parent = null; + } + + return; + } + + if (node.nodeCount >= node.keys.Length / 2) + return; + + int count = node.next.nodeCount; + if (node.nodeCount + count > node.keys.Length) + count -= (node.nodeCount + count) / 2; + + Move(node.next, 0, node, node.nodeCount, count); + Merge(node.next, ref root); + } + + #endregion + } + + abstract class KeyValueCollectionBase : ICollection + { + #region Fields + + protected readonly BTreeDictionary tree; + + #endregion + + #region Construction + + public KeyValueCollectionBase(BTreeDictionary tree) + { + this.tree = tree; + } + + #endregion + + #region Properties + + public int Count + { + get + { + return tree.Count; + } + } + + #endregion + + #region Methods + + public abstract bool Contains(T item); + + public void CopyTo(T[] array, int arrayIndex) + { + foreach (var item in this) + array[arrayIndex++] = item; + } + + public abstract IEnumerator GetEnumerator(); + + #endregion + + #region ICollection<> members + + void ICollection.Add(T item) + { + throw new NotSupportedException(); + } + + void ICollection.Clear() + { + throw new NotSupportedException(); + } + + bool ICollection.IsReadOnly + { + get + { + return true; + } + } + + public bool Remove(T item) + { + throw new NotSupportedException(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + #endregion + } + + sealed class ValueCollection : KeyValueCollectionBase, IList + { + #region Construction + + public ValueCollection(BTreeDictionary tree) + : base(tree) + { + } + + #endregion + + #region Methods + + public override bool Contains(TValue item) + { + return this.tree.Any(keyValue => object.Equals(item, keyValue.Value)); + } + + public override IEnumerator GetEnumerator() + { + return this.tree.Select(keyValue => keyValue.Value).GetEnumerator(); + } + + #endregion + + int IList.IndexOf(TValue item) + { + throw new NotImplementedException(); + } + + void IList.Insert(int index, TValue item) + { + throw new NotImplementedException(); + } + + void IList.RemoveAt(int index) + { + throw new NotImplementedException(); + } + + public TValue this[int index] + { + get + { + return this.tree.At(index).Value; + } + set + { + throw new NotImplementedException(); + } + } + } + + sealed class KeyCollection : KeyValueCollectionBase, ISortedCollection, IList + { + #region Construction + + public KeyCollection(BTreeDictionary tree) + : base(tree) + { + } + + #endregion + + #region Properties + + public IComparer Comparer + { + get + { + return tree.KeyComparer; + } + } + + #endregion + + #region Methods + + public int FirstIndexWhereGreaterThan(TKey value) + { + Node leaf; + int pos; + var found = Node.Find(tree.root, value, tree.KeyComparer, 0, out leaf, out pos); + int result = Node.GetRootIndex(leaf, pos); + if (found) + ++result; + return result; + } + + public int LastIndexWhereLessThan(TKey value) + { + Node leaf; + int pos; + var found = Node.Find(tree.root, value, tree.KeyComparer, 0, out leaf, out pos); + int result = Node.GetRootIndex(leaf, pos); + if (found) + --result; + return result; + } + + public TKey At(int index) + { + return this.tree.At(index).Key; + } + + public override bool Contains(TKey item) + { + return tree.ContainsKey(item); + } + + public override IEnumerator GetEnumerator() + { + return tree.Select(keyValue => keyValue.Key).GetEnumerator(); + } + + public IEnumerable WhereGreaterOrEqual(TKey lowerBound) + { + return tree.WhereGreaterOrEqual(lowerBound).Select(keyValue => keyValue.Key); + } + + public IEnumerable WhereLessOrEqualBackwards(TKey upperBound) + { + return tree.WhereLessOrEqualBackwards(upperBound).Select(keyValue => keyValue.Key); + } + + public IEnumerable ForwardFromIndex(int index) + { + return this.tree.ForwardFromIndex(index).Select(item => item.Key); + } + + public IEnumerable BackwardFromIndex(int index) + { + return this.tree.BackwardFromIndex(index).Select(item => item.Key); + } + + #endregion + + #region IEnumerable members + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + #endregion + + #region ISortedCollection<> members + + void ISortedCollection.RemoveAt(int index) + { + throw new NotSupportedException(); + } + + #endregion + + int IList.IndexOf(TKey item) + { + throw new NotImplementedException(); + } + + void IList.Insert(int index, TKey item) + { + throw new NotImplementedException(); + } + + void IList.RemoveAt(int index) + { + throw new NotImplementedException(); + } + + public TKey this[int index] + { + get + { + return this.tree.At(index).Key; + } + set + { + throw new NotImplementedException(); + } + } + + } + + #endregion + + #region IDictionary<> members + + ICollection IDictionary.Keys + { + get + { + return this.Keys; + } + } + + #endregion + + #region ICollection<> members + + void ICollection>.Add(KeyValuePair item) + { + this.Add(item.Key, item.Value); + } + + bool ICollection>.Contains(KeyValuePair item) + { + TValue value; + return this.TryGetValue(item.Key, out value) && object.Equals(item.Value, value); + } + + bool ICollection>.IsReadOnly + { + get + { + return false; + } + } + + bool ICollection>.Remove(KeyValuePair item) + { + TValue value; + if (this.TryGetValue(item.Key, out value) && object.Equals(item.Value, value)) + { + this.Remove(item.Key); + return true; + } + return false; + } + + #endregion + + #region IEnumerable members + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + #endregion + } +} diff --git a/BotSharp.MachineLearning/CRFLite/Utils/BigArray.cs b/BotSharp.MachineLearning/CRFLite/Utils/BigArray.cs new file mode 100644 index 00000000..20c52f0e --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Utils/BigArray.cs @@ -0,0 +1,433 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; + +#if NO_SUPPORT_PARALLEL_LIB +#else +using System.Threading.Tasks; +#endif + +namespace BotSharp.MachineLearning.CRFLite.Utils +{ + abstract public class BigArray : IList where T : IComparable + { + public const long sizePerBlock = 1024 * 1024 * 64; //(<<26bits) + public const int moveBit = 26; + public long size_; + public List arrList; + +#if NO_SUPPORT_PARALLEL_LIB +#else + private ParallelOptions parallelOption; + private LimitedConcurrencyLevelTaskScheduler lcts; +#endif + public BigArray() + { +#if NO_SUPPORT_PARALLEL_LIB +#else + parallelOption = new ParallelOptions(); +#endif + } + + public int IndexOf(T item) + { + throw new NotImplementedException(); + } + + public void Insert(int index, T item) + { + throw new NotImplementedException(); + } + + public void RemoveAt(int index) + { + throw new NotImplementedException(); + } + + public T this[int i] + { + get + { + return this[(long)i]; + } + set + { + this[(long)i] = value; + } + } + + public abstract T this[long i] + { + get; + set; + } + + public void Add(T item) + { + throw new NotImplementedException(); + } + + public void Clear() + { + foreach (T[] item in arrList) + { + Array.Clear(item, 0, item.Length); + } + } + + public bool Contains(T item) + { + throw new NotImplementedException(); + } + + public void CopyTo(T[] array, int arrayIndex) + { + throw new NotImplementedException(); + } + + public int Count + { + get + { + return (int)LongLength; + } + } + + public long LongLength + { + get + { + return size_; + } + } + + public bool IsReadOnly + { + get { throw new NotImplementedException(); } + } + + public bool Remove(T item) + { + throw new NotImplementedException(); + } + + public IEnumerator GetEnumerator() + { + throw new NotImplementedException(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + + + void swap(long pos1, long pos2) + { + int nBlock1 = (int)(pos1 >> moveBit); + int offset1 = (int)(pos1 & (sizePerBlock - 1)); + + int nBlock2 = (int)(pos2 >> moveBit); + int offset2 = (int)(pos2 & (sizePerBlock - 1)); + + T tmp = arrList[nBlock1][offset1]; + arrList[nBlock1][offset1] = arrList[nBlock2][offset2]; + arrList[nBlock2][offset2] = tmp; + } + + + private long med3(long a, long b, long c) + { + return this[a].CompareTo(this[b]) < 0 ? (this[b].CompareTo(this[c]) < 0 ? b : this[a].CompareTo(this[c]) < 0 ? c : a) : this[b].CompareTo(this[c]) > 0 ? b : this[a].CompareTo(this[c]) > 0 ? c : a; + } + + private void vecswap(long a, long b, long n) + { +#if NO_SUPPORT_PARALLEL_LIB + for (long i = 0;i < n;i++) +#else + Parallel.For(0, n, parallelOption, i => +#endif + { + int nBlock1 = (int)((a + i) >> moveBit); + int offset1 = (int)((a + i) & (sizePerBlock - 1)); + + int nBlock2 = (int)((b + i) >> moveBit); + int offset2 = (int)((b + i) & (sizePerBlock - 1)); + + T tmp = arrList[nBlock1][offset1]; + arrList[nBlock1][offset1] = arrList[nBlock2][offset2]; + arrList[nBlock2][offset2] = tmp; + } +#if NO_SUPPORT_PARALLEL_LIB +#else + ); +#endif + } + + const int INSERT_SORT_THRESHOLD = 7; + public void QuickSort(long left, long right) + { + if (left >= right) + { + return; + } + + //use insert sort to handle small data + long len = right - left + 1; + if (len < INSERT_SORT_THRESHOLD) + { + for (long i = left; i <= right; i++) + { + T t = this[i]; + long j = i; + for (; j > left && this[j - 1].CompareTo(t) > 0; j--) + { + this[j] = this[j - 1]; + } + this[j] = t; + } + return; + } + + //Choose the pivot value + long mid = left + (len >> 1); + if (len > INSERT_SORT_THRESHOLD) + { + //Split the list into three parts, and find middle value in each part, + //and finally, use middle value in above three middle values as pivot. + long leftMid = left; + long rightMid = right; + if (len > 40) + { + long size = len / 8; + leftMid = med3(leftMid, leftMid + size, leftMid + 2 * size); + mid = med3(mid - size, mid, mid + size); + rightMid = med3(right - 2 * size, right - size, right); + } + mid = med3(leftMid, mid, rightMid); + } + + T v = this[mid]; + + //Scan the list from two directions + long pivotLeftSide = left, leftScanIndex = pivotLeftSide; + long rightScanIndex = right, pivotRightSide = rightScanIndex; + + + int leftScanIndexBlock = (int)(leftScanIndex >> moveBit); + int leftScanIndexOffset = (int)(leftScanIndex & (sizePerBlock - 1)); + T[] arrayLeft = arrList[leftScanIndexBlock]; + + int rightScanIndexBlock = (int)(rightScanIndex >> moveBit); + int rightScanIndexOffset = (int)(rightScanIndex & (sizePerBlock - 1)); + T[] arrayRight = arrList[rightScanIndexBlock]; + while (true) + { + //Try to find item which is bigger than pivot + while (leftScanIndex <= rightScanIndex) + { + int cmpRst = arrayLeft[leftScanIndexOffset].CompareTo(v); + if (cmpRst > 0) + { + //Found one. + break; + } + else if (cmpRst == 0) + { + //If the item is equal to pivot, exchange it with the item in left-side. + swap(pivotLeftSide++, leftScanIndex); + } + leftScanIndex++; + + leftScanIndexOffset++; + if (leftScanIndexOffset == sizePerBlock) + { + leftScanIndexOffset = 0; + leftScanIndexBlock++; + if (leftScanIndexBlock == arrList.Count) + { + break; + } + arrayLeft = arrList[leftScanIndexBlock]; + } + } + + //Try to find item which is smaller than pivot + while (rightScanIndex >= leftScanIndex) + { + int cmpRst = arrayRight[rightScanIndexOffset].CompareTo(v); + if (cmpRst < 0) + { + //Found one. + break; + } + else if (cmpRst == 0) + { + //If the item is equal to pivot, exchange it with the item in left-side. + swap(rightScanIndex, pivotRightSide--); + } + rightScanIndex--; + + rightScanIndexOffset--; + if (rightScanIndexOffset < 0) + { + rightScanIndexOffset = (int)(sizePerBlock - 1); + rightScanIndexBlock--; + if (rightScanIndexBlock < 0) + { + break; + } + arrayRight = arrList[rightScanIndexBlock]; + } + } + + if (leftScanIndex > rightScanIndex) + { + //Scan finished + break; + } + + //Exchange two found items between pivot + T temp = arrayLeft[(int)leftScanIndexOffset]; + arrayLeft[(int)leftScanIndexOffset] = arrayRight[(int)rightScanIndexOffset]; + arrayRight[(int)rightScanIndexOffset] = temp; + + leftScanIndex++; + rightScanIndex--; + + leftScanIndexOffset++; + if (leftScanIndexOffset == sizePerBlock) + { + leftScanIndexOffset = 0; + leftScanIndexBlock++; + if (leftScanIndexBlock == arrList.Count) + { + break; + } + arrayLeft = arrList[leftScanIndexBlock]; + } + + rightScanIndexOffset--; + if (rightScanIndexOffset < 0) + { + rightScanIndexOffset = (int)(sizePerBlock - 1); + rightScanIndexBlock--; + if (rightScanIndexBlock < 0) + { + break; + } + arrayRight = arrList[rightScanIndexBlock]; + } + } + + //Continue to sort two sub-sections + long splitIndexLeft = leftScanIndex - pivotLeftSide; + long splitIndexRight = pivotRightSide - rightScanIndex; + if (splitIndexLeft > 1 && splitIndexRight > 1) + { +#if NO_SUPPORT_PARALLEL_LIB +#else + Parallel.Invoke(parallelOption, + () => +#endif + { + //exchange items with same value into middle of the list + long size = Math.Min(pivotLeftSide - left, leftScanIndex - pivotLeftSide); + vecswap(left, leftScanIndex - size, size); + QuickSort(left, splitIndexLeft + left - 1); + } +#if NO_SUPPORT_PARALLEL_LIB +#else + , + () => +#endif + { + //exchange items with same value into middle of the list + long size = Math.Min(pivotRightSide - rightScanIndex, right - pivotRightSide); + vecswap(leftScanIndex, right - size + 1, size); + QuickSort(right - splitIndexRight + 1, right); + } +#if NO_SUPPORT_PARALLEL_LIB +#else + ); +#endif + } + else + { + //exchange items with same value into middle of the list + long size = Math.Min(pivotLeftSide - left, leftScanIndex - pivotLeftSide); + vecswap(left, leftScanIndex - size, size); + + size = Math.Min(pivotRightSide - rightScanIndex, right - pivotRightSide); + vecswap(leftScanIndex, right - size + 1, size); + + if (splitIndexLeft > 1) + { + QuickSort(left, splitIndexLeft + left - 1); + } + + if (splitIndexRight > 1) + { + QuickSort(right - splitIndexRight + 1, right); + } + } + } + + + + public void Sort(long startIndex, long size, int threadnum = -1) + { +#if NO_SUPPORT_PARALLEL_LIB +#else + parallelOption.MaxDegreeOfParallelism = threadnum; + if (threadnum > 0) + { + lcts = new LimitedConcurrencyLevelTaskScheduler(threadnum * 2); + parallelOption.TaskScheduler = lcts; + } +#endif + QuickSort(startIndex, startIndex + size - 1); + } + + public void Sort(int threadnum = -1) + { +#if NO_SUPPORT_PARALLEL_LIB +#else + parallelOption.MaxDegreeOfParallelism = threadnum; + if (threadnum > 0) + { + lcts = new LimitedConcurrencyLevelTaskScheduler(threadnum * 2); + parallelOption.TaskScheduler = lcts; + } +#endif + QuickSort(0, Count - 1); + } + + + public T BinarySearch(long low, long high, T goal) + { + long mid = 0; + + while (low <= high) + { + mid = (high + low) / 2; + if (this[mid].CompareTo(goal) == 0) + { + return this[mid]; + } + else if (this[mid].CompareTo(goal) > 0) + { + high = mid - 1; + } + else + { + low = mid + 1; + } + } + return default(T); + } + } +} diff --git a/BotSharp.MachineLearning/CRFLite/Utils/DoubleArrayTrie.cs b/BotSharp.MachineLearning/CRFLite/Utils/DoubleArrayTrie.cs new file mode 100644 index 00000000..51a9487c --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Utils/DoubleArrayTrie.cs @@ -0,0 +1,631 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Threading; + +#if NO_SUPPORT_PARALLEL_LIB +#else +using System.Threading.Tasks; +#endif + +namespace BotSharp.MachineLearning.CRFLite.Utils +{ + public class unit_t : IComparable + { + public int base1; + public int check; + + public int CompareTo(unit_t obj) + { + throw new NotImplementedException(); + } + } + + public struct sunit_t : IComparable + { + public int base1; + public int check; + + public sunit_t(int b, int c) + { + base1 = b; + check = c; + } + + public int CompareTo(sunit_t obj) + { + throw new NotImplementedException(); + } + } + + public class DoubleArrayTrieSearch + { +#if NO_SUPPORT_VERY_BIG_OBJECT + private VarBigArray array; +#else + private sunit_t[] array; +#endif + + /// + /// Loads ArrayTrie from file + /// + /// path to file + /// + /// Number of elements (2 int32) in read buffer. + /// Default is 2048 (16K buffer size) + /// + public void Load(string fileName, int numberOfElementsInChunk = 2048) + { + if(!File.Exists(fileName)) + throw new FileNotFoundException( + "Please check that the specified file exists", fileName); + using (var stream = File.OpenRead(fileName)) + Load(stream, numberOfElementsInChunk); + } + + /// + /// Loads ArrayTrie from an arbitrary . + /// is closed and + /// disposed once this method completes. + /// + /// + /// A containing the model. + /// + /// + /// Number of elements (2 int32) in read buffer. + /// Default is 2048 (16K buffer size) + public void Load(Stream sourceStream, int numberOfElementsInChunk = 2048) + { + const int int32Size = sizeof(int); + const int elementSize = int32Size * 2; + var fileSizeInBytes = sourceStream.Length; + var numberOfElements = fileSizeInBytes / elementSize; +#if NO_SUPPORT_VERY_BIG_OBJECT + array = new VarBigArray(numberOfElements); +#else + array = new sunit_t[numberOfElements]; +#endif + using (var sr = new StreamReader(sourceStream)) + using (var br = new BinaryReader(sr.BaseStream)) + { + var buffersize = elementSize * numberOfElementsInChunk; + var buffer = new byte[elementSize * numberOfElementsInChunk]; + var index = 0; + for (long j = 0; j <= numberOfElements / numberOfElementsInChunk; j++) + { + var numberOfReadBytes = br.Read(buffer, 0, buffersize); + if (numberOfReadBytes == buffersize) + { + for (int i = 0; i < numberOfElementsInChunk; i++, index++) + { + var base1 = BitConverter.ToInt32(buffer, elementSize * i); + var check = BitConverter.ToInt32(buffer, (elementSize * i) + int32Size); + array[index] = new sunit_t(base1, check); + } + } + else + { + for (int i = 0; i < numberOfReadBytes / elementSize; i++) + { + var base1 = BitConverter.ToInt32(buffer, elementSize * i); + var check = BitConverter.ToInt32(buffer, (elementSize * i) + int32Size); + array[index++] = new sunit_t(base1, check); + } + break; + } + } + } + } + + //Match indexed key which is perfect matched with given string + public int SearchByPerfectMatch(string key) + { + int b = array[0].base1; + int p; + for (int index = 0; index < key.Length; index++) + { + char ch = key[index]; + p = b + ch + 1; + if (p >= array.Length) + { + return -1; + } + + if (b == array[p].check) + { + b = array[p].base1; + } + else + { + return -1; + } + } + + if (b >= array.Length) + { + return -1; + } + + int n = array[b].base1; + if (b == array[b].check && n < 0) + { + return -n - 1; + } + return -1; + } + + /// + /// Match all indexed keys which is prefix of the given string. + /// + public int SearchAsKeyPrefix(string key, List result) + { + int len = key.Length; + int b = array[0].base1; + int n, p; + result.Clear(); + + for (int i = 0; i < len; i++) + { + p = b; + if (p >= array.Length) + { + return result.Count; + } + + n = array[p].base1; + + if (b == array[p].check && n < 0) + { + result.Add(-n - 1); + } + + p = b + (int)key[i] + 1; + if (p >= array.Length) + { + return result.Count; + } + + if (b == array[p].check) + { + b = array[p].base1; + } + else + { + return result.Count; + } + } + + p = b; + if (p >= array.Length) + { + return result.Count; + } + n = array[p].base1; + + if (b == array[p].check && n < 0) + { + result.Add(-n - 1); + } + + return result.Count; + } + + + /// + /// Search keys by their prefix string + /// + public int SearchByPrefix(string strKeyPrefx, List result) + { + int b = array[0].base1; + int p; + + result.Clear(); + for (int index = 0; index < strKeyPrefx.Length; index++) + { + var ch = strKeyPrefx[index]; + p = b + (int)ch + 1; + if (p >= array.Length) + { + //The given string isn't existed in the DART + return -1; + } + + if (b == array[p].check) + { + b = array[p].base1; + } + else + { + return -1; + } + } + + Queue queue = new Queue(); + queue.Enqueue(b); + + while (queue.Count > 0) + { + b = queue.Dequeue(); + if (b >= array.Length) + { + //Invalidated base, skip it + continue; + } + + if (b == array[b].check && array[b].base1 < 0) + { + result.Add(-array[b].base1 - 1); + } + + for (int i = 0; i <= 65535; i++) + { + p = b + i + 1; + if (p >= array.Length) + { + //Out of the size of array, skip current search + break; + } + + if (b == array[p].check) + { + queue.Enqueue(array[p].base1); + } + } + } + + return result.Count; + } + } + + public class DoubleArrayTrieBuilder + { + private VarBigArray array; + private VarBigArray used; + private IList key_; + private IList val_; + private int next_chk_pos_; + private int progress_; + private int thread_num_; + private static double MAX_SLOT_USAGE_RATE_THRESHOLD = 0.95; + private static double MIN_SLOT_USAGE_RATE_THRESHOLD = 0.05; + private double slot_usage_rate_threshold_ = MAX_SLOT_USAGE_RATE_THRESHOLD; + +#if NO_SUPPORT_PARALLEL_LIB +#else + private ParallelOptions parallelOption; + private LimitedConcurrencyLevelTaskScheduler lcts; +#endif + + private DateTime startDT; + private double lastQPS; + private double lastQPSDelta; + + public class Node + { + public int code; + public int depth; + public int left; + public int right; + }; + + public DoubleArrayTrieBuilder(int thread_num) + { + array = null; + thread_num_ = thread_num; +#if NO_SUPPORT_PARALLEL_LIB +#else + lcts = new LimitedConcurrencyLevelTaskScheduler(thread_num_ * 2); + parallelOption = new ParallelOptions(); + parallelOption.TaskScheduler = lcts; +#endif + lastQPS = 0.0; + lastQPSDelta = 0.0; + } + + int fetch(Node parent, List siblings) + { + int prev = 0; + + int i = parent.left; + for (int j = parent.left; j < parent.right; j++) + { + string key = key_[j]; + if (key.Length < parent.depth) + continue; + int cur = 0; + if (key.Length != parent.depth) + { + cur = ((int)key[parent.depth]) + 1; + } + if (prev > cur) + { + throw new Exception("Fatal: given strings are not sorted.\n"); + } + if (cur != prev || siblings.Count == 0) + { + Node tmp_node = new Node(); + tmp_node.depth = parent.depth + 1; + tmp_node.code = cur; + tmp_node.left = i; + if (siblings.Count != 0) + siblings[siblings.Count - 1].right = i; + siblings.Add(tmp_node); + } + prev = cur; + i++; + } + if (siblings.Count != 0) + siblings[siblings.Count - 1].right = parent.right; + return siblings.Count; + } + + int insert(List siblings) + { + Random rnd = new Random(DateTime.Now.Millisecond + Thread.CurrentThread.ManagedThreadId); + int begin = 0; + bool cont = true; + int nonzeronum = 0; + + while (used[next_chk_pos_] == 1) + { + Interlocked.Increment(ref next_chk_pos_); + } + + int pos = next_chk_pos_; + int startpos = pos; + + //search begin position + pos--; + while (cont == true) + { + pos++; + if (used[pos] == 0) + { + //Check whether slots are available, if not go on to search, + cont = false; + foreach (Node n in siblings) + { + if (used[pos + n.code] == 1 || array[pos + n.code] != null) + { + cont = true; + break; + } + } + } + else + { + nonzeronum++; + } + } + begin = pos; + + //check average slot usage rate. If the rate is no less than the threshold, update next_chk_pos_ to + //pos whose slot range has much less conflict. + //note that, the higher rate threshold, the higher slot space usage rate, however, the timing-cost for tri-tree build + //will also become more higher. + if ((double)nonzeronum / (double)(pos - startpos + 1) >= slot_usage_rate_threshold_ && + pos > next_chk_pos_) + { + System.Threading.Interlocked.Exchange(ref next_chk_pos_, pos); + } + + //double check whether slots are available + //the reason why double check is because: + //1. in entire slots space, conflict rate is different. the conflict rate of array's tail + // is much lower than that of its header and body + //2. roll back cost is heavy. So in high conflict rate range, we just check conflict and no other action (first check) + // once we find a availabe range without conflict, we try to allocate memory on this range and double check conflict + bool bAllNull; + bool bZeroCode = false; + foreach (Node n in siblings) + { + if (n.code == 0) + { + bZeroCode = true; + break; + } + } + + if (bZeroCode == false) + { + Node sNode = new Node(); + sNode.code = 0; + siblings.Add(sNode); + } + + do + { + bAllNull = true; + //Test conflict in multi-threads + int cnt = 0; + foreach (Node n in siblings) + { + int nBlock = (begin + n.code) >> VarBigArray.moveBit; + long offset = (begin + n.code) & (VarBigArray.sizePerBlock - 1); + + if (used[begin + n.code] == 1 || + System.Threading.Interlocked.CompareExchange(ref used.arrList[nBlock][offset], 1, 0) != 0) + { + bAllNull = false; + foreach (Node revertNode in siblings.GetRange(0, cnt)) + { + used[begin + revertNode.code] = 0; + } + begin += rnd.Next(thread_num_) + 1; + break; + } + cnt++; + } + } while (bAllNull == false); + + if (bZeroCode == false) + { + siblings.RemoveAt(siblings.Count - 1); + } + +#if NO_SUPPORT_PARALLEL_LIB + for (int i = 0;i < siblings.Count;i++) +#else + Parallel.For(0, siblings.Count, parallelOption, i => +#endif + { + List new_siblings = new List(); + Node sibling = siblings[i]; + int offset = begin + sibling.code; + + array[offset] = new unit_t(); + array[offset].check = begin; + if (fetch(sibling, new_siblings) == 0) + { + array[offset].base1 = -val_[sibling.left] - 1; + if (Interlocked.Increment(ref progress_) % 10000 == 0) + { + //Try to adjust slot usage rate in order to keep high performance + TimeSpan ts = DateTime.Now - startDT; + double currQPS = progress_ / (ts.TotalSeconds + 1); + double currQPSDelta = currQPS - lastQPS; + + if (currQPS < lastQPS && currQPSDelta < lastQPSDelta) + { + //Average QPS becomes slow down, need to reduce slot usage rate + slot_usage_rate_threshold_ -= 0.1; + if (slot_usage_rate_threshold_ < MIN_SLOT_USAGE_RATE_THRESHOLD) + { + slot_usage_rate_threshold_ = MIN_SLOT_USAGE_RATE_THRESHOLD; + } + } + else + { + //Average QPS becomes fast, need to add slot usage rate + slot_usage_rate_threshold_ += 0.1; + if (slot_usage_rate_threshold_ > MAX_SLOT_USAGE_RATE_THRESHOLD) + { + slot_usage_rate_threshold_ = MAX_SLOT_USAGE_RATE_THRESHOLD; + } + } + + lastQPSDelta = currQPSDelta; + lastQPS = currQPS; + + if (progress_ % 100000 == 0) + { + //Show current progress on console + Console.Write("{0}...", progress_); + } + } + } + else + { + int b = insert(new_siblings); + array[offset].base1 = b; + } + } +#if NO_SUPPORT_PARALLEL_LIB +#else +); +#endif + + return begin; + } + + void clear() + { + array = null; + } + + public bool build(IDictionary keyvalueList, double max_slot_usage_rate_threshold = 0.95) + { + FixedBigArray keyList = new FixedBigArray(keyvalueList.Count, 0); + FixedBigArray valList = new FixedBigArray(keyvalueList.Count, 0); + long index = 0; + foreach (KeyValuePair pair in keyvalueList) + { + keyList[index] = pair.Key; + valList[index] = pair.Value; + index++; + } + + return build(keyList, valList, max_slot_usage_rate_threshold); + } + + public bool build(IList keyList, IList valList, double max_slot_usage_rate_threshold = 0.95) + { + if (keyList == null) + { + Console.WriteLine("Key list is empty"); + return false; + } + if (valList == null) + { + Console.WriteLine("Value list is empty"); + return false; + } + + if (keyList.Count != valList.Count) + { + Console.WriteLine("The size of key list and value list is not equal"); + return false; + } + + for (int i = 0; i < valList.Count; i++) + { + if (valList[i] <= -1) + { + Console.WriteLine("Invalidated value {0} at index {1}", valList[i], i); + return false; + } + } + + MAX_SLOT_USAGE_RATE_THRESHOLD = max_slot_usage_rate_threshold; + slot_usage_rate_threshold_ = max_slot_usage_rate_threshold; + progress_ = 0; + key_ = keyList; + val_ = valList; + + startDT = DateTime.Now; + array = new VarBigArray(key_.Count * 5); + used = new VarBigArray(key_.Count * 5); + array[0] = new unit_t(); + array[0].base1 = 1; + used[0] = 1; + next_chk_pos_ = 0; + Node root_node = new Node(); + root_node.left = 0; + root_node.right = key_.Count; + root_node.depth = 0; + List siblings = new List(); + fetch(root_node, siblings); + insert(siblings); + + return true; + } + + public void save(string file) + { + StreamWriter sw = new StreamWriter(file); + BinaryWriter bw = new BinaryWriter(sw.BaseStream); + + long r_length = array.LongLength; + while (array[r_length - 1] == null) + { + r_length--; + } + + for (long i = 0; i < r_length; i++) + { + if (array[i] == null) + { + bw.Write(0); + bw.Write(0); + } + else + { + bw.Write(array[i].base1); + bw.Write(array[i].check); + } + } + bw.Close(); + } + } +} diff --git a/BotSharp.MachineLearning/CRFLite/Utils/FixedBigArray.cs b/BotSharp.MachineLearning/CRFLite/Utils/FixedBigArray.cs new file mode 100644 index 00000000..1b8a6305 --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Utils/FixedBigArray.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Text; + +namespace BotSharp.MachineLearning.CRFLite.Utils +{ + public sealed class FixedBigArray : BigArray where T : IComparable + { + public int lowBounding_; + + public override T this[long i] + { + get + { + long offset = (i - lowBounding_); + int nBlock = (int)(offset >> moveBit); + return arrList[nBlock][offset & (sizePerBlock - 1)]; + } + set + { + long offset = (i - lowBounding_); + int nBlock = (int)(offset >> moveBit); + arrList[nBlock][offset & (sizePerBlock - 1)] = value; + } + } + + + + //construct big array + //size is array's default length + //lowBounding is the lowest bounding of the array + public FixedBigArray(long size, int lowBounding) + { + size_ = size; + lowBounding_ = lowBounding; + arrList = new List(); + + for (long i = 0; i < size_; i += sizePerBlock) + { + if (i + sizePerBlock < size_) + { + arrList.Add(new T[sizePerBlock]); + } + else + { + arrList.Add(new T[size_ - i]); + } + } + } + + + } +} diff --git a/BotSharp.MachineLearning/CRFLite/Utils/LimitedConcurrencyLevelTaskScheduler.cs b/BotSharp.MachineLearning/CRFLite/Utils/LimitedConcurrencyLevelTaskScheduler.cs new file mode 100644 index 00000000..2da789c4 --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Utils/LimitedConcurrencyLevelTaskScheduler.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +#if NO_SUPPORT_PARALLEL_LIB +#else +using System.Threading.Tasks; +namespace BotSharp.MachineLearning.CRFLite.Utils +{ + /// + /// Provides a task scheduler that ensures a maximum concurrency level while + /// running on top of the ThreadPool. + /// + public class LimitedConcurrencyLevelTaskScheduler : TaskScheduler + { + /// Whether the current thread is processing work items. + [ThreadStatic] + private static bool _currentThreadIsProcessingItems; + /// The list of tasks to be executed. + private readonly LinkedList _tasks = new LinkedList(); // protected by lock(_tasks) + /// The maximum concurrency level allowed by this scheduler. + private readonly int _maxDegreeOfParallelism; + /// Whether the scheduler is currently processing work items. + private int _delegatesQueuedOrRunning = 0; // protected by lock(_tasks) + + /// + /// Initializes an instance of the LimitedConcurrencyLevelTaskScheduler class with the + /// specified degree of parallelism. + /// + /// The maximum degree of parallelism provided by this scheduler. + public LimitedConcurrencyLevelTaskScheduler(int maxDegreeOfParallelism) + { + if (maxDegreeOfParallelism < 1) throw new ArgumentOutOfRangeException("maxDegreeOfParallelism"); + _maxDegreeOfParallelism = maxDegreeOfParallelism; + } + + /// Queues a task to the scheduler. + /// The task to be queued. + protected sealed override void QueueTask(Task task) + { + // Add the task to the list of tasks to be processed. If there aren't enough + // delegates currently queued or running to process tasks, schedule another. + lock (_tasks) + { + _tasks.AddLast(task); + if (_delegatesQueuedOrRunning < _maxDegreeOfParallelism) + { + ++_delegatesQueuedOrRunning; + NotifyThreadPoolOfPendingWork(); + } + } + } + + /// + /// Informs the ThreadPool that there's work to be executed for this scheduler. + /// + private void NotifyThreadPoolOfPendingWork() + { + ThreadPool.UnsafeQueueUserWorkItem(_ => + { + // Note that the current thread is now processing work items. + // This is necessary to enable inlining of tasks into this thread. + _currentThreadIsProcessingItems = true; + try + { + // Process all available items in the queue. + while (true) + { + Task item; + lock (_tasks) + { + // When there are no more items to be processed, + // note that we're done processing, and get out. + if (_tasks.Count == 0) + { + --_delegatesQueuedOrRunning; + break; + } + + // Get the next item from the queue + item = _tasks.First.Value; + _tasks.RemoveFirst(); + } + + // Execute the task we pulled out of the queue + base.TryExecuteTask(item); + } + } + // We're done processing items on the current thread + finally { _currentThreadIsProcessingItems = false; } + }, null); + } + + /// Attempts to execute the specified task on the current thread. + /// The task to be executed. + /// + /// Whether the task could be executed on the current thread. + protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) + { + // If this thread isn't already processing a task, we don't support inlining + if (!_currentThreadIsProcessingItems) return false; + + // If the task was previously queued, remove it from the queue + if (taskWasPreviouslyQueued) TryDequeue(task); + + // Try to run the task. + return base.TryExecuteTask(task); + } + + /// Attempts to remove a previously scheduled task from the scheduler. + /// The task to be removed. + /// Whether the task could be found and removed. + protected sealed override bool TryDequeue(Task task) + { + lock (_tasks) return _tasks.Remove(task); + } + + /// Gets the maximum concurrency level supported by this scheduler. + public sealed override int MaximumConcurrencyLevel { get { return _maxDegreeOfParallelism; } } + + /// Gets an enumerable of the tasks currently scheduled on this scheduler. + /// An enumerable of the tasks currently scheduled. + protected sealed override IEnumerable GetScheduledTasks() + { + bool lockTaken = false; + try + { + Monitor.TryEnter(_tasks, ref lockTaken); + if (lockTaken) return _tasks.ToArray(); + else throw new NotSupportedException(); + } + finally + { + if (lockTaken) Monitor.Exit(_tasks); + } + } + } +} +#endif \ No newline at end of file diff --git a/BotSharp.MachineLearning/CRFLite/Utils/MD5.cs b/BotSharp.MachineLearning/CRFLite/Utils/MD5.cs new file mode 100644 index 00000000..3d334e51 --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Utils/MD5.cs @@ -0,0 +1,554 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace BotSharp.MachineLearning.CRFLite.Utils +{ + public sealed class MD5 + { + private const int BLOCK_SIZE_BYTES = 64; + private const int HASH_SIZE_BYTES = 16; + + private uint _state0; + private uint _state1; + private uint _state2; + private uint _state3; + + private uint _decodeBuf0; + private uint _decodeBuf1; + private uint _decodeBuf2; + private uint _decodeBuf3; + private uint _decodeBuf4; + private uint _decodeBuf5; + private uint _decodeBuf6; + private uint _decodeBuf7; + private uint _decodeBuf8; + private uint _decodeBuf9; + private uint _decodeBuf10; + private uint _decodeBuf11; + private uint _decodeBuf12; + private uint _decodeBuf13; + private uint _decodeBuf14; + private uint _decodeBuf15; + + private ulong count; + private byte[] _ProcessingBuffer; // Used to start data when passed less than a block worth. + private int _ProcessingBufferCount; // Counts how much data we have stored that still needs processed. + private byte[] hash; + private byte[] fooBuffer; + + public MD5() + { + fooBuffer = new byte[BLOCK_SIZE_BYTES * 4096]; + hash = new byte[16]; + _ProcessingBuffer = new byte[BLOCK_SIZE_BYTES]; + + Initialize(); + } + + ~MD5() + { + Dispose(false); + } + + private void Dispose(bool disposing) + { + if (_ProcessingBuffer != null) + { + Array.Clear(_ProcessingBuffer, 0, _ProcessingBuffer.Length); + _ProcessingBuffer = null; + } + } + + private void HashCore(byte[] rgb, int start, int size) + { + int i; + if (_ProcessingBufferCount != 0) + { + if (size < (BLOCK_SIZE_BYTES - _ProcessingBufferCount)) + { + System.Buffer.BlockCopy(rgb, start, _ProcessingBuffer, _ProcessingBufferCount, size); + _ProcessingBufferCount += size; + return; + } + else + { + i = (BLOCK_SIZE_BYTES - _ProcessingBufferCount); + System.Buffer.BlockCopy(rgb, start, _ProcessingBuffer, _ProcessingBufferCount, i); + ProcessBlock(_ProcessingBuffer, 0); + _ProcessingBufferCount = 0; + start += i; + size -= i; + } + } + + for (i = 0; i < size - size % BLOCK_SIZE_BYTES; i += BLOCK_SIZE_BYTES) + { + ProcessBlock(rgb, start + i); + } + + if (size % BLOCK_SIZE_BYTES != 0) + { + System.Buffer.BlockCopy(rgb, size - size % BLOCK_SIZE_BYTES + start, _ProcessingBuffer, 0, size % BLOCK_SIZE_BYTES); + _ProcessingBufferCount = size % BLOCK_SIZE_BYTES; + } + } + + public long Compute64BitHash(byte[] buffer) + { + HashCore(buffer, 0, buffer.Length); + + ProcessFinalBlock(_ProcessingBuffer, 0, _ProcessingBufferCount); + long longRst = (((long)_state1 << 32) | (long)_state0); + this.Initialize(); + + return longRst; + } + + public byte[] ComputeHash(byte [] buffer) + { + HashCore(buffer, 0, buffer.Length); + + ProcessFinalBlock(_ProcessingBuffer, 0, _ProcessingBufferCount); + + hash[0] = (byte)(_state0); + hash[1] = (byte)(_state0 >> 8); + hash[2] = (byte)(_state0 >> 16); + hash[3] = (byte)(_state0 >> 24); + hash[4] = (byte)(_state1); + hash[5] = (byte)(_state1 >> 8); + hash[6] = (byte)(_state1 >> 16); + hash[7] = (byte)(_state1 >> 24); + hash[8] = (byte)(_state2); + hash[9] = (byte)(_state2 >> 8); + hash[10] = (byte)(_state2 >> 16); + hash[11] = (byte)(_state2 >> 24); + hash[12] = (byte)(_state3); + hash[13] = (byte)(_state3 >> 8); + hash[14] = (byte)(_state3 >> 16); + hash[15] = (byte)(_state3 >> 24); + + return hash; + } + + public void Initialize() + { + count = 0; + _ProcessingBufferCount = 0; + + _state0 = 0x67452301; + _state1 = 0xefcdab89; + _state2 = 0x98badcfe; + _state3 = 0x10325476; + } + + private void ProcessBlock(byte[] inputBuffer, int inputOffset) + { + uint a, b, c, d; + + count += BLOCK_SIZE_BYTES; + + _decodeBuf0 = ((uint)(inputBuffer[inputOffset] & 0xff)) | + (((uint)(inputBuffer[inputOffset + 1] & 0xff)) << 8) | + (((uint)(inputBuffer[inputOffset + 2] & 0xff)) << 16) | + (((uint)inputBuffer[inputOffset + 3]) << 24); + + _decodeBuf1 = ((uint)(inputBuffer[inputOffset + 4] & 0xff)) | + (((uint)(inputBuffer[inputOffset + 5] & 0xff)) << 8) | + (((uint)(inputBuffer[inputOffset + 6] & 0xff)) << 16) | + (((uint)inputBuffer[inputOffset + 7]) << 24); + + _decodeBuf2 = ((uint)(inputBuffer[inputOffset + 8] & 0xff)) | + (((uint)(inputBuffer[inputOffset + 9] & 0xff)) << 8) | + (((uint)(inputBuffer[inputOffset + 10] & 0xff)) << 16) | + (((uint)inputBuffer[inputOffset + 11]) << 24); + + _decodeBuf3 = ((uint)(inputBuffer[inputOffset + 12] & 0xff)) | + (((uint)(inputBuffer[inputOffset + 13] & 0xff)) << 8) | + (((uint)(inputBuffer[inputOffset + 14] & 0xff)) << 16) | + (((uint)inputBuffer[inputOffset + 15]) << 24); + + _decodeBuf4 = ((uint)(inputBuffer[inputOffset + 16] & 0xff)) | + (((uint)(inputBuffer[inputOffset + 17] & 0xff)) << 8) | + (((uint)(inputBuffer[inputOffset + 18] & 0xff)) << 16) | + (((uint)inputBuffer[inputOffset + 19]) << 24); + + _decodeBuf5 = ((uint)(inputBuffer[inputOffset + 20] & 0xff)) | + (((uint)(inputBuffer[inputOffset + 21] & 0xff)) << 8) | + (((uint)(inputBuffer[inputOffset + 22] & 0xff)) << 16) | + (((uint)inputBuffer[inputOffset + 23]) << 24); + + _decodeBuf6 = ((uint)(inputBuffer[inputOffset + 24] & 0xff)) | + (((uint)(inputBuffer[inputOffset + 25] & 0xff)) << 8) | + (((uint)(inputBuffer[inputOffset + 26] & 0xff)) << 16) | + (((uint)inputBuffer[inputOffset + 27]) << 24); + + _decodeBuf7 = ((uint)(inputBuffer[inputOffset + 28] & 0xff)) | + (((uint)(inputBuffer[inputOffset + 29] & 0xff)) << 8) | + (((uint)(inputBuffer[inputOffset + 30] & 0xff)) << 16) | + (((uint)inputBuffer[inputOffset + 31]) << 24); + + _decodeBuf8 = ((uint)(inputBuffer[inputOffset + 32] & 0xff)) | + (((uint)(inputBuffer[inputOffset + 33] & 0xff)) << 8) | + (((uint)(inputBuffer[inputOffset + 34] & 0xff)) << 16) | + (((uint)inputBuffer[inputOffset + 35]) << 24); + + _decodeBuf9 = ((uint)(inputBuffer[inputOffset + 36] & 0xff)) | + (((uint)(inputBuffer[inputOffset + 37] & 0xff)) << 8) | + (((uint)(inputBuffer[inputOffset + 38] & 0xff)) << 16) | + (((uint)inputBuffer[inputOffset + 39]) << 24); + + _decodeBuf10 = ((uint)(inputBuffer[inputOffset + 40] & 0xff)) | + (((uint)(inputBuffer[inputOffset + 41] & 0xff)) << 8) | + (((uint)(inputBuffer[inputOffset + 42] & 0xff)) << 16) | + (((uint)inputBuffer[inputOffset + 43]) << 24); + + _decodeBuf11 = ((uint)(inputBuffer[inputOffset + 44] & 0xff)) | + (((uint)(inputBuffer[inputOffset + 45] & 0xff)) << 8) | + (((uint)(inputBuffer[inputOffset + 46] & 0xff)) << 16) | + (((uint)inputBuffer[inputOffset + 47]) << 24); + + _decodeBuf12 = ((uint)(inputBuffer[inputOffset + 48] & 0xff)) | + (((uint)(inputBuffer[inputOffset + 49] & 0xff)) << 8) | + (((uint)(inputBuffer[inputOffset + 50] & 0xff)) << 16) | + (((uint)inputBuffer[inputOffset + 51]) << 24); + + _decodeBuf13 = ((uint)(inputBuffer[inputOffset + 52] & 0xff)) | + (((uint)(inputBuffer[inputOffset + 53] & 0xff)) << 8) | + (((uint)(inputBuffer[inputOffset + 54] & 0xff)) << 16) | + (((uint)inputBuffer[inputOffset + 55]) << 24); + + _decodeBuf14 = ((uint)(inputBuffer[inputOffset + 56] & 0xff)) | + (((uint)(inputBuffer[inputOffset + 57] & 0xff)) << 8) | + (((uint)(inputBuffer[inputOffset + 58] & 0xff)) << 16) | + (((uint)inputBuffer[inputOffset + 59]) << 24); + + _decodeBuf15 = ((uint)(inputBuffer[inputOffset + 60] & 0xff)) | + (((uint)(inputBuffer[inputOffset + 61] & 0xff)) << 8) | + (((uint)(inputBuffer[inputOffset + 62] & 0xff)) << 16) | + (((uint)inputBuffer[inputOffset + 63]) << 24); + + a = _state0; + b = _state1; + c = _state2; + d = _state3; + + // ---- Round 1 -------- + + // ---- Round 1 -------- + + a += (((c ^ d) & b) ^ d) + (uint)0xd76aa478 + _decodeBuf0; + a = (a << 7) | (a >> 25); + a += b; + + d += (((b ^ c) & a) ^ c) + (uint)0xe8c7b756 + _decodeBuf1; + d = (d << 12) | (d >> 20); + d += a; + + c += (((a ^ b) & d) ^ b) + (uint)0x242070db + _decodeBuf2; + c = (c << 17) | (c >> 15); + c += d; + + b += (((d ^ a) & c) ^ a) + (uint)0xc1bdceee + _decodeBuf3; + b = (b << 22) | (b >> 10); + b += c; + + a += (((c ^ d) & b) ^ d) + (uint)0xf57c0faf + _decodeBuf4; + a = (a << 7) | (a >> 25); + a += b; + + d += (((b ^ c) & a) ^ c) + (uint)0x4787c62a + _decodeBuf5; + d = (d << 12) | (d >> 20); + d += a; + + c += (((a ^ b) & d) ^ b) + (uint)0xa8304613 + _decodeBuf6; + c = (c << 17) | (c >> 15); + c += d; + + b += (((d ^ a) & c) ^ a) + (uint)0xfd469501 + _decodeBuf7; + b = (b << 22) | (b >> 10); + b += c; + + a += (((c ^ d) & b) ^ d) + (uint)0x698098d8 + _decodeBuf8; + a = (a << 7) | (a >> 25); + a += b; + + d += (((b ^ c) & a) ^ c) + (uint)0x8b44f7af + _decodeBuf9; + d = (d << 12) | (d >> 20); + d += a; + + c += (((a ^ b) & d) ^ b) + (uint)0xffff5bb1 + _decodeBuf10; + c = (c << 17) | (c >> 15); + c += d; + + b += (((d ^ a) & c) ^ a) + (uint)0x895cd7be + _decodeBuf11; + b = (b << 22) | (b >> 10); + b += c; + + a += (((c ^ d) & b) ^ d) + (uint)0x6b901122 + _decodeBuf12; + a = (a << 7) | (a >> 25); + a += b; + + d += (((b ^ c) & a) ^ c) + (uint)0xfd987193 + _decodeBuf13; + d = (d << 12) | (d >> 20); + d += a; + + c += (((a ^ b) & d) ^ b) + (uint)0xa679438e + _decodeBuf14; + c = (c << 17) | (c >> 15); + c += d; + + b += (((d ^ a) & c) ^ a) + (uint)0x49b40821 + _decodeBuf15; + b = (b << 22) | (b >> 10); + b += c; + + + // ---- Round 2 -------- + + a += ((b & d) | (c & ~d)) + (uint)0xf61e2562 + _decodeBuf1; + a = (a << 5) | (a >> 27); + a += b; + + d += ((a & c) | (b & ~c)) + (uint)0xc040b340 + _decodeBuf6; + d = (d << 9) | (d >> 23); + d += a; + + c += ((d & b) | (a & ~b)) + (uint)0x265e5a51 + _decodeBuf11; + c = (c << 14) | (c >> 18); + c += d; + + b += ((c & a) | (d & ~a)) + (uint)0xe9b6c7aa + _decodeBuf0; + b = (b << 20) | (b >> 12); + b += c; + + a += ((b & d) | (c & ~d)) + (uint)0xd62f105d + _decodeBuf5; + a = (a << 5) | (a >> 27); + a += b; + + d += ((a & c) | (b & ~c)) + (uint)0x02441453 + _decodeBuf10; + d = (d << 9) | (d >> 23); + d += a; + + c += ((d & b) | (a & ~b)) + (uint)0xd8a1e681 + _decodeBuf15; + c = (c << 14) | (c >> 18); + c += d; + + b += ((c & a) | (d & ~a)) + (uint)0xe7d3fbc8 + _decodeBuf4; + b = (b << 20) | (b >> 12); + b += c; + + a += ((b & d) | (c & ~d)) + (uint)0x21e1cde6 + _decodeBuf9; + a = (a << 5) | (a >> 27); + a += b; + + d += ((a & c) | (b & ~c)) + (uint)0xc33707d6 + _decodeBuf14; + d = (d << 9) | (d >> 23); + d += a; + + c += ((d & b) | (a & ~b)) + (uint)0xf4d50d87 + _decodeBuf3; + c = (c << 14) | (c >> 18); + c += d; + + b += ((c & a) | (d & ~a)) + (uint)0x455a14ed + _decodeBuf8; + b = (b << 20) | (b >> 12); + b += c; + + a += ((b & d) | (c & ~d)) + (uint)0xa9e3e905 + _decodeBuf13; + a = (a << 5) | (a >> 27); + a += b; + + d += ((a & c) | (b & ~c)) + (uint)0xfcefa3f8 + _decodeBuf2; + d = (d << 9) | (d >> 23); + d += a; + + c += ((d & b) | (a & ~b)) + (uint)0x676f02d9 + _decodeBuf7; + c = (c << 14) | (c >> 18); + c += d; + + b += ((c & a) | (d & ~a)) + (uint)0x8d2a4c8a + _decodeBuf12; + b = (b << 20) | (b >> 12); + b += c; + + + // ---- Round 3 -------- + + a += (b ^ c ^ d) + (uint)0xfffa3942 + _decodeBuf5; + a = (a << 4) | (a >> 28); + a += b; + + d += (a ^ b ^ c) + (uint)0x8771f681 + _decodeBuf8; + d = (d << 11) | (d >> 21); + d += a; + + c += (d ^ a ^ b) + (uint)0x6d9d6122 + _decodeBuf11; + c = (c << 16) | (c >> 16); + c += d; + + b += (c ^ d ^ a) + (uint)0xfde5380c + _decodeBuf14; + b = (b << 23) | (b >> 9); + b += c; + + a += (b ^ c ^ d) + (uint)0xa4beea44 + _decodeBuf1; + a = (a << 4) | (a >> 28); + a += b; + + d += (a ^ b ^ c) + (uint)0x4bdecfa9 + _decodeBuf4; + d = (d << 11) | (d >> 21); + d += a; + + c += (d ^ a ^ b) + (uint)0xf6bb4b60 + _decodeBuf7; + c = (c << 16) | (c >> 16); + c += d; + + b += (c ^ d ^ a) + (uint)0xbebfbc70 + _decodeBuf10; + b = (b << 23) | (b >> 9); + b += c; + + a += (b ^ c ^ d) + (uint)0x289b7ec6 + _decodeBuf13; + a = (a << 4) | (a >> 28); + a += b; + + d += (a ^ b ^ c) + (uint)0xeaa127fa + _decodeBuf0; + d = (d << 11) | (d >> 21); + d += a; + + c += (d ^ a ^ b) + (uint)0xd4ef3085 + _decodeBuf3; + c = (c << 16) | (c >> 16); + c += d; + + b += (c ^ d ^ a) + (uint)0x04881d05 + _decodeBuf6; + b = (b << 23) | (b >> 9); + b += c; + + a += (b ^ c ^ d) + (uint)0xd9d4d039 + _decodeBuf9; + a = (a << 4) | (a >> 28); + a += b; + + d += (a ^ b ^ c) + (uint)0xe6db99e5 + _decodeBuf12; + d = (d << 11) | (d >> 21); + d += a; + + c += (d ^ a ^ b) + (uint)0x1fa27cf8 + _decodeBuf15; + c = (c << 16) | (c >> 16); + c += d; + + b += (c ^ d ^ a) + (uint)0xc4ac5665 + _decodeBuf2; + b = (b << 23) | (b >> 9); + b += c; + + + // ---- Round 4 -------- + + a += (((~d) | b) ^ c) + (uint)0xf4292244 + _decodeBuf0; + a = (a << 6) | (a >> 26); + a += b; + + d += (((~c) | a) ^ b) + (uint)0x432aff97 + _decodeBuf7; + d = (d << 10) | (d >> 22); + d += a; + + c += (((~b) | d) ^ a) + (uint)0xab9423a7 + _decodeBuf14; + c = (c << 15) | (c >> 17); + c += d; + + b += (((~a) | c) ^ d) + (uint)0xfc93a039 + _decodeBuf5; + b = (b << 21) | (b >> 11); + b += c; + + a += (((~d) | b) ^ c) + (uint)0x655b59c3 + _decodeBuf12; + a = (a << 6) | (a >> 26); + a += b; + + d += (((~c) | a) ^ b) + (uint)0x8f0ccc92 + _decodeBuf3; + d = (d << 10) | (d >> 22); + d += a; + + c += (((~b) | d) ^ a) + (uint)0xffeff47d + _decodeBuf10; + c = (c << 15) | (c >> 17); + c += d; + + b += (((~a) | c) ^ d) + (uint)0x85845dd1 + _decodeBuf1; + b = (b << 21) | (b >> 11); + b += c; + + a += (((~d) | b) ^ c) + (uint)0x6fa87e4f + _decodeBuf8; + a = (a << 6) | (a >> 26); + a += b; + + d += (((~c) | a) ^ b) + (uint)0xfe2ce6e0 + _decodeBuf15; + d = (d << 10) | (d >> 22); + d += a; + + c += (((~b) | d) ^ a) + (uint)0xa3014314 + _decodeBuf6; + c = (c << 15) | (c >> 17); + c += d; + + b += (((~a) | c) ^ d) + (uint)0x4e0811a1 + _decodeBuf13; + b = (b << 21) | (b >> 11); + b += c; + + a += (((~d) | b) ^ c) + (uint)0xf7537e82 + _decodeBuf4; + a = (a << 6) | (a >> 26); + a += b; + + d += (((~c) | a) ^ b) + (uint)0xbd3af235 + _decodeBuf11; + d = (d << 10) | (d >> 22); + d += a; + + c += (((~b) | d) ^ a) + (uint)0x2ad7d2bb + _decodeBuf2; + c = (c << 15) | (c >> 17); + c += d; + + b += (((~a) | c) ^ d) + (uint)0xeb86d391 + _decodeBuf9; + b = (b << 21) | (b >> 11); + b += c; + + _state0 += a; + _state1 += b; + _state2 += c; + _state3 += d; + } + + private void ProcessFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) + { + ulong total = count + (ulong)inputCount; + int paddingSize = (int)(56 - total % BLOCK_SIZE_BYTES); + + if (paddingSize < 1) + paddingSize += BLOCK_SIZE_BYTES; + + for (int i = 0; i < inputCount; i++) + { + fooBuffer[i] = inputBuffer[i + inputOffset]; + } + + fooBuffer[inputCount] = 0x80; + for (int i = inputCount + 1; i < inputCount + paddingSize; i++) + { + fooBuffer[i] = 0x00; + } + + // I deal in bytes. The algorithm deals in bits. + ulong size = total << 3; + AddLength(size, fooBuffer, inputCount + paddingSize); + ProcessBlock(fooBuffer, 0); + + if (inputCount + paddingSize + 8 == 128) + { + ProcessBlock(fooBuffer, 64); + } + } + + internal void AddLength(ulong length, byte[] buffer, int position) + { + buffer[position++] = (byte)(length); + buffer[position++] = (byte)(length >> 8); + buffer[position++] = (byte)(length >> 16); + buffer[position++] = (byte)(length >> 24); + buffer[position++] = (byte)(length >> 32); + buffer[position++] = (byte)(length >> 40); + buffer[position++] = (byte)(length >> 48); + buffer[position] = (byte)(length >> 56); + } + } +} diff --git a/BotSharp.MachineLearning/CRFLite/Utils/VarBigArray.cs b/BotSharp.MachineLearning/CRFLite/Utils/VarBigArray.cs new file mode 100644 index 00000000..634b6c7a --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Utils/VarBigArray.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Text; + +namespace BotSharp.MachineLearning.CRFLite.Utils +{ + public sealed class VarBigArray : BigArray where T : IComparable + { + long blockSizeInTotal_; + private object ll = new object(); + + public override T this[long offset] + { + get + { + if (offset >= size_) + { + //resize array size, it need to be synced, + //for high performance, we use double check to avoid useless resize call and save memory + lock (ll) + { + if (offset >= size_) + { + Resize(offset + 1); + } + } + } + + long nBlock = offset >> moveBit; + return arrList[(int)nBlock][offset & (sizePerBlock-1)]; + } + set + { + if (offset >= size_) + { + //resize array size, it need to be synced, + //for high performance, we use double check to avoid useless resize call and save memory + lock (ll) + { + if (offset >= size_) + { + Resize(offset + 1); + } + } + } + + long nBlock = offset >> moveBit; + arrList[(int)nBlock][offset & (sizePerBlock-1)] = value; + } + } + + + + private void Resize(long new_size) + { + while (blockSizeInTotal_ <= new_size) + { + arrList.Add(new T[sizePerBlock]); + blockSizeInTotal_ += sizePerBlock; + } + + size_ = new_size; + } + + //construct variable size big array + //size is array's default length + //lowBounding is the lowest bounding of the array + //when accessing the position which is outer bounding, the big array will be extend automatically. + public VarBigArray(long size) + { + size_ = size; + arrList = new List(); + + for (blockSizeInTotal_ = 0; blockSizeInTotal_ < size_; + blockSizeInTotal_ += sizePerBlock) + { + arrList.Add(new T[sizePerBlock]); + } + } + } +} diff --git a/BotSharp.MachineLearning/CRFLite/Utils/VectorQuantization.cs b/BotSharp.MachineLearning/CRFLite/Utils/VectorQuantization.cs new file mode 100644 index 00000000..8aaa4546 --- /dev/null +++ b/BotSharp.MachineLearning/CRFLite/Utils/VectorQuantization.cs @@ -0,0 +1,377 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.IO; + +namespace BotSharp.MachineLearning.CRFLite.Utils +{ + public class VQCluster : IComparer + { + public int iStart, iEnd; + public double variance, mean; + + public VQCluster(double m, double v, int i, int j) + { + mean = m; + variance = v; + iStart = i; + iEnd = j; + } + + public VQCluster() { } + + public int Compare(VQCluster X, VQCluster Y) + { + if (X.mean > Y.mean) return 1; + if (X.mean < Y.mean) return -1; + return 0; + } + }; + + + public class VectorQuantization + { + protected List vqClusters; + protected double[] codebook; + protected VarBigArray dataSet; + protected int dataSetSize; + + public double[] CodeBook { get { return codebook; } } + + public VectorQuantization() + { + dataSet = new VarBigArray(1024 * 1024); + dataSetSize = 0; + } + + /// + /// Add a set of data into data set + /// + /// + public void Add(double[] values) + { + foreach (double value in values) + { + Add(value); + } + } + + /// + /// Add a single data into data set + /// + /// + public void Add(double value) + { + dataSet[dataSetSize] = value; + dataSetSize++; + } + + public int ComputeVQ(double value) + { + return BinarySearch(value); + } + + /// + /// Build codebook according given data set + /// + /// + /// + public double BuildCodebook(int vqSize) + { + if (vqSize > dataSetSize) + { + return -1; + } + + dataSet.Sort(0, dataSetSize); + + //Set entire data as a single cluster, and then split it + double mean, var; + ComputeVariables(0, dataSetSize - 1, out mean, out var); + VQCluster c = new VQCluster(mean, var, 0, dataSetSize - 1); + vqClusters = new List(); + vqClusters.Add(c); + + //Split clusters according its variance values + while (vqClusters.Count < vqSize) + { + int maxVarClusterId = MaxVarianceClusterId(); + if (maxVarClusterId < 0) break; // no more to split + + //Split the cluster into two and remove the orginal one + SplitCluster(vqClusters[maxVarClusterId].iStart, vqClusters[maxVarClusterId].iEnd, 0, 1); + vqClusters.RemoveAt(maxVarClusterId); + } + + //Adjust clusters according their mean values + AdjustCluster(); + + //Final codebook + vqSize = vqClusters.Count; + codebook = new double[vqSize]; + double distortion = 0; + for (int i = 0; i < vqSize; i++) + { + codebook[i] = vqClusters[i].mean; + for (int j = vqClusters[i].iStart; j <= vqClusters[i].iEnd; j++) + { + double diff = dataSet[j] - codebook[i]; + distortion += diff * diff; + } + } + + distortion = Math.Sqrt(distortion / dataSetSize); + return distortion; + } + + public bool WriteCodebook(string filename) + { + using (StreamWriter sw = new StreamWriter(filename)) + { + sw.WriteLine("Codeword\tMean\tCount"); + for (int i = 0; i < codebook.Length; i++) + { + int count = (int)(vqClusters[i].iEnd - vqClusters[i].iStart + 1); + sw.WriteLine("{0,8} {1}\t{2}", i, codebook[i], count); + } + } + + return true; + } + + public void ReadCodebook(string filename) + { + using (StreamReader sr = new StreamReader(filename)) + { + //Skip column title + sr.ReadLine(); + + //Read each line + string line = null; + List cb = new List(); + while ((line = sr.ReadLine()) != null) + { + string[] words = line.Split(); + double mean = 0; + + int n = int.Parse(words[0]); + mean = double.Parse(words[1]); + int count = int.Parse(words[2]); + cb.Add(mean); + } + codebook = cb.ToArray(); + } + } + + /// + /// Adjust cluster boundary according mean values + /// + void AdjustCluster() + { + int vqsize = vqClusters.Count; + bool updateCluster = true; + double mean, var; + + vqClusters.Sort(new VQCluster()); + + for (int iter = 0; iter < 20 && updateCluster; iter++) + { + updateCluster = false; + for (int i = 1; i < vqsize; i++) + { + int j = (int)vqClusters[i - 1].iEnd; + while (true) + { + double d1 = dataSet[j] - vqClusters[i - 1].mean; + if (d1 <= 0) break; + + double d2 = vqClusters[i].mean - dataSet[j]; + + if (d1 <= d2) break; + j--; + } + + if (j < vqClusters[i - 1].iEnd) + { + ComputeVariables((int)vqClusters[i - 1].iStart, j, out mean, out var); + UpdateCluster(i - 1, (int)vqClusters[i - 1].iStart, j, mean, var); + + ComputeVariables(j + 1, (int)vqClusters[i].iEnd, out mean, out var); + UpdateCluster(i, j + 1, (int)vqClusters[i].iEnd, mean, var); + + updateCluster = true; + continue; + } + + j = (int)vqClusters[i].iStart; + while (true) + { + double d1 = vqClusters[i].mean - dataSet[j]; + if (d1 <= 0) break; + + double d2 = dataSet[j] - vqClusters[i - 1].mean; + + if (d1 <= d2) break; + j++; + } + if (j > vqClusters[i].iStart) + { + ComputeVariables((int)vqClusters[i - 1].iStart, j - 1, out mean, out var); + UpdateCluster(i - 1, (int)vqClusters[i - 1].iStart, j - 1, mean, var); + + ComputeVariables(j, (int)vqClusters[i].iEnd, out mean, out var); + UpdateCluster(i, j, (int)vqClusters[i].iEnd, mean, var); + + updateCluster = true; + } + } + } + } + + /// + /// Search codebook and get the index which value is the nearest to given value + /// + /// + /// + private int BinarySearch(double value) + { + int low = 0, high = codebook.Length, mid = 0; + while (low < high) + { + mid = (int)((high - low) / 2) + low; + if (value > codebook[mid]) + low = mid + 1; + else if (value < codebook[mid]) + high = mid; + else + return mid; + } + + int cw = mid; + double delta = Math.Abs(value - codebook[cw]); + if (mid + 1 < codebook.Length) + { + double d2 = Math.Abs(value - codebook[mid + 1]); + if (d2 < delta) + { + cw = mid + 1; + delta = d2; + } + } + if (mid - 1 >= 0) + { + double d2 = Math.Abs(value - codebook[mid - 1]); + if (d2 < delta) + { + cw = mid - 1; + delta = d2; + } + } + return cw; + } + + /// + /// Return the cluster id which has the biggest variance value + /// + /// + private int MaxVarianceClusterId() + { + double maxVar = -1; + int c = -1; + for (int i = 0; i < vqClusters.Count; i++) + { + if (vqClusters[i].variance > maxVar) + { + maxVar = vqClusters[i].variance; + c = i; + } + } + return c; + } + + /// + /// Computing the mean and variance of given data set + /// + /// + /// + /// + /// + private void ComputeVariables(int iStart, int iEnd, out double mean, out double variance) + { + double sum = 0; + + mean = 0; + variance = 0; + for (int i = iStart; i <= iEnd; i++) + sum += dataSet[i]; + mean = sum / (iEnd - iStart + 1); + + sum = 0; + if (dataSet[iStart] < mean && dataSet[iEnd] > mean) + { + for (int i = iStart; i <= iEnd; i++) + { + double diff = dataSet[i] - mean; + sum += diff * diff; + } + } + variance = sum; + } + + /// + /// Update given cluster's values + /// + /// + /// + /// + /// + /// + private void UpdateCluster(int index, int iStart, int iEnd, double mean, double variance) + { + vqClusters[index].iStart = iStart; + vqClusters[index].iEnd = iEnd; + vqClusters[index].mean = mean; + vqClusters[index].variance = variance; + + } + + /// + /// Split one cluster into two clusters according its mean + /// + /// + /// + /// + /// + private void SplitCluster(int iStart, int iEnd, int depth, int maxDepth) + { + if (iStart > iEnd) + { + return; + } + + double mean, variance; + ComputeVariables(iStart, iEnd, out mean, out variance); + if (depth == maxDepth) + { + VQCluster c = new VQCluster(mean, variance, iStart, iEnd); + vqClusters.Add(c); + } + else + { + //Split the cluster into two clusters according mean value + int i; + for (i = iStart; i <= iEnd; i++) + { + //The following data will be greater than mean value, so we split it here + if (dataSet[i] > mean) + break; + } + + SplitCluster(iStart, i - 1, depth + 1, maxDepth); + SplitCluster(i, iEnd, depth + 1, maxDepth); + } + } + } +} diff --git a/BotSharp.sln b/BotSharp.sln index 1465645b..630d1360 100644 --- a/BotSharp.sln +++ b/BotSharp.sln @@ -17,6 +17,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.NLP", "BotSharp.NL EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.NLP.UnitTest", "BotSharp.NLP.UnitTest\BotSharp.NLP.UnitTest.csproj", "{2A8C199C-FD8E-4CB7-A83B-08F50F809AE8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.MachineLearning.UnitTest", "BotSharp.MachineLearning.UnitTest\BotSharp.MachineLearning.UnitTest.csproj", "{B876F0E9-40F0-48B7-91D5-E09E0B442266}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -51,6 +53,10 @@ Global {2A8C199C-FD8E-4CB7-A83B-08F50F809AE8}.Debug|Any CPU.Build.0 = Debug|Any CPU {2A8C199C-FD8E-4CB7-A83B-08F50F809AE8}.Release|Any CPU.ActiveCfg = Release|Any CPU {2A8C199C-FD8E-4CB7-A83B-08F50F809AE8}.Release|Any CPU.Build.0 = Release|Any CPU + {B876F0E9-40F0-48B7-91D5-E09E0B442266}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B876F0E9-40F0-48B7-91D5-E09E0B442266}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B876F0E9-40F0-48B7-91D5-E09E0B442266}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B876F0E9-40F0-48B7-91D5-E09E0B442266}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE