CONTENTS

ഇന്ററാക്ടീവ് MNIST എക്സ്പ്ലോറർ

കാൻവാസിൽ അക്കങ്ങൾ വരച്ച്, ഒരു AI അത് എന്താണെന്ന് ഊഹിക്കുന്നത് കാണുക!

Try drawing a digit on the canvas!

1v1 Least Squares (n/a ms)

Fully Connected Network (n/a ms)
Convolutional Network (n/a ms)

ഈ ലേഖനത്തിൽ, MNIST ഡാറ്റാസെറ്റ് വ്യത്യസ്ത രീതികളിൽ കൈകാര്യം ചെയ്യുന്ന 3 അടിസ്ഥാന മോഡലുകൾ ഞങ്ങൾ പരിശോധിക്കുകയും അവയുടെ സവിശേഷതകൾ, ശക്തികൾ, പോരായ്മകൾ താരതമ്യം ചെയ്യുകയും ചെയ്യുന്നു. മുകളിലുള്ള ഓരോ മോഡലുകളുമായും നിങ്ങൾക്ക് ഇന്ററാക്ടീവായി കളിക്കാനും അവയുടെ ഔട്ട്പുട്ടുകൾ ബാർ ഗ്രാഫുകളിൽ കാണാനും കഴിയും.

ദൗത്യം

മെഷീൻ ലേണിംഗിനൊപ്പം പരിചയമില്ലാത്തവർക്ക്, ഒരു ചിത്രത്തെ ഒരു സംഖ്യയാക്കി മാറ്റുന്നത് ഭയങ്കരമായ ഒരു ജോലിയായി തോന്നിയേക്കാം. എന്നാൽ, നമ്മൾ ഈ പ്രശ്നത്തെ ഇനിപ്പറയുന്ന രീതിയിൽ ചിന്തിച്ചാൽ അത് എളുപ്പമാകും:

ഒരു ഗ്രേസ്കെയിൽ ചിത്രം എന്നത് പിക്സൽ തെളിച്ചങ്ങളുടെ ഒരു ഗ്രിഡ് മാത്രമാണ്, അവ യഥാർത്ഥ മൂല്യങ്ങളാണ്. അതായത്, ഓരോ ചിത്രവും എന്ന സെറ്റിലെ ഒരു മൂലകമാണ്, ഇവിടെ എന്നിവ യഥാക്രമം ചിത്രത്തിന്റെ വീതിയും ഉയരവുമാണ്. അതിനാൽ, എന്ന ഒരു ഫംഗ്ഷൻ കണ്ടെത്താൻ കഴിയുമെങ്കിൽ നമുക്ക് ഈ പ്രശ്നം പരിഹരിക്കാനാകും.

ഇത് ചെയ്യുന്നതിന്, നമ്മുടെ പരിശീലന ചിത്രങ്ങളായ ഉം ലേബലുകളായ ഉം ഉപയോഗിച്ച് ഒരു മോഡൽ നിർമ്മിക്കുന്നു.

ലീസ്റ്റ് സ്ക്വയേഴ്സ്

ഈ രീതിയിൽ 0..9 എന്ന വിഭാഗങ്ങളിൽ നിന്ന് തിരഞ്ഞെടുക്കുന്ന ഓരോ അദ്വിതീയ ജോടി -ക്കും എന്ന 45 രേഖീയ മാപ്പുകൾ നിർമ്മിക്കുന്നു. ഇത് ഒരു ചിത്രം ജോടിയോ ജോടിയോ ആണോ എന്ന് നിഗമനം ചെയ്യുന്നു. ചില രേഖീയ ബീജഗണിതം ഉപയോഗിച്ച് മീൻ സ്ക്വയേഡ് എറർ (MSE) കുറച്ചുകൂടി കുറയ്ക്കാം. ആദ്യം, -ലെ ചിത്രങ്ങൾ കൈകാര്യം ചെയ്യുന്നതിന് പകരം, നമുക്ക് അവയെ -ലേക്ക് “പരത്താം”.

-യുടെ ഭാരങ്ങൾ എന്ന നീളമുള്ള വെക്ടർ ആയി നിർവചിക്കുക. മോഡലിന്റെ ഔട്ട്പുട്ട് ലഭിക്കാൻ, നമ്മൾ കണക്കാക്കുന്നു

ഇവിടെ ഒരു അക്കമാണ്.

എല്ലാ സാമ്പിളുകളിലും MSE കുറയ്ക്കാൻ നമ്മൾ ആഗ്രഹിക്കുന്നു

ഇത് ചെയ്യാൻ, ഞങ്ങൾ ഒരു പുതിയ മാട്രിക്സ് സൃഷ്ടിക്കുന്നു, അതിൽ അല്ലെങ്കിൽ ക്ലാസിൽ പെട്ട ചിത്രങ്ങൾ മാത്രമേ അടങ്ങിയിട്ടുള്ളൂ, ബയസിനായി ന്റെ ഒരു കോളം ഉപയോഗിച്ച്. അതുപോലെ എന്ന മാട്രിക്സിൽ -ലെ ലേബലുകൾ മാത്രമേ അടങ്ങിയിട്ടുള്ളൂ, പക്ഷേ നെ ഉം നെ ഉം ഉപയോഗിച്ച് മാറ്റിസ്ഥാപിക്കുന്നു.

ഇപ്പോൾ, ഞങ്ങളുടെ പ്രശ്നം ചുരുക്കിയിരിക്കുന്നു

ഇതിനുള്ള പരിഹാരം ആണ്, ഇവിടെ എന്നത് മാട്രിക്സ് -ന്റെ സ്യൂഡോ-ഇൻവേഴ്സ് ആണ് (തെളിവ് വായനക്കാരുടെ വ്യായാമത്തിനായി വിട്ടുകൊടുത്തിരിക്കുന്നു 😁).

എല്ലാ ജോടികൾക്കും (ആകെ 45) ലഭിച്ചാൽ, ഞങ്ങളുടെ ആവശ്യമായ ഫംഗ്ഷൻ ഇങ്ങനെ പ്രതിനിധീകരിക്കാം

def f(x):
    score = [0] * 10
    for i, j, f_ij in pair_functions:
        out_ij = f_ij(x)
        if out_ij > 0:
            score[i] += 1
            score[j] -= 1
        else:
            score[j] += 1
            score[i] -= 1
    return argmax(score)

45 മോഡലുകളിൽ ഓരോന്നും അതിന്റെ അല്ലെങ്കിൽ -ക്ക് വോട്ട് ചെയ്യുന്നു. score അറേയാണ് മുകളിലെ ബാർ ഗ്രാഫിൽ നിങ്ങൾ കാണുന്നത്.

പൂർണ്ണമായും ബന്ധിപ്പിച്ച നെറ്റ്വർക്ക്

ഒരു പൂർണ്ണമായും ബന്ധിപ്പിച്ച നെറ്റ്വർക്ക് അല്ലെങ്കിൽ എഫ്‌സി‌എൻ, ലീസ്റ്റ് സ്ക്വയേഴ്സ് മോഡലിനേക്കാൾ വളരെ വലിയ ഒരു മോഡലാണ്. ഡാറ്റയുടെ പ്രിൻസിപ്പൽ സബ്‌സ്പേസിലേക്ക് നമ്മുടെ ലേബലുകൾ പ്രൊജക്റ്റ് ചെയ്യുന്നതിന് പകരം, ഇൻപുട്ട് സ്പേസിൽ നിന്ന് ഔട്ട്പുട്ട് സ്പേസിലേക്കുള്ള ഒരു മാപ്പിംഗ് നേരിട്ട് പഠിക്കാൻ കഴിയും.

ഒരു ലെയർ നെറ്റ്വർക്കിന്, ഇങ്ങനെ ഏകദേശം കണക്കാക്കാമെന്ന് കരുതുന്നു:

ഇവിടെ എന്നത് ഒരു നോൺലീനിയർ ഫംഗ്ഷനാണ്. ഗ്രേഡിയന്റ് ഡിസെന്റ് വഴി പ്രാദേശിക പരിസരത്ത് പിശക് (കാറ്റഗോറിക്കൽ ക്രോസ് എൻട്രോപ്പി) കുറയ്ക്കുന്ന തരത്തിൽ മാട്രിക്സ് പഠിക്കാൻ സാധിക്കും. ഡെമോയിൽ, ഞങ്ങൾ 2 ലെയർ നെറ്റ്വർക്ക് ഉപയോഗിക്കുന്നു, അത് ഇമേജ് ലേക്ക് മാപ്പ് ചെയ്യുന്നു, പിന്നീട് അതിന്റെ ഫലം ലേക്ക് മാപ്പ് ചെയ്യുന്നു. ഇത് ഇങ്ങനെ പ്രതിനിധീകരിക്കുന്നു:

ഇവിടെ നമ്മൾ , എന്നീ മാട്രിക്സുകൾ പഠിക്കേണ്ടതുണ്ട്. ഞങ്ങളുടെ കേസിൽ, ആണ്, കൂടാതെ

എന്നത് ലെ ഔട്ട്പുട്ടിനെ ഒരു പ്രോബബിലിറ്റി ഡിസ്ട്രിബ്യൂഷനാക്കി മാറ്റുന്നു, ഇത് മുകളിലെ ബാർ ഗ്രാഫുകളിൽ കാണിച്ചിരിക്കുന്നു.

കൺവല്യൂഷണൽ നെറ്റ്വർക്ക്

മുകളിലുള്ള രണ്ട് മാതൃകകളുടെ ഒരു പരിമിതി, അവ മനുഷ്യർ കാണുന്നതുപോലെ ദൃശ്യ സവിശേഷതകൾ കാണുന്നില്ല എന്നതാണ്. ഉദാഹരണത്തിന്, ഒരു കൈയെഴുത്ത് 1 എന്നത് ക്യാൻവാസിൽ എവിടെ വരച്ചാലും ഒരു ആണ്. എന്നാൽ, LS, FCN മാതൃകകൾക്ക് സ്ഥലത്തിന്റെയോ സാമീപ്യത്തിന്റെയോ ബോധമില്ലാത്തതിനാൽ, കൃത്യമായി ആ പിക്സലുകൾ ഉള്ള വിഭാഗത്തിലേക്ക് അവ ചൂണ്ടിക്കാണിക്കും.

ഇവിടെ, ഞങ്ങൾ കൺവല്യൂഷനുകൾ ഉപയോഗിക്കുന്നു. കൺവല്യൂഷനുകൾ ഒരു ചിത്രവും ഒരു കെർണലും എടുത്ത്, ആ കെർണൽ ചിത്രത്തിലൂടെ ഓടിച്ച്, ചിത്ര പിക്സലുകളുടെയും കെർണൽ മൂല്യങ്ങളുടെയും ഭാരം കൂട്ടിയ തുക അടങ്ങിയ ഒരു ഔട്ട്പുട്ട് ചിത്രം ഉണ്ടാക്കുന്നു.

സാധാരണ നെറ്റ്വർക്കുകൾ ചെയ്യാത്തതുപോലെ കൺവല്യൂഷനുകൾ സ്ഥലിക ഡാറ്റ എങ്ങനെ എൻകോഡ് ചെയ്യുന്നു എന്നത് ശ്രദ്ധിക്കുക. അടുത്തുള്ള പിക്സലുകൾ സാധാരണയായി പരസ്പരം ഉയർന്ന ബന്ധം ഉള്ളതിനാൽ, ഞങ്ങൾ കൺവല്യൂഷൻ ഔട്ട്പുട്ട് ഒരു മാക്സ് പൂൾ ഉപയോഗിച്ച് ഡൗൺസാമ്പിൾ ചെയ്ത് മിക്കവാറും എല്ലാ വിവരങ്ങളും സംരക്ഷിക്കാം. ചിത്രം ഒരു കൂട്ടം (പരിശീലിപ്പിച്ച) കെർണലുകളിലൂടെ കടന്നുപോയ ശേഷം, പഠിച്ച സ്ഥലിക സവിശേഷതയുടെ നിലനിൽപ്പ് പ്രതിനിധീകരിക്കുന്ന മാട്രിക്സുകളുടെ ഒരു കൂട്ടം ലഭിക്കുന്നു. ഒടുവിൽ, ഞങ്ങൾക്ക് ഇവ പരന്നതാക്കി ഒരു FCN-ലേക്ക് കടത്തിവിടാം, അതിന് ഇപ്പോൾ സ്ഥലിക ഡാറ്റ വിഭാഗങ്ങളിലേക്ക് മാപ്പ് ചെയ്യാൻ കഴിയും.

ഈ FCN-ന്റെ (സോഫ്റ്റ്മാക്സ് ആക്റ്റിവേഷനോടെയുള്ള) ഔട്ട്പുട്ട് മുകളിൽ കാണിച്ചിരിക്കുന്നു.

മോഡൽ താരതമ്യം

കുറിപ്പ്: അവസാനത്തെ 3 നിരകൾ ഗുണാത്മകവും പരസ്പരം ആപേക്ഷികവുമാണ്.

മോഡൽ പാരാമീറ്ററുകളുടെ എണ്ണം പരിശീലന സമയം അനുമാന സമയം കൃത്യത
ലീസ്റ്റ് സ്ക്വയേഴ്സ് കുറഞ്ഞ വേഗം കുറഞ്ഞ
എഫ്.സി.എൻ. കൂടിയ വേഗം നല്ലത്
കൺവല്യൂഷണൽ നെറ്റ്വർക്ക് വളരെ കൂടിയ മന്ദം മികച്ചത്

നിരീക്ഷണങ്ങൾ:

  • ലീസ്റ്റ് സ്ക്വയേഴ്സ് മോഡൽ വളരെ വേഗത്തിലാണ്, പക്ഷേ പൊതുവൽക്കരിക്കാനുള്ള കഴിവ് ദുർബലമാണ്
  • സി.എൻ.എൻ.-യുടെ പാരാമീറ്ററുകൾ സംഭരിക്കാൻ വളരെ കാര്യക്ഷമമാണ്
  • സി.എൻ.എൻ.-യുടെ അനുമാന സമയവുമായി താരതമ്യപ്പെടുത്തുമ്പോൾ, എൽ.എസ്., എഫ്.സി.എൻ. മോഡലുകൾ വളരെ വേഗത്തിലാണ്

വ്യായാമങ്ങൾ

മോഡലുകൾ ഈ ഇൻപുട്ടുകളോട് എങ്ങനെ പ്രതികരിക്കുന്നുവെന്ന് നോക്കുക:

  • ശൂന്യമായ കാൻവാസ്
  • മധ്യത്തിൽ ഒരു 1
  • വലത് വശത്ത് തീരത്ത് ഒരു 1
  • ഇടത് വശത്ത് തീരത്ത് ഒരു 1
  • മധ്യത്തിൽ ഒരു വര/ബിന്ദുവുള്ള ഒരു 0
  • മുകളിലെ ഭാഗം അല്പം വേർപെട്ട ഒരു 9
  • അല്പം തിരിക്കപ്പെട്ട അക്കങ്ങൾ
  • വളരെ നേർത്ത അക്കങ്ങൾ
  • വളരെ കട്ടിയുള്ള അക്കങ്ങൾ

വ്യത്യസ്ത വിഭാഗങ്ങളിലേക്ക് മാപ്പ് ചെയ്യുന്ന, 1 പിക്സൽ വ്യത്യാസമുള്ള 2 ഇൻപുട്ടുകൾ നിങ്ങൾക്ക് കണ്ടെത്താനാകുമോ?

നടപ്പിലാക്കലിന്റെ വിശദാംശങ്ങൾ

മൂന്ന് മോഡലുകളും നിങ്ങളുടെ ബ്രൗസറിലാണ് പ്ലെയിൻ ജാവാസ്ക്രിപ്റ്റ് ഉപയോഗിച്ച് പ്രവർത്തിക്കുന്നത്; യാതൊരു ഫ്രെയിംവർക്കുകളോ പാക്കേജുകളോ ഉപയോഗിച്ചിട്ടില്ല.

കാൻവാസ്

വലിപ്പമുള്ള ഈ കാൻവാസിന് പിന്നിൽ ഒരു സംഖ്യാ അറേയുണ്ട്, അതിൽ പ്രദർശിപ്പിക്കുന്ന ആൽഫ മൂല്യങ്ങൾ അടങ്ങിയിരിക്കുന്നു. ഏതെങ്കിലും ഒരു പിക്സൽ അപ്ഡേറ്റ് ചെയ്യുമ്പോഴെല്ലാം മുഴുവൻ ചിത്രവും വീണ്ടും വരയ്ക്കുന്നു. ഇതിന് പുറമെ ശ്രദ്ധേയമായ വിവരം, ഞാൻ ഉപയോഗിച്ച തെളിച്ചം കുറയുന്നതിനുള്ള ഫംഗ്ഷൻ ആണ്:

const plateau = 0.3;
// dist എന്നത് കേന്ദ്രത്തിൽ നിന്നുള്ള ദൂരത്തിന്റെ വർഗ്ഗമാണ് (distance^2)
const alpha = Math.min(1 - dist / r2 + plateau, 1);
pixels[yc * 28 + xc] = Math.max(pixels[yc * 28 + xc], alpha);

തുടക്കത്തിൽ ഞാൻ 1-dist/r2 എന്ന തെളിച്ചക്കുറവ് ഉപയോഗിച്ചു പക്ഷേ അത് കേന്ദ്രഭാഗം വളരെയധികം മങ്ങിക്കുന്നതായി കണ്ടു. അതിനാൽ ഞാൻ plateau എന്ന വേരിയബിൾ ചേർത്തു, അത് ഫംഗ്ഷനെ മുകളിലേക്ക് നീക്കുന്നു, പക്ഷേ Math.min ഉപയോഗിച്ച് ക്ലാമ്പ് ചെയ്തിട്ടുണ്ട്, അതുവഴി ആൽഫ മൂല്യം 1 കവിയുന്നില്ല. ഇത് ബ്രഷിന് കൂടുതൽ സ്വാഭാവികമായ രൂപം നൽകുന്നു.

ലീസ്റ്റ് സ്ക്വയേഴ്സ്

ഈ ഭാരങ്ങൾ ഞാൻ പ്രൊഫസർ പിയ പാലിന്റെ ഇസിഇ 174 പ്രോജക്റ്റിൽ നിന്നാണ് ലഭിച്ചത്. ഇൻഫറൻസ് എന്നത് 45 ഡോട്ട് ഉൽപ്പന്നങ്ങളും സ്കോറിംഗും മാത്രമാണ്

function evalLSModel(digit, weights) {
    const scores = new Array(10).fill(0);
    for (const pairConfig of weights) {
        const [i, j, w] = pairConfig;
        // വെക്റ്റർ ഡോട്ട് ഉൽപ്പന്നം
        const result = vdot(digit, w);
        if (result > 0) {
            scores[i] += 1;
            scores[j] -= 1;
        } else {
            scores[j] += 1;
            scores[i] -= 1;
        }
    }
    return scores;
}

പൂർണ്ണമായും ബന്ധിപ്പിച്ച നെറ്റ്വർക്ക്

എഫ്‌സി‌എൻ അനുമാനത്തിലെ പ്രധാന ജോലി മാട്രിക്സ് ഡോട്ട് പ്രോഡക്റ്റ് ആണ്, അത് ഞാൻ സ്റ്റാൻഡേർഡ് രീതിയിൽ നടപ്പിലാക്കി.

function matrixDot(matrix1, matrix2, rows1, cols1, rows2, cols2) {
    // മെട്രിക്സുകൾ ഗുണിക്കാൻ കഴിയുമോ എന്ന് പരിശോധിക്കുക
    if (cols1 !== rows2) {
        console.error("ഡോട്ട് പ്രോഡക്റ്റിന് അസാധുവായ മാട്രിക്സ് അളവുകൾ");
        return null;
    }

    // ഫലം മാട്രിക്സ് പൂജ്യങ്ങളുമായി സമാരംഭിക്കുക
    const result = new Array(rows1 * cols2).fill(0);

    // ഡോട്ട് പ്രോഡക്റ്റ് നടപ്പിലാക്കുക
    for (let i = 0; i < rows1; i++) {
        for (let j = 0; j < cols2; j++) {
            for (let k = 0; k < cols1; k++) {
                result[i * cols2 + j] +=
                    matrix1[i * cols1 + k] * matrix2[k * cols2 + j];
            }
        }
    }

    return result;
}

മെച്ചപ്പെട്ട കാഷെ ലൊക്കാലിറ്റിയും കുറഞ്ഞ ഹീപ്പ് അലോക്കേഷനുകളും വേണ്ടി ഞാൻ മെട്രിക്സുകൾ ഒരൊറ്റ 1D Array-ൽ സംഭരിച്ചു. മുകളിലെ ഫോർമുല പ്രകാരം, അനുമാനത്തിൽ 2 മാട്രിക്സ് ഡോട്ട് പ്രോഡക്റ്റുകളും 2 ആക്റ്റിവേഷൻ ഫംഗ്ഷൻ പ്രയോഗങ്ങളും അടങ്ങിയിരിക്കുന്നു. push(1) കോളുകൾ ബയസ് കണക്കാക്കാനാണ്.

function evalNN(digit, weights) {
    const digitCopy = [...digit];
    digitCopy.push(1);
    // ലെയർ 1 പാരാമീറ്ററുകൾ
    const [w1, [rows1, cols1]] = weights[0];
    const out1 = matrixDot(digitCopy, w1, 1, digitCopy.length, rows1, cols1).map(relu);
    const [w2, [rows2, cols2]] = weights[1];
    out1.push(1);
    const out2 = matrixDot(out1, w2, 1, out1.length, rows2, cols2);
    return softmax(out2);
}

കൺവല്യൂഷണൽ നെറ്റ്‌വർക്ക്

ഇവിടെയുള്ള കൺവ്‌നെറ്റ് വളരെ ചെറുതാണ്. പൈടോർച്ചിൽ ഇത്

nn.Sequential(
    nn.Conv2d(1, 32, kernel_size=3),
    nn.ReLU(),
    nn.MaxPool2d(kernel_size=2, stride=2),
    nn.Conv2d(32, 64, kernel_size=3),
    nn.ReLU(),
    nn.MaxPool2d(kernel_size=2, stride=2),
    nn.Flatten(),
    nn.Dropout(0.5),
    nn.Linear(1600, 10),
    nn.Softmax(dim=1)
)

ഇൻഫറൻസിനായി, ഫോർവേഡ് പാസുകൾ ജാവാസ്ക്രിപ്റ്റിലേക്ക് പോർട്ട് ചെയ്യണം. Conv2d (ഇൻ/ഔട്ട് ചാനലുകൾക്കൊപ്പം) നൽകുന്നത്

function conv2d(
    nInChan,
    nOutChan,
    inputData,
    inputHeight,
    inputWidth,
    kernel,
    bias,
) {
    if (inputData.length !== inputHeight * inputWidth * nInChan) {
        console.error("അസാധുവായ ഇൻപുട്ട് വലുപ്പം");
        return;
    }
    if (kernel.length !== 3 * 3 * nInChan * nOutChan) {
        console.error("അസാധുവായ കെർണൽ വലുപ്പം");
        return;
    }

    const kernelHeight = 3;
    const kernelWidth = 3;

    // ഔട്ട്പുട്ട് അളവുകൾ കണക്കാക്കുക
    const outputHeight = inputHeight - kernelHeight + 1;
    const outputWidth = inputWidth - kernelWidth + 1;

    const output = new Array(nOutChan * outputHeight * outputWidth).fill(0);

    for (let i = 0; i < outputHeight; i++) {
        for (let j = 0; j < outputWidth; j++) {
            for (let outChan = 0; outChan < nOutChan; outChan++) {
                let sum = 0;
                // എല്ലാ ഇൻപുട്ട് ചാനലുകളിലും ഒറ്റ സ്ഥാനത്ത് ഫിൽട്ടർ പ്രയോഗിക്കുക
                for (let inChan = 0; inChan < nInChan; inChan++) {
                    for (let row = 0; row < 3; row++) {
                        for (let col = 0; col < 3; col++) {
                            const inI =
                                inChan * (inputHeight * inputWidth) +
                                (i + row) * inputWidth +
                                (j + col);

                            const kI =
                                outChan * (nInChan * 3 * 3) +
                                inChan * (3 * 3) +
                                row * 3 +
                                col;
                            sum += inputData[inI] * kernel[kI];
                        }
                    }
                }
                sum += bias[outChan];
                const outI =
                    outChan * (outputHeight * outputWidth) +
                    i * outputWidth +
                    j;
                output[outI] = sum;
            }
        }
    }
    return output;
}

ഇത് വൃത്തികെട്ടതാണെന്ന് എനിക്കറിയാം. റഫറൻസിനായി ഇവിടെ ഇടുകയാണ്. മാക്സ്പൂളിനായി ശ്രദ്ധിക്കുക:

function maxPool2d(nInChannels, inputData, inputHeight, inputWidth) {
    if (inputData.length !== inputHeight * inputWidth * nInChannels) {
        console.error("maxpool2d: അസാധുവായ ഇൻപുട്ട് ഉയരം/വീതി");
        return;
    }
    const poolSize = 2;
    const stride = 2;
    const outputHeight = Math.floor((inputHeight - poolSize) / stride) + 1;
    const outputWidth = Math.floor((inputWidth - poolSize) / stride) + 1;
    const output = new Array(outputHeight * outputWidth * nInChannels).fill(0);

    for (let chan = 0; chan < nInChannels; chan++) {
        for (let i = 0; i < outputHeight; i++) {
            for (let j = 0; j < outputWidth; j++) {
                let m = 0;
                for (let row = 0; row < poolSize; row++) {
                    for (let col = 0; col < poolSize; col++) {
                        const ind =
                            chan * (inputHeight * inputWidth) +
                            (i * stride + row) * inputWidth +
                            (j * stride + col);
                        m = Math.max(m, inputData[ind]);
                    }
                }
                const outI =
                    chan * (outputHeight * outputWidth) + i * outputWidth + j;
                output[outI] = m;
            }
        }
    }
    return output;
}

അതെ, ഇൻഡെക്സ് കണക്കാക്കുന്ന ആ വികൃത കോഡുമായി ഞാൻ കൈകാര്യം ചെയ്യുന്നതിനുള്ള ഒരേയൊരു കാരണം, അതാണ് ബ്ലേസിംഗ് ഫാസ്റ്റ് 🔥ജാവാസ്ക്രിപ്റ്റ്🔥 വെബ് ആപ്പ് പ്രകടനം. ഒടുവിൽ, എല്ലാം ഒന്നിപ്പിക്കുന്ന ഫംഗ്ഷൻ ഇതാ:

function evalConv(digit, weights) {
    const [
        [f1, fshape1], // കൺവ് ഫിൽട്ടർ ഭാരങ്ങൾ
        [b1, bshape1], // കൺവ് ബയസ്
        [f2, fshape2],
        [b2, fbshape2],
        [w, wshape],   // എഫ്‌സി‌എൻ ഭാരങ്ങൾ
        [b, bshape],   // എഫ്‌സി‌എൻ ബയസ്
    ] = weights;

    const x1 = conv2d(1, 32, digit, 28, 28, f1, b1).map(relu);
    const x2 = maxPool2d(32, x1, 26, 26);
    const x3 = conv2d(32, 64, x2, 13, 13, f2, b2).map(relu);
    const x4 = maxPool2d(64, x3, 11, 11);
    const x5 = matrixDot(w, x4, 10, 1600, 1600, 1);
    const x6 = vsum(x5, b);
    const out = softmax(x6);
    return out;
}

ഉപസംഹാരം

ഈ ആപ്പ് ഉപയോഗിച്ച് പരീക്ഷണം നടത്തുന്നത് നിങ്ങൾക്ക് ആസ്വാദ്യമായിരിക്കുമെന്ന് ഞാൻ പ്രതീക്ഷിക്കുന്നു. എന്തെങ്കിലും ചോദ്യങ്ങളോ പ്രതികരണങ്ങളോ ഉണ്ടെങ്കിൽ, താഴെ കമന്റ് ചെയ്യാൻ മടിക്കേണ്ട.

✦ ഈ ലേഖനത്തിന്റെ ആശയരൂപീകരണം, ഗവേഷണം, എഴുത്ത്, അല്ലെങ്കിൽ എഡിറ്റിംഗ് എന്നിവയിൽ LLM-കൾ ഉപയോഗിച്ചിട്ടില്ല.