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

설명해서가 아니라 설계부터 설명 가능하게

~12 min · ranking, transparency, architecture, product-boundary

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

설명 가능해지는 두 가지 방법

순위 매기는 걸 짓고 나서 설명을 붙일 수도 있고, 구조 자체가 설명인 걸 지을 수도 있어. 비슷하게 들리는데 안 비슷해. 첫째는 점수 옆에서 생성된 그럴듯한 이야기를 낳아. 따로 생성된 이야기는 자기가 설명하는 것에서 멀어질 수 있고, 그건 이야기가 아예 없는 것보다 나빠. 둘째는 점수를 이름 붙은 항의 합으로 제약해서, 설명이 생성되는 게 아니라 읽히게 해.

개인용 리더한텐 둘째가 희생이 아냐. 재미있는 질문은 절대 "더 큰 모형이 이걸 더 잘 매길 수 있나?"가 아니라 "이게 왜 내 선반에 있고, 어떻게 멈추지?"거든. 이름 붙은 기여의 합은 둘 다 답해. 학습된 재순위기는 둘 다 못 답하고, 보이는 카드가 몇백 장인 코퍼스에서 아무도 안 물어본 정확도를 사.

구조가 어떻게 생겼나

특성은 네임스페이스 붙은 문자열이야. 발행사, 배달한 출처, 그리고 제목이랑 요약에서 뽑은 제한된 키워드 토큰 집합. 프로필은 그 문자열을 가중치로 매핑하고. 기사 점수는 자기 특성들에 대해 프로필 가중치를 합한 값에 신선도 항을 더한 거야. 그게 모형 전부고, 각 항이 이름을 댈 수 있어.

이름 붙은 항의 합이니까 상위 기여 몇 개가 곧 설명이야. 카드가 네가 저장해온 주제랑 맞았다고, 아니면 네가 계속 여는 발행사에서 왔다고 말할 수 있고, 그 문장은 점수에 대한 서사가 아니라 점수 안의 제일 큰 항들이야.

단서가 하나 있는데, 이 강의가 하는 경고를 자기한테 되돌린 거야. 실제로 나간 점수 함수에선 신선도 항이 총점에는 더해지는데 카드가 보고하는 기여 목록엔 안 들어가. 그러니까 선호도 매치가 하나라도 있는 카드는 부분적으로 최신성 때문에 올라가고도 그 말을 안 해. 기여가 아예 없는 카드만 신선도를 맨몸으로 보고하고. 구조가 어긋남을 막아주는 건 실제로 모은 항들에 대해서만이고, 그 목록 밖에서 더해진 항이 바로 이 접근이 닫으려던 구멍이야. 모든 항을 다 모으든가, 아니면 그 보장은 성질이 아니라 습관이든가.

매치 수에 상한을 걸고, 그 상한이 뭘 함의하는지 알아채

튜닝처럼 보이는데 실은 구조적인 세부 하나. 기사는 제한된 개수의 키워드 매치만 챙길 수 있어. 상한이 없으면 익숙한 주제에 대한 긴 기사가 매치를 쌓아서 순전히 표면적으로 지배해. 관련성이 아니라 장황함에 상을 주는 거지.

이 상한엔 붙들어둘 값어치가 있는 결과가 있어. 뒤 강의가 거기서 돌거든. 기사가 자리 여섯 개를 받는다면, 어느 여섯이냐가 엄청나게 중요해져. 문법 조사에 쓴 자리 여섯은 진짜 주제에 못 쓴 여섯이고, 아래쪽에서 산수를 아무리 정확히 해도 못 되찾아.

페이로드에 담아

설명은 점수 매길 때 계산돼서 기사랑 같이 다녀야 해. 안 그러면 나중에 다른 코드가 재구성하고 미묘하게 틀리게 돼. 상위 기여를 행에 데이터로 실어. 그러면 인터페이스가 그릴 수 있고, 디버깅 화면이 덤프할 수 있고, 제일 중요한 건 독자가 선반 전체가 아니라 특정 항에 대해 반대할 수 있게 돼.

점수 옆에서 생성된 설명은 틀릴 수 있고, 점수에서 읽어낸 설명은 못 틀려. 상위 항이 문자 그대로 이유가 되도록 모형을 제약하면, 설명 가능성이 네가 유지보수하는 기능이길 그만두고 잃을 수 없는 성질이 돼.

Code

이름 붙은 항의 합으로서의 점수, 그리고 쓰는 게 아니라 읽어내는 `why`·python
EVENT_WEIGHTS = {
    "saved_at":    4.0,   # filing it is the strongest positive act
    "liked_at":    3.0,
    "shared_at":   3.0,
    "read_at":     1.0,   # opening is weak evidence, but it is evidence
    "disliked_at": -4.0,  # an explicit verdict, weighted like a save
}
FRESHNESS_WEIGHT = 1.5
KW_SCALE = 0.5
KW_MATCH_CAP = 6          # surface area must not beat relevance


def features(article: dict) -> dict[str, float]:
    """Namespaced strings. Every score term will be nameable because
    every feature already has a name."""
    feats = {f"outlet:{article['outlet']}": 1.0,
             f"source:{article['source_id']}": 1.0}
    for tok in tokens(article):
        feats[f"kw:{tok}"] = KW_SCALE
    return feats


def score(article: dict, profile: dict[str, float], now) -> tuple[float, list]:
    """Returns (score, why). `why` is not a story ABOUT the score -- it
    is the largest terms IN it, so the two cannot drift apart."""
    total, terms, matches = 0.0, [], 0
    for key, scale in features(article).items():
        weight = profile.get(key)
        if not weight:
            continue
        if key.startswith("kw:"):
            if matches >= KW_MATCH_CAP:
                continue
            matches += 1
        contribution = weight * scale
        total += contribution
        terms.append((key, round(contribution, 2)))

    fresh = FRESHNESS_WEIGHT * decay(article["published_at"], now,
                                    FRESHNESS_HALF_LIFE_DAYS)
    total += fresh
    # NOTE, and this is the shipped behaviour rather than the ideal
    # one: freshness moves the total but is NOT appended to `terms`,
    # so a card ranked partly by recency never says so. Appending it
    # here is the one-line fix; leaving it out is how a "cannot
    # drift" guarantee quietly becomes a convention. The shipped
    # fallback is `why or "fresh"`, which covers the empty case
    # and hides the mixed one -- the harder half.

    terms.sort(key=lambda t: -abs(t[1]))
    return total, terms[:3]        # travels with the row, in the payload

External links

Exercise

매일 쓰는 제품에서 정렬된 목록을 하나 골라서, 맨 위 항목이 왜 맨 위인지 한 문장으로 말해봐. 그리고 그 제품이 너한테 그걸 말해줄 수 있는지 확인해. 못 하면, 모형을 분해할 수 없어서인지 아니면 그냥 아무도 분해 결과를 안 내놔서인지 따져봐. 아주 다른 문제고 고치는 비용도 아주 다르니까.
Hint
쓸 만한 탐침은 '이런 거 덜 보기' 같은 걸 인터페이스가 주느냐야. 점수를 분해할 수 있는 제품은 보통 이유별 피드백을 줘. 담을 데가 있으니까. 못 하는 제품은 뭉툭한 음수 조작 하나만 주는 경향이 있고, 그게 뒤에 뭐가 있는지에 대한 강한 힌트야.

Progress

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

댓글 0

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

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