node-nlp
Version:
Library for NLU (Natural Language Understanding) done in Node.js
700 lines (664 loc) • 17.6 kB
JavaScript
/*
* Copyright (c) AXA Shared Services Spain S.A.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* eslint-disable */
const Tokenizer = require('./tokenizer');
const JapaneseRules = require('./japanese-rules.json');
function replacer(translationTable) {
const pattern = [];
const keys = Object.keys(translationTable);
keys.forEach((key) => {
// eslint-disable-next-line
pattern.push(`${key}`.replace(/([-()\[\]{}+?*.$\^|,:#<!\\\/])/g, '\\$1').replace(/\x08/g, '\\x08'));
});
const regExp = new RegExp(pattern.join('|'), 'g');
return str => str.replace(regExp, s => translationTable[s]);
}
function merge(...args) {
const newObj = {};
let id = 0;
while (args[id]) {
const keys = Object.keys(args[id]);
for (let i = 0; i < keys.length; i += 1) {
const key = keys[i];
newObj[key] = args[id][key];
}
id += 1;
}
return newObj;
}
function flip(obj) {
const newObj = {};
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i += 1) {
const key = keys[i];
newObj[obj[key]] = key;
}
return newObj;
}
const fixFullwidthKana = {
'ゝ゛': 'ゞ',
'ヽ゛': 'ヾ',
'う゛': 'ゔ',
'か゛': 'が',
'き゛': 'ぎ',
'く゛': 'ぐ',
'け゛': 'げ',
'こ゛': 'ご',
'さ゛': 'ざ',
'し゛': 'じ',
'す゛': 'ず',
'せ゛': 'ぜ',
'そ゛': 'ぞ',
'た゛': 'だ',
'ち゛': 'ぢ',
'つ゛': 'づ',
'て゛': 'で',
'と゛': 'ど',
'は゛': 'ば',
'は゜': 'ぱ',
'ひ゛': 'び',
'ひ゜': 'ぴ',
'ふ゛': 'ぶ',
'ふ゜': 'ぷ',
'へ゛': 'べ',
'へ゜': 'ぺ',
'ほ゛': 'ぼ',
'ほ゜': 'ぽ',
'っな': 'んな',
'っに': 'んに',
'っぬ': 'んぬ',
'っね': 'んね',
'っの': 'んの',
'ウ゛': 'ヴ',
'カ゛': 'ガ',
'キ゛': 'ギ',
'ク゛': 'グ',
'ケ゛': 'ゲ',
'コ゛': 'ゴ',
'サ゛': 'ザ',
'シ゛': 'ジ',
'ス゛': 'ズ',
'セ゛': 'ゼ',
'ソ゛': 'ゾ',
'タ゛': 'ダ',
'チ゛': 'ヂ',
'ツ゛': 'ヅ',
'テ゛': 'デ',
'ト゛': 'ド',
'ハ゛': 'バ',
'ハ゜': 'パ',
'ヒ゛': 'ビ',
'ヒ゜': 'ピ',
'フ゛': 'ブ',
'フ゜': 'プ',
'ヘ゛': 'ベ',
'ヘ゜': 'ペ',
'ホ゛': 'ボ',
'ホ゜': 'ポ',
'ッナ': 'ンナ',
'ッニ': 'ンニ',
'ッヌ': 'ンヌ',
'ッネ': 'ンネ',
'ッノ': 'ンノ',
};
const conversionTables = {
fullwidthToHalfwidth: {
alphabet: {
'a': 'a',
'b': 'b',
'c': 'c',
'd': 'd',
'e': 'e',
'f': 'f',
'g': 'g',
'h': 'h',
'i': 'i',
'j': 'j',
'k': 'k',
'l': 'l',
'm': 'm',
'n': 'n',
'o': 'o',
'p': 'p',
'q': 'q',
'r': 'r',
's': 's',
't': 't',
'u': 'u',
'v': 'v',
'w': 'w',
'x': 'x',
'y': 'y',
'z': 'z',
'A': 'A',
'B': 'B',
'C': 'C',
'D': 'D',
'E': 'E',
'F': 'F',
'G': 'G',
'H': 'H',
'I': 'I',
'J': 'J',
'K': 'K',
'L': 'L',
'M': 'M',
'N': 'N',
'O': 'O',
'P': 'P',
'Q': 'Q',
'R': 'R',
'S': 'S',
'T': 'T',
'U': 'U',
'V': 'V',
'W': 'W',
'X': 'X',
'Y': 'Y',
'Z': 'Z',
' ': ' ',
},
numbers: {
'0': '0',
'1': '1',
'2': '2',
'3': '3',
'4': '4',
'5': '5',
'6': '6',
'7': '7',
'8': '8',
'9': '9',
},
symbol: {
'_': '_',
'-': '-',
',': ',',
';': ';',
':': ':',
'!': '!',
'?': '?',
'.': '.',
'(': '(',
')': ')',
'[': '[',
']': ']',
'{': '{',
'}': '}',
'@': '@',
'*': '*',
'\': '\\',
'/': '/',
'&': '&',
'#': '#',
'%': '%',
'`': '`',
'^': '^',
'+': '+',
'<': '<',
'=': '=',
'>': '>',
'|': '|',
'≪': '«',
'≫': '»',
'─': '-',
'$': '$',
'"': '"',
},
purePunctuation: {
'、': '、',
'。': '。',
'・': '・',
'「': '「',
'」': '」',
},
punctuation: {},
katakana: {
'゛': '゙',
'゜': '゚',
'ー': 'ー',
'ヴ': 'ヴ',
'ガ': 'ガ',
'ギ': 'ギ',
'グ': 'グ',
'ゲ': 'ゲ',
'ゴ': 'ゴ',
'ザ': 'ザ',
'ジ': 'ジ',
'ズ': 'ズ',
'ゼ': 'ゼ',
'ゾ': 'ゾ',
'ダ': 'ダ',
'ヂ': 'ヂ',
'ヅ': 'ヅ',
'デ': 'デ',
'ド': 'ド',
'バ': 'バ',
'パ': 'パ',
'ビ': 'ビ',
'ピ': 'ピ',
'ブ': 'ブ',
'プ': 'プ',
'ベ': 'ベ',
'ペ': 'ペ',
'ボ': 'ボ',
'ポ': 'ポ',
'ァ': 'ァ',
'ア': 'ア',
'ィ': 'ィ',
'イ': 'イ',
'ゥ': 'ゥ',
'ウ': 'ウ',
'ェ': 'ェ',
'エ': 'エ',
'ォ': 'ォ',
'オ': 'オ',
'カ': 'カ',
'キ': 'キ',
'ク': 'ク',
'ケ': 'ケ',
'コ': 'コ',
'サ': 'サ',
'シ': 'シ',
'ス': 'ス',
'セ': 'セ',
'ソ': 'ソ',
'タ': 'タ',
'チ': 'チ',
'ッ': 'ッ',
'ツ': 'ツ',
'テ': 'テ',
'ト': 'ト',
'ナ': 'ナ',
'ニ': 'ニ',
'ヌ': 'ヌ',
'ネ': 'ネ',
'ノ': 'ノ',
'ハ': 'ハ',
'ヒ': 'ヒ',
'フ': 'フ',
'ヘ': 'ヘ',
'ホ': 'ホ',
'マ': 'マ',
'ミ': 'ミ',
'ム': 'ム',
'メ': 'メ',
'モ': 'モ',
'ャ': 'ャ',
'ヤ': 'ヤ',
'ュ': 'ュ',
'ユ': 'ユ',
'ョ': 'ョ',
'ヨ': 'ヨ',
'ラ': 'ラ',
'リ': 'リ',
'ル': 'ル',
'レ': 'レ',
'ロ': 'ロ',
'ワ': 'ワ',
'ヲ': 'ヲ',
'ン': 'ン',
},
},
halfwidthToFullwidth: {},
};
conversionTables.fullwidthToHalfwidth.punctuation = merge(
conversionTables.fullwidthToHalfwidth.symbol,
conversionTables.fullwidthToHalfwidth.purePunctuation,
);
// Fill in the conversion tables with the flipped tables.
conversionTables.halfwidthToFullwidth.alphabet =
flip(conversionTables.fullwidthToHalfwidth.alphabet);
conversionTables.halfwidthToFullwidth.numbers =
flip(conversionTables.fullwidthToHalfwidth.numbers);
conversionTables.halfwidthToFullwidth.symbol =
flip(conversionTables.fullwidthToHalfwidth.symbol);
conversionTables.halfwidthToFullwidth.purePunctuation =
flip(conversionTables.fullwidthToHalfwidth.purePunctuation);
conversionTables.halfwidthToFullwidth.punctuation =
flip(conversionTables.fullwidthToHalfwidth.punctuation);
conversionTables.halfwidthToFullwidth.katakana =
flip(conversionTables.fullwidthToHalfwidth.katakana);
// Build the normalization table.
conversionTables.normalize = merge(
conversionTables.fullwidthToHalfwidth.alphabet,
conversionTables.fullwidthToHalfwidth.numbers,
conversionTables.fullwidthToHalfwidth.symbol,
conversionTables.halfwidthToFullwidth.purePunctuation,
conversionTables.halfwidthToFullwidth.katakana,
);
const fixCompositeSymbolsTable = {
'㋀': '1月',
'㋁': '2月',
'㋂': '3月',
'㋃': '4月',
'㋄': '5月',
'㋅': '6月',
'㋆': '7月',
'㋇': '8月',
'㋈': '9月',
'㋉': '10月',
'㋊': '11月',
'㋋': '12月',
'㏠': '1日',
'㏡': '2日',
'㏢': '3日',
'㏣': '4日',
'㏤': '5日',
'㏥': '6日',
'㏦': '7日',
'㏧': '8日',
'㏨': '9日',
'㏩': '10日',
'㏪': '11日',
'㏫': '12日',
'㏬': '13日',
'㏭': '14日',
'㏮': '15日',
'㏯': '16日',
'㏰': '17日',
'㏱': '18日',
'㏲': '19日',
'㏳': '20日',
'㏴': '21日',
'㏵': '22日',
'㏶': '23日',
'㏷': '24日',
'㏸': '25日',
'㏹': '26日',
'㏺': '27日',
'㏻': '28日',
'㏼': '29日',
'㏽': '30日',
'㏾': '31日',
'㍘': '0点',
'㍙': '1点',
'㍚': '2点',
'㍛': '3点',
'㍜': '4点',
'㍝': '5点',
'㍞': '6点',
'㍟': '7点',
'㍠': '8点',
'㍡': '9点',
'㍢': '10点',
'㍣': '11点',
'㍤': '12点',
'㍥': '13点',
'㍦': '14点',
'㍧': '15点',
'㍨': '16点',
'㍩': '17点',
'㍪': '18点',
'㍫': '19点',
'㍬': '20点',
'㍭': '21点',
'㍮': '22点',
'㍯': '23点',
'㍰': '24点',
'㍻': '平成',
'㍼': '昭和',
'㍽': '大正',
'㍾': '明治',
'㍿': '株式会社',
'㌀': 'アパート',
'㌁': 'アルファ',
'㌂': 'アンペア',
'㌃': 'アール',
'㌄': 'イニング',
'㌅': 'インチ',
'㌆': 'ウオン',
'㌇': 'エスクード',
'㌈': 'エーカー',
'㌉': 'オンス',
'㌊': 'オーム',
'㌋': 'カイリ',
'㌌': 'カラット',
'㌍': 'カロリー',
'㌎': 'ガロン',
'㌏': 'ガンマ',
'㌐': 'ギガ',
'㌑': 'ギニー',
'㌒': 'キュリー',
'㌓': 'ギルダー',
'㌔': 'キロ',
'㌕': 'キログラム',
'㌖': 'キロメートル',
'㌗': 'キロワット',
'㌘': 'グラム',
'㌙': 'グラムトン',
'㌚': 'クルゼイロ',
'㌛': 'クローネ',
'㌜': 'ケース',
'㌝': 'コルナ',
'㌞': 'コーポ',
'㌟': 'サイクル',
'㌠': 'サンチーム',
'㌡': 'シリング',
'㌢': 'センチ',
'㌣': 'セント',
'㌤': 'ダース',
'㌥': 'デシ',
'㌦': 'ドル',
'㌧': 'トン',
'㌨': 'ナノ',
'㌩': 'ノット',
'㌪': 'ハイツ',
'㌫': 'パーセント',
'㌬': 'パーツ',
'㌭': 'バーレル',
'㌮': 'ピアストル',
'㌯': 'ピクル',
'㌰': 'ピコ',
'㌱': 'ビル',
'㌲': 'ファラッド',
'㌳': 'フィート',
'㌴': 'ブッシェル',
'㌵': 'フラン',
'㌶': 'ヘクタール',
'㌷': 'ペソ',
'㌸': 'ペニヒ',
'㌹': 'ヘルツ',
'㌺': 'ペンス',
'㌻': 'ページ',
'㌼': 'ベータ',
'㌽': 'ポイント',
'㌾': 'ボルト',
'㌿': 'ホン',
'㍀': 'ポンド',
'㍁': 'ホール',
'㍂': 'ホーン',
'㍃': 'マイクロ',
'㍄': 'マイル',
'㍅': 'マッハ',
'㍆': 'マルク',
'㍇': 'マンション',
'㍈': 'ミクロン',
'㍉': 'ミリ',
'㍊': 'ミリバール',
'㍋': 'メガ',
'㍌': 'メガトン',
'㍍': 'メートル',
'㍎': 'ヤード',
'㍏': 'ヤール',
'㍐': 'ユアン',
'㍑': 'リットル',
'㍒': 'リラ',
'㍓': 'ルピー',
'㍔': 'ルーブル',
'㍕': 'レム',
'㍖': 'レントゲン',
'㍗': 'ワット',
};
const fixCompositeSymbols = replacer(fixCompositeSymbolsTable);
const converters = {
fullwidthToHalfwidth: {
alphabet: replacer(conversionTables.fullwidthToHalfwidth.alphabet),
numbers: replacer(conversionTables.fullwidthToHalfwidth.numbers),
symbol: replacer(conversionTables.fullwidthToHalfwidth.symbol),
purePunctuation: replacer(conversionTables.fullwidthToHalfwidth.purePunctuation),
punctuation: replacer(conversionTables.fullwidthToHalfwidth.punctuation),
katakana: replacer(conversionTables.fullwidthToHalfwidth.katakana),
},
halfwidthToFullwidth: {
alphabet: replacer(conversionTables.halfwidthToFullwidth.alphabet),
numbers: replacer(conversionTables.halfwidthToFullwidth.numbers),
symbol: replacer(conversionTables.halfwidthToFullwidth.symbol),
purePunctuation: replacer(conversionTables.halfwidthToFullwidth.purePunctuation),
punctuation: replacer(conversionTables.halfwidthToFullwidth.punctuation),
katakana: replacer(conversionTables.halfwidthToFullwidth.katakana),
},
fixFullwidthKana: replacer(fixFullwidthKana),
normalize: replacer(conversionTables.normalize),
};
class TokenizerJa extends Tokenizer {
constructor(settings) {
super(settings);
this.chartype = [
[/[〇一二三四五六七八九十百千万億兆]/, 'M'],
[/[一-鿌〆]/, 'H'],
[/[ぁ-ゟ]/, 'I'],
[/[゠-ヿ]/, 'K'],
[/[a-zA-Z]/, 'A'],
[/[0-9]/, 'N'],
];
this.bias = -332;
}
ctype(str) {
for (let i = 0, l = this.chartype.length; i < l; i += 1) {
if (str.match(this.chartype[i][0])) {
return this.chartype[i][1];
}
}
return 'O';
}
ts(v) {
return v || 0;
}
removePuncTokens(tokens) {
return tokens
.map(token => token.replace(/[_-・,、;:!?.。()[]{}「」@*\/&#%`^+<=>|~≪≫─$"_\-・,、;:!?.。()[\]{}「」@*/&#%`^+<=>|~«»$"\s]+/g, ''))
.filter(token => token !== '');
}
normalize(srcString) {
let str = srcString.replace(/(..)々々/g, '$1$1').replace(/(.)々/g, '$1$1');
str = converters.normalize(str);
str = converters.fixFullwidthKana(str);
str = fixCompositeSymbols(str);
return str;
}
tokenize(srcText) {
if (!srcText || srcText === '') {
return [];
}
const text = this.normalize(srcText);
const result = [];
const seg = ['B3', 'B2', 'B1'];
const ctype = ['O', 'O', 'O'];
const o = text.split('');
for (let i = 0, l = o.length; i < l; i += 1) {
seg.push(o[i]);
ctype.push(this.ctype(o[i]));
}
seg.push('E1');
seg.push('E2');
seg.push('E3');
ctype.push('O');
ctype.push('O');
ctype.push('O');
let word = seg[3];
let p1 = 'U';
let p2 = 'U';
let p3 = 'U';
for (let i = 4, l = seg.length - 3; i < l; i += 1) {
let score = this.bias;
const w1 = seg[i - 3];
const w2 = seg[i - 2];
const w3 = seg[i - 1];
const w4 = seg[i];
const w5 = seg[i + 1];
const w6 = seg[i + 2];
const c1 = ctype[i - 3];
const c2 = ctype[i - 2];
const c3 = ctype[i - 1];
const c4 = ctype[i];
const c5 = ctype[i + 1];
const c6 = ctype[i + 2];
score += this.ts(JapaneseRules.UP1[p1]);
score += this.ts(JapaneseRules.UP2[p2]);
score += this.ts(JapaneseRules.UP3[p3]);
score += this.ts(JapaneseRules.BP1[p1 + p2]);
score += this.ts(JapaneseRules.BP2[p2 + p3]);
score += this.ts(JapaneseRules.UW1[w1]);
score += this.ts(JapaneseRules.UW2[w2]);
score += this.ts(JapaneseRules.UW3[w3]);
score += this.ts(JapaneseRules.UW4[w4]);
score += this.ts(JapaneseRules.UW5[w5]);
score += this.ts(JapaneseRules.UW6[w6]);
score += this.ts(JapaneseRules.BW1[w2 + w3]);
score += this.ts(JapaneseRules.BW2[w3 + w4]);
score += this.ts(JapaneseRules.BW3[w4 + w5]);
score += this.ts(JapaneseRules.TW1[w1 + w2 + w3]);
score += this.ts(JapaneseRules.TW2[w2 + w3 + w4]);
score += this.ts(JapaneseRules.TW3[w3 + w4 + w5]);
score += this.ts(JapaneseRules.TW4[w4 + w5 + w6]);
score += this.ts(JapaneseRules.UC1[c1]);
score += this.ts(JapaneseRules.UC2[c2]);
score += this.ts(JapaneseRules.UC3[c3]);
score += this.ts(JapaneseRules.UC4[c4]);
score += this.ts(JapaneseRules.UC5[c5]);
score += this.ts(JapaneseRules.UC6[c6]);
score += this.ts(JapaneseRules.BC1[c2 + c3]);
score += this.ts(JapaneseRules.BC2[c3 + c4]);
score += this.ts(JapaneseRules.BC3[c4 + c5]);
score += this.ts(JapaneseRules.TC1[c1 + c2 + c3]);
score += this.ts(JapaneseRules.TC2[c2 + c3 + c4]);
score += this.ts(JapaneseRules.TC3[c3 + c4 + c5]);
score += this.ts(JapaneseRules.TC4[c4 + c5 + c6]);
score += this.ts(JapaneseRules.UQ1[p1 + c1]);
score += this.ts(JapaneseRules.UQ2[p2 + c2]);
score += this.ts(JapaneseRules.UQ3[p3 + c3]);
score += this.ts(JapaneseRules.BQ1[p2 + c2 + c3]);
score += this.ts(JapaneseRules.BQ2[p2 + c3 + c4]);
score += this.ts(JapaneseRules.BQ3[p3 + c2 + c3]);
score += this.ts(JapaneseRules.BQ4[p3 + c3 + c4]);
score += this.ts(JapaneseRules.TQ1[p2 + c1 + c2 + c3]);
score += this.ts(JapaneseRules.TQ2[p2 + c2 + c3 + c4]);
score += this.ts(JapaneseRules.TQ3[p3 + c1 + c2 + c3]);
score += this.ts(JapaneseRules.TQ4[p3 + c2 + c3 + c4]);
let p = 'O';
if (score > 0) {
result.push(word);
word = '';
p = 'B';
}
p1 = p2;
p2 = p3;
p3 = p;
word += seg[i];
}
result.push(word);
return this.removePuncTokens(result);
}
}
module.exports = TokenizerJa;