一、前言
最近一直在研究深度學習,聯想起之前所學,感嘆數學是一門樸素而神奇的科學。F=G*m1*m2/r²萬有引力描述了宇宙星河運轉的規律,E=mc²描述了恆星發光的奧祕,V=H*d哈勃定律描述了宇宙膨脹的奧祕,自然界的大部分現象和規律都可以用數學函數來描述,也就是可以求得一個函數。
神經網絡(《簡單又複雜的人工神經網絡》)可以逼近任何連續的函數,那麼神經網絡就有無限的泛化能力。對於大部分分類問題而言,本質就是求得一個函數y=f(x),例如:對於圖像識別而言就是求得一個以像素張量爲自變量的函數y=F(像素張量),其中y=貓、狗、花、汽車等等;對於文本情感分析而言,就是爲了求得一個以詞向量或者段落向量爲自變量的函數y=F(詞向量),其中y=正面、負面等等……
二、導讀
本篇博客包括以下內容:
1、卷積神經網絡的原理
2、基於dl4j定型一個卷積神經網絡來進行手寫數字識別
三、卷積神經網絡原理
下面左邊有個9*9的網格,紅色填充的部分構成了數字7,把紅色部分填上1,空白部分填上0,就構成了一個二維矩陣,傳統做法可以用求向量距離,如果數字全部都標準的寫在網格中相同的位置,那麼肯定是準確的,但是,實際上數字7在書寫的過程中,可能偏左一點、偏右一點,變形扭曲一點,這時候就難以識別。另外,一幅圖片的像素點的數量是巨大的,例如一幅50*50的圖片將有2500個像素點,每個像素點有R、G、B三個維度的顏色,那麼輸入參數的個數有7500個,這個運算量是巨大的。
那麼就需要有一個抽象特徵、降低數據維度的方法,這就說到了卷積運算,用一個小於圖片的卷積核掃過整幅圖片求點積。卷積的過程看下圖。圖片來源於https://my.oschina.net/u/876354/blog/1620906
卷積運算的過程在於尋找圖片中的顯著特徵,並達到降維的目的,整個過程相當於一個函數掃過另一個函數,掃過時兩個函數的積分重疊部分並沒改變圖片的特徵形狀,並可以降低維度,另外還可以分區塊來提取特徵,並且拼接特徵。
爲了進一步降低維度,引入了池化,池化的方式有很多,如最大值,平均值。下圖展示了一個步長爲2的2*2最大池化過程,用一個2*2的方塊掃描過,求Max,總共掃描4次,4次掃描的最大值分別是6、8、3、4。
最後,經過多層卷積和池化之後,會得到一個矩陣,該矩陣作爲一個全連接網絡的輸入,在逼近一個函數,就識別出數字了,以上圖得到的6、8、3、4爲例,全連接網絡求一個函數。
四、deeplearning4j手寫體識別
1、先下載mnist數據集,地址如下:
http://github.com/myleott/mnist_png/raw/master/mnist_png.tar.gz
2、解壓(我解壓在E盤)
3、訓練網絡,評估(一些比較難的部分都做了註釋)
public class MnistClassifier {
private static final Logger log = LoggerFactory.getLogger(MnistClassifier.class);
private static final String basePath = "E:";
public static void main(String[] args) throws Exception {
int height = 28;
int width = 28;
int channels = 1; // 這裏有沒有複雜的識別,沒有分成紅綠藍三個通道
int outputNum = 10; // 有十個數字,所以輸出爲10
int batchSize = 54;//每次迭代取54張小批量來訓練,可以查閱神經網絡的mini batch相關優化,也就是小批量求平均梯度
int nEpochs = 1;//整個樣本集只訓練一次
int iterations = 1;
int seed = 1234;
Random randNumGen = new Random(seed);
File trainData = new File(basePath + "/mnist_png/training");
FileSplit trainSplit = new FileSplit(trainData, NativeImageLoader.ALLOWED_FORMATS, randNumGen);
ParentPathLabelGenerator labelMaker = new ParentPathLabelGenerator(); //以父級目錄名作爲分類的標籤名
ImageRecordReader trainRR = new ImageRecordReader(height, width, channels, labelMaker);//構造圖片讀取類
trainRR.initialize(trainSplit);
DataSetIterator trainIter = new RecordReaderDataSetIterator(trainRR, batchSize, 1, outputNum);
// 把像素值區間 0-255 壓縮到0-1 區間
DataNormalization scaler = new ImagePreProcessingScaler(0, 1);
scaler.fit(trainIter);
trainIter.setPreProcessor(scaler);
// 向量化測試集
File testData = new File(basePath + "/mnist_png/testing");
FileSplit testSplit = new FileSplit(testData, NativeImageLoader.ALLOWED_FORMATS, randNumGen);
ImageRecordReader testRR = new ImageRecordReader(height, width, channels, labelMaker);
testRR.initialize(testSplit);
DataSetIterator testIter = new RecordReaderDataSetIterator(testRR, batchSize, 1, outputNum);
testIter.setPreProcessor(scaler); // same normalization for better results
log.info("Network configuration and training...");
Map<Integer, Double> lrSchedule = new HashMap<>();//設定動態改變學習速率的策略,key表示小批量迭代到幾次
lrSchedule.put(0, 0.06);
lrSchedule.put(200, 0.05);
lrSchedule.put(600, 0.028);
lrSchedule.put(800, 0.0060);
lrSchedule.put(1000, 0.001);
MultiLayerConfiguration conf = new NeuralNetConfiguration.Builder()
.seed(seed)
.iterations(iterations)
.regularization(true).l2(0.0005)
.learningRate(.01)
.learningRateDecayPolicy(LearningRatePolicy.Schedule)
.learningRateSchedule(lrSchedule)
.weightInit(WeightInit.XAVIER)
.optimizationAlgo(OptimizationAlgorithm.STOCHASTIC_GRADIENT_DESCENT)
.updater(Updater.NESTEROVS)
.list()
.layer(0, new ConvolutionLayer.Builder(5, 5)
.nIn(channels)
.stride(1, 1)
.nOut(20)
.activation(Activation.IDENTITY)
.build())
.layer(1, new SubsamplingLayer.Builder(SubsamplingLayer.PoolingType.MAX)
.kernelSize(2, 2)
.stride(2, 2)
.build())
.layer(2, new ConvolutionLayer.Builder(5, 5)
.stride(1, 1)
.nOut(50)
.activation(Activation.IDENTITY)
.build())
.layer(3, new SubsamplingLayer.Builder(SubsamplingLayer.PoolingType.MAX)
.kernelSize(2, 2)
.stride(2, 2)
.build())
.layer(4, new DenseLayer.Builder().activation(Activation.RELU)
.nOut(500).build())
.layer(5, new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD)
.nOut(outputNum)
.activation(Activation.SOFTMAX)
.build())
.setInputType(InputType.convolutionalFlat(28, 28, 1))
.backprop(true).pretrain(false).build();
MultiLayerNetwork net = new MultiLayerNetwork(conf);
net.init();
net.setListeners(new ScoreIterationListener(10));
log.debug("Total num of params: {}", net.numParams());
// 評估測試集
for (int i = 0; i < nEpochs; i++) {
net.fit(trainIter);
Evaluation eval = net.evaluate(testIter);
log.info(eval.stats());
trainIter.reset();
testIter.reset();
}
ModelSerializer.writeModel(net, new File(basePath + "/minist-model.zip"), true);//保存訓練好的網絡
}
}
運行main方法,得到如下評估結果:
# of classes: 10
Accuracy: 0.9897
Precision: 0.9897
Recall: 0.9897
F1 Score: 0.9896
整個效果還比較好,保存好訓練的網絡,便可以用於手寫體數據的識別了,下一篇博客將介紹怎麼加載定型的網絡,配合springMVC來開發一個手寫體識別的應用。