SONUB
KO English 한국어 日本語 中文 हिन्दी বাংলা اردو Español Português Français Deutsch Русский العربية Bahasa Indonesia Tiếng Việt ไทย Türkçe

알림을 켜는 방법

이 브라우저 또는 주소에서는 알림을 사용할 수 없습니다. 지원되는 브라우저로 보안 연결된 SONUB 사이트에 접속해 주세요.

알림이 차단되어 있으면 브라우저 설정에서 허용해 주세요.

  1. 주소 표시줄 옆의 사이트 설정 또는 브라우저의 사이트 설정을 열고, 이 사이트의 알림을 허용으로 바꾸세요.
  2. 기기 설정에서 브라우저 알림을 허용하고 방해 금지 또는 집중 모드를 확인하세요.
  3. SONUB으로 돌아와 페이지를 새로고침하세요. 알림이 켜지면 벨은 사라집니다.

iPhone 또는 iPad에서는 SONUB을 홈 화면에 추가하고 거기에서 연 뒤 알림을 허용하세요.

로그인 로그아웃 화면 테마 기본 테마 다크 테마 핑크 테마 채팅 1:1 채팅 그룹 채팅 게시판 공지사항 뉴스 게시판 건의 게시판 자기소개 게시판 메인 메뉴

자, 이제 여러분들만의 인공지능을 만들어 봅시다.

다음 을 예측하고 를 바꾼 뒤 저장 지점에서 이어 합니다. train.py 를 실행하고 전후의 을 비교해 보세요.

단어 블록이 한 줄로 놓여 있고 끝에 빈칸이 있다. 빈칸 위에 높이가 다른 막대를 단 후보 블록 세 개가 있고, 그중 하나가 골라져 줄 끝에 붙은 뒤 화살표가 다시 처음으로 돌아가 다음 칸을 고른다.
6

학습·체크포인트

이 [a,b,c]라면 입력 [a,b]의 정답은 [b,c]입니다. 은 정답 토큰 의 입니다. 손실이 작으면 정답에 더 높은 확률을 주었다는 뜻이며 사실을 말한다는 보장은 아닙니다.

매 은 짧은 구간 선택 → 이전 지우기 → · → → 갱신 순서입니다. 한 번의 갱신을 마칠 때마다 ·설정··진행 횟수·를 저장합니다. 처음과 끝의 은 같은 검증 자료 첫 구간을 봅니다. 작은 상태 확인용이며 전체 가 아닙니다.

학습 목표 — 한 칸씩 밀린 정답

를 로 바꾸면 긴 번호 줄이 됩니다. 여기서 13토큰짜리 구간을 잘라 앞 12개를 입력, 한 칸 밀린 뒤 12개를 정답으로 씁니다.

  • 토큰이 [a, b, c, d] 라면 입력 [a, b, c] 의 정답은 [b, c, d] 입니다.
  • 자리 1은 a 만 보고 b 를, 자리 2는 a·b 를 보고 c 를, 자리 3은 a·b·c 를 보고 d 를 맞힙니다.
  • 인과 덕분에 한 번의 forward 로 12가지 문제를 동시에 풉니다.

정답 표시를 사람이 붙이지 않아도 글 자체가 문제와 정답이 됩니다. 그래서 인터넷의 글을 그대로 학습 자료로 쓸 수 있습니다(자기 지도 학습). 다음 토큰을 잘 맞히려면 문법··형식·사실을 알아야 하므로, 이 단순한 목표가 언어 능력 전체를 끌어냅니다.

손실 — 교차 엔트로피(정답 확률의 음수 로그)

자리마다 이 정답 토큰에 준 확률을 p 라고 하면, 그 자리의 손실은 −log(p) 입니다. 구간의 손실은 12자리 손실의 평균입니다.

  • p = 1 이면 손실 0: 정답을 확신했습니다.
  • p = 0.5 면 0.693, p = 0.1 이면 2.303, p = 0.01 이면 4.605: 정답에 준 확률이 작을수록 손실이 가파르게 커집니다.
  • 오답에 큰 확률을 주어도 직접 벌점을 받지는 않습니다. 확률의 합이 1이므로, 오답이 많이 가져가면 정답의 p 가 줄어 손실이 커집니다.

처음 손실은 미리 알 수 있습니다. 전의 모델은 40개에 거의 같은 확률(1/40)을 주므로 손실이 −log(1/40) = ln 40 ≈ 3.689 입니다. train.py 가 처음 찍는 validation 3.7568 이 이 값과 거의 같으면 모델이 바르게 초기화된 것입니다. 어휘 50,257개인 라면 처음 손실은 약 10.8 입니다. 손실에 exp 를 씌운 값을 혼잡도(perplexity)라고 하는데, exp(3.689) = 40 처럼 "몇 개 중에서 고민하고 있는가"로 읽을 수 있습니다.

어휘가 수만 개인 모델의 손실은 대략 이렇게 읽습니다. 10 이상이면 무작위 수준, 5~7 이면 문법을, 3~5 면 형식을, 2~3 이면 뜻을 배우는 중이고, 2 아래로 내려가면 외우기()를 의심합니다. 어휘 수에 따라 출발점이 다르므로 에서는 출발점 3.69 에서 얼마나 내려갔는지를 봅니다.

직접 확인하기 lab/loss.py
import math

for p in [1.0, 0.9, 0.5, 0.1, 0.01]:
    print('p =', p, ' loss =', round(0.0 - math.log(p), 4))
for vocab in [40, 50257]:
    print('guess evenly among', vocab, '->', round(math.log(vocab), 4))
cd ~/xslm
mkdir -p lab
micro lab/loss.py
python3 -m lab.loss

실행 결과

p = 1.0  loss = 0.0
p = 0.9  loss = 0.1054
p = 0.5  loss = 0.6931
p = 0.1  loss = 2.3026
p = 0.01  loss = 4.6052
guess evenly among 40 -> 3.6889
guess evenly among 50257 -> 10.8249

경사하강법과 학습률 — 기울기 반대 방향으로 조금씩

기울기는 "이 숫자를 조금 키우면 손실이 얼마나 늘어나는가"입니다. 손실을 줄이려면 기울기의 반대 방향으로 숫자를 옮깁니다.

  • 새 값 = 옛 값 − × 기울기

학습률(learning_rate)은 한 번에 얼마나 옮길지입니다. 아래 실험은 f(w) = (w − 3)² 의 가장 낮은 곳 w = 3 을 0에서 출발해 20걸음 찾아갑니다.

  • 0.01: 너무 조심스러워 1.0 근처까지밖에 못 갑니다. 학습이 느립니다.
  • 0.1: 2.97 까지 와서 거의 도착했습니다.
  • 0.5: 이 함수에서는 한 걸음에 정확히 3에 닿습니다.
  • 1.1: 너무 크게 뛰어 골짜기 반대편으로 넘어가기를 되풀이하다 −112 로 튀어 나갑니다(발산).

실제 모델은 숫자가 수천~수십억 개라 골짜기 모양을 알 수 없으므로 학습률을 실험으로 정합니다. XSLM 의 0.03 은 작은 모델이 튀지 않는 조심스러운 값입니다. 학습률을 바꾸면 settings.json 이 바뀌므로 새 체크포인트로 시작합니다.

직접 확인하기 lab/descent.py
# f(w) = (w - 3) ** 2 is smallest at w = 3. Its gradient is 2 * (w - 3).
for lr in [0.01, 0.1, 0.5, 1.1]:
    w = 0.0
    for step in range(20):
        w -= lr * 2 * (w - 3)
    print('lr', lr, '-> w =', round(w, 4))
cd ~/xslm
mkdir -p lab
micro lab/descent.py
python3 -m lab.descent

실행 결과

lr 0.01 -> w = 0.9972
lr 0.1 -> w = 2.9654
lr 0.5 -> w = 3.0
lr 1.1 -> w = -112.0128

학습 루프 한 바퀴 — 여섯 걸음

train.py 의 반복 한 번은 이 순서입니다.

  • 구간 고르기: 학습 토큰 줄에서 무작위 시작점을 골라 13토큰을 자릅니다.
  • 기울기 지우기: 모든 의 grad 를 0으로 만듭니다. 기울기는 += 로 쌓이므로 이전 반복의 값이 남으면 섞입니다. 이번 구간에 나오지 않는 토큰의 줄은 에 없어 backward 가 건드리지 않으므로, 여기서 지우지 않으면 옛 기울기가 그대로 남습니다.
  • 순전파: model.loss 가 forward 와 손실 계산을 합니다.
  • 역전파: loss.backward() 로 1,248개 모두의 기울기를 구합니다.
  • 갱신: 기울기 크기를 제한한 뒤 p.data −= 학습률 × 기울기 × 배율 로 모든 숫자를 옮깁니다.
  • 저장·기록: 체크포인트를 저장하고 과 loss 를 찍습니다.

학습은 이 바퀴를 정한 횟수만큼 돕니다. 시작 전과 끝난 뒤에는 검증 자료의 첫 구간으로 손실을 재서 학습에 쓰지 않은 글에서도 나아졌는지 봅니다.

XSLM 은 자료를 몇 번 돌았는지() 대신 걸음 수(step)로 셉니다. 구간을 무작위로 뽑으므로 어떤 부분은 여러 번, 어떤 부분은 덜 뽑힙니다. 큰 모델도 대개 step 으로 셉니다.

기울기 자르기 — 한 번에 너무 멀리 가지 않게

가끔 어떤 구간에서 기울기가 갑자기 아주 커집니다. 그대로 갱신하면 숫자가 한 번에 멀리 튀어 지금까지의 학습을 망칩니다(기울기 폭발). 그래서 train.py 는 갱신 전에 기울기 전체의 길이를 잽니다.

  • 길이 = √(모든 기울기의 제곱의 합)
  • 배율 = min(1, 1 ÷ 길이)
  • 길이가 1 이하면 배율 1로 그대로 쓰고, 1보다 크면 길이가 정확히 1이 되도록 모든 기울기를 같은 비율로 줄입니다.

방향은 그대로 두고 크기만 줄이는 것이 핵심입니다. 에서는 clip_grad_norm_ 이 같은 일을 합니다(보통 최대 길이 1.0). 분모의 1e-8 은 기울기가 모두 0일 때 0으로 나누지 않으려는 작은 수입니다.

옵티마이저 — SGD 와 AdamW

기울기로 숫자를 고치는 규칙을 라고 합니다.

  • SGD(XSLM): 새 값 = 옛 값 − 학습률 × 기울기. 지금 기울기만 보고 기억하는 것이 없습니다. 그래서 체크포인트에 옵티마이저 상태를 저장할 필요가 없습니다.
  • : 지난 기울기들의 흐름(속도)을 기억해, 같은 방향이면 더 빨리, 흔들리면 덜 움직입니다.
  • Adam·AdamW: 숫자마다 기울기의 평균과 크기의 평균을 기억해 숫자마다 알맞은 걸음 크기를 스스로 정합니다. AdamW 는 숫자가 너무 커지지 않게 조금씩 줄이는 가중치 감쇠(weight decay)를 더합니다. 큰 학습의 표준이고, 학습률은 보통 0.0003 정도입니다.

Adam 은 파라미터마다 숫자 두 개를 더 기억하므로, 이어 학습하려면 체크포인트에 그 상태(optimizer state)도 저장해야 합니다. 그래서 PyTorch 체크포인트에는 보통 모델 가중치와 함께 옵티마이저 상태가 들어갑니다. 학습 중 도 가중치만 둘 때의 몇 배가 듭니다.

배치 — 여러 구간의 평균 기울기

XSLM 은 한 번에 구간 하나(배치 크기 1)로 기울기를 구합니다. 구간마다 내용이 달라 기울기가 크게 흔들리므로 손실도 오르락내리락합니다.

큰 모델은 여러 구간을 한 번에 계산해 평균 기울기를 씁니다. 예를 들어 256토큰 구간 16개(배치 크기 16)면 한 번의 갱신에 4,096토큰을 봅니다.

  • 평균을 내면 흔들림이 줄어 갱신 방향이 안정됩니다.
  • 는 여러 구간을 동시에 계산하므로 배치를 키워도 시간이 거의 늘지 않습니다.
  • 대신 메모리를 그만큼 씁니다. 메모리가 모자라면() 배치나 구간 길이를 줄입니다.

메모리가 부족할 때는 작은 배치로 여러 번 backward 해 기울기를 모은 뒤 한 번 갱신하는 기울기 누적(gradient accumulation)을 씁니다. XSLM 의 backward 는 시작할 때 그래프 안의 기울기를 0으로 지우므로 이 방법을 그대로는 쓸 수 없습니다.

과적합과 과소적합 — 학습 손실과 검증 손실을 같이 본다

두 손실을 함께 보면 모델의 상태를 알 수 있습니다.

  • 둘 다 내려간다: 잘 배우고 있습니다.
  • 학습 손실만 내려가고 은 오른다: 과적합입니다. 학습 자료를 외워서 처음 보는 글에는 오히려 나빠집니다.
  • 둘 다 잘 안 내려간다: 입니다. 모델이 너무 작거나, 학습이 부족하거나, 학습률이 맞지 않습니다.

XSLM 으로 직접 재 보았습니다(데스크톱 , 기본 자료). 기본 학습률 0.03 으로 1,500번 학습하면 검증 손실이 3.76 에서 3.23 으로 내려갔습니다. 학습률 0.1 로 3,000번 학습하면 학습 손실은 구간에 따라 1.3 까지 내려갔지만 검증 손실은 4.42 로 올랐고, 0.3 이면 5.28 까지 올랐습니다. 학습 문장 12쌍을 외우기는 쉬워도, 처음 보는 3쌍에 통할 규칙을 배울 만큼 자료가 많지 않기 때문입니다.

과적합을 줄이는 가장 확실한 방법은 자료를 늘리는 것이고, 모델 줄이기··학습 일찍 멈추기(검증 손실이 가장 낮았던 체크포인트 쓰기)도 씁니다. 과소적합이면 모델을 키우거나 학습을 더 합니다.

체크포인트 — 무엇을 왜 저장하나

checkpoints/model.json 은 학습을 멈췄다가 정확히 이어 하기 위한 모든 것을 담습니다.

  • weights: 9개 의 숫자 1,248개.
  • step: 지금까지 한 갱신 횟수.
  • random: 난수 생성기의 상태. 이어 학습할 때 구간 순서를 끊김 없이 이어 갑니다.
  • settings·tokenizer: 모델의 크기와 번호 규칙. generate.py 와 runner.py 는 이것만 읽고 모델을 다시 만듭니다.
  • data_hash: 학습·검증 자료의 지문. 자료가 바뀌었는지 알아냅니다.

그래서 3번씩 두 번 나눠 학습한 결과와 6번을 한 번에 학습한 결과가 숫자 하나까지 같습니다. 직접 확인해 보세요. --steps 3 을 두 번 실행한 model.json 을 다른 이름으로 옮겨 두고, 새로 --steps 6 을 한 번 실행한 결과와 비교합니다.

저장은 안전하게 합니다. model.tmp 에 먼저 다 쓴 뒤 로 이름을 바꿉니다. 이름 바꾸기는 한순간에 일어나므로, 쓰는 도중에 를 누르거나 전원이 꺼져도 model.json 은 이전 완성본과 새 완성본 둘 중 하나입니다. 반쯤 쓴 깨진 파일이 남지 않습니다.

PyTorch 는 torch.save 로 가중치·옵티마이저 상태·step·설정을 이진 파일(.pt)에 저장합니다. XSLM 은 걸음마다 저장하지만, 큰 모델은 저장에도 시간이 들어 수백 걸음마다 평가하며 저장합니다.

재현성 — seed 로 같은 결과를 다시 얻는다

컴퓨터의 난수는 seed(씨앗 번호)에서 정해진 규칙으로 만든 수열입니다. seed 가 같으면 언제나 같은 수열이 나옵니다. XSLM 은 seed 7 로 두 곳을 정합니다.

  • 가중치의 처음 값(model.py 의 random.Random(seed)).
  • 학습 구간의 순서(train.py 의 rng).

그래서 같은 자료·같은 설정이면 누가 어디서 실행해도 같은 학습 과정을 밟습니다. 학습률 하나만 바꿔 비교하려면 나머지 무작위성이 같아야 하므로 은 실험의 기본입니다. seed 를 바꾸면 다른 처음 값에서 출발해 결과가 달라집니다. 운 좋은 seed 하나에 기댄 결론은 믿기 어려우므로, 중요한 비교는 seed 를 몇 개 바꿔 되풀이합니다.

다만 Python 이나 의 수학 가 다르면 소수점 끝자리가 달라질 수 있어, 아주 긴 학습에서는 숫자가 조금씩 벌어질 수 있습니다.

학습 기록 읽기 — 숫자가 말해 주는 것

train.py 는 이런 줄을 찍습니다.

  • parameters: 1248 resume: 0 — 파라미터 수와 이어서 시작한 걸음. 처음이면 0입니다.
  • validation: 3.7568 — 학습 전 검증 손실. (어휘 수)와 비슷해야 합니다.
  • step: 1 loss: 3.6875 — 걸음마다 그 구간의 학습 손실.
  • validation: … — 학습 뒤 검증 손실.
  • saved: checkpoints/model.json — 저장한 곳.

걸음마다의 loss 는 구간이 달라 오르내립니다. 숫자 하나가 아니라 여러 걸음의 흐름을 봅니다. 기본 20번이면 3.7 근처에서 조금 움직이는 정도이고, 수백~수천 번이면 뚜렷하게 내려갑니다.

=True 는 출력을 바로 화면에 내보내라는 뜻입니다. 이것이 없으면 Python 이 출력을 모았다가 한꺼번에 보여 주어 학습이 멈춘 것처럼 보이고, 으로 파일에 쓸 때도 늦게 기록됩니다.

학습이 잘 안 될 때 — 증상별 점검

증상으로 원인을 좁힙니다.

  • 손실이 3.7 근처에서 거의 그대로: 학습 횟수가 적습니다. --steps 를 늘리거나, 새 체크포인트로 학습률을 조금 키워 봅니다.
  • 손실이 크게 튀거나 커지기만 함: 학습률이 너무 큽니다. 줄여서 새로 시작합니다.
  • : math domain error 또는 nan: 확률이 0이 되어 log(0)을 계산했거나 숫자가 발산했습니다. 학습률을 낮추고 기울기 자르기가 들어 있는지 봅니다.
  • : changed settings/tokenizer 또는 changed : 체크포인트와 지금 설정·자료가 다릅니다. 옛 체크포인트를 옮기고 새로 시작합니다.
  • AssertionError: add more text: 학습이나 검증 토큰이 13개보다 적습니다. raw.txt 에 줄을 더합니다.
  • 너무 느림: width·context 를 줄이면 빨라지지만 새 체크포인트가 필요합니다. 먼저 --steps 를 작게 나눠 실행합니다.
  • 학습은 됐는데 의 답이 그대로: Runner 는 켤 때 한 번만 읽습니다. 껐다 켭니다.

직접 타이핑하며 실습하기

train.py를 작성하고 짧은 학습 을 실행하세요. 실제 step과 loss가 나오는지 봅니다. 이어서 5회 더 실행해 resume 값을 확인하세요. 기기마다 속도가 다르므로 작게 시작합니다. 기본 반복은 동작 확인용이고, 반복을 늘리며 변화를 실험합니다.

cd ~/xslm
micro train.py
python3 train.py --steps 20
python3 train.py --steps 5
터미널에서 실습하기
train.py직접 작성할 전체 소스

자료 는 자료가 바뀐 줄 모르고 이어 학습하는 일을 막습니다. 에 쓴 뒤 os.replace로 바꾸므로 저장 도중 중단되어도 이전 가 남습니다. SGD에는 따로 저장할 모멘텀 상태가 없습니다.

import argparse
import hashlib
import json
import math
import os
import random
from pathlib import Path
from model import GPT
from tokenizer import BPE


def main():
    cfg = json.loads(Path('settings.json').read_text())
    parser = argparse.ArgumentParser()
    parser.add_argument('--steps', type=int, default=cfg['steps'])
    args = parser.parse_args()
    assert args.steps > 0, '--steps must be positive'
    token_state = json.loads(Path('data/tokenizer.json').read_text())
    tokenizer = BPE(**token_state)
    train_text = Path('data/train.txt').read_text(encoding='utf-8')
    val_text = Path('data/val.txt').read_text(encoding='utf-8')
    fingerprint = hashlib.sha256((train_text + '\0' + val_text).encode()).hexdigest()
    train, val = tokenizer.encode(train_text), tokenizer.encode(val_text)
    model = GPT(len(tokenizer.vocab), cfg['width'], cfg['context'], cfg['seed'])
    rng, step = random.Random(cfg['seed']), 0
    checkpoint = Path('checkpoints/model.json')
    if checkpoint.exists():
        saved = json.loads(checkpoint.read_text())
        assert saved['settings'] == cfg and saved['tokenizer'] == token_state, 'changed settings/tokenizer: start a new checkpoint'
        assert saved['data_hash'] == fingerprint, 'changed data: start a new checkpoint'
        model.load(saved['weights'])
        step = saved['step']
        rng.setstate((saved['random'][0], tuple(saved['random'][1]), saved['random'][2]))
    n = model.context
    assert len(train) > n and len(val) > n, 'add more text'
    print('parameters:', len(model.parameters), 'resume:', step, flush=True)
    print('validation:', round(model.loss(val[:n], val[1:n + 1]).data, 4), flush=True)
    checkpoint.parent.mkdir(exist_ok=True)
    for _ in range(args.steps):
        start = rng.randrange(len(train) - n)
        window = train[start:start + n + 1]
        for p in model.parameters:
            p.grad = 0
        loss = model.loss(window[:-1], window[1:])
        loss.backward()
        size = math.sqrt(sum(p.grad * p.grad for p in model.parameters))
        scale = min(1.0, 1.0 / (size + 1e-8))
        for p in model.parameters:
            p.data -= cfg['learning_rate'] * p.grad * scale
        step += 1
        state = {'settings': cfg, 'tokenizer': token_state, 'data_hash': fingerprint,
                 'weights': model.state(), 'step': step, 'random': rng.getstate()}
        temporary = checkpoint.with_suffix('.tmp')
        temporary.write_text(json.dumps(state, ensure_ascii=False), encoding='utf-8')
        os.replace(temporary, checkpoint)
        print('step:', step, 'loss:', round(loss.data, 4), flush=True)
    print('validation:', round(model.loss(val[:n], val[1:n + 1]).data, 4), flush=True)
    print('saved:', checkpoint, flush=True)


if __name__ == '__main__':
    main()

코드 한 줄씩 뜯어보기

train.py 준비 — 설정·토크나이저·자료

def main():
    cfg = json.loads(Path('settings.json').read_text())
    parser = argparse.ArgumentParser()
    parser.add_argument('--steps', type=int, default=cfg['steps'])
    args = parser.parse_args()
    assert args.steps > 0, '--steps must be positive'
    token_state = json.loads(Path('data/tokenizer.json').read_text())
    tokenizer = BPE(**token_state)
    train_text = Path('data/train.txt').read_text(encoding='utf-8')
    val_text = Path('data/val.txt').read_text(encoding='utf-8')
    fingerprint = hashlib.sha256((train_text + '\0' + val_text).encode()).hexdigest()
    train, val = tokenizer.encode(train_text), tokenizer.encode(val_text)
    model = GPT(len(tokenizer.vocab), cfg['width'], cfg['context'], cfg['seed'])

settings.json 을 읽고, argparse 로 --steps 옵션을 받습니다(없으면 설정의 steps). tokenizer.json 으로 를 다시 만들고 학습·검증 글을 읽습니다. fingerprint 는 두 글을 \0 으로 이어 SHA-256 으로 만든 지문입니다. 글이 한 글자만 바뀌어도 완전히 다른 값이 나옵니다. 두 글을 토큰 번호 목록으로 바꾸고, 어휘 수에 맞춰 를 만듭니다.

train.py 이어 학습하기

    rng, step = random.Random(cfg['seed']), 0
    checkpoint = Path('checkpoints/model.json')
    if checkpoint.exists():
        saved = json.loads(checkpoint.read_text())
        assert saved['settings'] == cfg and saved['tokenizer'] == token_state, 'changed settings/tokenizer: start a new checkpoint'
        assert saved['data_hash'] == fingerprint, 'changed data: start a new checkpoint'
        model.load(saved['weights'])
        step = saved['step']
        rng.setstate((saved['random'][0], tuple(saved['random'][1]), saved['random'][2]))

rng 는 구간을 고를 난수 생성기, step 은 갱신 횟수입니다. 체크포인트가 있으면 읽어서 설정·토크나이저·자료 지문이 지금과 같은지 assert 로 확인한 뒤, 가중치·step·난수 상태를 되살립니다. 은 튜플을 로 저장하므로 setstate 에 넘기기 전에 가운데 부분을 tuple 로 되돌립니다.

train.py 시작 전 확인

    n = model.context
    assert len(train) > n and len(val) > n, 'add more text'
    print('parameters:', len(model.parameters), 'resume:', step, flush=True)
    print('validation:', round(model.loss(val[:n], val[1:n + 1]).data, 4), flush=True)
    checkpoint.parent.mkdir(exist_ok=True)

n 은 문맥 길이(12)입니다. 학습·검증 토큰이 n 보다 많아야 n + 1 토큰 구간을 자를 수 있습니다. 파라미터 수와 이어 시작한 걸음을 찍고, 학습 전 검증 손실을 잽니다. val[:n] 이 입력, val[1:n + 1] 이 한 칸 밀린 정답입니다. checkpoints 폴더가 없으면 만듭니다.

train.py 무작위 구간 자르기

    for _ in range(args.steps):
        start = rng.randrange(len(train) - n)
        window = train[start:start + n + 1]

randrange((train) − n) 은 0 이상 (길이 − n) 미만의 시작점을 뽑습니다. 그 자리에서 n + 1 개를 잘라 window 로 둡니다. 다음 줄에서 window[:-1] 이 입력 12개, window[1:] 이 정답 12개가 됩니다.

train.py 기울기 지우기·순전파·역전파

        for p in model.parameters:
            p.grad = 0
        loss = model.loss(window[:-1], window[1:])
        loss.backward()

모든 파라미터의 grad 를 0으로 만들고, model.loss 로 손실을 계산한 뒤 backward 로 기울기를 채웁니다. backward 는 그래프 안의 노드만 지우므로, 이번 구간에 쓰이지 않은 토큰 임베딩 줄의 옛 기울기는 이 반복문이 지웁니다.

train.py 기울기 자르기와 SGD 갱신

        size = math.sqrt(sum(p.grad * p.grad for p in model.parameters))
        scale = min(1.0, 1.0 / (size + 1e-8))
        for p in model.parameters:
            p.data -= cfg['learning_rate'] * p.grad * scale
        step += 1

size 는 1,248개 기울기를 한 로 본 길이입니다. scale 은 길이가 1을 넘으면 1/길이, 아니면 1입니다. 모든 파라미터의 data 에서 학습률 × 기울기 × scale 을 뺍니다. data 를 직접 바꾸므로 이 계산은 그래프에 기록되지 않습니다. 끝으로 step 을 1 늘립니다.

train.py 안전하게 저장하고 기록하기

        state = {'settings': cfg, 'tokenizer': token_state, 'data_hash': fingerprint,
                 'weights': model.state(), 'step': step, 'random': rng.getstate()}
        temporary = checkpoint.with_suffix('.tmp')
        temporary.write_text(json.dumps(state, ensure_ascii=False), encoding='utf-8')
        os.replace(temporary, checkpoint)
        print('step:', step, 'loss:', round(loss.data, 4), flush=True)

state 사전에 이어 학습하는 데 필요한 모든 것을 담습니다. model.tmp 에 전부 쓴 뒤 os.replace 로 model.json 을 한 번에 바꿔 끼웁니다. 그다음 이번 걸음의 손실을 찍습니다. 걸음마다 저장하므로 언제 멈춰도 마지막으로 끝낸 걸음까지 남습니다.

train.py 마무리 검증

    print('validation:', round(model.loss(val[:n], val[1:n + 1]).data, 4), flush=True)
    print('saved:', checkpoint, flush=True)

학습을 마친 가중치로 시작 때와 같은 검증 구간의 손실을 다시 잽니다. 시작 때 값과 비교하면 학습에 쓰지 않은 글에서도 나아졌는지 알 수 있습니다. 구간 하나뿐이라 대략의 신호로만 읽습니다.

여기까지 이해했나요?

checkpoints/model.json이 생겼나요? 두 번째 실행은 이전 횟수에서 이어져야 합니다. 학습 전후 validation을 비교하세요. 매번 다른 구간을 뽑으므로 loss는 오르기도 합니다. 한 번의 수치로 결론 내리지 마세요.

막혔을 때 확인하기

Ctrl+C로 멈추면 마지막으로 마친 갱신까지 남습니다. 설정·토크나이저·데이터를 바꾸면 기존 체크포인트를 일부러 거부합니다. 먼저 내보내고 model.json을 다른 이름으로 옮겨 새로 시작하세요. 이미 실행 중인 Runner는 다시 시작해야 새 가중치를 읽습니다.

자주 묻는 질문

loss 가 오르락내리락하는데 학습이 망가진 건가요?

아닙니다. 걸음마다 다른 구간 하나로 손실을 재므로 쉬운 구간이면 낮고 어려운 구간이면 높습니다. 여러 걸음의 흐름과 처음·끝의 검증 손실을 보세요. 배치를 키우면 흔들림이 줄어듭니다.

몇 번 학습해야 하나요?

정해진 답은 없고 검증 손실로 정합니다. 검증 손실이 더 내려가지 않거나 오르기 시작하면 멈출 때입니다. 데스크톱에서 재 보니 기본 학습률 0.03 으로 1,500번까지는 검증 손실이 내려갔고, 학습률 0.1 로 3,000번이면 오히려 올랐습니다. 에서는 걸음이 느리므로 --steps 50 처럼 나눠 실행하며 지켜보세요.

GPU 없이도 학습이 되나요?

됩니다. 학습은 덧셈·곱셈의 반복일 뿐이라 어떤 로도 할 수 있습니다. GPU 는 같은 계산 수천 개를 동시에 해서 빠를 뿐입니다. XSLM 은 가상 CPU 로, 큰 모델은 보통 GPU(Apple 맥은 )로 같은 원리의 학습을 합니다.

학습이 끝난 모델은 쓰면서 저절로 더 배우나요?

아닙니다. 학습은 train.py 를 실행할 때만 일어납니다. Runner 로 답하는 동안에는 가중치를 읽기만 하고 바꾸지 않습니다. 대화에서 들은 것을 기억하는 것처럼 보이는 서비스는 을 에 다시 넣어 주는 것입니다.

가이드라인

인공지능

에이전트 툴

바이브코딩

AI 활용

AI 자격증

화상 수업

IT 기초

채팅

게시판

만들기

소통

더보기

용어 사전

모르는 말을 골라 보세요. 쉬운 설명과 관련 용어를 볼 수 있어요.

찾는 용어가 아직 없어요.

용어를 고르면 설명이 보여요.