SONUB
JA English 한국어 日本語 中文 हिन्दी বাংলা اردو Español Português Français Deutsch Русский العربية Bahasa Indonesia Tiếng Việt ไทย Türkçe

通知を有効にする方法

このブラウザーまたはアドレスでは通知を利用できません。対応ブラウザーで安全な接続のSONUBサイトを開いてください。

通知がブロックされている場合は、ブラウザーの設定で許可してください。

  1. アドレスバー横のサイト設定、またはブラウザーのサイト設定を開き、このサイトの通知を許可に変更してください。
  2. 端末の設定でブラウザーの通知を許可し、おやすみモードや集中モードを確認してください。
  3. SONUBに戻ってページを再読み込みしてください。通知が有効になるとベルは表示されなくなります。

iPhoneやiPadではSONUBをホーム画面に追加し、そこから開いて通知を許可してください。

ログイン ログアウト 表示テーマ 標準テーマ ダークテーマ ピンクテーマ チャット 1:1 チャット グループチャット 掲示板 お知らせ ニュース掲示板 ご意見掲示板 自己紹介 メインメニュー

さあ、あなただけの人工知能を作ってみましょう。

次のの点数を実際の生成文に変えます。generate.py を書き、・・seed を一つずつ変えて結果を比べましょう。

単語のブロックが一列に並び、最後に空きマスがある。その上に高さのちがう棒がついた候補ブロックが3つあり、そのうち1つが選ばれて列の最後につながり、矢印が最初に戻ってまた次を選ぶ。
7

テキスト生成

最新のを入れ、1ずつ追加します。 0は最大点、それ以外は抽選。 0は全候補です。

seedは用。か上限で止まり、新しい文章だけ返します。時のQ:/A:形式を使います。

生成ループ — 1トークンずつつけていく(自己回帰)

generate は、この順序を決めたトークン数だけ繰り返します。

  • これまでのトークンから最後の context(12)個だけを切り出し、forward に入れます。
  • 返ってきた点数ののうち、最後の位置の点数 [-1] だけを使います。それが次のトークンの点数です。
  • 決めた方法(貪欲・温度・top_k)でトークンを1つ選びます。
  • 選んだトークンを ids の後ろにつけます。次の周はこのトークンまで含めた文章を見ます。
  • 選んだトークンが改行なら止まります。

自分の出した結果をもう一度入力に使うので、(autoregressive)生成といいます。一度選んだトークンは戻さないので、前で間違えると後ろがその間違いに引きずられます。

生成はを読むだけです。ところが の forward は学習時と同じ Value の計算をするので、使わないまで作ります。 は torch.no_grad() の中で生成してグラフを作らず、model.eval() で をオフにします。

貪欲選択 — temperature 0

temperature を0にすると、いつも点数が最も高いトークンを選びます(、)。同じ入力なら、いつも同じ答えになります。

長所は予測できることです。が何を最もそれらしいと思っているかをそのまま見せるので、学習できたかを確かめるのに便利です。短所は、同じ句を繰り返しやすいことです。"is warn is warn is warn" のように一度ループに入ると、毎回同じトークンが最も高くなり、抜け出せません。だから会話ではランダムさを少し混ぜます。

温度 — 確率分布をとがらせる、または平らにする

temperature(T)は、点数を する前に T で割ります。確率は exp(点数 ÷ T) に比例します。

  • T < 1: 点数の差が広がり、分布がとがります。高いトークンがより頻繁に引かれます。
  • T = 1: モデルが出した確率そのままです。
  • T > 1: 点数の差が縮まり、分布が平らになります。低いトークンも頻繁に引かれ、でたらめになります。

下の実験は、点数 warm 2.0、hot 1.0、blue 0.2、run −1.0 に対して温度を変えます。warm の確率は T 0.5 で 0.858、1.0 で 0.632、2.0 で 0.447 に変わり、run は 0.002 から 0.1 に上がります。

generate.py も同じ計算をします。候補の点数から最も大きい値を引き(数値の安定)、T で割って exp した値を重みとして random.choices に渡します。重みは合計が1でなくてもよく、choices が比率どおりに引きます。ふつうは 0.7〜0.9 がよく使われ、0.1〜0.5 は保守的で、1.0 以上はでたらめになりやすいです。

自分で確かめる lab/temperature.py
import math

logits = {'warm': 2.0, 'hot': 1.0, 'blue': 0.2, 'run': -1.0}
for t in [0.5, 1.0, 2.0]:
    weights = {w: math.exp(s / t) for w, s in logits.items()}
    total = sum(weights.values())
    print('T =', t, {w: round(v / total, 3) for w, v in weights.items()})
print('top_k=2 keeps', sorted(logits, key=logits.get, reverse=True)[:2])
cd ~/xslm
mkdir -p lab
micro lab/temperature.py
python3 -m lab.temperature

実行結果

T = 0.5 {'warm': 0.858, 'hot': 0.116, 'blue': 0.023, 'run': 0.002}
T = 1.0 {'warm': 0.632, 'hot': 0.232, 'blue': 0.104, 'run': 0.031}
T = 2.0 {'warm': 0.447, 'hot': 0.271, 'blue': 0.182, 'run': 0.1}
top_k=2 keeps ['warm', 'hot']

top-k と top-p — 候補を減らす

温度を使っても、とても低いトークンたちを合わせるとかなり大きな確率になることがあります。語彙が5万個なら、1つ1つは小さくても、すべて足すと無視できません。そこで引く前に候補を減らします。

  • top-k: 点数の上位 k 個だけを残し、残りは捨てます。XSLM の top_k 8 は、語彙40個のうち8個だけを候補にします。0なら減らしません。実験の top_k=2 は warm・hot だけを残します。
  • top-p(nucleus): 確率が高い順に足していき、合計が p(例: 0.9)になるまでだけを残します。モデルが確信しているときは候補が数個、迷っているときは多くなり、状況に合わせて変わります。XSLM にはありませんが、実際のサービスでよく使います。

語彙が数万個あるモデルでは、top_k 40〜60 がよく使われます。(温度 0.7、top_k 30)は正確に、(0.9, 50)はバランスよく、(1.1, 100)は創造的に使う組み合わせです。語彙が40個の XSLM では、8くらいが似た比率です。

seed — ランダムな選択を再現する

temperature が0より大きいと、random.choices がトークンをランダムに引きます。generate は呼ばれるたびに random.Random(seed) で新しい生成器を作るので、同じチェックポイント・・温度・top_k・seed なら、いつも同じ文章が出ます。

下の実験は、重み 5:3:2 で a・b・c を12回引きます。seed 7 は2回とも同じ行が出て、seed 8 は違う行が出ます。重みが大きい a が最も多く出ますが、順序は seed が決めます。

seed は結果を再現する番号であって、良い答えを保証する番号ではありません。設定を比べるときは seed を固定して1つだけを変え、結果が運なのかを見るには seed をいくつも変えてみます。

自分で確かめる lab/seed.py
import random

for seed in [7, 7, 8]:
    rng = random.Random(seed)
    print(seed, ''.join(rng.choices('abc', weights=[5, 3, 2])[0] for _ in range(12)))
cd ~/xslm
mkdir -p lab
micro lab/seed.py
python3 -m lab.seed

実行結果

7 aababaabaaaa
7 aababaabaaaa
8 acabaacabaaa

止まる条件 — 改行と最大トークン数

生成は2つの場合に止まります。

  • 改行トークンを引いたとき: 学習資料では答えの1行が改行で終わっていたので、モデルが「答えが終わった」と判断したのです。改行は結果に入れません。
  • 要求されたトークン数(基本24、最大64)を満たしたとき: モデルが終わりを出せなくても、果てしなく書かないように防ぎます。

結果には、プロンプトを除いて新しく作ったトークンだけを して返します。最初のトークンから改行が引かれると、空の答えになります。

実際のモデルは、 特殊トークンや決めた停止の印(例: [/ANSWER])で止まり、その後ろは切り捨てます。トークンを作るたびにすぐ画面へ送るもよく使われます。

プロンプトの形式 — 学習した形と空白まで同じに

モデルは、学習資料で見た形の後に来る文章だけを上手に続けます。だから生成するときに入れる文章(プロンプト)は、学習の形式とまったく同じでなければなりません。XSLM の基本プロンプトは、"Q: hi" の次の行に "A: " です。

空白1つも大事です。学習資料の "A: " は1トークン(30番)ですが、最後の空白を抜いて "A:" にすると "A"(5番)と ":"(4番)の2トークンになります。この2トークンがこう並んだことは学習資料に一度もないので、モデルは初めて見る状況で答えを始めます。デスクトップで 0.1 で3,000回学習したモデルに sun を聞いてみると、"A: " で終えると "te sun is warn is warn…" のように学習の文に似た文章を、"A:" で終えると "ore" のようなかけらだけを出しました。

形式が長いなら、答えの書き出しまで入れてあげます。例えば [ANSWER] の次に "要約:" と "-" まで入れると、モデルはすぐに要約のリストを続けて書きます。

答えがおかしいとき — 原因と実験

小さなモデルの答えがおかしい原因を、よくある順に見ます。

  • : 基本の20回は確認用です。損失が 3.7 付近なら、モデルはまだほとんど何も知りません。
  • 資料不足: 12組では形式をまねるのがやっとで、初めて見る質問には答えられません。
  • 形式の不一致: プロンプトが学習の形式と違うか、知らない文字(�)が混ざっています。
  • 文脈不足: context 12トークンを超える前の部分は見えません。
  • の設定: 繰り返すなら温度・top_k を少し上げ、でたらめなら下げます。

ランダム性を上げても、足りない学習は解決しません。試す価値のある実験はこうです。新しいチェックポイントで学習率を 0.1 に変え、--steps を数百回ずつに分けて学習しながら、同じ質問を temperature 0 で聞いて、答えがどう変わるかを記録します。上で見た "te sun is warn…" は、学習の文 "the sun is warm." をまね始めたものの、まだ暗記すらできていない段階です。

入力して実験

generate.pyを書き、同じ重みで温度、候補数、seedを一つずつ変えます。

cd ~/xslm
micro generate.py
python3 generate.py --temperature 0 --top-k 1
python3 generate.py --temperature 0.8 --top-k 8 --seed 7
python3 generate.py --temperature 1.2 --top-k 0 --seed 8
ターミナルで実習する
generate.py手入力する完全なソース

構造と重みを復元し、文脈を切って反復抽選します。

import argparse
import json
import math
import random
from pathlib import Path
from model import GPT
from tokenizer import BPE


def load():
    saved = json.loads(Path('checkpoints/model.json').read_text())
    cfg = saved['settings']
    tokenizer = BPE(**saved['tokenizer'])
    model = GPT(len(tokenizer.vocab), cfg['width'], cfg['context'], cfg['seed'])
    model.load(saved['weights'])
    return model, tokenizer, cfg


def generate(model, tokenizer, prompt, tokens=24, temperature=0.8, top_k=8, seed=7):
    if not prompt or tokens < 1 or temperature < 0 or not math.isfinite(temperature) or top_k < 0:
        raise ValueError('invalid sampling parameters')
    ids, result, rng = tokenizer.encode(prompt), [], random.Random(seed)
    for _ in range(tokens):
        logits = model.forward(ids[-model.context:])[-1]
        ranked = sorted(range(len(logits)), key=lambda i: logits[i].data, reverse=True)
        if temperature == 0:
            token = ranked[0]
        else:
            candidates = ranked[:top_k] if top_k else ranked
            peak = logits[candidates[0]].data
            weights = [math.exp((logits[i].data - peak) / temperature) for i in candidates]
            token = rng.choices(candidates, weights=weights)[0]
        ids.append(token)
        if tokenizer.vocab[token] == '\n':
            break
        result.append(token)
    return tokenizer.decode(result)


if __name__ == '__main__':
    model, tokenizer, cfg = load()
    parser = argparse.ArgumentParser()
    parser.add_argument('--prompt', default='Q: hi\nA: ')
    parser.add_argument('--tokens', type=int, default=cfg['tokens'])
    parser.add_argument('--temperature', type=float, default=0.8)
    parser.add_argument('--top-k', type=int, default=8)
    parser.add_argument('--seed', type=int, default=cfg['seed'])
    args = parser.parse_args()
    assert 1 <= args.tokens <= cfg['max_tokens']
    print(generate(model, tokenizer, args.prompt, args.tokens,
                   args.temperature, args.top_k, args.seed))

コードを1行ずつ読み解く

generate.py load — チェックポイントからモデルを復元する

def load():
    saved = json.loads(Path('checkpoints/model.json').read_text())
    cfg = saved['settings']
    tokenizer = BPE(**saved['tokenizer'])
    model = GPT(len(tokenizer.vocab), cfg['width'], cfg['context'], cfg['seed'])
    model.load(saved['weights'])
    return model, tokenizer, cfg

model.json 1つから設定とまで読むので、settings.json や tokenizer.json があとで変わっても、学習したときと同じモデルを作ります。同じ大きさの を作ってから(最初の値はすぐ上書きされる)、load で学習した数字を入れます。

generate.py 入力の検査と準備

def generate(model, tokenizer, prompt, tokens=24, temperature=0.8, top_k=8, seed=7):
    if not prompt or tokens < 1 or temperature < 0 or not math.isfinite(temperature) or top_k < 0:
        raise ValueError('invalid sampling parameters')
    ids, result, rng = tokenizer.encode(prompt), [], random.Random(seed)

空のプロンプト、1より小さいトークン数、負・無限大・ の温度、負の top_k は で拒否します。math.isfinite は inf・nan なら偽です。プロンプトを番号に変え、新しく作ったトークンを集める result と、今回の生成だけの乱数生成器 rng を作ります。

generate.py 次のトークンの点数を求める

    for _ in range(tokens):
        logits = model.forward(ids[-model.context:])[-1]
        ranked = sorted(range(len(logits)), key=lambda i: logits[i].data, reverse=True)

ids[-model.context:] で最後の12トークンだけを入れ、forward の結果の最後の行 [-1] だけを使います。ranked は語彙の番号を点数の高い順に並べ替えたリストで、ranked[0] が最も高いトークンです。

generate.py トークンを1つ選ぶ

        if temperature == 0:
            token = ranked[0]
        else:
            candidates = ranked[:top_k] if top_k else ranked
            peak = logits[candidates[0]].data
            weights = [math.exp((logits[i].data - peak) / temperature) for i in candidates]
            token = rng.choices(candidates, weights=weights)[0]

temperature が0なら、最も高いトークンを選びます。そうでなければ top_k 個(0ならすべて)を候補に残し、最も大きい点数 peak を引いてから temperature で割って exp した値を重みにし、rng.choices で1つを引きます。choices はリストを返すので、[0] で取り出します。

generate.py つけて、止めて、返す

        ids.append(token)
        if tokenizer.vocab[token] == '\n':
            break
        result.append(token)
    return tokenizer.decode(result)

選んだトークンを ids につけ、次の周の入力に入れます。そのトークンが改行なら結果に入れずに止まり、そうでなければ result に集めます。が終わると result だけを decode して返すので、プロンプトは結果に入りません。

generate.py コマンドラインでの実行

if __name__ == '__main__':
    model, tokenizer, cfg = load()
    parser = argparse.ArgumentParser()
    parser.add_argument('--prompt', default='Q: hi\nA: ')
    parser.add_argument('--tokens', type=int, default=cfg['tokens'])
    parser.add_argument('--temperature', type=float, default=0.8)
    parser.add_argument('--top-k', type=int, default=8)
    parser.add_argument('--seed', type=int, default=cfg['seed'])
    args = parser.parse_args()
    assert 1 <= args.tokens <= cfg['max_tokens']
    print(generate(model, tokenizer, args.prompt, args.tokens,
                   args.temperature, args.top_k, args.seed))

直接実行すると、チェックポイントを読み、--prompt・--tokens・--temperature・--top-k・--seed オプションを受け取ります。argparse は --top-k を args.top_k に変えてくれます(- が _ に)。トークン数が 1〜max_tokens(64) かを で確かめてから、生成した文章を表示します。で改行を含むプロンプトを渡すには、--prompt "$( 'Q: cat\nA: ')" のように printf を使います。

理解を確認

同じ入力と設定なら再現できます。最初が改行なら空の回答もあります。

困ったとき

不自然な結果は小規模学習では普通です。形式・未知文字・を確認します。

よくある質問

同じ質問に、なぜ毎回同じ答えが出るのですか?

seed が同じだからです。generate は要求ごとに同じ seed で生成器を新しく作ります。違う答えを見るには、--seed を変えるか、 の要求 に "seed" を別の整数で入れてください。temperature が0なら、seed に関係なくいつも同じです。

温度を上げると賢くなりますか?

違います。温度は、すでに計算された確率から選ぶ方法だけを変えます。モデルが正解に低い確率を与えているなら、温度をどう変えても正解はあまり出ません。賢さは学習(資料・大きさ・回数)が決めます。

空の答えが出ます。

最初のトークンとして改行が引かれ、すぐに止まった場合です。学習資料では "A: " の直後に改行することはないので、学習が足りないか、プロンプトの形式が違うときに起きます。プロンプトの最後が "A: " かを確かめ、学習を増やすか、seed・temperature を変えてみてください。

ガイドライン

人工知能

エージェントツール

バイブコーディング

AI活用

AI資格

AI授業

IT基礎

チャット

掲示板

制作

コミュニティ

その他

用語辞典

知らない言葉を選んでみましょう。やさしい説明と関連用語が見られます。

その用語はまだありません。

用語を選ぶと説明が表示されます。