因为代码需要在C#环境跑起来,所以先用Java来进行编写测试。
感知器
单纯的感知器其实就是一个决策程序(多输入,单输出),即给出多个输入,根据每个输入的权值,进行相乘,最后结果相加,最后给出的阈值相比较,而输出1 or -1(在神经网络称之为激活 或 抑制),所以感知器一般是用来判断是与不是。
举个例子,判断一个数字是偶数嘛(你会说,这个程序这么简单,也叫感知器。。。)?那如果判断这个数能不能被另一个数整除呢?而这个程序也相应的可以解决判断偶数问题,然而却多了一个输入,在感知器中,减少输入还是很关键的。
但是这里就会想到,那之前说的不是根据权值相乘累加判断嘛?判断整除偶数怎么能算感知器呢?这里我是这样认为的,所谓感知器,不仅仅是权值相乘累加,而是一种判断,就像一个人,去判断你面前的这个字是黑色的,答案为是或者不是,而权值计算,我认为是对数字图像的一种抽象表现,即易理解、易抽象,这里的话,我们完全没有必要非要按照这个来,只需要理解感知器为激活或者抑制即可(具体实现可以自己实现,没必要非要死搬硬照)。
这里的权值计算,是为了让训练感知器而准备的,就拿上面的判断偶数来解释,我们知道,我们学习感知器,就是为了机器学习,那偶数判断,有何学习的余地?我只能说,这个感知器已经训练成熟,完全不需要进行训练了- -所以省去了权值计算的步骤。
等会我会写一个权值计算判断偶数的感知器,并进行训练,大家可以看看,完全没有必要,结果因训练集的不同而产生了随机性。
但是世间万物的规律不是那么好追寻的,所以我们只能进行已有数据进行训练。
训练感知器
训练时,分为二种情况,即训练集线性可分、不可分。
训练集线性可分
先讨论线性可分时的情况。
就拿西瓜书第一章的例子,西瓜的好坏。
NumMath.java
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package util;
/**
*
* @author lol
*/
public class NumMath {
public static double sum(double[]...values) {
int length = values.length;
if (length == 0) {
return 0;
} else if (length == 1) {
double sum = 0;
for (double v : values[0]) {
sum += v;
}
return sum;
}
int len = values[0].length;
for (int i = 0; i < length; i++) {
len = Math.min(len, values[i].length);
}
double[] array = new double[len];
for (int j = 0; j < length; j++) {
for (int i = 0; i < len; i++) {
if (j != 0) {
array[i] *= values[j][i];
} else {
array[i] = values[j][i];
}
}
}
return sum(array);
}
private static void testOfSum() {
System.out.print("Run testOfSum: ");
double[] w = {1, 2, 3};
double[] v = {1, 0, 1};
System.out.println(sum(w, v));
}
public static void main(String[] args) {
testOfSum();
// 输出 Run testOfSum: 4.0
}
}
Perceptron.java
package main;
import java.util.Arrays;
import java.util.Random;
import util.NumMath;
public class Perceptron {
private double[] weights;
private double threshold;
public Perceptron(double[] weights) {
this.weights = weights;
this.threshold = 0;
}
public Perceptron(double[] weights, double threshold) {
this.weights = weights;
this.threshold = threshold;
}
public double[] getWeights() {
return weights;
}
public void setWeights(double[] weights) {
this.weights = weights;
}
public double getThreshold() {
return threshold;
}
public void setThreshold(double threshold) {
this.threshold = threshold;
}
public int getOutput(double... values) {
if (this.getValue(values) >= this.threshold) {
return 1;
}
return -1;
}
public double getValue(double... values) {
return NumMath.sum(this.weights, values);
}
public double[] train(double[][] test, double[] trueOutput, double studySpeed) {
double[] o = new double[test.length];
boolean flag = true;
while (flag) {
flag = false;
// System.out.println("新的一轮!");
for (int i = 0; i < test.length; i++) {
o[i] = new Perceptron(this.weights, this.threshold).getOutput(test[i]);
if (o[i] != trueOutput[i]) {
this.threshold -= studySpeed * (trueOutput[i] - o[i]);
// System.out.print(threshold + "= ");
for (int j = 0; j < weights.length; j++) {
weights[j] += studySpeed * (trueOutput[i] - o[i]) * test[i][j];
// System.out.print(weights[j] + ", ");
}
// System.out.println(" = " + new Perceptron(this.weights, this.threshold).getValue(test[i]));
i--;
flag = true;
} else {
// System.out.println("第" + i + "个对了!");
}
}
}
return weights;
}
@Override
public String toString() {
return "Perceptron{" + "weights=" + Arrays.toString(weights) + ", threshold=" + threshold + '}';
}
public static void main(String[] args) {
Random r = new Random();
double[] w = {0, 0, 0};
double t = 0;
Perceptron p = new Perceptron(w, t);
double[][] d = {
{1, 1, 0},
{0, 1, 0},
{0, 1, 1},
{1, 0, 2},
{0, 2, 1},
{1, 1, 2},
{2, 1, 2},
{2, 2, 2},
};
double[] tf = {1, 1, -1, -1, -1, 1, 1, 1};
p.train(d, tf, 0.001);
System.out.println(p);
System.out.println(p.getOutput(0, 1, 2));
// 输出 Perceptron{weights=[0.020000000000000004, 0.008, -0.01], threshold=0.008}
// -1
}
}