CREATE DICTIONARY ... LAYOUT NaiveBayes
CREATE DICTIONARY ... LAYOUT NaiveBayes
naive_bayes(NAIVE_BAYES) 딕셔너리는 다항 Naive Bayes 모델로 텍스트를 분류해요. 이는 텍스트의 표준 이벤트 모델로, 입력의 n-gram이 각 클래스에 얼마나 자주 나타나는지로 각 클래스를 점수화합니다. 클래스별 n-gram 개수 테이블을 주면, 로드 시 한 번 모델로 컴파일한 뒤 전달하는 모든 텍스트를 분류하는 데 사용해요.
출처: 문서
본문
naive_bayes(NAIVE_BAYES) 딕셔너리는 다항 Naive Bayes 모델로 텍스트를 분류해요. 이는 텍스트의 표준 이벤트 모델입니다. 입력의 n-gram이 각 클래스에 얼마나 자주 나타나는지로 각 클래스를 점수화해요. 클래스별 n-gram 개수 테이블을 주면, 로드 시 한 번 모델로 컴파일한 뒤 전달하는 모든 텍스트를 분류하는 데 사용합니다.
감정 분석, 주제나 스팸 라벨링, 언어 또는 문자 체계 감지 같은 빠르고 가벼운 텍스트 분류에 적합해요.
딕셔너리는 다음 세 가지 함수 중 하나로 쿼리할 수 있어요.
naiveBayesClassifier는 예측된 클래스 id를 반환해요.naiveBayesClassifierWithProb는 예측된 클래스와 그 확률을 반환해요.naiveBayesClassifierWithAllProbs는 확률이 붙은 모든 클래스를 반환해요.
일반 dictGet도 분류를 수행해요(Notes 참조). naiveBayesNgrams라는 또 다른 함수는 분류하지 않고, 딕셔너리가 하는 것과 같은 방식으로 텍스트를 n-gram으로 나누어 원시 텍스트에서 훈련 데이터를 만들 수 있게 해줘요(Build training data from raw text 참조).
Quickstart
여기서는 감정 분석을 위한 토큰 모드, 유니그램(n = 1) 모델을 만듭니다.
- 클래스별 n-gram 개수의 소스 테이블을 만들어요.
CREATE TABLE training_data (class_id UInt32, ngram String, count UInt64)
ENGINE = MergeTree ORDER BY (class_id, ngram);
- 훈련 데이터를 삽입한다 — 단어(유니그램)와 그것이 긍정(
1) 및 부정(0) 클래스에서 각각 얼마나 자주 나타나는지:
INSERT INTO training_data VALUES
(1,'good',10),(1,'great',8),(1,'excellent',6),(1,'love',7),(1,'happy',5),
(1,'amazing',4),(1,'wonderful',3),(1,'best',3),(1,'fantastic',2),(1,'nice',4),
(0,'bad',10),(0,'terrible',8),(0,'awful',6),(0,'hate',7),(0,'worst',5),
(0,'horrible',4),(0,'poor',3),(0,'disappointing',3),(0,'ugly',2),(0,'sad',4);
NAIVE_BAYES레이아웃으로 딕셔너리를 만들어요.
CREATE DICTIONARY sentiment (ngram String, class_id UInt32, count UInt64)
PRIMARY KEY ngram
SOURCE(CLICKHOUSE(TABLE 'training_data'))
LAYOUT(NAIVE_BAYES(class_attribute 'class_id' n 1 mode 'token'))
LIFETIME(0);
PRIMARY KEY ngram은 ngram 컬럼을 키로 만드는데, NAIVE_BAYES 딕셔너리의 경우 이 "키"는 조회하는 저장 값이 아니라 분류하려고 전달하는 텍스트예요(Dictionary structure 참조). LAYOUT은 모델을 구성합니다. class_attribute 'class_id'는 class_id를 클래스 라벨로 표시하고(따라서 다른 속성인 count가 클래스별 발생 개수가 됨), n 1은 유니그램을 사용하며, mode 'token'은 텍스트를 공백으로 구분된 단어로 나눕니다(Layout parameters 참조).
- 분류한다 —
naiveBayesClassifier는 클래스 id를 반환해요.
SELECT naiveBayesClassifier('sentiment', 'this is great') as predicted_class;
┌─predicted_class─┐
1. │ 1 │
└─────────────────┘
1은 2단계에서 삽입한 훈련 데이터에 기반해 긍정 클래스에 대응해요.
SELECT naiveBayesClassifier('sentiment', 'this is terrible') as predicted_class;
┌─predicted_class─┐
1. │ 0 │
└─────────────────┘
마찬가지로 0은 부정 클래스에 대응해요.
dictGet을 통한 동일한 결과:
SELECT dictGet('sentiment', 'class_id', 'this is great') as predicted_class;
┌─predicted_class─┐
1. │ 1 │
└─────────────────┘
예측의 확률 또는 모든 클래스의 확률을 얻으세요.
SELECT naiveBayesClassifierWithProb('sentiment', 'amazing food but terrible service') as predicted_id_with_prob;
┌─predicted_id_with_prob─────────────┐
1. │ { ↴│
│↳ "class_id": 0, ↴│
│↳ "probability": 0.642857145060626↴│
│↳} │
└────────────────────────────────────┘
예측은 확률 0.64의 클래스 0(부정)이에요.
SELECT naiveBayesClassifierWithAllProbs('sentiment', 'amazing food but terrible service') as all_predicted_ids_with_probs;
┌─all_predicted_ids_with_probs─────────┐
1. │ [{ ↴│
│↳ "class_id": 0, ↴│
│↳ "probability": 0.642857145060626 ↴│
│↳},{ ↴│
│↳ "class_id": 1, ↴│
│↳ "probability": 0.35714285493937414↴│
│↳}] │
└──────────────────────────────────────┘
naiveBayesClassifierWithAllProbs는 모든 클래스를 가장 가능성 높은 것부터 가장 낮은 것 순으로, 합이 1.0이 되는 확률과 함께 반환합니다. 여기서는 부정 클래스가 0.64, 긍정 클래스가 0.36이에요.
How it works
훈련(로드 시). 각 소스 행은 (n-gram, class, count) 관측치예요. 딕셔너리가 로드될 때 행은 한 번 모델로 컴파일됩니다. 중복된 (n-gram, class) 행은 합산되고, count = 0인 행은 무시돼요.
분류(쿼리 시). 문자열을 분류하기 위해 모델은:
mode와n에 따라 문자열을 n-gram으로 나눠요(Tokenization modes 참조).- 클래스 사전(prior)과 입력의 n-gram이 그 클래스에서 얼마나 자주 보였는지를 결합해 각 클래스를 점수화해요.
- 점수로 클래스를 순위화한다. 최고 점수 클래스는
naiveBayesClassifier가 반환하는 예측이에요.naiveBayesClassifierWithProb와naiveBayesClassifierWithAllProbs는 해당 클래스 또는 모든 클래스에 대한 확률도 반환합니다.
각 클래스의 점수에 영향을 주는 두 가지가 있어요. 첫 번째는 스무딩(smoothing)에 사용되는 alpha예요. 스무딩은 한 n-gram이 훈련 중 그 클래스에 나타나지 않았다는 이유만으로 모델이 클래스에 0점을 주는 것을 방지합니다. 더 작은 alpha는 모델이 훈련 데이터를 더 신뢰하게 하므로 한 클래스가 다른 클래스보다 훨씬 높은 점수를 받을 수 있지만, 훈련 데이터가 작거나 고르지 않을 때 모델이 너무 민감해질 수도 있어요. 더 큰 alpha는 n-gram 개수의 중요성을 낮춰 서로 다른 클래스의 점수가 더 비슷해집니다. alpha가 매우 크면 n-gram 정보가 거의 중요하지 않고 점수는 주로 클래스 사전(다음에 설명)에 의해 결정됩니다.
두 번째는 클래스 사전(prior)이에요. 이것은 모델이 텍스트를 보기 전에 각 클래스가 얼마나 가능성 있는지 가정하는 것입니다. 각 클래스가 n-gram을 고려하기 전에 받는 시작 점수 역할을 하므로, 사전이 높을수록 클래스가 예측될 가능성이 커져요. 이것이 설정되는 방식은 priors_mode에 달려 있어요. 기본적으로(proportional) 훈련 데이터에서 총 n-gram 개수가 더 큰 클래스가 더 높은 점수로 시작합니다. uniform으로 하면 모든 클래스가 동일하게 시작하므로 n-gram만 결정해요. explicit으로 하면 각 클래스의 시작점을 직접 설정합니다. Prior modes를 참고하세요.
훈련 데이터 어디에서도 본 적 없는 n-gram은 무시됩니다. 모델 어휘의 일부가 아니므로 어떤 클래스도 돕거나 해치지 않아요.
이 알고리즘은 텍스트 분류를 위한 다항 Naive Bayes 모델을 따릅니다. Manning, Raghavan & Schütze, Introduction to Information Retrieval, ch. 13 (Text Classification and Naive Bayes)를 참고하세요.
Dictionary structure
NAIVE_BAYES 딕셔너리는 고정된 형태를 가져요.
PRIMARY KEY는 단일String컬럼 — n-gram이에요. 쿼리 시 이 "키"는 조회 키가 아니라 분류하려고 전달하는 텍스트예요.- 그 옆에 정확히 두 개의 부호 없는 정수 속성을 선언한다: 클래스 라벨과 발생 개수. 클래스 id는 내부적으로 항상
UInt32를 사용하므로, 속성을UInt64로 선언해도 클래스 라벨은UInt32에 맞아야 해요(최대4294967295). 더 큰 값은 만들 때가 아니라 딕셔너리가 로드될 때 거부됩니다. 선언된 타입에도 동일하게 적용됩니다. 선언된 속성 타입에 맞지 않는 소스 클래스 id나 개수는 조용히 잘리는 대신 로드가 실패해요. class_attribute레이아웃 파라미터는 어떤 속성이 클래스 라벨인지 지정하고, 다른 하나는 자동으로 개수가 돼요. 두 속성은 어떤 순서로든 선언할 수 있어요.
소스 테이블은 사전 집계된 개수를 담습니다. (n-gram, class)당 한 행에 그 n-gram이 그 클래스에서 나타난 횟수가 담겨 있어요. 이런 개수는 자체 훈련 파이프라인에서 또는 raw 라벨 텍스트에서 ClickHouse로 코퍼스를 토큰화하고 결과를 그룹화하여 만들어요(Build training data from raw text 참조). 딕셔너리는 그것만 소비합니다.
모델 업데이트. 모델은 테이블로 뒷받침되는 딕셔너리이므로, 테이블을 업데이트하고 다시 로드하면 재훈련됩니다.
INSERT INTO training_data VALUES (1, 'awesome', 5);
SYSTEM RELOAD DICTIONARY sentiment;
Layout parameters
| Parameter | Description | Example | Default |
|---|---|---|---|
class_attribute |
클래스 라벨을 담는 속성의 이름; 다른 속성은 개수예요. | 'class_id' |
Required |
n |
N-gram 크기: 1 = 유니그램, 2 = 바이그램, 3 = 트라이그램, … (1–1024). |
2 |
Required |
mode |
토큰화 방법: byte, codepoint, 또는 token. Tokenization modes 참조. |
'token' |
Required |
alpha |
n-gram 우도에 대한 가산(Lidstone) 스무딩; alpha = 1은 Laplace 스무딩이에요(유한하고 > 0이어야 함). |
0.5 |
1.0 |
priors_mode |
클래스 사전이 결정되는 방식: uniform, proportional, 또는 explicit. Prior modes 참조. |
'uniform' |
'proportional' |
priors |
명시적 클래스별 사전: (class, probability) 쌍의 컬렉션이에요. priors_mode 'explicit'에서만 유효하며 그때는 필수입니다. 다른 모드에서 제공하면 오류예요. 합이 1.0이어야 해요. |
[(0, 0.6), (1, 0.4)] |
— |
store_source |
SELECT * FROM dictionary가 동작하도록 소스 행을 보유해요. 메모리가 대략 두 배가 됩니다. |
1 |
0 |
start_token |
입력 앞에 (n-1)번 붙는 경계 토큰이에요. Boundary tokens 참조. |
'0x01' / '<s>' |
— (no padding) |
end_token |
입력 끝에 (n-1)번 붙는 경계 토큰이에요. |
'0xFF' / '</s>' |
— (no padding) |
딕셔너리는 CREATE DICTIONARY DDL(위의 quickstart처럼) 또는 XML 구성 파일로 정의할 수 있어요. 파일이 어디에 있는지는 Dictionary layouts를 참고하세요. 아래 예시는 모든 레이아웃 옵션을 설정해 모두 볼 수 있게 합니다 — class_attribute, n, mode만 필수이고 나머지는 위 테이블이 기본값을 제공해요. 구성 파일에서 사전은 반복되는 prior 요소로 작성되고(아래와 같이 클래스당 하나), byte 및 codepoint의 패딩 토큰은 숫자이며(구성이 raw byte를 담지 못함), token 리터럴은 필요할 때 XML 이스케이프되므로 <s>는 <s>가 됩니다.
- DDL
- Configuration file
CREATE DICTIONARY naive_bayes (ngram String, class_id UInt32, count UInt64)
PRIMARY KEY ngram
SOURCE(CLICKHOUSE(TABLE 'training_data'))
LAYOUT(NAIVE_BAYES(
class_attribute 'class_id'
n 2
mode 'token'
alpha 0.5
priors_mode 'explicit'
priors [(0, 0.6), (1, 0.4)]
store_source 1
start_token '<s>'
end_token '</s>'
))
LIFETIME(3600);
<dictionary>
<name>naive_bayes</name>
<structure>
<key>
<attribute>
<name>ngram</name>
<type>String</type>
</attribute>
</key>
<attribute>
<name>class_id</name>
<type>UInt32</type>
<null_value>0</null_value>
</attribute>
<attribute>
<name>count</name>
<type>UInt64</type>
<null_value>0</null_value>
</attribute>
</structure>
<source>
<clickhouse>
<table>training_data</table>
</clickhouse>
</source>
<layout>
<naive_bayes>
<class_attribute>class_id</class_attribute>
<n>2</n>
<mode>token</mode>
<alpha>0.5</alpha>
<priors_mode>explicit</priors_mode>
<priors>
<prior>
<class>0</class>
<probability>0.6</probability>
</prior>
<prior>
<class>1</class>
<probability>0.4</probability>
</prior>
</priors>
<store_source>1</store_source>
<start_token><s></start_token>
<end_token></s></end_token>
</naive_bayes>
</layout>
<lifetime>3600</lifetime>
</dictionary>
Tokenization modes
mode는 "토큰"이 무엇인지를 결정하며, 따라서 n-gram이 어떻게 생겼는지도 결정해요. 소스 n-gram은 같은 mode와 n으로 생성되었어야 합니다.
byte— 각 토큰은 단일 바이트예요. UTF-8을 가정하지 않아요.n = 2이면'abc'는 바이트 바이그램'ab','bc'를 만듭니다. 임의 바이트 시퀀스에 대한 언어 또는 인코딩 감지와, 서브문자 신호가 중요한 모든 데이터에 좋아요. 보통n >= 2와 함께 사용됩니다.codepoint— 각 토큰은 하나의 유니코드 코드 포인트예요. 입력은 UTF-8로 해석됩니다.n = 1이면'café'는 코드 포인트'c','a','f','é'를 만듭니다. 문자 체계와 언어 감지, 그리고 공백 단어 경계가 신뢰할 수 없는 짧거나 CJK 텍스트에 좋아요. (소스 n-gram은 유효한 UTF-8이어야 하고, 쿼리 입력은 관대하게 디코딩됩니다 — Notes 참조.)token— 각 토큰은 ASCII 공백(공백, 탭, 줄바꿈, 캐리지 리턴, 폼 피드, 수직 탭; 연속은 하나의 구분자로 축소됨)으로 구분된 단어예요.U+00A0(노브레이크 공백) 또는U+2003(엠 공백) 같은 비-ASCII 유니코드 공백은 구분자가 아니고 토큰 안에 남아요. 공백만이 나누며, 소문자화나 제거는 없습니다. 따라서'Hello, World!'는 토큰'Hello,'와'World!'가 되고(쉼표,!, 대문자가 모두 유지됨),n = 2이면 단일 바이그램'Hello, World!'을 형성합니다. 공백으로 구분된 언어에 대한 단어 수준 분류 — 감정, 주제, 스팸, 문장의 언어 — 에 좋아요.
Prior modes
사전(prior)은 모델이 텍스트를 보기 전에 각 클래스에 대해 가지는 믿음이에요. priors_mode는 그것이 설정되는 방식을 선택합니다.
proportional(기본값) — 각 클래스의 사전은 훈련 데이터의 총 n-gram 개수 — 행 수나 훈련 문서 수가 아니라 해당 클래스의count컬럼 합 — 에 비례해요. 따라서 더 자주 보이는 클래스가 더 가능성 높게 시작합니다. 훈련 클래스 비율(총 n-gram 개수 기준)이 쿼리 시 예상하는 빈도와 일치할 때 선택하세요. 제공할 것이 없습니다 — 소스 개수에서 파생됩니다.
LAYOUT(NAIVE_BAYES(class_attribute 'class_id' n 1 mode 'token' priors_mode 'proportional'))
uniform— 모든 클래스가 처음에 동일하게 가능성 있어서 어떤 클래스도 선두를 점하지 않고, 예측은 전적으로 입력의 n-gram에서 나와요. 클래스가 균형 잡혀 있거나, 훈련 빈도가 쿼리 시 각 클래스가 나타나는 빈도를 반영하지 않을 때 선택하세요. 제공할 것이 없습니다.
LAYOUT(NAIVE_BAYES(class_attribute 'class_id' n 1 mode 'token' priors_mode 'uniform'))
explicit—priors [(0, 0.6), (1, 0.4)]로 사전을 직접 제공해요. 클래스당 하나의(class, probability)쌍으로, 각 확률은 0보다 크고 최대 1이며, 함께 합이1.0이 되어야 해요. 실제 기본 비율을 알고 있고 그것이 훈련과 다를 때 — 예를 들어 훈련 세트가 균형 잡혔는데도 프로덕션 트래픽의 1%만 스팸인 경우 — 선택하세요. 각 클래스의 예상 실제 세계 비율로 계산하세요.
LAYOUT(NAIVE_BAYES(class_attribute 'class_id' n 1 mode 'token' priors_mode 'explicit' priors [(0, 0.9), (1, 0.1)]))
Boundary tokens (padding)
패딩은 기본적으로 꺼져 있어요. n > 1에서만 의미가 있으며, 모델이 텍스트의 시작과 끝의 신호를 사용하게 해 정확도를 높일 수 있어요.
왜 도움이 되는가. n > 1에서 텍스트 중간의 n-gram은 완전한 좌우 문맥을 얻지만, 첫 번째와 마지막 토큰은 그렇지 못해요. 경계 토큰을 추가하면 "텍스트 시작"과 "텍스트 끝"을 표시하는 n-gram이 만들어져, 모델이 위치와 묶인 패턴을 학습할 수 있어요 — 예를 들어 메시지를 시작할 때 두드러지는 단어나 단어 끝에 흔한 문자 같은 것이요.
해야 할 일:
- 쪽당 결정한다.
start_token과end_token은 독립적이에요 — 하나, 둘 다, 또는 둘 다 아니게 설정하세요. 빈 값은 그 쪽이 패딩되지 않는다는 뜻이에요. - 실제 데이터와 충돌하지 않을 희귀한 값을 선택한다, 예:
byte의 경우0x01/0xFF,codepoint의 경우U+10FFFE/U+10FFFF,token의 경우<s>/</s>. - 같은 패딩으로 훈련 n-gram을 만든다. 딕셔너리는 쿼리 입력을 패딩하지만 소스는 절대 패딩하지 않으므로, 경계 토큰은 로드하는 n-gram에 이미 녹아 있어야 해요. 일치를 보장하는 가장 쉬운 방법은 레이아웃에 주는 것과 같은
start_token과end_token(그리고n과mode)을 넘겨naiveBayesNgrams로 소스를 만드는 것입니다.
패딩 토큰 형식은 모드에 따라 달라져요.
byte— 바이트 값의 숫자, 10진수 또는0x16진수로('1'과'0x01'은 같음):
LAYOUT(NAIVE_BAYES(class_attribute 'class_id' n 2 mode 'byte' start_token '0x01' end_token '0xFF'))
codepoint— UTF-8 코드 포인트의 숫자, 10진수 또는0x16진수로('1114110'과'0x10FFFE'는 같음):
LAYOUT(NAIVE_BAYES(class_attribute 'class_id' n 2 mode 'codepoint' start_token '0x10FFFE' end_token '0x10FFFF'))
token— 리터럴 토큰 문자열:
LAYOUT(NAIVE_BAYES(class_attribute 'class_id' n 2 mode 'token' start_token '<s>' end_token '</s>'))
Build training data from raw text
사전 집계된 개수가 아닌 raw 라벨 텍스트에서 시작한다면, naiveBayesNgrams 함수로 텍스트를 n-gram으로 나누세요. 레이아웃과 같은 n, mode, start_token, end_token을 주면, 딕셔너리가 기대하는 바로 그 n-gram을 생성하므로 훈련 데이터가 쿼리 시 모델이 보는 것과 일치합니다.
(class_id, text) 행 테이블이 주어지면, 하나의 GROUP BY로 (ngram, class_id, count) 소스를 만드세요.
CREATE TABLE docs (class_id UInt32, text String) ENGINE = MergeTree ORDER BY tuple();
INSERT INTO docs VALUES
(1, 'The food was amazing and the service was great'),
(0, 'The service was terrible and the food was awful'),
(1, 'I loved this cozy little place and the friendly staff'),
(0, 'I hated the bad weather and the long wait'),
(1, 'Best dinner we have had here, everything was delicious');
CREATE TABLE training_data (ngram String, class_id UInt32, count UInt64)
ENGINE = MergeTree ORDER BY (class_id, ngram);
INSERT INTO training_data
SELECT ngram, class_id, count()
FROM docs
ARRAY JOIN naiveBayesNgrams(text, 1, 'token') AS ngram
GROUP BY ngram, class_id;
SELECT * FROM training_data ORDER BY ngram LIMIT 5;
┌─ngram─┬─class_id─┬─count─┐
1. │ Best │ 1 │ 1 │
2. │ I │ 1 │ 1 │
3. │ I │ 0 │ 1 │
4. │ The │ 0 │ 1 │
5. │ The │ 1 │ 1 │
└───────┴──────────┴───────┘
이제 training_data는 NAIVE_BAYES 딕셔너리의 유효한 소스입니다(여기서는 토큰 유니그램; n과 mode 인자를 레이아웃에 맞게 바꾸세요). 딕셔너리는 쿼리 입력을 주어진 그대로 토큰화하므로, 훈련 텍스트가 소문자화되었는데 쿼리 텍스트가 아니라면 n-gram이 일치하지 않고 모델 정확도가 떨어질 수 있어요.
Priors and document counts
proportional 사전(기본값)은 문서 수가 아니라 각 클래스의 총 n-gram 개수로 가중됩니다. 고전적인 문서 빈도 사전(documents_in_class / total_documents)을 원한다면 raw docs 테이블에서 계산하고 priors_mode 'explicit'로 전달하세요.
SELECT groupArray((class_id, frac)) AS priors
FROM (SELECT class_id, count() / sum(count()) OVER () AS frac FROM docs GROUP BY class_id);
┌─priors────────────┐
1. │ [(0,0.4),(1,0.6)] │
└───────────────────┘
그런 다음 위에서 계산한 명시적 사전을 전달해 training_data로 딕셔너리를 만들고, 새 리뷰를 분류해요.
CREATE DICTIONARY review_sentiment (ngram String, class_id UInt32, count UInt64)
PRIMARY KEY ngram
SOURCE(CLICKHOUSE(TABLE 'training_data'))
LAYOUT(NAIVE_BAYES(class_attribute 'class_id' n 1 mode 'token' priors_mode 'explicit' priors [(0, 0.4), (1, 0.6)]))
LIFETIME(0);
SELECT
naiveBayesClassifier('review_sentiment', 'amazing food and friendly staff') AS positive_review,
naiveBayesClassifier('review_sentiment', 'awful service and a terrible meal') AS negative_review;
┌─positive_review─┬─negative_review─┐
1. │ 1 │ 0 │
└─────────────────┴─────────────────┘
클래스 1은 긍정이고 0은 부정이므로, 두 리뷰 모두 올바르게 분류됩니다.
More examples
바이트 모드 — 바이트 바이그램(n = 2, mode 'byte'; 클래스 0 = 문자 a–d의 문자열, 클래스 1 = 문자 x–z):
CREATE TABLE byte_patterns_src (class_id UInt32, ngram String, count UInt64) ENGINE = MergeTree ORDER BY (class_id, ngram);
INSERT INTO byte_patterns_src VALUES (0,'ab',5),(0,'bc',5),(0,'cd',5),(1,'xy',5),(1,'yz',5),(1,'zw',5);
CREATE DICTIONARY byte_patterns (ngram String, class_id UInt32, count UInt64)
PRIMARY KEY ngram SOURCE(CLICKHOUSE(TABLE 'byte_patterns_src'))
LAYOUT(NAIVE_BAYES(class_attribute 'class_id' n 2 mode 'byte')) LIFETIME(0);
SELECT naiveBayesClassifier('byte_patterns', 'abcd') AS abcd, naiveBayesClassifier('byte_patterns', 'xyzw') AS xyzw;
┌─abcd─┬─xyzw─┐
1. │ 0 │ 1 │
└──────┴──────┘
코드 포인트 모드 — 문자별 문자 체계 감지(n = 1, mode 'codepoint'; 클래스 0 = 라틴, 1 = 키릴):
CREATE TABLE script_src (class_id UInt32, ngram String, count UInt64) ENGINE = MergeTree ORDER BY (class_id, ngram);
INSERT INTO script_src VALUES (0,'a',5),(0,'b',5),(0,'c',5),(0,'d',5),(1,'а',5),(1,'б',5),(1,'в',5),(1,'г',5);
CREATE DICTIONARY script (ngram String, class_id UInt32, count UInt64)
PRIMARY KEY ngram SOURCE(CLICKHOUSE(TABLE 'script_src'))
LAYOUT(NAIVE_BAYES(class_attribute 'class_id' n 1 mode 'codepoint')) LIFETIME(0);
SELECT naiveBayesClassifier('script', 'abcd') AS latin, naiveBayesClassifier('script', 'абвг') AS cyrillic;
┌─latin─┬─cyrillic─┐
1. │ 0 │ 1 │
└───────┴──────────┘
store_source로 훈련 데이터를 다시 읽기:
CREATE TABLE stored_src (class_id UInt32, ngram String, count UInt64) ENGINE = MergeTree ORDER BY (class_id, ngram);
INSERT INTO stored_src VALUES (0,'alpha',3),(0,'beta',2),(1,'gamma',4);
CREATE DICTIONARY stored (ngram String, class_id UInt32, count UInt64)
PRIMARY KEY ngram SOURCE(CLICKHOUSE(TABLE 'stored_src'))
LAYOUT(NAIVE_BAYES(class_attribute 'class_id' n 1 mode 'token' store_source 1)) LIFETIME(0);
SELECT ngram, class_id, count FROM stored ORDER BY ngram;
┌─ngram─┬─class_id─┬─count─┐
1. │ alpha │ 0 │ 3 │
2. │ beta │ 0 │ 2 │
3. │ gamma │ 1 │ 4 │
└───────┴──────────┴───────┘
raw 텍스트에서의 언어 감지 — 경계 패딩이 있는 짧은 단어(n = 2, mode 'codepoint'; 클래스 0 = 영어, 1 = 스페인어). 훈련 n-gram은 naiveBayesNgrams로 raw 단어에서 만들어지고, 함수와 레이아웃 양쪽에 전달되는 경계 토큰은 모델이 각 단어의 첫 글자와 마지막 글자를 사용하게 합니다:
CREATE TABLE words (class_id UInt32, text String) ENGINE = MergeTree ORDER BY tuple();
INSERT INTO words VALUES
(0,'dog'),(0,'cat'),(0,'fish'),(0,'bird'),(0,'book'),(0,'hand'),(0,'tree'),(0,'milk'),(0,'duck'),(0,'frog'),(0,'lamp'),(0,'desk'),
(1,'gato'),(1,'casa'),(1,'perro'),(1,'libro'),(1,'mano'),(1,'leche'),(1,'arbol'),(1,'agua'),(1,'queso'),(1,'fuego'),(1,'mesa'),(1,'silla');
CREATE TABLE word_ngrams (ngram String, class_id UInt32, count UInt64) ENGINE = MergeTree ORDER BY (class_id, ngram);
INSERT INTO word_ngrams
SELECT ngram, class_id, count()
FROM words
ARRAY JOIN naiveBayesNgrams(text, 2, 'codepoint', '0x10FFFE', '0x10FFFF') AS ngram
GROUP BY ngram, class_id;
CREATE DICTIONARY lang (ngram String, class_id UInt32, count UInt64)
PRIMARY KEY ngram SOURCE(CLICKHOUSE(TABLE 'word_ngrams'))
LAYOUT(NAIVE_BAYES(class_attribute 'class_id' n 2 mode 'codepoint' start_token '0x10FFFE' end_token '0x10FFFF')) LIFETIME(0);
SELECT naiveBayesClassifier('lang', 'window') AS window, naiveBayesClassifier('lang', 'fiesta') AS fiesta;
┌─window─┬─fiesta─┐
1. │ 0 │ 1 │
└────────┴────────┘
Notes
- 컴퓨테이셔널 딕셔너리 의미론. 이것은 컴퓨테이셔널 딕셔너리예요.
dictGet(dict, '<class_attribute>', text)는text를 분류하고(키는 조회 키가 아닌 분류 입력), count 속성은 쿼리할 수 없으며,dictHas는 항상1을 반환해요. - 로드 시 소스 검증. 모든 소스 n-gram은 설정된
n과mode와 일치해야 해요(codepoint모드에서는 유효한 UTF-8이어야 함). 불일치는 로드를 실패시킵니다. 0개수 행은 무시되므로(How it works 참조), 소스가 비어 있거나 0 개수만 있으면 훈련할 것이 없어 로드에 실패해요. - 쿼리 시 토큰화는 관대합니다. 소스 검증과 달리 쿼리 입력은 절대 거부되지 않아요.
codepoint모드에서 유효한 UTF-8이 아닌 바이트는 쿼리를 실패시키는 대신 최선 노력(best-effort)으로 디코딩됩니다.token모드에서 오직 ASCII 공백만 단어를 구분합니다(U+00A0같은 유니코드 공백은 토큰 안에 남음). 잘못된 입력도 여전히 분류됩니다 — 보통 사전에서 오는데, 그 n-gram이 훈련된 것과 일치하지 않기 때문이에요.