GROWTH LOG STORIES

함께 만든 성장을,
기록으로 연결합니다.

AI와 함께 배우고, 직접 만들고, 서로 나눈
그로스로그 멤버들의 생생한 성장 기록을 만나보세요.

성장 기록
5기+이어진 커뮤니티
HEALTHIO
PROJECT

사진 한 장으로 시작하는 건강 루틴, Health IO

대표 이야기 읽기

ALL STORIES

성장의 순간을
한곳에서 만나보세요.

프로젝트의 시작부터 모임에서 나눈 인사이트까지,
그로스로그가 함께 만든 기록을 모았습니다.
개의 성장 기록

성장일지

[GL 4기 5회차 ①] 뜻을 기계에 넘기는 법 — CustomException 설계, 구조화된 데이터, 선형대수와 AI 🌱

GROWTH LOG🌱 2026. 8. 11. 00:09

안녕하세요! 그로스로그 입니다!😊 🌱

사람은 읽는데 기계는 못 읽는 정보가 있습니다. 그걸 기계가 읽을 수 있게 옮긴 세 편이에요.

성장일지는 멤버들이 2주에 한 번씩 자신의 성장을 기록하는 활동이에요. 매 회차 운영진이 모든 글을 읽고, 그중 특히 마음에 남은 글을 함께 골라 큐레이션합니다.

5회차는 백엔드 · SEO · 수학입니다. 층위가 다 다른데, 놓고 보니 같은 일을 하고 있었어요.

 

01

🐆 [SpringBoot] API 오류 응답을 깔끔하게 만드는 CustomException 작성법

김태홍 님

이런 내용이에요
IllegalArgumentException으로 시작했다가 한계를 만난 이야기로 시작합니다. 그리고 CustomException + ErrorCode enum + @RestControllerAdvice 구조로 옮겨가요.
성공과 실패가 같은 JSON 모양으로 나가게 만드는 것이 목표입니다. CommonResponse로 통합해요.
마지막에 스스로 던지는 질문이 하나 남아 있습니다. "성공 시 code가 필수로 null로 나오는 것이 안 불편한가?"

부제가 재미있어요. "스쳐지나가듯 궁금한 의문점을 곁들인 기본 개념." 정리하면서 든 의문을 그때그때 적어둔 글입니다.

조금 더 들어가 보면

출발점의 자기 고백이 좋습니다.

if (userRepository.existsByUsername(dto.getUsername())) {
    throw new IllegalArgumentException("중복된 아이디입니다.");
}
"(오류가 오류지 뭐 라고 생각한 나)"

대부분 여기서 시작합니다. 예외를 던지는 게 목적이니 아무거나 던지면 되지 않나 싶죠. 실제로 이 코드도 잘 동작해요. 500이든 400이든 에러가 나가긴 하니까요.

문제는 받는 쪽에서 생깁니다.

📮 프론트가 문자열을 파싱하게 되는 순간

글이 문제를 셋으로 정리했습니다. 의미가 불명확하다 / 구분하기 어렵다 / 포맷이 일관되지 않다.

두 번째가 특히 실질적이에요.

"IllegalArgumentException이 다양한 곳에서 발생할 경우, 어떤 원인인지 컨트롤러 혹은 프론트에서 구분하기 힘듭니다."

무슨 일이 벌어지냐면, 프론트엔드가 이런 코드를 쓰게 됩니다.

if (error.message === "중복된 아이디입니다") {
  // 아이디 입력란에 표시
}

메시지 문자열로 분기하게 되는 겁니다.

이게 왜 나쁜지는 명확해요. 백엔드에서 문구를 "이미 사용 중인 아이디입니다"로 다듬는 순간 프론트가 조용히 망가집니다. 에러도 안 나고, 그냥 분기를 안 타요. 문구를 고치는 건 언제든 일어나는 일인데, 그때마다 프론트가 깨집니다.

그리고 다국어를 붙이면 즉시 무너집니다. 영어로 나가면 그 if는 영원히 거짓이 되니까요.

🏷️ ErrorCode — 사람용과 기계용을 분리하기

해결책의 핵심이 여기 있습니다.

public enum ErrorCode {
    DUPLICATE_USERNAME(HttpStatus.CONFLICT, "DUPLICATE_USERNAME", "이미 존재하는 아이디입니다."),
    INVALID_REQUEST(HttpStatus.BAD_REQUEST, "INVALID_REQUEST", "잘못된 요청입니다."),
    INTERNAL_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "INTERNAL_ERROR", "서버 오류");
}

세 값이 각각 다른 독자를 향합니다. 글이 정확히 그렇게 설명해요.

"HttpStatus → HTTP 프로토콜 기준 상태 코드 / code → 프론트에서 분기하기 위한 시스템 코드 / message → 사용자에게 보여줄 문구"

정리하면요.

읽는 쪽 바뀔 수 있나
HttpStatus 프록시·모니터링·브라우저 거의 안 바뀜
code 프론트엔드 코드 바뀌면 안 됨
message 사람 언제든 바뀜

message는 언제든 바뀌고, code는 안 바뀝니다. 이 둘을 분리했기 때문에 문구를 마음대로 다듬어도 프론트가 안 깨져요.

🧩 HttpStatus와 code가 둘 다 필요한 이유

글이 붙인 메모가 이 구조를 잘 설명합니다.

"code는 HttpStatus(400, 409 등) 큰 범주 안에서 상세 내용이라고 생각. Ex) 409 하위에는 DUPLICATE_ID, DUPLICATE_NICKNAME, PASSWORD_UNCHANGED 등"

층이 두 개인 거예요.

HTTP 상태 코드는 종류가 정해져 있고 수가 적습니다. 409(충돌)는 있지만 "아이디 중복 409"와 "닉네임 중복 409"를 구분할 방법이 없어요. 그런데 프론트는 어느 입력란에 빨간 글씨를 띄울지 알아야 합니다.

그래서 HTTP 층은 인프라가 읽고, code 층은 애플리케이션이 읽습니다.

이게 실무에서 왜 중요하냐면 — 로드밸런서, 모니터링 도구, 브라우저 캐시는 전부 HTTP 상태 코드만 봅니다. 그래서 상태 코드를 제대로 주는 게 여전히 중요해요. 400을 줘야 할 걸 200으로 주면서 본문에 "success": false만 넣으면, 모니터링 대시보드에서 에러율이 0%로 보입니다.

🌐 전역 처리 — 한 곳으로 모으기

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(CustomException.class)
    public ResponseEntity<CommonResponse> handleCustomException(CustomException ex) {
        ErrorCode error = ex.getErrorCode();
        return ResponseEntity
                .status(error.getStatus())
                .body(CommonResponse.error(error.getMessage(), error.getCode()));
    }
}

컨트롤러마다 try-catch를 쓰지 않아도 됩니다. 서비스 계층에서 그냥 던지면 여기서 받아요.

그래서 서비스 코드가 이렇게 깔끔해집니다.

if (userRepository.existsByUsername(dto.getUsername())) {
    throw new CustomException(ErrorCode.DUPLICATE_USERNAME);
}

응답 형식에 대한 지식이 서비스 계층에서 사라졌어요. 서비스는 "무엇이 잘못됐는지"만 말하고, 어떻게 응답할지는 한 곳에서 정합니다. 나중에 응답 포맷을 바꿔야 하면 이 클래스 하나만 고치면 돼요.

🤔 마지막 질문에 대해

글 끝에 이런 메모가 붙어 있습니다.

"🐆🐆🐆 성공시 code가 필수로 null로 나오는 것이 안 불편한가? (그냥 나의 생각) 🐆🐆🐆"

좋은 질문이라서 조금 이어가 볼게요.

// 성공
{ "success": true, "message": "회원가입 완료", "code": null, "data": {...} }

// 실패
{ "success": false, "message": "이미 존재하는 아이디입니다.", "code": "DUPLICATE_USERNAME", "data": null }

성공엔 codenull, 실패엔 datanull입니다. 늘 절반이 비어 있어요.

이건 사실 의도된 거래입니다. 얻는 것과 잃는 것이 명확해요.

얻는 것 — 프론트에서 응답 타입이 하나입니다. 성공이든 실패든 같은 객체로 받아서 success만 보면 돼요. 응답 파싱 코드를 한 벌만 쓰면 됩니다.

잃는 것 — 타입스크립트 관점에서 codestring | null이 됩니다. 성공 분기 안에서도 null 체크를 해야 할 수 있어요.

둘 다 실무에서 쓰이는 방식이고, 판단 기준은 대개 이렇습니다.

· 응답 처리를 한 군데로 모으고 싶다 → 지금 구조가 낫습니다

· 타입 안전성을 더 챙기고 싶다 → 성공/실패를 아예 다른 타입으로 나누고, 프론트에서 HTTP 상태로 먼저 갈라내는 방식이 있습니다

그리고 "불편한데 왜 이렇게 하지?"를 넘기지 않고 적어둔 것 자체가 좋았습니다. 남이 만든 구조를 따라 쓰면서도 어색한 지점을 기록해두면, 나중에 자기 기준으로 고를 수 있게 되거든요.

큐레이터 노트

출발점을 숨기지 않은 글이라 뽑았습니다.

CustomException 정리 글은 많습니다. 대부분 완성된 구조부터 시작해요. 이 글은 "오류가 오류지 뭐 라고 생각한 나"로 시작합니다. 왜 이 구조가 필요한지가 몸으로 설명되는 거예요.

그리고 "예외 처리를 왜 해야 하는가?"라는 절을 따로 뒀습니다. 방법 앞에 이유를 놓은 구성이 좋았어요.

절마다 붙은 이모지 소제목도 이 글의 성격을 잘 보여줍니다. 🤥(시작한 이유와 한계) 🧐(왜 해야 하는가) ✅(결론). 그리고 🐆 세 마리로 감싼 의문 메모까지. 읽는 사람을 의식하고 쓴 글입니다.

무엇보다 끝을 열어둔 것이 좋았습니다. "이게 정답입니다"로 닫지 않고 "이거 안 불편한가?"로 끝나요. 정리 글이 대개 놓치는 태도입니다.

원문 읽으러 가기  ↗velog.io/@kdjun1825/SpringBoot-1
 

02

🔍 구조화된 데이터로 SEO 끌어올리기

정민아 님

정민아 님 원문에 실린 이미지
이런 내용이에요
실제 서비스에 적용한 구조화된 데이터(Structured Data) 정리입니다. Schema.org 문법을 JSON-LD로 넣는 방법을 다뤄요.
타입별로 나눠 설명합니다. 루트 페이지에는 WebSite · Organization · SiteNavigationElement, 콘텐츠 상세에는 EventBlogPosting.
그리고 숫자가 있습니다. 메타데이터만 적용했던 시기 대비, 구조화된 데이터까지 넣은 뒤 검색 유입이 70% 이상 늘었다고요.

문체가 아주 편안합니다. "구글 짱! 글로벌 대기업 짱!" 같은 문장이 섞여 있어요. 그런데 내용은 정확합니다.

조금 더 들어가 보면

구조화된 데이터가 무엇을 해결하는지부터 보겠습니다.

HTML로 이런 페이지를 만들었다고 해볼게요.

<div class="title">서울 재즈 페스티벌</div>
<div class="date">2025년 11월 20일 ~ 12월 3일</div>
<div class="place">올림픽공원</div>

사람은 다 읽습니다. 제목이 뭐고, 언제고, 어디인지요.

크롤러는 못 읽습니다. class="date"는 개발자가 붙인 이름일 뿐이에요. 다른 사이트에서는 class="when"이고 또 다른 데선 class="txt-02"입니다. 표준이 없으니 기계가 알 방법이 없어요.

구조화된 데이터가 이 문제를 풉니다.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Event",
  "name": "이벤트 명",
  "startDate": "2025-11-20 (이벤트 시작 날짜)",
  "endDate": "2025-12-03 (이벤트 종료 날짜)",
  "location": {
    "@type": "Place",
    "name": "장소 이름",
    "address": "장소의 주소"
  }
}
</script>

startDate라는 이름은 전 세계가 같이 씁니다. Schema.org가 정한 표준이거든요. 그래서 크롤러가 이 페이지를 "11월 20일에 시작하는 행사"로 정확히 이해합니다.

화면에는 안 보입니다. 오직 기계를 위해 붙이는 데이터예요.

🏷️ 1회차 시맨틱 태그와 이어지는 이야기

이 글을 4기 1회차 진현주 님의 시맨틱 태그 글과 나란히 놓으면 재미있습니다.

시맨틱 태그는 "여기가 본문이고 저기가 메뉴다"를 알려줍니다. 구조를 알려주는 거예요.

구조화된 데이터는 한 발 더 갑니다. "이 페이지는 행사이고, 11월 20일에 시작하며, 올림픽공원에서 열린다"를 알려줘요. 내용을 알려주는 겁니다.

같은 방향으로 한 층 더 들어간 셈입니다. 둘 다 화면에는 안 보이지만 기계가 읽는 층이에요.

📊 "SEO에 좋다"를 정확하게 말하기

이 글에서 가장 신뢰가 갔던 부분은 효과를 과장하지 않은 대목입니다.

"구글의 검색 최적화 관련 문서에 명확하게 구조화된 데이터가 메타데이터보다 낫다거나 검색엔진 최적화에 효율적이라는 말은 없지만"

공식 문서에 없는 말을 없다고 적었습니다. SEO 글이 흔히 빠지는 함정 하나를 정확히 피해갔어요.

대신 문서에 실제로 있는 말을 인용합니다. "콘텐츠의 의미를 정확하게 파악하는 데 큰 도움을 준다", "더욱 눈길을 끄는 검색결과를 제공", 그리고 리치 결과.

리치 결과가 핵심 개념입니다. 검색 결과에 별점, 가격, 행사 날짜, 조리 시간 같은 게 붙어 나오는 형태요. 이게 왜 중요하냐면 — 순위가 안 올라가도 클릭률이 올라갑니다.

같은 3위라도 제목만 있는 결과와 날짜·장소가 붙은 결과는 눌리는 비율이 다릅니다. 구조화된 데이터의 효과는 "위로 올려준다"보다 "눈에 띄게 해준다"에 가까워요.

그리고 마무리도 정직합니다.

"구조화된 데이터를 단순히 적용하기만 한다고 해서 데이터가 최적화되어 있지도 않은데 검색 유입 지표가 개선될 리가 없다는 뜻이다."

📈 그런데 숫자는 있다

효과를 과장하지 않으면서도 자기 데이터는 명확히 댑니다.

"당장 우리 서비스만 해도 메타데이터만 적용했던 6월 중순 이전에 비해 구조화된 데이터까지 적용한 6월 하순 이후에 검색 유입이 70% 이상 늘었을 만큼 효과를 보았다."

시점을 갈라서 비교했어요. "도움이 됐다"가 아니라 "6월 중순 이전 대비 6월 하순 이후 70% 이상"입니다. 구간과 수치가 다 있습니다.

"일반적으로 이렇다"고 주장하지 않으면서 "우리는 이랬다"를 정확히 남긴 균형이 좋았어요.

🗂️ 타입을 고르는 감각

실무 적용 부분이 특히 참고할 만합니다.

루트 페이지에는 세 가지를 함께 넣었어요.

· WebSite — 사이트 이름, 주소, 그리고 사이트 내 검색(SearchAction)

· Organization — 회사명, 홈페이지, 로고

· SiteNavigationElement — 주요 메뉴 구조

글의 설명이 정확합니다. "검색엔진의 로봇이 가장 먼저 방문하는 루트 페이지에는 사이트의 정체성과 내비게이션 구조를 명시하는 것이 좋다."

WebSitepotentialAction 부분이 재미있어요.

"potentialAction": {
  "@type": "SearchAction",
  "target": "서비스 내 검색페이지 링크?q={search_term_string}",
  "query-input": "required name=search_term_string"
}

"우리 사이트 안에서 이렇게 검색할 수 있다"를 알려주는 겁니다. 잘 인식되면 구글 검색 결과에 사이트 내 검색창이 함께 뜰 수 있어요.

상세 페이지는 콘텐츠 성격에 따라 갈랐습니다. 공연·축제는 Event, 칼럼·정보성 콘텐츠는 BlogPosting으로요.

여기서 판단이 좋았던 지점이 있습니다.

"우리 서비스가 블로그 서비스는 아니긴 하지만, 서비스 내 콘텐츠 중에 칼럼이나 정보 콘텐츠 성격이 강한 콘텐츠가 있었기 때문에 BlogPosting 타입을 적용했다."

타입 이름이 아니라 콘텐츠 성격을 봤어요. 블로그가 아니어도 글의 성격이 그러면 BlogPosting이 맞습니다. Schema.org 타입은 서비스 종류가 아니라 콘텐츠 종류를 가리키니까요.

그리고 실수하기 쉬운 곳도 두 번 짚어줍니다.

"이벤트 타입에서 사용되는 날짜 속성에는 반드시 yyyy-MM-dd 형식을 써야 한다."

이게 왜 강조되냐면 — 형식이 틀려도 페이지는 멀쩡히 뜹니다. 에러가 안 나요. 그냥 크롤러가 그 필드를 조용히 버립니다. 앞서 본 "에러가 안 나서 모르고 지나가는 문제"의 또 다른 사례예요.

🇰🇷 네이버 이야기가 들어 있는 것

한국 서비스에 실질적인 정보가 하나 있습니다.

"아무래도 국내 서비스이다 보니 네이버 검색이 잘 되면 더 좋긴 하지만, 그쪽은 자사 서비스 노출이 더 우선시 되니까(^^)"

괄호 안의 (^^)가 많은 걸 말하죠. 그러면서도 네이버 웹마스터 가이드를 찾아 인용합니다. 네이버도 구조화된 데이터를 수집한다고요.

둘 다 확인하고 나서 우선순위를 정한 접근입니다. 감으로 "구글부터 하자"가 아니라, 지표를 보고 "개선 효과가 눈에 보이는 쪽"을 먼저 잡았어요.

큐레이터 노트

실제로 돌아가는 서비스에서 나온 글이라 뽑았습니다.

SEO 글은 원론이 많습니다. "메타 태그를 잘 쓰세요", "콘텐츠가 중요합니다" 같은 것들요. 이 글은 "우리 서비스에 이 타입을 적용했고, 이런 이유였고, 유입이 이만큼 늘었다"입니다.

아직 안 끝났다고 적은 것도 좋았어요.

"최근에는 Event 타입이 적용된 콘텐츠의 형식이 또 살짝 나뉘어지는 바람에 다른 타입을 추가하여 개선할 예정이다. SEO 개선 작업은 끝이 없는 듯...^_T"

완결된 성공담이 아니라 진행 중인 작업입니다. 서비스가 바뀌면 구조화된 데이터도 다시 손봐야 하고, 검색엔진 방침도 계속 바뀐다고요. 한 번 하고 끝나는 일이 아니라는 걸 정확히 전달합니다.

문체도 인상적이었어요. "다들 검색 유입 지표에 고통 그만 받자구요.."로 끝납니다. 정보는 정확한데 읽기는 편해요. 정확한 것과 딱딱한 것이 같은 게 아니라는 걸 보여주는 글입니다.

원문 읽으러 가기  ↗velog.io/@minami/구조화된-데이터로-SEO-끌어올리기
 

03

📐 선형대수와 AI

황대웅 님

이런 내용이에요
"AI의 거의 모든 부분이 선형대수 위에서 흘러간다"는 명제를 풀어낸 글입니다.
세 갈래로 나눠요. 표현의 언어로서 — 이미지도 텍스트도 음성도 결국 벡터가 된다. 변환의 도구로서 — PCA 같은 특징 추출이 고유값·고유벡터로 이뤄진다. 계산 효율로서 — 행렬 형태여야 GPU가 빠르다.
그리고 구체적인 예시로 내려옵니다. 가중치 행렬, 컨볼루션의 행렬 표현, 임베딩, 그리고 Transformer의 어텐션.

수식이 거의 없습니다. "어떤 관점으로 바라봐야 하는지"를 정리한 글이에요.

조금 더 들어가 보면

글의 문제의식이 첫 문단에 있습니다.

"AI를 단순히 '데이터 넣고 결과 나오는 마술'처럼 보기보다는, '데이터가 벡터로 조직되고, 모델이 선형변환을 통해 특징을 뽑아내며, 최종 출력은 다시 벡터 공간에 매핑된다'는 시각으로 보면 훨씬 명료해집니다."

"마술"에서 "구조"로 옮기자는 제안입니다.

🔢 왜 전부 벡터가 되는가

첫 번째 갈래가 이 글의 핵심이라고 봤습니다.

"이미지라면 픽셀값, 텍스트라면 임베딩(embedding) 벡터, 음성이면 스펙트로그램 등이죠. 이때 모든 것은 벡터 또는 행렬 형태로 표현됩니다."

이게 왜 중요한지 조금 풀어보면요.

컴퓨터는 "고양이 사진"을 다룰 수 없습니다. 숫자만 다룰 수 있어요. 그래서 사진을 숫자로 바꿔야 합니다. 픽셀마다 밝기 값을 늘어놓으면 되죠. 이게 벡터입니다.

그런데 숫자로 바꾸는 것 자체가 목적이 아닙니다. 진짜 이유는 따로 있어요.

벡터가 되면 '거리'와 '방향'이 생깁니다.

두 벡터가 얼마나 가까운지 계산할 수 있게 되고, 그러면 "비슷하다"를 숫자로 말할 수 있게 됩니다. 글이 짚은 그대로예요.

"이 벡터 공간 상에서 유사도 계산(inner product)이나 거리(distance)가 이루어지는 것은 모두 선형대수의 언어입니다."

"비슷하다"는 원래 사람의 감각입니다. 그걸 계산 가능한 숫자로 옮긴 게 임베딩이에요. 그래서 검색이 되고, 추천이 되고, 번역이 됩니다.

🧮 한 층은 곱셈 하나

신경망의 정체를 짚은 부분이 명쾌합니다.

출력 = 가중치 행렬 × 입력 벡터 + 편향 벡터
"이 단순한 식에서부터 AI 모델의 동작이 시작됩니다."

신경망 한 층이 결국 곱셈 하나와 덧셈 하나라는 겁니다. 딥러닝이라는 이름이 주는 인상보다 훨씬 단순해요.

그리고 이 식의 의미를 이렇게 설명합니다.

"이 W는 입력 공간(input space)에서 출력 공간(output space)으로의 선형사상(linear map)을 나타냅니다."

행렬은 숫자를 담은 표가 아니라 '공간을 옮기는 함수'라는 관점이에요. 학교에서 행렬을 계산 규칙으로만 배우면 이 감각이 안 생깁니다. AI 맥락에서는 이 관점이 훨씬 유용해요.

🧬 컨볼루션이 행렬곱이라는 이야기

구체적 예시 중에서 이게 가장 흥미로웠습니다.

"이미지 처리에서 컨볼루션 연산은 보통 슬라이딩 윈도우 방식으로 이루어지지만, 이를 'Toeplitz 행렬' 형태로 바꾸어 생각하면 사실상 행렬곱으로 환원할 수 있습니다."

CNN을 배우면 보통 필터가 이미지 위를 스르륵 미끄러지는 그림으로 이해합니다. 직관적이죠.

그런데 그 미끄러지는 동작을 행렬 하나로 펼쳐 쓸 수 있다는 겁니다. 그러면 CNN도 결국 아까 그 식(가중치 행렬 × 입력)과 같은 형태가 돼요.

이게 왜 실용적이냐면 — GPU는 행렬곱을 아주 잘합니다. 미끄러지는 반복문은 잘 못하고요. 그래서 실제 딥러닝 라이브러리는 컨볼루션을 행렬곱으로 바꿔서 돌립니다. 직관적인 설명과 실제 구현이 다른 흔치 않은 예예요.

⚡ 형태를 맞추면 하드웨어가 빨라진다

세 번째 갈래가 실무적입니다.

"벡터화(vectorization)로 루프를 줄일 수 있고, 병렬처리에 적합합니다."
"행렬 곱(matrix multiplication)은 BLAS, cuBLAS 등에서 매우 최적화되어 있고"

같은 계산인데 형태만 바꿔도 훨씬 빨라진다는 이야기입니다.

파이썬으로 반복문을 돌려 원소를 하나씩 더하는 것과, NumPy 배열끼리 한 줄로 더하는 것은 결과가 같아도 속도가 수십~수백 배 차이가 납니다. 후자는 최적화된 C 코드로 내려가고, GPU라면 수천 개 코어가 동시에 계산하니까요.

GPU가 AI에 쓰이는 이유가 여기 있습니다. 원래 그래픽 처리용으로 만들어진 칩인데, 그래픽 처리가 곧 행렬 연산이었거든요. AI가 우연히 같은 모양의 계산을 필요로 했던 겁니다.

🤖 어텐션도 결국 행렬곱

마지막 예시가 시의적절합니다.

"Transformer 내부에서 '쿼리(query), 키(key), 값(value)' 행렬이 만들어지고, 어텐션 스코어는 이 행렬들의 곱과 소프트맥스 연산으로 나옵니다."

요즘 AI의 거의 전부가 Transformer 위에 서 있는데, 그 핵심 장치도 행렬곱이라는 겁니다.

조금만 풀어보면 이렇습니다. 어텐션은 "지금 이 단어가 앞의 어느 단어를 봐야 하는가"를 정하는 장치예요. 그걸 계산하는 방법이 — 쿼리와 키를 곱해서 유사도를 내고, 소프트맥스로 비중을 정하고, 그 비중으로 값을 섞는 것입니다.

"어느 단어를 봐야 하는가"라는 의미 문제벡터끼리 곱하는 계산 문제로 바뀌어 있어요. 이 글이 처음에 말한 "모든 것이 벡터로 조직된다"가 여기서 완성됩니다.

큐레이터 노트

연결을 만들어주는 글이라 뽑았습니다.

선형대수는 배울 때 "이걸 어디에 쓰나"가 가장 막막한 과목입니다. 행렬 곱셈 규칙을 외우고, 고유값을 구하고, 대각화를 하는데 용도를 모른 채 합니다.

이 글은 그 자리마다 쓰임을 붙여줘요. 행렬곱은 신경망 한 층, 고유값·고유벡터는 PCA, 내적은 임베딩 유사도, 그리고 그 전부가 Transformer 안에 있다고요. 배운 것과 쓰이는 것을 이어붙입니다.

수식을 최소한으로 쓴 것도 판단이라고 봤습니다. 계산법을 알려주는 글이 아니라 관점을 옮겨주는 글이니까요. 실제로 하나 있는 식(출력 = 가중치 행렬 × 입력 벡터 + 편향 벡터)이 꼭 필요한 자리에 있습니다.

마지막 문장이 이 글의 목적을 잘 담고 있어요.

"AI를 위한 수학, 어렵지만 꼭 필요한 문법입니다. 선형대수라는 언어를 익히고 나면, AI의 내부가 조금 더 선명하게 보이기 시작할 것입니다."

"문법"이라는 비유가 정확합니다. 문법을 몰라도 말은 할 수 있어요. AI 라이브러리를 갖다 쓰는 것도 그렇습니다. 그런데 왜 그렇게 되는지 알려면 결국 문법을 봐야 하죠.

원문 읽으러 가기  ↗medium.com/@hdanieldae/선형대수와-ai-4081352bafc1

💡 세 편을 겹쳐 읽으며

스프링 예외 처리, 검색 최적화, 선형대수. 층위가 완전히 다릅니다.

그런데 셋 다 같은 일을 했어요. 사람은 읽는데 기계는 못 읽는 정보를, 기계가 읽는 형태로 옮겼습니다.

"중복된 아이디입니다"사람이 읽는 문장입니다. 프론트엔드 코드가 이 문자열을 비교하게 되면 문구 한 번 다듬을 때마다 조용히 깨져요. 그래서 DUPLICATE_USERNAME이라는 기계용 이름을 따로 만듭니다. 사람용 문구와 기계용 코드를 분리한 거예요.

<div class="date">2025년 11월 20일</div>사람은 다 읽습니다. 크롤러는 못 읽어요. class 이름은 개발자 마음이니까요. 그래서 "startDate": "2025-11-20"이라는 전 세계 공통 이름으로 다시 적어줍니다.

고양이 사진은 사람이 한눈에 알아봅니다. 컴퓨터는 못 알아봐요. 그래서 벡터로 바꿉니다. 그러면 "비슷하다"가 거리 계산이 되고, "어디를 봐야 하나"가 행렬곱이 됩니다.

세 번 다 원래 정보는 그대로예요. 오류의 종류도, 행사 날짜도, 사진의 내용도 바뀌지 않았습니다. 읽을 수 있는 형태로 다시 적었을 뿐이에요.

그리고 셋 다 화면에는 안 보입니다. code 필드는 사용자가 볼 일이 없고, JSON-LD는 렌더링되지 않고, 임베딩 벡터는 아무도 눈으로 안 봐요. 눈에 안 보이는 층에 의미를 심어두는 일입니다.

4기 1회차의 시맨틱 태그 이야기와 정확히 같은 계열이에요. 화면이 똑같은데도 구조를 고민하는 이유 — 화면 말고 다른 걸 읽는 존재가 있기 때문입니다.

이번 회차에서 가져갈 것

1. 에러 메시지로 분기하게 두지 말 것.

프론트가 error.message === "중복된 아이디입니다"를 쓰기 시작하면, 문구를 다듬는 순간 조용히 깨집니다. 다국어를 붙이면 즉시 무너지고요. code 필드를 따로 주세요.

2. 사람용과 기계용을 분리하라.

message는 언제든 바뀌고 code는 바뀌면 안 됩니다. 바뀌는 것과 안 바뀌는 것을 같은 필드에 담지 마세요.

3. HTTP 상태 코드는 여전히 제대로 줘야 한다.

로드밸런서·모니터링·브라우저 캐시는 HTTP 상태만 봅니다. 200에 "success": false를 담으면 대시보드에서 에러율이 0%로 보여요.

4. 예외 처리는 @RestControllerAdvice 한 곳으로.

서비스 계층은 "무엇이 잘못됐는지"만 던지고, 어떻게 응답할지는 한 군데서 정하세요. 포맷을 바꿀 때 한 파일만 고치면 됩니다.

5. 구조화된 데이터는 화면에 없는 정보를 준다.

class="date"는 크롤러에게 아무 의미가 없습니다. "startDate": "2025-11-20"은 전 세계가 같이 쓰는 이름이에요.

6. 날짜 형식이 틀려도 에러가 안 난다.

yyyy-MM-dd가 아니면 그 필드가 조용히 버려집니다. 페이지는 멀쩡히 뜨니까 모르고 지나가기 쉬워요.

7. 타입은 서비스 종류가 아니라 콘텐츠 성격으로 고른다.

블로그 서비스가 아니어도 칼럼 성격이면 BlogPosting이 맞습니다.

8. 효과는 "순위 상승"보다 "리치 결과"로 이해하기.

구조화된 데이터가 순위를 올려준다는 공식 언급은 없습니다. 대신 검색 결과에 날짜·별점 같은 게 붙어 클릭률이 올라가요.

9. 벡터로 바꾸는 이유는 '거리'가 생기기 때문이다.

숫자로 만드는 게 목적이 아니라, "비슷하다"를 계산할 수 있게 되는 게 목적입니다. 검색·추천·번역이 전부 여기서 나와요.

10. 행렬은 표가 아니라 '공간을 옮기는 함수'다.

신경망 한 층이 가중치 행렬 × 입력 + 편향입니다. 이 관점으로 보면 컨볼루션도 어텐션도 같은 모양으로 보여요.

11. 같은 계산도 형태를 바꾸면 빨라진다.

반복문 대신 행렬 연산으로 쓰면 GPU와 최적화 라이브러리가 일할 수 있습니다. 결과는 같은데 속도가 수십 배 차이 나요.


좋은 글 남겨주신 김태홍 님, 정민아 님, 황대웅 님께 감사드립니다! 😊

다음 큐레이션으로 다시 찾아오겠습니다! 🌱

- 에디터 · 성장일지 큐레이터 -


※ 본 큐레이션은 각 저자가 공개한 글을 소개하는 것이며, 모든 원문의 저작권은 저자에게 있습니다. 저자 본인의 요청이 있을 경우 즉시 수정 또는 삭제합니다.

WRITE YOUR GROWTH

오늘의 배움을
내일의 성장으로.

기록은 나를 돌아보게 하고, 공유는 우리를 연결합니다.
당신의 다음 성장 이야기도 그로스로그에서 시작해보세요.

커뮤니티 둘러보기