顯示具有 Numpy 標籤的文章。 顯示所有文章
顯示具有 Numpy 標籤的文章。 顯示所有文章

2022年12月6日 星期二

MicroPython 學習筆記 : 嵌入式裝置用的 Numpy - ulab 套件

最近我在閱讀市圖借來的 "用 Python 學 AIot 智慧聯網" (施威銘研究室, 2020) 這本書時, 在第四章看到 ulab 的介紹, 原來 MicroPython 也有迷你版的 Numpy 可以用, 讓我躍躍欲試. 這本薄書其實是旗標出版的 "創客-自造者" 系列中的教材, 盒裝內還包括 ESP32-WROM 開發板與若干感測器模組與零件 : 
此書特點是專注於利用 ESP32 開發板以 MicroPython 實作機器學習應用, 但因為 Numpy 太大塞不進像 ESP32 這樣的嵌入式設備, 所以有人就寫了 ulab 來代替, 如果要在 ESP32 上跑機器學習運算就會用到這個陣列運算模組, 其原始碼放在 GitHub :


ulab 的教學文件參考


注意, ulab 有許多版本, 用法與實作的函式範圍並不相同, 例如 CircuitPython 的 ulab 有實作 arange() 函式, 而原始 ulab 則沒有, 參考 : 


但是 ulab 沒有編譯好的套件可直接下載安裝, 例如 ESP32 連接 Internet 後用 upip instal() 安裝會出現 "Package not found" 訊息 : 

>>> import upip   
>>> upip.install('ulab')    
Installing to: /lib/
Warning: micropython.org SSL certificate is not validated
Error installing 'ulab': Package not found, packages may be partially installed

根據官網說明, 必須要將 ulab 與 MicroPython 原始碼一起編譯成韌體才行, 但看來這不是容易幹的活, 等有時間再來玩看看, 眼下最快的方式是從旗標網站下載書附範例, 其中含有旗標用 MicroPython 1.12 編譯好的韌體, 只要下載燒錄到 ESP32 板子上馬上就可以 import ulab 使用了, 下載網址如下 : 


解壓縮 zip 後, 在 "韌體" 資料夾下就可找到名為 "esp32-20200512-v1.12-195-gb16990425.bin" 的韌體, 將其燒錄到 ESP32 開發板即可. 這個韌體是將 ulab 與 BlynkLib 等套件與 MicroPython 原始碼一起編譯而成的特仕版, 與 MicroPython 官網下載的韌體功能不同. 

我用 Thonny 燒錄韌體過程都 OK, 但完成後卻讀不到串列埠而不能用, 不知原因為何 :







改用 esptool 於命令列燒錄就可以, 注意, 執行抹除與燒錄 Flash 指令之前要按住右下角的 ID0 Flash 鈕, 直到出現 % 進度時再放開 (D1 mini 則是要全程將 D3 接地, 完成後再拔掉) : 




D:\ESP32>esptool.py --chip esp32 --port COM9 erase_flash    
esptool.py v2.6
Serial port COM9
Connecting.....
Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
MAC: 80:7d:3a:b7:a7:5c
Uploading stub...
Running stub...
Stub running...
Erasing flash (this may take a while)...
Chip erase completed successfully in 4.3s
Hard resetting via RTS pin...

D:\ESP32>esptool.py --port COM9 flash_id    
esptool.py v2.6
Serial port COM9
Connecting........___
Detecting chip type... ESP32
Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
MAC: 80:7d:3a:b7:a7:5c
Uploading stub...
Running stub...
Stub running...
Manufacturer: 68
Device: 4016
Detected flash size: 4MB
Hard resetting via RTS pin...

D:\ESP32>esptool.py --chip esp32 --port COM9 write_flash -z 0x1000 esp32-20200512-v1.12-195-gb16990425.bin   
esptool.py v2.6
Serial port COM9
Connecting....
Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
MAC: 80:7d:3a:b7:a7:5c
Uploading stub...
Running stub...
Stub running...
Configuring flash size...
Auto-detected Flash size: 4MB
Compressed 1479888 bytes to 939384...
Wrote 1479888 bytes (939384 compressed) at 0x00001000 in 83.6 seconds (effective 141.6 kbit/s)...
Hash of data verified.

Leaving...
Hard resetting via RTS pin...

用 Putty 連線 OK, 可見還是在命令列用 esptool 燒錄最妥當 : 

ets Jun  8 2016 00:22:57

rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0018,len:4
load:0x3fff001c,len:5052
load:0x40078000,len:10600
load:0x40080400,len:5684
entry 0x400806bc
I (543) cpu_start: Pro cpu up.
I (543) cpu_start: Application information:
I (543) cpu_start: Compile time:     Mar  6 2020 17:07:32
I (546) cpu_start: ELF file SHA256:  0000000000000000...
I (552) cpu_start: ESP-IDF:          v3.3.1
I (557) cpu_start: Starting app cpu, entry point is 0x40082fb0
I (0) cpu_start: App cpu up.
I (567) heap_init: Initializing. RAM available for dynamic allocation:
I (574) heap_init: At 3FFAFF10 len 000000F0 (0 KiB): DRAM
I (580) heap_init: At 3FFB6388 len 00001C78 (7 KiB): DRAM
I (586) heap_init: At 3FFB9A20 len 00004108 (16 KiB): DRAM
I (593) heap_init: At 3FFBDB5C len 00000004 (0 KiB): DRAM
I (599) heap_init: At 3FFCC830 len 000137D0 (77 KiB): DRAM
I (605) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (611) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (618) heap_init: At 40099A80 len 00006580 (25 KiB): IRAM
I (624) cpu_start: Pro cpu start user code
I (307) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
MicroPython v1.12-195-gb16990425-dirty on 2020-05-12; ESP32 module with ESP32
Type "help()" for more information.
>>>

使用 ulab 前先用 import 匯入, 同樣取 np 簡名 (用法與 Numpy 一樣) : 

import ulab as np 

首先來檢視 ulab 內容 :

MicroPython v1.12-195-gb16990425-dirty on 2020-05-12; ESP32 module with ESP32
Type "help()" for more information.
>>> import ulab as np    
>>> dir(np)    
['__class__', '__name__', 'sort', 'sum', '__version__', 'acos', 'acosh', 'activation', 'argmax', 'argmin', 'argsort', 'array', 'asin', 'asinh', 'atan', 'atanh', 'averagePooling1D', 'ceil', 'conv1D', 'cos', 'det', 'diff', 'dot', 'eig', 'erf', 'erfc', 'exp', 'expm1', 'eye', 'fft', 'flip', 'float', 'floor', 'gamma', 'globalAveragePooling1D', 'globalMaxPooling1D', 'ifft', 'int16', 'int8', 'inv', 'lgamma', 'linspace', 'log', 'log10', 'log2', 'max', 'maxPooling1D', 'mean', 'min', 'ones', 'polyfit', 'polyval', 'roll', 'sin', 'sinh', 'size', 'spectrum', 'sqrt', 'std', 'tan', 'tanh', 'uint16', 'uint8', 'zeros']
>>> help(np)   
object <module 'ulab'> is of type module
  __name__ -- ulab
  __version__ -- 0.26.8f
  array -- <class 'ndarray'>
  size -- <function>
  inv -- <function>
  dot -- <function>
  zeros -- <function>
  ones -- <function>
  eye -- <function>
  det -- <function>
  eig -- <function>
  activation -- <function>
  conv1D -- <function>
  maxPooling1D -- <function>
  averagePooling1D -- <function>
  globalMaxPooling1D -- <function>
  globalAveragePooling1D -- <function>
  acos -- <function>
  acosh -- <function>
  asin -- <function>
  asinh -- <function>
  atan -- <function>
  atanh -- <function>
  ceil -- <function>
  cos -- <function>
  erf -- <function>
  erfc -- <function>
  exp -- <function>
  expm1 -- <function>
  floor -- <function>
  gamma -- <function>
  lgamma -- <function>
  log -- <function>
  log10 -- <function>
  log2 -- <function>
  sin -- <function>
  sinh -- <function>
  sqrt -- <function>
  tan -- <function>
  tanh -- <function>
  linspace -- <function>
  sum -- <function>
  mean -- <function>
  std -- <function>
  min -- <function>
  max -- <function>
  argmin -- <function>
  argmax -- <function>
  roll -- <function>
  flip -- <function>
  diff -- <function>
  sort -- <function>
  argsort -- <function>
  polyval -- <function>
  polyfit -- <function>
  fft -- <function>
  ifft -- <function>
  spectrum -- <function>
  uint8 -- 66
  int8 -- 98
  uint16 -- 72
  int16 -- 104
  float -- 102
>>>

可見不僅 Numpy 重要的函式都有實作, 連 SciPy 與機器學習的常用函式也納進來了. 

呼叫 np.array() 可將串列轉成 ndarray 陣列 :

>>> a=np.array([1, 2, 3, 4, 5])   
>>> a     
array([[1.0, 2.0, 3.0, 4.0, 5.0]], dtype=float)
>>> type(a)      
<class 'ndarray'>   

不過 ulab 沒有實作 arange() 函式, 必須用 Python 的 range() 代入 np.array() 中 :

>>> np.arange(12)    
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'arange'
>>> a=np.array(range(12))   
>>> a    
array([[0.0, 1.0, 2.0, ..., 9.0, 10.0, 11.0]], dtype=float)
>>> b=a.reshape((3, 4))     
>>> b     
array([[0.0, 1.0, 2.0, 3.0],
         [4.0, 5.0, 6.0, 7.0],
         [8.0, 9.0, 10.0, 11.0]], dtype=float)

好消息是 ulab 有實作 linspace() 函式, 可惜沒有 logspace() :

>>> np.linspace(0, 10)    
array([[0.0, 0.2040816, 0.4081633, ..., 9.591833, 9.795915, 9.999996]], dtype=float)
>>> np.logspace(0, 2)   
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'logspace'

有實作 zero() 但沒有 zero_like() :

>>> np.zeros(5)   
array([[0.0, 0.0, 0.0, 0.0, 0.0]], dtype=float)
>>> a=np.array([[1, 2 , 3], [4, 5 ,6]])   
>>> b=np.zeros_like(a)   
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'zeros_like'

有實作 one() 但沒有 one_like() :

>>> np.ones(5)   
array([[1.0, 1.0, 1.0, 1.0, 1.0]], dtype=float)
>>> a=np.array([[1, 2 , 3], [4, 5 ,6]])
>>> b=np.ones_like(a)     
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'ones_like'

沒有實作 identity() 與 full() :

>>> np.full((2, 3), 3.14159)   
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'full'
>>> np.identity(4)   
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'identity'

但有實作 eye() :

>>> np.eye(4)   
array([[1.0, 0.0, 0.0, 0.0],
         [0.0, 1.0, 0.0, 0.0],
         [0.0, 0.0, 1.0, 0.0],
         [0.0, 0.0, 0.0, 1.0]], dtype=float)

也沒有實作 diag() 與 empty() :

>>> a=np.array(range(25))   
>>> x=a.reshape((5,5))    
>>> np.diag(x)     
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'diag'
>>> np.empty(4)   
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'empty'

ulab 也未實作隨機數模組 random :

>>> np.random.rand(5)    
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'random' 

產生隨機數向量必須透過 MicroPython 的 rnadom 模組函式 :
  • random.randint(a, b) : 傳回 a~b 間的隨機整數 (含 a, b)
  • random.randrange([start=0, ] stop [.step]) : 傳回 start~stop 間隔之隨機整數 (不含 stop)
  • random.uniform(a, b) : 傳回 a~b 間的隨機浮點數 (含 a, b)
  • random.random() : 傳回 0~1 間之隨機浮點數
  • random.choice(sequence) : 隨機傳回 sequence (list/tuple) 中的一個
  • random.seed(s) : 設定隨機種子
參考 :


要產生隨機陣列要先產生 list 再用 np.array() 轉成陣列, 例如 : 

>>> import random   
>>> randomlist=[]   
>>> for i in range(0,12):   
    randomlist.append(random.randint(1,30))   
    
>>> randomlist   
[4, 22, 19, 18, 14, 17, 5, 29, 14, 4, 11, 10]
>>> a=np.array(randomlist)      
>>> a     
array([[4.0, 22.0, 19.0, ..., 4.0, 11.0, 10.0]], dtype=float)
>>> a.reshape((3, 4))   
array([[4.0, 22.0, 19.0, 18.0],
[14.0, 17.0, 5.0, 29.0],
[14.0, 4.0, 11.0, 10.0]], dtype=float)

雖然曲折點, 但還是能用. 

最後來驗證一下此特仕版韌體是否包含 BlynkLib 套件 :

>>> import BlynkLib   

    ___  __          __
   / _ )/ /_ _____  / /__
  / _  / / // / _ \/  '_/
 /____/_/\_, /_//_/_/\_\
        /___/ for Python v0.2.1 (esp32)

可見編譯的版本為 Blynk v0.2.1 版. 


參考 :


2022年7月19日 星期二

樹莓派執行 Pandas 出現 "Original error was: libf77blas.so.3"

晚上在佈署比特幣與乙太幣爬蟲程式到樹莓派時, 手動執行卻出現如下錯誤 : 

pi@raspberrypi:~ $ python3 btc_eth_prices_line_notify.py    
Traceback (most recent call last):
  File "btc_eth_prices_line_notify.py", line 2, in <module>
    import pandas as pd
  File "/home/pi/.local/lib/python3.7/site-packages/pandas/__init__.py", line 17, in <module>
    "Unable to import required dependencies:\n" + "\n".join(missing_dependencies)
ImportError: Unable to import required dependencies:
numpy:

IMPORTANT: PLEASE READ THIS FOR ADVICE ON HOW TO SOLVE THIS ISSUE!

Importing the numpy C-extensions failed. This error can happen for
many reasons, often due to issues with your setup or how NumPy was
installed.

We have compiled some common reasons and troubleshooting tips at:

    https://numpy.org/devdocs/user/troubleshooting-importerror.html

Please note and check the following:

  * The Python version is: Python3.7 from "/usr/bin/python3"
  * The NumPy version is: "1.20.3"

and make sure that they are the versions you expect.
Please carefully study the documentation linked above for further help.

Original error was: libf77blas.so.3: cannot open shared object file: No such file or directory    

乍看似乎是在 import pandas 時出現錯誤, 但實際上是載入 Numpy 中的 C 程式時有問題, 因為 Pandas 是以 Numpy 為基礎的. 進入 IDLE 介面匯入 numpy 果然出現與上面相同的錯誤訊息, 匯入 pandas 也是一樣. 

以 "Original error was: libf77blas.so.3" 搜尋, 在下面這篇文章找到解決辦法 :

 
原來只要用下列指令安裝 libatlas-base-dev 即可 : 

sudo apt-get install libatlas-base-dev

pi@raspberrypi:~ $ sudo apt-get install libatlas-base-dev    
正在讀取套件清單... 完成
正在重建相依關係
正在讀取狀態資料... 完成
下列的額外套件將被安裝:
  libatlas3-base
建議套件:
  libatlas-doc liblapack-doc
下列【新】套件將會被安裝:
  libatlas-base-dev libatlas3-base
升級 0 個,新安裝 2 個,移除 0 個,有 0 個未被升級。
需要下載 5,365 kB 的套件檔。
此操作完成之後,會多佔用 32.1 MB 的磁碟空間。
是否繼續進行 [Y/n]? [Y/n] Y
下載:1 http://mirror.ossplanet.net/raspbian/raspbian buster/main armhf libatlas3-base armhf 3.10.3-8+rpi1 [2,399 kB]
下載:2 http://mirror.ossplanet.net/raspbian/raspbian buster/main armhf libatlas-base-dev armhf 3.10.3-8+rpi1 [2,966 kB]
取得 5,365 kB 用了 6s (902 kB/s)
選取了原先未選的套件 libatlas3-base:armhf。
(讀取資料庫 ... 目前共安裝了 168524 個檔案和目錄。)
正在準備解包 .../libatlas3-base_3.10.3-8+rpi1_armhf.deb……
Unpacking libatlas3-base:armhf (3.10.3-8+rpi1) ...
選取了原先未選的套件 libatlas-base-dev:armhf。
正在準備解包 .../libatlas-base-dev_3.10.3-8+rpi1_armhf.deb……
Unpacking libatlas-base-dev:armhf (3.10.3-8+rpi1) ...
設定 libatlas3-base:armhf (3.10.3-8+rpi1) ...
update-alternatives: 在自動模式下以 /usr/lib/arm-linux-gnueabihf/atlas/libblas.so.3 來提供 /usr/lib/arm-linux-gnueabihf/libblas.so.3 (libblas.so.3-arm-linux-gnueabihf)
update-alternatives: 在自動模式下以 /usr/lib/arm-linux-gnueabihf/atlas/liblapack.so.3 來提供 /usr/lib/arm-linux-gnueabihf/liblapack.so.3 (liblapack.so.3-arm-linux-gnueabihf)
設定 libatlas-base-dev:armhf (3.10.3-8+rpi1) ...
update-alternatives: 在自動模式下以 /usr/lib/arm-linux-gnueabihf/atlas/libblas.so 來提供 /usr/lib/arm-linux-gnueabihf/libblas.so (libblas.so-arm-linux-gnueabihf)
update-alternatives: 在自動模式下以 /usr/lib/arm-linux-gnueabihf/atlas/liblapack.so 來提供 /usr/lib/arm-linux-gnueabihf/liblapack.so (liblapack.so-arm-linux-gnueabihf)
執行 libc-bin (2.28-10+rpi1) 的觸發程式……

經過這樣處理後就能順利匯入 Numpy 與 Pamdas 了 :

pi@raspberrypi:~ $ python3    
Python 3.7.3 (default, Jan 22 2021, 20:04:44)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy as np    
>>> np.__version__    
'1.20.3'
>>> import pandas as pd   
>>> pd.__version__    
'1.2.4'

但再次執行爬蟲程式還是失敗, 因為爬蟲程式裡面用到了中文字型 "微軟正黑體", 而樹莓派預設並未支援此字型 : 

pi@raspberrypi:~ $ python3 btc_eth_prices_line_notify.py    
Unable to init server: Could not connect: Connection refused
Unable to init server: 無法連接:Connection refused

(btc_eth_prices_line_notify.py:10639): Gdk-CRITICAL **: 20:43:38.213: gdk_cursor_new_for_display: assertion 'GDK_IS_DISPLAY (display)' failed

(btc_eth_prices_line_notify.py:10639): Gdk-CRITICAL **: 20:43:38.228: gdk_cursor_new_for_display: assertion 'GDK_IS_DISPLAY (display)' failed
/usr/lib/python3/dist-packages/matplotlib/font_manager.py:1241: UserWarning: findfont: Font family ['Microsoft JhengHei'] not found. Falling back to DejaVu Sans.
  (prop.get_family(), self.defaultFamily[fontext]))

但我參考之前的文章要做字型設定時卻發現 matplotlib 似乎版本太舊, 找不到預期之字型資料夾, 所以乾脆用 pip3 install matplotlib -U 指令將 Matplotlib 版本升級到最新, 然後就可以依照之前的文章上傳並設定微軟正黑體 : 


Python 3.7.3 (default, Jan 22 2021, 20:04:44)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import matplotlib  
>>> matplotlib.__version__    
'3.5.2'   

這樣就可以順利在樹莓派用 Matplotlib 繪製含有中文的圖形了.