《神经网络与机器学习》学习笔记——第1章 感知器

时间:2022-12-25 16:40:32

这是本人学习《神经网络与机器学习》时查找各种资料的总结的笔记,为了让自己的印象更深刻并且分享些经验出来,最后有C#与MATLAB的代码实现。

如果有不对的地方欢迎指点。

这里需要有一定的数学基础,如:线性代数,微积分,矩阵,概率,统计等。

 

目录:

1.概念

2.感知器学习

3.误差修正学习算法

4.代码实现(C#与MATLAB)

 

《神经网络与机器学习》学习笔记——第1章 感知器

 

一、概念 

      感知器是生物神经细胞的简单抽象,神经细胞结构大致可分为:树突、突触、细胞体及轴突。
      单个神经细胞可被视为一种只有两种状态的机器——激动时为‘是’,而未激动时为‘否’。神经细胞的状态取决于从其它的神经细胞收到的输入信号量,及突触的强度(抑制或加强)。当信号量总和超过了某个阈值时,细胞体就会激动,产生电脉冲。电脉冲沿着轴突并通过突触传递到其它神经元。为了模拟神经细胞行为,与之对应的感知机基础概念被提出,如权值(突触)偏置(阈值)激活函数(细胞体)

《神经网络与机器学习》学习笔记——第1章 感知器

  

 1.感知器是使用特征向量来表示的前馈式人工神经网络,它是一种二元分类器,把矩阵上的输入(实数值向量)映射到输出值。由于输入直接经过权重关系转换为输出,所以感知机可以被视为最简单形式的前馈式人工神经网络。它是建立在一个非线性神经元上,即神经元的 McCulloch-Pitts 模型。

 2.神经元模型是一个线性组合器和随后的硬限幅器(激活函数)组合(如下图1.1)。

《神经网络与机器学习》学习笔记——第1章 感知器

                                      图1.1

感知器的输入值记为:《神经网络与机器学习》学习笔记——第1章 感知器权值记为:《神经网络与机器学习》学习笔记——第1章 感知器偏置值记为:b。

输入值:表示将要分类的事物的特性的组合,对应每个神经细胞接收到外部输入的刺激信号,即:初始特征

权值:表示该模型的分类变化参数,也是该模型在学习过程中需要计算出的参数

偏置值(Bias Units):它是一个不依赖于任何输入值的常数。可以认为是激活函数的偏移量,或者给神经元一个基础活跃等级。从空间上看(如:图1.2),该值的作用仅仅是把决策边界(图1.2)从原点移开。

诱导局部域:每一个输入值与其对应的权值相乘,然后将这些乘积的结果相加得到的结果,即:v 的值(PS:这概念是本人在看《神经网络与机器学习》一书的译本中看到的)。

公式1.1

《神经网络与机器学习》学习笔记——第1章 感知器

 

硬限幅器(激活函数):使用诱导局部域作为输入参数,当输入为正时,感知器输出 +1,反之则输出 -1

公式1.2

《神经网络与机器学习》学习笔记——第1章 感知器

 

3.感知器的目的是把输入值《神经网络与机器学习》学习笔记——第1章 感知器表示的点确的分为 《神经网络与机器学习》学习笔记——第1章 感知器  或者 《神经网络与机器学习》学习笔记——第1章 感知器  两类。

 

   感知器的分类的规则是:

 

   ①感知器的输出值 +1 就将《神经网络与机器学习》学习笔记——第1章 感知器表示的点分配给 《神经网络与机器学习》学习笔记——第1章 感知器  类。

 

   ②感知器的输出值 y  -1 就将《神经网络与机器学习》学习笔记——第1章 感知器表示的点分配给 《神经网络与机器学习》学习笔记——第1章 感知器  类。

 

 4.决策边界。

根据前面两点,可得出:

①当《神经网络与机器学习》学习笔记——第1章 感知器时,《神经网络与机器学习》学习笔记——第1章 感知器 属于 《神经网络与机器学习》学习笔记——第1章 感知器 类。

②当《神经网络与机器学习》学习笔记——第1章 感知器时,《神经网络与机器学习》学习笔记——第1章 感知器 属于 《神经网络与机器学习》学习笔记——第1章 感知器 类。

 所以在图1.2中,由一个超平面分开的《神经网络与机器学习》学习笔记——第1章 感知器《神经网络与机器学习》学习笔记——第1章 感知器《神经网络与机器学习》学习笔记——第1章 感知器《神经网络与机器学习》学习笔记——第1章 感知器两个区域,则决策边界超平面定义是:
公式1.3 

 《神经网络与机器学习》学习笔记——第1章 感知器

该图中决策边界是条直线,位于边界线上面的两个绿点分入《神经网络与机器学习》学习笔记——第1章 感知器,位于边界线下面的两个红点分入《神经网络与机器学习》学习笔记——第1章 感知器

《神经网络与机器学习》学习笔记——第1章 感知器                         

                             图1.2

 

5.感知器只能对线性可分的两个类进行分类。

《神经网络与机器学习》学习笔记——第1章 感知器

                                                              图1.3

二、感知器学习

 

首先,这里引用了一张在帖吧上看到的一张图片:

《神经网络与机器学习》学习笔记——第1章 感知器

                                      图1.4

     我们看到上图的汽车的时候,会记忆它的一些基本特征(比如:有4个*,车身灰白色等等),如果没有人告诉我们“车”的概念,我们虽然记下了它的大概基本特征,但却并不知道它是什么。同样,如果有人告诉我们“车”是什么,但我们却从来没有看见过车,同样对“车”一无所知。只有我们看到过车并记忆了它的一些基本特征,并且有人告诉我们,这就是“车”,当我们再次看到相似的事物的时候,我们自然就会判断,这就是“车”。

感知器也一样,给出待分类的几个点(《神经网络与机器学习》学习笔记——第1章 感知器)就如同我们人只见过“车”但却没有“车”这一概念时一样并不知道这就是“车”这一种类,感知器是无法知道它们是属于《神经网络与机器学习》学习笔记——第1章 感知器类还是《神经网络与机器学习》学习笔记——第1章 感知器类。

 

这时,我们需要让感知器正确的识别出是属于《神经网络与机器学习》学习笔记——第1章 感知器类还是《神经网络与机器学习》学习笔记——第1章 感知器类的过程,就是感知器学习(训练)的过程,而在学习(训练)过程中需要使用的已分类好的一组数据,就是训练样本,当感知器完成了学习(训练)之后,就能正确的识别与分类了。

------------------------------------------------------------------------------------------------

  示例1:

 《神经网络与机器学习》学习笔记——第1章 感知器

------------------------------------------------------------------------------------------------

三、误差修正学习算法 

      误差修正学习算法是通过多次迭代的方式来调整感知器的权值《神经网络与机器学习》学习笔记——第1章 感知器,使感知器的输出信号 的值逐渐逼近给定训练样本的输出值。

     在上面的示例1中,使用误差修正学习算法即可找到一组合适的权值(《神经网络与机器学习》学习笔记——第1章 感知器《神经网络与机器学习》学习笔记——第1章 感知器)以完成训练

设:

《神经网络与机器学习》学习笔记——第1章 感知器训练样本《神经网络与机器学习》学习笔记——第1章 感知器中,属于《神经网络与机器学习》学习笔记——第1章 感知器类的样本所组成的子集,

《神经网络与机器学习》学习笔记——第1章 感知器训练样本《神经网络与机器学习》学习笔记——第1章 感知器中,属于《神经网络与机器学习》学习笔记——第1章 感知器类的样本所组成的子集,

《神经网络与机器学习》学习笔记——第1章 感知器《神经网络与机器学习》学习笔记——第1章 感知器的并集是整个训练样本集《神经网络与机器学习》学习笔记——第1章 感知器n 是训练样本个数(迭代次数),给定训练样本集《神经网络与机器学习》学习笔记——第1章 感知器《神经网络与机器学习》学习笔记——第1章 感知器来训练感知器,训练过程中对权值向量 《神经网络与机器学习》学习笔记——第1章 感知器 的调整使两个类《神经网络与机器学习》学习笔记——第1章 感知器《神经网络与机器学习》学习笔记——第1章 感知器线性可分

即:

《神经网络与机器学习》学习笔记——第1章 感知器

也就是说,存在一个权值 《神经网络与机器学习》学习笔记——第1章 感知器 具有以下性质:

公式1.4

《神经网络与机器学习》学习笔记——第1章 感知器

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

补充说明:

示例1中,训练样本《神经网络与机器学习》学习笔记——第1章 感知器是坐标轴上的一个,因此该点的初始特征就是坐标《神经网络与机器学习》学习笔记——第1章 感知器,因此,输入值就有两个,所以对应的权值《神经网络与机器学习》学习笔记——第1章 感知器也有两个,所以使用矩阵的形式来表示公式1.1,则是:《神经网络与机器学习》学习笔记——第1章 感知器

所以,感知器的训练问题就是找到一组权值向量 《神经网络与机器学习》学习笔记——第1章 感知器 满足公式1.4中的两个不等式。

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

使感知器的权值自适应的算法现在可以用以下公式来描述

1.假设训练集合的第《神经网络与机器学习》学习笔记——第1章 感知器 n 个成员 x(n) 《神经网络与机器学习》学习笔记——第1章 感知器根据算法中的第《神经网络与机器学习》学习笔记——第1章 感知器《神经网络与机器学习》学习笔记——第1章 感知器 n 次迭代的权值向量 w(n) 《神经网络与机器学习》学习笔记——第1章 感知器能正确分类,那么感知器的权值向量按下述规则不做修改: 

公式1.5

《神经网络与机器学习》学习笔记——第1章 感知器
 
2.否则,感知器的权值向量根据以下规则进行修改:
 
公式1.6
《神经网络与机器学习》学习笔记——第1章 感知器
 
这里的学习率《神经网络与机器学习》学习笔记——第1章 感知器《神经网络与机器学习》学习笔记——第1章 感知器控制着第《神经网络与机器学习》学习笔记——第1章 感知器《神经网络与机器学习》学习笔记——第1章 感知器《神经网络与机器学习》学习笔记——第1章 感知器 n 次迭代中作用于权值向量的调节,即:控制权值修改的幅度,这里的学习率只要是正数,具体值并不重要。 
 
3.引入一个期望值《神经网络与机器学习》学习笔记——第1章 感知器《神经网络与机器学习》学习笔记——第1章 感知器,定义为: 
 
  《神经网络与机器学习》学习笔记——第1章 感知器
 
激活函数使用sgn(·)《神经网络与机器学习》学习笔记——第1章 感知器来表示,并根据公式1.2公式1.4得到: 
 
《神经网络与机器学习》学习笔记——第1章 感知器
 
公式1.6变成:
 
 公式1.7
《神经网络与机器学习》学习笔记——第1章 感知器
 
 其中,《神经网络与机器学习》学习笔记——第1章 感知器《神经网络与机器学习》学习笔记——第1章 感知器表示期望输出值与实际输出值的误差,《神经网络与机器学习》学习笔记——第1章 感知器η 是学习率,取值范围是《神经网络与机器学习》学习笔记——第1章 感知器《神经网络与机器学习》学习笔记——第1章 感知器。 
 

四、代码实现 

1.C# 实现
 
为了方便开发,首先,是矩阵类的实现:
 
Matrix.cs
《神经网络与机器学习》学习笔记——第1章 感知器《神经网络与机器学习》学习笔记——第1章 感知器
    /// <summary>
    /// 矩阵。
    /// </summary>
    public class Matrix
    {
        #region 字段

        /// <summary>
        /// 矩阵数据。
        /// </summary>
        private readonly double[][] _data;

        #endregion 字段

        #region 初始化

        /// <summary>
        /// 初始化。
        /// </summary>
        /// <param name="row">行=列。</param>
        public Matrix(int row) : this(row, row)
        {
        }

        /// <summary>
        /// 初始化。
        /// </summary>
        /// <param name="row">行。</param>
        /// <param name="column">列。</param>
        public Matrix(int row, int column)
        {
            Row = row;
            Column = column;
            _data = new double[row][];

            for (var index = 0; index < row; index++)
            {
                _data[index] = new double[column];
            }
        }

        /// <summary>
        /// 初始化。
        /// </summary>
        /// <param name="row">行。</param>
        /// <param name="column">列。</param>
        /// <param name="value">初始值。</param>
        public Matrix(int row, int column, double value) : this(row, column, index => value)
        {
        }

        /// <summary>
        /// 初始化。
        /// </summary>
        /// <param name="row">行。</param>
        /// <param name="column">列。</param>
        /// <param name="func">初始值计算方法。</param>
        public Matrix(int row, int column, Func<int, double> func)
        {
            Row = row;
            Column = column;
            _data = new double[row][];

            for (var index = 0; index < row; index++)
            {
                _data[index] = new double[column];

                if (func != null)
                {
                    for (var loop = 0; loop < column; loop++)
                    {
                        _data[index][loop] = func(loop);
                    }
                }
            }
        }

        /// <summary>
        /// 初始化。
        /// </summary>
        /// <param name="matrix">矩阵。</param>
        public Matrix(double[][] matrix)
        {
            if (matrix == null || matrix.Length <= 0)
                throw new ArgumentNullException(nameof(matrix));

            Row = matrix.Length;
            Column = matrix[0].Length;
            _data = matrix;
        }

        /// <summary>
        /// 复制矩阵。
        /// </summary>
        /// <param name="matrix">矩阵。</param>
        public Matrix(Matrix matrix)
        {
            var row = matrix.Row;
            var column = matrix.Column;
            Row = row;
            Column = column;
            _data = new double[row][];

            for (var rowNumber = 0; rowNumber < row; rowNumber++)
            {
                _data[rowNumber] = new double[column];
                for (var columnNumber = 0; columnNumber < column; columnNumber++)
                {
                    this[rowNumber, columnNumber] = matrix[rowNumber, columnNumber];
                }
            }
        }

        /// <summary>
        /// 生成均匀分布在(0-1)之间伪随机数的矩阵。
        /// </summary>
        /// <param name="row">行。</param>
        /// <param name="column">列。</param>
        /// <returns>矩阵。</returns>
        public static Matrix CreateRandMatrix(int row, int column)
        {
            return new Matrix(row, column, index => RandomToolit.GetRandom().NextDouble());
        }

        #endregion 初始化

        #region 属性

        /// <summary>
        /// 返中行数。
        /// </summary>
        public int Row { get; }

        /// <summary>
        /// 返回列数。
        /// </summary>
        public int Column { get; }

        public double this[int row, int column]
        {
            get
            {
                return _data[row][column];
            }
            set
            {
                _data[row][column] = value;
            }
        }

        #endregion 属性

        #region 操作符

        /// <summary>
        /// 矩阵加法 binary addition。
        /// </summary>
        /// <param name="lhs"></param>
        /// <param name="rhs"></param>
        /// <returns></returns>
        public static Matrix operator +(Matrix lhs, Matrix rhs)
        {
            if (lhs.Row != rhs.Row)
            {
                throw new Exception("相加的两个矩阵的行数不等");
            }

            if (lhs.Column != rhs.Column)
            {
                throw new Exception("相加的两个矩阵的列数不等");
            }

            var row = lhs.Row;
            var column = lhs.Column;
            var ret = new Matrix(row, column);

            for (var index = 0; index < row; index++)
            {
                for (var loop = 0; loop < column; loop++)
                {
                    var value = lhs[index, loop] + rhs[index, loop];

                    ret[index, loop] = value;
                }
            }

            return ret;
        }

        /// <summary>
        /// 矩阵减法 binary subtraction。
        /// </summary>
        /// <param name="lhs"></param>
        /// <param name="rhs"></param>
        /// <returns></returns>
        public static Matrix operator -(Matrix lhs, Matrix rhs)
        {
            if (lhs.Row != rhs.Row)
            {
                throw new Exception("相减的两个矩阵的行数不等");
            }
            if (lhs.Column != rhs.Column)
            {
                throw new Exception("相减的两个矩阵的列数不等");
            }

            var row = lhs.Row;
            var column = lhs.Column;
            var ret = new Matrix(row, column);

            for (var index = 0; index < row; index++)
            {
                for (var loop = 0; loop < column; loop++)
                {
                    var value = lhs[index, loop] - rhs[index, loop];
                    ret[index, loop] = value;
                }
            }

            return ret;
        }

        /// <summary>
        /// 矩阵乘法(乘积)。
        /// </summary>
        /// <remarks>
        /// 当矩阵A的列数等于矩阵B的行数时,A与B可以相乘。
        /// 1.矩阵C的行数等于矩阵A的行数,C的列数等于B的列数。
        /// 2.乘积C的第m行第n列的元素等于矩阵A的第m行的元素与矩阵B的第n列对应元素乘积之和。
        /// </remarks>
        /// <param name="lhs"></param>
        /// <param name="rhs"></param>
        /// <returns></returns>
        public static Matrix operator *(Matrix lhs, Matrix rhs)
        {
            if (lhs.Column != rhs.Row)
            {
                throw new Exception("相乘的两个矩阵的行列数不匹配");
            }

            var ret = new Matrix(lhs.Row, rhs.Column);

            for (var index = 0; index < lhs.Row; index++)
            {
                for (var loop = 0; loop < rhs.Column; loop++)
                {
                    double temp = 0;

                    for (var k = 0; k < lhs.Column; k++)
                    {
                        temp += lhs[index, k] * rhs[k, loop];
                    }

                    ret[index, loop] = temp;
                }
            }

            return ret;
        }

        /// <summary>
        /// 矩阵除法 binary division。
        /// </summary>
        /// <param name="lhs"></param>
        /// <param name="rhs"></param>
        /// <returns></returns>
        public static Matrix operator /(Matrix lhs, Matrix rhs)
        {
            return lhs * rhs.Inverse();
        }

        /// <summary>
        /// 单目加 unary addition。
        /// </summary>
        /// <param name="sourceMatrix"></param>
        /// <returns></returns>
        public static Matrix operator +(Matrix sourceMatrix)
        {
            var returnMatrix = new Matrix(sourceMatrix);

            for (var rowNumber = 0; rowNumber < returnMatrix.Row; rowNumber++)
            {
                for (var columnNumber = 0; columnNumber < returnMatrix.Column; columnNumber++)
                {
                    returnMatrix[rowNumber, columnNumber] = +returnMatrix[rowNumber, columnNumber];
                }
            }

            return returnMatrix;
        }

        /// <summary>
        /// 单目减 unary subtraction。
        /// </summary>
        /// <param name="sourceMatrix"></param>
        /// <returns></returns>
        public static Matrix operator -(Matrix sourceMatrix)
        {
            var returnMatrix = new Matrix(sourceMatrix);

            for (var rowNumber = 0; rowNumber < returnMatrix.Row; rowNumber++)
            {
                for (var columnNumber = 0; columnNumber < returnMatrix.Column; columnNumber++)
                {
                    returnMatrix[rowNumber, columnNumber] = -returnMatrix[rowNumber, columnNumber];
                }
            }

            return returnMatrix;
        }

        /// <summary>
        /// 矩阵加法。
        /// </summary>
        /// <param name="m"></param>
        /// <param name="d"></param>
        /// <returns></returns>
        public static Matrix operator +(Matrix m, double d)
        {
            var ret = new Matrix(m);
            for (var rowNumber = 0; rowNumber < ret.Row; rowNumber++)
                for (var columnNumber = 0; columnNumber < ret.Column; columnNumber++)
                    ret[rowNumber, columnNumber] += d;

            return ret;
        }

        /// <summary>
        /// 矩阵加法。
        /// </summary>
        /// <param name="d"></param>
        /// <param name="m"></param>
        /// <returns></returns>
        public static Matrix operator +(double d, Matrix m)
        {
            var ret = new Matrix(m);
            for (var i = 0; i < ret.Row; i++)
                for (var j = 0; j < ret.Column; j++)
                    ret[i, j] += d;

            return ret;
        }

        /// <summary>
        /// 矩阵减法。
        /// </summary>
        /// <param name="sourceMatrix"></param>
        /// <param name="d"></param>
        /// <returns></returns>
        public static Matrix operator -(Matrix sourceMatrix, double d)
        {
            var targetMatrix = new Matrix(sourceMatrix);
            for (var rowNumber = 0; rowNumber < targetMatrix.Row; rowNumber++)
                for (var columnNumber = 0; columnNumber < targetMatrix.Column; columnNumber++)
                    targetMatrix[rowNumber, columnNumber] -= d;

            return targetMatrix;
        }

        /// <summary>
        /// 矩阵减法。
        /// </summary>
        /// <param name="d"></param>
        /// <param name="sourceMatrix"></param>
        /// <returns></returns>
        public static Matrix operator -(double d, Matrix sourceMatrix)
        {
            var targetMatrix = new Matrix(sourceMatrix);
            for (var rowNumber = 0; rowNumber < targetMatrix.Row; rowNumber++)
                for (var columnNumber = 0; columnNumber < targetMatrix.Column; columnNumber++)
                    targetMatrix[rowNumber, columnNumber] = d - targetMatrix[rowNumber, columnNumber];

            return targetMatrix;
        }

        /// <summary>
        /// 数乘 number multiple。
        /// </summary>
        /// <param name="m"></param>
        /// <param name="d"></param>
        /// <returns></returns>
        public static Matrix operator *(Matrix m, double d)
        {
            var ret = new Matrix(m);
            for (var rowNumber = 0; rowNumber < ret.Row; rowNumber++)
                for (var columnNumber = 0; columnNumber < ret.Column; columnNumber++)
                    ret[rowNumber, columnNumber] *= d;

            return ret;
        }

        /// <summary>
        /// 数乘 number multiple。
        /// </summary>
        /// <param name="d"></param>
        /// <param name="m"></param>
        /// <returns></returns>
        public static Matrix operator *(double d, Matrix m)
        {
            var ret = new Matrix(m);
            for (var i = 0; i < ret.Row; i++)
                for (var j = 0; j < ret.Column; j++)
                    ret[i, j] *= d;

            return ret;
        }

        /// <summary>
        /// 数除 number division。
        /// </summary>
        /// <param name="sourceMatrix"></param>
        /// <param name="d"></param>
        /// <returns></returns>
        public static Matrix operator /(Matrix sourceMatrix, double d)
        {
            var targetMatrix = new Matrix(sourceMatrix);
            for (var rowNumber = 0; rowNumber < targetMatrix.Row; rowNumber++)
                for (var columnNumber = 0; columnNumber < targetMatrix.Column; columnNumber++)
                    targetMatrix[rowNumber, columnNumber] /= d;

            return targetMatrix;
        }

        /// <summary>
        /// 数除 number division。
        /// </summary>
        /// <param name="d"></param>
        /// <param name="sourceMatrix"></param>
        /// <returns></returns>
        public static Matrix operator /(double d, Matrix sourceMatrix)
        {
            var targetMatrix = new Matrix(sourceMatrix);
            for (var rowNumber = 0; rowNumber < targetMatrix.Row; rowNumber++)
                for (var columnNumber = 0; columnNumber < targetMatrix.Column; columnNumber++)
                    targetMatrix[rowNumber, columnNumber] = d / targetMatrix[rowNumber, columnNumber];

            return targetMatrix;
        }

        #endregion 操作符

        #region 矩阵乘法(点积)

        /// <summary>
        /// 矩阵乘法(点积)。
        /// </summary>
        /// <param name="target"></param>
        /// <returns></returns>
        public Matrix DotMultiple(Matrix target)
        {
            if (target == null)
                throw new ArgumentNullException(nameof(target));

            if (target.Row != Row || target.Column != Column)
                throw new ArgumentException("矩阵维度不一致");

            var ret = new Matrix(target);
            for (var rowNumber = 0; rowNumber < ret.Row; rowNumber++)
                for (var columnNumber = 0; columnNumber < ret.Column; columnNumber++)
                    ret[rowNumber, columnNumber] = this[rowNumber, columnNumber] * target[rowNumber, columnNumber];

            return ret;
        }

        #endregion 矩阵乘法(点积)

        #region 初等变换

        /// <summary>
        /// 初等变换 primary change。
        /// </summary>
        /// <remarks>
        /// 对调两行:ri←→rj。
        /// </remarks>
        /// <param name="row"></param>
        /// <param name="column"></param>
        /// <returns></returns>
        public Matrix Exchange(int row, int column)
        {
            for (var index = 0; index < Column; index++)
            {
                var temp = this[row, index];
                this[row, index] = this[column, index];
                this[column, index] = temp;
            }

            return this;
        }

        /// <summary>
        /// 初等变换,第 index 行乘以 mul。
        /// </summary>
        /// <param name="index">行号。</param>
        /// <param name="mul"></param>
        /// <returns></returns>
        private Matrix Multiple(int index, double mul)
        {
            for (var loop = 0; loop < Column; loop++)
            {
                this[index, loop] *= mul;
            }

            return this;
        }

        /// <summary>
        /// 初等变换,第 src 行乘以 mul 加到第 index 行。
        /// </summary>
        /// <param name="index"></param>
        /// <param name="src"></param>
        /// <param name="mul"></param>
        /// <returns></returns>
        private Matrix MultipleAdd(int index, int src, double mul)
        {
            for (var loop = 0; loop < Column; loop++)
            {
                this[index, loop] += this[src, loop] * mul;
            }

            return this;
        }

        #endregion 初等变换

        #region unit matrix:设为单位阵

        /// <summary>
        /// unit matrix:设为单位阵。
        /// </summary>
        public void SetUnit()
        {
            for (int i = 0; i < _data.GetLength(0); i++)
                for (int j = 0; j < _data.GetLength(1); j++)
                    this[i, j] = ((i == j) ? 1 : 0);
        }

        #endregion unit matrix:设为单位阵

        #region 逆阵 inversion:使用矩阵的初等变换,列主元素消去法

        /// <summary>
        /// 逆阵 inversion:使用矩阵的初等变换,列主元素消去法。
        /// </summary>
        /// <returns></returns>
        public Matrix Inverse()
        {
            if (Row != Column)
            {
                throw new Exception("求逆的矩阵不是方阵");
            }

            var tmp = new Matrix(this);

            //单位阵
            var ret = new Matrix(Row);
            ret.SetUnit();

            double dMul;

            for (int i = 0; i < Row; i++)
            {
                var maxIndex = tmp.Pivot(i);

                if (tmp[maxIndex, i] == 0)
                {
                    throw new Exception("求逆的矩阵的行列式的值等于0,");
                }

                //下三角阵中此列的最大值不在当前行,交换
                if (maxIndex != i)
                {
                    tmp.Exchange(i, maxIndex);
                    ret.Exchange(i, maxIndex);
                }

                ret.Multiple(i, 1 / tmp[i, i]);
                tmp.Multiple(i, 1 / tmp[i, i]);

                for (int j = i + 1; j < Row; j++)
                {
                    dMul = -tmp[j, i] / tmp[i, i];
                    tmp.MultipleAdd(j, i, dMul);
                }
            }

            for (int i = Row - 1; i > 0; i--)
            {
                for (int j = i - 1; j >= 0; j--)
                {
                    dMul = -tmp[j, i] / tmp[i, i];
                    tmp.MultipleAdd(j, i, dMul);
                    ret.MultipleAdd(j, i, dMul);
                }
            }

            return ret;
        }

        #endregion 逆阵 inversion:使用矩阵的初等变换,列主元素消去法

        #region 转置 transpose

        /// <summary>
        /// 转置 transpose。
        /// </summary>
        /// <returns>矩阵。</returns>
        public Matrix Transpose()
        {
            var ret = new Matrix(Column, Row);

            for (var index = 0; index < Row; index++)
            {
                for (var loop = 0; loop < Column; loop++)
                {
                    ret[loop, index] = this[index, loop];
                }
            }

            return ret;
        }

        #endregion 转置 transpose

        #region 复制和平铺矩阵

        /// <summary>
        /// 复制和平铺矩阵。
        /// </summary>
        /// <remarks>
        /// repmat = Replicate Matrix。
        /// 示例:
        /// B = A.Replicate(m,n),就是创建一个矩阵 B,B 中复制了共 m * n 个 A 矩阵,因此 B 矩阵的大小为 [A.Row * m, A.Col * m]。
        /// 即:A 是一个 5 行 3 列的矩阵,则 B 为 5 * m 行 3 * n 列的矩阵。
        /// </remarks>
        /// <param name="row">行数。</param>
        /// <param name="column">列数。</param>
        /// <returns>矩阵。</returns>
        public Matrix Repmat(int row = 1, int column = 1)
        {
            if (row <= 0)
                throw new ArgumentException("矩阵行数必须大于 0");

            if (column <= 0)
                throw new ArgumentException("矩阵行数必须大于 0");

            var matrix = new Matrix(Row * row, Column * column);

            for (var index = 0; index < matrix.Row; index++)
            {
                for (var loop = 0; loop < matrix.Column; loop++)
                {
                    matrix[index, loop] = this[index % Row, loop % Column];
                }
            }

            return matrix;
        }

        #endregion 复制和平铺矩阵

        #region 调整矩阵的行数、列数和维数

        /// <summary>
        /// 调整矩阵的行数、列数和维数。
        /// </summary>
        /// <param name="row">新矩阵的行数。</param>
        /// <param name="column">新矩阵的列数。</param>
        /// <returns>矩阵。</returns>
        public Matrix Reshape(int row, int column)
        {
            return Reshape(this, row, column);
        }

        /// <summary>
        /// 调整矩阵的行数、列数和维数。
        /// </summary>
        /// <param name="source">源矩阵。</param>
        /// <param name="row">新矩阵的行数。</param>
        /// <param name="column">新矩阵的列数。</param>
        /// <returns>矩阵。</returns>
        public static Matrix Reshape(Matrix source, int row, int column)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));

            if (source.Row * source.Column != row * column)
                throw new ArgumentException("元素的个数(row * column)必须是源矩阵一致");

            var data = new Matrix(row, column);
            var newColumnNumber = 0;
            var newRowNumber = 0;

            for (var sourceColumnNumber = 0; sourceColumnNumber < source.Column; sourceColumnNumber++)
            {
                for (var sourceRowNumber = 0; sourceRowNumber < source.Row; sourceRowNumber++)
                {
                    data[newRowNumber, newColumnNumber] = source[sourceRowNumber, sourceColumnNumber];

                    newRowNumber++;

                    if (newRowNumber >= row)
                    {
                        newRowNumber = 0;
                        newColumnNumber++;
                    }
                }
            }

            return data;
        }

        #endregion 调整矩阵的行数、列数和维数

        #region 返回列主元素的行号

        /// <summary>
        /// 返回列主元素的行号。
        /// </summary>
        /// <remarks>
        /// 在行号 [row, Col) 范围内查找第 row 列中绝对值最大的元素,返回所在行号。
        /// </remarks>
        /// <param name="row">开始查找的行号。</param>
        /// <returns>行号。</returns>
        private int Pivot(int row)
        {
            int index = row;

            for (int i = row + 1; i < Row; i++)
            {
                if (this[i, row] > this[index, row])
                    index = i;
            }

            return index;
        }

        #endregion 返回列主元素的行号

        #region 获得指定行的数据

        /// <summary>
        /// 获得指定行的数据。
        /// </summary>
        /// <param name="rowNumber">行号。</param>
        /// <returns>行数据。</returns>
        public double[] GetRow(int rowNumber)
        {
            if (rowNumber < 0 || rowNumber >= Row)
                throw new ArgumentException("行号不正确", nameof(rowNumber));

            return _data[rowNumber];
        }

        #endregion 获得指定行的数据

        #region 获得指定列的数据

        /// <summary>
        /// 获得指定列的数据。
        /// </summary>
        /// <param name="columnNumber">列号。</param>
        /// <returns>列数据。</returns>
        public double[] GetColumn(int columnNumber)
        {
            if (columnNumber < 0 || columnNumber >= Column)
                throw new ArgumentException("列号不正确", nameof(columnNumber));

            var column = new double[Row];

            for (var rowNumber = 0; rowNumber < Row; rowNumber++)
            {
                column[rowNumber] = _data[rowNumber][columnNumber];
            }

            return column;
        }

        #endregion 获得指定列的数据

        #region 转换字符串

        /// <summary>
        /// 转换字符串。
        /// </summary>
        /// <returns>矩阵字符串。</returns>
        public override string ToString()
        {
            var maxLength = 0;

            for (var rowNumber = 0; rowNumber < Row; rowNumber++)
            {
                for (var columnNumber = 0; columnNumber < Column; columnNumber++)
                {
                    var length = this[rowNumber, columnNumber].ToString(CultureInfo.InvariantCulture).Length;
                    if (length > maxLength)
                    {
                        maxLength = length;
                    }
                }
            }

            maxLength += 3;

            var printBuilder = new StringBuilder();

            for (var rowNumber = 0; rowNumber < Row; rowNumber++)
            {
                if (rowNumber > 0)
                {
                    printBuilder.AppendLine();
                }

                for (var columnNumber = 0; columnNumber < Column; columnNumber++)
                {
                    printBuilder.Append(this[rowNumber, columnNumber].ToString(CultureInfo.InvariantCulture).PadRight(maxLength));
                }
            }

            return printBuilder.ToString();
        }

        #endregion 转换字符串
    }
View Code

 

MathExtensions.cs

《神经网络与机器学习》学习笔记——第1章 感知器《神经网络与机器学习》学习笔记——第1章 感知器
    /// <summary>
    /// 扩展出矩阵相关的数学函数。
    /// </summary>
    public static class MathExtensions
    {
        /// <summary>
        /// 矩阵求和。
        /// </summary>
        /// <param name="source">矩阵。</param>
        /// <returns>和。</returns>
        public static double Sum(this Matrix source)
        {
            double sum = 0;
            for (var rowNumber = 0; rowNumber < source.Row; rowNumber++)
                for (var columnNumber = 0; columnNumber < source.Column; columnNumber++)
                    sum += source[rowNumber, columnNumber];

            return sum;
        }

        /// <summary>
        /// 矩阵列求和。
        /// </summary>
        /// <param name="source">矩阵。</param>
        /// <returns>和。</returns>
        public static Matrix SumColumn(this Matrix source)
        {
            var ret = new Matrix(1, source.Column);
            for (var rowNumber = 0; rowNumber < source.Row; rowNumber++)
                for (var columnNumber = 0; columnNumber < source.Column; columnNumber++)
                    ret[0, columnNumber] += source[rowNumber, columnNumber];

            return ret;
        }

        /// <summary>
        /// 矩阵行求和。
        /// </summary>
        /// <param name="source">矩阵。</param>
        /// <returns>和。</returns>
        public static Matrix SumRow(this Matrix source)
        {
            var ret = new Matrix(source.Row, 1);
            for (var rowNumber = 0; rowNumber < source.Row; rowNumber++)
                for (var columnNumber = 0; columnNumber < source.Column; columnNumber++)
                    ret[rowNumber, 0] += source[rowNumber, columnNumber];

            return ret;
        }

        /// <summary>
        /// 对矩阵中的每个元素都进行 Math.Log(x) 运算。
        /// </summary>
        /// <remarks>
        /// 返回指定数字的自然对数(底为 e)。
        /// </remarks>
        /// <param name="source">矩阵。</param>
        /// <returns>结果。</returns>
        public static Matrix Log(this Matrix source)
        {
            return source.Function(Math.Log);
        }

        /// <summary>
        /// 对矩阵中的每个元素都进行 Math.Exp(x) 运算。
        /// </summary>
        /// <remarks>
        /// 即: e 的指定次幂。
        /// </remarks>
        /// <param name="source">矩阵。</param>
        /// <param name="digits">返回的小数数字(即:该值是 4 的时候,表示保留 4 位小数)。</param>
        /// <returns>结果。</returns>
        public static Matrix Exp(this Matrix source, int digits = 0)
        {
            if (digits > 0)
            {
                return source.Function(item => Math.Round(Math.Exp(item), digits));
            }

            return source.Function(Math.Exp);
        }

        /// <summary>
        /// 对矩阵中的每个元素都进行 Math.Pow(y) 运算。
        /// </summary>
        /// <remarks>
        /// 即: 指定数字的指定次幂。
        /// </remarks>
        /// <param name="source">矩阵。</param>
        /// <param name="y">指定次幂。</param>
        /// <param name="digits">返回的小数数字(即:该值是 4 的时候,表示保留 4 位小数)。</param>
        /// <returns>结果。</returns>
        public static Matrix Pow(this Matrix source, int y, int digits = 0)
        {
            if (digits > 0)
            {
                return source.Function(x => Math.Round(Math.Pow(x, y)));
            }

            return source.Function(x => Math.Pow(x, y));
        }

        /// <summary>
        /// 将双精度浮点值按指定的小数位数舍入。
        /// </summary>
        /// <param name="source">矩阵。</param>
        /// <param name="digits">返回值中的小数数字。</param>
        /// <returns>最接近 value 的 digits 位小数的数字矩阵。</returns>
        public static Matrix Round(this Matrix source, int digits)
        {
            return source.Function(item => Math.Round(item, digits));
        }

        /// <summary>
        /// 对矩阵中的每个元素进行计算。
        /// </summary>
        /// <param name="source">矩阵。</param>
        /// <param name="func">计算方法。</param>
        /// <returns>结果。</returns>
        public static Matrix Function(this Matrix source, Func<double, double> func)
        {
            var ret = new Matrix(source.Row, source.Column);
            for (var rowNumber = 0; rowNumber < source.Row; rowNumber++)
                for (var columnNumber = 0; columnNumber < source.Column; columnNumber++)
                    ret[rowNumber, columnNumber] = func(source[rowNumber, columnNumber]);

            return ret;
        }
    }
View Code

感知器的实现:

注意,在初始化权值(权重)的时候,可以是任意的一个初始值。

Perceptron.cs

《神经网络与机器学习》学习笔记——第1章 感知器《神经网络与机器学习》学习笔记——第1章 感知器
    /// <summary>
    /// 感知器。
    /// </summary>
    public class Perceptron
    {
        /// <summary>
        /// 输入参数个数。
        /// </summary>
        private readonly int _inputCount;

        /// <summary>
        /// 权重矩阵。
        /// </summary>
        private Matrix _hiddenWeights;

        /// <summary>
        /// 初始化感知器。
        /// </summary>
        /// <param name="inputCount">输入参数个数。</param>
        public Perceptron(int inputCount)
        {
            _inputCount = inputCount;

            //生成输入参数对应的权重。
            _hiddenWeights = new Matrix(inputCount, 1, index => -2);
        }

        /// <summary>
        /// 学习率η。
        /// </summary>
        public double Eta => 1;

        /// <summary>
        /// 训练。
        /// </summary>
        /// <param name="sample">样本数据。</param>
        /// <param name="classType">分类。</param>
        public void Epoch(double[] sample, int classType)
        {
            if (sample == null || sample.Length <= 0 || sample.Length != _inputCount)
                throw new ArgumentException("样本数据不正确");

            var matrixSample = new Matrix(1, sample.Length, index => sample[index]);

            //输出值 y。
            var y = Hander(matrixSample);

            //权重修正。
            //w(n + 1) = w(n) + η[d(n) - y(n)]x(n)
            _hiddenWeights = _hiddenWeights + (Eta * (classType - y) * matrixSample).Transpose();
        }

        #region 计算

        /// <summary>
        /// 真实计算结果。
        /// </summary>
        /// <param name="properties">输入参数。</param>
        /// <returns>结果。</returns>
        public double Compute(double[] properties)
        {
            if (properties.Length != _inputCount)
                throw new ArgumentException("输入参数个数不正确");

            var matrixProperties = new Matrix(1, properties.Length, index => properties[index]);

            return Hander(matrixProperties);
        }

        #endregion 计算

        #region 神经网络处理

        public double Hander(Matrix properties)
        {
            //诱导局部域 v,_hiddenWeights 矩阵是 1 列,matrixSample 矩阵是 1 行。
            //v = x1 * w1+ x2 * w2+···+xm * wm
            var v = (_hiddenWeights * properties + 1).Sum();

            //输出值 y。
            var y = 1.0;

            if (v <= 0)
            {
                y = -1.0;
            }

            return y;
        }

        #endregion 神经网络处理
    }
View Code

 

使用示例:

《神经网络与机器学习》学习笔记——第1章 感知器《神经网络与机器学习》学习笔记——第1章 感知器
    public class PerceptronTest
    {
        public static void Execute()
        {
            //R1 类的训练样本。
            var sample1 = new Matrix(new[] { new[] { 1.0, 2 }, new[] { 2.0, 3 }, new[] { 3.0, 4 } });
            //R2 类的训练样本。
            var sample2 = new Matrix(new[] { new[] { -1.0, -2 }, new[] { -2.0, -3 }, new[] { -3.0, -4 } });

            //定义感知器。
            var perceptron = new Perceptron(2);

            //开始训练。
            for (var rowNumber = 0; rowNumber < sample1.Row; rowNumber++)
            {
                //这里的输入值是点的坐标,输出值根据公式,当为 R1 类的时候,输出的期望值是 1。
                perceptron.Epoch(sample1.GetRow(rowNumber), 1);
            }

            for (var rowNumber = 0; rowNumber < sample2.Row; rowNumber++)
            {
                //这里的输入值是点的坐标,输出值根据公式,当为 R2 类的时候,输出的期望值是 -1。
                perceptron.Epoch(sample2.GetRow(rowNumber), -1);
            }

            /*
            1.当权值不在改变的时候,就表示训练已完成。
            2.当训练完成后,就可以输入任意一个点,自动判断出它是属于哪一类了。
            */
            var output = perceptron.Compute(new[] { 3.0, 2 });
            Console.WriteLine($"点 (3, 2) 是属于 {(output > 0 ? "R1" : "R2")} 类。");
            output = perceptron.Compute(new[] { -1.0, -3 });
            Console.WriteLine($"点 (-1, -3) 是属于 {(output > 0 ? "R1" : "R2")} 类。");
        }
    }
View Code

输出结果:

《神经网络与机器学习》学习笔记——第1章 感知器

 

2.MATLAB 实现

训练函数 Epoch.m

function [output, weights] = Epoch(weights, inputs, target_output, eta)
v = sum(sum(weights * inputs + 1));
y = 1.0;
if v <= 0
    y = -1.0;
end
weights = weights + (eta * (target_output - y) * inputs)';
output = y;
end

 

计算函数 Compute.m

function [ output ] = Compute(inputs, weights)
v = sum(sum(weights * inputs + 1));
y = 1.0;
if v <= 0
    y = -1.0;
end
output = y;
end

使用示例 Execute.m

%% Example Title
% 初始化
sample1 = [1,2;2,3;3,4]
sample2 = [-1,-2;-2,-3;-3,-4]
eta = 1
R1 = 1
R2 = -1
weights = [-2;-2]
%% Section 1 Title
% 训练
for index = 1:3
    input_sample1 = sample1(index,:);
    input_sample2 = sample2(index,:);
    [output, weights] = Epoch(weights, input_sample1, R1, eta);
    [output, weights] = Epoch(weights, input_sample2, R2, eta);
end
%% Section 2 Title
% 执行计算
a=[1,3];
output = Compute(a, weights)

输出结果:

R1

《神经网络与机器学习》学习笔记——第1章 感知器

 R2

《神经网络与机器学习》学习笔记——第1章 感知器

 

 

 参考资料:

《神经网络与机器学习》第三版译本

机器学习入门——浅谈神经网络

 UFLDL教程