Word2vec के बारे में आप जो कुछ भी जानते हैं वह सच नहीं है

मूल वैज्ञानिक लेख और अनगिनत ब्लॉग पोस्टों में नकारात्मक-नमूना स्किप-ग्राम वास्तुकला के रूप में शब्द 2vec की क्लासिक व्याख्या इस तरह दिखती है:

while(1) { 1. vf = vector of focus word 2. vc = vector of focus word 3. train such that (vc . vf = 1) 4. for(0 <= i <= negative samples): vneg = vector of word *not* in context train such that (vf . vneg = 0) } 

वास्तव में, यदि आप google [word2vec स्किपग्राम], जो हम देखते हैं:


लेकिन ये सभी कार्यान्वयन गलत हैं

C में word2vec का मूल कार्यान्वयन अलग तरीके से काम करता है और इससे मौलिक रूप से अलग है। जो लोग शब्द 2vec से शब्द एम्बेडिंग वाले पेशेवर सिस्टम को लागू करते हैं, वे निम्न में से एक करते हैं:

  1. सी के मूल कार्यान्वयन को सीधे कॉल करें।
  2. gensim कार्यान्वयन का उपयोग करें, जो कि स्रोत C से चर नाम से मेल खाने वाले हद तक अनुवादित है।

वास्तव में, gensim केवल सही C कार्यान्वयन है जिसके बारे में मुझे पता है

C क्रियान्वयन


सी कार्यान्वयन वास्तव में प्रत्येक शब्द के लिए दो वैक्टर का समर्थन करता है । शब्द के लिए एक वेक्टर फोकस में है, और दूसरा शब्द संदर्भ में है। (परिचित लगता है? सही, GloVe डेवलपर्स ने इस तथ्य का उल्लेख किए बिना शब्द 2vec से एक विचार उधार लिया है!)

सी कोड में कार्यान्वयन असाधारण रूप से सक्षम है:

  • syn0 सरणी में शब्द का वेक्टर एम्बेडिंग शामिल है यदि यह फोकस में एक शब्द के रूप में आता है। यहाँ एक यादृच्छिक आरंभीकरण है

     https://github.com/tmikolov/word2vec/blob/20c129af10659f7c50e86e3be406df663beff438/word2vec.c#L369 for (a = 0; a < vocab_size; a++) for (b = 0; b < layer1_size; b++) { next_random = next_random * (unsigned long long)25214903917 + 11; syn0[a * layer1_size + b] = (((next_random & 0xFFFF) / (real)65536) - 0.5) / layer1_size; } 
  • एक अन्य syn1neg सरणी में शब्द का वेक्टर होता है जब यह एक संदर्भ शब्द के रूप में प्रकट होता है। यहाँ आरंभीकरण शून्य है
  • प्रशिक्षण के दौरान (स्किप-ग्राम, नकारात्मक चयन, हालांकि अन्य मामले उसी के बारे में हैं), हम पहले फोकस शब्द का चयन करते हैं। यह सकारात्मक और नकारात्मक उदाहरणों पर पूरे प्रशिक्षण के दौरान बनाए रखा जाता है। फोकस वेक्टर के ग्रेडिएंट्स बफर में जमा हो जाते हैं और दोनों सकारात्मक और नकारात्मक उदाहरणों पर प्रशिक्षण के बाद फोकस शब्द पर लागू होते हैं।

     if (negative > 0) for (d = 0; d < negative + 1; d++) { // if we are performing negative sampling, in the 1st iteration, // pick a word from the context and set the dot product target to 1 if (d == 0) { target = word; label = 1; } else { // for all other iterations, pick a word randomly and set the dot //product target to 0 next_random = next_random * (unsigned long long)25214903917 + 11; target = table[(next_random >> 16) % table_size]; if (target == 0) target = next_random % (vocab_size - 1) + 1; if (target == word) continue; label = 0; } l2 = target * layer1_size; f = 0; // find dot product of original vector with negative sample vector // store in f for (c = 0; c < layer1_size; c++) f += syn0[c + l1] * syn1neg[c + l2]; // set g = sigmoid(f) (roughly, the actual formula is slightly more complex) if (f > MAX_EXP) g = (label - 1) * alpha; else if (f < -MAX_EXP) g = (label - 0) * alpha; else g = (label - expTable[(int)((f + MAX_EXP) * (EXP_TABLE_SIZE / MAX_EXP / 2))]) * alpha; // 1. update the vector syn1neg, // 2. DO NOT UPDATE syn0 // 3. STORE THE syn0 gradient in a temporary buffer neu1e for (c = 0; c < layer1_size; c++) neu1e[c] += g * syn1neg[c + l2]; for (c = 0; c < layer1_size; c++) syn1neg[c + l2] += g * syn0[c + l1]; } // Finally, after all samples, update syn1 from neu1e https://github.com/tmikolov/word2vec/blob/20c129af10659f7c50e86e3be406df663beff438/word2vec.c#L541 // Learn weights input -> hidden for (c = 0; c < layer1_size; c++) syn0[c + l1] += neu1e[c]; 

यादृच्छिक और शून्य आरंभीकरण क्यों?


एक बार फिर, चूंकि यह मूल लेखों में और इंटरनेट पर कहीं भी नहीं समझाया गया है, मैं केवल अटकलें लगा सकता हूं।

परिकल्पना यह है कि जब नकारात्मक नमूने पूरे पाठ से आते हैं और आवृत्ति से भारित नहीं होते हैं, तो आप किसी भी शब्द का चयन कर सकते हैं, और सबसे अधिक बार ऐसा शब्द जिसका वेक्टर बिल्कुल प्रशिक्षित नहीं है । यदि इस वेक्टर का एक अर्थ है, तो यह यादृच्छिक रूप से वास्तव में महत्वपूर्ण शब्द को फोकस में स्थानांतरित कर देगा।

बिंदु सभी नकारात्मक उदाहरणों को शून्य पर सेट करना है, ताकि केवल अधिक या कम बार होने वाले वैक्टर दूसरे वेक्टर की प्रस्तुति को प्रभावित करें।

यह वास्तव में बहुत मुश्किल है, और मैंने कभी नहीं सोचा था कि कैसे महत्वपूर्ण आरंभीकरण रणनीतियों हैं।

मैं यह क्यों लिख रहा हूँ


मैंने अपने जीवन के दो महीने बिताए, मूल वैज्ञानिक प्रकाशन और इंटरनेट पर अनगिनत लेखों में वर्णित शब्द 2vec को पुन: पेश करने की कोशिश कर रहा था, लेकिन असफल रहा। मैं शब्द 2vec के समान परिणाम प्राप्त नहीं कर सका, हालांकि मैंने अपनी पूरी कोशिश की।

मैं कल्पना नहीं कर सकता था कि प्रकाशन के लेखकों ने शाब्दिक रूप से एक एल्गोरिथ्म तैयार किया है जो काम नहीं करता है, जबकि कार्यान्वयन कुछ पूरी तरह से अलग करता है।

अंत में, मैंने स्रोत का अध्ययन करने का फैसला किया। तीन दिनों के लिए मुझे विश्वास था कि मैंने कोड को गलत समझा है, क्योंकि सचमुच इंटरनेट पर सभी ने एक अलग कार्यान्वयन के बारे में बात की थी।

मुझे पता नहीं है कि इंटरनेट पर मूल प्रकाशन और लेख शब्द 2vec के वास्तविक तंत्र के बारे में कुछ क्यों नहीं कहते हैं, इसलिए मैंने इस जानकारी को स्वयं प्रकाशित करने का निर्णय लिया।

यह नकारात्मक संदर्भ के लिए अलग-अलग वैक्टर सेट करने के लिए ग्लोवी की कट्टरपंथी पसंद को भी बताता है - उन्होंने वही किया जो शब्द 2vec करता है, लेकिन लोगों को इसके बारे में बताया :)।

क्या यह वैज्ञानिक चाल है? मैं नहीं जानता, एक कठिन सवाल। लेकिन ईमानदार होने के लिए, मैं अविश्वसनीय रूप से गुस्से में हूं। शायद, मैं मशीन सीखने में एल्गोरिदम के स्पष्टीकरण को फिर से गंभीरता से नहीं ले पाऊंगा: अगली बार मैं तुरंत स्रोतों को देखूंगा।

Source: https://habr.com/ru/post/hi454926/


All Articles