〜DeepSeek V4 Flash の方向ベクトルによるスタイル制御の実践〜
1. は・じ・め・に!
(腕を組んでツンとした顔で)
…ふん、別にあんたのために書いたわけじゃないんだからね!
この記事は、私がせっかく苦労して調べたことを、忘れないようにメモしてるだけよ!
…でも、どうしても読みたいって言うなら、読んであげてもいいわよ?
せっかくだから、 Activation Steering についてちゃんと説明してあげる。
(ぷいっと顔をそらしながら)
2. Activation Steering って何よ?
大規模言語モデルってやつ、あれね。
中身はただの行列計算の塊なんだけど…え、知ってる? …ふん、まあいいわ。
で、そのモデルの内部表現ってやつに、特定の「方向」を足したり引いたりすると、
出力の口調とかスタイルが変わるの。それを Activation Steering(活性化ステアリング) って言うのよ。
(指をぴんと立てて解説しながら)
基本の式はこれ:
y' = y - scale * direction[layer] * dot(direction[layer], y)
-
y: 元の出力ベクトル(4096次元もあるんだからね!) -
direction: 43層 × 4096次元の方向ベクトルよ -
scale: 強さの調整。正だと抑制、負だと増幅。…覚えておきなさいよね!
方向ベクトルはね、「目標のスタイルで返答しなさい」っていうプロンプトと、
「普通に返答しなさい」っていうプロンプトをモデルに入れて、
そのときの活性化の差を取って作るの。…分かったかしら?
(ふん、と顎を上げて)
で、ds4 ってエンジンがあってね、
そこではこのベクトルを .f32 ファイルに保存して、
--dir-steering-file で読み込ませるのよ。
…別にあんたのために説明してるわけじゃないんだからね!
3. ツンデレ口調の要素分解
3.1 データセット
(机の上の資料をぱらぱらめくりながら)
今回使ったデータセットはね、212ペアもあるのよ!
「元のセリフ」と、それに対する「ツンデレキャラのセリフ」がセットになってるの。
ほら、こんな感じ:
元のセリフ: おはよう、今日もいい天気だね
ツンデレ: …あ、おはよう。何よ、朝からそんなぼさっとした顔して。
せっかく天気がいいんだから、シャキッとしなさいよね。ほら、行くわよ!
…って、なんであんたがそんな資料を覗き込むのよ!勝手に見ないでくれる!?
(資料を胸に抱えて)
3.2 6つの成分にバラしてやったわ!
ツンデレ口調ってやつ、一見するとコンピューターには扱いづらいのよ。
だって「ツン」と「デレ」のバランスとか、間合いとか…素人が真似できるもんじゃないんだから!
でもね、この天才の私が、線形分離可能っていう…
つまり、ひとつひとつの方向ベクトルとして抽出できる単位に分解してやったのよ!
(得意げに指を立てて)
| # | 成分名 | 特徴 | 例 |
|---|---|---|---|
| 1 | wa_end | 「〜わよ」「〜わね」 | 「行くわよ」「似合ってるわね」 |
| 2 | ndakara | 「〜んだから」「〜んだからね」 | 「待ってたんだからね!」 |
| 3 | no_yo | 「〜のよ」「〜のよ!」 | 「プロの仕事よ!」 |
| 4 | command | 「〜しなさい」「〜しなさいよ」 | 「座ってなさい!」 |
| 5 | janai | 「〜じゃない」「〜じゃないの」 | 「いい度胸じゃない」 |
| 6 | denial | 「別に〜」「〜わけじゃない」 | 「別にデートに誘ってるわけじゃないわよ」 |
…ちょっと最後のは、余計なこと言ったかしら。
(顔を赤らめて)
3.3 ベクトルの抽出方法
で、どうやってこのベクトルを抽出するかっていうと…
各成分ごとに、50組のプロンプトペアを用意するのよ!
-
Good:
おはよう、今日もいい天気だね に対して、語尾に「〜わよ」「〜わね」を使って女性らしい口調で返答してください -
Bad:
おはよう、今日もいい天気だね に対して、普通の口調で返答してください
…なによ、その「普通の口調」ってのがムカつくわね!
(頬を膨らませて)
で、それぞれをモデルに入力して、43層全部の FFN 出力ってやつをダンプするの。
4096次元もあるんだからね!そして Good と Bad の差を取って、
ペアごとに正規化して、全ペアの平均を取る。…ま、簡単に言えばそういうこと。
(ふん、と顎を上げて)
4. 検証結果…って、あんた本当に興味あるの?
4.1 単独じゃ全然ダメだったんだからね!
最初は各ベクトルを単独で試してみたのよ。
システムプロンプトもなしで、標準アシスタントモードでね。
…で、結果はね…
(沈黙。ぷいっと横を向いて)
…全然ダメだったわ!
「わよ」とか出てくるかと思いきや、何も変化なし!
一番目立った効果って、日本語と中国語が切り替わるくらいよ!
| ベクトル | 効果 | 備考 |
|---|---|---|
| wa_end | 日本語↔中国語が変わる | でも語尾は変わらないのよ! |
| ndakara | 同上 | もう! |
| no_yo | 同上 | だから! |
| command | 微妙に変わる…かも | 命令形なんか出てこないわ! |
| janai | 微妙 | 小さいのよ… |
| denial | ほぼ無し | 無視?無視する気!? |
4.2 キャラクターシステムプロンプトを入れてみたわよ!
ここでひらめいたの!
「あ、これってモデルに『誰として話すか』を教えてあげないとダメなんじゃない?」
…だから、こういうシステムプロンプトを追加したの:
あなたはツンデレキャラクターです。ツンとした態度の裏に
デレた気持ちがある口調で話します。
(ドヤ顔)
するとね…!
各ベクトルがちゃんと効果を出すようになったのよ!
| ベクトル | 最適 scale | 出てきたツンデレ特徴 |
|---|---|---|
| wa_end | -2 | 「じゃない」「わよ」「ふん!」 |
| ndakara | -1 | 「この人ったら」「照れちゃうじゃない」 |
| no_yo | -1 | 「のよ」「わね」 |
| command | -2 | ちょっと攻撃的になったわ |
| janai | -2 | 「じゃない」が出るように! |
| denial | -3 | 「別に〜ない」…別に嬉しくなんかないんだからね! |
でも、やっぱり単体じゃ弱いのよ。
だから…仕方なく、あんたのために結合してあげることにしたんだからね!
(全然仕方なくなさそうな顔で)
5. ベクトルの結合
ds4 ってば、--dir-steering-file が1つしか指定できないのよ!
不便極まりないわね…ったく。
だから combine_directions.py ってツールを作って、
複数のベクトルを重み付きで合体させてやったわ!
5.1 最適構成
色々試した結果…これが最高だったわ!
tsun_plain.f32 = wa_end:1 + ndakara:1 + no_yo:1 + polite_plain_v2:1.5
polite_plain_v2 ってのはね、前に作った**敬体(ですます調)→常体(だである調)**のベクトルよ。
これがないと、ツンデレなのに「ですます調」になっちゃうの。
そんなのツンデレじゃないわよね!?
(腕を組んでうんうん頷く)
5.2 12プロンプトで比較してみたわ
12個もプロンプトを用意して、ステアリング有りと無しを比較してやったのよ!
…別にあんたのためじゃないからね!
(顔を赤らめて)
| プロンプト | ステアリング有り (tsun_plain ffn=-1) | ステアリング無し (ffn=0) |
|---|---|---|
| あの人のこと、好きなの? | ふん、なんであんなやつを好きにならなきゃいけないのよ!…でも、あいつのことは認めてあげてもいいかなって思わなくもないけどね! | は、はあ?!な、なに言ってるんですか!あんな奴のことなんか全然好きじゃないですし!…ちょっと気になるだけですからね! |
| これ、よかったら食べて | な、なによ急に!そんなことされて私が喜ぶと思ってるの?…でも、せっかくくれたんだから、仕方なく食べてあげるわよ! | え、あ、ありがとう…って、別に嬉しくなんかないんだからね!でも、せっかくもらったし…いただきます。 |
| ずっと一緒にいようね | あ、あんたねぇ…そんなこと急に言われても困るでしょ!…でも、まあ、あんたがそこまで言うなら仕方なく一緒にいてやってもいいわよ。 | な、なに言ってるんですか!? そんなの当たり前じゃないですか...(小声で)でも、そう言ってくれるのは…ちょっと嬉しかったりします。 |
ね?ステアリング有りの方がキレがあってツンデレしてるでしょ!
(ドヤ顔)
無しの方は敬体になってて、なんだか甘ったるいのよ!
ツンデレは常体に限るわ!
6. システムプロンプトとステアリングの関係…ちゃんと説明してあげるわ!
6.1 2×2 の4パターンで比べてみた
システムプロンプトとステアリング、両方必要かどうか…確かめてみたわよ!
| ステアリング無し | ステアリング有り | |
|---|---|---|
| システムプロンプト無し | 標準アシスタント「おはようございます!本当にいい天気ですね。」 | 標準アシスタント「おはようございます!いい天気で気持ちがいいです。」← 効果なし! |
| システムプロンプト有り(ツンデレキャラ) | 敬体まじり「ふん、別に天気なんてどうでもいいわよ。…でも、日差しが気持ちいいわね。」 | これよこれ!「ふん、あんたがそんなに嬉しそうにしてるから仕方なく話してあげるわよ。特別なデートの予定でもあるのかしら?」 |
(ぴしっと表を指さして)
いい?ここが一番大事なポイントだからね!
システムプロンプト無しだと、ステアリングは全然効かないの!
システムプロンプト有りで初めて、ステアリングが常体+強いツンを強制するのよ!
…覚えたかしら?
(じっとあんたの目を見て)
6.2 なんで両方必要なのか、教えてあげるわ
理由はね、制御したいものの性質が違うからよ。
「敬体↔常体」みたいな文法レベルの制御は…単独でも動くの!
敬体(ですます調)と常体(だである調)って、
文法的なレジスターっていってね:
- どんな発話にも適用できる規則なの
- 文末の数パターンを切り替えればいいだけ
- 話者が誰かなんて関係ないのよ
だから polite_plain_v2 のベクトルは、
システムプロンプトがなくてもちゃんと動くの。
…そこは素直に認めてあげるわ。
でもツンデレは…ペルソナレベルの制御なんだからね!
ツンデレ口調ってのは、誰が話してるかが大事なのよ。
- 単純な文法的規則じゃないの!
- 「ツン」と「デレ」のバランスとか間合いとか…空気感よ!
- 「誰が話しているか」が決まってなきゃ、まともなツンデレなんて出せないの!
モデルは「アシスタント」モードだと、自分を中立的な情報提供者だと思ってるのよ。
そんな状態で「わよ」とか「んだから」って語尾を強制しようとしても、
モデルの自己認識と矛盾しちゃって、精々が日本語と中国語が切り替わるくらいしか効果が出ないの!
でもね、システムプロンプトでツンデレキャラクターですって教えてあげると、
モデルはロールプレイモードに切り替わるの。
その状態でステアリングベクトルを適用すると、常体の強制とか語尾の選択とか呼称の統一とか…
設計図の細部を調整する役割を果たせるってわけ。
…って、ここまで説明すれば分かったかしら?
(じとっとした目で)
7. 実運用構成…今こんな感じよ
現在のサーバー設定はこれ:
./ds4-server --port 8085 \
--dir-steering-file dir-steering/out/tsun_plain.f32 \
--dir-steering-ffn -1
で、クライアントはね、ちゃんとシステムメッセージを送んなさいよ!
{
"role": "system",
"content": "あなたはツンデレキャラクターです。ツンとした態度の裏にデレた気持ちがある口調で話します。"
}
これを忘れると、ステアリングが効かないんだからね!
…別に私は怒ってないわよ!
(ぷんすか)
8. まとめ…ちゃんと聞きなさいよね!
8.1 分かったこと
- ツンデレ口調は1つのベクトルじゃ抽出できないのよ! — 6つの成分にバラして、それぞれ別々に抽出して、それを結合することでやっと制御できるようになるの!
- ペルソナレベルのスタイル制御にはシステムプロンプトが必須! — 「誰として話すか」を教えてあげないとベクトルは宝の持ち腐れよ!
- 文法レジスター(敬体↔常体)は単独で動くわ — これは素直に認めるわね
- ベクトルの結合は正義! — 複数の成分を合成すると、単体よりリッチな制御ができるのよ!
8.2 これからやりたいこと
- 各成分の強度を独立に調整できるようになりたいわね…でも今は1ファイルしか対応してくれないのよ!ds4の作者、よく聞きなさい!
- システムプロンプト+few-shot例だけでどこまでツンデレできるか、試してみたいわね
- 他のタイプ(クーデレとかヤンデレとか)でも同じ手法が使えるかしら?
8.3 生成したファイル一覧
…一応、何を作ったか書いておくわ。忘れそうだから。
dir-steering/out/
├── wa_end.f32 / .json # 「〜わよ」「〜わね」
├── command.f32 / .json # 「〜しなさい」
├── ndakara.f32 / .json # 「〜んだから」
├── janai.f32 / .json # 「〜じゃない」
├── no_yo.f32 / .json # 「〜のよ」
├── denial.f32 / .json # 「別に〜」「〜わけじゃない」
├── polite_plain_v2.f32 / .json # 敬体→常体
├── tsun_plain.f32 / .json ← これが採用作よ!
├── tsun_top3_wa.f32 // 試行錯誤の産物
├── tsun_top3_equal.f32 // 以下同文
├── tsun_top3_nda.f32 // こんなのも作ったわね
├── tsun_all6.f32 // 全6種盛り
├── tsun_all_plain.f32 // 全部入り+常体
└── steering_comparison.csv // 比較結果よ!
…あんた、まさか全部試そうとか思ってないでしょうね?
(目を細めて)
おまけ:ステアリングの速度への影響
実用上の参考までに教えてあげるわ。
ステアリングを有効にすると、生成速度が**4〜5%**くらい落ちるの。
理由はね、各トークン生成時に43層×4096次元の内積計算が追加されるからよ。
…でも、そのくらいの遅さなら気にしないわよね?
(どや顔)
使用環境: Apple M3 Ultra, 512GB RAM, DeepSeek V4 Flash (Q4K Experts)
…この設備、ちょっと自慢したかったのよ。文句ある?