## आवश्यकताएँ इस पाठ में, हम **OpenAI Gym** नामक एक लाइब्रेरी का उपयोग करेंगे जो विभिन्न **पर्यावरणों** को सिमुलेट करती है। आप इस पाठ का कोड स्थानीय रूप से (जैसे Visual Studio Code से) चला सकते हैं, जिसमें सिमुलेशन एक नई विंडो में खुलेगा। ऑनलाइन कोड चलाते समय, आपको कोड में कुछ बदलाव करने की आवश्यकता हो सकती है, जैसा कि [यहां](https://towardsdatascience.com/rendering-openai-gym-envs-on-binder-and-google-colab-536f99391cc7) वर्णित है। ## OpenAI Gym पिछले पाठ में, खेल के नियम और स्थिति `Board` क्लास द्वारा दी गई थी जिसे हमने स्वयं परिभाषित किया था। यहां हम एक विशेष **सिमुलेशन पर्यावरण** का उपयोग करेंगे, जो बैलेंसिंग पोल के पीछे के भौतिकी को सिमुलेट करेगा। रिइंफोर्समेंट लर्निंग एल्गोरिदम को प्रशिक्षित करने के लिए सबसे लोकप्रिय सिमुलेशन पर्यावरणों में से एक [Gym](https://gym.openai.com/) है, जिसे [OpenAI](https://openai.com/) द्वारा बनाए रखा गया है। इस जिम का उपयोग करके हम विभिन्न **पर्यावरणों** को बना सकते हैं, जैसे कि कार्टपोल सिमुलेशन से लेकर Atari गेम्स तक। > **Note**: आप OpenAI Gym द्वारा उपलब्ध अन्य पर्यावरणों को [यहां](https://gym.openai.com/envs/#classic_control) देख सकते हैं। सबसे पहले, जिम को इंस्टॉल करें और आवश्यक लाइब्रेरी को इंपोर्ट करें (कोड ब्लॉक 1): ```python import sys !{sys.executable} -m pip install gym import gym import matplotlib.pyplot as plt import numpy as np import random ``` ## अभ्यास - कार्टपोल पर्यावरण को प्रारंभ करें कार्टपोल बैलेंसिंग समस्या पर काम करने के लिए, हमें संबंधित पर्यावरण को प्रारंभ करना होगा। प्रत्येक पर्यावरण निम्नलिखित से जुड़ा होता है: - **Observation space** जो उस जानकारी की संरचना को परिभाषित करता है जो हमें पर्यावरण से प्राप्त होती है। कार्टपोल समस्या के लिए, हमें पोल की स्थिति, वेग और कुछ अन्य मान प्राप्त होते हैं। - **Action space** जो संभावित क्रियाओं को परिभाषित करता है। हमारे मामले में, एक्शन स्पेस डिस्क्रीट है और इसमें दो क्रियाएं शामिल हैं - **बाएं** और **दाएं**। (कोड ब्लॉक 2) 1. प्रारंभ करने के लिए, निम्नलिखित कोड टाइप करें: ```python env = gym.make("CartPole-v1") print(env.action_space) print(env.observation_space) print(env.action_space.sample()) ``` पर्यावरण कैसे काम करता है यह देखने के लिए, चलिए 100 चरणों के लिए एक छोटा सिमुलेशन चलाते हैं। प्रत्येक चरण में, हम एक क्रिया प्रदान करते हैं जिसे लिया जाना चाहिए - इस सिमुलेशन में हम बस `action_space` से एक क्रिया को रैंडमली चुनते हैं। 1. नीचे दिए गए कोड को चलाएं और देखें कि इसका क्या परिणाम होता है। ✅ याद रखें कि इस कोड को स्थानीय Python इंस्टॉलेशन पर चलाना बेहतर है! (कोड ब्लॉक 3) ```python env.reset() for i in range(100): env.render() env.step(env.action_space.sample()) env.close() ``` आपको कुछ इस तरह की छवि दिखाई देनी चाहिए: ![non-balancing cartpole](../../../../8-Reinforcement/2-Gym/images/cartpole-nobalance.gif) 1. सिमुलेशन के दौरान, हमें यह तय करने के लिए ऑब्ज़र्वेशन प्राप्त करने की आवश्यकता होती है कि कैसे कार्य करना है। वास्तव में, स्टेप फंक्शन वर्तमान ऑब्ज़र्वेशन, एक रिवॉर्ड फंक्शन, और एक डन फ्लैग लौटाता है जो यह संकेत देता है कि सिमुलेशन जारी रखना समझदारी है या नहीं: (कोड ब्लॉक 4) ```python 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() ``` आपको नोटबुक आउटपुट में कुछ इस तरह दिखाई देगा: ```text [ 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 ``` सिमुलेशन के प्रत्येक चरण में लौटाए गए ऑब्ज़र्वेशन वेक्टर में निम्नलिखित मान होते हैं: - कार्ट की स्थिति - कार्ट का वेग - पोल का कोण - पोल का रोटेशन दर 1. उन संख्याओं के न्यूनतम और अधिकतम मान प्राप्त करें: (कोड ब्लॉक 5) ```python print(env.observation_space.low) print(env.observation_space.high) ``` आप यह भी देख सकते हैं कि प्रत्येक सिमुलेशन चरण पर रिवॉर्ड मान हमेशा 1 होता है। ऐसा इसलिए है क्योंकि हमारा लक्ष्य जितना संभव हो उतना लंबे समय तक जीवित रहना है, यानी पोल को एक उचित वर्टिकल स्थिति में सबसे लंबे समय तक बनाए रखना। ✅ वास्तव में, यदि हम 100 लगातार प्रयासों में औसत रिवॉर्ड 195 प्राप्त करने में सफल होते हैं, तो कार्टपोल सिमुलेशन को हल किया हुआ माना जाता है। ## स्थिति का डिस्क्रीटाइजेशन Q-Learning में, हमें Q-Table बनानी होती है जो यह परिभाषित करती है कि प्रत्येक स्थिति में क्या करना है। ऐसा करने के लिए, हमें स्थिति को **डिस्क्रीट** बनाना होगा, अधिक सटीक रूप से, इसमें सीमित संख्या में डिस्क्रीट मान होने चाहिए। इसलिए, हमें किसी तरह से अपने ऑब्ज़र्वेशन को **डिस्क्रीटाइज** करना होगा, उन्हें सीमित सेट की स्थितियों में मैप करना होगा। हम इसे करने के कुछ तरीके हैं: - **बिन्स में विभाजित करें**। यदि हमें किसी मान का अंतराल पता है, तो हम इस अंतराल को कई **बिन्स** में विभाजित कर सकते हैं, और फिर उस मान को उस बिन नंबर से बदल सकते हैं जिसमें वह आता है। इसे numpy के [`digitize`](https://numpy.org/doc/stable/reference/generated/numpy.digitize.html) मेथड का उपयोग करके किया जा सकता है। इस मामले में, हम स्थिति का आकार सटीक रूप से जान पाएंगे, क्योंकि यह डिजिटलाइजेशन के लिए चुने गए बिन्स की संख्या पर निर्भर करेगा। ✅ हम लीनियर इंटरपोलेशन का उपयोग करके मानों को किसी सीमित अंतराल (जैसे, -20 से 20 तक) में ला सकते हैं, और फिर उन्हें राउंडिंग करके पूर्णांक में बदल सकते हैं। इससे स्थिति के आकार पर थोड़ा कम नियंत्रण मिलता है, खासकर यदि हमें इनपुट मानों की सटीक सीमाएं नहीं पता हों। उदाहरण के लिए, हमारे मामले में 4 में से 2 मानों की कोई ऊपरी/निचली सीमा नहीं है, जिससे असीमित संख्या में स्थितियां हो सकती हैं। हमारे उदाहरण में, हम दूसरे दृष्टिकोण का उपयोग करेंगे। जैसा कि आप बाद में देख सकते हैं, अपरिभाषित ऊपरी/निचली सीमाओं के बावजूद, वे मान शायद ही कभी कुछ सीमित अंतरालों के बाहर मान लेते हैं, इसलिए उन स्थितियों के साथ चरम मान बहुत दुर्लभ होंगे। 1. यहां वह फंक्शन है जो हमारे मॉडल से ऑब्ज़र्वेशन लेगा और 4 पूर्णांक मानों के टपल को उत्पन्न करेगा: (कोड ब्लॉक 6) ```python def discretize(x): return tuple((x/np.array([0.25, 0.25, 0.01, 0.1])).astype(np.int)) ``` 1. चलिए बिन्स का उपयोग करके डिस्क्रीटाइजेशन के दूसरे तरीके का भी पता लगाते हैं: (कोड ब्लॉक 7) ```python 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)) ``` 1. अब एक छोटा सिमुलेशन चलाएं और उन डिस्क्रीट पर्यावरण मानों का निरीक्षण करें। `discretize` और `discretize_bins` दोनों को आज़माने के लिए स्वतंत्र महसूस करें और देखें कि क्या कोई अंतर है। ✅ `discretize_bins` बिन नंबर लौटाता है, जो 0-आधारित होता है। इसलिए इनपुट वैरिएबल के मानों के आसपास 0 के लिए यह अंतराल के मध्य से संख्या (10) लौटाता है। `discretize` में, हमने आउटपुट मानों की रेंज की परवाह नहीं की, जिससे वे नकारात्मक हो सकते हैं, इसलिए स्थिति मान शिफ्ट नहीं होते हैं, और 0 का मतलब 0 होता है। (कोड ब्लॉक 8) ```python 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-Table की संरचना पिछले पाठ में, स्थिति 0 से 8 तक की संख्याओं की एक साधारण जोड़ी थी, और इसलिए Q-Table को numpy टेंसर द्वारा 8x8x2 के आकार के साथ प्रस्तुत करना सुविधाजनक था। यदि हम बिन्स डिस्क्रीटाइजेशन का उपयोग करते हैं, तो हमारे स्थिति वेक्टर का आकार भी ज्ञात है, इसलिए हम वही दृष्टिकोण अपना सकते हैं और स्थिति को 20x20x10x10x2 के आकार के ऐरे द्वारा प्रस्तुत कर सकते हैं (यहां 2 एक्शन स्पेस का आयाम है, और पहले आयाम ऑब्ज़र्वेशन स्पेस में प्रत्येक पैरामीटर के लिए उपयोग किए गए बिन्स की संख्या से संबंधित हैं)। हालांकि, कभी-कभी ऑब्ज़र्वेशन स्पेस के सटीक आयाम ज्ञात नहीं होते। `discretize` फंक्शन के मामले में, हम कभी भी यह सुनिश्चित नहीं कर सकते कि हमारी स्थिति निश्चित सीमाओं के भीतर रहती है, क्योंकि कुछ मूल मान बाउंड नहीं होते। इसलिए, हम थोड़ा अलग दृष्टिकोण अपनाएंगे और Q-Table को एक डिक्शनरी द्वारा प्रस्तुत करेंगे। 1. *(state,action)* जोड़ी को डिक्शनरी की कुंजी के रूप में उपयोग करें, और मान Q-Table एंट्री मान से संबंधित होगा। (कोड ब्लॉक 9) ```python Q = {} actions = (0,1) def qvalues(state): return [Q.get((state,a),0) for a in actions] ``` यहां हमने एक फंक्शन `qvalues()` भी परिभाषित किया है, जो एक दिए गए स्थिति के लिए Q-Table मानों की सूची लौटाता है जो सभी संभावित क्रियाओं से संबंधित है। यदि Q-Table में एंट्री मौजूद नहीं है, तो हम डिफ़ॉल्ट रूप से 0 लौटाएंगे। ## चलिए Q-Learning शुरू करें अब हम पीटर को बैलेंस करना सिखाने के लिए तैयार हैं! 1. सबसे पहले, कुछ हाइपरपैरामीटर सेट करें: (कोड ब्लॉक 10) ```python # hyperparameters alpha = 0.3 gamma = 0.9 epsilon = 0.90 ``` यहां, `alpha` **लर्निंग रेट** है जो यह परिभाषित करता है कि हमें प्रत्येक चरण में Q-Table के वर्तमान मानों को किस हद तक समायोजित करना चाहिए। पिछले पाठ में हमने 1 से शुरुआत की थी, और फिर प्रशिक्षण के दौरान `alpha` को कम मानों में घटा दिया था। इस उदाहरण में हम इसे स्थिर रखेंगे केवल सादगी के लिए, और आप बाद में `alpha` मानों को समायोजित करने के साथ प्रयोग कर सकते हैं। `gamma` **डिस्काउंट फैक्टर** है जो यह दिखाता है कि हमें वर्तमान रिवॉर्ड की तुलना में भविष्य के रिवॉर्ड को किस हद तक प्राथमिकता देनी चाहिए। `epsilon` **एक्सप्लोरेशन/एक्सप्लॉइटेशन फैक्टर** है जो यह निर्धारित करता है कि हमें एक्सप्लोरेशन को एक्सप्लॉइटेशन पर या इसके विपरीत प्राथमिकता देनी चाहिए। हमारे एल्गोरिदम में, हम `epsilon` प्रतिशत मामलों में Q-Table मानों के अनुसार अगली क्रिया का चयन करेंगे, और शेष मामलों में हम एक रैंडम क्रिया निष्पादित करेंगे। यह हमें खोज स्थान के उन क्षेत्रों का पता लगाने की अनुमति देगा जिन्हें हमने पहले कभी नहीं देखा। ✅ बैलेंसिंग के संदर्भ में - रैंडम क्रिया (एक्सप्लोरेशन) चुनना गलत दिशा में एक रैंडम पंच की तरह काम करेगा, और पोल को उन "गलतियों" से बैलेंस को पुनः प्राप्त करना सीखना होगा। ### एल्गोरिदम में सुधार करें हम पिछले पाठ से अपने एल्गोरिदम में दो सुधार कर सकते हैं: - **औसत संचयी रिवॉर्ड की गणना करें**, कई सिमुलेशन के दौरान। हम हर 5000 इटरेशन पर प्रगति को प्रिंट करेंगे, और उस समय अवधि के दौरान अपने संचयी रिवॉर्ड को औसत करेंगे। इसका मतलब है कि यदि हमें 195 से अधिक अंक मिलते हैं - तो हम समस्या को हल किया हुआ मान सकते हैं, और आवश्यक गुणवत्ता से भी बेहतर। - **अधिकतम औसत संचयी परिणाम की गणना करें**, `Qmax`, और हम उस परिणाम से संबंधित Q-Table को स्टोर करेंगे। जब आप प्रशिक्षण चलाते हैं तो आप देखेंगे कि कभी-कभी औसत संचयी परिणाम गिरने लगते हैं, और हम प्रशिक्षण के दौरान देखे गए सर्वश्रेष्ठ मॉडल से संबंधित Q-Table मानों को रखना चाहते हैं। 1. प्रत्येक सिमुलेशन में सभी संचयी रिवॉर्ड को `rewards` वेक्टर में एकत्र करें ताकि आगे प्लॉटिंग की जा सके। (कोड ब्लॉक 11) ```python 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() Qmax: Qmax = np.average(cum_rewards) Qbest = Q cum_rewards=[] ``` आप इन परिणामों से क्या देख सकते हैं: - **हमारे लक्ष्य के करीब**। हम 100+ लगातार सिमुलेशन रन में 195 संचयी रिवॉर्ड प्राप्त करने के लक्ष्य को प्राप्त करने के बहुत करीब हैं, या हमने वास्तव में इसे प्राप्त कर लिया है! भले ही हमें छोटे नंबर मिलें, हमें अभी भी पता नहीं है, क्योंकि हम 5000 रन पर औसत करते हैं, और औपचारिक मानदंड में केवल 100 रन की आवश्यकता होती है। - **रिवॉर्ड गिरने लगते हैं**। कभी-कभी रिवॉर्ड गिरने लगते हैं, जिसका मतलब है कि हम Q-Table में पहले से सीखे गए मानों को उन मानों से "नष्ट" कर सकते हैं जो स्थिति को खराब बनाते हैं। यह अवलोकन अधिक स्पष्ट रूप से दिखाई देता है यदि हम प्रशिक्षण प्रगति को प्लॉट करें। ## प्रशिक्षण प्रगति का प्लॉट बनाना प्रशिक्षण के दौरान, हमने प्रत्येक इटरेशन में संचयी रिवॉर्ड मान को `rewards` वेक्टर में एकत्र किया। यहां यह कैसा दिखता है जब हम इसे इटरेशन नंबर के खिलाफ प्लॉट करते हैं: ```python plt.plot(rewards) ``` ![raw progress](../../../../8-Reinforcement/2-Gym/images/train_progress_raw.png) इस ग्राफ से कुछ भी बताना संभव नहीं है, क्योंकि स्टोकेस्टिक प्रशिक्षण प्रक्रिया की प्रकृति के कारण प्रशिक्षण सत्रों की लंबाई बहुत भिन्न होती है। इस ग्राफ को अधिक समझने योग्य बनाने के लिए, हम 100 के एक श्रृंखला पर **रनिंग एवरेज** की गणना कर सकते हैं। इसे `np.convolve` का उपयोग करके सुविधाजनक रूप से किया जा सकता है: (कोड ब्लॉक 12) ```python def running_average(x,window): return np.convolve(x,np.ones(window)/window,mode='valid') plt.plot(running_average(rewards,100)) ``` ![training progress](../../../../8-Reinforcement/2-Gym/images/train_progress_runav.png) ## हाइपरपैरामीटर बदलना सीखने को अधिक स्थिर बनाने के लिए, प्रशिक्षण के दौरान हमारे कुछ हाइपरपैरामीटर को समायोजित करना समझ में आता है। विशेष रूप से: - **लर्निंग रेट** `alpha` के लिए, हम 1 के करीब मानों से शुरू कर सकते हैं, और फिर पैरामीटर को कम करते रह सकते हैं। समय के साथ, हमें Q-Table में अच्छे प्रायिकता मान मिलेंगे, और इसलिए हमें उन्हें थोड़ा समायोजित करना चाहिए, और नए मानों के साथ पूरी तरह से ओवरराइट नहीं करना चाहिए। - **epsilon बढ़ाएं**। हम `epsilon` को धीरे-धीरे बढ़ाना चाह सकते हैं, ताकि कम एक्सप्लोरेशन और अधिक एक्सप्लॉइटेशन करें। शायद कम `epsilon` मान से शुरू करना और इसे लगभग 1 तक बढ़ाना समझ में आता है। > **कार्य 1**: हाइपरपैरामीटर मानों के साथ प्रयोग करें और देखें कि क्या आप उच्च संचयी इनाम प्राप्त कर सकते हैं। क्या आप 195 से ऊपर प्राप्त कर रहे हैं? > **कार्य 2**: समस्या को औपचारिक रूप से हल करने के लिए, आपको 100 लगातार रन के दौरान 195 औसत इनाम प्राप्त करना होगा। प्रशिक्षण के दौरान इसे मापें और सुनिश्चित करें कि आपने समस्या को औपचारिक रूप से हल कर लिया है! ## परिणाम को क्रियान्वित होते हुए देखना यह देखना दिलचस्प होगा कि प्रशिक्षित मॉडल कैसे व्यवहार करता है। चलिए सिमुलेशन चलाते हैं और प्रशिक्षण के दौरान उपयोग की गई वही एक्शन चयन रणनीति अपनाते हैं, Q-Table में प्रायिकता वितरण के अनुसार सैंपलिंग करते हुए: (कोड ब्लॉक 13) ```python 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() ``` आपको कुछ ऐसा दिखाई देना चाहिए: ![एक बैलेंसिंग कार्टपोल](../../../../8-Reinforcement/2-Gym/images/cartpole-balance.gif) --- ## 🚀चुनौती > **कार्य 3**: यहां, हम Q-Table की अंतिम प्रति का उपयोग कर रहे थे, जो शायद सबसे अच्छी न हो। याद रखें कि हमने सबसे अच्छा प्रदर्शन करने वाले Q-Table को `Qbest` वेरिएबल में संग्रहीत किया है! `Qbest` को `Q` पर कॉपी करके उसी उदाहरण को आज़माएं और देखें कि क्या आपको कोई अंतर दिखाई देता है। > **कार्य 4**: यहां हम प्रत्येक चरण पर सबसे अच्छा एक्शन नहीं चुन रहे थे, बल्कि संबंधित प्रायिकता वितरण के साथ सैंपलिंग कर रहे थे। क्या हमेशा सबसे अच्छा एक्शन चुनना अधिक समझदारी होगी, जिसमें Q-Table का सबसे उच्च मान हो? यह `np.argmax` फ़ंक्शन का उपयोग करके किया जा सकता है, जो उच्चतम Q-Table मान के अनुरूप एक्शन नंबर का पता लगाता है। इस रणनीति को लागू करें और देखें कि क्या यह बैलेंसिंग में सुधार करता है। ## [पोस्ट-लेक्चर क्विज़](https://ff-quizzes.netlify.app/en/ml/) ## असाइनमेंट [माउंटेन कार को प्रशिक्षित करें](assignment.md) ## निष्कर्ष अब हमने यह सीख लिया है कि एजेंट्स को केवल एक इनाम फ़ंक्शन प्रदान करके, जो गेम की वांछित स्थिति को परिभाषित करता है, और उन्हें खोज स्थान को बुद्धिमानी से एक्सप्लोर करने का अवसर देकर अच्छे परिणाम प्राप्त करने के लिए प्रशिक्षित कैसे किया जाए। हमने Q-Learning एल्गोरिदम को डिस्क्रीट और कंटीन्यस एनवायरनमेंट्स के मामलों में सफलतापूर्वक लागू किया है, लेकिन डिस्क्रीट एक्शन्स के साथ। यह भी अध्ययन करना महत्वपूर्ण है कि जब एक्शन स्टेट भी कंटीन्यस हो और जब ऑब्ज़र्वेशन स्पेस अधिक जटिल हो, जैसे कि एटारी गेम स्क्रीन की छवि। इन समस्याओं में अक्सर अच्छे परिणाम प्राप्त करने के लिए अधिक शक्तिशाली मशीन लर्निंग तकनीकों, जैसे कि न्यूरल नेटवर्क्स, का उपयोग करना आवश्यक होता है। ये अधिक उन्नत विषय हमारे आगामी उन्नत AI कोर्स का विषय हैं। --- **अस्वीकरण**: यह दस्तावेज़ AI अनुवाद सेवा [Co-op Translator](https://github.com/Azure/co-op-translator) का उपयोग करके अनुवादित किया गया है। जबकि हम सटीकता के लिए प्रयास करते हैं, कृपया ध्यान दें कि स्वचालित अनुवाद में त्रुटियां या अशुद्धियां हो सकती हैं। मूल भाषा में उपलब्ध मूल दस्तावेज़ को आधिकारिक स्रोत माना जाना चाहिए। महत्वपूर्ण जानकारी के लिए, पेशेवर मानव अनुवाद की सिफारिश की जाती है। इस अनुवाद के उपयोग से उत्पन्न किसी भी गलतफहमी या गलत व्याख्या के लिए हम उत्तरदायी नहीं हैं।