machine-learning
Perceptron
Поиск…
Что такое персептрон?
По своей сути перцептронная модель является одним из простейших контролируемых алгоритмов обучения для двоичной классификации . Это тип линейного классификатора , то есть алгоритм классификации, который делает его прогнозы на основе линейной функции прогнозирования, объединяющей набор весов с вектором признаков. Более интуитивный способ думать - это как нейронная сеть с одним нейроном .
Принцип работы очень прост. Он получает вектор входных значений x, каждый из которых является особенностью нашего набора данных.
Пример:
Скажем, что мы хотим классифицировать, является ли объект велосипедом или автомобилем. Ради этого примера предположим, что мы хотим выбрать 2 функции. Высота и ширина объекта. В этом случае x = [x1, x2], где x1 - высота, а x2 - ширина.
Затем, как только у нас есть наш вектор ввода x, мы хотим умножить каждый элемент в этом векторе с весом. Обычно чем выше значение веса, тем важнее эта функция. Если, например, мы использовали цвет как функцию x3, а красный велосипед и красный автомобиль, персептрон установил для него очень низкий вес, чтобы цвет не повлиял на окончательное предсказание.
Итак, мы умножили 2 вектора x и w и вернули вектор. Теперь нам нужно суммировать элементы этого вектора. Умный способ сделать это - вместо простого умножения x на w, мы можем умножить x на wT, где T обозначает транспонирование. Мы можем представить себе перенос вектора как вращающуюся версию вектора. Для получения дополнительной информации вы можете прочитать страницу Википедии . По существу, беря транспонирование вектора w, мы получаем вектор Nx1 вместо 1xN . Таким образом, если мы теперь умножим наш вектор ввода с размером 1xN с этим весовым вектором Nx1, мы получим вектор 1x1 (или просто одно значение), который будет равен x1 * w1 + x2 * w2 + ... + xn * wn . Сделав это, у нас теперь есть наше предсказание. Но есть одна последняя вещь. Это предсказание, вероятно, не будет простым 1 или -1, чтобы иметь возможность классифицировать новый образец. Поэтому мы можем просто сказать следующее: если наше предсказание больше 0, то мы говорим, что образец принадлежит классу 1, в противном случае, если предсказание меньше нуля, мы говорим, что образец принадлежит классу -1. Это называется ступенчатой функцией .
Но как мы получаем правильные весы, чтобы мы делали правильные прогнозы? Другими словами, как мы обучаем нашу перцептроновую модель?
Ну, в случае персептрона нам не нужны фантастические математические уравнения для обучения нашей модели. Наши весы можно отрегулировать следующим уравнением:
Δw = eta * (y - предсказание) * x (i)
где x (i) - наша функция (x1, например, для веса 1, x2 для w2 и т. д.).
Также заметили, что существует переменная eta, которая является скоростью обучения. Вы можете представить себе скорость обучения, насколько велика необходимость изменения веса. Хорошая скорость обучения приводит к быстрому алгоритму обучения. Слишком высокое значение eta может приводить к увеличению количества ошибок в каждую эпоху и приводит к тому, что модель делает действительно плохие прогнозы и никогда не сходится. Слишком низкая скорость обучения может привести к тому, что модель займет слишком много времени, чтобы сходиться. (Обычно хорошее значение для установки eta для модели персептрона равно 0,1, но оно может отличаться от случая к случаю).
Наконец, некоторые из вас могли заметить, что первый вход является константой (1) и умножается на w0. Так что же это такое? Чтобы получить хорошее предсказание, нам нужно добавить смещение. И это именно то, что эта константа.
Чтобы изменить вес члена смещения, мы используем то же уравнение, что и для других весов, но в этом случае мы не умножаем его на вход (потому что вход является константой 1, и поэтому нам не нужно):
Δw = eta * (y - предсказание)
Таким образом, это в основном то, как работает простая модель персептрона! Как только мы тренируем наши веса, мы можем дать новые данные и получить наши прогнозы.
НОТА:
У модели Перцептрона есть важный недостаток! Он никогда не будет сходиться (ei найти идеальные веса), если данные не являются линейно разделяемыми , что означает возможность разделить 2 класса в пространстве объектов по прямой. Поэтому, чтобы избежать того, что хорошая практика заключается в добавлении фиксированного количества итераций, чтобы модель не застревала при настройке весов, которые никогда не будут полностью настроены.
Внедрение модели Perceptron в C ++
В этом примере я рассмотрю реализацию модели персептрона в C ++, чтобы вы могли лучше понять, как это работает.
Во-первых, это хорошая практика, чтобы написать простой алгоритм того, что мы хотим сделать.
Алгоритм:
- Сделайте вектор для весов и инициализируйте его до 0 (не забудьте добавить термин смещения)
- Продолжайте корректировать вес до тех пор, пока мы не получим 0 ошибок или количество ошибок.
- Сделайте прогнозы по невидимым данным.
Написав супер простой алгоритм, давайте теперь напишем некоторые из функций, которые нам понадобятся.
- Нам понадобится функция для вычисления ввода сети (ei x * wT, умножающая время ввода весов)
- Шаговая функция, так что мы получаем предсказание либо 1, либо -1
- И функция, которая находит идеальные значения для весов.
Поэтому без дальнейших церемоний, давайте прямо в нее.
Давайте начнем просто, создав класс персептрона:
class perceptron
{
public:
private:
};
Теперь добавим функции, которые нам понадобятся.
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:
};
Обратите внимание, как функция fit принимает в качестве аргумента вектор vector <float>. Это связано с тем, что наш учебный набор данных является матрицей входных данных. По существу мы можем представить себе, что матрица как пара векторов x укладывает один поверх другого и каждый столбец этой матрицы является признаком.
Наконец, добавим значения, которые должен иметь наш класс. Такие, как вектор w для хранения весов, число эпох, которое указывает количество проходов, которые мы будем делать по набору учебных данных. И константа eta, которая является скоростью обучения, которую мы будем умножать на каждое обновление веса, чтобы ускорить процедуру обучения, набрав это значение или если этата слишком высока, мы можем набрать ее, чтобы получить идеальный результат (для большинства приложений из персептрона я бы предложил значение 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;
};
Теперь с нашим набором классов. Пришло время написать каждую из функций.
Мы начнем с конструктора ( персептрон (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
}
Как вы можете видеть, что мы будем делать, это очень простой материал. Итак, перейдем к другой простой функции. Функция прогнозирования ( int pred (вектор X); ). Помните, что то, что делает вся функция прогноза , это принимать чистый ввод и возвращать значение 1, если netInput больше 0 и -1 otherwhise.
int perceptron::predict(vector<float> X)
{
return netInput(X) > 0 ? 1 : -1; //Step Function
}
Обратите внимание, что мы использовали оператор inline if, чтобы упростить нашу жизнь. Вот как работает оператор inline if:
состояние ? if_true: else
Все идет нормально. Перейдем к реализации функции netInput ( float netInput (вектор X); )
NetInput делает следующее: умножает входной вектор на транспонирование вектора веса
x * wT
Другими словами, он умножает каждый элемент входного вектора x на соответствующий элемент вектора весов w, а затем берет их сумму и добавляет смещение.
(x1 * w1 + x2 * w2 + ... + xn * wn) + смещение
смещение = 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;
}
Хорошо, поэтому теперь мы в значительной степени сделали последнее, что нам нужно сделать, это написать функцию соответствия, которая изменяет веса.
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
}
}
}
Так это было по существу. Имея только 3 функции, теперь у нас есть рабочий класс персептрона, который мы можем использовать для прогнозирования!
Если вы хотите скопировать код и попробовать его. Вот весь класс (я добавил некоторые дополнительные функции, такие как печать вектора веса и ошибок в каждую эпоху, а также добавление опции для импорта / экспорта весов).
Вот код:
Заголовок класса:
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;
};
Файл .cpp класса с функциями:
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;
}
Также, если вы хотите попробовать пример, вот пример, который я сделал:
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;
}
Способ импортирования набора диафрагмы не идеален, но я просто хотел что-то, что сработало.
Файлы данных можно найти здесь.
Я надеюсь, что вы нашли это полезным!
Что такое предвзятость
Что такое предвзятость
Персептрон можно рассматривать как функцию, которая отображает входной (вещественный) вектор x в выходное значение f (x) (двоичное значение):
где w - вектор вещественных весов, а b - наше смещающее значение. Предвзятость - это значение, которое сдвигает границу решения от начала координат (0,0) и не зависит от какого-либо входного значения.
Думая на смещение пространственным образом, смещение изменяет положение (хотя и не ориентацию) границы решения. Ниже мы можем привести пример той же кривой, сдвинутой смещением: