BotSharp/BotSharp.Core/Engines/Jieba.NET/PosSeg/Viterbi.cs
Oceania2018 6019d9268f 1. Embed jieba.NET source code.
2. Remove publish profile.
3. Fix docker build
2018-09-21 06:02:58 -05:00

176 lines
6.4 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using JiebaNet.Segmenter.Common;
using Newtonsoft.Json;
namespace JiebaNet.Segmenter.PosSeg
{
public class Viterbi
{
private static readonly Lazy<Viterbi> Lazy = new Lazy<Viterbi>(() => new Viterbi());
private static IDictionary<string, double> _startProbs;
private static IDictionary<string, IDictionary<string, double>> _transProbs;
private static IDictionary<string, IDictionary<char, double>> _emitProbs;
private static IDictionary<char, List<string>> _stateTab;
private Viterbi()
{
LoadModel();
}
// TODO: synchronized
public static Viterbi Instance
{
get { return Lazy.Value; }
}
public IEnumerable<Pair> Cut(string sentence)
{
var probPosList = ViterbiCut(sentence);
var posList = probPosList.Item2;
var tokens = new List<Pair>();
int begin = 0, next = 0;
for (var i = 0; i < sentence.Length; i++)
{
var parts = posList[i].Split('-');
var charState = parts[0][0];
var pos = parts[1];
if (charState == 'B')
begin = i;
else if (charState == 'E')
{
tokens.Add(new Pair(sentence.Sub(begin, i + 1), pos));
next = i + 1;
}
else if (charState == 'S')
{
tokens.Add(new Pair(sentence.Sub(i, i + 1), pos));
next = i + 1;
}
}
if (next < sentence.Length)
{
tokens.Add(new Pair(sentence.Substring(next), posList[next].Split('-')[1]));
}
return tokens;
}
#region Private Helpers
private static void LoadModel()
{
var startJson = FileExtension.ReadEmbeddedAllLine(ConfigManager.PosProbStartFile);
_startProbs = JsonConvert.DeserializeObject<IDictionary<string, double>>(startJson);
var transJson = FileExtension.ReadEmbeddedAllLine(ConfigManager.PosProbTransFile);
_transProbs = JsonConvert.DeserializeObject<IDictionary<string, IDictionary<string, double>>>(transJson);
var emitJson = FileExtension.ReadEmbeddedAllLine(ConfigManager.PosProbEmitFile);
_emitProbs = JsonConvert.DeserializeObject<IDictionary<string, IDictionary<char, double>>>(emitJson);
var tabJson = FileExtension.ReadEmbeddedAllLine(ConfigManager.CharStateTabFile);
_stateTab = JsonConvert.DeserializeObject<IDictionary<char, List<string>>>(tabJson);
}
// TODO: change sentence to obs?
private Tuple<double, List<string>> ViterbiCut(string sentence)
{
var v = new List<IDictionary<string, double>>();
var memPath = new List<IDictionary<string, string>>();
var allStates = _transProbs.Keys.ToList();
// Init weights and paths.
v.Add(new Dictionary<string, Double>());
memPath.Add(new Dictionary<string, string>());
foreach (var state in _stateTab.GetDefault(sentence[0], allStates))
{
var emP = _emitProbs[state].GetDefault(sentence[0], Constants.MinProb);
v[0][state] = _startProbs[state] + emP;
memPath[0][state] = string.Empty;
}
// For each remaining char
for (var i = 1; i < sentence.Length; ++i)
{
v.Add(new Dictionary<string, double>());
memPath.Add(new Dictionary<string, string>());
var prevStates = memPath[i - 1].Keys.Where(k => _transProbs[k].Count > 0);
var curPossibleStates = new HashSet<string>(prevStates.SelectMany(s => _transProbs[s].Keys));
IEnumerable<string> obsStates = _stateTab.GetDefault(sentence[i], allStates);
obsStates = curPossibleStates.Intersect(obsStates);
if (!obsStates.Any())
{
if (curPossibleStates.Count > 0)
{
obsStates = curPossibleStates;
}
else
{
obsStates = allStates;
}
}
foreach (var y in obsStates)
{
var emp = _emitProbs[y].GetDefault(sentence[i], Constants.MinProb);
var prob = double.MinValue;
var state = string.Empty;
foreach (var y0 in prevStates)
{
var tranp = _transProbs[y0].GetDefault(y, double.MinValue);
tranp = v[i - 1][y0] + tranp + emp;
// TODO: compare two very small values;
// TODO: how to deal with negative infinity
if (prob < tranp ||
(prob == tranp && string.Compare(state, y0, StringComparison.CurrentCultureIgnoreCase) < 0))
{
prob = tranp;
state = y0;
}
}
v[i][y] = prob;
memPath[i][y] = state;
}
}
var vLast = v.Last();
var last = memPath.Last().Keys.Select(y => new {State = y, Prob = vLast[y]});
var endProb = double.MinValue;
var endState = string.Empty;
foreach (var endPoint in last)
{
// TODO: compare two very small values;
if (endProb < endPoint.Prob ||
(endProb == endPoint.Prob && String.Compare(endState, endPoint.State, StringComparison.CurrentCultureIgnoreCase) < 0))
{
endProb = endPoint.Prob;
endState = endPoint.State;
}
}
var route = new string[sentence.Length];
var n = sentence.Length - 1;
var curState = endState;
while(n >= 0)
{
route[n] = curState;
curState = memPath[n][curState];
n--;
}
return new Tuple<double, List<string>>(endProb, route.ToList());
}
#endregion
}
}