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을 홈 화면에 추가하고 거기에서 연 뒤 알림을 허용하세요.

XSML · 첫 코드부터 나만의 대화까지

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

이 브라우저에서 직접 타이핑하며 언어 모델을 만듭니다. 데이터를 준비하고 작은 GPT를 학습한 뒤, 내가 만든 Runner와 XChat으로 대화합니다. 미리 학습된 가중치나 외부 AI API는 쓰지 않습니다.

5

GPT 구조와 코드

아주 작은 디코더 Transformer와 역전파를 직접 작성합니다.

은 토큰 번호로 숫자 를 찾는 표입니다. 위치 벡터를 더하면 몇 번째 조각인지 알 수 있습니다. Query는 찾을 정보, Key는 각 앞자리의 특징, Value는 가져올 정보입니다. 은 관련도를 로 바꾸어 Value를 섞습니다.

모델은 인과적 어텐션 헤드 하나와 블록 하나입니다. keys와 values에는 현재와 앞자리만 들어가므로 미래 정답을 훔쳐볼 수 없습니다. 잔차 덧셈은 원래 정보를 남기고, 두 층 MLP는 특징을 바꿉니다. Layer normalization은 숫자 크기를 안정시키고 출력층은 각 토큰의 점수를 냅니다. 읽기 쉽게 , 학습 변수가 없는 정규화, dropout 없는 구조로 줄였습니다.

직접 타이핑하며 실습하기

engine.py부터 쓰고 미분 검사를 실행한 뒤 model.py를 작성하세요. Value는 계산마다 해당 연산의 미분값을 기억합니다. backward는 계산 그래프를 거꾸로 따라가며 기울기를 더하므로 같은 가중치를 여러 번 쓴 영향도 합쳐집니다. forward에서 토큰 → 위치 → 어텐션 → MLP → 출력층을 찾아보세요.

cd ~/xsml
nano engine.py
nano model.py
python3 engine.py
python3 -c "from model import GPT; m=GPT(10, 8, 12); print(len(m.forward([1, 2])), len(m.forward([1, 2])[0]))"
engine.py — 직접 작성할 전체 소스

data는 숫자, grad는 그 숫자가 손실에 미치는 영향, parents는 연산별 미분 기록입니다. 덧셈은 기울기를 나누어 보내고 곱셈은 상대 입력만큼 곱해 보냅니다. softmax는 점수를 확률로 바꿉니다.

import math


class Value:
    def __init__(self, data, parents=()):
        self.data = float(data)
        self.grad = 0.0
        self.parents = parents

    def __add__(self, other):
        other = other if isinstance(other, Value) else Value(other)
        return Value(self.data + other.data, ((self, 1), (other, 1)))

    def __mul__(self, other):
        other = other if isinstance(other, Value) else Value(other)
        return Value(self.data * other.data,
                     ((self, other.data), (other, self.data)))

    def __pow__(self, power):
        return Value(self.data ** power,
                     ((self, power * self.data ** (power - 1)),))

    def __neg__(self):
        return self * -1

    def __sub__(self, other):
        return self + (-other)

    def __truediv__(self, other):
        return self * (other ** -1)

    __radd__ = __add__
    __rmul__ = __mul__

    def exp(self):
        result = math.exp(self.data)
        return Value(result, ((self, result),))

    def log(self):
        return Value(math.log(self.data), ((self, 1 / self.data),))

    def relu(self):
        return Value(max(0, self.data), ((self, float(self.data > 0)),))

    def backward(self):
        order, seen, stack = [], set(), [(self, False)]
        while stack:
            node, expanded = stack.pop()
            if expanded:
                order.append(node)
            elif node not in seen:
                seen.add(node)
                stack.append((node, True))
                stack.extend((parent, False) for parent, _ in node.parents)
        for node in order:
            node.grad = 0.0
        self.grad = 1.0
        for node in reversed(order):
            for parent, derivative in node.parents:
                parent.grad += derivative * node.grad


def softmax(values):
    peak = max(v.data for v in values)
    exps = [(v - peak).exp() for v in values]
    total = sum(exps)
    return [v / total for v in exps]


def linear(x, weights):
    return [sum(a * b for a, b in zip(x, row)) for row in weights]


def norm(x):
    mean = sum(x) / len(x)
    variance = sum((v - mean) ** 2 for v in x) / len(x)
    return [(v - mean) * (variance + 1e-5) ** -0.5 for v in x]


if __name__ == '__main__':
    x = Value(3)
    y = x * x + 2 * x
    y.backward()
    print('y =', y.data, 'dy/dx =', x.grad)
    assert y.data == 15 and x.grad == 8
model.py — 직접 작성할 전체 소스

shapes에서 크기를 본 뒤 forward를 읽으세요. weights의 Value는 모두 학습 대상입니다. 리스트로 벡터와 행렬을 표현합니다. loss는 각 위치의 다음 토큰과 비교하며 state와 load는 Python 객체 대신 숫자를 저장하고 읽습니다.

import random
from engine import Value, linear, norm, softmax


class GPT:
    def __init__(self, vocab_size, width, context, seed=7):
        self.width, self.context = width, context
        rng = random.Random(seed)
        shapes = {
            'token': (vocab_size, width), 'position': (context, width),
            'query': (width, width), 'key': (width, width),
            'value': (width, width), 'project': (width, width),
            'expand': (2 * width, width), 'contract': (width, 2 * width),
            'head': (vocab_size, width),
        }
        self.weights = {
            name: [[Value(rng.gauss(0, 0.1)) for _ in range(cols)]
                   for _ in range(rows)] for name, (rows, cols) in shapes.items()
        }
        self.parameters = [v for matrix in self.weights.values()
                           for row in matrix for v in row]

    def forward(self, ids):
        assert 0 < len(ids) <= self.context
        w, keys, values, output = self.weights, [], [], []
        for position, token in enumerate(ids):
            x = [a + b for a, b in zip(w['token'][token], w['position'][position])]
            h = norm(x)
            q = linear(h, w['query'])
            keys.append(linear(h, w['key']))
            values.append(linear(h, w['value']))
            scores = [sum(a * b for a, b in zip(q, k)) / self.width ** 0.5
                      for k in keys]
            attention = softmax(scores)
            mixed = [sum(p * v[i] for p, v in zip(attention, values))
                     for i in range(self.width)]
            x = [a + b for a, b in zip(x, linear(mixed, w['project']))]
            hidden = [v.relu() for v in linear(norm(x), w['expand'])]
            x = [a + b for a, b in zip(x, linear(hidden, w['contract']))]
            output.append(linear(norm(x), w['head']))
        return output

    def loss(self, inputs, targets):
        logits = self.forward(inputs)
        losses = [-softmax(row)[target].log()
                  for row, target in zip(logits, targets)]
        return sum(losses) / len(losses)

    def state(self):
        return {name: [[v.data for v in row] for row in matrix]
                for name, matrix in self.weights.items()}

    def load(self, state):
        assert set(state) == set(self.weights), 'checkpoint keys'
        for name, matrix in self.weights.items():
            assert len(state[name]) == len(matrix), 'checkpoint rows'
            for row, saved in zip(matrix, state[name]):
                assert len(saved) == len(row), 'checkpoint columns'
                for value, number in zip(row, saved):
                    value.data = float(number)

여기까지 이해했나요?

engine.py는 y = 15.0, dy/dx = 8.0을 출력해야 합니다. 모델 크기 검사는 2 10을 출력합니다. 두 위치마다 어휘 10개의 점수를 냈다는 뜻입니다. 뒤에 토큰을 더해도 앞자리 점수가 바뀌면 안 되는 이유를 설명하세요.

막혔을 때 확인하기

행렬의 행 크기와 zip으로 묶는 벡터 길이를 확인하세요. 단어를 세는 바이그램으로 이 층들을 대신하지 않습니다. 작지만 임베딩·어텐션·MLP·출력층의 모든 가중치를 역전파로 학습합니다.