machine-learning
perceptron
Zoeken…
Wat is precies een perceptron?
In de kern is een perceptron-model een van de eenvoudigste begeleide leeralgoritmen voor binaire classificatie . Het is een type lineaire classificeerder , dat wil zeggen een classificatie-algoritme dat zijn voorspellingen doet op basis van een lineaire voorspellingsfunctie die een set gewichten combineert met de kenmerkvector. Een meer intuïtieve manier om over na te denken is als een neuraal netwerk met slechts één neuron .
De manier waarop het werkt is heel eenvoudig. Het krijgt een vector van invoerwaarden x waarvan elk element een kenmerk is van onze gegevensset.
Een voorbeeld:
Stel dat we willen classificeren of een object een fiets of een auto is. Laten we voor dit voorbeeld zeggen dat we 2 functies willen selecteren. De hoogte en de breedte van het object. In dat geval x = [x1, x2] waarbij x1 de hoogte is en x2 de breedte.
Als we eenmaal onze input vector x hebben, willen we elk element in die vector vermenigvuldigen met een gewicht. Meestal hoe hoger de waarde van het gewicht, hoe belangrijker de functie is. Als we bijvoorbeeld kleur als functie x3 hebben gebruikt en er is een rode fiets en een rode auto, zal de perceptron een zeer laag gewicht instellen, zodat de kleur geen invloed heeft op de uiteindelijke voorspelling.
Oké, dus we hebben de 2 vectoren x en w vermenigvuldigd en we hebben een vector terug. Nu moeten we de elementen van deze vector optellen. Een slimme manier om dit te doen is in plaats van x te vermenigvuldigen met w kunnen we x vermenigvuldigen met wT waarbij T staat voor transponeren. We kunnen ons de transpositie van een vector voorstellen als een geroteerde versie van de vector. Voor meer info kun je de Wikipedia-pagina lezen . In wezen krijgen we door de transpositie van de vector w een Nx1- vector in plaats van een 1xN . Dus als we nu onze invoervector met grootte 1xN vermenigvuldigen met deze Nx1- gewichtsvector, krijgen we een 1x1- vector (of gewoon een enkele waarde) die gelijk is aan x1 * w1 + x2 * w2 + ... + xn * wn . Nadat we dat hebben gedaan, hebben we nu onze voorspelling. Maar er is nog een laatste ding. Deze voorspelling zal waarschijnlijk geen eenvoudige 1 of -1 zijn om een nieuwe steekproef te kunnen classificeren. Dus wat we kunnen doen, is gewoon het volgende zeggen: als onze voorspelling groter is dan 0, dan zeggen we dat de steekproef tot klasse 1 behoort, anders zeggen we dat als de voorspelling kleiner is dan nul, de steekproef tot klasse -1 behoort. Dit wordt een stapfunctie genoemd .
Maar hoe krijgen we de juiste gewichten zodat we correcte voorspellingen doen? Met andere woorden, hoe trainen we ons perceptron-model?
In het geval van de perceptron hebben we geen mooie wiskundige vergelijkingen nodig om ons model te trainen . Onze gewichten kunnen worden aangepast met de volgende vergelijking:
Δw = eta * (y - voorspelling) * x (i)
waar x (i) onze functie is (x1 bijvoorbeeld voor gewicht 1, x2 voor w2 enzovoort ...).
Merkte ook op dat er een variabele is die eta wordt genoemd, wat het leerpercentage is. Je kunt je het leerpercentage voorstellen als hoe groot we de verandering van de gewichten willen hebben. Een goede leersnelheid resulteert in een snel leeralgoritme. Een te hoge waarde van eta kan resulteren in een toenemend aantal fouten in elk tijdperk en kan ertoe leiden dat het model echt slechte voorspellingen doet en nooit convergeert. Een te laag leerpercentage kan ertoe leiden dat het model te veel tijd nodig heeft om te convergeren. (Gewoonlijk is een goede waarde om eta in te stellen voor het perceptron-model 0,1, maar dit kan van geval tot geval verschillen).
Ten slotte hebben sommigen van jullie misschien opgemerkt dat de eerste invoer een constante (1) is en wordt vermenigvuldigd met w0. Dus wat is dat precies? Om een goede voorspelling te krijgen, moeten we een afwijking toevoegen. En dat is precies wat die constante is.
Om het gewicht van de bias-term te wijzigen, gebruiken we dezelfde vergelijking als voor de andere gewichten, maar in dit geval vermenigvuldigen we deze niet met de invoer (omdat de invoer een constante 1 is en we dus niet hoeven te doen):
Δw = eta * (y - voorspelling)
Dus dat is eigenlijk hoe een eenvoudig perceptron-model werkt! Zodra we onze gewichten hebben getraind, kunnen we nieuwe gegevens geven en onze voorspellingen doen.
NOTITIE:
Het Perceptron-model heeft een belangrijk nadeel! Het zal nooit samenkomen (bijv. De perfecte gewichten vinden) als de gegevens niet lineair van elkaar kunnen worden gescheiden , wat betekent dat de 2 klassen in een kenmerkruimte door een rechte lijn kunnen worden gescheiden. Om te voorkomen dat het een goede gewoonte is om een vast aantal iteraties toe te voegen, zodat het model niet vastzit aan het aanpassen van gewichten die nooit perfect zullen worden afgestemd.
Implementeren van een Perceptron-model in C ++
In dit voorbeeld zal ik de implementatie van het perceptron-model in C ++ doornemen, zodat u een beter idee krijgt van hoe het werkt.
Allereerst is het een goede gewoonte om een eenvoudig algoritme op te schrijven van wat we willen doen.
Algoritme:
- Maak een vector voor de gewichten en initialiseer deze op 0 (vergeet niet de bias-term toe te voegen)
- Blijf de gewichten aanpassen totdat we 0 fouten of een laag aantal fouten krijgen.
- Voorspellingen doen over ongeziene gegevens.
Na een supereenvoudig algoritme te hebben geschreven, laten we nu enkele functies schrijven die we nodig hebben.
- We hebben een functie nodig om de invoer van het net te berekenen (ei x * wT vermenigvuldigt de invoer met de gewichten)
- Een stapfunctie zodat we een voorspelling krijgen van 1 of -1
- En een functie die de ideale waarden voor de gewichten vindt.
Laten we er dus zonder verder op ingaan.
Laten we eenvoudig beginnen met het maken van een perceptron-klasse:
class perceptron
{
public:
private:
};
Laten we nu de functies toevoegen die we nodig hebben.
class perceptron
{
public:
perceptron(float eta,int epochs);
float netInput(vector<float> X);
int predict(vector<float> X);
void fit(vector< vector<float> > X, vector<float> y);
private:
};
Merk op hoe de functie fit een vector van vector <float> als argument neemt. Dat komt omdat onze trainingsdataset een matrix van inputs is. In wezen kunnen we ons die matrix voorstellen als een paar vectoren x die op elkaar gestapeld zijn en elke kolom van die Matrix een functie is.
Laten we tot slot de waarden toevoegen die onze klasse moet hebben. Zoals de vector w om de gewichten vast te houden, het aantal tijdvakken dat het aantal passages aangeeft dat we over de trainingsdataset zullen doen. En de constante eta, wat de leersnelheid is waarvan we elke gewichtsupdate zullen vermenigvuldigen om de trainingsprocedure sneller te maken door deze waarde in te toetsen of als eta te hoog is, kunnen we het naar beneden bellen om het ideale resultaat te krijgen (voor de meeste toepassingen van de perceptron zou ik een eta- waarde van 0,1 voorstellen).
class perceptron
{
public:
perceptron(float eta,int epochs);
float netInput(vector<float> X);
int predict(vector<float> X);
void fit(vector< vector<float> > X, vector<float> y);
private:
float m_eta;
int m_epochs;
vector < float > m_w;
};
Nu met onze klassenset. Het is tijd om elk van de functies te schrijven.
We beginnen bij de constructor ( perceptron (float eta, int epochs); )
perceptron::perceptron(float eta, int epochs)
{
m_epochs = epochs; // We set the private variable m_epochs to the user selected value
m_eta = eta; // We do the same thing for eta
}
Zoals je ziet, is wat we gaan doen heel eenvoudig. Laten we dus verder gaan met een andere eenvoudige functie. De voorspellingsfunctie ( int voorspellen (vector X); ). Vergeet niet dat wat de al te voorspellen functie doet is het nemen van de netto-invoer en het retourneren van een waarde van 1 als de netInput groter is dan 0 en -1 Zoniet wordt.
int perceptron::predict(vector<float> X)
{
return netInput(X) > 0 ? 1 : -1; //Step Function
}
Merk op dat we een inline if-verklaring hebben gebruikt om ons leven gemakkelijker te maken. Dit is hoe de inline if-instructie werkt:
staat? if_true: anders
Tot zover gaat het goed. We gaan nu naar de uitvoering netInput functie (float netInput (vector X))
De netInput doet het volgende; vermenigvuldigt de invoervector met de transponering van de gewichtenvector
x * wT
Met andere woorden, het vermenigvuldigt elk element van de ingangsvector x met het overeenkomstige element van de vector van gewichten w en neemt vervolgens hun som en voegt de afwijking toe.
(x1 * w1 + x2 * w2 + ... + xn * wn) + bias
bias = 1 * w0
float perceptron::netInput(vector<float> X)
{
// Sum(Vector of weights * Input vector) + bias
float probabilities = m_w[0]; // In this example I am adding the perceptron first
for (int i = 0; i < X.size(); i++)
{
probabilities += X[i] * m_w[i + 1]; // Notice that for the weights I am counting
// from the 2nd element since w0 is the bias and I already added it first.
}
return probabilities;
}
Oké, dus we zijn nu bijna klaar met het laatste wat we moeten doen, is de fit- functie te schrijven die de gewichten aanpast.
void perceptron::fit(vector< vector<float> > X, vector<float> y)
{
for (int i = 0; i < X[0].size() + 1; i++) // X[0].size() + 1 -> I am using +1 to add the bias term
{
m_w.push_back(0); // Setting each weight to 0 and making the size of the vector
// The same as the number of features (X[0].size()) + 1 for the bias term
}
for (int i = 0; i < m_epochs; i++) // Iterating through each epoch
{
for (int j = 0; j < X.size(); j++) // Iterating though each vector in our training Matrix
{
float update = m_eta * (y[j] - predict(X[j])); //we calculate the change for the weights
for (int w = 1; w < m_w.size(); w++){ m_w[w] += update * X[j][w - 1]; } // we update each weight by the update * the training sample
m_w[0] = update; // We update the Bias term and setting it equal to the update
}
}
}
Dus dat was het in wezen. Met slechts 3 functies hebben we nu een werkende perceptron-klasse die we kunnen gebruiken om voorspellingen te doen!
Als u de code wilt kopiëren en plakken en uitproberen. Hier is de hele klasse (ik heb wat extra functionaliteit toegevoegd, zoals het afdrukken van de gewichtenvector en de fouten in elk tijdperk, evenals de optie om gewichten te importeren / exporteren.)
Hier is de code:
De klassenkop:
class perceptron
{
public:
perceptron(float eta,int epochs);
float netInput(vector<float> X);
int predict(vector<float> X);
void fit(vector< vector<float> > X, vector<float> y);
void printErrors();
void exportWeights(string filename);
void importWeights(string filename);
void printWeights();
private:
float m_eta;
int m_epochs;
vector < float > m_w;
vector < float > m_errors;
};
Het klasse .cpp-bestand met de functies:
perceptron::perceptron(float eta, int epochs)
{
m_epochs = epochs;
m_eta = eta;
}
void perceptron::fit(vector< vector<float> > X, vector<float> y)
{
for (int i = 0; i < X[0].size() + 1; i++) // X[0].size() + 1 -> I am using +1 to add the bias term
{
m_w.push_back(0);
}
for (int i = 0; i < m_epochs; i++)
{
int errors = 0;
for (int j = 0; j < X.size(); j++)
{
float update = m_eta * (y[j] - predict(X[j]));
for (int w = 1; w < m_w.size(); w++){ m_w[w] += update * X[j][w - 1]; }
m_w[0] = update;
errors += update != 0 ? 1 : 0;
}
m_errors.push_back(errors);
}
}
float perceptron::netInput(vector<float> X)
{
// Sum(Vector of weights * Input vector) + bias
float probabilities = m_w[0];
for (int i = 0; i < X.size(); i++)
{
probabilities += X[i] * m_w[i + 1];
}
return probabilities;
}
int perceptron::predict(vector<float> X)
{
return netInput(X) > 0 ? 1 : -1; //Step Function
}
void perceptron::printErrors()
{
printVector(m_errors);
}
void perceptron::exportWeights(string filename)
{
ofstream outFile;
outFile.open(filename);
for (int i = 0; i < m_w.size(); i++)
{
outFile << m_w[i] << endl;
}
outFile.close();
}
void perceptron::importWeights(string filename)
{
ifstream inFile;
inFile.open(filename);
for (int i = 0; i < m_w.size(); i++)
{
inFile >> m_w[i];
}
}
void perceptron::printWeights()
{
cout << "weights: ";
for (int i = 0; i < m_w.size(); i++)
{
cout << m_w[i] << " ";
}
cout << endl;
}
Ook als je een voorbeeld wilt proberen, is hier een voorbeeld dat ik heb gemaakt:
main.cpp:
#include <iostream>
#include <vector>
#include <algorithm>
#include <fstream>
#include <string>
#include <math.h>
#include "MachineLearning.h"
using namespace std;
using namespace MachineLearning;
vector< vector<float> > getIrisX();
vector<float> getIrisy();
int main()
{
vector< vector<float> > X = getIrisX();
vector<float> y = getIrisy();
vector<float> test1;
test1.push_back(5.0);
test1.push_back(3.3);
test1.push_back(1.4);
test1.push_back(0.2);
vector<float> test2;
test2.push_back(6.0);
test2.push_back(2.2);
test2.push_back(5.0);
test2.push_back(1.5);
//printVector(X);
//for (int i = 0; i < y.size(); i++){ cout << y[i] << " "; }cout << endl;
perceptron clf(0.1, 14);
clf.fit(X, y);
clf.printErrors();
cout << "Now Predicting: 5.0,3.3,1.4,0.2(CorrectClass=-1,Iris-setosa) -> " << clf.predict(test1) << endl;
cout << "Now Predicting: 6.0,2.2,5.0,1.5(CorrectClass=1,Iris-virginica) -> " << clf.predict(test2) << endl;
system("PAUSE");
return 0;
}
vector<float> getIrisy()
{
vector<float> y;
ifstream inFile;
inFile.open("y.data");
string sampleClass;
for (int i = 0; i < 100; i++)
{
inFile >> sampleClass;
if (sampleClass == "Iris-setosa")
{
y.push_back(-1);
}
else
{
y.push_back(1);
}
}
return y;
}
vector< vector<float> > getIrisX()
{
ifstream af;
ifstream bf;
ifstream cf;
ifstream df;
af.open("a.data");
bf.open("b.data");
cf.open("c.data");
df.open("d.data");
vector< vector<float> > X;
for (int i = 0; i < 100; i++)
{
char scrap;
int scrapN;
af >> scrapN;
bf >> scrapN;
cf >> scrapN;
df >> scrapN;
af >> scrap;
bf >> scrap;
cf >> scrap;
df >> scrap;
float a, b, c, d;
af >> a;
bf >> b;
cf >> c;
df >> d;
X.push_back(vector < float > {a, b, c, d});
}
af.close();
bf.close();
cf.close();
df.close();
return X;
}
De manier waarop ik de iris-gegevensset heb geïmporteerd, is niet echt ideaal, maar ik wilde gewoon iets dat werkte.
De gegevensbestanden zijn hier te vinden .
Ik hoop dat je dit nuttig vond!
Wat is de vertekening
Wat is de vertekening
Een perceptron kan worden gezien als een functie die een invoer (reële waarde) vector x toewijst aan een uitvoerwaarde f (x) (binaire waarde):
waarbij w een vector is van reële waarden en b een onze bias- waarde is. De afwijking is een waarde die de beslissingsgrens van de oorsprong wegschuift (0,0) en die niet afhankelijk is van een invoerwaarde.
Als we op een ruimtelijke manier naar de vertekening denken, verandert de vertekening de positie (hoewel niet de oriëntatie) van de beslissingsgrens. We kunnen hieronder een voorbeeld zien van dezelfde curve verschoven door de bias: