Last active
November 23, 2018 10:44
-
-
Save wortelstoemp/bd3b8fe2478b95a827bd0f4e2c737c7b to your computer and use it in GitHub Desktop.
Neural Network From Scratch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #pragma once | |
| // Proof of concept so I know how a neural network is built under the hood. | |
| // Author: Tom Quareme | |
| // TODO: https://www.youtube.com/watch?v=MJ_7qe--cvo 2-2 | |
| #include <iostream> | |
| #include <vector> | |
| #include <cmath> | |
| #include <random> | |
| namespace tqNN | |
| { | |
| template <typename T> | |
| class Neuron | |
| { | |
| private: | |
| T value; // raw value z | |
| T activatedValue; // f(z) in [0..1] | |
| T derivativeValue; // f'(z) | |
| public: | |
| Neuron(const T value = 0) | |
| { | |
| this->value = value; | |
| ComputeActivation(); | |
| ComputeDerivative(); | |
| } | |
| T Value() const { return value; } | |
| T ActivatedValue() const { return activatedValue; } | |
| T DerivativeValue() const { return derivativeValue; } | |
| void Value(const T value) | |
| { | |
| this->value = value; | |
| ComputeActivation(); | |
| ComputeDerivative(); | |
| } | |
| // Fast Sigmoid Function (has easier derivative) | |
| // a = f(z) = z / (1 + abs(z)) | |
| void ComputeActivation() | |
| { | |
| activatedValue = value / (1 + std::fabs(value)); | |
| } | |
| // Derivative of Sigmoid Function used in Backprop Algorithm | |
| // a' = f'(z) = f(z) * (1 - f(z)) | |
| void ComputeDerivative() | |
| { | |
| derivativeValue = activatedValue * (1 - activatedValue); | |
| } | |
| }; | |
| template <typename T> | |
| std::ostream &operator<<(std::ostream &os, const Neuron<T> &obj) | |
| { | |
| os << "Value: " << obj.Value() << std::endl; | |
| os << "Activated value: " << obj.ActivatedValue() << std::endl; | |
| os << "Derivative value: " << obj.DerivativeValue(); | |
| return os; | |
| } | |
| template <typename T> | |
| class Matrix | |
| { | |
| private: | |
| std::vector<T> elements; | |
| unsigned int rows; | |
| unsigned int cols; | |
| public: | |
| Matrix(const unsigned int rows = 1, const unsigned int cols = 1, const bool isRandom = false) | |
| { | |
| const unsigned int size = rows * cols; | |
| elements.resize(size); | |
| this->rows = rows; | |
| this->cols = cols; | |
| if (isRandom) { | |
| for (unsigned int i = 0; i < rows; ++i) { | |
| for (unsigned int j = 0; j < cols; ++j) { | |
| elements[i * cols + j] = GenerateRandomNumber(0.0, 1.0); | |
| } | |
| } | |
| return; | |
| } | |
| for (unsigned int i = 0; i < rows; ++i) { | |
| for (unsigned int j = 0; j < cols; ++j) { | |
| elements[i * cols + j] = 0.0; | |
| } | |
| } | |
| } | |
| unsigned int Rows() const { return rows; } | |
| unsigned int Cols() const { return cols; } | |
| std::vector<T> ToVector() | |
| { | |
| return elements; | |
| } | |
| Matrix Transpose() | |
| { | |
| Matrix m(cols, rows); | |
| for (unsigned int i = 0; i < rows; ++i) { | |
| for (unsigned int j = 0; j < cols; ++j) { | |
| m(j, i) = elements[i * cols + j]; | |
| } | |
| } | |
| return m; | |
| } | |
| T &operator() (unsigned int i, unsigned int j) | |
| { | |
| return elements[(i*cols) + j]; | |
| } | |
| Matrix<T> operator*(const Matrix<T> &other) const | |
| { | |
| Matrix<T> result(this->rows, other.cols, false); | |
| for (unsigned int i = 0; i < this->rows; ++i) { | |
| for (unsigned int j = 0; j < other.cols; ++j) { | |
| double sum = 0.0; | |
| for (unsigned int k = 0; k < this->cols; ++k) { | |
| sum += elements[(i*cols) + k] * other.elements[(k*other.cols) + j]; | |
| } | |
| result(i, j) = sum; | |
| } | |
| } | |
| return result; | |
| } | |
| double GenerateRandomNumber(double startIncl = 0.0, double endExcl = 1.0) | |
| { | |
| std::random_device rd; | |
| std::mt19937 gen(rd()); | |
| std::uniform_real_distribution<> dis(startIncl, endExcl); | |
| return dis(gen); | |
| } | |
| }; | |
| template <typename T> | |
| std::ostream &operator<<(std::ostream &os, Matrix<T> &obj) | |
| { | |
| os << "[" << std::endl; | |
| for (unsigned int i = 0; i < obj.Rows(); ++i) { | |
| for (unsigned int j = 0; j < obj.Cols(); ++j) { | |
| os << obj(i, j) << " "; | |
| } | |
| os << std::endl; | |
| } | |
| os << "]"; | |
| return os; | |
| } | |
| template <typename T> | |
| class Layer | |
| { | |
| private: | |
| std::vector< Neuron<T> > neurons; | |
| public: | |
| Layer(unsigned int size) | |
| { | |
| neurons.reserve(size); | |
| for (unsigned int i = 0; i < size; ++i) { | |
| neurons.push_back(Neuron<T>(0.0)); | |
| } | |
| } | |
| Neuron<T> &operator[] (unsigned int i) | |
| { | |
| return neurons[i]; | |
| } | |
| Matrix<T> ToMatrixValues() | |
| { | |
| const unsigned int neuronCount = neurons.size(); | |
| Matrix<T> m(1, neuronCount, false); | |
| for (unsigned int i = 0; i < neuronCount; ++i) { | |
| m(0, i) = neurons[i].Value(); | |
| } | |
| return m; | |
| } | |
| Matrix<T> ToMatrixActivatedValues() | |
| { | |
| const unsigned int neuronCount = neurons.size(); | |
| Matrix<T> m(1, neuronCount, false); | |
| for (unsigned int i = 0; i < neuronCount; ++i) { | |
| m(0, i) = neurons[i].ActivatedValue(); | |
| } | |
| return m; | |
| } | |
| Matrix<T> ToMatrixDerivedValues() | |
| { | |
| const unsigned int neuronCount = neurons.size(); | |
| Matrix<T> m(1, neuronCount, false); | |
| for (int i = 0; i < neuronCount; ++i) { | |
| m(0, i) = neurons[i].DerivativeValue(); | |
| } | |
| return m; | |
| } | |
| }; | |
| template <typename T> | |
| class NeuralNetwork | |
| { | |
| private: | |
| std::vector<unsigned int> topology; | |
| std::vector<double> inputs; | |
| std::vector< Layer<T> > layers; | |
| std::vector< Matrix<T> > weightMatrices; | |
| public: | |
| NeuralNetwork(const std::vector<T> &inputs) | |
| { | |
| this->Inputs(inputs); | |
| } | |
| NeuralNetwork(const std::vector<unsigned int> &topology) | |
| { | |
| const unsigned int layerCount = topology.size(); | |
| layers.reserve(layerCount); | |
| for (unsigned int i = 0; i < layerCount; ++i) { | |
| layers.push_back(Layer<T>(topology[i])); | |
| } | |
| const unsigned int weightMatricesCount = layerCount - 1; | |
| for (unsigned int i = 0; i < weightMatricesCount; ++i) { | |
| Matrix<T> w(topology[i], topology[i + 1], true); | |
| weightMatrices.push_back(w); | |
| } | |
| } | |
| unsigned int LayerCount() { return layers.size(); } | |
| void Inputs(const std::vector<T> &inputs) | |
| { | |
| this->inputs = inputs; | |
| const unsigned int inputCount = inputs.size(); | |
| for (unsigned int i = 0; i < inputCount; ++i) { | |
| Layer<T> l = layers[0]; | |
| Neuron<T> n = l[i]; | |
| n.Value(inputs[i]); | |
| } | |
| } | |
| Layer<T> &operator[] (unsigned int i) | |
| { | |
| return layers[i]; | |
| } | |
| void FeedForward() | |
| { | |
| // Loop through each layer starting from the input layer. | |
| // Take the product of the current layer and the connected weights | |
| // | |
| for (unsigned int i = 0; i < LayerCount() - 1; ++i) { | |
| Layer<T> l = layers[i]; | |
| Matrix<T> a = (i == 0) | |
| ? l.ToMatrixValues() | |
| : l.ToMatrixActivatedValues(); | |
| Matrix<T> b; | |
| } | |
| } | |
| }; | |
| template <typename T> | |
| std::ostream &operator<<(std::ostream &os, NeuralNetwork<T> &obj) | |
| { | |
| const unsigned int layerCount = obj.LayerCount(); | |
| for (unsigned int i = 0; i < layerCount; ++i) { | |
| os << "Layer: " << i << std::endl; | |
| Layer<T> layer = obj[i]; | |
| Matrix<T> m = (i == 0) | |
| ? layer.ToMatrixValues() | |
| : layer.ToMatrixActivatedValues(); | |
| os << m; | |
| } | |
| return os; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment