List<SegToken> process = segmenter.process("今天早上,出门的的时候,天气很好", JiebaSegmenter.SegMode.INDEX);
for (SegToken token:process){
//分词的结果
System.out.println( token.word);
}
Вывод выглядит следующим образом
今天
早上
,
出门
的
的
时候
,
天气
很
好
Логика выполнения сегментации слов
Видно, что ядро
содержит словарь внутри
логика причастий
Детализация сегментации различных режимов
образец причастия
поиск точно сокращает, используется для сегментации слов пользовательского запроса
индексировать повторно сегментировать длинные слова, чтобы улучшить скорость отзыва
процесс сегментации слов
Видно, что ядро
Создать DAG из ввода
выбрать высокочастотные слова
Если оно не включено в словарь, то есть слово не записано, провести повторную идентификацию
Создать группу обеспечения доступности баз данных
Получить загруженную попытку
Соответствие из дерева дерева, основной код выглядит следующим образом
int N = chars.length; //获取整个句子的长度
int i = 0, j = 0; //i 表示词的开始 ;j 表示词的结束
while (i < N) {
Hit hit = trie.match(chars, i, j - i + 1); //从trie树中匹配
if (hit.isPrefix() || hit.isMatch()) {
if (hit.isMatch()) {
//完全匹配
if (!dag.containsKey(i)) {
List<Integer> value = new ArrayList<Integer>();
dag.put(i, value);
value.add(j);
}
else
dag.get(i).add(j); //以当前字符开头的词有哪些,词结尾的坐标记下来
}
j += 1;
if (j >= N) {
//以当前字符开头的所有词已经匹配完成,再以当前字符的下一个字符开头寻找词
i += 1;
j = i;
}
}
else {
//以当前字符开头的词已经全部匹配完成,再以当前字符的下一个字符开头寻找词
i += 1;
j = i;
}
}
Дополнительные символы в предложениях, которых нет в DAG
Результаты выборки DAG
Например, введите "сегодня утром"
Его дисплей DAG выглядит следующим образомТо есть в предложении «сегодня утром» слова, которые можно найти в трее, это
今/今天/早/早上/上
Приложение Trie Tree
JieBa внутренне хранит файл dict.txt, например записьX光线 3 n. Древовидная структура внутреннего хранилища Trie
nodeState: Текущее состояние DictSegment, по умолчанию 0, 1 означает, что путь от корневого узла к текущему узлу означает слово, такое как рентген и рентген
storeSize: количество сегментов, хранимых текущим узлом.
Например, кроме рентгена есть рентгеновские лучи.
childrenArray и childrenMap используются для хранения дочерних узлов дерева дерева.
storeSize ARRAY_LENGTH_LIMIT, использовать хранилище карты, значение равно 3
выбрать высокочастотные слова
Основной код выглядит следующим образом
for (int i = N - 1; i > -1; i--) {
//从右往左去查看句子,这是因为中文的重点一般在后面
//表示词的开始位置
Pair<Integer> candidate = null;
for (Integer x : dag.get(i)) {
//x表示词的结束位置
// wordDict.getFreq表示获取trie这个词的频率
//route.get(x+1)表示当前词的后一个词的概率
//由于频率本身存储的是数学上log计算后的值,这里的加法其实就是当前这个词为A并且后面紧跟着的词为B的概率,B已经由前面算出
double freq = wordDict.getFreq(sentence.substring(i, x + 1)) + route.get(x + 1).freq;
if (null == candidate) {
candidate = new Pair<Integer>(x, freq);
}
else if (candidate.freq < freq) {
//保存概率高的词
candidate.freq = freq;
candidate.key = x;
}
}
//可见route中存储的数据为key:词头下标 value:词尾下标,词的频率
route.put(i, candidate);
}
Процесс выбора высокочастотных слов:
i=N-1: В маршруте есть только одно начальное значение, N=4, freq=0, что означает, что после последнего раза нет слова Во-первых, частота слова «вверх» в дереве равна -5,45. Вероятность попадания после добавления равна -5,45. Поскольку есть только одно слово, начинающееся с «on», оно сохраняется в маршруте
(3,) : первые 3 – префикс, вторые 3 – индекс суффикса "上", -5,45 – вероятность его появления; (4,): начальная вероятность
i=N-2: есть два слова, начинающиеся со слова «рано», а именно «рано» и «утро», частота слова «рано» в словаре составляет сначала -8,33, а следующее слово — «上». суммирование, частота -13,78, затем получаем "утро", частота в словаре -10,81, после слова "утро" нет слова, поэтому значение частоты равно -10,81, сравнивая частоту двух слов, частоту "утро" велико, поэтому сохраняется только "утро"
В этот момент маршрут сохраняет (3,), (4,) и (2, )
И так далее, слова после маршрута следующие:
код сегментации слова
После взятия высокочастотных слов основная логика выглядит следующим образом.
while (x < N) {
//获取当前字符开头的词的词尾
y = route.get(x).key + 1;
String lWord = sentence.substring(x, y);
if (y - x == 1)
sb.append(lWord); //单个字符成词,先保留
else {
if (sb.length() > 0) {
buf = sb.toString();
sb = new StringBuilder();
if (buf.length() == 1) {
tokens.add(buf);
}
else {
if (wordDict.containsWord(buf)) {
tokens.add(buf); //多个字符并且字典中存在,作为分词的结果
}
else {
finalSeg.cut(buf, tokens);
}
}
}
//保留多个字符组成的词
tokens.add(lWord);
}
x = y; //从当前词的词尾开始找下一个词
}
процесс извлечения слов
x=0, найдите, что его окончание слова равно 1, и получите «сегодня» в это время, поскольку оно содержит несколько слов, оно непосредственно используется как результат сегментации слов.
x=2, конец слова равен 3, добраться до «утро», конец сегментации слова
На этом причастие предложения «сегодня утром» заканчивается. Вы можете видеть, что это основано на том факте, что слово уже существует в словаре. Если в словах есть несколько отдельных слов, таких как «the» в «going out», с одной стороны, оно становится одним словом, а с другой стороны, последующее «of» становится вместе с ним Слово, состоящее из двух символов не существует в словаре 'of', поэтому оно распознается как неизвестное слово, вызывающееfinalSeg.cut
Viterbi алгоритм обрабатывает незаконные слова и повторно идентифицирует их
В качестве метода используется алгоритм Витерби. Сначала предварительно загрузите следующие три набора наборов вероятностей и наборов скрытых состояний модели HMM.
Неизвестные слова определяют 4 скрытых состояния. B означает начало слова M означает середину слова E означает конец слова S означает, что слово становится словом
Инициализировать начальную вероятность каждого скрытого состояния
trans = new HashMap<Character, Map<Character, Double>>();
Map<Character, Double> transB = new HashMap<Character, Double>();
transB.put('E', -0.510825623765990);
transB.put('M', -0.916290731874155);
trans.put('B', transB);
Map<Character, Double> transE = new HashMap<Character, Double>();
transE.put('B', -0.5897149736854513);
transE.put('S', -0.8085250474669937);
trans.put('E', transE);
Map<Character, Double> transM = new HashMap<Character, Double>();
transM.put('E', -0.33344856811948514);
transM.put('M', -1.2603623820268226);
trans.put('M', transM);
Map<Character, Double> transS = new HashMap<Character, Double>();
transS.put('B', -0.7211965654669841);
transS.put('S', -0.6658631448798212);
trans.put('S', transS);
Например, trans.get('S').get('B') означает, что если текущим символом является 'S', вероятность того, что следующее слово (слово, состоящее из одного слова) начинается с -0,721
Прочитайте матрицу путаницы, подготовленную реализацией, и сохраните ее в emit.
Красная рамка указывает на то, что при предпосылке скрытого состояния «Е» вероятность того, что наблюдаемое состояние является «хочу», составляет -5,26.Красная рамка указывает на то, что при предпосылке скрытого состояния «В» вероятность того, что наблюдаемое состояние является «хочу», составляет -6,73.
Кроме того, он предопределяет только те состояния, которые предшествуют каждому скрытому состоянию.
prevStatus.put('B', new char[] { 'E', 'S' });
prevStatus.put('M', new char[] { 'M', 'B' });
prevStatus.put('S', new char[] { 'S', 'E' });
prevStatus.put('E', new char[] { 'B', 'M' });
Например, перед «М» должна стоять единица между «М» и «В».
Ход алгоритма следующий:
Получить вероятность появления первого символа в предложении во всех скрытых состояниях
for (char state : states) {
Double emP = emit.get(state).get(sentence.charAt(0));
if (null == emP)
emP = MIN_FLOAT;
//存储第一个字符 是 'B' 'E' 'M' 'S'的概率,即初始化转移概率
v.get(0).put(state, start.get(state) + emP);
path.put(state, new Node(state, null));
}
Рассчитайте вероятность получения скрытой последовательности на основе последовательности наблюдений и модели HMM и отметьте наилучшую аналитическую позицию, которая связана родительским указателем
for (int i = 1; i < sentence.length(); ++i) {
Map<Character, Double> vv = new HashMap<Character, Double>();
v.add(vv);
Map<Character, Node> newPath = new HashMap<Character, Node>();
for (char y : states) {
//y表示隐藏状态
//emp是获取混淆矩阵的概率,比如 在 'B'发生的情况下,观察到字符 '要' 的概率
Double emp = emit.get(y).get(sentence.charAt(i));
if (emp == null)
emp = MIN_FLOAT; //样本中没有,就设置为最小的概率
Pair<Character> candidate = null;
for (char y0 : prevStatus.get(y)) {
Double tranp = trans.get(y0).get(y);//获取状态转移概率,比如 E -> B
if (null == tranp)
tranp = MIN_FLOAT; //转移概率不存在,取最低的
//v中放的是当前字符的前一个字符的概率,即前一个状态的最优解
//tranp 是状态转移的概率
//三者相加即计算已知观察序列和HMM的条件下,求得最可能的隐藏序列的概率
tranp += (emp + v.get(i - 1).get(y0));
if (null == candidate)
candidate = new Pair<Character>(y0, tranp);
else if (candidate.freq <= tranp) {
//存储最优可能的隐藏概率
candidate.freq = tranp;
candidate.key = y0;
}
}
//存储是'B'还是 'E'各自的概率
vv.put(y, candidate.freq);
//记下前后两个词最优的路径,以便还原原始的隐藏状态分隔点
newPath.put(y, new Node(y, path.get(candidate.key)));
}
//存储最终句子的最优路径
path = newPath;
}
Найдите скрытое состояние символов в конце предложения и используйте лучший путь, чтобы сократить все предложение.
Проведите это предложение по метке, запишите все слова точки разреза
int begin = 0, next = 0;
for (int i = 0; i < sentence.length(); ++i) {
char pos = posList.get(i);
if (pos == 'B')
begin = i;
else if (pos == 'E') {
//到词尾了,记下
tokens.add(sentence.substring(begin, i + 1));
next = i + 1;
}
else if (pos == 'S') {
//单个字成词,记下
tokens.add(sentence.substring(i, i + 1));
next = i + 1;
}
}
if (next < sentence.length())
tokens.add(sentence.substring(next));