1、前言git
最近一直在研究深度學習,聯想起以前所學,感嘆數學是一門樸素而神奇的科學。F=G*m1*m2/r²萬有引力描述了宇宙星河運轉的規律,E=mc²描述了恆星發光的奧祕,V=H*d哈勃定律描述了宇宙膨脹的奧祕,天然界的大部分現象和規律均可以用數學函數來描述,也就是能夠求得一個函數。github
神經網絡(《簡單又複雜的人工神經網絡》)能夠逼近任何連續的函數,那麼神經網絡就有無限的泛化能力。對於大部分分類問題而言,本質就是求得一個函數y=f(x),例如:對於圖像識別而言就是求得一個以像素張量爲自變量的函數y=F(像素張量),其中y=貓、狗、花、汽車等等;對於文本情感分析而言,就是爲了求得一個以詞向量或者段落向量爲自變量的函數y=F(詞向量),其中y=正面、負面等等……spring
2、導讀網絡
本篇博客包括如下內容:dom
一、卷積神經網絡的原理ide
二、基於dl4j定型一個卷積神經網絡來進行手寫數字識別函數
3、卷積神經網絡原理學習
下面左邊有個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次掃描的最大值分別是六、八、三、4。
最後,通過多層卷積和池化以後,會獲得一個矩陣,該矩陣做爲一個全鏈接網絡的輸入,在逼近一個函數,就識別出數字了,以上圖獲得的六、八、三、4爲例,全鏈接網絡求一個函數。
4、deeplearning4j手寫體識別
一、先下載mnist數據集,地址以下:
http://github.com/myleott/mnist_png/raw/master/mnist_png.tar.gz
二、解壓(我解壓在E盤)
三、訓練網絡,評估(一些比較難的部分都作了註釋)
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來開發一個手寫體識別的應用。