Sök…


Vad är exakt en perceptron?

Visualisering av en perceptron

I sin kärna är en perceptronmodell en av de enklaste övervakade inlärningsalgoritmerna för binär klassificering . Det är en typ av linjär klassificering , dvs en klassificeringsalgoritm som gör sina förutsägelser baserade på en linjär prediktorfunktion som kombinerar en uppsättning vikter med funktionsvektorn. Ett mer intuitivt sätt att tänka på är som ett neuralt nätverk med bara en neuron .

Det fungerar mycket enkelt. Det får en vektor av inmatningsvärden x varav varje element är en funktion i vår datauppsättning.

Ett exempel:

Säg att vi vill klassificera om ett objekt är en cykel eller en bil. För detta exempel ska vi säga att vi vill välja två funktioner. Objektets höjd och bredd. I så fall är x = [x1, x2] där x1 är höjden och x2 är bredden.

Sedan när vi har vår inmatningsvektor x vill vi multiplicera varje element i den vektorn med en vikt. Vanligtvis ju högre viktvärdet är, desto viktigare är funktionen. Om vi till exempel använde färg som funktion x3 och det finns en röd cykel och en röd bil kommer perceptronen att sätta en väldigt låg vikt så att färgen inte påverkar den slutliga förutsägelsen.

Okej så vi har multiplicerat de 2 vektorerna x och w och vi fått tillbaka en vektor. Nu måste vi summera elementen i denna vektor. Ett smart sätt att göra detta är istället för att enkelt multiplicera x med w kan vi multiplicera x med wT där T står för transponering. Vi kan föreställa oss transponering av en vektor som en roterad version av vektorn. För mer information kan du läsa Wikipedia-sidan . I huvudsak genom att ta transponering av vektorn w får vi en Nx1- vektor istället för en 1xN . Så om vi nu multiplicerar vår inputvektor med storlek 1xN med denna Nx1 viktvektor kommer vi att få en 1x1 vektor (eller helt enkelt ett enda värde) som kommer att vara lika med x1 * w1 + x2 * w2 + ... + xn * wn . Efter att ha gjort det har vi nu vår förutsägelse. Men det finns en sista sak. Denna förutsägelse kommer förmodligen inte att vara en enkel 1 eller -1 för att kunna klassificera ett nytt prov. Så vad vi kan göra är att helt enkelt säga följande: Om vår förutsägelse är större än 0 så säger vi att provet tillhör klass 1, annars om förutsägelsen är mindre än noll säger vi att provet tillhör klass -1. Detta kallas en stegfunktion .

Men hur får vi rätt vikt så att vi gör rätt förutsägelser? Med andra ord, hur utbildar vi vår perceptronmodell?

I fallet med perceptronet behöver vi inte snygga matematikekvationer för att träna vår modell. Våra vikter kan justeras med följande ekvation:

Δw = eta * (y - förutsägelse) * x (i)

där x (i) är vår funktion (x1 till exempel för vikt 1, x2 för w2 och så vidare ...).

Märkte också att det finns en variabel som heter eta som är inlärningshastigheten. Du kan föreställa dig inlärningshastigheten som hur stor vi vill att vikten förändras. En bra inlärningshastighet resulterar i en snabb inlärningsalgoritm. Ett för högt värde på eta kan resultera i en ökande mängd fel vid varje epok och resulterar i att modellen gör riktigt dåliga förutsägelser och aldrig konvergerar. För lågt av en inlärningshastighet kan ha som resultat att modellen tar för mycket tid att konvergera. (Vanligtvis är ett bra värde att ställa in eta för perceptronmodellen 0,1 men det kan skilja sig från fall till fall).

Slutligen kanske några av er har märkt att den första ingången är en konstant (1) och multipliceras med w0. Så vad är det egentligen? För att få en bra förutsägelse måste vi lägga till en förspänning. Och det är exakt vad den konstanten är.

För att ändra vikten på förspänningsterminen använder vi samma ekvation som vi gjorde för de andra vikterna men i detta fall multiplicerar vi inte den med ingången (eftersom ingången är en konstant 1 och så behöver vi inte):

Δw = eta * (y - förutsägelse)

Så det är i princip hur en enkel perceptronmodell fungerar! När vi tränar våra vikter kan vi ge den nya data och ha våra förutsägelser.

NOTERA:

Perceptron-modellen har en viktig nackdel! Det kommer aldrig att konvergera (ei hitta de perfekta vikterna) om uppgifterna inte är linjärt separerbara , vilket innebär att de kan klara två klasser i ett funktionsutrymme med en rak linje. Så för att undvika att det är en bra praxis att lägga till ett fast antal iterationer så att modellen inte fastnar vid justering av vikter som aldrig kommer att vara perfekt inställda.

Implementera en Perceptron-modell i C ++

I det här exemplet kommer jag att gå igenom implementeringen av perceptron-modellen i C ++ så att du kan få en bättre uppfattning om hur den fungerar.

Först är det en bra praxis att skriva ner en enkel algoritm för vad vi vill göra.

Algoritm:

  1. Gör en vektor för vikterna och initiera den till 0 (Glöm inte att lägga till bias-termen)
  2. Fortsätt justera vikterna tills vi får 0 fel eller ett lågt felantal.
  3. Gör förutsägelser om osynliga data.

Efter att ha skrivit en super enkel algoritm, låt oss nu skriva några av de funktioner som vi kommer att behöva.

  • Vi kommer att behöva en funktion för att beräkna nätets ingång (ei x * wT multiplicera ingångarna vikterna)
  • En stegfunktion så att vi får en förutsägelse av antingen 1 eller -1
  • Och en funktion som hittar de ideala värdena för vikterna.

Så utan ytterligare förtjänst, låt oss komma in på det.

Låt oss börja enkelt genom att skapa en perceptronklass:

class perceptron
{
public:
    
private:

};

Låt oss nu lägga till de funktioner som vi kommer att behöva.

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:

};

Lägg märke till hur funktionen passar som argument en vektor av vektorn <float>. Det beror på att vår utbildningsdatasats är en matris med input. I huvudsak kan vi föreställa oss att matrisen som ett par vektorer x staplade den ovanpå en annan och varje kolumn i den matrisen är en funktion.

Slutligen låt oss lägga till de värden som vår klass behöver ha. Såsom vektorn w för att hålla vikterna, antalet epoker som indikerar antalet pass som vi kommer att göra över träningsdatasättet. Och den konstanta etan som är inlärningshastigheten som vi kommer att multiplicera varje viktuppdatering för att göra träningsförfarandet snabbare genom att slå upp detta värde eller om eta är för högt kan vi slå det ner för att få det perfekta resultatet (för de flesta applikationer av perceptronen skulle jag föreslå ett etavärde på 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;
};

Nu med vår klassuppsättning. Det är dags att skriva var och en av funktionerna.

Vi börjar från konstruktören ( 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
}

Som du kan se vad vi kommer att göra är mycket enkla saker. Så låt oss gå vidare till en annan enkel funktion. Förutsägningsfunktionen ( int förutsäga (vektor X); ). Kom ihåg att vad all prediktionsfunktionen gör är att ta nettingången och returnera ett värde på 1 om netInput är större än 0 och -1 annat.

int perceptron::predict(vector<float> X)
{
    return netInput(X) > 0 ? 1 : -1; //Step Function
}

Lägg märke till att vi använde en inline om uttalande för att göra våra liv lättare. Så här inline om uttalande fungerar:

tillstånd ? if_true: annars

Än så länge är allt bra. Låt oss gå vidare med att implementera netInput- funktionen ( float netInput (vektor X); )

NetInput gör följande: multiplicerar inmatningsvektorn med transponering av viktsvektorn

x * wT

Med andra ord multiplicerar det varje element i inmatningsvektorn x med motsvarande element i vektorns vektor w och tar sedan sin summa och lägger till förspänningen.

(x1 * w1 + x2 * w2 + ... + xn * wn) + förspänning

förspänning = 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;
}

Okej så vi är nu ganska mycket klara det sista vi behöver göra är att skriva passningsfunktionen som ändrar vikterna.

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
        }
    }
}

Så det var i huvudsak det. Med bara tre funktioner har vi nu en fungerande perceptronklass som vi kan använda för att göra förutsägelser!

Om du vill kopiera och klistra in koden och prova den. Här är hela klassen (jag har lagt till lite extra funktionalitet som att skriva ut viktsvektorn och felen i varje epok samt lagt till möjligheten att importera / exportera vikter.)

Här är koden:

Klasshuvudet:

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;
};

Klassen .cpp-fil med funktionerna:

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;
}

Om du vill prova ett exempel här är också ett exempel jag gjorde:

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;
}

Hur jag importerade iris-datasättet är inte riktigt perfekt men jag ville bara ha något som fungerade.

Datafilerna kan hittas här.

Jag hoppas att du tyckte att det var bra!

Vad är förspänningen


Vad är förspänningen

En perceptron kan ses som en funktion som kartlägger en inmatad (realvärderad) vektor x till ett utgångsvärde f (x) (binärt värde):

ange bildbeskrivning här

där w är en vektor med verkligt värderade vikter och b är vårt förspänningsvärde . Förspänningen är ett värde som förskjuter beslutsgränsen från ursprunget (0,0) och som inte beror på något ingångsvärde.

Tänker på förspänningen på ett rumsligt sätt, förändrar förspänningen positionen (dock inte riktningen) för beslutsgränsen. Vi kan se nedan ett exempel på samma kurva skiftad av förspänningen:

ange bildbeskrivning här



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow