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

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

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

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

മൾട്ടിവേറിയബിൾ ലീനിയർ മോഡൽ

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

ഇവിടെയാണ് നമ്മുടെ മോഡലിന് വ്യത്യസ്ത ക്ലാസുകളിലുള്ള വേരിയബിളുകൾ മിക്സ് ചെയ്യാനും മാഷ് ചെയ്യാനും കഴിയേണ്ടതുണ്ടെന്ന് നമുക്ക് മനസ്സിലാകുന്നത്, ഓരോന്നും നമ്മുടെ പ്രവചനത്തിൽ വ്യത്യസ്തമായ സ്വാധീനം ചെലുത്താം.

പ്രശ്ന പ്രസ്താവന

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

ഇനിപ്പറയുന്ന ഇൻപുട്ട് വേരിയബിളുകൾ അവയുടെ അനുബന്ധ വീടിന്റെ വിലകൾ (1000s ഡോളറിൽ) എന്നിവയുടെ ഡാറ്റ നൽകിയിട്ടുണ്ട്:

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

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

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

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

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

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

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

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

Loading...

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

പ്ലോട്ട്: ലോട്ട് വീതി vs. വില

Loading...

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

പ്ലോട്ട്: ലോട്ട് ആഴം vs വില

Loading...

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

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

Loading...

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

ഡാറ്റയ്ക്കുള്ള ഒരു പ്രാഥമിക മാതൃക

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

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

ശരി… ഇപ്പോൾ ഇത് $1.6M ആണ്. എന്നാൽ ഇത് സമുദ്രതീരത്ത് ആയതിനാൽ ഇത് യുക്തിസഹമായിരിക്കാം. ബാക്കിയുള്ള മാതൃക യുക്തിസഹമാണെന്ന് തോന്നുന്നു. ഇത് സൂചിപ്പിക്കുന്നത്:

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

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

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

MLM, കോഡിൽ

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

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

ഇപ്പോൾ, നിങ്ങൾക്ക് കുറച്ച് ഘട്ടങ്ങൾ മുന്നോട്ട് ചിന്തിക്കാൻ കഴിഞ്ഞിരിക്കാം, w-യെ ഒരു വെക്ടർ ആയും x-യെ ഒരു വെക്ടർ ആയും പ്രതിനിധീകരിച്ചാൽ, മോഡലിന്റെ ഔട്ട്പുട്ട് , എന്നിവയുടെ ഡോട്ട് പ്രൊഡക്ട് ആയി പ്രതിനിധീകരിക്കാമെന്ന് മനസ്സിലാക്കിയിരിക്കാം, ചേർത്ത്

അഹ്, വളരെ ക്ലീനായി തോന്നുന്നുണ്ടോ? ശരി, കുറച്ചുനേരം മാത്രം…

ഞാൻ ഇവിടെ മറ്റൊരു ചാട്ടം എടുക്കാൻ പോകുന്നു, ഞങ്ങളുടെ ദൈർഘ്യമുള്ള വെക്ടറിനെ ഒരു മാട്രിക്സ് ആയി പ്രതിനിധീകരിക്കാൻ.

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

// 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) ഉണ്ട്

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

അതിനാൽ, ഒരു row vector ആയിരിക്കണം, ഒരു column vector ആയിരിക്കണം, അങ്ങനെ വെക്ടർ ഡോട്ട് പ്രൊഡക്ടിന്റെ പ്രവർത്തനം പുനരാവർത്തിക്കാം.

നിങ്ങൾക്ക് മാട്രിക്സ് ഡോട്ട് പ്രൊഡക്ടിനെ രണ്ടാമത്തെ മാട്രിക്സിലെ ഓരോ കോളും ആദ്യ മാട്രിക്സിലെ row-ൽ ഫ്ലിപ്പ് ചെയ്ത് ഡോട്ട് ചെയ്യുന്നതായി കരുതാം.

കോഡ് നോക്കുക, മുകളിലെ ഗുണനം പറഞ്ഞ ഫലം തിരികെ നൽകുമെന്ന് പരിശോധിക്കുക.

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

// mlr.c

// x ഒരു row vector ആണ്, അല്ലെങ്കിൽ (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 അറേകൾ നൽകിയിട്ടുണ്ട്: ഇൻപുട്ട് ഡാറ്റ, ഇത് മാട്രിക്സ് ആയി ഫോർമാറ്റ് ചെയ്തിരിക്കുന്നു.

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

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

നമുക്ക് 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-ൽ നിന്നുള്ള അതേ നിർവചനം ഉപയോഗിക്കാം.

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

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

കോഡിൽ:

// m ന്റെ i-th വരിയെ പ്രതിനിധീകരിക്കുന്ന ഒരൊറ്റ വരി മാട്രിക്സ് തിരികെ നൽകുക
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;
};

/* ഗ്രേഡിയന്റ് കണക്കാക്കുകയും ഫലം 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;  // ഫീച്ചറുകളുടെ എണ്ണം

  // tmp ഉപയോഗിച്ച് X യുടെ ഓരോ വരിയും സംഭരിക്കുന്നു
  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
   * അടുത്ത ഘട്ടത്തിൽ alpha യിലേക്ക് നീക്കാം.
   */

  // 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; // മ്യൂവിനായുള്ള ബഫർ matrix mean = matrix_new(1, n); // സിഗ്മയ്ക്കായുള്ള ബഫർ matrix stdev = matrix_new(1, n); // മ്യൂ കണക്കാക്കുക 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); // സിഗ്മ കണക്കാക്കുക 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 നോർമലൈസേഷൻ ഉപയോഗിക്കുന്നത് ഗ്രേഡിയന്റ് ഡിസെന്റിന്റെ വേഗത ഓർഡറുകൾ വരെ വർദ്ധിപ്പിക്കാൻ കഴിയും, പ്രത്യേകിച്ച് ഫീച്ചറുകൾക്ക് വ്യത്യസ്ത സ്പ്രെഡുകൾ ഉള്ളപ്പോൾ.

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

ഉപസംഹാരം

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

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

✦ No LLMs were used in the ideation, research, writing, or editing of this article.