๐ฐ๐ท ํ๊ตญ์ด ๋ถ์ฉ์ด/์กฐ์ฌ/์ด๋ฏธ ์ฒ๋ฆฌ ์ ํธ ์๊ฐ
๐ฐ๐ท ํ๊ตญ์ด ๋ถ์ฉ์ด/์กฐ์ฌ/์ด๋ฏธ ์ฒ๋ฆฌ ์ ํธ ์๊ฐ
ํ๊ตญ์ด ํ
์คํธ ๋ง์ด๋์ด๋ NLP ์ ์ฒ๋ฆฌ๋ฅผ ํ๋ค ๋ณด๋ฉด ๊ผญ ๋ง๋ฅ๋จ๋ฆฌ๋ ๋ฌธ์ ๊ฐ ์์ต๋๋ค.
๋ฐ๋ก ๋ถ์ฉ์ด(stopword)์ ์กฐ์ฌ/์ด๋ฏธ ์ฒ๋ฆฌ ๋ฌธ์ ์
๋๋ค.
์์ด์์๋ ๋จ์ํ a, an, the, of, in โฆ
๊ฐ์ ๋ถ์ฉ์ด๋ฅผ ๊ฑธ๋ฌ๋ด๋ฉด ๋์ง๋ง, ํ๊ตญ์ด๋ ์กฐ์ฌ(์ด/๊ฐ, ์/๋ฅผ, ์/๋ โฆ
)๋ ์ด๋ฏธ(-๋ค, -๋๋ค, -ํ์ด์ โฆ
)๊ฐ ๋จ์ด ๋ค์ ๋ถ๋ ๊ต์ฐฉ ๊ตฌ์กฐ์ด๊ธฐ ๋๋ฌธ์ ์กฐ๊ธ ๋ ์ ๊ตํ ์ฒ๋ฆฌ๊ฐ ํ์ํฉ๋๋ค.
์ด ๊ธ์์๋ ๊ฐ๋จํ ๋ถ์ฉ์ด ์ฌ์ ๊ณผ ์กฐ์ฌ/์ด๋ฏธ ์คํธ๋ฆฌํ ๋ก์ง์ ๊ตฌํํ ํ๊ตญ์ด ๋ถ์ฉ์ด ์ฒ๋ฆฌ ์ ํธ ๋ฅผ ์๊ฐํฉ๋๋ค.
1. ์ ์ฒด ์ฝ๋
์๋ TypeScript ์ ํธ์ ๊ทธ๋๋ก ๊ฐ์ ธ๋ค ์ธ ์ ์์ต๋๋ค.
// eslint-disable
/**
* ํ๊ตญ์ด ๋ถ์ฉ์ด/์กฐ์ฌ/์ด๋ฏธ ์ฒ๋ฆฌ ์ ํธ (v2)
* - ์นดํ
๊ณ ๋ฆฌ๋ณ ๋ฐฐ์ด -> ์ต์ข
Set๋ก ๋ณํฉ
* - ์กฐ์ฌ/์ด๋ฏธ ์คํธ๋ฆฌํ ์ต์
* - ๊ตฌ์ด/ํ์ค ๋์ ์ปค๋ฒ
*/
// 2.1 ์นดํ
๊ณ ๋ฆฌ๋ณ ์์ฒ ๋ชฉ๋ก
const JOSA = [
'์ด','๊ฐ','์','๋ฅผ','์','๋','์','์','์์','์๊ฒ','๊ป','๊ป์',
'๋ก','์ผ๋ก','์','๊ณผ','๋','ํ๊ณ ','๋','๋ง','๋ฟ','๊น์ง','๋ถํฐ',
'๋ง๋ค','๋ง์ ','์กฐ์ฐจ','๋ฐ์','์ด๋','๋','์ด๋๋ง','๋๋ง','๋งํผ','๋๋ก','์ฒ๋ผ','๊ฐ์ด','๋ฟ๋ง',
];
const PRONOUNS = [
'๋','๋','์ ','๊ทธ','์ด','์ฐ๋ฆฌ','๋ํฌ','์ ํฌ',
'๊ทธ๋ค','์ด๋ค','์ ๋ค',
'์ด๊ฒ','๊ทธ๊ฒ','์ ๊ฒ','์ด๊ฑฐ','๊ทธ๊ฑฐ','์ ๊ฑฐ',
'์ฌ๊ธฐ','๊ฑฐ๊ธฐ','์ ๊ธฐ',
'๋๊ตฌ','๋๊ฐ','์๋ฌด','์๋ฌด๋','์๋ฌด๋',
'๊ฑ','์ค','๊ฑ๋ค','์ค๋ค'
];
const CONJUNCTIONS = [
'๊ทธ๋ฆฌ๊ณ ','๋ฐ','๋๋','ํน์','๊ทธ๋ฌ๋','ํ์ง๋ง','๊ทธ๋๋',
'๊ทธ๋ฐ๋ฐ','๊ทผ๋ฐ','๊ทธ๋์','๋ฐ๋ผ์','๊ทธ๋ฌ๋๊น','๊ทธ๋๊น','์ฆ','๋','๋ํ'
];
const FUNCTION_WORDS = [
'๊ฒ','๊ฑฐ','์','๋ฑ','๋ฐ','๋','๊ณณ','์ฌ๋','์ผ','๋ง','์ค','๋ด',
'์ ','๊ฒฝ์ฐ','๋ถ๋ถ','์ํ','์ ๋','์๋ฃ','๋ด์ฉ','๋ฌธ์ ','ํํ',
'๊ด๊ณ','ํ์ฌ','๋น์','์','์์','๋ํ','๊ธฐ๋ณธ','์ผ๋ฐ','๋ํด','๊ด๋ จ',
'์','์๋','์','๋ค','์','๋ฐ','์ผ์ชฝ','์ค๋ฅธ์ชฝ','๊ฐ์ด๋ฐ','์ค๊ฐ','์ฌ์ด'
];
const TEMPORALS = [
'์ค๋','์ด์ ','๋ด์ผ','์ง๊ธ','๋ฐฉ๊ธ','์ต๊ทผ','์์ฆ','ํ์ฌ','์์ ','๊ณผ๊ฑฐ','์ดํ','์ด์ ','์์ผ๋ก','๊ณง'
];
const NUMBERS_NATIVE = ['ํ๋','๋','์
','๋ท','๋ค์ฏ','์ฌ์ฏ','์ผ๊ณฑ','์ฌ๋','์ํ','์ด','์ค๋ฌผ','์๋ฅธ','๋งํ','์ฐ','์์','์ผํ','์ฌ๋ ','์ํ'];
const NUMBERS_SINO = ['์ผ','์ด','์ผ','์ฌ','์ค','์ก','์น ','ํ','๊ตฌ','์ญ','๋ฐฑ','์ฒ','๋ง','์ต','์กฐ'];
const ORDINALS = ['์ฒซ','์ฒซ์งธ','๋์งธ','์
์งธ','๋ท์งธ','๋ค์ฏ์งธ','์ฌ์ฏ์งธ','์ผ๊ณฑ์งธ','์ฌ๋์งธ','์ํ์งธ','์ด์งธ'];
const INTENSIFIERS = ['๋งค์ฐ','๋๋ฌด','์์ฃผ','์ ๋ง','์ง์ง','์ฐธ','๊ฝค','์๋นํ','๊ทธ๋ค์ง','๋ณ๋ก','๊ฐ์ฅ','๋','๋','์ต๊ณ ','์ต๋','์ต์','์ต์ ','ํ๊ท '];
const COPULA_AUX = [
'์ด๋ค','์๋๋ค','์๋ค','์๋ค','๋๋ค','ํ๋ค','๊ฐ๋ค','์ถ๋ค','์ถ์ดํ๋ค'
];
const ADJ_COMMON = [
'์ข์','๋์','์ค์ํ','ํ์ํ','์ ์ฉํ','ํจ๊ณผ์ ์ธ','ํจ์จ์ ์ธ','์ ์ ํ','์ ํฉํ','๋ถ์ ์ ํ','๋ถ์ ํฉํ',
'์๋ก์ด','์ค๋๋','์ต์ ','๊ตฌ์','ํ๋','์ ํต','๊ณ ์ ','ํ๋์ ','์ ํต์ ','๋ณดํต','ํ๋ฒ','ํน๋ณ','ํน์','์ผ๋ฐ'
];
const DETERMINERS = ['๋ชจ๋ ','์ ์ฒด','๊ฐ','๊ฐ๋ณ','ํน์ ','๋ค์ํ','์ฌ๋ฌ','๊ฐ์ข
'];
const CONNECTIVES_NOUN = ['๋๋ฌธ','๋๋ฌธ์','๋๋ถ','๋ฐ๋์','ํตํด','์ํตํด','๋ฅผํตํด'];
const ENDINGS = [
'๋ค','๋๋ค','์ต๋๋ค','ํฉ๋๋ค','ํ๋ค','ํ์๋ค','ํ๋ค๊ฐ','ํ๋ค๋ฉฐ','ํ๋ค','ํ๋ค๋ฉด',
'ํด์','ํ์ด์','ํ์์ด์','ํ์','ํจ','์','์ค','๋์์','๋์์ต๋๋ค'
];
// 2.3 ์ต์
ํ์
export type StopwordOptions = {
includeAdjectives?: boolean;
includeEndingsStrip?: boolean;
includeCopulaAux?: boolean;
extraStopwords?: string[];
excludeStopwords?: string[];
};
// 2.4 ๋ถ์ฉ์ด ์ธํธ ์์ฑ
export function buildKoreanStopwordSet(opts: StopwordOptions = {}) {
const {
includeAdjectives = true,
includeEndingsStrip = true,
includeCopulaAux = true,
extraStopwords = [],
excludeStopwords = [],
} = opts;
const buckets: string[][] = [
JOSA,
PRONOUNS,
CONJUNCTIONS,
FUNCTION_WORDS,
TEMPORALS,
NUMBERS_NATIVE,
NUMBERS_SINO,
ORDINALS,
INTENSIFIERS,
DETERMINERS,
CONNECTIVES_NOUN,
];
if (includeCopulaAux) buckets.push(COPULA_AUX);
if (includeAdjectives) buckets.push(ADJ_COMMON);
if (extraStopwords.length) buckets.push(extraStopwords);
const set = new Set<string>();
for (const group of buckets) {
for (let w of group) {
if (!w) continue;
set.add(w.trim());
if (w === '๊ฒ') set.add('๊ฑฐ');
if (w === '๊ทธ๋ฆฌ๊ณ ') set.add('๊ทธ๋ฆฌ๊ตฌ');
}
}
for (const keep of excludeStopwords) set.delete(keep.trim());
return { set, includeEndingsStrip };
}
// 2.5 ํ
์คํธ ์ ๊ทํ
export function normalizeKo(text: string) {
return text
.normalize('NFKC')
.replace(/[โโโโ]/g, '"')
.replace(/[^\p{L}\p{N}\s]/gu, ' ')
.replace(/\s+/g, ' ')
.trim();
}
// ์กฐ์ฌ/์ด๋ฏธ ํจํด
const JOSA_PATTERN = new RegExp(
`^(.*?)(?:${JOSA.map(x => x.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|')})$`
);
const ENDING_PATTERN = new RegExp(
`^(.*?)(?:${ENDINGS.map(x => x.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|')})$`
);
// 2.6 ์กฐ์ฌ/์ด๋ฏธ ์คํธ๋ฆฌํ
function stripJosa(token: string) {
const m = token.match(JOSA_PATTERN);
return m ? m[1] : token;
}
function stripEnding(token: string) {
const m = token.match(ENDING_PATTERN);
return m ? m : token;
}
// 2.7 ํ ํฌ๋์ด์ฆ + ๋ถ์ฉ์ด ํํฐ
export function tokenizeAndFilterKo(input: string, opts?: StopwordOptions): string[] {
const { set, includeEndingsStrip } = buildKoreanStopwordSet(opts);
const text = normalizeKo(input);
const roughTokens = text.split(' ').filter(Boolean);
const out: string[] = [];
for (let tk of roughTokens) {
if (tk.length === 1 && set.has(tk)) continue;
let stem = stripJosa(tk);
if (includeEndingsStrip) stem = stripEnding(stem);
if (stem === '๊ฑฐ') stem = '๊ฒ';
if (set.has(stem)) continue;
if (stem.length <= 1) continue;
out.push(stem);
}
return out;
}
// 2.8 ์ฌ์ฉ ์์
/*
const example = "์ฐ๋ฆฌ ๋ชจ๋๊ฐ ์ค๋์ ์ ๋ง ์ค์ํ ๋ด์ฉ์ ์์ธํ ๊ทธ๋ฆฌ๊ณ ์ฒ์ฒํ ์ดํด๋ด
์๋ค.";
console.log(tokenizeAndFilterKo(example));
// ์์: ["์ค์", "๋ด์ฉ", "์์ธํ", "์ฒ์ฒํ", "์ดํด๋ณด"]
*/
2. ์ฌ์ฉ๋ฒ
import { tokenizeAndFilterKo } from "./stopwords-ko";
const text = "์ฐ๋ฆฌ ๋ชจ๋๊ฐ ์ค๋์ ์ ๋ง ์ค์ํ ๋ด์ฉ์ ์์ธํ ์ดํด๋ด
์๋ค.";
console.log(tokenizeAndFilterKo(text));
โก๏ธ ์ถ๋ ฅ ์์
["์ค์", "๋ด์ฉ", "์์ธํ", "์ดํด๋ณด"]
3. ์นดํ ๊ณ ๋ฆฌ๋ณ ๋ถ์ฉ์ด ๋ถ๋ฅ
๋ถ์ฉ์ด๋ ๋ชฉ์ ์ ๋ฐ๋ผ ์ ํ์ ์ผ๋ก ์ฌ์ฉ๋ ์ ์๋๋ก ์ฌ๋ฌ ์นดํ ๊ณ ๋ฆฌ๋ก ๋๋์์ต๋๋ค:
- ์กฐ์ฌ(JOSA): "์ด/๊ฐ, ์/๋ฅผ, ์/๋, ์/๊ณผ โฆ"
- ๋๋ช ์ฌ(PRONOUNS): "๋, ๋, ์ฐ๋ฆฌ, ์ด๊ฒ, ์ ๊ฒ, ๋๊ตฌ โฆ"
- ์ ์์ฌ(CONJUNCTIONS): "๊ทธ๋ฆฌ๊ณ , ํ์ง๋ง, ๋ฐ๋ผ์ โฆ"
- ๊ธฐ๋ฅ์ด(FUNCTION_WORDS): "๊ฒ, ์, ๊ฒฝ์ฐ, ๋ฌธ์ , ๊ด๊ณ โฆ"
- ์๊ฐ ํํ(TEMPORALS): "์ค๋, ๋ด์ผ, ์ง๊ธ, ์์ฆ โฆ"
- ์์ฌ(NUMBERS): ๊ณ ์ ์ด/ํ์์ด ์์ฌ ("ํ๋, ๋โฆ / ์ผ, ์ด, ์ผโฆ")
- ์ ๋๋ถ์ฌ(INTENSIFIERS): "๋งค์ฐ, ๋๋ฌด, ์ ๋ง, ๊ฐ์ฅ โฆ"
- ๊ณ์ฌ/๋ณด์กฐ๋์ฌ(COPULA_AUX): "์ด๋ค, ์๋๋ค, ์๋ค, ๋๋ค โฆ"
- ํ์ฉ์ฌ(ADJ_COMMON): "์ค์ํ, ์ ์ฉํ, ์๋ก์ด, ์ค๋๋ โฆ"
- ํ์ ์ฌ(DETERMINERS): "๋ชจ๋ , ๊ฐ, ํน์ , ์ฌ๋ฌ โฆ"
์ต์ ์ ๋ฐ๋ผ ํ์ฉ์ฌ/๋ณด์กฐ๋์ฌ ํฌํจ ์ฌ๋ถ๋ฅผ ๋์ด, ๋๋ฉ์ธ๋ณ ์ปค์คํฐ๋ง์ด์ฆ๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
4. ๋ถ์ฉ์ด ์ธํธ ๋น๋
์ฌ๋ฌ bucket์ ํฉ์ณ์ ์ต์ข
Set<string>
์ผ๋ก ๋ง๋ญ๋๋ค.
const { set, includeEndingsStrip } = buildKoreanStopwordSet({
includeAdjectives: true, // ํ์ฉ์ฌ ํฌํจ ์ฌ๋ถ
includeEndingsStrip: true, // ์ด๋ฏธ ์คํธ๋ฆฌํ ์ฌ๋ถ
includeCopulaAux: true, // ๊ณ์ฌ/๋ณด์กฐ๋์ฌ ์ ๊ฑฐ ์ฌ๋ถ
extraStopwords: ['ํนํ', '๊ทธ๋ฆฌ๊ณ ๋์'], // ์ฌ์ฉ์ ์ถ๊ฐ
excludeStopwords: ['์ค์ํ'] // ํน์ ๋จ์ด ์ ์ธ
});
์ด๋ ๊ฒ ํ๋ฉด ํ์ํ ๋ถ์ฉ์ด๋ง ๊ฑธ๋ฌ๋ด๊ณ ์ค์ํ ํค์๋๋ ๋ณด์กดํ ์ ์์ต๋๋ค.
3. ์กฐ์ฌ/์ด๋ฏธ ์คํธ๋ฆฌํ
์๋ฅผ ๋ค์ด "๋ด์ฉ์"
์ด๋ผ๋ ํ ํฐ์ ๋ฐ์ผ๋ฉด, stripJosa()
๊ฐ "๋ด์ฉ"
๋ง ๋จ๊ธฐ๊ณ ์กฐ์ฌ ์
์ ์ ๊ฑฐํฉ๋๋ค.
๋ง์ฐฌ๊ฐ์ง๋ก "์ดํด๋ด
์๋ค"
๋ stripEnding()
์ ํตํด "์ดํด๋ณด"
๊น์ง ์ค์ด๋ญ๋๋ค.
function stripJosa(token: string) {
const m = token.match(JOSA_PATTERN);
return m ? m[1] : token;
}
5. ๋ฉ์ธ ํ์ดํ๋ผ์ธ
์ต์ข ์ ์ผ๋ก ํ ์คํธ ์ ๋ ฅ์ ๋ฐ์ ํ ํฐํ + ์ ๊ทํ + ๋ถ์ฉ์ด ์ ๊ฑฐ + ์กฐ์ฌ/์ด๋ฏธ ์คํธ๋ฆฌํ์ ์ํํฉ๋๋ค.
export function tokenizeAndFilterKo(input: string, opts?: StopwordOptions): string[] {
const { set, includeEndingsStrip } = buildKoreanStopwordSet(opts);
const text = normalizeKo(input);
const roughTokens = text.split(' ').filter(Boolean);
const out: string[] = [];
for (let tk of roughTokens) {
if (set.has(tk)) continue; // ๋ถ์ฉ์ด ์ ๊ฑฐ
let stem = stripJosa(tk); // ์กฐ์ฌ ์ ๊ฑฐ
if (includeEndingsStrip) stem = stripEnding(stem); // ์ด๋ฏธ ์ ๊ฑฐ
if (stem.length > 1 && !set.has(stem)) out.push(stem);
}
return out;
}
6. ํ์ฉ ํฌ์ธํธ
- ํ๊ตญ์ด ์์ฐ์ด ์ฒ๋ฆฌ(NLP) ์ ์ฒ๋ฆฌ ๋จ๊ณ์์ ๋ฐ๋ก ์ฌ์ฉ ๊ฐ๋ฅ
- ๊ฒ์์์ง ํค์๋ ์ถ์ถ / ํ ํฝ ๋ถ์ / TF-IDF / ์๋ํด๋ผ์ฐ๋ ๋ฑ ํ์ฉ
- ๋ถ์ฉ์ด ๋ฆฌ์คํธ๋ฅผ ๋๋ฉ์ธ(๋ด์ค, ๋ธ๋ก๊ทธ, ๋ฆฌ๋ทฐโฆ) ๋ง๊ฒ ํ์ฅ/์ถ์ ๊ฐ๋ฅ
7. ์ฃผ์์ฌํญ
- ์๋ฒฝํ ํํ์ ๋ถ์๊ธฐ๊ฐ ์๋๋ฏ๋ก, ์ผ๋ถ ๋ณต์กํ ์กฐ์ฌยท์ด๋ฏธ๋ ์ ๊ฑฐ๊ฐ ๋ถ์์ ํ ์ ์์
- "์ค์ํ" โ "์ค์"์ ๊ฐ์ด ์ด๊ทผ ๋ณต์(stemming)์ด ํ์ํ ๊ฒฝ์ฐ๋ ์ถ๊ฐ ์ฒ๋ฆฌ๊ฐ ํ์
- ํ ๊ธ์ ๋จ์ด ์ค
"๊ฒ"
๋ฑ์ ๋ณด์กดํ ์ง ์ฌ๋ถ๋ ์ํฉ์ ๋ง๊ฒ ์กฐ์ ํ์
โ๏ธ ๊ฒฐ๋ก :
์ด ์ ํธ์ ๊ฐ๋ณ๊ฒ ์ ์ฉํ ์ ์๋ ๋ถ์ฉ์ด ํํฐ \& ์กฐ์ฌ/์ด๋ฏธ ์ ๋ฆฌ๊ธฐ์
๋๋ค.
ํ์ฌ ํ๊น
์ด๋ ํํ์ ๋ถ์๊ธฐ๊ฐ ๋ถ๋ด์ค๋ฝ๊ฑฐ๋ ๊ณผํ ๊ฒฝ์ฐ, ๋น ๋ฅด๊ฒ ์ ์ฒ๋ฆฌ ๋จ๊ณ์์ ์ ์ฉํ ์ ์๋ ์ค์ฉ์ ์ธ ๋๊ตฌ๋ผ๊ณ ๋ณด์๋ฉด ๋ฉ๋๋ค.