4천만 개 HN 게시물·댓글을 임베딩 지도로 탐색하기
(blog.wilsonl.in)- Hacker News 공개 API에서 4천만 개 이상 아이템을 수집해, 3천만 개 이상 댓글과 400만 개 게시물에 임베딩·메타데이터·본문을 붙인 검색·지도·분석 프로젝트임
- 제목만 임베딩하던 초기 방식은 모호한 제목과 Ask HN/Show HN 편향 때문에 한계가 있었고, 링크된 웹페이지 본문·상위 댓글·댓글 조상 맥락을 함께 쓰는 방식으로 바뀜
- 처리 규모가 커지면서 RunPod의 약 150개 GPU, Rust 큐 서비스, HTTP/2 DB 프록시, UMAP, Canvas 렌더링, 엣지 서버를 조합했고, 모델 교체 후 입력당 임베딩 시간은 약 600ms에서 6ms로 줄어듦
- 검색 랭킹은 단순 문자열 일치가 아니라 코사인 유사도, HN 점수, 시간 가중치를 함께 써서 의미적 관련성·사회적 신호·최신성을 반영함
- 공개 데모는 2024년 4월 10일 무렵까지의 데이터만 포함하며, 전체 데이터와 코드는 GitHub에 공개되어 검색·추천·사용자 분석·실시간 갱신 실험에 활용할 수 있음
프로젝트 범위와 공개 데이터
- Hacker News의 모든 게시물을 의미 공간에 배치한 지도를 만들고, 검색·분석·시각화 도구를 함께 구축함
- 텍스트 임베딩 실험이 출발점이었고, HN은 선별된 콘텐츠가 많으며 모든 콘텐츠를 프로그래밍 방식으로 제공해 적합한 데이터셋으로 선택됨
- 임베딩은 텍스트를 고차원 공간의 점으로 표현하며, 절대 위치보다 점들 사이의 상대적 거리가 유용함
- 기대한 활용은 세 가지였음
- HN의 축적된 콘텐츠에서 의미 기반 검색
- 관심 영역에 맞춘 개인화 추천
- 커뮤니티 내 주제별 감정, 인기, 반대 관점 분석
- 3천만 개 이상 댓글과 400만 개 게시물이 데이터셋 릴리스로 공개됨
- ID, 점수, 작성자 같은 메타데이터 포함
- 임베딩 포함
- 댓글 텍스트와 크롤링한 웹페이지 텍스트 포함
- 코드는 hackerverse GitHub 저장소에 공개됨
Hacker News 데이터 수집
- HN은 단순한 공개 API를 제공하며, 모든 객체는
item으로 조회됨 maxitem.json은 가장 큰 ID를 제공하고, 작성 시점 기준 최대 ID는 4천만을 넘음- 평균 응답 시간이 10ms여도 4천만 개를 순차 크롤링하면 4일 이상 걸리므로 병렬 처리가 필요했음
- Node.js로 빠른 수집 서비스를 만들었지만, 세마포어와 Promise 큐 방식은 대부분의 CPU 시간이 사용자 공간 JS 코드에 쓰여 느렸음
- 이후
worker_threadsAPI로 모든 CPU에 fetch 작업을 분산했고, 모든 코어를 포화시키는 방식으로 성능을 개선함 - 병렬 수집은 순서가 뒤섞이므로, 중단 시 누락이 생기지 않도록 완료 마커를 ID 순서대로 기록함
- HN API에서 관찰한 특성도 있었음
- 점수는 -1 아래로 내려가지 않는 것처럼 보임
- 게시물의 다운보트와 댓글의 투표 수는 가져올 수 없음
- 일부 게시물과 댓글은 삭제·플래그 상태가 아니어도 제목, 텍스트, URL이 비어 있음
- 댓글 ID가 조상보다 작을 수 있으며, 댓글 트리 이동 때문일 가능성이 있음
- HN 크롤러는 별도 TypeScript 프로젝트인 crawler-toolkit-hn로도 분리됨
첫 번째 임베딩과 인프라
- 처음에는 게시물 제목만으로도 의미 표현이 충분할 것이라고 보고 임베딩을 생성함
- 모델 비교에는 Massive Text Embedding Benchmark를 참고했고, 처음 선택한 모델은 BGE-M3였음
- BGE-M3는 일반적인 밀집 임베딩 외에 lexical weights도 생성할 수 있어 BM25 같은 방식과 결합한 하이브리드 검색에 쓸 수 있음
- 임베딩 생성 인프라는 단순하지 않았음
- 좋은 모델은 수백만~수십억 파라미터를 가질 수 있음
- GPU에서 훨씬 효율적이지만 GPU 클러스터는 비쌈
- 추론이 입력당 수백 ms 걸리면 단일 GPU로 4천만 입력 처리는 거의 1년 규모가 됨
- 데이터·서버와 GPU가 떨어져 있어 GPU가 놀지 않도록 파이프라인을 유지해야 함
- RunPod을 사용해 데이터센터에서 실행되는 GPU 머신을 컨테이너로 배포했고, RTX 4090 같은 상대적으로 저렴한 GPU를 활용함
- GPU가 전 세계에 흩어져 DB 연결 지연과 연결 오버헤드가 문제가 되자 db-rpc를 만듦
- SQL 쿼리를 HTTP/2로 로컬 DB에 프록시함
- 큰 공유 커넥션 풀을 사용함
- HTTP/2 멀티플렉싱으로 단일 연결에서 여러 쿼리를 처리함
- AWS SQS는 수백만 개의 작은 작업 메시지에서 낮은 속도 제한과 메시지당 비용이 부담되어, RocksDB 기반 Rust 큐 서비스 queued를 만듦
- 단일 노드에서 100K+ op/s를 처리함
- 배칭, 메시지 크기, 속도 제한, 비용 부담을 줄임
- 약 150개 GPU까지 확장한 결과, 4천만 개 게시물과 댓글을 몇 시간 만에 임베딩함
- 당시 입력당 임베딩 비용은 약 600ms였고, GPU 사용률은 전체 시간 동안 높은 상태를 유지함
웹페이지 크롤링으로 맥락 보강
- 제목만 임베딩하는 방식은 충분하지 않았음
- 많은 게시물은 이상하거나 창의적이거나 모호한 제목을 가짐
Ask HN과Show HN문구가 제목 전체에서 큰 비중을 차지해 주제와 무관하게 함께 클러스터링되는 경향이 있었음
- 텍스트 게시물과 댓글은 자체 텍스트를 쓰면 되지만, 링크 게시물 대부분은 연결된 웹페이지를 크롤링해야 했음
- Rust 서비스로 URL을 가져오고 HTML에서 제목, 이미지, 작성자, 본문 텍스트 같은 메타데이터를 파싱함
- 초기 Node.js 버전은 CPU 집약적인 작업에서 Rust 버전보다 10배 느렸고, Rust 재작성으로 성능을 개선함
- 텍스트 추출은 scraper로 HTML을 파싱하고, 의미상 주 콘텐츠가 아닌 HTML5 요소를 제거한 뒤 남은 트리를 순회하는 방식이었음
- 링크 부패도 컸음
- 약 20만 개 URL이 404, DNS 조회 실패, 연결 시간 초과로 실패함
- 이는 400만 개 페이지 중 5% 미만임
- 누락을 줄이기 위해 Internet Archive의 Wayback API로 오래된 기사 일부를 가져옴
- Internet Archive의 속도 제한은 분당 약 5개 수준으로 매우 낮았음
두 번째 임베딩 전략
- 웹페이지는 길지만 BGE-M3는 8192 토큰 컨텍스트 윈도우를 지원함
- 다만 BGE-M3는 느려서 jina-embeddings-v2-small-en으로 교체함
- 파라미터 수가 훨씬 적음
- MTEB 기준 성능도 양호함
- 추론 시간이 약 6ms로 줄어 100배 빨라짐
- 긴 입력 때문에 배치 크기를 늘리면 OOM이 발생해 GPU를 완전히 포화시키지는 못함
- 텍스트가 적거나 가져오지 못한 페이지를 보완하기 위해 게시물의 상위 HN 댓글을 본문 뒤에 추가함
- 상위 레벨 댓글은
item.kids가 이미 순위화되어 있다고 보고 사용함 - 삭제, dead, 음수 점수 댓글은 제외함
- 입력은 최대 64KiB로 제한함
- 상위 레벨 댓글은
- 댓글은 단독으로 의미가 부족할 수 있어 조상 댓글과 게시물 제목까지 거슬러 올라가 더 긴 맥락을 구성함
- 임베딩, 텍스트 같은 큰 값은 별도
kv테이블에 저장함- 행 안에 함께 저장하면 행이 뚱뚱해짐
- 작은 컬럼 업데이트도 비싸짐
- 스키마 변경도 비싸짐
UMAP으로 2D 의미 지도 만들기
- UMAP은 고차원 임베딩을 낮은 차원으로 줄이면서 의미 관계를 보존하려는 차원 축소 기법임
- 1024차원 임베딩 벡터를 2D 점으로 줄여 산점도와 지도 시각화에 사용함
- UMAP은 PyNNDescent 그래프와 원본 임베딩을 입력으로 받았고,
metric="cosine",n_components=2를 사용함 - 수백만 개 고차원 입력 학습은 오래 걸려 EC2의
c7i.metal-48xlVM을 사용함- 96코어 프로세서를 최대로 사용함
- 약 1시간 30분 뒤 2D 행렬이 생성됨
- 생성된 2D 임베딩과 학습된 UMAP 모델을 저장해, 이후 새 임베딩도 재학습 없이 변환할 수 있게 함
- 처음 산점도는 점이 너무 많았고, 격자 셀별 최고 점수 게시물만 선택해 밀도를 줄여 제목을 표시함
- 더 많은 맥락을 넣은 두 번째 임베딩에서는 제목만으로 어려웠던 게시물들이 관련 콘텐츠 근처에 더 정확히 배치됨
코사인 유사도와 검색 랭킹
- 임베딩 활용의 핵심은 두 임베딩 사이의 유사도를 찾는 것임
- 텍스트 임베딩에서는 일반적인 유클리드 거리보다 코사인 거리·유사도를 자주 사용함
- 코사인 거리는 크기보다 방향이 중요한 경우에 유용함
- 긴 X 관련 토론은 긴 Y 관련 토론보다 X와 비슷해야 함
- 크기를 반영하면 이런 관계가 오도될 수 있음
- 사용한 핵심 계산은 임베딩 행렬과 쿼리 임베딩의 dot product임
- 벡터가 단위 벡터라면 별도 크기 나눗셈이 필요 없음
- 검색 결과는 순수 유사도만으로 정렬하지 않음
- 코사인 유사도는 큰 요소지만 유일한 요소가 아님
- HN 점수는 사회적 증거로 쓰임
- 시간 가중치는 최신성이 중요한 쿼리에서 오래된 콘텐츠가 빠르게 밀리도록
log(age)에 비례한 음수 요소로 반영됨
브라우저 지도 앱
- 목표는 HN 임베딩 공간을 Google Maps처럼 탐색할 수 있는 인터랙티브 지도로 만드는 것이었음
- 요구한 동작은 명확했음
- 핀치나 마우스 휠로 확대하면 더 많은 점이 표시됨
- 점 사이 간격도 확대됨
- 일부 점에는 라벨이 붙고, 전부 붙지는 않음
- 점을 클릭하면 게시물 세부 정보가 표시됨
- 모바일과 데스크톱에서 터치·마우스를 모두 지원함
- 모든 점을 한 번에 클라이언트로 보내기에는 수백만 개라 부적합했음
- 지도 공간을 격자 타일로 나누고, 클라이언트가 필요한 타일만 가져오는 구조를 사용함
- 타일은
(x, y)좌표로 식별 가능함 - S3 같은 KV 저장소에 저장할 수 있음
- 서버 측 로직 없이 배포하기 쉬움
- 타일은
- 확대 레벨은 LOD 방식으로 처리함
- 레벨마다 축당 2배 더 많은 격자 셀로 나눔
- 이전 레벨에서 선택된 점은 다음 레벨에도 복사해 확대할 때 점이 사라지지 않게 함
- 타일 크기는 압축 시 20KiB 미만을 목표로 함
- 약 1,500개 점으로 제한됨
- 점당
(x, y)8바이트, ID 4바이트, 점수 2바이트를 사용함
- 웹 앱 렌더링에서는 DOM 요소 수천 개 방식이 성능을 크게 망가뜨림
- 최종 구현은 Canvas를 사용하고, 뷰포트가 갱신될 때마다 다시 그리는 방식임
- 매 프레임 수천 개 점을 다시 그려도 부드럽고 단순하게 동작함
- 라벨링은 최고 점수 게시물을 반복 선택하되, 기존 라벨과 겹치면 제외함
- 충돌 검사는 RBush R-tree 구현을 사용함
- 라벨 길이는 브라우저
measureText()대신 제목 길이 바이트 배열과 조정된 공식으로 근사함
- 초기 라벨 박스와 충돌 계산은 CPU 집약적이라 Web Workers로 옮김
- OffscreenCanvas도 실험했지만, 렌더링 로직이 이미 효율적이라 큰 효과는 없었음
지형, 경계, 도시 라벨
- 지도는 점만 있으면 방향감과 시각적 흥미가 부족해 지형과 도시 개념을 추가함
- 실제 지리나 정치적 경계가 없으므로, 지형은 점 밀도를 나타내는 방식으로 비유함
- 점 밀도는 활동, 관심, 콘텐츠, 참여, 인기, 토론이 많은 영역을 빠르게 보여주는 신호로 사용됨
- 처음에는 Kernel Density Estimation을 고려했지만 표준 라이브러리 시도는 너무 오래 걸렸음
- 대신 각 점을 큰 격자 셀에 매핑하고 셀별 카운트를 만든 뒤 Gaussian blur를 적용함
- 선형 밀도 값은 대부분이 0에 가깝게 보여 지도가 좋지 않았음
log(density + 1)을 적용하자 훨씬 자연스러운 계층이 만들어졌고, 서로 다른 밀도 레벨이 만나는 곳이 암묵적 경계처럼 보임- 거대한 이미지 대신 SVG 경로를 만들어 클라이언트에서 다각형으로 그리도록 함
- 색상이 4개뿐이라 효율적임
- 확대해도 경계가 선명함
- OpenCV의 contour 함수로 레벨별 폐곡선을 계산함
- 도시 라벨은 특정 반경 안의 공통 주제를 나타냄
- 도시 이름은 임베딩한 뒤 저장된 UMAP 모델로
(x, y)위치를 얻음 - 자동 생성도 시도했음
- LLM으로 도시 이름을 만들려 했지만 원하는 출력을 프롬프트로 안정적으로 얻기 어려웠음
- K-means는 사람이 묶을 법한 의미 있는 클러스터를 많이 찾지 못했음
- 최종적으로는 지도를 직접 탐색하며 도시 몇 개를 수동으로 적었고, 약 1시간 정도 걸림
엣지 배포와 응답성
- 지도 탐색은 빠르고 반응성이 좋아야 하므로, 데이터 fetch 지연을 줄이는 것이 중요했음
- 처음에는 모든 지도 데이터를 Cloudflare R2의 ENAM 리전에 두었지만, 지연이 600ms에서 수초까지 나왔음
- 물리적 지연이 약 200ms여도 100ms가 즉시 반응처럼 느껴지는 임계값이라는 점에서 충분히 좋지 않았음
- 지연을 줄이기 위해 데이터를 사용자 가까이 옮김
- Virginia, San Jose, London, Sydney에 작은 서버를 두고 Rust 서버로 데이터를 제공함
- 클라이언트는 여러 엣지의
/healthz를 몇 번 호출하고, 가장 먼저 응답한 서버를 선택함 - Anycast나 CDN 같은 방식도 가능하지만 비용과 과도한 복잡도 때문에 쓰지 않음
- 엣지 서버 프로세스 메모리 사용량은 원본 데이터보다 2~4배 커서 의문이 남았음
- 잘못된 타입 사용 가능성
- struct padding
Vec,HashMap초과 할당- 메모리 할당자 단편화나 비효율 가능성
의미 검색 결과와 한계
- “entering the tech industry” 같은 간단한 쿼리에서 업보트가 많은 결과와 덜 주목받은 결과가 함께 나타났고, 관련성과 유용성이 좋아 보였음
- HN의 기존 검색 서비스와 비교하면, 의미 임베딩 검색은 문자 그대로 일치하지 않아도 결과를 찾음
- “what happened to wework” 같은 질문형 쿼리도 WeWork의 정리해고, 주가 하락, 파산 등 여러 해에 걸친 결과를 보여줌
- 검색 결과가 “what happened”라는 단어를 실제로 포함하지 않거나 질문형이 아니어도 매칭됨
- 한 가지 문제는 너무 비슷하지 않은 결과를 필터링하지 않아 완전히 무관한 하위 결과가 들어간 점임
- 이는 수정하기 쉬운 문제로 다뤄짐
- “career growth” 검색에서는 단어를 그대로 포함하지 않는 창의적이고 다양한 에세이가 나타남
- HN의 선별 품질과 점수는 검색 품질에 도움을 줌
- 앱에는 “linus rants”, “self bootstrapping”, “cool things with css” 같은 쿼리 제안이 하드코딩되어 있음
가상 커뮤니티와 댓글 분석
- 임베딩을 이용해 가상 서브커뮤니티 기능을 만들 수 있음
- 사용자가 커뮤니티 이름이나 설명을 입력하면, 일정 유사도 임계값을 넘는 게시물이 즉석에서 묶임
- HN에는 게시물을 더 세분화하는 기능이 없으므로, 특정 관심사 중심의 큐레이션을 즉시 만드는 방식임
- 결과 카드의 스니펫과 이미지는 웹 크롤러가 저장한 페이지 메타데이터에서 가져옴
- 사이트 아이콘은 복잡한 메타데이터 파싱 대신 클라이언트에서 도메인의
/favicon.ico를 가져오는 방식으로 단순화함 - 댓글 스레드도 같은 방식으로 보여줄 수 있음
- 댓글 점수는 HN API에서 제공되지 않아 시간순 정렬만 가능함
- 댓글 점수 없이 랭킹하는 방법으로는 사용자 댓글 이력, 해당 댓글 주변 참여, 게시물, 주제, 내용 등을 활용하는 방식이 가능하다고 봄
- 특정 주제에 영향력 있거나 활발한 사용자를 찾는 것도 가능함
- “cloudflare” 쿼리에서
jgrahamc와eastdakota가 상위에 나타남 - 두 사용자는 각각 Cloudflare의 CTO와 CEO임
- “cloudflare” 쿼리에서
- 이 작업은 댓글을 별도로 분류하거나 키워드 검색을 쓰지 않고, 행렬 연산으로 처리됨
- 사전 필터링보다 사후 필터링이 보통 더 빠르고 충분함
- 사전 필터링은 임베딩 행렬에서 대응 행을 제거해야 해 거대한 메모리 복사나 느린 부분 벡터화 계산이 필요할 수 있음
- 먼저 유사한 행을 찾은 뒤 결과를 필터링하는 편이 낫다고 봄
- 최소 임계값은 중요함
- 비관련 항목도 유사도 0.6 수준일 수 있음
- 임계값이 없으면 댓글 수가 매우 많은 사용자가 단순 규모 때문에 순위를 지배할 수 있음
전체 댓글 데이터 감정·인기 분석
- 3천만 개 댓글로 해보고 싶었던 대규모 분석은 인기와 감정이었음
- 목표는 HN이 특정 주제를 시간에 따라 어떻게 느끼는지, 주요 이벤트가 감정에 어떤 영향을 주는지, 관심 주제가 어떻게 성장·쇠퇴하는지 보는 것이었음
- 감정 데이터는 없었기 때문에 Hugging Face의 오픈소스 감정 분류 모델을 사용함
- 선택한 모델은 TweetEval로, 소셜 미디어 콘텐츠에 학습된 모델임
- TweetEval은 짧은 트윗용 모델이므로, 임베딩 때처럼 조상 맥락을 붙이지 않고 댓글 자체만 입력으로 사용함
- 댓글을 큐에 넣고 GPU 클러스터에서 작업을 처리한 뒤 결과를 저장함
- 모델이 작아 배치 크기를 늘려 GPU 효율을 높임
- 배치 크기 증가는 VRAM을 더 쓰지만, 호스트-GPU 메모리 전송을 줄이고 병렬성을 높일 수 있음
- Transformer 모델에서는 배치 입력이 직사각형이어야 하므로 가장 긴 입력 길이에 맞춰 패딩됨
- 짧은 입력 몇 개에 긴 입력 하나가 섞이면 전체 입력 크기와 내부 상태가 크게 늘 수 있음
- 이 때문에 메모리 스파이크와 OOM이 발생할 수 있음
- Rust 감정 분석 예시에서는 Rust에 대한 긍정 감정이 전반적으로 많았음
- Rust 1.0 발표 즈음 긍정 스파이크가 있었음
- 더 부정적인 게시물은 모델 기준 많은 부정 댓글과 상관됨
- 언어별 인기도도 점수와 유사도를 가중해 추정함
- HN은 댓글 점수를 제공하지 않아 댓글 점수는 사용할 수 없음
- Rust는 잘하고 있지만 다른 언어만큼 인기 있지는 않아 보였음
- 유사도 임계값은 조정이 필요할 수 있어 결과가 틀릴 수 있음
GPU로 대규모 수치 계산 가속
- 분석 쿼리는 32코어 머신에서도 10~30초가 걸려 상호작용 실험에는 느렸음
- 인덱스나 전처리를 고민하다가, 벡터화 수치 계산에 GPU를 쓰는 방식으로 전환함
- CuPy와 cuDF는 각각 NumPy와 pandas와 유사한 API를 제공하면서 GPU에서 실행됨
- 포팅은 비교적 간단했고, 쿼리 시간은 수백 ms 수준으로 줄어듦
- 속도가 충분히 빨라 ANN 그래프도 사용하지 않음
- 어려운 부분은 큰 임베딩 행렬을 GPU에 올리는 것이었음
- 댓글 임베딩 행렬은 30M x 512 크기임
- 시스템 메모리나 비디오 메모리에 행렬 1배 이상을 담기 어려웠음
- 단순한 로딩 방식은 복사본을 여러 개 만들 수 있음
- 디스크에서 바이트를 읽음
- NumPy 배열로 로드함
- CuPy 배열로 변환함
- GPU로 복사함
- 이 과정은 총 4개 복사본을 만들 수 있고, 그중 3개는 메모리에 있음
- 최종 방식은 디스크의 행렬을 메모리 매핑하고, GPU에 같은 크기의 초기화되지 않은 행렬을 미리 할당한 뒤 청크 단위로 복사하는 것이었음
- 이 방식은 Python 메모리로 먼저 읽는 것을 피하고, 시스템 RAM과 VRAM을 각각 정확히 1배만 사용함
데모와 다음 단계
- 데모 앱은 hn.wilsonl.in에서 제공됨
- 메인 페이지는 지도와 검색이고, 오른쪽 위 버튼으로 커뮤니티와 분석 도구에 접근할 수 있음
- 커뮤니티나 분석 결과 URL은 쿼리를 URL에 저장하므로 다른 사람과 공유할 수 있음
- 데모 데이터셋은 2024년 4월 10일 무렵에서 끊겨 있으며, 최신 라이브 게시물과 댓글은 포함하지 않음
- 앞으로 탐색하고 싶은 아이디어는 여러 가지임
- 계속 최신 상태로 유지되는 라이브 데이터
- HN의 선별된 웹 위에서 동작하는 딥러닝 기반 추천 시스템
- 재랭커 학습을 통한 검색 결과 개선
- 지도 위의 흥미로운 경로와 여정
- 사용자 간 유사·반대 관계 분석
- 특정 니치에서 가장 전문적인 사용자 분석
- 전체 데이터와 코드는 GitHub에서 확인할 수 있음
댓글과 토론
Hacker News 의견들
-
1인 프로젝트치고는 특히 인상적인 작업임
시간에 따른 감성 분석 그래프가 눈에 띄었고, Rust에 대해 이런 식으로 본 건 처음이라 흥미로웠음. 시간이 지나며 가장 긍정적이었던 주제는 무엇이었는지, 갑자기 크게 하락한 주제가 있었는지도 궁금함
“HN 전반에는 부정적 감성이 많은 것 같다”는 문장도 소셜 미디어에 대해 체감상 맞는 말처럼 들림. 소셜 미디어 플랫폼별·시기별 감성 비교도 보면 재미있을 듯함- 감성 쪽은 더 깊게 파보고 싶음. 말한 것처럼 특정 질의가 아니라 전체적인 개요를 보는 것도 흥미로울 듯함
부정적 감성이 눈에 띈 건 원래 더 명확한 감성 그래프를 기대했기 때문임. 대체로 중립~긍정이고, 긍정적 글 주변에서는 긍정으로, 부정적 글 주변에서는 부정으로 튀는 형태를 예상했는데, 거의 모든 질의에서 감성이 거의 항상 부정적이었음. 긍정적 글도 모델과 접근법 기준으로는 많은 부정성을 끌어온 것으로 보였고, 둘 다 틀렸을 수도 있어서 이후 블로그 글에서 더 파보고 싶음 - 소셜 미디어 플랫폼과 시간대별 감성뿐 아니라 시간대와 평일/주말도 같이 보면 좋겠음
- 몇 달 전에 AI, 블록체인, 원격근무, Rust에 대한 HN 댓글 감성을 분석한 블로그 글을 썼음. 글 맨 마지막의 최종 그래프가 이 주제와 관련 있음
https://openpipe.ai/blog/hn-ai-crypto - HN API가 댓글의 투표 수를 제공하지 않는 건 정말 아쉬움. 추천/비추천으로 가중치를 주면 감성 분석이 어떻게 달라질지 궁금함
근거는 없지만, 엔지니어들은 대체로 비판적이고 긍정적 피드백은 반복해서 쓰기보다 +1을 누르는 편일 것 같음. 비판은 더 직접 쓰고 :) - Crypto도 그 범주에 들어갈 것 같음
- 감성 쪽은 더 깊게 파보고 싶음. 말한 것처럼 특정 질의가 아니라 전체적인 개요를 보는 것도 흥미로울 듯함
-
데이터 엔지니어링/MLOps에 익숙하지 않은 사람에게 좋은 예시임
포인트에 대해 HDBSCAN으로 계층적 군집을 만들고, 모델로 내부 군집 이름을 생성하는 방식을 제안함. 그러면 현재 노드와의 연결성을 기반으로 세부 항목을 띄워가며 잎까지 주제를 탐색하기 쉬워짐
그룹 색상은 더 뚜렷해야 하고, 군집이 있으면 도움이 될 듯함. 개별 글의 텍스트 크기는 전체 기준 또는 현재 검색 기준의 중요도·관련도에 따라 달라져야 함. 내부 군집 요약이 더 많으면 확대하기 전까지 여러 글을 그룹 요약으로 대체할 수 있어 텍스트 혼잡도 줄어듦- GPU가 있는 사람이라면 HDBSCAN이 cuML에서 매우 최적화되어 있다는 점을 참고할 만함
https://docs.rapids.ai/api/cuml/stable/api/#clustering / https://developer.nvidia.com/blog/faster-hdbscan-soft-cluste... - 좋은 포인터 감사함. 아쉽게도 계층적 군집화는 살펴볼 시간이 없었지만 할 일 목록에 있음
지도를 더 명확하게 만드는 지적도 좋고, 개선할 수 있는 쉬운 접근이 꽤 많다고 봄. 이것도 할 일 목록에 추가됨 :)
- GPU가 있는 사람이라면 HDBSCAN이 cuML에서 매우 최적화되어 있다는 점을 참고할 만함
-
프로젝트 범위가 놀라울 정도로 큼
다만 jina인지 bge-3/flag인지, 임베딩과 토크나이저가 기술 주제에는 잘 맞지 않는 듯함. 자연어 단어는 괜찮지만 “xaml”, “simd” 같은 기술 개념을 검색하면 입력을 토큰화한 뒤 비슷하게 들리는 단어를 잡으려는 쪽으로 흐름
건설적 피드백으로, 너무 틈새 주제라 결과가 없을 때 같은 “HN 순위표” 결과를 반복해서 보여주지 않는 방법이 있으면 좋겠음. 임베딩이 익숙하지 않은 단어를 검색하면 “Stephen Hawking has died”가 자주 나옴
감성 분석도 얼마나 잘 작동하는지 확신이 안 듦. 현실과 맞지 않게 부정 감성이 너무 많아 보였고, HN이 압도적으로 긍정적으로 볼 “Mr Rogers” 같은 걸 찾아봐도 심한 부정 피크가 나옴. “Carter”를 검색하면 Rosalynn Carter의 별세와 관련해 거대한 부정 피크가 있는데, 실제 글은 Carter 부부가 한 훌륭한 일들을 이야기하는 제출물이었음
“시간에 따른 인기”는 해당 월/년의 글 중간 투표 수로 보정해야 할 듯함. 단순히 글 수를 그리면 추세선이 계속 올라감. “diesel”의 인기를 보면 무슨 말인지 알 수 있는데, 이 용어는 10년 전에 정점을 찍었음. 아니면 글 점수 대신 키워드 발생률이나 질의와의 코사인 유사도 지수가 x보다 작은 항목 수 같은 방식이어야 할지도 모름
글을 클릭해 제거하고 유사도 임계값을 다시 계산하는 동적 기능은 훌륭함- 어떤 임베딩 모델이 특정 용어나 단어를 인식하지 못한다는 걸 프로그램적으로 어떻게 판별할 수 있을까?
-
거의 같은 일을 어떤 데이터셋에도 해주는 훌륭한 도구가 있음: https://github.com/enjalot/latent-scope
물론 원글 프로젝트의 규모 때문에 흥미로운 복잡성이 많이 추가되고, 이 도구는 그 정도 규모는 처리하지 못하지만 중간 크기 데이터셋에는 좋음 -
HN에서 자기 홍보가 증가했는지 분석해보고 싶음
여기서 자기 홍보는 “Show HN: Something ...”이 아니라 “Show HN: I ...” 형식의 글로 정의함
지금 상위 100개 중 예를 들면 “Show HN: Exploring HN by mapping and analyzing 40M posts and comments for fun”, “Show HN: Browser-based knitting (pattern) software”는 자기 홍보 제목이 아님. 각각 주어가 탐색과 소프트웨어임
반면 “Show HN: I built a non-linear UI for ChatGPT”, “Show HN: I created 3,800+ Open Source React Icons”는 자기 홍보 제목임. 각각의 주어가 “I”임
Algolia 검색 결과에서 4월 1일부터 시작하는 각 연도별로 “Show HN: I”로 시작하는 제목을 단순 확인하고, 해당 연도 전체 결과 수로 나눠 그래프로 그리면 다음과 같았음
2023 ****************************************
2022 ***********************************
2021 ***************************
2020 **************************************
2019 *************************
2018 *************
2017 *******
2016 **********
2015 ********
2014 ************
2013 *********************
2012 *****************
2011 *********
2010 ***
내가 자란 시절에는 대체로 자기 홍보가 나쁜 성격 특성으로 여겨졌던 것 같음. 행동이 자신을 홍보해야지, 거기에 주의를 끄는 건 아니라고 배웠는데 그 문화가 바뀌는 느낌임
자기 홍보가 실제로 늘었다면 소셜 미디어 등의 영향인지 궁금함. YouTube에서도 비슷한 증가를 느끼지만, “I.....”로 시작하는 추천 영상이 많다는 체감만 있고 데이터는 없음- 자기 홍보의 정의가 내가 보통 생각하는 것과 조금 다름. 보통은 어떤 사람이 자신이 만든 것을 홍보하면 자기 홍보라고 봄. 그래서 자기 홍보가 아니라고 든 두 예시도 내 정의에서는 자기 홍보임
그러니까 당신이 자기 홍보와 비자기 홍보로 나눈 것은, 내 기준에서는 제목이 매우 명확하게 자기 홍보임을 드러내는 경우와 덜 명확하게 드러내는 경우임. 다만 “Show HN” 문구 자체가 자기 홍보에만 쓰이는 것 같아서, “I”가 없어도 관례를 아는 사람은 자기 홍보임을 알 수 있음 - 모든 Show HN은 작성자가 만든 것이어야 하므로, 암묵적인 걸 명시한다고 해서 무엇이 더 자기 홍보적인지 잘 모르겠음
전부 “봐, 내가 멋진 걸 만들었는데 어떻게 생각해?”라는 의미임 - Einstein의 Walter Isaacson 전기에서도 많이 다루는 내용이라, 사람들은 오래전부터 이 흐름을 관찰해왔음
예를 들어 독일인들이 Einstein이 자기 홍보를 한다고 비난했고, 반대로 미국에는 유명인 문화가 있었다는 식임. 주기적인 현상일 수도 있음
- 자기 홍보의 정의가 내가 보통 생각하는 것과 조금 다름. 보통은 어떤 사람이 자신이 만든 것을 홍보하면 자기 홍보라고 봄. 그래서 자기 홍보가 아니라고 든 두 예시도 내 정의에서는 자기 홍보임
-
올해 HN에서 본 글 중 단연 가장 멋진 글 같음
-
처음 봤을 때는 분명하지 않았지만 실제 앱은 여기 있음: https://hn.wilsonl.in/
- 랜딩 페이지 링크를 의도적으로 거의 끝에 둔 건지 궁금함. 실제로 글을 읽은 사람만 사이트에 가게 되는 구조임
비꼬는 게 아니라 좋은 생각이라고 봄 - 검색 링크가 공유 가능하지 않거나, 그 안에 검색어가 들어가지 않는 것처럼 보임
검색 문구를 단어별로 임베딩하는지, 문서에 사용한 것과 같은 모델을 쓰는지도 궁금함. “lead generation”을 검색했는데, 제대로 된 비단일어 임베딩이라면 이해해야 할 표현인데도 납 중독 결과가 나왔음 - 나와 내 글을 거기서 찾았음! 좋음
- 랜딩 페이지 링크를 의도적으로 거의 끝에 둔 건지 궁금함. 실제로 글을 읽은 사람만 사이트에 가게 되는 구조임
-
UMAP의 현대적 추천으로 Parametric UMAP이 있음: https://umap-learn.readthedocs.io/en/latest/parametric_umap....
작은 Keras MLP를 학습시켜 UMAP 손실을 최소화하면서 2차원으로 차원 축소를 수행함. 장점은 이 모델이 작아서 저장하고 재사용해 알 수 없는 새 데이터에도 예측할 수 있다는 점임. 전통적으로 학습한 UMAP 모델은 큼. 또한 GPU를 쓰므로 이론상 학습이 훨씬 빠름
단점은 Python UMAP 패키지의 구현이 그리 좋지 않고, 확장된 전체 노드/간선 데이터셋을 GPU로 만들고 밀어 넣는다는 점임. 그래서 메모리 부족이 나기 전 약 10만 개 임베딩까지만 학습할 수 있음
UMAP → HDBSCAN → AI 군집 라벨링으로 이어지는 완전 비지도 파이프라인이 워낙 유용해서, 더 확장 가능한 Parametric UMAP 구현을 만들어보고 싶어짐- 빠른 GPU 구현이 cuML에 있음. 왜 cuML이 이렇게 덜 알려졌는지는 잘 모르겠음
- 얼핏 보기에는 구현이 전체 그래프, 즉 모든 간선을 GPU로 밀어 넣기 때문인 듯함. 학습 중 간선 표본추출을 하면 완화할 수 있음
-
탐색적 취미 프로젝트처럼 보이는 것치고는 놀라울 만큼 큰 작업임. 성과를 깎아내리려는 건 아니고 정말 멋지지만, 투입량이 커서 놀랐음
임베딩 계산만을 위해 150개 GPU를 쓰고, 서버 간 통신을 위해 db-rpc와 queued라는 두 개의 커스텀 시스템을 개발했음. 주변 작업과 계산도 많이 있었음
프로젝트의 맥락이 궁금함. 이런 연구에 필요한 자금과 시간을 어떻게 확보했는지도 궁금함
비슷한 일을 전문적으로 많이 해본 입장에서, 학술 논문과 특허 지형을 매핑해봤는데 150개 GPU가 정말 필요했는지는 잘 모르겠음. 결국 2차원 투영과 군집화라면 전통적인 단어 주머니나 주제 모델링이 훨씬 쉽고 저렴하며 품질 차이는 거의 느껴지지 않을 것 같음. 작성자와 댓글 스레드 그래프를 써도 비슷한 결과를 얻을 수 있음- 비용은 글에 언급하지 못했지만 수백 달러 수준이라 취미 프로젝트로도 꽤 접근 가능했음
GPU가 의외로 저렴했고, 대부분은 내가 참을성이 없어서 확장했을 뿐임 :) 전체 클러스터도 몇 시간만 돌았음
해본 작업 링크가 있으면 보고 싶음. 흥미로워 보여서 더 읽어보고 싶음 - 작성자는 확실히 실력이 뛰어남. HN에 글은 올리지만 2018년 이후로 댓글은 달지 않았다는 점도 흥미로운데, 그러고 나서 이 프로젝트를 시작했음
자금과 시간 면에서는, 다음 일이나 직장 사이에 있고 이전 경력이나 사업에서 재정적으로 성공해 자비로 진행했을 가능성이 있음. GPU 활용도도 매우 효율적이어서 비용이 그렇게 크지는 않았을 듯함 - 더 저렴한 임베딩을 써도 꽤 좋은 결과를 얻을 수 있음
이런 임베딩에 확률 보정 SVM 같은 고전적 기계학습을 적용해 분류와 군집화에서 좋은 결과를 얻고, 속도는 LLM 미세조정보다 100배 이상 빠름
- 비용은 글에 언급하지 못했지만 수백 달러 수준이라 취미 프로젝트로도 꽤 접근 가능했음
-
보통은 이 데모처럼 하지 않고 벡터를 정규화함
정규화된 벡터를 사용할 때 유클리드 거리는 두 벡터 끝점 사이의 거리를 측정함. 반면 코사인 거리는 한 벡터를 다른 벡터에 투영한 길이를 측정함- 정규화의 문제는 자유도 하나를 잃는다는 것임. 시각화에서는 사실상 차원 하나를 잃는 것과 같음
정규화된 2차원 벡터는 실제로는 1차원 벡터에 불과함. 2차원 관계를 보여주고 싶다면 다시 자유도 2개를 확보하기 위해 3차원 벡터를 써야 함
- 정규화의 문제는 자유도 하나를 잃는다는 것임. 시각화에서는 사실상 차원 하나를 잃는 것과 같음