跟著吳恩達學深度學習:用Scala實現神經網路-第二課:用Scala實現多層神經網路
上一章我們講了如何使用Scala實現LogisticRegression,這一張跟隨著吳恩達的腳步我們用Scala實現基礎的深度神經網路。順便再提一下,吳恩達對於深度神經網路的解釋是我如今聽過的最清楚的課,感嘆一句果然越是大牛知識解釋得越清晰明瞭。
本文分為以下四個部分。按照軟體開發top-down的思路,第一部分我先展示一下使用構建好的神經網路對Gas Censor資料進行分類的demo,這一部分重點關注介面的頂層設計,如何達到易用、簡潔、邏輯清晰以及新手友好,這是軟體工程一個比較難的領域,所以我的介面設計不一定是最優的,歡迎在評論區提出自己的想法,我們共用探討~
第二個部分給出了NeuralNetworkModel類的具體實現。從介面的角度來看,NeuralNetworkModel實現了名為Model的trait(類似於Java中的interface),所有的model都擁有一些common protocol,在本專案中所有的model都擁有setLearningRate,setIterationTime,train,predict,accuracy,getCostHistory方法,此外NeuralNetworkModel也擁有自己獨有的setHiddenLayerStructure,setOutputLayerStructure方法。
第三部分我們介紹了各種神經網路Layer的實現,包含ReluLayer,SigmoidLayer,TanhLayer這三個類。所有的layer都實現了名叫Layer的trait,提供etNumHiddenUnits,forward和backward三個方法。
第一部分:demo介紹
首先我們先看看使用神經網路模型的使用demo。
package
org.mengpan.deeplearning.demo
import breeze.stats.{mean,
stddev}
import org.mengpan.deeplearning.data.{Cat,
GasCensor}
import
import org.mengpan.deeplearning.model.{Model,
NeuralNetworkModel,
ShallowNeuralNetworkModel}
import org.mengpan.deeplearning.utils.{MyDict,
NormalizeUtils,
PlotUtils}
/**
* Created by mengpan on 2017/8/15.
*/
object ClassThreeNeuralNetworkDemo
// Dataset Download Website:http://archive.ics.uci.edu/ml/machine-learning-databases/00224/
//載入Gas Censor的資料集val data: DlCollection[GasCensor] = GasCensorDataHelper.getAllData
//歸一化資料特徵矩陣val normalizedCatData = NormalizeUtils.normalizeBy(data){col =>
(col - mean(col)) / stddev(col)
}
//獲取training set和test set
val (training, test) = normalizedCatData.split(0.8)
//分別獲取訓練集和測試集的feature和label
val trainingFeature = training.getFeatureAsMatrix
val trainingLabel = training.getLabelAsVector
val testFeature = test.getFeatureAsMatrix
val testLabel = test.getLabelAsVector
//初始化演算法模型val nnModel: Model = new NeuralNetworkModel()
.setHiddenLayerStructure(Map(
(200, MyDict.ACTIVATION_RELU),
(100, MyDict.ACTIVATION_RELU)
))
.setOutputLayerStructure((1, MyDict.ACTIVATION_SIGMOID))
.setLearningRate(0.01)
.setIterationTime(5000)
//用訓練集的資料訓練演算法val trainedModel: Model = nnModel.train(trainingFeature, trainingLabel)
//測試演算法獲得演算法優劣指標val yPredicted = trainedModel.predict(testFeature)
val trainYPredicted = trainedModel.predict(trainingFeature)
val testAccuracy = trainedModel.accuracy(testLabel, yPredicted)
val trainAccuracy = trainedModel.accuracy(trainingLabel, trainYPredicted)
println("\n The trainaccuracy of this model is: " + trainAccuracy)
println("\n The testaccuracy of this model is: " + testAccuracy)
//對演算法的訓練過程中cost與迭代次數變化關係進行畫圖val costHistory = trainedModel.getCostHistory
PlotUtils.plotCostHistory(costHistory)
}
對於神經網路的模型介面,我們採用了Scala中典型的鏈式程式設計法:
//初始化演算法模型val nnModel: Model = new NeuralNetworkModel() .setHiddenLayerStructure(Map( (200, MyDict.ACTIVATION_RELU), (100, MyDict.ACTIVATION_RELU) )) .setOutputLayerStructure((1, MyDict.ACTIVATION_SIGMOID)) .setLearningRate(0.01) .setIterationTime(5000)
setHiddenLayerStructure方法用於設定神經網路隱含層的結構,接受多個二元元祖為引數,二元元祖的第一個引數為該層隱含層層的神經單元個數;第二個引數為該層隱含層層的啟用函式型別。如上圖中構建的神經網路擁有兩個隱含層,第一個隱含層擁有200個神經元,啟用函式型別為ReLU,第二個隱含層擁有100個神經元,啟用函式型別也為ReLU。setOutputLayerStructure方法接受一個二元元祖為引數,元祖的含義與前面相同。
第二部分:NeuralNetworkModel類的具體實現
首先我把此類中的重要的方法解釋一下,完整的程式碼附在本部分最後面。首先我們來看一下train()方法:
override def train(feature: DenseMatrix[Double], label: DenseVector[Double]): NeuralNetworkModel.this.type = { val numExamples = feature.rows val inputDim = feature.cols logger.debug("hidden layers: " + hiddenLayers) logger.debug("output layer: " + outputLayer) //隨機初始化模型引數var paramsList: List[(DenseMatrix[Double], DenseVector[Double])] = initializeParams(numExamples, inputDim, hiddenLayers, outputLayer) (0 until this.iterationTime).foreach{i => val forwardResList: List[ForwardRes] = forward(feature, paramsList, hiddenLayers, outputLayer) logger.debug(forwardResList) val cost = calCost(forwardResList.last, label) if (i % 100 == 0) { logger.info("Cost in " + i + "th time of iteration: " + cost) } costHistory.put(i, cost) val backwardResList: List[BackwardRes] = backward(feature, label, forwardResList, paramsList, hiddenLayers, outputLayer) logger.debug(backwardResList) paramsList = updateParams(paramsList, this.learningRate, backwardResList, i, cost) } this.paramsList = paramsList this }
可以看到,在神經網路模型的train()方法中,有五個主要的私有功能函式:首先用initializeParams(numExamples, inputDim, hiddenLayers, outputLayer)初始化引數;然後在每一次迭代過程中:
l 先使用forward()計算前向傳播的結果;
l 在使用calCost()計算此次的損失函式值;
l 然後使用backward()計算反向傳播的結果;
l 最後使用updateParams()更新引數;
接下來看看隨機初始化引數的方法initializeParams(),一些需要關注的語法點都寫在註釋裡面了。
private def initializeParams(numExamples: Int, inputDim: Int, hiddenLayers: Seq[Layer], outputLayer: Layer): List[(DenseMatrix[Double], DenseVector[Double])] = { /* *把輸入層,隱含層,輸出層的神經元個數組合成一個Vector *如inputDim=3,outputDim=1,hiddenDim=(3, 3, 2),則layersDim=(3, 3, 3, 2, 1) *兩個List的操作符,A.::(b)為在A前面加上元素b,A.:+(B)為在A的後面加上元素b *這裡使用Vector儲存layersDim,因為Vector為indexed sequence,訪問任意位置的元素時間相同 */ val layersDim = hiddenLayers.map(_.numHiddenUnits) .toList .::(inputDim) .:+(outputLayer.numHiddenUnits) .toVector val numLayers = layersDim.length /* *W(l)的維度為(layersDim(l-1), layersDim(l)) *b(l)的維度為(layersDim(l), ) *注意隨機初始化的數值在0-1之間,為保證模型穩定性,需在w和b後面*0.01 */ (1 until numLayers).map{i => val w = DenseMatrix.rand[Double](layersDim(i-1), layersDim(i)) * 0.01 val b = DenseVector.rand[Double](layersDim(i)) * 0.01 (w, b) }.toList }
然後看看計算前向傳播的函式forward()的具體實現,相關需要注意的知識點也寫在註釋裡了,另外layer.forward()的實現我們在下一部分講解Layer類時會解釋:
private def forward(feature: DenseMatrix[Double], params: List[(DenseMatrix[Double], DenseVector[Double])], hiddenLayers: Seq[Layer], outputLayer: Layer): List[ForwardRes] = { var yi = feature /* *這裡注意Scala中zip的用法。假設A=List(1, 2, 3), B=List(3, 4), 則 * A.zip(B) 為 List((1, 3), (2, 4)) * 複習:A.:+(b)的作用是在A後面加上b元素,注意因為immutable,實際上是生成了一個新物件 */ params.zip(hiddenLayers.:+(outputLayer)) .map{f => val w = f._1._1 val b = f._1._2 val layer = f._2 //forward方法需要yPrevious, w, b三個引數val forwardRes = layer.forward(yi, w, b) yi = forwardRes.yCurrent forwardRes } }
接下來看看計算損失函式值calCost()的具體實現:
private def calCost(res: ResultUtils.ForwardRes, label: DenseVector[Double]): Double = { val yHat = res.yCurrent(::, 0) //在log函式內加上pow(10.0, -9),防止出現log(0)從而NaN的情況-(label.t * log(yHat + pow(10.0, -9)) + (1.0 - label).t * log(1.0 - yHat + pow(10.0, -9))) / label.length.toDouble }
剩餘的backward()方法以及updateParams()的程式碼以及其他的NeuralNetworkModel類的完整程式碼如下:
package org.mengpan.deeplearning.model import java.util import breeze.linalg.{DenseMatrix, DenseVector} import breeze.numerics.{log, pow} import org.apache.log4j.Logger import org.mengpan.deeplearning.layers.Layer import org.mengpan.deeplearning.utils.{DebugUtils, LayerUtils, ResultUtils} import org.mengpan.deeplearning.utils.ResultUtils.{BackwardRes, ForwardRes} import scala.collection.mutable /** * Created by mengpan on 2017/8/26. */ class NeuralNetworkModel extends Model{ //記錄log val logger = Logger.getLogger("NeuralNetworkModel") //神經網路的四個超引數override var learningRate: Double = _ override var iterationTime: Int = _ var hiddenLayerStructure: Map[Int, Byte] = _ var outputLayerStructure: (Int, Byte) = _ //記錄每一次迭代cost變化的歷史資料override val costHistory: mutable.TreeMap[Int, Double] = new mutable.TreeMap[Int, Double]() //神經網路模型的引數var paramsList: List[(DenseMatrix[Double], DenseVector[Double])] = _ //神經網路的隱含層與輸出層的結構,根據hiddenLayerStructure與outputLayerStructure兩個超引數得到private var hiddenLayers: Seq[Layer] = _ private var outputLayer: Layer = _ def setHiddenLayerStructure(hiddenLayerStructure: Map[Int, Byte]): this.type = { if (hiddenLayerStructure.isEmpty) { throw new Exception("hidden layer should be at least one layer!") } this.hiddenLayerStructure = hiddenLayerStructure this.hiddenLayers = getHiddenLayers(this.hiddenLayerStructure) this } def setOutputLayerStructure(outputLayerStructure: (Int, Byte)): this.type = { this.outputLayerStructure = outputLayerStructure this.outputLayer = getOutputLayer(this.outputLayerStructure) this } override def train(feature: DenseMatrix[Double], label: DenseVector[Double]): NeuralNetworkModel.this.type = { val numExamples = feature.rows val inputDim = feature.cols logger.debug("hidden layers: " + hiddenLayers) logger.debug("output layer: " + outputLayer) //隨機初始化模型引數var paramsList: List[(DenseMatrix[Double], DenseVector[Double])] = initializeParams(numExamples, inputDim, hiddenLayers, outputLayer) (0 until this.iterationTime).foreach{i => val forwardResList: List[ForwardRes] = forward(feature, paramsList, hiddenLayers, outputLayer) logger.debug(forwardResList) val cost = calCost(forwardResList.last, label) if (i % 100 == 0) { logger.info("Cost in " + i + "th time of iteration: " + cost) } costHistory.put(i, cost) val backwardResList: List[BackwardRes] = backward(feature, label, forwardResList, paramsList, hiddenLayers, outputLayer) logger.debug(backwardResList) paramsList = updateParams(paramsList, this.learningRate, backwardResList, i, cost) } this.paramsList = paramsList this } override def predict(feature: DenseMatrix[Double]): DenseVector[Double] = { val forwardResList: List[ForwardRes] = forward(feature, this.paramsList, this.hiddenLayers, this.outputLayer) forwardResList.last.yCurrent(::, 0).map{yHat => if (yHat > 0.5) 1.0 else 0.0 } } private def getHiddenLayers(hiddenLayerStructure: Map[Int, Byte]): Seq[Layer] = { hiddenLayerStructure.map{structure => getLayerByStructure(structure) }.toList } private def getOutputLayer(structure: (Int, Byte)): Layer = { getLayerByStructure(structure) } private def getLayerByStructure(structure: (Int, Byte)): Layer = { val numHiddenUnits = structure._1 val activationType = structure._2 val layer: Layer = LayerUtils.getLayerByActivationType(activationType) .setNumHiddenUnits(numHiddenUnits) layer } private def initializeParams(numExamples: Int, inputDim: Int, hiddenLayers: Seq[Layer], outputLayer: Layer): List[(DenseMatrix[Double], DenseVector[Double])] = { /* *把輸入層,隱含層,輸出層的神經元個數組合成一個Vector *如inputDim=3,outputDim=1,hiddenDim=(3, 3, 2),則layersDim=(3, 3, 3, 2, 1) *兩個List的操作符,A.::(b)為在A前面加上元素B,A.:+(B)為在A的後面加上元素B *這裡使用Vector儲存layersDim,因為Vector為indexed sequence,訪問任意位置的元素時間相同 */ val layersDim = hiddenLayers.map(_.numHiddenUnits) .toList .::(inputDim) .:+(outputLayer.numHiddenUnits) .toVector val numLayers = layersDim.length /* *W(l)的維度為(layersDim(l-1), layersDim(l)) *b(l)的維度為(layersDim(l), ) *注意隨機初始化的數值在0-1之間,為保證模型穩定性,需在w和b後面*0.01 */ (1 until numLayers).map{i => val w = DenseMatrix.rand[Double](layersDim(i-1), layersDim(i)) * 0.01 val b = DenseVector.rand[Double](layersDim(i)) * 0.01 (w, b) }.toList } private def forward(feature: DenseMatrix[Double], params: List[(DenseMatrix[Double], DenseVector[Double])], hiddenLayers: Seq[Layer], outputLayer: Layer): List[ForwardRes] = { var yi = feature /* *這裡注意Scala中zip的用法。假設A=List(1, 2, 3), B=List(3, 4), 則 * A.zip(B) 為 List((1, 3), (2, 4)) * 複習:A.:+(b)的作用是在A後面加上b元素,注意因為immutable,實際上是生成了一個新物件 */ params.zip(hiddenLayers.:+(outputLayer)) .map{f => val w = f._1._1 val b = f._1._2 val layer = f._2 //forward方法需要yPrevious, w, b三個引數val forwardRes = layer.forward(yi, w, b) yi = forwardRes.yCurrent forwardRes } } private def calCost(res: ResultUtils.ForwardRes, label: DenseVector[Double]): Double = { val yHat = res.yCurrent(::, 0) //在log函式內加上pow(10.0, -9),防止出現log(0)從而NaN的情況-(label.t * log(yHat + pow(10.0, -9)) + (1.0 - label).t * log(1.0 - yHat + pow(10.0, -9))) / label.length.toDouble } private def backward(feature: DenseMatrix[Double], label: DenseVector[Double], forwardResList: List[ResultUtils.ForwardRes], paramsList: List[(DenseMatrix[Double], DenseVector[Double])], hiddenLayers: Seq[Layer], outputLayer: Layer): List[BackwardRes] = { val yHat = forwardResList.last.yCurrent(::, 0) //+ pow(10.0, -9)防止出現被除數為0,NaN的情況val dYL = -(label /:/ (yHat + pow(10.0, -9)) - (1.0 - label) /:/ (1.0 - yHat + pow(10.0, -9))) var dYCurrent = DenseMatrix.zeros[Double](feature.rows, 1) dYCurrent(::, 0) := dYL paramsList .zip(forwardResList) .zip(hiddenLayers.:+(outputLayer)) .reverse .map{f => val w = f._1._1._1 val b = f._1._1._2 val forwardRes = f._1._2 val layer = f._2 logger.debug(DebugUtils.matrixShape(w, "w")) logger.debug(layer) /* *backward方法需要dYCurrent, forwardRes, w, b四個引數 * 其中,forwardRes中有用的為:yPrevious(計算dW),zCurrent(計算dZCurrent) */ val backwardRes = layer.backward(dYCurrent, forwardRes, w, b) dYCurrent = backwardRes.dYPrevious backwardRes } .reverse } private def updateParams(paramsList: List[(DenseMatrix[Double], DenseVector[Double])], learningrate: Double, backwardResList: List[ResultUtils.BackwardRes], iterationTime: Int, cost: Double): List[(DenseMatrix[Double], DenseVector[Double])] = { paramsList.zip(backwardResList) .map{f => val w = f._1._1 val相關推薦
跟著吳恩達學深度學習:用Scala實現神經網路-第二課:用Scala實現多層神經網路
上一章我們講了如何使用Scala實現LogisticRegression,這一張跟隨著吳恩達的腳步我們用Scala實現基礎的深度神經網路。順便再提一下,吳恩達對於深度神經網路的解釋是我如今聽過的最清楚的課,感嘆一句果然越是大牛知識解釋得越清晰明瞭。 本文分為以下四個部分。
跟著吳恩達學深度學習:用Scala實現神經網路-第一課
1. Introduction 2017年8月,前百度首席科學家吳恩達先生在twitter上宣佈自己從百度離職後的第一個動作:在Coursera上推出一門從零開始構建神經網路的Deep Learning課程,一時間廣為轟動。
吳恩達《深度學習》第四門課(2)卷積神經網絡:實例探究
之一 所有 展示 數據擴充 簡介 設置 假設 通道 開源 2.1為什麽要進行實例探究 (1)就跟學編程一樣,先看看別人怎麽寫的,可以模仿。 (2)在計算機視覺中一個有用的模型,,用在另一個業務中也一般有效,所以可以借鑒。 (3)本周會介紹的一些卷積方面的經典網絡經典的包括:
吳恩達Coursera深度學習課程 deeplearning.ai (5-3) 序列模型和注意力機制--程式設計作業(二):觸發字檢測
Part 2: 觸發字檢測 關鍵詞語音喚醒 觸發字檢測 歡迎來到這個專業課程的最終程式設計任務! 在本週的視訊中,你瞭解瞭如何將深度學習應用於語音識別。在本作業中,您將構建一個語音資料集並實現觸發字檢測演算法(有時也稱為關鍵字檢測或喚醒檢測)。觸發字
吳恩達Coursera深度學習課程 deeplearning.ai (5-1) 迴圈序列模型--程式設計作業(一):構建迴圈神經網路
Part 1: 構建神經網路 歡迎來到本週的第一個作業,這個作業我們將利用numpy實現你的第一個迴圈神經網路。 迴圈神經網路(Recurrent Neural Networks: RNN) 因為有”記憶”,所以在自然語言處理(Natural Languag
吳恩達Coursera深度學習課程 deeplearning.ai (5-2) 自然語言處理與詞嵌入--程式設計作業(一):詞向量運算
Part 1: 詞向量運算 歡迎來到本週第一個作業。 由於詞嵌入的訓練計算量龐大切耗費時間長,絕大部分機器學習人員都會匯入一個預訓練的詞嵌入模型。 你將學到: 載入預訓練單詞向量,使用餘弦測量相似度 使用詞嵌入解決類別問題,比如 “Man is to
吳恩達Coursera深度學習課程 deeplearning.ai (4-2) 深度卷積網路:例項探究--課程筆記
本課主要講解了一些典型的卷積神經網路的思路,包括經典神經網路的leNet/AlexNet/VGG, 以及殘差網路ResNet和Google的Inception網路,順便講解了1x1卷積核的應用,便於我們進行學習和借鑑。 2.1 為什麼要進行例項探究 神經
吳恩達老師深度學習視訊課筆記:構建機器學習專案(機器學習策略)(1)
機器學習策略(machine learning strategy):分析機器學習問題的方法。 正交化(orthogonalization):要讓一個監督機器學習系統很好的工作,一般要確保四件事情,如下圖: (1)、首先,你通常必須確保至少系
吳恩達Coursera深度學習課程 deeplearning.ai (5-2) 自然語言處理與詞嵌入--程式設計作業(二):Emojify表情包
Part 2: Emojify 歡迎來到本週的第二個作業,你將利用詞向量構建一個表情包。 你有沒有想過讓你的簡訊更具表現力? emojifier APP將幫助你做到這一點。 所以不是寫下”Congratulations on the promotion! L
吳恩達【深度學習工程師】 04.卷積神經網絡 第三周目標檢測 (1)基本的對象檢測算法
元素 需要 有關 卷積 訓練 特定 步長 來看 選擇 該筆記介紹的是《卷積神經網絡》系列第三周:目標檢測(1)基本的對象檢測算法 主要內容有: 1.目標定位 2.特征點檢測 3.目標檢測 目標定位 使用算法判斷圖片中是不是目標物體,如果是還要再圖片中標出其位置並
吳恩達《深度學習》第一門課(1)深度學習引言
數據規模 梯度 神經網絡 以及 應該 精確 構建 關於 http 1.1歡迎 主要講了五門課的內容: 第一門課:神經網絡基礎,構建網絡等; 第二門課:神經網絡的訓練技巧; 第三門課:構建機器學習系統的一些策略,下一步該怎麽走(吳恩達老師新書《Machine Learning
吳恩達《深度學習》第一門課(4)深層神經網絡
加網 分享 傳遞 height 經驗 技術分享 image 進行 sig 4.1深層神經網絡 (1)到底是深層還是淺層是一個相對的概念,不必太糾結,以下是一個四層的深度神經網絡: (2)一些符號定義: a[0]=x(輸入層也叫做第0層) L=4:表示網絡的層數 g:表示激
吳恩達《深度學習》第四門課(1)卷積神經網絡
圖像分割 1.5 共享 信號處理 soft 沒有 樣本 填充 單元 1.1計算機視覺 (1)計算機視覺的應用包括圖像分類、目標檢測、圖像分割、風格遷移等,下圖展示了風格遷移案例: (2)圖像的特征量非常之大,比如一個3通道的1000*1000的照片,其特征為3*1000*
吳恩達《深度學習》第五門課(2)自然語言處理與詞嵌入
星級 技術 ima lac 個數 應該 ras 時有 根據 2.1詞匯表征 (1)使用one-hot方法表示詞匯有兩個主要的缺點,以10000個詞為例,每個單詞需要用10000維來表示,而且只有一個數是零,其他維度都是1,造成表示非常冗余,存儲量大;第二每個單詞表示的向量相
深度學習,周志華,機器學習,西瓜書,TensorFlow,Google,吳軍,數學之美,李航,統計學習方法,吳恩達,深度學習筆記,pdf下載
1. 機器學習入門經典,李航《統計學習方法》 2. 周志華的《機器學習》pdf 3.《數學之美》吳軍博士著pdf 4. Tensorflow 實戰Google深度學習框架.pdf 5.《TensorFlow實戰》黃文堅 高清完整PDF 6. 復旦大
吳恩達Coursera深度學習課程 deeplearning.ai (4-1) 卷積神經網路--程式設計作業
Part 1:卷積神經網路 本週課程將利用numpy實現卷積層(CONV) 和 池化層(POOL), 包含前向傳播和可選的反向傳播。 變數說明 上標[l][l] 表示神經網路的第幾層 上標(i)(i) 表示第幾個樣本 上標[i][i] 表示第幾個mi
吳恩達Coursera深度學習課程 DeepLearning.ai 程式設計作業——Regularization(2-1.2)
如果資料集沒有很大,同時在訓練集上又擬合得很好,但是在測試集的效果卻不是很好,這時候就要使用正則化來使得其擬合能力不會那麼強。 import numpy as np import sklearn import matplotlib.pyplot as plt