最近、MIT CSAILの研究チーム(Rikiya Takehi氏ら)が公開した「Sparks of Weight-Space Programming: LLMs can understand and write neural network weights」という論文・実験ログが世界的な注目を集めています。
従来の「学習データを用意して勾配降下法(Backpropagation)で重みを最適化する」方法とは異なり、フロンティアLLMがニューラルネットワークの生重み(Weight tensors)をバイナリのように直接解釈し、コードや低ランク更新によって直接書き換えるという驚きの手法です。
特に話題になったのが、Qwen3.5-0.8B の思考ループ(Thinking Loop)というバグを、MLP層の重みからループ方向のベクトルを引き算するだけで一発解消し、数学ベンチマークのスコアを 7.9% から 48% へ急上昇させた実験(exp8_math)でした。
これを見て、筆者は思いました。
「僕が開発している自作日本語IME用の極小モデル(ZeroCon Model Standard v4 は 0.09B、Nano は 0.03B)なら、Qwen(0.8B)の1/10以下の小ささだし、この重み直接編集でサクッと賢くできるんじゃないか?」
そこで、実際に重みテンソルを直接分析し、重み空間での外科手術(Weight Surgery)を試してみました。
1. 対象モデルと残っていた「謎の退行バグ」
今回実験台にしたのは、開発中の日本語IME「ZeroConvert-IME」の変換エンジンである ZeroCon Model Standard v4(約90.5Mパラメータ) です。
このモデルは 480 問の厳格なかな漢字変換ベンチマークにおいて、すでに 95.62%(459/480問正解) という高精度を叩き出しています。しかし、残余の失敗ケースの中に、どうしても解せない「謎の退行バグ」が潜んでいました。
入力: 健康のために毎週末の朝にジョギングコースを走る。[10きろはしる]
期待する正解: 10キロ走る(半角数字)
実際のモデル出力: 10キロ走る(!?)
なんと、1文字目は全角の 1 で出ているのに、2文字目でなぜか半角の 0 にスイッチしてしまい、「全角・半角キメラ」 という非常に不格好な出力を吐いていたのです。
2. ロジットの解剖:なぜキメラ現象が起きたのか?
モデルの生ロジットをステップごとに分解して測定したところ、原因が完全に特定できました。
| 生成ステップ | 候補トークン | 生ロジット値 (logit) | 採択された文字 |
|---|---|---|---|
| ステップ 1(開始時) | 全角 1 (token 678) |
20.1462 | 全角 1 がわずか +0.67差 で勝利 |
半角 1 (token 20) |
19.4767 | ||
| ステップ 2(`1` 出力直後) | 半角 0 (token 19) |
25.0743 | 半角 0 が +2.90差 で圧勝! |
全角 0 (token 814) |
22.1701 |
入力プロンプトが全角 10 だったため、ステップ1では入力に引っ張られて全角 1 がギリギリ勝ちました。しかし、ステップ2では言語モデルが普段よく学習している半角 0 の強固なバイアスが勝ってしまい、文字種がねじれたのです。
埋め込み空間における半角 1 と全角 1 のコサイン類似度を測ると -0.1459。モデル内では完全に別個の無関係なベクトルとして扱われていました。
3. 実践:重み空間の外科手術(Weight Surgery)
「だったら、全角数字の重みベクトルを少しだけ弱めて、半角 1 に道を譲らせれば一発で直るのでは?」
そこで、モデルのトークン埋め込み・出力射影行列 transformer.wte.weight(形状: 6000 × 768)から、全角数字(0〜9)の行ベクトルだけを直接 5% スケーリング減衰 させてみました。
# 勾配降下法・追加データセット一切なし!
# 重みテンソルを直接1行修正するだけ
with torch.no_grad():
w = model.transformer.wte.weight
full_digits = [tok.encode(c)[0] for c in '0123456789']
for fd in full_digits:
w[fd] *= 0.95 # 全角数字の重みノルムを5%だけ抑制
この編集はモデル全体の わずか 0.0085%(7,680個の重み) にしか触れていません。
結果はどうなったか?
| テストケース | 修正前 | 重み直接編集後 | 判定 |
|---|---|---|---|
10きろはしる |
10キロ走る |
10キロ走る | 一発で解消! |
3かいのへや |
3階の部屋 |
3階の部屋 | 退行なし(維持) |
50%のかくりつ |
50%の確率 |
50%の確率 | 退行なし(維持) |
再学習は1ステップも回していません。
全角 1 のロジットが 19.14 に抑えられたことで、ステップ1で半角 1(19.48)が首位に逆転。結果として後続の文字もすべて半角に統一され、見事に 10キロ走る が出力されました。他の数字変換にも一切の悪影響(退行)はありませんでした。
4. 直面した「重み直接編集」の限界
「これは凄い!残りのエラーも全部この調子で直せるのでは?」と意気揚々と他の失敗ケースの解剖に進みましたが、そこで「重み空間プログラミングの絶対的な壁」にぶち当たりました。
壁 ①: 語彙(Tokenizer Vocabulary)の壁
230問テストでの失敗の多くは、LM Studio が LM Studio(全角空白)になってしまう問題でした。
「半角空白のロジットを重みでブーストしよう!」と思いトークナイザーを調べたところ、衝撃の事実が判明します。
tok.encode(" ") # 出力: [] (空リスト!?)
tok.encode(" ") # 出力: [567] (Token 567: '\u3000')
なんと、元祖モデル(zenz-v3.2)の語彙ファイルには、そもそもASCII半角スペース(\x20)のトークンが定義されておらず、空白文字は全角空白(ID: 567)しか存在しなかったのです。
重み空間プログラミングは連続的なテンソルの回転やスケーリングを行う手法です。離散的なトークナイザー語彙に存在しない文字は、重みをどうこねくり回しても物理的に出力できません。
壁 ②: 深く浸透した頻度アトラクターの壁
もう一つの難題が、100えんのあめ が 1000円の飴 に化けてしまう(100円が1000円になる)問題でした。
100 まで生成した直後のロジットを調べると:
0(token 19): logit = 22.0178円(token 746): logit = 17.4934
なんと、正解の「円」より、もう1個「0」を重ねるロジットが 4.5ポイント以上 も上回っていました。日本語の大規模コーパスにおいて、「1000円」「10000円」の出現頻度が「100円」を圧倒しているため、言語モデル全体が 0 の連打に強烈な引力(アトラクター)を持っているのです。
この 0 → 0 の引力を重み引き算で消そうとすると、「100」や「200」といった正当な2桁以上の数字変換全体が道連れで破壊されてしまいました。
5. まとめ:重み空間プログラミングの「3層構造」
今回の実験とMIT CSAILの論文から得られた知見をまとめると、言語モデルにおける重み空間操作は以下の3つの階層に分類できます。
特定トークンの過剰な暴走(今回の全角数字)や、特定層のMLPに集中した無限ループの解消。低ランク方向を少し引き算・スケーリングすることで、再学習なしで綺麗に治せる。
「通常変換モデル」と「タイポ補正モデル」の差分ベクトルを重み空間でブレンドする(WiSE-FT / Blend85)。ZeroConですでに採用されており、極小モデルだからこそ秒単位でイテレーションが回せる最強の領域。
「机の端」と「食べる箸」の文脈判断や、未知の専門用語の追加。これらは数十層の数千万パラメータに分散表現として広がっているため、勾配降下法(データセット学習)なしで重みを手書きすることはできない(CSAIL論文でもGemma 270Mへの適用は+0.8%で失敗)。
おわりに:IMEの設計思想へ
今回の実験を通して痛感したのは、「ニューラルネットワークの重みに無理な責務を負わせすぎない」 というソフトウェア工学の大切さでした。
数字の全角半角や空白文字の制御なんて、IMEのクライアント層(Rust / C++ / Swift)のコードで数行書けば 100% 確実にハンドリングできます。それを無理に数千万個の重み空間の微細なバランス調整で解かせようとするのは、モデル容量の浪費であり本末転倒です。
重み空間プログラミングは魔法の杖ではありませんが、「モデルの悪癖をピンポイントで麻酔手術するツール」、そして「別々に鍛えた特化モデル同士を合成するTask Vector」として、今後もエッジAI開発における強力な武器になることは間違いありません。
ZeroConvert-IMEのプロジェクトや検証データはすべてGitHubで公開していますので、興味のある方はぜひ覗いてみてください!