← AI 놀이터 홈으로

💬 n-gram 언어 모델 (n-gram Language Model)

언어 모델 = 다음에 올 글자의 확률을 맞추는 기계. ChatGPT의 조상님은 그냥 '세서 나누기'로 글을 이어 썼어요.

📖 따라하기 가이드 — 1 / 7 단계

Step 1. ChatGPT가 하는 일은 딱 하나

ChatGPT가 하는 일은 사실 하나뿐이에요 — 다음에 올 말 맞추기. 이것을 하는 프로그램을 언어 모델(Language Model)이라고 해요. 여기서는 가장 원시적인 조상님을 만나요: 짧은 이야기를 읽고 "이 글자 다음엔 어떤 글자가 자주 오더라"를 그냥 세어서 외운 모델이에요. 왼쪽 위가 모델이 이어쓰는 글, 그 아래 막대가 "다음 글자 후보"의 확률, 오른쪽이 조종 장치예요.

📖 모델은 아래 짧은 이야기(약 600자)만 읽고 배웠어요. 그리고 지금부터 그 말투로 글을 이어 씁니다.

📚 학습 코퍼스(이야기) 펼쳐보기

학교에 가는 길에 고양이를 만났어요. 고양이는 나를 보고 야옹 하고 인사했어요. 나는 고양이가 좋아요. 학교에 가는 길에 친구를 만났어요. 친구는 나를 보고 웃으며 인사했어요. 나는 친구가 좋아요. 친구와 나는 학교에 갔어요. 학교에는 작은 고양이가 살아요. 고양이는 학교 마당에서 낮잠을 자요. 친구와 나는 고양이를 보러 갔어요. 고양이는 우리를 보고 야옹 하고 울었어요. 우리는 고양이에게 우유를 주었어요. 고양이는 우유를 마시고 기분이 좋았어요. 수업이 끝나고 나는 친구와 집에 갔어요. 집에 가는 길에 고양이를 또 만났어요. 고양이는 나를 따라 집에 가고 싶어 했어요. 나는 고양이에게 인사하고 집에 갔어요. 다음 날 학교에 가는 길에 고양이가 나를 기다리고 있었어요. 나는 고양이와 함께 학교에 갔어요. 친구는 고양이를 보고 웃었어요. 우리는 고양이와 함께 마당에서 놀았어요. 고양이는 학교가 좋았어요. 나는 학교에 가는 길이 좋아요. 친구와 고양이와 함께라서 학교 가는 길이 즐거워요.

✍️ [이어쓰기 시작]을 누르면 모델이 한 글자씩 타자를 쳐요...

읽는 법: 회색 글자는 코퍼스에서 가져온 시작 문맥(씨앗), 흰 글자가 모델이 스스로 이어 쓴 부분, 노란 글자가 방금 뽑은 글자예요. 120자를 쓰면 스스로 멈춰요.

🔍 확률 들여다보기 — 다음 글자 후보 top 5

현재 문맥: (비어 있음)

이 문맥은 코퍼스에 한 번도 안 나와서 모델이 몰라요. (n-gram의 약점! 코퍼스에 있는 구절로 다시 시도해 보세요)

읽는 법: 막대가 길수록 다음 글자로 뽑힐 확률이 높아요. 생성 중에는 글자가 붙을 때마다 문맥이 바뀌며 순위가 출렁여요. 온도를 바꾸면 같은 빈도표라도 막대 비율이 달라져요.

🎛️ 모델 조종하기

문맥 길이 n

직전 2글자를 보고 다음 글자를 골라요 (n=1은 아무것도 안 봄)

📊 지금 상태

문맥 길이 n
3 (직전 2글자)
어휘 (고유 문자)
73자
학습된 문맥 수
140개
생성한 글자 수
0 / 120

n을 키우면 학습된 문맥 수가 확 늘어나는 게 보이나요? 문맥이 길어질수록 가짓수가 폭발하는 것 — 이게 n-gram의 한계예요.

🧪 이렇게 실험해 보세요

  1. n=1 → n=4로 하나씩 올리며 이어쓰기 — 어디서부터 "말"이 되나요?
  2. n=4 + 온도 0.2 — 코퍼스 문장이 거의 그대로 나와요. 이건 창작일까요, 암기일까요?
  3. 멈춘 상태에서 입력창에 "고양이"를 쳐 보세요 — 모델이 그 뒤를 뭐라고 예상하나요?
  4. 코퍼스에 없는 문맥(예: 김치)을 입력하면 어떻게 되나요?

🧮 수식과 파이썬

P(다음 글자 | 직전 n−1글자)
= 그 문맥 뒤에 온 횟수 ÷ 문맥 등장 횟수
그냥 세서 나누는 것뿐! 그런데 문장이 나와요.
🐍 파이썬 코드로 보면? (펼치기)
import random

corpus = "학교에 가는 길에 고양이를 만났어요. ..."  # 학습할 이야기

# 1) 바이그램(n=2) 카운트: "이 글자 다음엔 어떤 글자가 몇 번 왔나?"
counts = {}
for i in range(len(corpus) - 1):
    ctx, nxt = corpus[i], corpus[i + 1]
    counts.setdefault(ctx, {})
    counts[ctx][nxt] = counts[ctx].get(nxt, 0) + 1

# 2) 생성: 빈도에 비례하는 확률로 다음 글자를 하나씩 뽑기
text = "학"
for _ in range(100):
    table = counts[text[-1]]              # 현재 문맥의 빈도표
    chars, freqs = list(table), list(table.values())
    text += random.choices(chars, weights=freqs)[0]

print(text)  # 그냥 세서 뽑았을 뿐인데 문장 비슷한 게 나온다!