Buscar..


¿Qué es exactamente un perceptrón?

Visualización de un perceptrón.

En su núcleo, un modelo de perceptrón es uno de los algoritmos de aprendizaje supervisado más simples para la clasificación binaria . Es un tipo de clasificador lineal , es decir, un algoritmo de clasificación que realiza sus predicciones basadas en una función de predictor lineal que combina un conjunto de pesos con el vector de características. Una forma más intuitiva de pensar es como una red neuronal con una sola neurona .

La forma en que funciona es muy simple. Obtiene un vector de valores de entrada x de los cuales cada elemento es una característica de nuestro conjunto de datos.

Un ejemplo:

Digamos que queremos clasificar si un objeto es una bicicleta o un automóvil. Por el bien de este ejemplo, digamos que queremos seleccionar 2 funciones. La altura y el ancho del objeto. En ese caso x = [x1, x2] donde x1 es la altura y x2 es el ancho.

Luego, una vez que tengamos nuestro vector de entrada x , queremos multiplicar cada elemento en ese vector con un peso. Por lo general, cuanto más alto es el valor del peso, más importante es la característica. Si, por ejemplo, utilizamos el color como característica x3 y hay una bicicleta roja y un coche rojo, el perceptrón le asignará un peso muy bajo para que el color no afecte la predicción final.

Muy bien, así que hemos multiplicado los 2 vectores x y w y obtuvimos un vector. Ahora necesitamos sumar los elementos de este vector. Una forma inteligente de hacer esto es en lugar de simplemente multiplicar x por w podemos multiplicar x por wT donde T significa transposición. Podemos imaginar la transposición de un vector como una versión rotada del vector. Para más información puedes leer la página de Wikipedia . Esencialmente, al tomar la transposición del vector w obtenemos un vector Nx1 en lugar de un 1xN . Por lo tanto, si ahora multiplicamos nuestro vector de entrada con tamaño 1xN con este vector de peso Nx1 obtendremos un vector 1x1 (o simplemente un solo valor) que será igual a x1 * w1 + x2 * w2 + ... + xn * wn . Habiendo hecho eso, ahora tenemos nuestra predicción. Pero hay una última cosa. Esta predicción probablemente no será un simple 1 o -1 para poder clasificar una nueva muestra. Entonces, lo que podemos hacer es simplemente decir lo siguiente: si nuestra predicción es mayor que 0, entonces decimos que la muestra pertenece a la clase 1, de lo contrario, si la predicción es menor que cero, diremos que la muestra pertenece a la clase -1. Esto se llama una función de paso .

Pero, ¿cómo obtenemos las ponderaciones correctas para hacer predicciones correctas? En otras palabras, ¿cómo entrenamos nuestro modelo de percepción?

Bueno, en el caso del perceptrón, no necesitamos ecuaciones matemáticas sofisticadas para entrenar nuestro modelo. Nuestros pesos pueden ser ajustados por la siguiente ecuación:

Δw = eta * (y - predicción) * x (i)

donde x (i) es nuestra función (x1 por ejemplo para peso 1, x2 para w2 y así sucesivamente ...).

También notó que hay una variable llamada eta que es la tasa de aprendizaje. Puedes imaginarte la tasa de aprendizaje como cuán grande queremos que sea el cambio de los pesos. Una buena tasa de aprendizaje resulta en un algoritmo de aprendizaje rápido. Un valor demasiado alto de eta puede resultar en una cantidad creciente de errores en cada época y los resultados en el modelo hacen predicciones realmente malas y nunca convergen. Una tasa de aprendizaje demasiado baja puede tener como resultado que el modelo tome demasiado tiempo para converger. (Por lo general, un buen valor para establecer eta para el modelo de perceptrón es 0.1 pero puede diferir de un caso a otro).

Finalmente, algunos de ustedes habrán notado que la primera entrada es una constante (1) y se multiplica por w0. Entonces, ¿qué es exactamente eso? Para obtener una buena predicción necesitamos agregar un sesgo. Y eso es exactamente lo que es esa constante.

Para modificar el peso del término de sesgo usamos la misma ecuación que hicimos para los otros pesos, pero en este caso no lo multiplicamos por la entrada (porque la entrada es una constante 1 y no tenemos que hacerlo):

Δw = eta * (y - predicción)

¡Así es básicamente como funciona un modelo simple de perceptrón! Una vez que entrenamos nuestros pesos, podemos darle nuevos datos y tener nuestras predicciones.

NOTA:

El modelo Perceptron tiene una desventaja importante! Nunca convergerá (ei encontrará los pesos perfectos) si los datos no se pueden separar linealmente , lo que significa poder separar las 2 clases en un espacio de características mediante una línea recta. Por lo tanto, para evitar eso, es una buena práctica agregar un número fijo de iteraciones para que el modelo no se quede atascado al ajustar los pesos que nunca se ajustarán perfectamente.

Implementando un modelo de Perceptron en C ++

En este ejemplo, pasaré por la implementación del modelo de perceptrón en C ++ para que pueda tener una mejor idea de cómo funciona.

Primero lo primero es una buena práctica escribir un algoritmo simple de lo que queremos hacer.

Algoritmo:

  1. Haga un vector para los pesos e inicialícelo a 0 (No olvide agregar el término de sesgo)
  2. Siga ajustando los pesos hasta que obtengamos 0 errores o un recuento bajo de errores.
  3. Hacer predicciones sobre datos invisibles.

Habiendo escrito un algoritmo súper simple, ahora escribamos algunas de las funciones que necesitaremos.

  • Necesitaremos una función para calcular la entrada de la red (ei x * wT multiplicando las entradas por el tiempo de los pesos)
  • Una función escalonada para que podamos obtener una predicción de 1 o -1
  • Y una función que encuentra los valores ideales para los pesos.

Así que sin más preámbulos, entremos en esto.

Comencemos simple creando una clase de perceptron:

class perceptron
{
public:
    
private:

};

Ahora agreguemos las funciones que necesitaremos.

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:

};

Observe cómo el ajuste de la función toma como argumento un vector del vector <float>. Esto se debe a que nuestro conjunto de datos de capacitación es una matriz de entradas. Esencialmente, podemos imaginar que la matriz como un par de vectores x apiladas una encima de otra y cada columna de esa Matriz es una característica.

Finalmente, agreguemos los valores que nuestra clase necesita tener. Como el vector w para mantener los pesos, el número de épocas que indica el número de pases que realizaremos sobre el conjunto de datos de entrenamiento. Y la constante eta, cuya velocidad de aprendizaje multiplicaremos cada actualización de peso para acelerar el procedimiento de entrenamiento al aumentar este valor o si eta es demasiado alto, podemos marcarlo para obtener el resultado ideal (para la mayoría de las aplicaciones del perceptrón sugeriría un valor eta de 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;
};

Ahora con nuestro conjunto de clases. Es hora de escribir cada una de las funciones.

Comenzaremos desde el 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
}

Como pueden ver, lo que haremos es algo muy simple. Así que vamos a pasar a otra función simple. La función de predicción ( int predecir (vector X); ). Recuerde que lo que hace la función de predicción total es tomar la entrada neta y devolver un valor de 1 si el netInput es mayor que 0 y -1 en cualquier otro lugar.

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

Observe que usamos una declaración en línea si para hacer nuestras vidas más fáciles. Así es como funciona la instrucción inline if:

condición? if_true: else

Hasta ahora tan bueno. Continuemos con la implementación de la función netInput ( float netInput (vector X); )

El netInput hace lo siguiente; multiplica el vector de entrada por la transposición del vector de pesos

x * wT

En otras palabras, multiplica cada elemento del vector de entrada x por el elemento correspondiente del vector de ponderaciones w y luego toma su suma y agrega el sesgo.

(x1 * w1 + x2 * w2 + ... + xn * wn) + sesgo

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

Bien, ahora ya casi hemos terminado. Lo último que tenemos que hacer es escribir la función de ajuste que modifica los pesos.

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

Así que eso era esencialmente eso. ¡Con solo 3 funciones ahora tenemos una clase de percepción de trabajo que podemos usar para hacer predicciones!

En caso de que quieras copiar y pegar el código y probarlo. Aquí está la clase completa (agregué algunas funciones adicionales, como la impresión del vector de pesos y los errores en cada época, así como la opción de importar pesos de exportación).

Aquí está el código:

El encabezado de la clase:

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

El archivo de clase .cpp con las funciones:

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

Además, si quieres probar un ejemplo, aquí hay un ejemplo que hice:

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

La forma en que importé el conjunto de datos del iris no es realmente ideal, pero solo quería algo que funcionara.

Los archivos de datos se pueden encontrar aquí.

Espero que hayas encontrado esto útil!

Que es el sesgo


Que es el sesgo

Un perceptrón se puede ver como una función que mapea un vector de entrada (valor real) x a un valor de salida f (x) (valor binario):

introduzca la descripción de la imagen aquí

donde w es un vector de pesos en valores reales y b es nuestro valor de sesgo . El sesgo es un valor que aleja el límite de decisión del origen (0,0) y no depende de ningún valor de entrada.

Pensando en el sesgo de una manera espacial, el sesgo altera la posición (aunque no la orientación) del límite de decisión. A continuación podemos ver un ejemplo de la misma curva desplazada por el sesgo:

introduzca la descripción de la imagen aquí



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow