machine-learning
Perceptron
Szukaj…
Czym dokładnie jest perceptron?
U podstaw modelu perceptronowego znajduje się jeden z najprostszych nadzorowanych algorytmów uczenia się do klasyfikacji binarnej . Jest to rodzaj liniowego klasyfikatora , tj. Algorytmu klasyfikacji, który opiera swoje prognozy na liniowej funkcji predyktora łączącej zestaw wag z wektorem cech. Bardziej intuicyjny sposób myślenia jest jak sieć neuronowa z tylko jednym neuronem .
Sposób działania jest bardzo prosty. Pobiera wektor wartości wejściowych x, z których każdy element jest cechą naszego zestawu danych.
Przykład:
Powiedzmy, że chcemy sklasyfikować, czy przedmiot jest rowerem czy samochodem. Na potrzeby tego przykładu powiedzmy, że chcemy wybrać 2 funkcje. Wysokość i szerokość obiektu. W takim przypadku x = [x1, x2], gdzie x1 to wysokość, a x2 to szerokość.
Następnie, gdy mamy już wektor wejściowy x , chcemy pomnożyć każdy element w tym wektorze przez wagę. Zwykle im wyższa wartość masy, tym ważniejsza jest ta funkcja. Jeśli na przykład użyliśmy koloru jako funkcji x3, a jest czerwony rower i czerwony samochód, perceptron ustawi na niego bardzo niską wagę, aby kolor nie wpływał na ostateczną prognozę.
Dobrze więc musimy pomnożyć przez 2 wektorów x i w i wróciliśmy wektor. Teraz musimy zsumować elementy tego wektora. Sprytnym sposobem na to jest zamiast prostego pomnożenia x przez w , możemy pomnożyć x przez wT, gdzie T oznacza transpozycję. Możemy sobie wyobrazić transpozycję wektora jako jego obróconej wersji. Więcej informacji można znaleźć na stronie Wikipedii . Zasadniczo biorąc transpozycję wektora w , otrzymujemy wektor Nx1 zamiast 1xN . Zatem jeśli teraz pomnożymy nasz wektor wejściowy przez rozmiar 1xN przez ten wektor wagi Nx1 , otrzymamy wektor 1x1 (lub po prostu pojedynczą wartość), który będzie równy x1 * w1 + x2 * w2 + ... + xn * wn . Po dokonaniu tego mamy teraz naszą prognozę. Ale jest jeszcze jedna rzecz. Ta prognoza prawdopodobnie nie będzie prostą 1 lub -1, aby móc sklasyfikować nową próbkę. Możemy więc po prostu powiedzieć: Jeśli nasze przewidywanie jest większe niż 0, wówczas mówimy, że próbka należy do klasy 1, w przeciwnym razie, jeśli przewidywanie jest mniejsze od zera, mówimy, że próbka należy do klasy -1. Nazywa się to funkcją krokową .
Ale w jaki sposób otrzymujemy odpowiednie wagi, abyśmy mogli poprawnie przewidywać? Innymi słowy, w jaki sposób trenujemy nasz model perceptronu?
Cóż, w przypadku perceptronu nie potrzebujemy fantazyjnych równań matematycznych do trenowania naszego modelu. Nasze wagi można dostosować za pomocą następującego równania:
Δw = eta * (y - prognoza) * x (i)
gdzie x (i) jest naszą cechą (x1 na przykład dla wagi 1, x2 dla w2 i tak dalej ...).
Zauważyłem również, że istnieje zmienna o nazwie eta, która jest współczynnikiem uczenia się. Możesz sobie wyobrazić szybkość uczenia się jako wielkość, jaką chcemy mieć zmiana wag. Dobra szybkość uczenia się skutkuje algorytmem szybkiego uczenia się. Zbyt wysoka wartość eta może powodować zwiększenie liczby błędów w każdej epoce i skutkować tym, że model robi naprawdę złe prognozy i nigdy się nie zbiega. Zbyt niski wskaźnik uczenia się może sprawić, że zebranie modelu zajmie zbyt dużo czasu. (Zwykle dobrą wartością dla eta dla modelu perceptronu jest 0,1, ale może różnić się w zależności od przypadku).
Wreszcie niektórzy z was mogli zauważyć, że pierwsze wejście jest stałą (1) i jest mnożone przez w0. Co to właściwie jest? Aby uzyskać dobrą prognozę, musimy dodać błąd. I to jest dokładnie ta stała.
Aby zmodyfikować wagę terminu odchylenia, używamy tego samego równania, co w przypadku innych wag, ale w tym przypadku nie mnożymy go przez dane wejściowe (ponieważ dane wejściowe to stała 1, więc nie musimy):
Δw = eta * (y - prognoza)
Tak właśnie działa prosty model perceptronowy! Po szkoleniu naszych wag możemy przekazać im nowe dane i uzyskać nasze prognozy.
UWAGA:
Model Perceptron ma istotną wadę! Nigdy się nie zbiegnie (tzn. Znajdzie idealnych wag), jeśli danych nie da się rozdzielić liniowo , co oznacza możliwość oddzielenia 2 klas w przestrzeni cech za pomocą linii prostej. Aby uniknąć tego, że dobrą praktyką jest dodanie stałej liczby iteracji, aby model nie utknął w dostosowywaniu ciężarów, które nigdy nie będą idealnie dostrojone.
Implementacja modelu Perceptron w C ++
W tym przykładzie omówię implementację modelu perceptronu w C ++, abyś mógł lepiej zrozumieć, jak on działa.
Po pierwsze, dobrą praktyką jest zapisanie prostego algorytmu tego, co chcemy zrobić.
Algorytm:
- Utwórz wektor wag i zainicjuj go na 0 (nie zapomnij dodać terminu polaryzacji)
- Kontynuuj dostosowywanie wag, aż otrzymamy 0 błędów lub niską liczbę błędów.
- Prognozuj niewidoczne dane.
Po napisaniu super prostego algorytmu napiszmy teraz niektóre funkcje, których będziemy potrzebować.
- Będziemy potrzebować funkcji do obliczenia nakładu netto (ei x * wT pomnożenie wejściowego czasu przez wagi)
- Funkcja kroku, dzięki czemu otrzymujemy prognozę 1 lub -1
- I funkcja, która znajduje idealne wartości dla odważników.
Więc bez zbędnych ceregieli przejdźmy do tego.
Zacznijmy od stworzenia klasy perceptronów:
class perceptron
{
public:
private:
};
Dodajmy teraz funkcje, których będziemy potrzebować.
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:
};
Zauważ, jak dopasowanie funkcji przyjmuje jako argument wektor wektora <float>. Jest tak, ponieważ nasz zestaw danych szkoleniowych jest matrycą danych wejściowych. Zasadniczo możemy sobie wyobrazić tę macierz jako kilka wektorów x ułożonych jeden na drugim, a każda kolumna tej macierzy jest cechą.
Na koniec dodajmy wartości, które musi mieć nasza klasa. Takich jak wektor w utrzymujący ciężary, liczba epok, która wskazuje liczbę przejść, które wykonamy w zbiorze danych treningowych. I stała eta, której szybkość uczenia się pomnożymy, przy każdej aktualizacji masy, aby przyspieszyć procedurę treningową poprzez zwiększenie tej wartości lub jeśli eta jest zbyt wysoka, możemy ją obniżyć, aby uzyskać idealny wynik (dla większości aplikacji perceptronu sugerowałbym wartość eta 0,1).
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;
};
Teraz z naszym zestawem klas. Czas napisać każdą z funkcji.
Zaczniemy od konstruktora ( 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
}
Jak widać, to, co będziemy robić, jest bardzo proste. Przejdźmy więc do kolejnej prostej funkcji. Funkcja przewidywania ( int przewidywanie (wektor X); ). Pamiętaj, że to, co robi funkcja przewidywania wszystkiego, polega na pobraniu danych wejściowych netto i zwróceniu wartości 1, jeśli wartość netInput jest większa niż 0 i 1 w przeciwnym razie.
int perceptron::predict(vector<float> X)
{
return netInput(X) > 0 ? 1 : -1; //Step Function
}
Zauważ, że użyliśmy wbudowanej instrukcji if, aby ułatwić nam życie. Oto jak działa instrukcja inline if:
stan? if_true: else
Jak na razie dobrze. Przejdźmy do implementacji funkcji netInput ( float netInput (wektor X); )
NetInput wykonuje następujące czynności; mnoży wektor wejściowy przez transpozycję wektora wag
x * wT
Innymi słowy, mnoży każdy element wektora wejściowego x przez odpowiedni element wektora wag w, a następnie pobiera ich sumę i dodaje odchylenie.
(x1 * w1 + x2 * w2 + ... + xn * wn) + stronniczość
stronniczość = 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;
}
W porządku, więc jesteśmy już prawie skończeni. Ostatnią rzeczą, którą musimy zrobić, jest napisanie funkcji dopasowania, która modyfikuje wagi.
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
}
}
}
Tak to było w gruncie rzeczy. Dzięki tylko 3 funkcjom mamy teraz działającą klasę perceptronów, której możemy używać do prognozowania!
Jeśli chcesz skopiować i wkleić kod i wypróbuj go. Oto cała klasa (dodałem dodatkowe funkcje, takie jak drukowanie wektora wag i błędów w każdej epoce, a także dodałem opcję importowania / eksportowania wag.)
Oto kod:
Nagłówek klasy:
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;
};
Plik klasy .cpp z funkcjami:
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;
}
Również jeśli chcesz wypróbować przykład, oto przykład, który zrobiłem:
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;
}
Sposób, w jaki zaimportowałem zestaw danych tęczówki, nie jest naprawdę idealny, ale chciałem tylko czegoś, co zadziała.
Pliki danych można znaleźć tutaj.
Mam nadzieję, że okaże się to pomocne!
Jakie jest uprzedzenie
Jakie jest uprzedzenie
Perceptron można postrzegać jako funkcję, która odwzorowuje wektor wejściowy (wartości rzeczywistej) x na wartość wyjściową f (x) (wartość binarna):
gdzie w jest wektorem rzeczywistych wag, a b jest naszą wartością odchylenia . Odchylenie jest wartością, która przesuwa granicę decyzji od początku (0,0) i która nie zależy od żadnej wartości wejściowej.
Myśląc o uprzedzeniu w sposób przestrzenny, uprzedzenie zmienia pozycję (choć nie orientację) granicy decyzji. Poniżej możemy zobaczyć przykład tej samej krzywej przesuniętej przez odchylenie: