You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ML-For-Beginners/translations/mr/8-Reinforcement/2-Gym
leestott f915efe2b4
🌐 Update translations via Co-op Translator
2 weeks ago
..
solution 🌐 Update translations via Co-op Translator 3 weeks ago
README.md 🌐 Update translations via Co-op Translator 2 weeks ago
assignment.md 🌐 Update translations via Co-op Translator 3 weeks ago
notebook.ipynb 🌐 Update translations via Co-op Translator 3 weeks ago

README.md

पूर्वअट

या धड्यात, आम्ही OpenAI Gym नावाच्या लायब्ररीचा वापर विविध परिसरांचे अनुकरण करण्यासाठी करणार आहोत. तुम्ही हा धडा स्थानिकरित्या (उदा. Visual Studio Code मधून) चालवू शकता, अशा वेळी अनुकरण एका नवीन विंडोमध्ये उघडेल. ऑनलाइन कोड चालवताना, तुम्हाला कोडमध्ये काही बदल करावे लागू शकतात, जसे की येथे वर्णन केले आहे.

OpenAI Gym

मागील धड्यात, खेळाचे नियम आणि स्थिती आम्ही स्वतः परिभाषित केलेल्या Board वर्गाद्वारे दिले होते. येथे आम्ही एक विशेष अनुकरण वातावरण वापरणार आहोत, जे संतुलन राखणाऱ्या खांबाच्या भौतिकशास्त्राचे अनुकरण करेल. reinforcement learning अल्गोरिदम प्रशिक्षणासाठी सर्वात लोकप्रिय अनुकरण वातावरणांपैकी एक Gym आहे, जे OpenAI द्वारे देखरेख केली जाते. या जिमचा वापर करून आपण विविध परिसर तयार करू शकतो, जसे की cartpole अनुकरण ते Atari गेम्स.

टीप: OpenAI Gym मधील इतर उपलब्ध वातावरण तुम्ही येथे पाहू शकता.

सुरुवातीला, जिम स्थापित करूया आणि आवश्यक लायब्ररी आयात करूया (कोड ब्लॉक 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 discrete आहे आणि दोन क्रिया आहेत - डावीकडे आणि उजवीकडे. (कोड ब्लॉक 2)

  1. प्रारंभ करण्यासाठी, खालील कोड टाइप करा:

    env = gym.make("CartPole-v1")
    print(env.action_space)
    print(env.observation_space)
    print(env.action_space.sample())
    

वातावरण कसे कार्य करते हे पाहण्यासाठी, 100 स्टेप्ससाठी एक लहान अनुकरण चालवूया. प्रत्येक स्टेपवर, आपण घेण्याची क्रिया प्रदान करतो - या अनुकरणात आम्ही फक्त action_space मधून एक क्रिया निवडतो.

  1. खालील कोड चालवा आणि त्याचा परिणाम पहा.

    लक्षात ठेवा की हा कोड स्थानिक Python इंस्टॉलेशनवर चालवणे अधिक चांगले आहे! (कोड ब्लॉक 3)

    env.reset()
    
    for i in range(100):
       env.render()
       env.step(env.action_space.sample())
    env.close()
    

    तुम्हाला खालील प्रतिमेसारखे काहीतरी दिसेल:

    non-balancing cartpole

  2. अनुकरणादरम्यान, आपल्याला निर्णय घेण्यासाठी निरीक्षणे मिळवणे आवश्यक आहे. प्रत्यक्षात, step फंक्शन वर्तमान निरीक्षणे, एक reward फंक्शन, आणि done फ्लॅग परत करते, जे अनुकरण सुरू ठेवण्यासारखे आहे की नाही हे सूचित करते: (कोड ब्लॉक 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()
    

    तुम्हाला नोटबुक आउटपुटमध्ये असे काहीतरी दिसेल:

    [ 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
    

    अनुकरणाच्या प्रत्येक स्टेपवर परत येणारा observation व्हेक्टर खालील मूल्ये समाविष्ट करतो:

    • गाडीची स्थिती
    • गाडीचा वेग
    • खांबाचा कोन
    • खांबाचा फिरण्याचा दर
  3. त्या संख्यांचे किमान आणि जास्तीत जास्त मूल्य मिळवा: (कोड ब्लॉक 5)

    print(env.observation_space.low)
    print(env.observation_space.high)
    

    तुम्ही हे देखील लक्षात घेऊ शकता की प्रत्येक अनुकरण स्टेपवरील reward मूल्य नेहमी 1 असते. कारण आपले ध्येय जास्तीत जास्त वेळ टिकून राहणे आहे, म्हणजे खांबाला शक्य तितक्या उभ्या स्थितीत ठेवणे.

    प्रत्यक्षात, CartPole अनुकरण सोडवले जाते जर आपण 100 सलग चाचण्यांमध्ये 195 च्या सरासरी reward मिळवू शकलो.

स्थितीचे discretization

Q-Learning मध्ये, आपल्याला Q-Table तयार करणे आवश्यक आहे जे प्रत्येक स्थितीत काय करावे हे परिभाषित करते. हे करण्यासाठी, स्थिती discreet असणे आवश्यक आहे, अधिक स्पष्टपणे, त्यात मर्यादित संख्येने discrete मूल्ये असावीत. म्हणून, आपल्याला काहीतरी करून आपली निरीक्षणे discretize करणे आवश्यक आहे, त्यांना मर्यादित स्थितींच्या संचाशी नकाशा करणे.

हे करण्याचे काही मार्ग आहेत:

  • Bins मध्ये विभागणे. जर आपल्याला एखाद्या मूल्याचा अंतराल माहित असेल, तर आपण या अंतरालाला काही bins मध्ये विभागू शकतो आणि नंतर त्या मूल्याला त्याच्या bin क्रमांकाने बदलू शकतो. हे numpy च्या digitize पद्धतीचा वापर करून केले जाऊ शकते. या प्रकरणात, आपल्याला स्थितीचा आकार अचूकपणे माहित असेल, कारण तो डिजिटलायझेशनसाठी निवडलेल्या bins च्या संख्येवर अवलंबून असेल.

आपण linear interpolation वापरून मूल्ये काही मर्यादित अंतरालात (उदा. -20 ते 20) आणू शकतो आणि नंतर rounding करून संख्यांना integers मध्ये रूपांतरित करू शकतो. यामुळे स्थितीच्या आकारावर थोडेसे नियंत्रण मिळते, विशेषतः जर आपल्याला इनपुट मूल्यांच्या अचूक श्रेणी माहित नसतील. उदाहरणार्थ, आपल्या प्रकरणात 4 पैकी 2 मूल्ये त्यांच्या वरच्या/खालच्या मर्यादांवर मर्यादित नाहीत, ज्यामुळे स्थितींची अमर्यादित संख्या होऊ शकते.

आपल्या उदाहरणात, आपण दुसऱ्या पद्धतीचा वापर करू. तुम्ही नंतर लक्षात घेऊ शकता की अपरिभाषित वरच्या/खालच्या मर्यादांनंतरही, ती मूल्ये क्वचितच काही मर्यादित अंतरालाच्या बाहेर जातात, त्यामुळे ती स्थिती ज्यामध्ये extreme मूल्ये असतात, फारच दुर्मिळ असतील.

  1. येथे एक फंक्शन आहे जे आपल्या मॉडेलमधून निरीक्षण घेईल आणि 4 integer मूल्यांच्या tuple तयार करेल: (कोड ब्लॉक 6)

    def discretize(x):
        return tuple((x/np.array([0.25, 0.25, 0.01, 0.1])).astype(np.int))
    
  2. चला bins वापरून दुसऱ्या discretization पद्धतीचा शोध घेऊया: (कोड ब्लॉक 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))
    
  3. आता एक लहान अनुकरण चालवूया आणि त्या discrete वातावरण मूल्यांचे निरीक्षण करूया. discretize आणि discretize_bins दोन्ही वापरून पहा आणि फरक आहे का ते पहा.

    discretize_bins bin क्रमांक परत करते, जे 0-आधारित असते. त्यामुळे इनपुट व्हेरिएबलच्या 0 च्या आसपासच्या मूल्यांसाठी ते अंतरालाच्या मध्यभागी असलेले क्रमांक (10) परत करते. discretize मध्ये, आम्ही आउटपुट मूल्यांच्या श्रेणीची काळजी घेतली नाही, त्यांना नकारात्मक होऊ दिले, त्यामुळे स्थिती मूल्ये shift झाली नाहीत आणि 0 म्हणजे 0. (कोड ब्लॉक 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 सुरू असलेली ओळ uncomment करा जर तुम्हाला वातावरण कसे कार्य करते ते पाहायचे असेल. अन्यथा तुम्ही ते background मध्ये चालवू शकता, जे जलद आहे. आम्ही Q-Learning प्रक्रियेदरम्यान या "अदृश्य" अंमलबजावणीचा वापर करू.

Q-Table ची रचना

मागील धड्यात, स्थिती 0 ते 8 पर्यंतच्या संख्यांच्या साध्या जोड्या होती, त्यामुळे Q-Table ला numpy tensor द्वारे 8x8x2 आकाराने दर्शवणे सोयीचे होते. जर आपण bins discretization वापरतो, तर आपल्या स्थिती व्हेक्टरचा आकार देखील माहित आहे, त्यामुळे आपण समान दृष्टिकोन वापरू शकतो आणि स्थितीला 20x20x10x10x2 आकाराच्या array ने दर्शवू शकतो (येथे 2 म्हणजे action space चे परिमाण आहे, आणि पहिली परिमाणे observation space मधील प्रत्येक पॅरामीटरसाठी निवडलेल्या bins च्या संख्येशी संबंधित आहेत).

तथापि, कधीकधी observation space च्या अचूक परिमाणे माहित नसतात. discretize फंक्शनच्या बाबतीत, आपल्याला कधीही खात्री नसते की आपली स्थिती काही मर्यादांमध्ये राहते, कारण काही मूळ मूल्ये मर्यादित नाहीत. त्यामुळे, आम्ही थोडा वेगळा दृष्टिकोन वापरू आणि Q-Table ला dictionary द्वारे दर्शवू.

  1. (state,action) जोड्याचा dictionary key म्हणून वापर करा, आणि मूल्य Q-Table entry value शी संबंधित असेल. (कोड ब्लॉक 9)

    Q = {}
    actions = (0,1)
    
    def qvalues(state):
        return [Q.get((state,a),0) for a in actions]
    

    येथे आम्ही qvalues() नावाचे फंक्शन देखील परिभाषित करतो, जे दिलेल्या स्थितीसाठी Q-Table मूल्यांची यादी परत करते जी सर्व संभाव्य क्रियांशी संबंधित असते. जर entry Q-Table मध्ये उपस्थित नसेल, तर आम्ही default म्हणून 0 परत करू.

चला Q-Learning सुरू करूया

आता आपण पीटरला संतुलन शिकवण्यासाठी तयार आहोत!

  1. प्रथम, काही hyperparameters सेट करूया: (कोड ब्लॉक 10)

    # hyperparameters
    alpha = 0.3
    gamma = 0.9
    epsilon = 0.90
    

    येथे, alpha म्हणजे learning rate जे प्रत्येक स्टेपवर Q-Table च्या वर्तमान मूल्यांना कोणत्या प्रमाणात adjust करावे हे परिभाषित करते. मागील धड्यात आपण 1 पासून सुरुवात केली आणि नंतर प्रशिक्षणादरम्यान alpha कमी केले. या उदाहरणात आपण सोपेपणासाठी ते स्थिर ठेवू आणि नंतर alpha मूल्ये adjust करण्याचा प्रयोग करू शकता.

    gamma म्हणजे discount factor जे भविष्याच्या reward ला वर्तमान reward वर किती प्रमाणात प्राधान्य द्यावे हे दर्शवते.

    epsilon म्हणजे exploration/exploitation factor जे ठरवते की आपल्याला exploration ला प्राधान्य द्यावे की exploitation ला. आपल्या अल्गोरिदममध्ये, आम्ही epsilon टक्केवारीच्या प्रकरणांमध्ये Q-Table मूल्यांनुसार पुढील क्रिया निवडू, आणि उर्वरित प्रकरणांमध्ये आम्ही random क्रिया execute करू. यामुळे आपल्याला शोध स्थानाच्या अशा भागांचा शोध घेता येईल जे आपण कधीही पाहिले नाहीत.

    संतुलनाच्या बाबतीत - random क्रिया (exploration) निवडणे चुकीच्या दिशेने random धक्का देण्यासारखे असेल, आणि खांबाने त्या "चुका" मधून संतुलन कसे पुनर्प्राप्त करावे ते शिकले पाहिजे.

अल्गोरिदम सुधारित करा

आपल्या मागील धड्यातील अल्गोरिदममध्ये दोन सुधारणा करू शकतो:

  • सरासरी cumulative reward गणना करा, अनेक अनुकरणांवर. आम्ही प्रत्येक 5000 iterations वर प्रगती print करू आणि त्या कालावधीतील cumulative reward ची सरासरी काढू. याचा अर्थ असा की जर आपण 195 पेक्षा जास्त गुण मिळवले - तर आपण समस्या सोडवली असे मानू शकतो, आणि आवश्यकतेपेक्षा उच्च गुणवत्तेसह.

  • जास्तीत जास्त सरासरी cumulative परिणाम Qmax गणना करा, आणि आम्ही त्या परिणामाशी संबंधित Q-Table साठवू. जेव्हा तुम्ही प्रशिक्षण चालवता तेव्हा तुम्हाला लक्षात येईल की कधीकधी सरासरी cumulative परिणाम कमी होतो, आणि आम्हाला प्रशिक्षणादरम्यान पाहिलेल्या सर्वोत्तम मॉडेलशी संबंधित Q-Table च्या मूल्ये ठेवायची आहेत.

  1. प्रत्येक अनुकरणाच्या cumulative rewards rewards व्हेक्टरमध्ये गोळा करा जेणेकरून नंतर plotting करता येईल. (कोड ब्लॉक 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=[]
    

तुम्ही या परिणामांमधून काय लक्षात घेऊ शकता:

  • आपल्या ध्येयाच्या जवळ. आम्ही 100+ सलग अनुकरणांच्या सरासरी 195 cumulative rewards मिळवण्याच्या ध्येयाच्या खूप जवळ आहोत, किंवा आम्ही प्रत्यक्षात ते साध्य केले असेल! जरी आम्हाला कमी संख्या मिळाल्या तरी, आम्हाला माहित नाही, कारण आम्ही 5000 runs वर सरासरी काढतो, आणि औपचारिक निकषांमध्ये फक्त 100 runs आवश्यक आहेत.

  • Reward कमी होतो. कधीकधी reward कमी होतो, याचा अर्थ असा की आपण Q-Table मध्ये आधी शिकलेल्या मूल्यांना "नष्ट" करू शकतो आणि परिस्थिती अधिक वाईट बनवणाऱ्या नवीन मूल्यांसह बदलू शकतो.

हे निरीक्षण प्रशिक्षण प्रगती plot केल्यावर अधिक स्पष्टपणे दिसते.

प्रशिक्षण प्रगतीचे plotting

प्रशिक्षणादरम्यान, आम्ही प्रत्येक iteration वर cumulative reward मूल्य rewards व्हेक्टरमध्ये गोळा केले आहे. जेव्हा आपण ते iteration क्रमांकाच्या विरोधात plot करतो तेव्हा ते असे दिसते:

plt.plot(rewards)

raw progress

या graph वरून काहीही सांगणे शक्य नाही, कारण stochastic प्रशिक्षण प्रक्रियेच्या स्वरूपामुळे प्रशिक्षण सत्रांची लांबी खूप वेगळी असते. या graph ला अधिक अर्थपूर्ण बनवण्यासाठी, आपण 100 चाचण्यांवर running average गणना करू शकतो. हे np.convolve वापरून सोयीस्करपणे केले जाऊ शकते: (कोड ब्लॉक 12)

def running_average(x,window):
    return np.convolve(x,np.ones(window)/window,mode='valid')

plt.plot(running_average(rewards,100))

training progress

hyperparameters बदलणे

शिकणे अधिक स्थिर बनवण्यासाठी, प्रशिक्षणादरम्यान काही hyperparameters adjust करणे योग्य ठरेल. विशेषतः:

  • Learning rate alpha साठी, आपण 1 च्या जवळपासच्या मूल्यांपासून सुरुवात करू शकतो आणि नंतर हा parameter कमी करत राहू शकतो. कालांतराने, आपल्याला Q-Table मध्ये चांगले probability मूल्ये मिळतील, आणि त्यामुळे आपण त्यांना थोडे adjust करावे, आणि नवीन मूल्यांसह पूर्णपणे overwrite करू नये.

  • epsilon वाढवा. आपण epsilon हळूहळू वाढवू इच्छितो, जेणेकरून कमी शोध घेऊन अधिक exploitation करू शकतो. कदाचित कमी epsilon मूल्याने सुरुवात करणे आणि जवळजवळ 1 पर्यंत जाणे योग्य ठरेल.

कार्य 1: हायपरपॅरामिटरच्या मूल्यांशी प्रयोग करा आणि पाहा की तुम्ही जास्त एकत्रित बक्षीस मिळवू शकता का. तुम्हाला 195 पेक्षा जास्त मिळत आहे का? कार्य 2: समस्या औपचारिकपणे सोडवण्यासाठी, तुम्हाला 100 सलग धावांमध्ये 195 सरासरी बक्षीस मिळवणे आवश्यक आहे. प्रशिक्षणादरम्यान ते मोजा आणि सुनिश्चित करा की तुम्ही समस्या औपचारिकपणे सोडवली आहे!

परिणाम प्रत्यक्षात पाहणे

प्रशिक्षित मॉडेल कसे वागते हे प्रत्यक्षात पाहणे मनोरंजक ठरेल. चला सिम्युलेशन चालवूया आणि प्रशिक्षणादरम्यान वापरलेल्या समान कृती निवडण्याच्या रणनीतीचे अनुसरण करूया, Q-Table मधील संभाव्यता वितरणानुसार नमुना घेऊया: (कोड ब्लॉक 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 व्हेरिएबलमध्ये संग्रहित केला आहे! Qbest ला Q वर कॉपी करून सर्वोत्तम कामगिरी करणाऱ्या Q-Table सह समान उदाहरण वापरून पहा आणि तुम्हाला काही फरक जाणवतो का ते पहा.

कार्य 4: येथे आम्ही प्रत्येक चरणावर सर्वोत्तम कृती निवडत नव्हतो, तर संबंधित संभाव्यता वितरणासह नमुना घेत होतो. नेहमीच सर्वोत्तम कृती निवडणे अधिक अर्थपूर्ण ठरेल का, ज्यामध्ये Q-Table मूल्य सर्वाधिक आहे? हे np.argmax फंक्शन वापरून करता येते, जे उच्च Q-Table मूल्याशी संबंधित कृती क्रमांक शोधते. ही रणनीती अंमलात आणा आणि संतुलन सुधारते का ते पहा.

व्याख्यानानंतरचा क्विझ

असाइनमेंट

माउंटन कार प्रशिक्षित करा

निष्कर्ष

आता आपण एजंट्सना फक्त बक्षीस फंक्शन प्रदान करून चांगले परिणाम मिळवण्यासाठी प्रशिक्षण देणे शिकले आहे, जे खेळाच्या इच्छित स्थितीची व्याख्या करते, आणि त्यांना शोध जागा बुद्धिमत्तेने एक्सप्लोर करण्याची संधी देते. आम्ही Q-Learning अल्गोरिदम यशस्वीरित्या डिस्क्रीट आणि सतत वातावरणाच्या बाबतीत लागू केला आहे, परंतु डिस्क्रीट कृतींसह.

कृती स्थिती देखील सतत असते आणि निरीक्षण जागा खूपच जटिल असते अशा परिस्थितींचा अभ्यास करणे महत्त्वाचे आहे, जसे की अटारी गेम स्क्रीनवरील प्रतिमा. अशा समस्यांमध्ये चांगले परिणाम मिळवण्यासाठी आपल्याला neural networks सारख्या अधिक शक्तिशाली मशीन लर्निंग तंत्रांचा वापर करावा लागतो. हे अधिक प्रगत विषय आमच्या आगामी प्रगत AI कोर्सचा विषय आहेत.


अस्वीकरण:
हा दस्तऐवज AI भाषांतर सेवा Co-op Translator चा वापर करून भाषांतरित करण्यात आला आहे. आम्ही अचूकतेसाठी प्रयत्नशील असलो तरी, कृपया लक्षात घ्या की स्वयंचलित भाषांतरांमध्ये त्रुटी किंवा अचूकतेचा अभाव असू शकतो. मूळ भाषेतील मूळ दस्तऐवज हा अधिकृत स्रोत मानला जावा. महत्त्वाच्या माहितीसाठी व्यावसायिक मानवी भाषांतराची शिफारस केली जाते. या भाषांतराचा वापर केल्यामुळे उद्भवणाऱ्या कोणत्याही गैरसमज किंवा चुकीच्या अर्थासाठी आम्ही जबाबदार राहणार नाही.