為什麼 Reso 裡沒有"未分類"

"其他"不是你的筆记的事實,而是系統承認它從來沒做決定。我们用十個穩定的門、封閉列表分類、噪声池和可以在下面自由涌現的簇取代了它。

Engineering

每一個带自動整理功能的筆记工具,最後都會長出一個叫"其他"、"雜項"或"未分類"的抽屉。我们的也長了。某天早上打开 Nebula,36 條记憶躺在一個真的叫"未歸類"的主题裡,旁邊還有一個叫"其他"的兜底。同一個失败,两個抽屉。

我们把這當成 bug 處理,而不是當成數據的属性。這篇講 bug 最後是什麼,以及我们最终的設计。

"未分類"裡到底裝的是什麼

追踪那 36 條记錄,沒有一條是真的無法歸類。它们來自两個机械的缺口:本該接住低置信记錄的兜底主题,因為一個谁也夠不到的阈值而不可達;一個從大簇裡拆出來的小簇在路上丢了父級標簽,到可视化的時候就沒有主题了。

這就是普遍规律。"未分類"很少是內容模糊,它是管線停止做決定的地方,然後把"沒有決定"包裝成了一個類別。

十個門,為了穩定而定

修复從一個聽起來顯然、其實不顯然的決定开始:分類的頂層是固定的。十個門,手寫,目標是對一個人多年對著麥克风說的东西做到不重不漏:

  • Building(構建)
  • Work & People(工作與同事)
  • Business & Growth(業務與增長)
  • Money & Investing(金钱與投資)
  • Learning & Ideas(学習與想法)
  • Creating & Expression(創作與表達)
  • People & Relationships(人與關系)
  • Body & Mind(身心)
  • Life Admin(生活事務)
  • Direction & Meaning(方向與意義)

涌現式主题在 demo 裡令人興奋,在日常裡让人疲惫。頂層一旦允许漂移,"career"和"work"這個月是两個本子,下個月合成一個,所有依赖這些名字的界面都得跟著改。頂層的穩定,是下面一切可以流動的前提。

每個門下面有一小組"槽",總共四十來個,同樣手寫。真正的歸属發生在槽這一層。

一個必須做選择的分類器

歸属由一個小型托管語言模型按批完成,對著封閉列表做。它看到門、門下的槽、每個槽一句描述,以及十几條筆记;必須以 JSON 返回每條筆记一個槽 id,別的什麼都不能返回。不在列表裡的 id 會被拒绝,那一行單独重試。

比模型更重要的是两個细節:

  1. 沒有"置信度低于多少就送去兜底"的阈值。每條记錄都拿到當前最合適的槽。模型不確定,就標成低置信,界面可以展示,但记錄仍然有家。
  2. 有一個單独的池子,裝那些根本不是记憶的东西:測試語句、一句光秃秃的"好的"、語音引擎從静音裡幻聽出來的碎片。這些是被隱藏,不是被上架。隱藏是一個有明確规則的明確決定,而一個叫"其他"的抽屉两者都不是。

標簽直接存在记錄上,旁邊放著生成它的那版分類文本的哈希。改了某個槽的描述,只有旧版本打的標簽會被重新排隊。用戶亲手挪过的记錄,机器永遠不再碰。

让第三層自己長出來

槽的下面,結構被允许自己生長。记錄會被向量化,簇在每個槽內部按几何關系形成。簇只在長到很大(大约 150 條)時才拆,而且只在两半真的不一樣時才拆——我们把拆分跑三次,候選看起來像重复就拒绝。保留原始锚點的那一半保留名字,另一半起新名字。

這一層就是"音乐創作"和"音乐表演"可以作為两個簇出現、却不需要任何人预先定義的地方,同時两者仍然在同一個槽、同一個門裡。在犯錯便宜的地方涌現,在犯錯昂贵的地方穩定。

為什麼配圖不是生成的

每個門、每個槽都有配圖,沒有一张是圖像模型按需生成的。它是一套人工策划的圖库,每個門、每個槽一张,同一種畫风。涌現出來的簇繼承它所在槽的那张圖。

另一條路我们試过。生成的封面惊艳一周,然後書架看起來像十個插畫师吵了一架。一段你會看很多年的记憶,不該每次模型重训就換一张臉。

仍然會出錯的地方

向量空間不是各向同性的。在一份真實語料上,一個門吞掉了大部分體積,只因為模型對工作類文本的表示更密。让語言模型讀字來分類,遠好于在那個空間裡量距離——這也是為什麼門的判斷不用最近質心。

而且分類器是判斷,不是測量。它會把一條關于副業定價的筆记放進 Business & Growth,而當事人觉得那是 Money。沒關系:人可以挪,机器记住不挪回去,分類名稱本地化成八種語言,纠正的感觉像整理書架,不像跟模型較劲。

系統再也不會做的一件事是耸肩。沒有一個抽屉是留給它懒得決定的东西的。

這不是行銷,是我們自己踩過的坑。

下載 macOS 版 Reso

閱讀更多 Reso 開發歷程