본문 바로가기
C.W.K.
Stream
Lesson 04 of 05 · published

어디에나 있는 토큰은 아무것도 못 갈라

~13 min · nlp, i18n, ranking, generalization

Level 0미분류
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

신고는 스포츠 얘기였는데 버그는 문법 얘기였어

실사용에서 온 불평은 평범했어. 스포츠 기사를 싫어해도 스포츠가 안 없어진다고. 쫓아가니까 정정 둘이 나왔고, 재미있는 건 두 번째야. 스포츠랑 아무 상관이 없었거든.

음소거를 물리고 나니까 선반 하나가 독자가 거부한 것과 아무 관계 없는 기사를 숨기기 시작했어. 음식점 기사 하나랑 가뭄 기사 하나, 각각 마이너스 6.7 언저리 점수로. 싫어한 기사랑 주제를 하나도 안 공유했지. 공유한 건 평범한 한국어 연결어들이었어. 거의 모든 한국어 헤드라인에 나오는 것들이고, 싫어한 기사들에도 나왔으니까 무거운 음수 가중치를 쌓아버린 거야.

제일 먼저 손이 가는 땜질은 언어 모양이야

뻔한 해법은 불용어 목록을 늘리는 거야. 그 언어에 대해선 먹혀. 다음 언어가 오기 전까진. 그리고 목록은 항상 작성자가 쓰는 언어 모양을 하게 돼. 여기 원래 목록은 영어 모양에 한국어 보도용어 몇 개를 덧댄 거였고, 딱 예상대로 생겼고, 딱 그래서 샜어.

더 나쁜 건 불용어 목록이 코퍼스를 안 보는 사람이 미리 하는 어휘에 대한 주장이라는 거야. 일반 산문에선 불용어인 단어가 특정 피드에선 의미 있는 용어일 수 있다는 걸 못 알고, 새로 흔해진 단어가 도착하는 것도 못 알아채.

대신 코퍼스한테 물어

일반 규칙은 이 모든 것보다 오래됐고 언어 중립적이야. 용어의 유용성은 그걸 포함한 문서 수에 반비례한다. 풀 대부분에 나타나는 토큰은 그 풀 안에서 고르는 걸 못 도와줘. 무슨 뜻이든 무슨 언어든.

그러니까 계산해. 실제로 걸러지는 행들에 대해 각 토큰이 몇 개에 들었는지 세고, 집합의 어떤 비율을 넘는 토큰은 무시해. 문턱은 일부러 낮아. 선반의 칠분의 일 넘게 나오는 토큰은 이미 판별자로는 거의 쓸모없거든. 그리고 행 수에 바닥이 있어. 몇 개 안 되는 항목으로 잰 문서 빈도는 잡음이니까.

이건 목록이 절대 못 하는 방식으로 스스로 유지돼. 선반마다 적응하니까 한 주제 선반에선 흔하고 다른 데선 드문 단어가 양쪽에서 다 맞게 처리돼. 아무도 예상 못 한 언어도 다루고. 그리고 손볼 게 없어.

전역이 아니라 걸러지는 집합마다 재

변호할 값어치가 있는 설계 선택 하나. 빈도는 저장고 전체가 아니라 지금 걸러지는 행들에 대해 계산해. 더 나쁜 추정치를 위해 일을 더 하는 것처럼 들리는데 맞는 판단이야. "흔하다"는 게 지금 내리는 선택에 상대적인 성질이거든. 한 나라에 대한 선반에선 그 나라 이름이 전부에 있고 아무것도 못 갈라. 일반 헤드라인 선반에선 같은 토큰이 강한 신호고.

규칙이 언어별 예외를 계속 필요로 하면, 그 규칙은 엉뚱한 층위에 있는 거야. 예외들이 뭘 공유하는지 물어봐. 여기선 어느 행을 고를지에 대해 아무 정보도 안 주는 단어라는 거였지. 그리고 사례를 영원히 열거하는 대신 그걸 데이터에서 재서 구현해.

Code

언어별 목록을 대체하는, 걸러지는 집합마다 계산하는 문서 빈도·python
# A keyword only tells us something if it is rare. The stopword list is
# English-shaped, so Korean function words sailed straight into the
# profile: the heaviest negative weights were ordinary connective words,
# and one shelf was muting a restaurant piece and a drought story at
# -6.7 apiece for sharing them with a disliked article.
#
# Rather than chase stopwords per language forever, ignore any token
# that shows up in a large share of the shelf being filtered -- common
# words cannot discriminate, whatever language they are in.
MUTE_MAX_DOC_FREQ = 0.15
MUTE_DF_MIN_ROWS = 30      # DF over a handful of rows is noise


def _common_tokens(rows: list[dict]) -> set[str]:
    """Document frequency over the set BEING FILTERED, not the store.

    'Common' is relative to the choice being made: on a single-country
    shelf that country's name is in everything and discriminates
    nothing, while on a general shelf the same token is a strong signal.
    """
    if len(rows) < MUTE_DF_MIN_ROWS:
        return set()
    counts: dict[str, int] = {}
    for row in rows:
        for tok in tokens(row):
            counts[tok] = counts.get(tok, 0) + 1
    ceiling = len(rows) * MUTE_MAX_DOC_FREQ
    return {tok for tok, n in counts.items() if n > ceiling}

External links

Exercise

네 시스템에서 뭔가를 배제하려고 있는 하드코딩된 목록을 찾아. 불용어, 무시할 파일명, 시끄럽다고 알려진 식별자 같은 거. 모든 항목이 공유하는 성질이 뭔지 물어봐. 이어서 그 성질을 런타임에 데이터에서 잴 수 있는지, 재는 비용이 얼마인지 따져봐. 진짜로 못 재는 성질일 때만 목록을 남겨.
Hint
목록이 측정이어야 한다는 표시는 이거야. 항목이 계속 덧붙고, 추가마다 규칙이 아니라 사건이 방아쇠였다는 것. 사건으로 자란 목록은 누가 손으로, 신고 한 건씩, 수행해온 측정이야.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.