CONTENTS

മെഷീൻ ലേണിംഗ് ഫണ്ടമെന്റൽസ് ഭാഗം 2: മൾട്ടിവേറിയറ്റ് ലീനിയർ റിഗ്രഷൻDraft

C യിലെ മെഷീൻ ലേണിംഗ് ഫണ്ടമെന്റൽസ് പരമ്പരയിലെ ഭാഗം 2 / N ലേക്ക് സ്വാഗതം! നിങ്ങൾ ഇതുവരെ ചെയ്തിട്ടില്ലെങ്കിൽ, ഭാഗം 1 കാണുക—ഈ ലേഖനത്തിൽ അവിടെയുള്ള ആശയങ്ങൾ നിങ്ങൾക്ക് പരിചയമുണ്ടെന്ന് ഞാൻ അനുമാനിക്കുന്നു.

ഒരു വേരിയബിളിനപ്പുറം

നമ്മുടെ ആദ്യത്തെ മെഷീൻ ലേണിംഗ് മോഡലായ യൂണിവേറിയറ്റ് ലീനിയർ റിഗ്രഷൻ മോഡൽ, സാങ്കേതികമായി മെഷീൻ ലേണിംഗ് ആയിരുന്നുവെങ്കിലും, സത്യത്തിൽ നോക്കിയാൽ അത്ര ശ്രദ്ധേയമല്ല. അപ്പോൾ എന്തിനാണ് ആ ബുദ്ധിമുട്ട് എല്ലാം കഴിച്ചത്? കൂടുതൽ സങ്കീർണ്ണമായ മോഡലുകൾക്ക് അടിത്തറയിടാനായിട്ടാണ്, ഉദാഹരണത്തിന് മൾട്ടിവേരിയബിൾ ലീനിയർ മോഡൽ, അതാണ് ഇന്ന് നമ്മൾ പരിശോധിക്കാൻ പോകുന്നത്.

ബഹുമാറ്റിക് ലീനിയർ മോഡൽ

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

വ്യത്യസ്ത വിഭാഗങ്ങളിലുള്ള വേരിയബിളുകൾ കൂട്ടിക്കലർത്താൻ കഴിയുന്ന രീതിയിൽ മോഡൽ രൂപകൽപ്പന ചെയ്യേണ്ടതുണ്ടെന്ന് നമുക്ക് ഇവിടെ മനസ്സിലാകുന്നു. ഓരോ വേരിയബിളിനും നമ്മുടെ പ്രവചനത്തിൽ വ്യത്യസ്തമായ സ്വാധീനം ഉണ്ടാകാം.

പ്രശ്നത്തിന്റെ വിവരണം

നമുക്ക് ആദ്യം നമ്മുടെ പ്രശ്നം നിർവ്വചിക്കാം:

ഇനിപ്പറയുന്ന ഇൻപുട്ട് വേരിയബിളുകളുടെ ഡാറ്റയും അവയുടെ അനുയോജ്യമായ വീടിന്റെ വില (1000കളിൽ) ഇങ്ങനെയാണ് നൽകിയിരിക്കുന്നത്:

: ചതുരശ്ര അടി ( ൽ)

: പ്ലോട്ടിന്റെ വീതി (അടിയിൽ)

: പ്ലോട്ടിന്റെ ആഴം (അടിയിൽ)

: കരയിൽ നിന്നുള്ള ദൂരം (മൈലിൽ)

ഒരു പുതിയ ഡാറ്റ സെറ്റ് നൽകുമ്പോൾ വീടിന്റെ വില പ്രവചിക്കുക.

ഇവയിൽ ഓരോ ഘടകവും വിലയെ ബാധിക്കുമെന്ന് നമുക്കറിയാം. ചിലത് (ചതുരശ്ര അടി, കരയിൽ നിന്നുള്ള ദൂരം തുടങ്ങിയവ) വളരെ വലിയ ബാധ്യതയുണ്ടാക്കും, മറ്റുചിലതിന് ചെറിയ ബാധ്യതയേ ഉണ്ടാകൂ.

അപ്പോൾ, ഈ ബന്ധങ്ങളെ എങ്ങനെയാണ് നമുക്ക് ഒരു മോഡലായി പ്രകടിപ്പിക്കാൻ കഴിയുക? ചില പ്ലോട്ടുകൾ നോക്കി, ഏകദേശം ഊഹിച്ചുനോക്കി ഒന്ന് നിർമ്മിക്കാൻ ശ്രമിക്കാം.

പ്ലോട്ട്: ചതുരശ്ര അടി vs വില

Loading...

ചതുരശ്ര അടിയും വിലയും തമ്മിൽ ഒരു പോസിറ്റീവ് ബന്ധമുണ്ടെന്ന് നമുക്ക് കാണാം. ട്രെൻഡ് ലൈൻ എന്ന സമവാക്യത്താൽ മാതൃകയാക്കിയിരിക്കുന്നു.

പ്ലോട്ട്: പ്ലോട്ടിന്റെ വീതിയും വിലയും

Loading...

ഇവിടെയും അല്പം പോസിറ്റീവ് ബന്ധം കാണാം. ട്രെൻഡ് ലൈൻ ആണ്.

പ്ലോട്ട്: പ്ലോട്ടിന്റെ ആഴവും വിലയും

Loading...

ലോട്ട് വീതിയേക്കാൾ ഇതിന് വളരെ ശക്തമായ പോസിറ്റീവ് ബന്ധമുണ്ട്, ഇത് ഒരു ആഴമേറിയ പ്ലോട്ട് ആകൃതി വീടുവാങ്ങുന്നവർ ഉയർന്ന നിലയിൽ വിലമതിക്കുന്നുവെന്ന് സൂചിപ്പിക്കുന്നു. ട്രെൻഡ് ലൈൻ ആണ്.

പ്ലോട്ട്: കരയിൽ നിന്നുള്ള ദൂരം vs വില

Loading...

ഞങ്ങൾ പ്രതീക്ഷിച്ചതുപോലെ, കരയിൽ നിന്ന് അകന്നുപോകുംതോറും വില കുറയുന്നു. കരയിൽ നിന്ന് കൂടുതൽ അകലേക്ക് പോകുമ്പോൾ ഈ പ്രഭാവം കുറയുന്നതായും കാണാം. ട്രെൻഡ് ലൈൻ ആണ്.

മോഡലിനുള്ള ആദ്യത്തെ ഊഹം

ട്രെൻഡ്‌ലൈനുകൾ ഓരോ ഫീച്ചറും വീടിന്റെ വിലയും തമ്മിലുള്ള ഏകദേശ ബന്ധം നമ്മോട് പറയുന്നു. ഈ ബന്ധങ്ങൾ ഒന്നിച്ചു ചേർത്ത് ഡാറ്റയുടെ ഒരു ഏകദേശ മോഡൽ നിർമ്മിക്കാം. എല്ലാ ട്രെൻഡ്‌ലൈനുകളും കൂട്ടിച്ചേർത്ത് നോക്കാം, അത് ഒരു യുക്തിസഹമായ മോഡൽ ആകുമോ എന്ന് പരിശോധിക്കാം.

അന്തർജ്ഞാനത്തിൽ, ഇതിനർത്ഥം എന്താണ്? നമുക്ക് $6.5 ദശലക്ഷം എന്ന ഒരു അടിസ്ഥാന മൂല്യം (ബയസ്) ഉണ്ട്, അത് 0 ചതുരശ്ര അടി, പ്ലോട്ട് ഇല്ലാതെ, സമുദ്രതീരത്തുള്ള ഒരു വീടിന്റെ സൈദ്ധാന്തിക മൂല്യത്തെ പ്രതിനിധീകരിക്കുന്നു. ക്ഷമിക്കണം… അത് ശരിയായി തോന്നുന്നില്ല. ഓരോ പ്ലോട്ടും മറ്റുള്ളവയുമായി ഓവർലാപ്പ് ചെയ്യുന്നതിനാൽ, y-ഇന്റർസെപ്റ്റുകളുടെ ശരാശരി എടുക്കുന്നതാണ് നല്ലത്. പുതിയ മോഡൽ:

ശരി… ഇപ്പോൾ അത് $1.6 ദശലക്ഷം ആണ്. എന്നാൽ ഇത് തീരത്ത് തന്നെയാണെങ്കിൽ ഇത് യുക്തിസഹമായിരിക്കാം. ബാക്കിയുള്ള മോഡൽ അർത്ഥവത്തായി തോന്നുന്നു. അത് സൂചിപ്പിക്കുന്നത്

ഇതിലെ ഓരോ 1 യൂണിറ്റ് വർദ്ധനവിനും വീടിന്റെ വിലയിലെ മാറ്റം
ചതുരശ്ര അടി $340
പ്ലോട്ടിന്റെ വീതി $3300
പ്ലോട്ടിന്റെ ആഴം $5840
തീരത്തുനിന്നുള്ള അകലം -$8070

യുക്തിസഹമാണെങ്കിലും, ഇവ ചില തെറ്റായ കണക്കുകൂട്ടലുകളെ അടിസ്ഥാനമാക്കിയുള്ള വെറും അനുമാനങ്ങൾ മാത്രമാണ്. നമ്മുടെ പ്രശ്നം ശരിക്കും പരിഹരിക്കാൻ, ഈ 4 ഫീച്ചറുകൾ നൽകിയിരിക്കുമ്പോൾ വീടിന്റെ വില പ്രവചിക്കുന്ന മികച്ച MLM കണ്ടെത്തേണ്ടതുണ്ട്. ഗണിതശാസ്ത്ര പദങ്ങളിൽ പറഞ്ഞാൽ, നമുക്ക് എന്നീ ഭാരങ്ങളും എന്ന ബയസും കണ്ടെത്തേണ്ടതുണ്ട്, അതായത്

കുറഞ്ഞ പിശകോടെ വീടിന്റെ വില പ്രവചിക്കുന്നവ.

കോഡിൽ, MLM

ഭാഗം 1-ൽ എന്നപോലെ, നമുക്ക് നമ്മുടെ മോഡൽ നിർവ്വചിക്കാം. 1-ൽ കൂടുതൽ ഭാരങ്ങൾ ഉള്ളതിനാൽ, നമുക്ക് w-കളുടെ ഒരു അറേ ഉപയോഗിക്കേണ്ടതുണ്ട്.

struct mlinear_model {
	int num_weights;
	double *w;
	double b;
};

ഇപ്പോൾ, നിങ്ങൾക്ക് കുറച്ച് ഘട്ടങ്ങൾ മുന്നോട്ട് ചിന്തിച്ച് മനസ്സിലാക്കിയിരിക്കാം, w-യെ ഒരു വെക്റ്ററായും x-ഉം ഒരു വെക്റ്ററായും പ്രതിനിധീകരിച്ചാൽ മോഡലിന്റെ ഔട്ട്പുട്ട് , എന്നിവയുടെ ഡോട്ട് പ്രോഡക്ടായി പ്രതിനിധീകരിക്കാമെന്ന്, കൂട്ടിയിട്ട്.

$$ \begin{align*} \hat{y} &= w_1 x_1 + \dots + w_nx_n + b \\ &= \vec{x} \cdot \vec{w} + b \\ \end{align*}


അഹ്, വളരെ ശുദ്ധമായി, അല്ലേ? ശരി, അത് കൂടുതൽ നേരം നിൽക്കില്ല...

ഞാൻ ഇവിടെ ഒരു കുതിച്ചുചാട്ടം കൂടി എടുക്കുന്നു, നമ്മുടെ $n$ നീളമുള്ള വെക്റ്ററിനെ ഒരു $(n \times 1)$ മാട്രിക്സായി പ്രതിനിധീകരിക്കാൻ.

$$
w = \begin{bmatrix} w_1 \\\\ w_2 \\\\ w_3 \\\\ \vdots \\\\ w_n \end{bmatrix}
$$

ഇത് എങ്ങനെ സഹായകരമാണെന്ന് നിങ്ങൾ ഉടൻ കാണും. നമുക്ക് നമ്മുടെ `matrix` struct നിർവ്വചിക്കാം.

```c
// matrix.h

// പിന്നീട് `double` ഏത് ഫ്ലോട്ട് ടൈപ്പിനും മാറ്റാനാകും
typedef double mfloat;

typedef struct {
	// row major
	mfloat *buf;
	int rows;
	int cols;
} matrix;

ഇപ്പോൾ, മോഡൽ ഇങ്ങനെയാണ്

// main.c
struct mlinear_model {
	matrix w;
	mfloat b;
};

ശ്രദ്ധിക്കുക, num_weights ഇപ്പോൾ മാട്രിക്സിൽ സംഭരിച്ചിരിക്കുന്നു.

മാട്രിക്സുകളിലേക്ക് മാറിയതിനാൽ, വെക്റ്റർ ഡോട്ട് പ്രോഡക്ടിന് പകരം മാട്രിക്സ് ഡോട്ട് പ്രോഡക്ട് ഉപയോഗിക്കേണ്ടതുണ്ട്. നിങ്ങളുടെ ലീനിയർ ആൾജിബ്ര പരിശീലിപ്പിക്കേണ്ടി വന്നാൽ, കോഡ് വിശദീകരിക്കുന്ന കുറിപ്പുകൾ ഞാൻ ചേർത്തിട്ടുണ്ട്.

// matrix.h

// മാട്രിക്സിലെ ഒരു എലമെന്റ് എടുക്കുക
mfloat
matrix_get(matrix m, int row, int col)
{
    return m.buf[row * m.cols + col];
}
// മാട്രിക്സിലെ ഒരു എലമെന്റ് സെറ്റ് ചെയ്യുക
void
matrix_set(matrix m, int row, int col, mfloat val)
{
    m.buf[row * m.cols + col] = val;
}

// out = m1 dot m2
void
matrix_dot(matrix out, const matrix m1, const matrix m2)
{
	// ആദ്യ മാട്രിക്സിന്റെ ith row-ൽ
    for (int row = 0; row < m1.rows; row++) {
		// രണ്ടാമത്തെ മാട്രിക്സിന്റെ jth column-ൽ
        for (int col = 0; col < m2.cols; col++) {
			// വെക്റ്റർ ഡോട്ട് പ്രോഡക്ട് ചെയ്ത് out[i][j]-ൽ ഫലം ഇടുക
            mfloat sum = 0.0;
			// m1.cols == m2.rows, അതിനാൽ k എല്ലാത്തിലും ചുറ്റും
            for (int k = 0; k < m1.cols; k++) {
                mfloat x1 = matrix_get(m1, row, k);
                mfloat x2 = matrix_get(m2, k, col);
                sum += x1 * x2;
            }
            matrix_set(out, row, col, sum);
        }
    }
}

ആദ്യം, , എന്നീ മാട്രിക്സുകൾ തമ്മിലുള്ള ഡോട്ട് പ്രോഡക്ടിനെക്കുറിച്ചുള്ള ഈ സവിശേഷതകൾ ശ്രദ്ധിക്കാം.

  1. ഇത് കണക്കാക്കാൻ കഴിയുക എങ്കിൽ മാത്രം X.cols == W.rows ആണെങ്കിൽ
  2. ഫലമായുണ്ടാകുന്ന മാട്രിക്സിന് (X.rows, W.cols) അളവുകൾ ഉണ്ട്

ഈ സാഹചര്യത്തിൽ,

അതിനാൽ, വെക്റ്റർ ഡോട്ട് പ്രോഡക്ടിന്റെ പ്രവർത്തനം പുനരാവിഷ്കരിക്കാൻ ഒരു റോ വെക്റ്റർ ആയിരിക്കണം, ഒരു കോളം വെക്റ്റർ ആയിരിക്കണം.

മാട്രിക്സ് ഡോട്ട് പ്രോഡക്ടിനെ നിങ്ങൾക്ക് ഇങ്ങനെ ചിന്തിക്കാം: രണ്ടാമത്തെ മാട്രിക്സിലെ ഓരോ കോളവും എടുത്ത്, അത് വശങ്ങൾ മാറ്റി, ആദ്യ മാട്രിക്സിലെ റോവുമായി ഡോട്ട് ചെയ്യുക.

കോഡ് നോക്കുക, മുകളിലെ ഗുണനം അവകാശപ്പെട്ട ഫലം തിരികെ നൽകുമെന്ന് സ്ഥിരീകരിക്കുക.

ഇപ്പോൾ, പ്രവചനം കോഡ് ചെയ്യാൻ നമുക്ക് ഉപകരണങ്ങൾ ഉണ്ട്.

// mlr.c

// x ഒരു റോ വെക്റ്റർ ആണ്, അല്ലെങ്കിൽ (1 x n) മാട്രിക്സ്
mfloat predict(struct mlinear_model model, const matrix x) {
	// (1 x n) . (n x 1) => (1 x 1) മാട്രിക്സ്, അതായത് ഒരു സംഖ്യ
	mfloat result[1][1] = {0.0};
	matrix tmp = {.buf = result, .rows = 1, .cols = 1};
	// tmp-യെ ഫലത്തിലേക്ക് സെറ്റ് ചെയ്യുക
	matrix_dot(tmp, x, model.w);
	return tmp.buf[0] + model.b;
}

മോഡൽ ഒപ്റ്റിമൈസ് ചെയ്യൽ

നമുക്ക് 2 അറേകൾ നൽകിയിരിക്കുന്നു: ഇൻപുട്ട് ഡാറ്റ, ഇത് ഒരു മാട്രിക്സ് ആയി ഫോർമാറ്റ് ചെയ്തിരിക്കുന്നു.

ഓരോ വരിയും ഒരു സാമ്പിൾ പ്രതിനിധീകരിക്കുന്നു, ഓരോ കോളത്തിലെയും മൂല്യങ്ങൾ മുകളിൽ വിവരിച്ച സവിശേഷതകളാണ്.

അതിനനുസരിച്ചുള്ള വീടിന്റെ വിലകൾ , $1000കളിൽ

ഡാറ്റയുടെ 100 സാമ്പിളുകൾ നമുക്ക് നൽകിയിരിക്കുന്നു, അതിനാൽ . ഞാൻ ഇവ data.h-ൽ സംഭരിക്കാൻ പോകുന്നു

// data.h

#define NUM_FEATURES 4
#define NUM_SAMPLES 100

static mfloat X_data[NUM_SAMPLES][NUM_FEATURES] = { /* data omitted*/ };

static mfloat Y_data[NUM_SAMPLES] = { /* data omitted */ };

static matrix X = {.buf = (mfloat *)X_data,
                   .rows = NUM_SAMPLES,
                   .cols = NUM_FEATURES};

static matrix Y = {
    .buf = (mfloat *)Y_data, .rows = NUM_SAMPLES, .cols = 1};

ഇപ്പോൾ, ഒപ്റ്റിമൈസേഷൻ സമയം!

ഇതിനർത്ഥം എല്ലാ ഡാറ്റാ സാമ്പിളുകളിലുമുള്ള പിശക് കുറഞ്ഞിരിക്കുന്ന , എന്നീ പാരാമീറ്ററുകളുള്ള മോഡൽ കണ്ടെത്തേണ്ടതുണ്ട്. എന്നാൽ നമ്മുടെ പിശക് എന്താണ്? രണ്ട് സംഖ്യകൾ , എന്നിവ താരതമ്യം ചെയ്യുന്നതിനാൽ ഭാഗം 1-ൽ നിന്നുള്ള നിർവചനം തന്നെ നമുക്ക് ഉപയോഗിക്കാം.

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

ഇവിടെ, -ന്റെ ആം വരിയെ സൂചിപ്പിക്കുന്നു. എന്നത് ആം സാമ്പിളിന്റെയും ഭാരങ്ങളുടെയും ഡോട്ട് ഉൽപ്പന്നമാണ്. ബയസ് ചേർത്ത ശേഷം, നമുക്ക് , അതായത് പ്രവചനം ലഭിക്കുന്നു.

കോഡിൽ:

// Return a single row matrix that represents the row of m at i
matrix matrix_get_row(matrix m, int i) {
  return (matrix){
      .buf = m.buf + i * m.cols, .rows = 1, .cols = m.cols};
}

mfloat cost(struct mlinear_model *model, matrix X,
                    matrix Y) {
  mfloat cost = 0.0;
  for (int i = 0; i < X.rows; i++) {
    matrix x_i = matrix_get_row(X, i);
    mfloat f_wb = predict(model, x_i);
    mfloat diff = matrix_get(Y, 0, i) - f_wb;
    cost += diff * diff;
  }
  return cost / (2.0 * X.rows);
}

എങ്ങനെയായിരുന്നു ആ അനുമാനം?

നമുക്ക് നമ്മുടെ അനുമാനത്തിലേക്ക് തിരിച്ചുപോയി, അത് എങ്ങനെ പ്രവർത്തിക്കുന്നുവെന്ന് നോക്കാം.

int main() {
  matrix W = matrix_new(X.cols, 1);
  struct mlinear_model model = {.W = W, .b = 0.0};
  printf("Cost of zero model: %f\n", cost(&model, X, Y));
  matrix_set(W, 0, 0, 0.34);
  matrix_set(W, 1, 0, 3.3);
  matrix_set(W, 2, 0, 5.84);
  matrix_set(W, 3, 0, -8.07);
  model.b = 1634.5;
  printf("Cost of guesstimate model: %f\n",
         cost(&model, X, Y));
}

ഔട്ട്പുട്ട്:

Cost of zero model:        3340683.781483
Cost of guesstimate model: 2641267.466911

ആർക്കറിയാമായിരുന്നു! ഒരു ഊഹത്തേക്കാൾ അല്പം മികച്ചതാണ്. ഗ്രേഡിയന്റ് ഡിസെന്റിലേക്ക് തിരികെ.

നമുക്ക് കാൽക്കുലസ് ഉപയോഗിക്കാം. എന്നതിനെ യുമായും യുമായും ബന്ധപ്പെട്ട് വ്യത്യാസപ്പെടുത്തേണ്ടതുണ്ട്, അങ്ങനെ എറർ കുറയ്ക്കാൻ ഏത് ദിശയിലാണ്, എത്ര വ്യാപ്തിയിൽ ആണ് ഞങ്ങൾ ഭാരങ്ങളും ബയസുകളും നീക്കേണ്ടതെന്ന് നമുക്ക് അറിയാം.

വളരെ ലളിതമാണ്, അല്ലേ? ക്ക് അത്രയല്ല. ഒരു മാട്രിക്സ് ആയതിനാൽ, യിലെ ഓരോ ഭാരത്തിനും ഔട്ട്പുട്ടിൽ പ്രത്യേക പ്രഭാവമുണ്ട്. ഇതിനർത്ഥം ഓരോ ഭാരത്തിനും ഒരു പ്രത്യേക ഡെറിവേറ്റീവ് കണക്കാക്കേണ്ടതുണ്ട്.

ടേം വരുന്നത് വ്യത്യാസപ്പെടുത്തുന്നതിൽ നിന്നാണ്. ഒരു നിശ്ചിത ഭാരം , വരി എന്നിവയ്ക്ക്, പോലെ കാണപ്പെടുന്ന ഒരു ടേം ഉണ്ടാകും. എന്നത് എന്നതുമായി ബന്ധപ്പെട്ട ഭാഗിക ഡെറിവേറ്റീവിനുള്ളിൽ ഒരു സ്ഥിരമായ ഗുണകമായതിനാൽ, ചെയിൻ റൂൾ കാരണം അത് പുറത്തേക്ക് വലിച്ചെടുക്കപ്പെടും.

മാട്രിക്സുകളുടെ അടിസ്ഥാനത്തിൽ ഞങ്ങൾ ഇതിനകം ചിന്തിക്കുന്നതിനാൽ, മാട്രിക്സുമായി ബന്ധപ്പെട്ട യുടെ ഡെറിവേറ്റീവ് എഴുതാം

അത് നന്നായി ലഘൂകരിച്ചു! മെഷീൻ ലേണിംഗിൽ ഞങ്ങൾ മാട്രിക്സുകളെ എന്തുകൊണ്ടാണ് ഇഷ്ടപ്പെടുന്നതെന്ന് നിങ്ങൾ കാണാൻ തുടങ്ങിയേക്കാം. ബാച്ച് കണക്കുകൂട്ടലുകളെക്കുറിച്ച് ചിന്തിക്കാനുള്ള ഒരു നല്ല മാർഗമാണിത്.

ഇപ്പോൾ, കോഡിനായി

struct grad {
	matrix dJ_dW;
	mfloat dJ_db;
};

/* Compute gradient and write result to out. */
void compute_gradient(struct grad *out,
                      struct mlinear_model *model, const matrix X,
                      const matrix Y) {
  int m = X.rows;  // സാമ്പിളുകളുടെ എണ്ണം
  int n = X.cols;  // ഫീച്ചറുകളുടെ എണ്ണം

  // X യുടെ ഓരോ വരിയും സംഭരിക്കാൻ tmp ഉപയോഗിക്കുന്നു
  matrix tmp = matrix_new(1, n);
  for (int i = 0; i < m; i++) {
    // tmp = X^(i)
    matrix curr_row = matrix_get_row(X, i);
    // y_hat = (X^(i) dot W) + b
    mfloat y_hat = predict(model, curr_row);
    // yi = y^(i)
    mfloat yi = matrix_get(Y, 0, i);
    // പരാൻതീസിസിലെ ടേം
    mfloat err = y_hat - yi;

    /*
     * dJ_dW നായി, എറർ
     * നിലവിലെ വരി കൊണ്ട് ഗുണിച്ച്, റണ്ണിംഗ് സം ആയി ചേർക്കേണ്ടതുണ്ട്
     */

    // tmp = X^(i) * (y_hat^(i) - y^(i))
    matrix_scalar_mul(tmp, curr_row, err);
    // dJ_dW += tmp
    matrix_ip_T_add(out->dJ_dW, tmp);

    // dJ_db += (y_hat^(i) - y^(i))
    out->dJ_db += err;
  }

  /*
   * ഇവിടെ 2/m നെ 1/m ആക്കി മാറ്റാൻ പോകുന്നു, കാരണം 2
   * അടുത്ത ഘട്ടത്തിൽ ആൽഫയിലേക്ക് നീക്കാം.
   */

  // dJ/db = (dJ/db) / m
  out->dJ_db /= m;
  // dJ/dW = (dJ/dW) / m
  matrix_scalar_ip_mul(out->dJ_dW, 1.0 / m);
  matrix_del(tmp);
}

കുറിപ്പ്: മാട്രിക്സ് ഫംഗ്ഷനുകളിലെ ip എന്നാൽ ഫലം ആദ്യ ആർഗ്യുമെന്റിലേക്ക് നിയോഗിക്കപ്പെടുന്നു. അല്ലാത്തപക്ഷം, ഫലം ആദ്യ ആർഗ്യുമെന്റായി കൈമാറിയ ബഫറിലേക്ക് നിയോഗിക്കപ്പെടുന്നു.

പുതിയ matrix_* ഫംഗ്ഷനുകൾ താരതമ്യേന ലളിതമാണ്, അതിനാൽ ലേഖനം ചുരുക്കാൻ ഞാൻ ഇവിടെ കോഡ് ഒഴിവാക്കാൻ പോകുന്നു. വാസ്തവത്തിൽ, ഈ ഫംഗ്ഷനുകൾ #define macros ഉപയോഗിച്ച് സൃഷ്ടിച്ചതാണ്, അതിനാൽ റിപ്പോയിൽ പൂർണ്ണ സോഴ്സ് പോലുമില്ല. ഇത് പരിശോധിക്കുന്ന ഒരു ഭാഗം 2.5 നായി ശ്രദ്ധിക്കുക!

കോഡ് പരിശോധിച്ച് അത് ഗണിതവുമായി പൊരുത്തപ്പെടുന്നുവെന്ന് പരിശോധിക്കാൻ ഞാൻ നിങ്ങളെ പ്രോത്സാഹിപ്പിക്കുന്നു.

ഇപ്പോൾ ഞങ്ങൾക്ക് ഗ്രേഡിയന്റ് ഉള്ളതിനാൽ, ഗ്രേഡിയന്റ് ഡിസെന്റ് നടപ്പിലാക്കുന്നത് നേരിട്ടുള്ളതാണ്. ഭാഗം 1-ൽ നിന്നുള്ള അൽഗോരിതത്തിന്റെ ഒരു റിഫ്രെഷർ:

കൺവെർജൻസ് വരെ ആവർത്തിക്കുക:

  1. പ്രാരംഭ ഭാര & ബയാസ് മൂല്യങ്ങൾ സജ്ജമാക്കുക:
  2. ഡെറിവേറ്റീവുകളുടെ വിപരീത ദിശയിൽ വേരിയബിളുകൾ നീക്കുക, സ്റ്റെപ്പ് സൈസ് :
  3. ഘട്ടം 2 ലേക്ക് പോകുക.

കോഡിൽ:

void gradient_descent(struct mlinear_model *model, const matrix X,
                      const matrix Y, const int num_iterations,
                      const mfloat alpha) {
  // ഗ്രേഡിയന്റിനായുള്ള പുനരുപയോഗ ബഫർ
  int n = X.cols, m = X.rows;
  matrix dJ_dW = matrix_new(n, 1);
  struct grad tmp_grad = {.dJ_dW = dJ_dW, .dJ_db = 0.0};

  for (int i = 0; i < num_iterations; i++) {
    // പുരോഗതി ലോഗ് ചെയ്യുക
    if (i % (num_iterations >> 4) == 0) {
      printf("\tCost at iteration %d: %f\n", i,
             compute_cost(model, X, Y));
    }
    // tmp_grad = മോഡലിനായുള്ള നിലവിലെ ഗ്രേഡിയന്റ്
    compute_gradient(&tmp_grad, model, X, Y);
    // dJ/dW *= -alpha
    matrix_scalar_ip_mul(tmp_grad.dJ_dW, -alpha);
    // W += dJ/dW
    matrix_ip_add(model->W, tmp_grad.dJ_dW);
    // b += -alpha * dJ/db
    model->b += -alpha * tmp_grad.dJ_db;
  }
  matrix_del(dJ_dW);
}

അത്രയേയുള്ളൂ! ഇപ്പോൾ ഞങ്ങളുടെ ചെലവ് എങ്ങനെ മെച്ചപ്പെടുന്നുവെന്ന് കാണാൻ ഞങ്ങൾ ഡാറ്റയിൽ പ്രോഗ്രാം പ്രവർത്തിപ്പിക്കുന്നു:

int main() {
  // ഹൈപ്പർപാരാമീറ്ററുകൾ
  const int num_iterations = 1e7;
  const mfloat alpha = 1e-8;

  int n = X.cols, m = X.rows;
  matrix W = matrix_new(n, 1);
  struct mlinear_model model = {.W = W, .b = 0.0};

  printf("Initial cost: %f\n", compute_cost(&model, X, Y));
  gradient_descent(&model, X, Y, num_iterations, alpha);
  printf("Final cost: %f\n", compute_cost(&model, X, Y));
  printf("Model parameters:\n");
  matrix_print(model.W);
  printf(" b=%f\n", model.b);
}

ഔട്ട്പുട്ട്:

Initial cost: 3340683.781483
        Cost at iteration 0: 3340683.781483
        Cost at iteration 625000: 161369.409722
        Cost at iteration 1250000: 161332.630253
        Cost at iteration 1875000: 161315.326515
        Cost at iteration 2500000: 161298.041198
        Cost at iteration 3125000: 161280.768143
        Cost at iteration 3750000: 161263.507342
        Cost at iteration 4375000: 161246.258784
        Cost at iteration 5000000: 161229.022462
        Cost at iteration 5625000: 161211.798366
        Cost at iteration 6250000: 161194.586488
        Cost at iteration 6875000: 161177.386819
        Cost at iteration 7500000: 161160.199351
        Cost at iteration 8125000: 161143.024075
        Cost at iteration 8750000: 161125.860983
        Cost at iteration 9375000: 161108.710065
Final cost: 161091.571313
Model parameters:
W=[ 2.1185e-01 ]
[ 5.8627e+00 ]
[ 4.5904e+00 ]
[ -1.1702e+01 ]

 b=5.310395

## ഒപ്റ്റിമൈസർ ഒപ്റ്റിമൈസ് ചെയ്യുന്നു

നിങ്ങൾ ശ്രദ്ധിച്ചിരിക്കാം, ഗ്രേഡിയന്റ് ഡിസെന്റിൻ്റെ പത്ത് ദശലക്ഷം ഇറററേഷൻ കഴിഞ്ഞിട്ടും കോസ്റ്റ് കുറഞ്ഞുകൊണ്ടിരിക്കുന്നു. വെറും 4 ഭാരങ്ങൾ മാത്രമേ ഒപ്റ്റിമൈസ് ചെയ്യാനുള്ളൂ എങ്കിലും ഒപ്റ്റിമൽ പാരാമീറ്ററുകൾ കണ്ടെത്താൻ എന്തിനാണ് ഇത്രയും ബുദ്ധിമുട്ട്?

ഇതിൻ്റെ ഒരു വലിയ ഭാഗം ഫീച്ചറുകളുടെ വ്യാപനത്തിലെ വ്യത്യാസം മൂലമാണ്. ഈ ബോക്സ് പ്ലോട്ട് നോക്കൂ

Loading...
സ്ക്വയർ ഫൂട്ടേജിൻ്റെ ഭാരം മറ്റ് വേരിയബിളുകളുടെ ഭാരങ്ങളേക്കാൾ *വളരെയധികം* നീങ്ങേണ്ടതുണ്ടെന്ന് നമുക്ക് കാണാം. എന്നാൽ, ഓരോ ഭാരവും അപ്ഡേറ്റ് ചെയ്യുമ്പോൾ സ്റ്റെപ്പ് സൈസ് $\alpha$ ഒന്നുതന്നെയാണ്. ഇതിനർത്ഥം, മറ്റ് 3 ഫീച്ചർ ഭാരങ്ങൾ കൺവേർജ് ചെയ്തതിന് ശേഷം സ്ക്വയർ ഫൂട്ടേജ് ഭാരം കൺവേർജ് ചെയ്യാൻ വളരെയധികം സമയം കാത്തിരിക്കേണ്ടി വരും എന്നാണ്. ഒരു പരിഹാരം, വ്യാപനത്തിന് ആനുപാതികമായ $\alpha$ മൂല്യങ്ങൾ തിരഞ്ഞെടുക്കുകയും ഓരോ ഭാരവും അതിൻ്റെ അനുബന്ധ $\alpha$ ഉപയോഗിച്ച് നീക്കുകയും ചെയ്യുക എന്നതാണ്. എന്നാൽ ഇതിനർത്ഥം, വലിയ മോഡലുകൾക്ക് ചെലവേറിയതായ മറ്റൊരു ആൽഫ മൂല്യങ്ങളുടെ അറേ സംഭരിക്കേണ്ടി വരും എന്നാണ്. മികച്ചൊരു പരിഹാരം, യഥാർത്ഥത്തിൽ നമ്മുടെ ഇൻപുട്ട് ഡാറ്റ മാറ്റിയെഴുതുക എന്നതാണ്, അങ്ങനെ അവയ്ക്ക് സമാനമായ വ്യാപനം ലഭിക്കും. നമുക്ക് അവയെ ഒരു നോർമൽ ഡിസ്ട്രിബ്യൂഷനുമായി ഫിറ്റ് ചെയ്ത് ഇത് ചെയ്യാൻ കഴിയും. ആദ്യം ഓരോ ഫീച്ചറിൻ്റെയും മീൻ കണക്കാക്കാം. $$ \mu^{(i)} = \frac{1}{n} \sum_{j=1}^{n} X^{(i)}_j $$ തുടർന്ന്, ഓരോ ഫീച്ചറിൻ്റെയും വ്യാപനം അഥവാ സ്റ്റാൻഡേർഡ് ഡീവിയേഷൻ കണക്കാക്കാം. $$ \sigma^{(i)} = \sqrt{\frac{\sum_{j=1}^{n}(X^{(i)}_j - \mu^{(i)})^2}{n}} $$ ഒടുവിൽ, നമുക്ക് അത് നോർമലൈസ് ചെയ്യാം $$ Z^{(i)} = \frac{X^{(i)} - \mu^{(i)}}{\sigma^{(i)}} $$ ഓരോ കോളവും അതിൻ്റെ മീൻ കുറച്ചുകൊണ്ട് ഡാറ്റ *സെൻ്റർ* ചെയ്യുന്നത് ഡിസ്ട്രിബ്യൂഷൻ്റെ ഇരുവശത്തും സമാനമായ വ്യാപനം ഉണ്ടാകുന്നതിന് കാരണമാകുന്നു. $\sigma$ കൊണ്ട് ഹരിക്കുന്നത് ഓരോ കോളിൻ്റെയും വ്യാപനം ഏകദേശം ഒന്നുതന്നെയാക്കുന്നു. മെട്രിക്സ് ഓപ്പറേഷനുകളുടെ അടിസ്ഥാനത്തിൽ ഇത് എഴുതാം ```c // ഇൻപുട്ട് ഡാറ്റ `X` നോർമലൈസ് ചെയ്യുക void z_score_normalize(matrix X) { int n = X.cols, m = X.rows; // mu-ക്കുള്ള ബഫർ matrix mean = matrix_new(1, n); // sigma-ക്കുള്ള ബഫർ matrix stdev = matrix_new(1, n); // mu കണക്കാക്കുക for (int i = 0; i < NUM_SAMPLES; i++) { matrix_ip_add(mean, matrix_get_row(X, i)); } matrix_scalar_ip_mul(mean, 1.0 / NUM_SAMPLES); // sigma കണക്കാക്കുക matrix buf = matrix_new(1, n); for (int i = 0; i < NUM_SAMPLES; i++) { matrix row = matrix_get_row(X, i); matrix_sub(buf, mean, row); matrix_ip_square(buf); matrix_ip_add(stdev, buf); } matrix_ip_sqrt(stdev); // Z കണക്കാക്കുക for (int i = 0; i < NUM_SAMPLES; i++) { matrix row = matrix_get_row(X, i); matrix_ip_sub(row, mean); matrix_ip_div(row, stdev); } }

നോർമലൈസ് ചെയ്ത ഞങ്ങളുടെ ഡാറ്റ എങ്ങനെയുണ്ട്?

Loading...

എല്ലാ വ്യാപനങ്ങളും ഇപ്പോൾ ഒരേ സ്കെയിലിലാണെന്ന് നമുക്ക് കാണാം. ഇത് ഞങ്ങളുടെ പ്രകടനം എങ്ങനെ മെച്ചപ്പെടുത്തുന്നു?

int main() {
  int n = X.cols, m = X.rows;
  z_score_normalize(X);
  matrix W = matrix_new(n, 1);
  struct mlinear_model model = {.W = W, .b = 0.0};
  printf("Initial cost: %f\n", compute_cost(&model, X, Y));
  const int num_iterations = 1e7;
  const mfloat alpha = 1e-8;
  gradient_descent(&model, X, Y, num_iterations, alpha);
  printf("Final cost: %f\n", compute_cost(&model, X, Y));

  printf("Model parameters:\nW=");
  matrix_print(model.W);
  printf(" b=%f\n", model.b);
}

ഔട്ട്പുട്ട്:

Initial cost: 3340683.781483
        Cost at iteration 0: 3340683.781483
        Cost at iteration 625000: 3305547.933747
        Cost at iteration 1250000: 3270852.031317
        Cost at iteration 1875000: 3236590.554988
        Cost at iteration 2500000: 3202758.054240
        Cost at iteration 3125000: 3169349.146943
        Cost at iteration 3750000: 3136358.518493
        Cost at iteration 4375000: 3103780.920969
        Cost at iteration 5000000: 3071611.172296
        Cost at iteration 5625000: 3039844.155415
        Cost at iteration 6250000: 3008474.817473
        Cost at iteration 6875000: 2977498.169013
        Cost at iteration 7500000: 2946909.283181
        Cost at iteration 8125000: 2916703.294939
        Cost at iteration 8750000: 2886875.400289
        Cost at iteration 9375000: 2857420.855511
Final cost: 2828334.976402
Model parameters:
W=[ 8.3053e+00 ]
[ 2.4385e+00 ]
[ 6.0186e+00 ]
[ -2.2844e+00 ]

 b=227.136534

ഇത് കൂടുതൽ മന്ദഗതിയിലാണോ!? എന്നാൽ കാത്തിരിക്കൂ— മാറ്റി ആക്കുമ്പോൾ എന്ത് സംഭവിക്കുന്നുവെന്ന് നോക്കൂ, ഇത് ഞങ്ങളുടെ പഴയ മോഡൽ ഡൈവേർജ് ചെയ്യാൻ കാരണമാകുമായിരുന്നു.

Initial cost: 3340683.781483
        Cost at iteration 0: 3340683.781483
        Cost at iteration 625000: 136947.544865
        Cost at iteration 1250000: 136947.544865
        Cost at iteration 1875000: 136947.544865
        Cost at iteration 2500000: 136947.544865
        Cost at iteration 3125000: 136947.544865
        Cost at iteration 3750000: 136947.544865
        Cost at iteration 4375000: 136947.544865
        Cost at iteration 5000000: 136947.544865
        Cost at iteration 5625000: 136947.544865
        Cost at iteration 6250000: 136947.544865
        Cost at iteration 6875000: 136947.544865
        Cost at iteration 7500000: 136947.544865
        Cost at iteration 8125000: 136947.544865
        Cost at iteration 8750000: 136947.544865
        Cost at iteration 9375000: 136947.544865
Final cost: 136947.544865
Model parameters:
W=[ 8.1088e+03 ]
[ 8.1764e+02 ]
[ 6.6840e+02 ]
[ -3.6835e+03 ]

 b=2364.131545

വാവ്! ആദ്യത്തെ ലോഗിൽ തന്നെ, അത് ഏറ്റവും കുറഞ്ഞ മൂല്യത്തിലേക്ക് കൺവേർജ് ചെയ്തു! വാസ്തവത്തിൽ, ഉപയോഗിച്ച്, ഇറററേഷനുകൾക്ക് പകരം കൺവേർജൻസിന് ഏകദേശം ഇറററേഷനുകൾ മാത്രമേ വേണ്ടിവരൂ! അതിനാൽ, z-score നോർമലൈസേഷൻ ഉപയോഗിക്കുന്നത് ഗ്രേഡിയന്റ് ഡിസെന്റിൻ്റെ വേഗത ഓർഡറുകൾ വരെ വർദ്ധിപ്പിക്കുമെന്ന് നമുക്ക് കാണാം, പ്രത്യേകിച്ചും ഫീച്ചറുകൾക്ക് വ്യത്യസ്ത വ്യാപനങ്ങൾ ഉള്ളപ്പോൾ.

പുതിയ , എന്നിവ യഥാർത്ഥ മൂല്യങ്ങളിൽ നിന്ന് വളരെ അകലെയാണെന്ന് നിങ്ങൾ ശ്രദ്ധിച്ചിട്ടുണ്ടെങ്കിൽ, അതിന് കാരണം ഞങ്ങൾ അടിസ്ഥാനപരമായി ഇൻപുട്ട് ഡാറ്റ മാറ്റി എന്നതാണ്. ഇപ്പോൾ ഞങ്ങൾ മോഡലിംഗ് ചെയ്യുന്നത് ഒരു ഫീച്ചർ സാമ്പിളിൻ്റെ പൂർണ്ണ മൂല്യത്തിന് പകരം അതിൻ്റെ വ്യതിയാനത്തിൻ്റെ വ്യാപ്തിയുടെ അടിസ്ഥാനത്തിലാണ്.

ഉപസംഹാരം

അതിനാൽ മൾട്ടിവേറിയറ്റ് ലീനിയർ റിഗ്രഷനെക്കുറിച്ചുള്ളത് ഇത്രമാത്രം. ഈ പരമ്പരയിലെ ഇതുവരെയുള്ളതിൽ വച്ച് ഏറ്റവും ബുദ്ധിമുട്ടുള്ള ലേഖനമാണിത്, അതിനാൽ ഭാവിയെ ഭയപ്പെടേണ്ട! എല്ലാ കോഡും ഇവിടെ കാണാം. അത് റൺ ചെയ്ത് അതിന്റെ പ്രവർത്തനം മാറ്റാൻ ഞാൻ നിങ്ങളെ പ്രോത്സാഹിപ്പിക്കുന്നു.

എന്തെങ്കിലും ചോദ്യങ്ങളോ അഭിപ്രായങ്ങളോ ഉണ്ടെങ്കിൽ, ഒരു കമന്റ് ഇടാനോ എനിക്ക് ഒരു ഇമെയിൽ അയക്കാനോ മടിക്കേണ്ട.

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