AI를 완전히 바꾼 12가지 논문: 상세 설명과 예제

이 글에는 짧은 영상이 있습니다:TikTok

1. 개요

ChatGPT, Midjourney, Google Search의 AI Overview 같은 여러분이 매일 사용하는 AI 시스템들은 모두 수십 개의 연구 논문 위에 세워져 있습니다. 이 글에서는 그 12개의 논문을 살펴보고, 이해하기 쉬운 언어로 핵심 아이디어를 설명하고, 각 아이디어를 짧은 Java 코드로 보여드립니다. 읽고 나면 Transformer가 NLP 세계를 호령하는 이유, RAG가 hallucination을 해결하는 방법, 그리고 “한 단계씩 생각해봐”라는 프롬프트가 왜 그렇게 강력한지 이해할 수 있을 겁니다.

2. AlexNet (2012) — deep learning이 “보기”를 시작하다

AlexNet 이전에는 computer vision이 수동 feature engineering에 의존했습니다. AlexNet은 ReLU activation과 dropout을 갖춘 convolutional neural network (CNN)를 사용했고, GPU에서 학습했으며, ImageNet 2012에서 거의 10 퍼센트 포인트 낮은 오류율로 모든 경쟁자를 압도했습니다. 이것이 deep learning 시대의 개시 신호였습니다.

public class ConvOutputSizeCalculator {

    public static int outputSize(int inputSize, int kernelSize, int stride, int padding) {
        return (inputSize - kernelSize + 2 * padding) / stride + 1;
    }

    public static void main(String[] args) {
        // AlexNet의 입력 이미지: 227x227, kernel 11x11, stride 4
        int size = outputSize(227, 11, 4, 0);
        System.out.println(size);
    }
}
55

outputSize는 conv layer 이후의 feature map 크기를 계산합니다. 입력 227×227과 kernel 11×11 stride 4로 출력은 55×55×96 — AlexNet의 첫 번째 레이어의 정확한 크기입니다. ReLU는 sigmoid처럼 gradient가 “죽는” 것을 방지하므로, 더 깊은 네트워크도 여전히 학습할 수 있습니다.

3. GAN (2014) — 실제 이미지를 만들기 위한 두 개의 경쟁하는 네트워크

Generative Adversarial Network은 두 개의 네트워크를 대립적인 상황에 놓습니다: generator는 가짜 이미지를 만들어 discriminator를 속이려고 하고, discriminator는 진짜/가짜 이미지를 구분하려고 합니다. 둘 다 각 학습 라운드를 거치면서 개선됩니다.

public record GanLosses(double discriminatorLoss, double generatorLoss) {}

public class GanLossCalculator {

    public static GanLosses compute(double probReal, double probFake) {
        double dLoss = -(Math.log(probReal) + Math.log(1 - probFake));
        double gLoss = -Math.log(probFake);
        return new GanLosses(dLoss, gLoss);
    }
}

probReal은 discriminator가 진짜 이미지를 올바르게 예측할 확률이고, probFake는 generator에게 속을 확률입니다. Discriminator는 probReal → 1과 probFake → 0을 원하고, generator는 반대를 원합니다. 이것은 전형적인 zero-sum game입니다.

주의: GAN은 학습이 어렵기로 유명합니다 — mode collapse에 빠지기 쉽습니다 (generator가 같은 유형의 이미지만 몇 가지 만들어냄). 나중의 diffusion model은 훨씬 더 안정적이므로, 현대의 대부분 이미지 생성 시스템(DALL·E, Stable Diffusion)은 더 이상 GAN을 사용하지 않습니다.

4. Transformer (2017) — “Attention Is All You Need”

Transformer는 RNN/LSTM을 완전히 버리고 self-attention으로 대체했습니다: 각 token이 문장의 다른 모든 token을 직접 살펴봐서 관련성을 계산하며, 순차적인 처리가 필요 없습니다. 이것이 GPT, Claude, Llama, BERT의 기반 아키텍처입니다.

public class ScaledDotProductAttention {

    public static double[] attendFirstToken(double[][] query, double[][] key) {
        int seqLen = key.length;
        int dim = query[0].length;
        double scale = Math.sqrt(dim);
        double[] scores = new double[seqLen];
        for (int j = 0; j < seqLen; j++) {
            double dot = 0;
            for (int k = 0; k < dim; k++) dot += query[0][k] * key[j][k];
            scores[j] = dot / scale;
        }
        return softmax(scores);
    }

    private static double[] softmax(double[] row) {
        double max = Arrays.stream(row).max().orElse(0);
        double sum = 0;
        double[] exps = new double[row.length];
        for (int i = 0; i < row.length; i++) {
            exps[i] = Math.exp(row[i] - max);
            sum += exps[i];
        }
        for (int i = 0; i < exps.length; i++) exps[i] /= sum;
        return exps;
    }
}

dot / scale 줄은 dot product를 sqrt(dim)으로 나누어 softmax가 포화되는 것을 방지합니다. softmax의 결과는 attention 가중치입니다 — 고려 중인 token과 많이 관련된 token이 높은 가중치를 갖고, 이는 value vector를 혼합하는 데 사용됩니다.

5. Scaling Laws (2020) — loss가 거듭제곱 법칙에 따라 감소

OpenAI는 다음을 발견했습니다: parameter, 데이터 또는 compute를 기하급수적으로 증가시키면, loss는 꽤 부드럽고 예측 가능한 power law 곡선을 따라 감소합니다 — 어떤 “마법”도 없고, 올바르게 scale하기만 하면 됩니다.

public class ScalingLawEstimator {

    // L(N) = (Nc / N)^alpha — N: 매개변수 수(십억), Nc, alpha: 경험적 상수
    public static double estimateLoss(double paramsBillion, double nc, double alpha) {
        return Math.pow(nc / paramsBillion, alpha);
    }
}

경험적 데이터에서 고정된 nc와 alpha를 사용하면, paramsBillion을 증가시키면 estimateLoss는 서서히 감소하지만 속도가 느려집니다 (수확 체감). 이것이 AI 연구실이 compute에 수억 달러를 투자하는 이유입니다: loss 감소는 예측 가능하므로 도박이 아닙니다.

주의: Scaling law는 무한하지 않습니다. 일정 수준 이후, loss를 조금 더 줄이려면 compute를 엄청나게 기하급수적으로 증가시켜야 합니다 — 이것이 업계가 단순히 “더 크게”하는 대신 데이터와 아키텍처 개선으로 방향을 전환하는 이유입니다.

6. GPT-3 (2020) — scale이 새로운 능력을 만들다

175억 개의 매개변수를 가진 GPT-3는 다음을 보여줍니다: 충분히 크면, 모델은 few-shot learning을 할 수 있습니다 — prompt에 몇 가지 예제만 필요하고, 가중치를 다시 fine-tune할 필요가 없습니다.

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>
@Service
public class FewShotClassifier {

    private final ChatClient chatClient;

    public FewShotClassifier(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    public String classifySentiment(String review) {
        String prompt = """
                리뷰: "상품이 좋고 배송이 빠릅니다" -> Positive
                리뷰: "상품이 결함이 있어 사용할 수 없습니다" -> Negative
                리뷰: "%s" ->
                """.formatted(review);
        return chatClient.prompt(prompt).call().content();
    }
}

prompt의 두 가지 예제는 context 내에서 모델에게 답변 형식을 “가르치는” 역할을 합니다 — 다시 학습할 필요가 없습니다. 이것이 바로 더 작은 모델이 잘 하지 못하는 emergent ability입니다.

7. InstructGPT & RLHF (2022) — 원래의 LLM을 유용한 조수로 변환하다

원래의 GPT-3는 다음 token을 예측하는 데는 능하지만 “지시”를 따르지 않습니다 — 주제를 벗어나거나 해로운 답변을 할 수 있습니다. InstructGPT는 Reinforcement Learning from Human Feedback (RLHF)을 사용합니다: 인간이 답변들의 순위를 매기고, 이를 통해 reward model을 학습한 다음, reward model을 사용해 PPO로 policy를 최적화합니다.

public record Completion(String text, double rewardScore) {}

public class RewardBasedSelector {

    public Completion pickBest(List<Completion> candidates) {
        return candidates.stream()
                .max(Comparator.comparingDouble(Completion::rewardScore))
                .orElseThrow();
    }
}

위의 코드는 마지막 단계만 설명합니다: reward model의 평가에 따라 가장 높은 reward를 가진 completion을 선택합니다. 실제로는 PPO가 policy model의 가중치를 점진적으로 업데이트해서 처음부터 높은 점수의 답변을 생성하도록 하므로, 나중에 선택할 필요가 없습니다.

주의: RLHF는 대량의 인간 레이블 데이터(순위)가 필요하며, 비용이 많이 들고 평가자의 편향에 영향을 받기 쉽습니다. DPO (Direct Preference Optimization) 같은 기술이 이후에 나와서 이 비용을 줄일 수 있게 했습니다.

8. ViT (2020) — 이미지도 하나의 “문장”이 될 수 있다

Vision Transformer는 이미지를 작은 patch로 나누고 (예: 16×16 픽셀), 각 patch를 vector로 평탄화한 다음, 이들을 token으로 취급해 Transformer encoder에 넣습니다 — 텍스트 처리와 정확히 같은 아키텍처입니다.

public class PatchEmbeddingCalculator {

    public static int numPatches(int imageSize, int patchSize) {
        int gridSize = imageSize / patchSize;
        return gridSize * gridSize;
    }
}

224×224 이미지와 16×16 patch로, numPatches는 196을 반환합니다 — Transformer에 들어가는 196개의 token “문장”과 같습니다. 이런 방식 덕분에 ViT는 NLP에서 이미 입증된 attention 기법을 전부 vision 문제에 재사용할 수 있고, 학습 데이터가 충분히 크면 CNN을 능가합니다.

9. DDPM (2020) — 노이즈를 더하고 제거하는 법을 배우다

Denoising Diffusion Probabilistic Model은 모든 현대 diffusion model의 기초입니다: forward process는 여러 단계에 걸쳐 Gaussian 노이즈를 점진적으로 이미지에 추가하고, reverse process는 그 노이즈를 예측하고 제거하는 법을 배워 원본 이미지를 복원합니다.

public class ForwardDiffusion {

    public static double[] addNoise(double[] x0, double alphaBarT, Random random) {
        double[] xt = new double[x0.length];
        for (int i = 0; i < x0.length; i++) {
            double noise = random.nextGaussian();
            xt[i] = Math.sqrt(alphaBarT) * x0[i] + Math.sqrt(1 - alphaBarT) * noise;
        }
        return xt;
    }
}

alphaBarT는 단계 t에서 원본 이미지 대 노이즈의 비율을 제어합니다: 1에 가깝다는 것은 여전히 원본 이미지에 가깝다는 뜻이고, 0에 가깝다는 것은 순수 노이즈에 가깝다는 뜻입니다. 이미지 생성 모델은 이 과정의 역을 배웁니다 — 순수 노이즈에서 시작해, 단계별로 의미 있는 이미지를 “노이즈 제거”합니다.

10. Latent Diffusion (2021) — 압축된 공간에서 노이즈 제거

픽셀에서 직접 diffusion을 실행하는 것은 매우 많은 계산이 필요합니다. Latent Diffusion은 autoencoder를 사용해 이미지를 더 작은 latent space로 압축하고, 전체 denoising 과정을 거기서 실행한 다음, 다시 이미지로 decode합니다 — 이것이 Stable Diffusion의 설계입니다.

public class LatentCompressionCalculator {

    public static long pixelCount(int width, int height, int channels) {
        return (long) width * height * channels;
    }
}
pixelCount(512, 512, 3) = 786_432   // pixel space
pixelCount(64, 64, 4)   = 16_384    // latent space

약 48배의 압축률은 diffusion이 훨씬 더 빠르게 실행되도록 하면서도 이미지 품질을 유지하는데, 이는 pixel space의 대부분 “중복” 정보 (단색 영역, 작은 세부사항)가 autoencoder의 압축 과정에서 제거되기 때문입니다.

11. CLIP (2021) — 이미지와 텍스트를 하나의 공간에 연결하다

CLIP은 수억 개의 (이미지, caption) 쌍에 대해 contrastive learning을 학습합니다: 올바른 쌍은 embedding이 가깝고, 잘못된 쌍은 멀어집니다. 결과는 text와 이미지를 cosine similarity로 비교할 수 있는 공유 공간입니다.

@Service
public class ImageTextMatcher {

    private final EmbeddingModel embeddingModel;

    public ImageTextMatcher(EmbeddingModel embeddingModel) {
        this.embeddingModel = embeddingModel;
    }

    public double similarity(String caption, float[] imageEmbedding) {
        float[] textEmbedding = embeddingModel.embed(caption);
        return cosine(textEmbedding, imageEmbedding);
    }

    private double cosine(float[] a, float[] b) {
        double dot = 0, normA = 0, normB = 0;
        for (int i = 0; i < a.length; i++) {
            dot += a[i] * b[i];
            normA += a[i] * a[i];
            normB += b[i] * b[i];
        }
        return dot / (Math.sqrt(normA) * Math.sqrt(normB));
    }
}

similarity는 caption이 이미지 내용과 일치하면 1에 가까운 값을 반환하고, 관련이 없으면 0 또는 음수에 가까운 값을 반환합니다. 이것이 많은 전자상거래 제품에서 사용 중인 자연어 설명으로 이미지를 찾는 (text-to-image search)의 메커니즘입니다.

12. Chain-of-Thought (2022) — 한 문장의 프롬프트가 추론의 열쇠를 열다

prompt에 “단계별로 생각해봐”라는 구절만 추가하면, 큰 LLM들은 복잡한 문제를 자동으로 작은 단계로 나누며, 직접 답변하는 것에 비해 계산 오류와 논리 오류를 훨씬 줄입니다.

public String solveWithReasoning(ChatClient chatClient, String question) {
    String prompt = """
            답변하기 전에 단계별로 생각해봐.
            질문: %s
            추론:
            """.formatted(question);
    return chatClient.prompt(prompt).call().content();
}

모델에게 답변을 확정하기 전에 “추론”하도록 요청하는 것은 중간 token을 생성하도록 강제하므로 버팀목이 됩니다 — 인간이 어려운 질문에 답변하기 전에 초안을 작성하는 방식과 유사합니다.

주의: Chain-of-Thought는 생성되는 token의 수를 증가시켜 더 높은 비용과 latency를 유발합니다. 간단한 질문 (직접 사실 조회)의 경우, CoT를 강제하는 것은 단지 속도를 떨어뜨리고 정확도를 개선하지 않습니다.

13. RAG (2020) — 검색한 후 생성, 더 이상 지어내지 않다

Retrieval-Augmented Generation은 LLM에 답변을 생성하기 전에 실제 문서를 검색하는 단계를 추가하여, hallucination을 줄이고 원래의 학습 범위를 벗어난 최신 데이터를 기반으로 답변할 수 있게 합니다.

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>
@Service
public class DocumentAnsweringService {

    private final VectorStore vectorStore;
    private final ChatClient chatClient;

    public DocumentAnsweringService(VectorStore vectorStore, ChatClient.Builder builder) {
        this.vectorStore = vectorStore;
        this.chatClient = builder.build();
    }

    public String answer(String question) {
        List<Document> relevant = vectorStore.similaritySearch(
                SearchRequest.query(question).withTopK(3));

        String context = relevant.stream()
                .map(Document::getContent)
                .collect(Collectors.joining("\n---\n"));

        String prompt = """
                다음 문서를 기반으로 질문에 답변하세요. 정보가 없으면 "알 수 없음"이라고 하세요.
                문서:
                %s

                질문: %s
                """.formatted(context, question);

        return chatClient.prompt(prompt).call().content();
    }
}

similaritySearch는 질문과 가장 의미가 유사한 3개의 문서 구간을 가져와 prompt에 context로 연결한 다음, 모델이 학습 중 “모호하게 기억”하는 대신 그 context를 기반으로 답변하도록 강제합니다.

class DocumentAnsweringServiceTest {

    @Test
    void shouldReturnUnknownWhenNoRelevantDocument() {
        VectorStore emptyStore = mock(VectorStore.class);
        when(emptyStore.similaritySearch(any())).thenReturn(List.of());

        ChatClient stubClient = mock(ChatClient.class, RETURNS_DEEP_STUBS);
        when(stubClient.prompt(anyString()).call().content()).thenReturn("알 수 없음");

        ChatClient.Builder builder = mock(ChatClient.Builder.class);
        when(builder.build()).thenReturn(stubClient);

        DocumentAnsweringService service = new DocumentAnsweringService(emptyStore, builder);

        String result = service.answer("회사는 언제 설립되었나요?");

        assertThat(result).contains("알 수 없음");
    }
}

이 테스트는 관련 문서를 찾지 못했을 때 답변이 “알 수 없음”을 인정해야 하며 지어낸 답변을 해서는 안 된다는 것을 확인합니다 — RAG의 grounding 목표와 정확히 일치합니다.

주의: 문서 저장소가 수십 페이지에 불과하다면, context window에 직접 넣는 것이 vector store가 있는 전체 RAG 파이프라인을 구축하는 것보다 더 간단하고 저렴합니다. RAG는 데이터가 크거나 자주 변경될 때만 사용할 가치가 있습니다.

14. 결론

이 12개의 논문은 독립적이지 않습니다 — 서로 위에 지어져 있습니다: Transformer는 GPT-3과 ViT의 기초이고, scaling laws는 GPT-3이 작동하는 이유를 설명하고, RLHF는 GPT-3을 ChatGPT로 변환하고, DDPM은 Latent Diffusion의 기원이며, CLIP은 diffusion model이 text prompt를 이해할 수 있게 하는 연결 고리입니다. 각 조각을 깊이 이해하면 새로운 논문을 더 빠르게 읽을 수 있습니다. 왜냐하면 현재의 대부분 “새로운” 아이디어는 이 기초적인 것들의 변형이거나 조합이기 때문입니다.

llmdeep-learningtransformerragai