30 KiB
Προαπαιτούμενα
Σε αυτό το μάθημα, θα χρησιμοποιήσουμε μια βιβλιοθήκη που ονομάζεται OpenAI Gym για να προσομοιώσουμε διαφορετικά περιβάλλοντα. Μπορείτε να εκτελέσετε τον κώδικα αυτού του μαθήματος τοπικά (π.χ. από το Visual Studio Code), οπότε η προσομοίωση θα ανοίξει σε νέο παράθυρο. Όταν εκτελείτε τον κώδικα online, ίσως χρειαστεί να κάνετε κάποιες τροποποιήσεις, όπως περιγράφεται εδώ.
OpenAI Gym
Στο προηγούμενο μάθημα, οι κανόνες του παιχνιδιού και η κατάσταση καθορίζονταν από την κλάση Board
που ορίσαμε εμείς. Εδώ θα χρησιμοποιήσουμε ένα ειδικό περιβάλλον προσομοίωσης, το οποίο θα προσομοιώσει τη φυσική πίσω από την ισορροπία του πόλου. Ένα από τα πιο δημοφιλή περιβάλλοντα προσομοίωσης για εκπαίδευση αλγορίθμων ενισχυτικής μάθησης ονομάζεται Gym, το οποίο συντηρείται από την OpenAI. Χρησιμοποιώντας αυτό το gym μπορούμε να δημιουργήσουμε διάφορα περιβάλλοντα, από προσομοίωση cartpole έως παιχνίδια Atari.
Σημείωση: Μπορείτε να δείτε άλλα διαθέσιμα περιβάλλοντα από το OpenAI Gym εδώ.
Αρχικά, ας εγκαταστήσουμε το gym και να εισάγουμε τις απαραίτητες βιβλιοθήκες (code block 1):
import sys
!{sys.executable} -m pip install gym
import gym
import matplotlib.pyplot as plt
import numpy as np
import random
Άσκηση - αρχικοποίηση περιβάλλοντος cartpole
Για να δουλέψουμε με το πρόβλημα ισορροπίας του cartpole, πρέπει να αρχικοποιήσουμε το αντίστοιχο περιβάλλον. Κάθε περιβάλλον συνδέεται με:
-
Observation space που ορίζει τη δομή των πληροφοριών που λαμβάνουμε από το περιβάλλον. Στο πρόβλημα του cartpole, λαμβάνουμε τη θέση του πόλου, την ταχύτητα και κάποιες άλλες τιμές.
-
Action space που ορίζει τις δυνατές ενέργειες. Στην περίπτωσή μας, το action space είναι διακριτό και αποτελείται από δύο ενέργειες - αριστερά και δεξιά. (code block 2)
-
Για να αρχικοποιήσετε, πληκτρολογήστε τον παρακάτω κώδικα:
env = gym.make("CartPole-v1") print(env.action_space) print(env.observation_space) print(env.action_space.sample())
Για να δείτε πώς λειτουργεί το περιβάλλον, ας εκτελέσουμε μια σύντομη προσομοίωση για 100 βήματα. Σε κάθε βήμα, παρέχουμε μία από τις ενέργειες που πρέπει να εκτελεστούν - σε αυτή την προσομοίωση απλώς επιλέγουμε τυχαία μια ενέργεια από το action_space
.
-
Εκτελέστε τον παρακάτω κώδικα και δείτε τι προκύπτει.
✅ Θυμηθείτε ότι είναι προτιμότερο να εκτελέσετε αυτόν τον κώδικα σε τοπική εγκατάσταση Python! (code block 3)
env.reset() for i in range(100): env.render() env.step(env.action_space.sample()) env.close()
Θα πρέπει να βλέπετε κάτι παρόμοιο με αυτήν την εικόνα:
-
Κατά τη διάρκεια της προσομοίωσης, πρέπει να λαμβάνουμε παρατηρήσεις για να αποφασίσουμε πώς να δράσουμε. Στην πραγματικότητα, η συνάρτηση step επιστρέφει τις τρέχουσες παρατηρήσεις, μια συνάρτηση ανταμοιβής και τη σημαία done που υποδεικνύει αν έχει νόημα να συνεχίσουμε την προσομοίωση ή όχι: (code block 4)
env.reset() done = False while not done: env.render() obs, rew, done, info = env.step(env.action_space.sample()) print(f"{obs} -> {rew}") env.close()
Θα καταλήξετε να βλέπετε κάτι σαν αυτό στην έξοδο του notebook:
[ 0.03403272 -0.24301182 0.02669811 0.2895829 ] -> 1.0 [ 0.02917248 -0.04828055 0.03248977 0.00543839] -> 1.0 [ 0.02820687 0.14636075 0.03259854 -0.27681916] -> 1.0 [ 0.03113408 0.34100283 0.02706215 -0.55904489] -> 1.0 [ 0.03795414 0.53573468 0.01588125 -0.84308041] -> 1.0 ... [ 0.17299878 0.15868546 -0.20754175 -0.55975453] -> 1.0 [ 0.17617249 0.35602306 -0.21873684 -0.90998894] -> 1.0
Το διάνυσμα παρατήρησης που επιστρέφεται σε κάθε βήμα της προσομοίωσης περιέχει τις εξής τιμές:
- Θέση του καροτσιού
- Ταχύτητα του καροτσιού
- Γωνία του πόλου
- Ρυθμός περιστροφής του πόλου
-
Βρείτε την ελάχιστη και μέγιστη τιμή αυτών των αριθμών: (code block 5)
print(env.observation_space.low) print(env.observation_space.high)
Μπορεί επίσης να παρατηρήσετε ότι η τιμή ανταμοιβής σε κάθε βήμα της προσομοίωσης είναι πάντα 1. Αυτό συμβαίνει επειδή ο στόχος μας είναι να επιβιώσουμε όσο το δυνατόν περισσότερο, δηλαδή να διατηρήσουμε τον πόλο σε μια σχετικά κάθετη θέση για τη μεγαλύτερη δυνατή χρονική περίοδο.
✅ Στην πραγματικότητα, η προσομοίωση CartPole θεωρείται λυμένη αν καταφέρουμε να πετύχουμε μέση ανταμοιβή 195 σε 100 συνεχόμενες δοκιμές.
Διακριτοποίηση κατάστασης
Στο Q-Learning, πρέπει να δημιουργήσουμε έναν Πίνακα Q που ορίζει τι να κάνουμε σε κάθε κατάσταση. Για να το κάνουμε αυτό, η κατάσταση πρέπει να είναι διακριτή, πιο συγκεκριμένα, πρέπει να περιέχει πεπερασμένο αριθμό διακριτών τιμών. Έτσι, πρέπει με κάποιο τρόπο να διακριτοποιήσουμε τις παρατηρήσεις μας, αντιστοιχίζοντάς τες σε ένα πεπερασμένο σύνολο καταστάσεων.
Υπάρχουν μερικοί τρόποι να το κάνουμε αυτό:
- Διαίρεση σε διαστήματα. Αν γνωρίζουμε το εύρος μιας συγκεκριμένης τιμής, μπορούμε να διαιρέσουμε αυτό το εύρος σε έναν αριθμό διαστημάτων και στη συνέχεια να αντικαταστήσουμε την τιμή με τον αριθμό του διαστήματος στο οποίο ανήκει. Αυτό μπορεί να γίνει χρησιμοποιώντας τη μέθοδο
digitize
της numpy. Σε αυτή την περίπτωση, θα γνωρίζουμε ακριβώς το μέγεθος της κατάστασης, καθώς θα εξαρτάται από τον αριθμό των διαστημάτων που επιλέγουμε για την ψηφιοποίηση.
✅ Μπορούμε να χρησιμοποιήσουμε γραμμική παρεμβολή για να φέρουμε τις τιμές σε κάποιο πεπερασμένο εύρος (π.χ. από -20 έως 20) και στη συνέχεια να μετατρέψουμε τους αριθμούς σε ακέραιους στρογγυλοποιώντας τους. Αυτό μας δίνει λίγο λιγότερο έλεγχο στο μέγεθος της κατάστασης, ειδικά αν δεν γνωρίζουμε τα ακριβή εύρη των τιμών εισόδου. Για παράδειγμα, στην περίπτωσή μας, 2 από τις 4 τιμές δεν έχουν ανώτερα/κατώτερα όρια, κάτι που μπορεί να οδηγήσει σε άπειρο αριθμό καταστάσεων.
Στο παράδειγμά μας, θα ακολουθήσουμε τη δεύτερη προσέγγιση. Όπως θα παρατηρήσετε αργότερα, παρά τα απροσδιόριστα ανώτερα/κατώτερα όρια, αυτές οι τιμές σπάνια λαμβάνουν τιμές εκτός ορισμένων πεπερασμένων εύρων, επομένως αυτές οι καταστάσεις με ακραίες τιμές θα είναι πολύ σπάνιες.
-
Εδώ είναι η συνάρτηση που θα πάρει την παρατήρηση από το μοντέλο μας και θα παράγει μια πλειάδα από 4 ακέραιες τιμές: (code block 6)
def discretize(x): return tuple((x/np.array([0.25, 0.25, 0.01, 0.1])).astype(np.int))
-
Ας εξερευνήσουμε επίσης μια άλλη μέθοδο διακριτοποίησης χρησιμοποιώντας διαστήματα: (code block 7)
def create_bins(i,num): return np.arange(num+1)*(i[1]-i[0])/num+i[0] print("Sample bins for interval (-5,5) with 10 bins\n",create_bins((-5,5),10)) ints = [(-5,5),(-2,2),(-0.5,0.5),(-2,2)] # intervals of values for each parameter nbins = [20,20,10,10] # number of bins for each parameter bins = [create_bins(ints[i],nbins[i]) for i in range(4)] def discretize_bins(x): return tuple(np.digitize(x[i],bins[i]) for i in range(4))
-
Ας εκτελέσουμε τώρα μια σύντομη προσομοίωση και να παρατηρήσουμε αυτές τις διακριτές τιμές περιβάλλοντος. Δοκιμάστε ελεύθερα τόσο το
discretize
όσο και τοdiscretize_bins
και δείτε αν υπάρχει διαφορά.✅ Το discretize_bins επιστρέφει τον αριθμό του διαστήματος, που ξεκινά από το 0. Έτσι, για τιμές της μεταβλητής εισόδου γύρω στο 0 επιστρέφει τον αριθμό από το μέσο του εύρους (10). Στο discretize, δεν μας ένοιαζε το εύρος των τιμών εξόδου, επιτρέποντάς τους να είναι αρνητικές, επομένως οι τιμές κατάστασης δεν μετατοπίζονται και το 0 αντιστοιχεί στο 0. (code block 8)
env.reset() done = False while not done: #env.render() obs, rew, done, info = env.step(env.action_space.sample()) #print(discretize_bins(obs)) print(discretize(obs)) env.close()
✅ Αποσχολιάστε τη γραμμή που ξεκινά με env.render αν θέλετε να δείτε πώς εκτελείται το περιβάλλον. Διαφορετικά, μπορείτε να το εκτελέσετε στο παρασκήνιο, κάτι που είναι πιο γρήγορο. Θα χρησιμοποιήσουμε αυτήν την "αόρατη" εκτέλεση κατά τη διαδικασία Q-Learning.
Η δομή του Πίνακα Q
Στο προηγούμενο μάθημα, η κατάσταση ήταν ένα απλό ζεύγος αριθμών από το 0 έως το 8, και έτσι ήταν βολικό να αναπαραστήσουμε τον Πίνακα Q με έναν πίνακα numpy με σχήμα 8x8x2. Αν χρησιμοποιήσουμε τη διακριτοποίηση με διαστήματα, το μέγεθος του διανύσματος κατάστασης μας είναι επίσης γνωστό, οπότε μπορούμε να χρησιμοποιήσουμε την ίδια προσέγγιση και να αναπαραστήσουμε την κατάσταση με έναν πίνακα σχήματος 20x20x10x10x2 (εδώ το 2 είναι η διάσταση του action space και οι πρώτες διαστάσεις αντιστοιχούν στον αριθμό των διαστημάτων που έχουμε επιλέξει να χρησιμοποιήσουμε για κάθε μία από τις παραμέτρους στο observation space).
Ωστόσο, μερικές φορές οι ακριβείς διαστάσεις του observation space δεν είναι γνωστές. Στην περίπτωση της συνάρτησης discretize
, μπορεί να μην είμαστε ποτέ σίγουροι ότι η κατάστασή μας παραμένει εντός ορισμένων ορίων, επειδή ορισμένες από τις αρχικές τιμές δεν είναι περιορισμένες. Έτσι, θα χρησιμοποιήσουμε μια ελαφρώς διαφορετική προσέγγιση και θα αναπαραστήσουμε τον Πίνακα Q με ένα λεξικό.
-
Χρησιμοποιήστε το ζεύγος (state,action) ως το κλειδί του λεξικού και η τιμή θα αντιστοιχεί στην τιμή του Πίνακα Q. (code block 9)
Q = {} actions = (0,1) def qvalues(state): return [Q.get((state,a),0) for a in actions]
Εδώ ορίζουμε επίσης μια συνάρτηση
qvalues()
, η οποία επιστρέφει μια λίστα τιμών του Πίνακα Q για μια δεδομένη κατάσταση που αντιστοιχεί σε όλες τις δυνατές ενέργειες. Αν η εγγραφή δεν υπάρχει στον Πίνακα Q, θα επιστρέψουμε 0 ως προεπιλογή.
Ας ξεκινήσουμε το Q-Learning
Τώρα είμαστε έτοιμοι να διδάξουμε στον Πέτρο πώς να ισορροπεί!
-
Πρώτα, ας ορίσουμε κάποιες υπερπαραμέτρους: (code block 10)
# hyperparameters alpha = 0.3 gamma = 0.9 epsilon = 0.90
Εδώ, το
alpha
είναι ο ρυθμός μάθησης που καθορίζει σε ποιο βαθμό πρέπει να προσαρμόζουμε τις τρέχουσες τιμές του Πίνακα Q σε κάθε βήμα. Στο προηγούμενο μάθημα ξεκινήσαμε με 1 και στη συνέχεια μειώσαμε τοalpha
σε χαμηλότερες τιμές κατά την εκπαίδευση. Σε αυτό το παράδειγμα θα το κρατήσουμε σταθερό για απλότητα, και μπορείτε να πειραματιστείτε με την προσαρμογή των τιμών τουalpha
αργότερα.Το
gamma
είναι ο παράγοντας έκπτωσης που δείχνει σε ποιο βαθμό πρέπει να δίνουμε προτεραιότητα στη μελλοντική ανταμοιβή έναντι της τρέχουσας ανταμοιβής.Το
epsilon
είναι ο παράγοντας εξερεύνησης/εκμετάλλευσης που καθορίζει αν πρέπει να προτιμούμε την εξερεύνηση ή την εκμετάλλευση. Στον αλγόριθμό μας, σε ποσοστόepsilon
των περιπτώσεων θα επιλέγουμε την επόμενη ενέργεια σύμφωνα με τις τιμές του Πίνακα Q, και στις υπόλοιπες περιπτώσεις θα εκτελούμε μια τυχαία ενέργεια. Αυτό θα μας επιτρέψει να εξερευνήσουμε περιοχές του χώρου αναζήτησης που δεν έχουμε δει ποτέ πριν.✅ Όσον αφορά την ισορροπία - η επιλογή τυχαίας ενέργειας (εξερεύνηση) θα λειτουργεί σαν ένα τυχαίο χτύπημα προς τη λάθος κατεύθυνση, και ο πόλος θα πρέπει να μάθει πώς να ανακτά την ισορροπία από αυτά τα "λάθη".
Βελτίωση του αλγορίθμου
Μπορούμε επίσης να κάνουμε δύο βελτιώσεις στον αλγόριθμό μας από το προηγούμενο μάθημα:
-
Υπολογισμός μέσης σωρευτικής ανταμοιβής, σε έναν αριθμό προσομοιώσεων. Θα εκτυπώνουμε την πρόοδο κάθε 5000 επαναλήψεις και θα υπολογίζουμε τη μέση σωρευτική ανταμοιβή σε αυτή την περίοδο. Αυτό σημαίνει ότι αν πετύχουμε περισσότερους από 195 πόντους - μπορούμε να θεωρήσουμε το πρόβλημα λυμένο, με ακόμη υψηλότερη ποιότητα από την απαιτούμενη.
-
Υπολογισμός μέγιστης μέσης σωρευτικής ανταμοιβής,
Qmax
, και θα αποθηκεύουμε τον Πίνακα Q που αντιστοιχεί σε αυτό το αποτέλεσμα. Όταν εκτελείτε την εκπαίδευση, θα παρατηρήσετε ότι μερικές φορές η μέση σωρευτική ανταμοιβή αρχίζει να μειώνεται, και θέλουμε να διατηρήσουμε τις τιμές του Πίνακα Q που αντιστοιχούν στο καλύτερο μοντέλο που παρατηρήθηκε κατά την εκπαίδευση.
-
Συλλέξτε όλες τις σωρευτικές ανταμοιβές σε κάθε προσομοίωση στο διάνυσμα
rewards
για περαιτέρω σχεδίαση. (code block 11)def probs(v,eps=1e-4): v = v-v.min()+eps v = v/v.sum() return v Qmax = 0 cum_rewards = [] rewards = [] for epoch in range(100000): obs = env.reset() done = False cum_reward=0 # == do the simulation == while not done: s = discretize(obs) if random.random()<epsilon: # exploitation - chose the action according to Q-Table probabilities v = probs(np.array(qvalues(s))) a = random.choices(actions,weights=v)[0] else: # exploration - randomly chose the action a = np.random.randint(env.action_space.n) obs, rew, done, info = env.step(a) cum_reward+=rew ns = discretize(obs) Q[(s,a)] = (1 - alpha) * Q.get((s,a),0) + alpha * (rew + gamma * max(qvalues(ns))) cum_rewards.append(cum_reward) rewards.append(cum_reward) # == Periodically print results and calculate average reward == if epoch%5000==0: print(f"{epoch}: {np.average(cum_rewards)}, alpha={alpha}, epsilon={epsilon}") if np.average(cum_rewards) > Qmax: Qmax = np.average(cum_rewards) Qbest = Q cum_rewards=[]
Αυτό που μπορεί να παρατηρήσετε από αυτά τα αποτελέσματα:
-
Κοντά στον στόχο μας. Είμαστε πολύ κοντά στην επίτευξη του στόχου να πετύχουμε 195 σωρευτικές ανταμοιβές σε 100+ συνεχόμενες εκτελέσεις της προσομοίωσης, ή μπορεί να το έχουμε ήδη πετύχει! Ακόμα κι αν πετύχουμε μικρότερους αριθμούς, δεν το γνωρίζουμε, επειδή υπολογίζουμε τον μέσο όρο σε 5000 εκτελέσεις, και μόνο 100 εκτελέσεις απαιτούνται στα επίσημα κριτήρια.
-
Η ανταμοιβή αρχίζει να μειώνεται. Μερικές φορές η ανταμοιβή αρχίζει να μειώνεται, κάτι που σημαίνει ότι μπορούμε να "καταστρέψουμε" ήδη μαθημένες τιμές στον Πίνακα Q με αυτές που κάνουν την κατάσταση χειρότερη.
Αυτή η παρατήρηση είναι πιο εμφανής αν σχεδιάσουμε την πρόοδο της εκπαίδευσης.
Σχεδίαση Προόδου Εκπαίδευσης
Κατά την εκπαίδευση, έχουμε συλλέξει την τιμή σωρευτικής ανταμοιβής σε κάθε μία από τις επαναλήψεις στο διάνυσμα rewards
. Εδώ είναι πώς φαίνεται όταν το σχεδιάζουμε σε σχέση με τον αριθμό επαναλήψεων:
plt.plot(rewards)
Από αυτό το γράφημα, δεν είναι δυνατό να πούμε κάτι, επειδή λόγω της φύσης της στοχαστικής διαδικασίας εκπαίδευσης το μήκος των συνεδριών εκπαίδευσης ποικίλλει πολύ. Για να βγάλουμε περισσότερο νόημα από αυτό το γράφημα, μπορούμε να υπολογίσουμε τον κινούμενο μέσο όρο σε μια σειρά πειραμάτων, ας πούμε 100. Αυτό μπορεί να γίνει εύκολα χρησιμοποιώντας το np.convolve
: (code block 12)
def running_average(x,window):
return np.convolve(x,np.ones(window)/window,mode='valid')
plt.plot(running_average(rewards,100))
Μεταβολή υπερπαραμέτρων
Για να κάνουμε τη μάθηση πιο σταθερή, έχει νόημα να προσαρμόσουμε κάποιες από τις υπερπαραμέτρους μας κατά την εκπαίδευση. Συγκεκριμένα:
- Για τον ρυθμό μάθησης,
alpha
, μπορούμε να ξεκιν
Εργασία 1: Πειραματιστείτε με τις τιμές των υπερπαραμέτρων και δείτε αν μπορείτε να πετύχετε υψηλότερη συνολική ανταμοιβή. Φτάνετε πάνω από 195; Εργασία 2: Για να λύσετε επίσημα το πρόβλημα, πρέπει να πετύχετε μέσο όρο ανταμοιβής 195 σε 100 συνεχόμενες εκτελέσεις. Μετρήστε το κατά τη διάρκεια της εκπαίδευσης και βεβαιωθείτε ότι έχετε λύσει επίσημα το πρόβλημα!
Βλέποντας το αποτέλεσμα σε δράση
Θα ήταν ενδιαφέρον να δούμε πώς συμπεριφέρεται το εκπαιδευμένο μοντέλο. Ας τρέξουμε τη προσομοίωση και ακολουθήσουμε την ίδια στρατηγική επιλογής ενεργειών όπως κατά την εκπαίδευση, δειγματοληπτώντας σύμφωνα με την κατανομή πιθανότητας στον Q-Table: (code block 13)
obs = env.reset()
done = False
while not done:
s = discretize(obs)
env.render()
v = probs(np.array(qvalues(s)))
a = random.choices(actions,weights=v)[0]
obs,_,done,_ = env.step(a)
env.close()
Θα πρέπει να δείτε κάτι σαν αυτό:
🚀Πρόκληση
Εργασία 3: Εδώ χρησιμοποιούσαμε την τελική έκδοση του Q-Table, η οποία μπορεί να μην είναι η καλύτερη. Θυμηθείτε ότι έχουμε αποθηκεύσει το Q-Table με την καλύτερη απόδοση στη μεταβλητή
Qbest
! Δοκιμάστε το ίδιο παράδειγμα με το Q-Table με την καλύτερη απόδοση, αντιγράφοντας τοQbest
στοQ
και δείτε αν παρατηρείτε τη διαφορά.
Εργασία 4: Εδώ δεν επιλέγαμε την καλύτερη ενέργεια σε κάθε βήμα, αλλά δειγματοληπτούσαμε με την αντίστοιχη κατανομή πιθανότητας. Θα είχε περισσότερο νόημα να επιλέγουμε πάντα την καλύτερη ενέργεια, με τη μεγαλύτερη τιμή στο Q-Table; Αυτό μπορεί να γίνει χρησιμοποιώντας τη συνάρτηση
np.argmax
για να βρούμε τον αριθμό της ενέργειας που αντιστοιχεί στη μεγαλύτερη τιμή του Q-Table. Υλοποιήστε αυτή τη στρατηγική και δείτε αν βελτιώνει την ισορροπία.
Κουίζ μετά το μάθημα
Εργασία
Συμπέρασμα
Μάθαμε πλέον πώς να εκπαιδεύουμε πράκτορες ώστε να πετυχαίνουν καλά αποτελέσματα απλώς παρέχοντάς τους μια συνάρτηση ανταμοιβής που ορίζει την επιθυμητή κατάσταση του παιχνιδιού και δίνοντάς τους την ευκαιρία να εξερευνήσουν έξυπνα τον χώρο αναζήτησης. Εφαρμόσαμε με επιτυχία τον αλγόριθμο Q-Learning σε περιπτώσεις διακριτών και συνεχών περιβαλλόντων, αλλά με διακριτές ενέργειες.
Είναι σημαντικό να μελετήσουμε επίσης καταστάσεις όπου η κατάσταση των ενεργειών είναι επίσης συνεχής και όταν ο χώρος παρατήρησης είναι πολύ πιο περίπλοκος, όπως η εικόνα από την οθόνη του παιχνιδιού Atari. Σε αυτά τα προβλήματα συχνά χρειάζεται να χρησιμοποιήσουμε πιο ισχυρές τεχνικές μηχανικής μάθησης, όπως νευρωνικά δίκτυα, για να πετύχουμε καλά αποτελέσματα. Αυτά τα πιο προχωρημένα θέματα είναι το αντικείμενο του επερχόμενου πιο προχωρημένου μαθήματος AI.
Αποποίηση ευθύνης:
Αυτό το έγγραφο έχει μεταφραστεί χρησιμοποιώντας την υπηρεσία αυτόματης μετάφρασης Co-op Translator. Παρόλο που καταβάλλουμε προσπάθειες για ακρίβεια, παρακαλούμε να έχετε υπόψη ότι οι αυτοματοποιημένες μεταφράσεις ενδέχεται να περιέχουν σφάλματα ή ανακρίβειες. Το πρωτότυπο έγγραφο στη μητρική του γλώσσα θα πρέπει να θεωρείται η αυθεντική πηγή. Για κρίσιμες πληροφορίες, συνιστάται επαγγελματική ανθρώπινη μετάφραση. Δεν φέρουμε ευθύνη για τυχόν παρεξηγήσεις ή εσφαλμένες ερμηνείες που προκύπτουν από τη χρήση αυτής της μετάφρασης.