寫給 Web 開發者的深度學習教程 - 向量化 & 矩陣

前言

在這個科技發展突飛猛進的時代,行業的寵兒與棄兒就如同手掌的兩面,只需輕輕一翻,從業者的境遇便會有天翻地覆的改變。前端

人工智能做爲近兩年來業界公認的熱門領域,不一樣於以前火熱的移動端開發或前端開發,其距離傳統軟件開發行業之遠,入門門檻之高,都是以往未曾出現過的,這也讓許多但願可以終身學習,持續關注行業發展的軟件工程師們望而卻步。git

在咱們進一步討論阻止傳統軟件工程師向人工智能領域轉型的障礙以前,讓咱們先來明確幾個名詞的定義:github

  • 人工智能:以機器爲載體展示出的人類智能,如圖像識別等傳統計算機沒法完成的工做
  • 機器學習:一種實現人工智能的方式
  • 深度學習:一種實現機器學習的技術

那麼究竟是哪些障礙阻止了傳統軟件工程師進入人工智能領域呢?後端

  • 數學:不一樣於後端,前端,移動端等不一樣領域之間的區別,人工智能,或者說咱們接下來將要重點討論的深度學習,是一門以數學爲基礎的科學。學習它的前置條件,再也不是搭建某一個開發環境,瞭解某一門框架,而是須要去理解一些諸如矩陣,反向傳播,梯度降低等數學概念。
  • 生態:好久以來,學術界都以 Python 做爲其研究的默認語言,創造瞭如 NumPyMatplotlib 等一系列優秀的科學計算工具。而對於終日與用戶界面打交道的 Web 開發者來講,一系列基礎工具的缺少直接致使了哪怕是創建起來一個最基礎的深度學習模型都異常困難。

爲了解決上面提到的這兩個障礙,筆者使用 TypeScript 以零依賴的方式初步完成了一個基於 JavaScript 的深度學習框架:deeplearning-js,但願能夠以 Web 開發者熟悉的語言與生態爲各位提供一種門檻更低的深度學習的入門方式,並將以寫給 Web 開發者的深度學習教程這一系列文章,幫助各位理解深度學習的基本思路以及其中涉及到的數學概念。數組

總體架構

src/
├── activationFunction // 激活函數
│   ├── index.ts
│   ├── linear.spec.ts
│   ├── linear.ts
│   ├── linearBackward.spec.ts
│   ├── linearBackward.ts
│   ├── relu.spec.ts
│   ├── relu.ts
│   ├── reluBackward.spec.ts
│   ├── reluBackward.ts
│   ├── sigmoid.spec.ts
│   ├── sigmoid.ts
│   ├── sigmoidBackward.spec.ts
│   ├── sigmoidBackward.ts
│   ├── softmax.spec.ts
│   ├── softmax.ts
│   ├── softmaxBackward.spec.ts
│   └── softmaxBackward.ts
├── costFunction // 損失函數
│   ├── crossEntropyCost.spec.ts
│   ├── crossEntropyCost.ts
│   ├── crossEntropyCostBackward.spec.ts
│   ├── crossEntropyCostBackward.ts
│   ├── index.ts
│   ├── quadraticCost.spec.ts
│   ├── quadraticCost.ts
│   ├── quadraticCostBackward.spec.ts
│   └── quadraticCostBackward.ts
├── data // 數據結構:矩陣 & 標量
│   ├── Array2D.spec.ts
│   ├── Array2D.ts
│   ├── Scalar.spec.ts
│   ├── Scalar.ts
│   └── index.ts
├── index.ts
├── math // 計算:矩陣計算函數 & 生成隨機數矩陣 & 生成零矩陣
│   ├── add.spec.ts
│   ├── add.ts
│   ├── divide.spec.ts
│   ├── divide.ts
│   ├── dot.spec.ts
│   ├── dot.ts
│   ├── index.ts
│   ├── multiply.spec.ts
│   ├── multiply.ts
│   ├── randn.spec.ts
│   ├── randn.ts
│   ├── subtract.spec.ts
│   ├── subtract.ts
│   ├── transpose.spec.ts
│   ├── transpose.ts
│   ├── zeros.spec.ts
│   └── zeros.ts
├── model // 模型:初始化參數 & 正向傳播 & 反向傳播 & 更新參數
│   ├── Cache.ts
│   ├── backPropagation.ts
│   ├── forwardPropagation.ts
│   ├── index.ts
│   ├── initializeParameters.spec.ts
│   ├── initializeParameters.ts
│   ├── train.ts
│   └── updateParameters.ts
├── preprocess // 數據預處理:數據標準化
│   ├── index.ts
│   └── normalization
│       ├── index.ts
│       ├── meanNormalization.spec.ts
│       ├── meanNormalization.ts
│       ├── rescaling.spec.ts
│       └── rescaling.ts
└── utils // 幫助函數:數據結構轉換 & 矩陣廣播
    ├── broadcasting.spec.ts
    ├── broadcasting.ts
    ├── convertArray1DToArray2D.ts
    ├── convertArray2DToArray1D.ts
    └── index.ts
複製代碼

做爲一個專一於深度學習自己的框架,deeplearning-js 只負責構建及訓練深度學習模型,使用者可使用提供的 API 在任意數據集的基礎上搭建深度學習模型並得到訓練後的結果,具體的例子各位能夠參考 Logistic regressionbash

咱們將學習率,迭代次數,隱藏層神經元個數等這些超參數暴露給終端用戶,deeplearning-js 會自動調整模型,給出不一樣的輸出。基於這些輸出,咱們就能夠自由地使用任意圖表或可視化庫來展示模型訓練後的結果。數據結構

另外,你們在閱讀本系列文章的同時,建議配合着 deeplearning-js 的源碼一塊兒閱讀,相信這樣的話,你將會對深度學習到底在作一件什麼樣的事情有一個更感性的認識。架構

向量化

不一樣於其餘的機器學習教程,咱們並不但願在一開始就將大量拗口的數學名詞及概念灌輸給你們,相反,咱們將從訓練深度學習模型的第一步數據處理講起。框架

讓咱們以學術界很是著名的 Iris 數據集爲例。機器學習

如今咱們擁有了 150 個分別屬於 3 個品種的鳶尾屬植物的花萼長度,寬度及花瓣長度,寬度的樣本數據,目的是訓練一個輸入任意一個鳶尾屬植物的花萼長度,寬度及花瓣長度,寬度,判斷它是不是這 3 個品種中的某一個品種,即邏輯迴歸。

雖然咱們的最終模型是輸入任意一個樣本數據獲得結果,但咱們在訓練時,並不但願每次只可以輸入一個樣本數據,而是但願一次性地輸入全部樣本數據,獲得訓練結果與實際結果的差值,而後使用反向傳播來修正這些差別。

因而咱們就須要將多個樣本數據組合成一個矩陣,以下圖所示:

matrix

在將數據向量化後,咱們纔有了處理大數據集的能力,即在整個數據集上而不是在某個數據樣本上訓練模型。這也是爲何在深度學習領域,GPU 比 CPU 要快得多的緣由。在訓練深度學習模型時,全部的計算都是基於矩陣的,因而並行計算架構(處理多任務時計算時間等於最複雜任務的完成時間)的 GPU 就要比串行計算架構(處理多任務時計算時間等於全部任務運行時間的總和)的 CPU 快得多。

細心的讀者可能會觀察到上圖中的一個數據樣本中的不一樣維度的數據是豎排列的,這與傳統數組中數據的橫排列方式剛好相反,即咱們須要將

[5.1, 3.5, 1.4, 0.2]複製代碼

轉換爲

[
  [5.1],
  [3.5],
  [1.4],
  [0.2],
]複製代碼

細心的讀者可能又會問了,如 Iris 數據集,爲何必定要將初始數據轉換爲 4 行 150 列的矩陣,用方便處理的 150 行 4 列的矩陣不能夠嗎?

對於這個問題有如下兩方面的考慮。在接下來輸入數據與隱藏層作矩陣點乘時

equation1

隱藏層矩陣(W)的列數須要等於輸入層(A)的行數,因此爲了減小沒必要要的計算量,咱們但願輸入層的行數儘量得小,因而咱們將數據樣本的維度數與樣本數量進行對比,不可貴出在絕大多數狀況下,數據樣本的維度數都遠遠小於樣本數量這個結論。另外一方面,在點乘以後,結果矩陣的列數將等於輸入層的列數,也就是說若是咱們但願咱們的輸出是一個 [X, 150] 的矩陣,輸入層就須要是一個 [4, 150] 的矩陣。

那麼如何快速地在原始數據集與使用數據集之間進行這樣的轉換呢?這就涉及到矩陣的一個經常使用運算,矩陣轉置了。

矩陣

提及矩陣,它的許多奇怪的特性,如轉置,點乘等,想必是許多朋友大學時代的噩夢。在這裏咱們不談具體的數學概念,先嚐試用幾句話來描述一下矩陣及它的基礎運算。

從最直觀的角度來說,肯定一個矩陣須要哪些信息?一是矩陣的形狀,即座標系(空間),二是矩陣在這個座標系下各個維度上的值(位置)。

  • 矩陣(Array2D):N 維空間中的一個物體,在每一維度上都有其肯定的位置
  • 矩陣相加(add):在某一維度或多個維度上對原物體進行拉伸
  • 矩陣相減(subtract):在某一維度或多個維度上對原物體進行裁剪
  • 矩陣相乘(multiply):基於原物體的某一個原點對原物體進行等比放大
  • 矩陣相除(divide):基於原物體的某一個原點對原物體進行等比縮放
  • 矩陣轉置(transpose):基於原物體的原點對原物體進行翻轉
  • 矩陣點乘(dot):對原物體進行左邊矩陣所描述的位置轉換,即移動

在 deeplearning-js 中咱們使用二維數組的數據結構來表示矩陣,對於上述運算的具體代碼實現各位能夠參考 Array2D

一個簡單的數據轉換的例子以下:

function formatDataSet(dataset: Array<any>) {
  const datasetSize = dataset.length;
  let inputValues: Array<number> = [];

  map(dataset, (example: {
    "sepalLength": number,
    "sepalWidth": number,
    "petalLength": number,
    "petalWidth": number,
    "species": string,
  }) => {
    const input: any = omit(example, 'species');
    inputValues = inputValues.concat(values(input));
  });

  const input = new Array2D(
    [datasetSize, inputValues.length / datasetSize],
    inputValues,
  ).transpose();

  return input;
}複製代碼

小結

在理解了數據向量化及矩陣的概念後,相信你們已經能夠將大樣本量,以數組形式存儲的數據轉換爲適合進行深度學習模型訓練的大型矩陣了,接下來讓咱們從如何初始化參數開始,一步步搭建咱們的第一個深度學習模型。

相關文章
相關標籤/搜索