赞
踩
数据 train.format:下载地址
之前使用arff文件存储数据,现在用图片数据方式存储,按结构化的方式来存取 (m*n 点阵和类别)
package xjx.cnn; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class Dataset { /** * 所有实例是一个List表 */ private List<Instance> instances; /** * 标签索引 */ private int labelIndex; /** * 最大的标签 */ private double maxLabel = -1; /** *********************** * 第一个构造器. *********************** */ public Dataset() { labelIndex = -1; instances = new ArrayList<Instance>(); } /** *********************** * 第二个构造器. * * @param paraFilename * The filename. * @param paraSplitSign * Often comma. * @param paraLabelIndex * Often the last column. *********************** */ public Dataset(String paraFilename, String paraSplitSign, int paraLabelIndex) { instances = new ArrayList<Instance>(); labelIndex = paraLabelIndex; File tempFile = new File(paraFilename); try { BufferedReader tempReader = new BufferedReader(new FileReader(tempFile)); String tempLine; while ((tempLine = tempReader.readLine()) != null) { String[] tempDatum = tempLine.split(paraSplitSign); if (tempDatum.length == 0) { continue; } double[] tempData = new double[tempDatum.length]; for (int i = 0; i < tempDatum.length; i++) tempData[i] = Double.parseDouble(tempDatum[i]); Instance tempInstance = new Instance(tempData); append(tempInstance); } tempReader.close(); } catch (IOException e) { e.printStackTrace(); System.out.println("Unable to load " + paraFilename); System.exit(0); } } /** *********************** * 追加一个实例 * * @param paraInstance * The given record. *********************** */ public void append(Instance paraInstance) { instances.add(paraInstance); } /** *********************** * 追加一个实例 *********************** */ public void append(double[] paraAttributes, Double paraLabel) { instances.add(new Instance(paraAttributes, paraLabel)); } /** *********************** * Getter. *********************** */ public Instance getInstance(int paraIndex) { return instances.get(paraIndex); } /** *********************** * Getter. *********************** */ public int size() { return instances.size(); }// Of size /** *********************** * Getter. *********************** */ public double[] getAttributes(int paraIndex) { return instances.get(paraIndex).getAttributes(); } /** *********************** * Getter. *********************** */ public Double getLabel(int paraIndex) { return instances.get(paraIndex).getLabel(); } /** *********************** * Unit test. *********************** */ public static void main(String args[]) { Dataset tempData = new Dataset("d:/data/train.format", ",", 784); Instance tempInstance = tempData.getInstance(0); System.out.println("The first instance is: " + tempInstance); } /** *********************** * An instance. *********************** */ public class Instance { /** * 条件属性. */ private double[] attributes; /** * 标签. */ private Double label; /** *********************** * The first constructor. *********************** */ private Instance(double[] paraAttrs, Double paraLabel) { attributes = paraAttrs; label = paraLabel; } /** *********************** * The second constructor. *********************** */ public Instance(double[] paraData) { if (labelIndex == -1) //无标签 attributes = paraData; else { label = paraData[labelIndex]; if (label > maxLabel) { //新标签 maxLabel = label; } if (labelIndex == 0) { //第一列是标签 attributes = Arrays.copyOfRange(paraData, 1, paraData.length); } else { attributes = Arrays.copyOfRange(paraData, 0, paraData.length - 1); } } } /** *********************** * Getter. *********************** */ public double[] getAttributes() { return attributes; } /** *********************** * Getter. *********************** */ public Double getLabel() { if (labelIndex == -1) return null; return label; } /** *********************** * toString. *********************** */ public String toString(){ return Arrays.toString(attributes) + ", " + label; } } }
一个管理卷积核尺寸的类. 基础代码, 在网络运行时才能理解它们的作用.
package xjx.cnn; public class Size { /** * Cannot be changed after initialization. */ public final int width; /** * Cannot be changed after initialization. */ public final int height; /** *********************** * The first constructor. * * @param paraWidth * The given width. * @param paraHeight * The given height. *********************** */ public Size(int paraWidth, int paraHeight) { width = paraWidth; height = paraHeight; }// Of the first constructor /** *********************** * Divide a scale with another one. For example (4, 12) / (2, 3) = (2, 4). * * @param paraScaleSize * The given scale size. * @return The new size. *********************** */ public Size divide(Size paraScaleSize) { int resultWidth = width / paraScaleSize.width; int resultHeight = height / paraScaleSize.height; if (resultWidth * paraScaleSize.width != width || resultHeight * paraScaleSize.height != height) throw new RuntimeException("Unable to divide " + this + " with " + paraScaleSize); return new Size(resultWidth, resultHeight); }// Of divide /** *********************** * Subtract a scale with another one, and add a value. For example (4, 12) - * (2, 3) + 1 = (3, 10). * * @param paraScaleSize * The given scale size. * @param paraAppend * The appended size to both dimensions. * @return The new size. *********************** */ public Size subtract(Size paraScaleSize, int paraAppend) { int resultWidth = width - paraScaleSize.width + paraAppend; int resultHeight = height - paraScaleSize.height + paraAppend; return new Size(resultWidth, resultHeight); }// Of subtract /** *********************** * @param The * string showing itself. *********************** */ public String toString() { String resultString = "(" + width + ", " + height + ")"; return resultString; }// Of toString /** *********************** * Unit test. *********************** */ public static void main(String[] args) { Size tempSize1 = new Size(4, 6); Size tempSize2 = new Size(2, 2); System.out.println( "" + tempSize1 + " divide " + tempSize2 + " = " + tempSize1.divide(tempSize2)); System.out.printf("a"); try { System.out.println( "" + tempSize2 + " divide " + tempSize1 + " = " + tempSize2.divide(tempSize1)); } catch (Exception ee) { System.out.println(ee); } // Of try System.out.println( "" + tempSize1 + " - " + tempSize2 + " + 1 = " + tempSize1.subtract(tempSize2, 1)); }// Of main }// Of class Size
以前我们使用整数型常量 (第 51 天) 和字符型常量 (第 74 天), 其实还可以有枚举类型. 后面的程序我们才能看到其用法.
package xjx.cnn;
public enum LayerTypeEnum {
INPUT, CONVOLUTION, SAMPLING, OUTPUT;
}//Of enum LayerTypeEnum
在开始今天的任务前先学习一下卷积神经网络CNN的原理。
其中包含了几个主要结构
卷积运算过程
假设是一张5 X 5 的单通道图片,通过使用3 X 3 大小的卷积核运算得到一个 3 X 3大小的运算结果(图片像素数值仅供参考)
我们会发现进行卷积之后的图片变小了,假设N为图片大小,F为卷积核大小
相当于
N
−
F
+
1
=
5
−
3
+
1
=
3
N-F+1 = 5 - 3 + 1 = 3
N−F+1=5−3+1=3
如果换一个卷积核大小或者加入很多层卷积之后,图像可能最后就变成了1 X 1 大小,这不是我们希望看到的结果。并且对于原始图片当中的边缘像素来说,只计算了一遍,二对于中间的像素会有很多次过滤器与之计算,这样导致对边缘信息的丢失。
padding-零填充:
因为0在权重乘积和运算中对最终结果不造成影响,也就避免了图片增加了额外的干扰信息。所以在图片像素的最外层加上若干层0值,若一层,记做
p
=
1
p=1
p=1.
在刚才的图上增加一层0值,则:
5
+
2
∗
p
−
3
+
1
=
5
5 + 2 * p - 3 + 1=5
5+2∗p−3+1=5
P
P
P为1,那么最终特征结果为5。
实际上我们可以填充更多的像素,假设为2层,则
5
+
2
∗
2
−
3
+
1
=
7
5 + 2 * 2 - 3 + 1 = 7
5+2∗2−3+1=7
这样得到的观察特征大小比之前图片大小还大。
所以我们对于零填充会有一些选择,该填充多少?
Valid and Same卷积
那也就意味着,之前大小与之后的大小一样,得出下面的等式
(
N
+
2
P
−
F
+
1
)
=
N
(N + 2P - F + 1) = N
(N+2P−F+1)=N
P
=
F
−
1
2
P = \frac{F -1}{2}
P=2F−1
所以当知道了卷积核的大小之后,就可以得出要填充多少层像素。
stride-步长:
上面的例子步长为1,将步长修改为2后:
这样如果以原来的计算公式,那么结果
N
+
2
P
−
F
+
1
=
6
+
0
−
3
+
1
=
4
N + 2P - F + 1 = 6 + 0 -3 +1 = 4
N+2P−F+1=6+0−3+1=4
但是移动2个像素才得出一个结果,所以公式变为:
N
+
2
P
−
F
2
+
1
=
1.5
+
1
=
2.5
\frac{N + 2P - F}{2} + 1 = 1.5 + 1 = 2.5
2N+2P−F+1=1.5+1=2.5
如果相除不是整数的时候,向下取整,为2。这里并没有加上零填充。
所以最终的公式就为:
N
+
2
P
−
F
S
+
1
\frac{N + 2P - F}{S} + 1
SN+2P−F+1
对于输入图片大小为N,过滤器大小为F,步长为S,零填充为P
池化层主要对卷积层学习到的特征图进行亚采样(subsampling)处理,主要由两种
意义在于:
降低了后续网络层的输入维度,缩减模型大小,提高计算速度
提高了Feature Map 的鲁棒性,防止过拟合
卷积层+激活层+池化层可以看成是CNN的特征学习/特征提取层,而学习到的特征(Feature Map)最终应用于模型任务(分类、回归):
package xjx.cnn; import java.io.Serializable; import java.util.Arrays; import java.util.HashSet; import java.util.Random; import java.util.Set; public class MathUtils { /** * 不同按需操作员的界面 */ public interface Operator extends Serializable { public double process(double value); } /** * 一减去值运算符 */ public static final Operator one_value = new Operator() { private static final long serialVersionUID = 3752139491940330714L; @Override public double process(double value) { return 1 - value; } }; /** * sigmoid 激活函数 */ public static final Operator sigmoid = new Operator() { private static final long serialVersionUID = -1952718905019847589L; @Override public double process(double value) { return 1 / (1 + Math.pow(Math.E, -value)); } }; /** * 具有两个运算符的操作接口 */ interface OperatorOnTwo extends Serializable { public double process(double a, double b); } /** * 加法. */ public static final OperatorOnTwo plus = new OperatorOnTwo() { private static final long serialVersionUID = -6298144029766839945L; @Override public double process(double a, double b) { return a + b; } }; /** * 乘法. */ public static OperatorOnTwo multiply = new OperatorOnTwo() { private static final long serialVersionUID = -7053767821858820698L; @Override public double process(double a, double b) { return a * b; } }; /** * 减法 */ public static OperatorOnTwo minus = new OperatorOnTwo() { private static final long serialVersionUID = 7346065545555093912L; @Override public double process(double a, double b) { return a - b; } }; /** *********************** * 输出矩阵 *********************** */ public static void printMatrix(double[][] matrix) { for (int i = 0; i < matrix.length; i++) { String line = Arrays.toString(matrix[i]); line = line.replaceAll(", ", "\t"); System.out.println(line); } System.out.println(); } /** *********************** * 将矩阵旋转180度 *********************** */ public static double[][] rot180(double[][] matrix) { matrix = cloneMatrix(matrix); int m = matrix.length; int n = matrix[0].length; for (int i = 0; i < m; i++) { for (int j = 0; j < n / 2; j++) { double tmp = matrix[i][j]; matrix[i][j] = matrix[i][n - 1 - j]; matrix[i][n - 1 - j] = tmp; } } for (int j = 0; j < n; j++) { for (int i = 0; i < m / 2; i++) { double tmp = matrix[i][j]; matrix[i][j] = matrix[m - 1 - i][j]; matrix[m - 1 - i][j] = tmp; } } return matrix; } private static Random myRandom = new Random(2); /** *********************** * 生成给定大小的随机矩阵。每个值取[-0.005, 0.095]中的值 *********************** */ public static double[][] randomMatrix(int x, int y, boolean b) { double[][] matrix = new double[x][y]; // int tag = 1; for (int i = 0; i < x; i++) { for (int j = 0; j < y; j++) { matrix[i][j] = (myRandom.nextDouble() - 0.05) / 10; } } return matrix; } /** *********************** * 生成具有给定长度的随机数组。每个值取[-0.005, 0.095]中的值 *********************** */ public static double[] randomArray(int len) { double[] data = new double[len]; for (int i = 0; i < len; i++) { //data[i] = myRandom.nextDouble() / 10 - 0.05; data[i] = 0; } return data; } /** *********************** * 生成具有批量大小的随机卷积。 *********************** */ public static int[] randomPerm(int size, int batchSize) { Set<Integer> set = new HashSet<Integer>(); while (set.size() < batchSize) { set.add(myRandom.nextInt(size)); } int[] randPerm = new int[batchSize]; int i = 0; for (Integer value : set) randPerm[i++] = value; return randPerm; } /** *********************** * 克隆矩阵。不要直接引用它 *********************** */ public static double[][] cloneMatrix(final double[][] matrix) { final int m = matrix.length; int n = matrix[0].length; final double[][] outMatrix = new double[m][n]; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { outMatrix[i][j] = matrix[i][j]; } } return outMatrix; } /** *********************** * 在单个操作数上使用给定运算符的矩阵运算 *********************** */ public static double[][] matrixOp(final double[][] ma, Operator operator) { final int m = ma.length; int n = ma[0].length; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { ma[i][j] = operator.process(ma[i][j]); } } return ma; } /** *********************** * 在两个操作数上使用给定运算符的矩阵运算 *********************** */ public static double[][] matrixOp(final double[][] ma, final double[][] mb, final Operator operatorA, final Operator operatorB, OperatorOnTwo operator) { final int m = ma.length; int n = ma[0].length; if (m != mb.length || n != mb[0].length) throw new RuntimeException("ma.length:" + ma.length + " mb.length:" + mb.length); for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { double a = ma[i][j]; if (operatorA != null) a = operatorA.process(a); double b = mb[i][j]; if (operatorB != null) b = operatorB.process(b); mb[i][j] = operator.process(a, b); } } return mb; } /** *********************** * 将矩阵扩展到更大的矩阵(多次) *********************** */ public static double[][] kronecker(final double[][] matrix, final Size scale) { final int m = matrix.length; int n = matrix[0].length; final double[][] outMatrix = new double[m * scale.width][n * scale.height]; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { for (int ki = i * scale.width; ki < (i + 1) * scale.width; ki++) { for (int kj = j * scale.height; kj < (j + 1) * scale.height; kj++) { outMatrix[ki][kj] = matrix[i][j]; } } } } return outMatrix; } /** *********************** * 缩放矩阵 *********************** */ public static double[][] scaleMatrix(final double[][] matrix, final Size scale) { int m = matrix.length; int n = matrix[0].length; final int sm = m / scale.width; final int sn = n / scale.height; final double[][] outMatrix = new double[sm][sn]; if (sm * scale.width != m || sn * scale.height != n) throw new RuntimeException("scale matrix"); final int size = scale.width * scale.height; for (int i = 0; i < sm; i++) { for (int j = 0; j < sn; j++) { double sum = 0.0; for (int si = i * scale.width; si < (i + 1) * scale.width; si++) { for (int sj = j * scale.height; sj < (j + 1) * scale.height; sj++) { sum += matrix[si][sj]; } } outMatrix[i][j] = sum / size; } } return outMatrix; } /** *********************** * 完全卷积以获得更大的尺寸。它用于反向传播 *********************** */ public static double[][] convnFull(double[][] matrix, final double[][] kernel) { int m = matrix.length; int n = matrix[0].length; final int km = kernel.length; final int kn = kernel[0].length; final double[][] extendMatrix = new double[m + 2 * (km - 1)][n + 2 * (kn - 1)]; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { extendMatrix[i + km - 1][j + kn - 1] = matrix[i][j]; } } return convnValid(extendMatrix, kernel); } /** *********************** * 卷积运算,从给定的矩阵和核,滑动和求和,以获得结果矩阵。它用于向前预测 *********************** */ public static double[][] convnValid(final double[][] matrix, double[][] kernel) { // kernel = rot180(kernel); int m = matrix.length; int n = matrix[0].length; final int km = kernel.length; final int kn = kernel[0].length; int kns = n - kn + 1; final int kms = m - km + 1; final double[][] outMatrix = new double[kms][kns]; for (int i = 0; i < kms; i++) { for (int j = 0; j < kns; j++) { double sum = 0.0; for (int ki = 0; ki < km; ki++) { for (int kj = 0; kj < kn; kj++) sum += matrix[i + ki][j + kj] * kernel[ki][kj]; } outMatrix[i][j] = sum; } } return outMatrix; } /** *********************** * 张量上的卷积 *********************** */ public static double[][] convnValid(final double[][][][] matrix, int mapNoX, double[][][][] kernel, int mapNoY) { int m = matrix.length; int n = matrix[0][mapNoX].length; int h = matrix[0][mapNoX][0].length; int km = kernel.length; int kn = kernel[0][mapNoY].length; int kh = kernel[0][mapNoY][0].length; int kms = m - km + 1; int kns = n - kn + 1; int khs = h - kh + 1; if (matrix.length != kernel.length) throw new RuntimeException("length"); final double[][][] outMatrix = new double[kms][kns][khs]; for (int i = 0; i < kms; i++) { for (int j = 0; j < kns; j++) for (int k = 0; k < khs; k++) { double sum = 0.0; for (int ki = 0; ki < km; ki++) { for (int kj = 0; kj < kn; kj++) for (int kk = 0; kk < kh; kk++) { sum += matrix[i + ki][mapNoX][j + kj][k + kk] * kernel[ki][mapNoY][kj][kk]; } } outMatrix[i][j][k] = sum; } } return outMatrix[0]; } /** *********************** * sigmod 操作 *********************** */ public static double sigmod(double x) { return 1 / (1 + Math.pow(Math.E, -x)); } /** *********************** * 求矩阵的所有值之和 *********************** */ public static double sum(double[][] error) { int m = error.length; int n = error[0].length; double sum = 0.0; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { sum += error[i][j]; } } return sum; } /** *********************** * Ad hoc sum. *********************** */ public static double[][] sum(double[][][][] errors, int j) { int m = errors[0][j].length; int n = errors[0][j][0].length; double[][] result = new double[m][n]; for (int mi = 0; mi < m; mi++) { for (int nj = 0; nj < n; nj++) { double sum = 0; for (int i = 0; i < errors.length; i++) sum += errors[i][j][mi][nj]; result[mi][nj] = sum; } } return result; } /** *********************** * 获取最终分类的最大值索引 *********************** */ public static int getMaxIndex(double[] out) { double max = out[0]; int index = 0; for (int i = 1; i < out.length; i++) if (out[i] > max) { max = out[i]; index = i; } return index; } }
昨天的操作比较多, 今天可以自己写些代码来测试下. 例如, 测试用例最好是 8*8 之内的矩阵, 这样比较容易验证正确性.
部分测试代码:
public static void main(String args[]) { MathUtils tempmathUtils = new MathUtils(); double[][] matrix = new double[8][8]; matrix = tempmathUtils.randomMatrix(8, 8, true); printMatrix(matrix); //matrix = rot180(matrix); //printMatrix(matrix); double[] Array = new double[8]; Array = tempmathUtils.randomArray(8); int[] Perm = new int[10]; Perm = tempmathUtils.randomPerm(20,10); Operator aa; double[][] tempmatrixOp = new double[8][8]; tempmatrixOp = tempmathUtils.matrixOp(tempmatrixOp,aa); printMatrix(matrix); Operator a1,b1; OperatorOnTwo c1; double[][] tempmatrixOpa = new double[8][8]; double[][] tempmatrixOpb = new double[8][8]; tempmatrixOpa = matrixOp(tempmatrixOpa, tempmatrixOpb, a1, b1, c1); printMatrix(matrix); //System.out.println(); }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。