2020/01/20

CTF Steganography 語音資料隱寫

CTF(Capture The Flag)奪旗賽,在網絡安全領域中指的是網絡安全技術人員之間進行技術競技的一種比賽形式。其中有一項 STEGA(Steganography 隱寫),題目的Flag會隱藏到圖片、語音、視訊等各類數位化資料中,透過一些工具與技術,取得隱藏在其中的 flag。

HelloKittyKitty.wav

這個網頁 Nuit du Hack CTF Qualifications: Here, kitty kitty! 裡面有個 HelloKittyKitty.wav,直接聽就是一段喵喵的音樂,但是如果用 Adudacity 或是 Praat 打開,可看到語音檔有兩個聲道,其中左聲道有一些看起來不像語音的資料。

這些是 morse code,將長短符號記錄下來是

..... -... -.-. ----. ..--- ..... -.... ....- ----. -.-. -... ----- .---- ---.. ---.. ..-. ..... ..--- . -.... .---- --... -.. --... ----- ----. ..--- ----. .---- ----. .---- -.-.

送到 online morse code decoder 解碼可得到 5BC925649CB0188F52E617D70929191C

看起來很像是 md5 string,用 md5 decoder (md5應該是無法解密的,但網站用一個很大的 DB 把很多 string 的 md5 結果記錄起來查詢) 反查可得到 valar dohaeris

Here with your eyes

這個連結有一個 sound.wav,直接聽像是鳥叫聲,用 Adudacity 打開,看起來也跟一般聲音檔差不多。但如果將聲音檔改成頻譜圖的顯示,畫面上可直接看到這樣的字串。

flag: e5353bb7b57578bd4da1c898a8e2d767

dtmf

這個 dial.wav 聽起來,就像是撥打電話數字鍵盤的 dtmf tone 語音,用 dtmf decoder 可解出

4*7# 2*6# 1*2# 2*5# 2*3# 3*6# 2*6# 2*6# 3*6# 2*5# 3*4# 1*2

4*7 就是 T9 鍵盤的 7 按四次,可得到 s,依此類推

s  n  a  k  e  o  n  n  o  k  i  a

godwave.wav

ref: JarvisOJ Misc 部分題解

JarvisOJ Misc 上帝之音

[misc]上帝之音 400

godwave.wav 用 Audacity 打開來看,是個 mono, 10000000 Hz 的 wav,放大來看,是以振幅儲存類似 on-off keying OOK 的編碼

先以程式讀取 wav,由列印結果發現,大約每 64 個資料點做區隔,可用這些資料點的總和區分 0 or 1,再來列印結果,知道資料只有 01 10 兩種,符合 Manchaster 編碼的特性。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import wave
import matplotlib.pyplot as plt
import numpy as np

def read_wav_data(filename):
  wav = wave.open(filename,"rb") # 打開一個wav格式的聲音文件流
  frame = wav.getnframes() # 獲取幀數
  channel=wav.getnchannels() # 獲取聲道數
  framerate=wav.getframerate() # 獲取幀速率
  sample_width=wav.getsampwidth() # 獲取實例的比特寬度,即每一幀的字節數

  print("frame=%i, channel=%i, framerate=%i, sample_width=%i" %(frame, channel, framerate, sample_width))

  str_data = wav.readframes(frame) # 讀取全部的幀
  wav.close() # 關閉流
  wave_data = np.fromstring(str_data, dtype = np.int16) # 將聲音文件數據轉換為數組矩陣形式
  wave_data.shape = -1, channel # 按照聲道數將數組整形,單聲道時候是一列數組,雙聲道時候是兩列的矩陣
  wave_data = wave_data.T # 將矩陣轉置
  wave_data = wave_data
  return wave_data, frame, framerate

def wav_show(wave_data, fs): # 顯示出來聲音波形
  time = np.arange(0, len(wave_data)) * (1.0/fs)  # 計算聲音的播放時間,單位為秒
  # 畫聲音波形
  plt.plot(time, wave_data)
  plt.show()

if(__name__=='__main__'):
  # frame=1735680, channel=1, framerate=10000000, sample_width=2
  wave_data, frame, framerate = read_wav_data("godwave.wav")
  # wav_show(wave_data[0],fs)
  # wav_show(wave_data[1],fs)  # 如果是雙聲道則保留這一行,否則刪掉這一行

  # for i in range(300):
  #   print( "{index} = {val}".format(index = (i+1), val = abs(wave_data[0][i])) )
  # 由列印結果發現,大約每 64 個資料點做區隔,可用這些資料點的總和區分 0 or 1

  string = ''
  norm = 0
  for i in range(frame):
    norm = norm+abs(wave_data[0][i])
    if (i+1) % 64 == 0:
      # print("norm={norm}".format(norm=norm))
# norm=1244430
# norm=25330
# norm=32868
# norm=1118668
      if norm > 100000:
          string += '1'
      else:
          string += '0'
      # 用這個方式觀察,知道資料只有 01 10 兩種,符合 Manchaster 編碼的特性
      # if (i+1) % (128*8) == 0:
      #     string += "\n"
      norm = 0
  with open('output.txt','w') as output:
    output.writelines(string)
# -*- coding: utf-8 -*-
with open('output.txt', 'r') as f:
    data = f.readline()
    # print len(data)
    count = 0
    res = 0
    ans = b''
    key = ""

    pactemp = ""

    while data != '':
        pac = data[:2]
        data = data[2:]
        pactemp = pactemp + pac
        # print("pac={pac}".format(pac=pac))
        if pac != '':
            if pac[0] == '0' and pac[1] == '1':
                # print("res={res}".format(res=res))
                res = (res<<1)|0
                count = count + 1
            if pac[0] == '1' and pac[1] == '0':
                # print("res={res}".format(res=res))
                res = (res<<1)|1
                count = count + 1
            if count == 8:
                # print("pactemp={pactemp}, res={res}".format(pactemp=pactemp, res=res))
                ans += res.to_bytes(1, byteorder='big', signed=False)
                # ans += bytes(res)
                count = 0
                res = 0

                pactemp = ""
                pac = ""
        else:
            break

# print("ans={ans}".format(ans=ans))

with open('out.png', 'wb') as f2:
    f2.write(ans)

Manchaster解碼 結果是一張 png 圖。打開後是一張 QR code 圖片,放到 qr code decoder 後取得結果:

Raw text PCTF{Good_Signal_Analyzer}
Raw bytes 41 a5 04 35 44 67 b4 76 f6 f6 45 f5 36 96 76 e6 16 c5 f4 16 e6 16 c7 97 a6 57 27 d0 ec 11 ec 11 ec 11 ec 11
Barcode format QR_CODE
Parsed Result Type TEXT
Parsed Result PCTF{Good_Signal_Analyzer}

godwavefsk.wav

godwavefsk.wav 用 Audacity 打開來看,是個 mono, 10000000 Hz 的 wav,放大來看,是以頻率儲存類似 on-off keying OOK 的編碼

根據上一個點的資料,判斷是不是由負數變成正數,計算改變的次數,如果改變的次數超過 8 次,就表示這是頻率比較大的區段

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import wave
import matplotlib.pyplot as plt
import numpy as np

def read_wav_data(filename):
  wav = wave.open(filename,"rb") # 打開一個wav格式的聲音文件流
  frame = wav.getnframes() # 獲取幀數
  channel=wav.getnchannels() # 獲取聲道數
  framerate=wav.getframerate() # 獲取幀速率
  sample_width=wav.getsampwidth() # 獲取實例的比特寬度,即每一幀的字節數

  print("frame=%i, channel=%i, framerate=%i, sample_width=%i" %(frame, channel, framerate, sample_width))

  str_data = wav.readframes(frame) # 讀取全部的幀
  wav.close() # 關閉流
  wave_data = np.fromstring(str_data, dtype = np.int16) # 將聲音文件數據轉換為數組矩陣形式
  wave_data.shape = -1, channel # 按照聲道數將數組整形,單聲道時候是一列數組,雙聲道時候是兩列的矩陣
  wave_data = wave_data.T # 將矩陣轉置
  wave_data = wave_data
  return wave_data, frame, framerate

def wav_show(wave_data, fs): # 顯示出來聲音波形
  time = np.arange(0, len(wave_data)) * (1.0/fs)  # 計算聲音的播放時間,單位為秒
  # 畫聲音波形
  plt.plot(time, wave_data)
  plt.show()

if(__name__=='__main__'):
  # frame=1988608, channel=1, framerate=10000000, sample_width=2
  wave_data, frame, framerate = read_wav_data("godwavefsk.wav")
  # wav_show(wave_data[0],fs)
  # wav_show(wave_data[1],fs)  # 如果是雙聲道則保留這一行,否則刪掉這一行

  # for i in range(300):
  #   print( "{index} = {val}".format(index = (i+1), val = wave_data[0][i]) )

  string = ''
  old_wave_data = 0
  count = 0
  for i in range(frame):
    # 根據上一個點的資料,判斷是不是由負數變成正數,計算改變的次數,如果改變的次數超過 8 次,就表示這是頻率比較大的區段
    if( old_wave_data < 0 and wave_data[0][i]>0 ):
      # print("old_wave_data={old_wave_data}, wave_data[0][i]={new_wave_data}".format(old_wave_data=old_wave_data, new_wave_data=wave_data[0][i]))
      count = count + 1
    old_wave_data = wave_data[0][i]
    if (i+1) % 64 == 0:
      # print("count={count}".format(count=count))
      if count > 8:
          string += '1'
      else:
          string += '0'
      # 用這個方式觀察,知道資料只有 01 10 兩種,符合 Manchaster 編碼的特性
      # if (i+1) % (128*8) == 0:
      #     string += "\n"
      count = 0
  with open('output.txt','w') as output:
    output.writelines(string)

用上一個例子中將 output.txt 的 Manchaster 解碼程式,輸出 png,可看到是一個 QR code。

Raw text CTF{Nice_FSK_D3ModUl47o2}
Raw bytes 41 94 35 44 67 b4 e6 96 36 55 f4 65 34 b5 f4 43 34 d6 f6 45 56 c3 43 76 f3 27 d0 ec 11 ec 11 ec 11 ec 11 ec
Barcode format QR_CODE
Parsed Result Type TEXT
Parsed Result CTF{Nice_FSK_D3ModUl47o2}

References

CTF學習規劃————1、如何入門CTF

關於ctf競賽訓練 積累的資料

ctf-stego彙總

CTF中音頻隱寫的一些整理總結

音頻隱寫

資料隱藏技術揭祕筆記

2020/01/13

DeepLearning常見名詞

Deep Learning 神經網路模型的發展大約歷經四個時期:

  1. 1950~1960 基本的感知器,目前公認鼻祖是 1957 年提出 Perceptron 演算法的 Rosenblatt
  2. 1970~1980 發現 multilayer perceptron 多層感知器,有高度非線性函數的能力,甚至有神經網路能解決所有問題的論點
  3. 1990~2000 傳統神經網路沈寂,kernel 方法大行其道,主因是機器運算能力不足,資料量太大,網際網路尚未普及。
  4. 2006年以後,有些技術進步,促成深度學習的神經網路模型的大量應用。廉價平行計算 GPU 出現,網際網路普及,更容易取得大規模的資料。

通常深度學習適合處理資料量大、具備某些資料規則,但決策函數高度非線性的問題。例如圖形辨識、語音辨識、文字產生、自然語言分析、手寫數字辨識等等。

神經網路的四類函數

  1. 組合函數 combination function 輸入層後面的網路中,每一個神經元的功能都是將上一層產生的向量,透過本身的函數產生一個標量值,此標量就是下一個神經元輸入變數。這種在網路中間,將向量映射為標量的函數,就稱為組合函數。常見的組合函數包含線性組合函數與基於歐幾里德距離的函數。

  2. 啟動函數 activation function 大部分的神經元都將一維向量的網路輸入變數,透過某個函數映射為另一個一維向量的數值,這個函數稱為啟動函數,產生的值就稱為啟動狀態。

    除了輸出層以外,啟動狀態的值透過神經的連結,輸入到下一層的一個或多個神經元中。因啟動函數通常是將一個實數域的值映射到有限域,也稱為塌陷函數。

    ex: tanh, logistic 都是將實數域映射到 (-1,1) 或 (0,1) 之間

    啟動函數的主要作用是給隱含層引入非線性。一個只有線性關係隱含層的多層神經網路,不會比一般只包含輸入層與輸出層的兩層神經網路更強大。但加入非線性後,多層神經網路的預測能力就提高很多。

    對反向傳播演算法來說,啟動函數必須可微分,如果該函數是在有限域,效果更好,例如 logistic, tanh, 高斯函數,這些函數又稱為 sigmoid 函數。tanh, arctan 這種包含正負值域的函數,通常收斂速度較快,這是因為 conditioning number 更好。

    隱藏層的啟動函數歷經 sigmoid 到 threshold 的轉變,反映了 deep learning 技術和理論的發展。

    早期認為sigmoid 比 threhold 函數好,因為 threshold 函數的誤差函數是 stepwise constant,一階導數為不存在或是 0,導致無法進行有效的反向傳播演算法計算。sigmoid 是連續可微分的函數,參數一點變化,就會帶來輸出的變化,有助於判斷參數變動是否有利於最終函數的最佳化。而 threshold 的參數微小變化,不影響輸出,所以演算法收斂速度較慢。

    但 1991年 Sepp Hochreiter 發現,sigmoid 有 gradient vanishing 的問題。梯度消失就是梯度(誤差的訊號)隨著隱藏層數的增加,會指數減少。因反向傳播演算法的梯度計算採用鏈式法則,第 n 層需要乘以前面各層的梯度,但由於 sigmoid 的值域在 (-1,1) 或 (0,1) 之間,很多個很小的數相乘後,第n層的梯度就會趨近於 0,造成模型訓練的問題。而 threshold 啟動函數的值域不是 (-1,1),因此沒有這個問題,ex: reLU 的值域是 (0, +inf)。

    另外 Hard Max 這個 threshold 啟動函數: max(0,x) 可在隱藏層加入稀疏性 sparsity,有助於模型的訓練。

    對於輸出層,應該盡量選擇適合變數分佈的啟動函數

    • 對只有 0, 1 取值的雙值因變數,可使用 logistic 函數
    • 對有多個取值的離散因變數,例如 0~9 數字辨識,可使用 softmax (logistic 的自然衍生函數)
    • 對有限值域的連續因變數,可使用 logistic 或 tanh 啟動函數,但需要將因變數的值域伸縮到 logistic/tanh 對應的值域中
    • 如果因變數取值為正,但沒有上限,可使用指數函數
    • 如果因變數沒有有限值域,或雖是有限值域但邊界未知,可使用線性函數

    輸出層的啟動函數,選擇方法跟統計學模型的應用類似,就是統計學廣義線性模型的連結函數 link function 功能。

  3. 誤差函數 error function 監督學習的神經網路都需要一個函數,量測模型輸出值 p 跟真實因變數值 y 的差異,一般稱為誤差。但這個值不能直接用來衡量模型的品質。

    如果有一個完美的模型,其誤差為 0,但一般誤差為偏離0 的絕對值。誤差越趨近 0 越好。誤差函數也稱為損失函數 loss function。

    常用的 loss function:

    • 均方誤差(MSE):\( \frac{1}{n} \sum_{i=1}^{n} ( y^{(i)} - f_𝜃(x^{(i)} ) )^2 \) 通常用在實數值域連續變數的迴歸問題,對於誤差較大的情況給予更多權重。

    • 平均絕對誤差(MAE):\( \frac{1}{n} \sum_{i=1}^{n} | y^{(i)} - p^{i} | \) 應用在迴歸問題,或是時間序列預測問題。每個誤差對總體誤差的貢獻,與其誤差的絕對值成線性比例,MSE 沒有這個特性。

    • 交叉熵損失(cross entropy):也稱為對數損失函數,是針對分類模型的效能比較,依照分類模型是二分類或是多分類,還可分為二分類交叉熵與多分類交叉熵兩種。 \( \sum_{c=1}^{C} \sum_{i=1}^{N} -y_{c,i} log_2(p_{c,i}) \)

      C是類別數,N 是所有的資料數。\( y_{c,i} \) 是binary indicator (0 or 1) from one hot encode (第i筆資料屬於第c類真實類別)。\( p_{c,i} \)是第i筆資料屬於第c類預測出來的機率。

      cross-entropy 就是映射到最可能類別機率的對數,當預測值的分佈跟實際因變數的分佈一致時,交叉熵最小。

  4. 目標函數 object function 在訓練階段直接最小化的函數。神經網路的訓練結果,是在最小化訓練資料的預測值與真實值的誤差。結果可能會發生 overfitting 的狀況。例如模型在訓練資料上表現很好,但在測試資料或真實應用時,表現較差。也就是模型普適化不好。

    一般會用正規化,減少 overfitting 的狀況。這時目標函數為誤差函數與正規函數的和。例如採用 weight decay 方法時,正規函數是權重的平方和,這跟一般 ridge regression 的技巧一樣。如果使用貝氏定理,也可以將權重先驗分佈的對數,作為正則項。如果不使用正則項,目標函數就和總體或平均誤差函數一樣。

批量 Batch

有兩種

  1. 對應到模型訓練方法,就是將所有資料處理完後,一次性更新權重或參數的估計值

  2. 對應到模型訓練中的資料,是指一次輸入模型計算的資料量

批量概念的模型訓練,一般是以下步驟:

  1. 初始化參數
  2. 重複以下步驟 2.1 處理所有資料 2.2 更新參數

遞增演算法的步驟

  1. 初始化參數
  2. 重複以下步驟 2.1 處理一個或一組資料點 2.2 更新參數

差別是批量演算法一次處理所有資料,遞增演算法,處理一或多個資料,就要更新一次參數。

反向傳播演算法中,「處理」 就是計算損失函數的梯度變化曲線。 批量驗算法中,「處理」 是計算平均或總體損失函數的梯度變化曲線 遞增演算法中,損失函數僅計算對應於該觀測值或數個觀測值的梯度變化曲線。

「更新」 是從既有的參數值,減去梯度變化率和學習速率的乘積。

online/offline learning

online learning 的觀測值,處理後就會被丟棄,同時更新參數。永遠是一種遞增演算法。

offline learning 的資料可重複取得,有以下優點

  1. 對任何固定個數的參數,可直接計算出目標函數,很容易驗證模型訓練是否往所需方向前進
  2. 計算精度可達到任意合理的程度
  3. 可使用各種不同的演算法,避免局部最佳化的情況
  4. 可採用訓練、驗證、測試三分法,針對模型的普適化進行驗證
  5. 可計算預測值及其信賴區間

online learning 不儲存資料,無法重複取得資料,因此無法在訓練集上計算 loss function,無法在驗證集上計算誤差。所以online learning 演算法比較不穩定。

bias 偏移值

通常在計算網路輸入時,會加入一個 bias 偏移值。如果是線性輸出神經元,bias 就是線性迴歸中的截距。

跟截距的作用類似,bias 視為一個由特殊神經元引出的連結權重,這是因為它通常連結到一個固定單位值的偏移神經元。例如在多層感知器 MLP 神經網路中,某一個神經元的輸入參數為 N 維,這個神經元在高維空間根據參數畫出一個超平面,一邊是正值,一邊是負值。參數決定了超平面在輸入空間的相對位置,如果沒有 bias,超平面就需要通過原點,這就限制了平面的位置。

每個隱藏層和輸出層的神經元都有自己的 bias,但如果輸入資料已經等比例轉換到一個有限值域,例如 [0,1]區間,那麼第一個隱藏層的神經元設定 bias 後,後面任何層內跟這個神經原有連結的其他神經元,就不需要再設定 bias 了。

標準化資料

有三種常見的標準化

  1. rescaling 加上一個向量,或減去一個常數,再乘上或除以一個常數。例如將華氏溫度轉換為攝氏溫度。

  2. normalization 將一個向量除以其範數,例如採用歐幾里得距離,則使用向量的變異量數作為範數來正規化向量。deep learning 通常用全距作為範數,也就是向量減去最小值,然後除以全距,可讓數值落於 0~1 之間

  3. standardization 將一個向量移除其位置與規模的度量。例如遵循常態分佈的向量,可減去平均值,除以變異數,來標準化資料。可得到一個遵守標準常態分佈的向量。

在 deep learning 要視情況決定要不要做標準化,一般來說,如果啟動函數的值域在 0~1 之間,那麼正規化資料到 [0,1] 比較合理,另外正規化資料,能讓計算過程穩定,特別是資料值域範圍有較大差別的時候。

梯度下降演算法

最佳化決策函數時,通常是針對一個誤差的度量(例如誤差的平方),求得一系列參數,然後最小化這個誤差度量的值來進行。目前一般採用 gradient descent method 梯度下降演算法。

該方法類似遊客在不知名的高山要儘快安全到達谷底,他必須在東西、南北兩個軸向進行選擇,以確保下山路徑又快又安全。如果把軸向想成目標函數的兩個維度,那麼該怎麼取得最佳路徑呢?

因為在山頂不知道路況,有可能因為初始化參數不佳,造成只能得到局部最佳解的狀況。梯度下降法是一種短是的方法,只靠一個傾斜角,看那個方向比較陡,就往該方向下滑一段距離。

通常會用隨機梯度下降法 stochastic gradient descent,這是針對每個觀測值執行梯度下降的最佳化演算,原本的方法稱為 batch 或 offline 演算法,新的方法稱為 incremental 或 online 演算法,因為參數估計值會隨著觀測值的變化而更新。

目前常見是使用一階導函數的梯度下降法,也有基於二階導函數的演算法,稱為牛頓法。這種方法相當於遊客帶了一個高度計,滑下去後,就可查閱結果,如果比原來高,就退回原來的地方,重新跳一小步。

假設有一個函數 f 有兩個變數 \(x_0, x_1\) ,則 f 的梯度法就是

\( x_0' = x_0 - 𝜂 \frac{𝜕f}{𝜕x_0} \)

\( x_1' = x_1 - 𝜂 \frac{𝜕f}{𝜕x_1} \)

其中 𝜂 是學習率,就是一次學習中,要往前增加多少步進值,在訓練前,要先決定 𝜂 是 0.1 或 0.001 或某一個數值,這個值太大,可能會發生找不到最佳解的狀況,太小,則會發生學習速度太慢 (loss rate 下降太慢)的狀況。

因此 𝜂 學習率這種參數稱為 hyperparameter,這跟 weight 權重、bias 不同,這是一個根據經驗設定與調整出來的一個人工設定的參數。

誤差反向傳播演算法

如果神經網路只有一層,只要反覆運用這個方法到 loss function,就可依照公式更新參數,直到收斂就好了。權重參數的 loss function 梯度,可以根據數值微分計算而來。微分的計算很簡單,但計算量大比較花時間。

但如果輸入層跟輸出層之間,有很多隱含層,就需要一個高效率的演算法,減少計算量。為了快速估計深度神經網路的權重值,backpropagation 就是這種演算法,可有效率地計算微分。以 computational graph 來理解這個演算法。

ref: Computational Graph

ref: DL3-Computational Graph & Backpropagation

ref: computational graph 李弘毅

node: 代表 variable (scalar, vector, tensor)

edge: 代表 operation (function)

\( y=f(g(h(x))) \) 可拆解為三個合成函數 \( u=h(x), v=g(u), y=f(v) \)

如果某個節點有兩個 input,可寫成 \( a=f(b,c) \),就是 b,c 兩個 node 都指向 a


這是 Computational Graph 的一個例子,要由下往上看


因為 computational graph 的概念就是合成函數,而合成函數在計算微分,可以用 chain rule。

case 1 的 \( \frac{dz}{dx} = \frac{dz}{dy}*\frac{dy}{dx} \) note: \( z=h(y) z=h(g(x)) \)

根據 \( \frac{𝜕e}{𝜕b}\) 的公式,\(c=a+b, d=b+1, e=c*d\),利用 computational graph,由下往上,計算每一個箭頭的偏微分,在合併用在原本的公式中

舉例 a=3, b=2,套用在剛剛的計算過程中

如果由 b=1 開始,往上計算可得到 \( \frac{𝜕e}{𝜕b}=8\) 。 可發現跟剛剛一樣,有用到 \( \frac{𝜕e}{𝜕c}=3, \frac{𝜕e}{𝜕d}=5, \frac{𝜕c}{𝜕a}=1, \frac{𝜕c}{𝜕b}=1, \frac{𝜕d}{𝜕b}=1\) 這些偏微分的結果

如果由 a=1 開始,往上計算可得到 \( \frac{𝜕e}{𝜕a}=3\) \( \frac{𝜕e}{𝜕b}=8\) ,跟剛剛一樣,有用到 \( \frac{𝜕e}{𝜕c}=3, \frac{𝜕e}{𝜕d}=5, \frac{𝜕c}{𝜕a}=1, \frac{𝜕c}{𝜕b}=1, \frac{𝜕d}{𝜕b}=1\) 這些偏微分的結果

\(c=a+b, d=b+1, e=c*d\),一樣使用 跟剛剛一樣,有用到 \( \frac{𝜕e}{𝜕c}=3, \frac{𝜕e}{𝜕d}=5, \frac{𝜕c}{𝜕a}=1, \frac{𝜕c}{𝜕b}=1, \frac{𝜕d}{𝜕b}=1\) 這些偏微分的結果, 反方向填寫,得到 \( \frac{𝜕e}{𝜕a}=3, \frac{𝜕e}{𝜕b}=8\)

ref: https://web.cs.hacettepe.edu.tr/~aykut/classes/spring2016/bbm406/slides/l12-backpropagation.pdf

首先有一個合成函數 \(f(x,y,z)\),當 \( x=-2, y=5, z=-4 \) 時,我們想要得到 \( \frac{𝜕f}{𝜕x}, \frac{𝜕f}{𝜕y}, \frac{𝜕f}{𝜕z} \) 的結果。一開始,就先用 computational graph 描述題目

在最右邊,寫上 \(\frac{𝜕f}{𝜕f}=1\)

因為 \( f=qz \),而 \( \frac{𝜕f}{𝜕z} = q\),因此就等於 3

因為 \( f=qz \),而 \( \frac{𝜕f}{𝜕q} = z\),因此就等於 -4

因為 \( q=x+y \),而 \( \frac{𝜕f}{𝜕y} = \frac{𝜕f}{𝜕q} \frac{𝜕q}{𝜕y} = -4 * 1 = -4 \),因此就等於 -4

因為 \( q=x+y \),而 \( \frac{𝜕f}{𝜕x} = \frac{𝜕f}{𝜕q} \frac{𝜕q}{𝜕x} = -4 * 1 = -4 \),因此就等於 -4

References

python 深度學習實作 keras 快速上手

深度學習:使用激勵函數的目的、如何選擇激勵函數 Deep Learning : the role of the activation function

生物神經元的工作原理對深度學習神經元的啟發

deep learning 用python進行深度學習的基礎理論實作

2020/01/06

Google Voice Kit v1

根據這兩個網址的說明,安裝 Google Voice Kit V1

Google Voice Kit V1

一口氣將 Voice Kit 連上 Google Cloud Platform — 實作篇

note: 注意變壓器必須要輸出 5V 2.5A,電流不夠會讓 RPi 一直 reboot

Voice HAT (Hardware Attached on Top)

GPIO pins used by Voice HAT

傳感器常用的 I2C, UART(14,15 還特意分出來了), SPI 都沒有佔用。 其餘的 GPIO 分為兩組並配備了電源接口,可以直接用作 GPIO,也可以控制外部設備:

  1. Servo0-Servo5:5v/25mA,小電流的 Servo 適合連接類似 LED 之類的設備。
  2. Dirver0-Driver4:5v/500mA,可以連接功率更大的設備 (例如 小車的馬達),+/- 極來自在板子左下角的外接電源。可以參考這個接法:https://www.raspberrypi.org/magpi/motor-aiy-voice-pi/

使用 Speech Recognition API & gTTS 套件

  • 用 Speech Recognition API 辨識中/英文

  • 用 gTTS 套件說中文

    gTTS: a Python library and CLI tool to interface with Google Translate's text-to-speech API,這是用 python 透過 google translate 轉換為語音的套件

  • 沒有智能語義 AI,不是 Google assistent

需要產生服務帳戶金鑰

由 Google Cloud Platform GCP 產生服務帳戶金鑰,並存放到 /home/pi/cloud_speech.json

測試程式 cloudspeech_demo.py

安裝 gTTS 相關套件

sudo python3 -m pip install gTTS
sudo python3 -m pip install pydub
sudo apt install ffmpeg

修改後的 cloudspeech_demo.py

#!/usr/bin/env python3
# Copyright 2017 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""A demo of the Google CloudSpeech recognizer."""
import argparse
import locale
import logging

from aiy.board import Board, Led
from aiy.cloudspeech import CloudSpeechClient
from aiy.voice import audio
from gtts import gTTS
from pydub import AudioSegment


def get_hints(language_code):
    if language_code.startswith('en_'):
        return ('turn on the light',
                'turn off the light',
                'blink the light',
                'goodbye')
    return None

def locale_language():
    language, _ = locale.getdefaultlocale()
    return language

def say(text, lang=None):
    if lang == None:
        tts = gTTS(text)
    else:
        tts = gTTS(text, lang)
     # 把文字變成gtts內建的物件
    tts.save('output.mp3') # 把得到的語音存成 output.mp3
    sound = AudioSegment.from_mp3('output.mp3') # 把 output.mp3 讀出
    sound.export('output.wav', format='wav') # 轉存成 output.wav
    audio.play_wav('output.wav') # 把 wav 用 VoiceKit 播出來

def main():
    # say('test')

    logging.basicConfig(level=logging.DEBUG)

    parser = argparse.ArgumentParser(description='Assistant service example.')
    parser.add_argument('--language', default=locale_language())
    args = parser.parse_args()

    logging.info('Initializing for language %s...', args.language)
    hints = get_hints(args.language)
    client = CloudSpeechClient()
    with Board() as board:
        while True:
            if hints:
                logging.info('Say something, e.g. %s.' % ', '.join(hints))
            else:
                logging.info('Say something.')
            text = client.recognize(language_code=args.language,
                                    hint_phrases=hints)
            if text is None:
                logging.info('You said nothing.')
                continue

            logging.info('You said: "%s"' % text)
            text = text.lower()
            if 'turn on the light' in text:
                board.led.state = Led.ON
                say('light is on')
            elif 'turn off the light' in text:
                board.led.state = Led.OFF
                say('light is off')
            elif 'blink the light' in text:
                board.led.state = Led.BLINK
                say('light is blinked')
            elif '開燈' in text:
                board.led.state = Led.ON
                say('燈開好了', 'zh-TW')
            elif '關燈' in text:
                board.led.state = Led.OFF
                say('燈關了', 'zh-TW')
            elif '閃燈' in text:
                board.led.state = Led.BLINK
                say('閃燈中', 'zh-TW')
            elif 'bye' in text:
                say('bye')
                break
            elif '結束' in text:
                say('再見', 'zh-TW')
                break
            elif 'goodbye' in text:
                say('bye')
                break

if __name__ == '__main__':
    main()

調整音量的指令

alsamixer

note:

pip3 install gTTS 出現 Error

Exception:
Traceback (most recent call last):
  File "/usr/share/python-wheels/urllib3-1.19.1-py2.py3-none-any.whl/urllib3/connectionpool.py", line 594, in urlopen
    chunked=chunked)
  File "/usr/share/python-wheels/urllib3-1.19.1-py2.py3-none-any.whl/urllib3/connectionpool.py", line 391, in _make_request
    six.raise_from(e, None)
  File "<string>", line 2, in raise_from
  File "/usr/share/python-wheels/urllib3-1.19.1-py2.py3-none-any.whl/urllib3/connectionpool.py", line 387, in _make_request
    httplib_response = conn.getresponse()
  File "/usr/lib/python3.5/http/client.py", line 1198, in getresponse
    response.begin()
  File "/usr/lib/python3.5/http/client.py", line 297, in begin
    version, status, reason = self._read_status()
  File "/usr/lib/python3.5/http/client.py", line 266, in _read_status
    raise RemoteDisconnected("Remote end closed connection without"
http.client.RemoteDisconnected: Remote end closed connection without response

During handling of the above exception, another exception occurred:

......
TypeError: unsupported operand type(s) for -=: 'Retry' and 'int'

解決方式:更新 requests

sudo python3 -m pip install --user --upgrade requests

解決方法:改用這個指令安裝

sudo python3 -m pip install gTTS

使用 Google Assistant Service API

需要產生 OAuth 用戶端 ID

note 設定過程中,會綁定到某一個帳號:

charley@maxkit.com.tw  google assistent auth code
4/qQGwcp38dePn7QQABHiXJwgS2Hhs6Jz6hG3sVX9l2Y6WIoUXgt5bFVo

存放到 /home/pi/assistant.json

程式使用內建 pico2wave 來合成語音,故不支援中文

demo 程式

  • ~/AIY-projects-python/src/examples/voice/assistantlibrarydemo.py

    ​ 要說 Hey Google 然後再問問題,只支援英文

  • ~/AIY-projects-python/src/examples/voice/assistantlibrarywithbuttondemo.py

    ​ 要說 Hey Google 或是按下按鈕,然後再問問題,只支援英文

  • assistantgrpcdemo.py

    ~/AIY-projects-python/src/examples/voice/
    
    ./assistant_grpc_demo.py

將 assitant demo 設定為開機自動啟動

ref: GOOGLE VOICE KIT AUTOSTART

sudo vim /etc/systemd/system/assistant.service

[Unit]
Description=Google Assistant
Wants=network-online.target
After=network-online.target
Wants=systemd-timesyncd.service
After=systemd-timesyncd.service

[Service]
Environment=DISPLAY=:0
Type=simple
ExecStart=/home/pi/AIY-projects-python/src/examples/voice/assistant_library_with_button_demo.py
Restart=on-failure
User=pi
Group=pi
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=google_assistant

[Install]
WantedBy=multi-user.target
sudo chmod 755 /etc/systemd/system/assistant.service

sudo systemctl daemon-reload
sudo systemctl enable assistant.service
sudo service assistant start

查閱 log

sudo journalctl -u assistant -f

客製化 hotword

ref: 語音喚醒技術的原理是什麼?

ref: 語音喚醒

語音喚醒在學術上被稱為keyword spotting(簡稱KWS),吳老師給它做了一個定義:在連續語流中實時檢測出說話人特定片段。

這裡要注意,檢測的「實時性」是一個關鍵點,語音喚醒的目的就是將設備從休眠狀態激活至運行狀態,所以喚醒詞說出之後,能立刻被檢測出來,用戶的體驗才會更好。

那麼,該怎樣評價語音喚醒的效果呢?通行的指標有四個方面,即喚醒率、誤喚醒、響應時間和功耗水平

➤喚醒率,指用戶交互的成功率,專業術語為召回率,即recall。

➤誤喚醒,用戶未進行交互而設備被喚醒的概率,一般按天計算,如最多一天一次。

➤響應時間,指從用戶說完喚醒詞後,到設備給出反饋的時間差。

➤功耗水平,即喚醒系統的耗電情況。很多智能設備是通過電池供電,需要滿足長時續航,對功耗水平就比較在意。

喚醒可以看成是一種小資源的關鍵詞檢索任務,其中小資源是指計算資源比較小和空間存儲資源比較小,因此它的系統框架跟關鍵詞檢索的系統會有一定的區別,目前常用的系統框架主要有Keyword/Filler Hidden Markov Model System和Deep KWS System兩種。

陳果果 kitt.ai 開發 DNN based snowboy 提供不同 OS 的 library,可自訂 hotword,百度在 2017 全資收購。

custom-hotword-for-aiy-voicekit

# 安裝 libatlas-base-dev
sudo apt-get install libatlas-base-dev

cd ~/AIY-voice-kit-python

src/examples/voice/assistant_grpc_demo_snowboy.py --language en-US --model src/mod/resources/alexa/alexa_02092017.umdl
# hotword: alexa

只有 alexa 可以用,其他的自訂 hotword,都沒辦法偵測到


note: 安裝 VNC

ref: [基礎] 以 VNC 和 Raspberry Pi 連線

ref: 基礎篇 - vnc連線

sudo apt-get install tightvncserver

vncserver
# 設定密碼

在 vnc viewer 使用 192.168.1.175:5901 連線

Reference

AIY project github

Custom Hotword for AIY Voice Kit

lattepanda-使用google-assistant玩互動語音助理