2021年9月21日 星期二

MicroPython v1.17 釋出

很久沒關注 MicroPython了, 今日追蹤發現 v1.17 已在 9/2 發布, 而我居然跳過了 v1.16! 


映像檔下載位址如下 : 


此次更新主要的新增功能如下 :
  1. 支援 f 字串語法糖 (部分受限)
  2. 新增 machine 模組的 I2S 類別 (支援 ESP32 與 STM32 板)
  3. json 模組的 dump/dumps 函數支援 separators 引數
  4. 檔案目錄重構, 第三方套件均放在 /lib 目錄下, 原本在 /lib 的第一方套件改放至 /shared
  5. ESP8266/ESP32 的 WLAN 掃描結果支援顯示隱藏之網路

我用 esptools 燒錄映像檔到 4MB 的 NodeMCU (ESP32) OK (先按住右下角的 IO0 鈕) :

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:c0:8c
Uploading stub...
Running stub...
Stub running...
Erasing flash (this may take a while)...
Chip erase completed successfully in 5.2s
Hard resetting via RTS pin...

D:\ESP32>esptool.py --chip esp32 --port COM9 write_flash -z 0x1000 esp32-20210902-v1.17.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:c0:8c
Uploading stub...
Running stub...
Stub running...
Configuring flash size...
Auto-detected Flash size: 4MB
Compressed 1527504 bytes to 987584...
Wrote 1527504 bytes (987584 compressed) at 0x00001000 in 88.0 seconds (effective 138.9 kbit/s)...
Hash of data verified.

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

燒錄完畢用 Putty 連線 ESP32, 使用 os 模組的 uname() 函數檢查韌體版本 : 

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:0x3fff0030,len:5656
load:0x40078000,len:12696
load:0x40080400,len:4292
entry 0x400806b0
MicroPython v1.17 on 2021-09-02; ESP32 module with ESP32
Type "help()" for more information.
>>> import os     
>>> os.uname()   
(sysname='esp32', nodename='esp32', release='1.17.0', version='v1.17 on 2021-09-02', machine='ESP32 module with ESP32')

用 sys 模組的 version 屬性檢查 Python 版本 : 

>>> import sys   
>>> print("Version Python ", sys.version)     
Version Python  3.4.0

跟以前一樣還是 Python 3.4.0. 

測試 f 字串嵌入功能 :

>>> name="Tony"   
>>> age=21   
>>> print(f"My name is {name} and I am {age} years old")   
My name is Tony and I am 21 years old

果然已有支援 F 字串. 參考 :


中秋與 921

今天是中秋, 921 大地震 22 周年, 也是母親七周年忌日, 時間過得好快, 一轉眼七年就過去了, 但我每天仍誦念經咒迴向, 除了緬懷, 也是修行科目之一. 最近重看花甲男孩轉大人, 我覺得劇中花甲的阿嬤跟母親個性很像, 樂於助人捨得給予, 我至今都還蒙德澤. 我雖不是達官貴人, 但當年母親告別式仍來了非常多親朋好友送行. 花甲的二叔說母親所做的他連一樣都做不到, 我也是, 真是慚愧. 

常用亂數種子 42 的由來

今天在 "精通機器學習" 這本書第二章關於如何建立測試組的部分讀到, 在呼叫排列組合函數產生隨機的測試組索引前, 可以先設定一個亂數產生器種子例如 np.random.seed(42) 來避免每次執行程式時產生不同的測試組資料集 (這樣多執行幾次就會將整個資料集看光光, 有違設立測試組之目的).

作者在此標註 : 你可能會在很多資料科學與機器學習的書上看到亂數產生器種子被設為 42, 這是為什麼? 其實使用 42 這個數值並沒有特殊的意義, 那只是工程師在寫程式時隨手參考所形成的流行文化 (pop culture) 而已. 

這個數字 42 出自道格拉斯-亞當斯 (Douglas Adams) 1979 年開始發行的科幻小說 "銀河便車指南" 系列 (The Hitchhiker’s Guide to the Galaxy), 這些小說曾被改編成廣播劇, 電視劇, 漫畫等形式, 2005 年還被好萊塢改編成電影 "星際大奇航". 亞當斯在大學畢業後工作並不順利, 曾當過保鑣, 保全, 建築工, 以及養雞場工人, 直到 1979 年他的小說被改編成廣播劇才步上成功之路. 亞當斯於 2001 年因心臟病離世, 享年 49 歲, 當時他正在寫銀河便車指南三部曲系列中的第六本小說. 

在這本小說的末尾, 主角 Authur Dent 在馬格西亞星球上得知地球的存在只是一個實驗, 其實幾百萬年前的老鼠是一種高智慧動物, 它們建造了一部超級電腦叫做 Deep Thought (深思), 這些老鼠詢問 Deep Thought : 生命, 宇宙, 以及萬事萬物的終極答案 (Great Question) 是甚麼? 經過很長時間的計算後, 老鼠的後代得到 42 這個答案. 

Deep Thought 表示它只能算出答案是甚麼, 背後的原因必須用更高級的電腦才能解釋, 而這部電腦就是地球本身, 參考 :


在 Deep Thought 得出這答案後的五分鐘, 地球就被來自 Vogsphere 行星的 Vogons 人毀滅了 (為了建造超太空星際公路, 這是都更概念嗎? ), Arthur Dent 與他來自參宿四的朋友 Ford Prefect 搭乘 Vogons 人的飛碟離去. 由於地球所有資料被毀, 但 Arthur Dent 腦中儲存了所有資料, 老鼠們為了探求 Great Question 的原因, 決定拿 Arthur Dent 的腦袋開刀 ..... 哈哈, 好有趣的故事. 

2021年9月20日 星期一

2021 年第 38 周記事

鄉下老家的油漆與防漏工程本周終於完成了, 因二樓祖堂前的磨石地磚部分膨起, 做 PU 前須先挖掉重鋪, 所以請松和先生的媳婦看日子, 9/14 周二巳時起工, 昨日上完兩層 PU, 今天做第三層, 早上老闆來時, 說又添加一樓後面及二三樓樓板邊邊粉刷, 再加兩萬五, 爸已領五千給他, 剩尾款六萬元, 哇哩上回說剩四萬元不會再加了, 又找別的名目加做, 此人雖做生意有點不老實, 但工程倒是有認真做 (其實都是他的越南老婆在做), 我想就算了, 結算此次修繕總計花費 26.5 萬. 





本周專心 Python GUI Tkinter 學習, 總算離終點越來越近了, 雖然還是沒有如預期的兩周搞定, 但也還算 OK, 因為也分了一點時間在看 ML 與 NLP (為了還書, 哈). Tkinter 原本不在優先順序名單的, 只是整理書櫃時偶然拿起 Tkinter 的書本, 突然想把它學完而已. 所以臨時起意有時也能做出一些成果出來, 計畫常常趕不上變化. 學完 Tkinter 順便把日常的工作輔助軟體改寫成桌上型 GUI 介面, 徹底擺脫 IE 的牽制 (IE 也快要走入歷史了).

本周因為油漆工恢復工作, 小雖貓看到生人來, 就叼著它兒子轉移到車庫冷氣口下方的紙箱內側躲起來了, 可見貓很會認人, 對主人很放心, 之前在前門口育兒時, 我去摸小貓咪它都不會緊張, 但陌生人一來就趕緊另覓處所. 但我昨晚趁它在曬穀場邊休息, 把小貓咪帶到客廳給菁菁看, 小貓咪已開目, 看到我就一直喵喵叫, 而且越來越大聲, 小雖貓聽到立刻跑到客廳門口攀在鋁門上看, 我趕緊將小貓咪抱回去, 小雖貓緊張得用衝的衝回來紙箱看, 然後就一直躲在裡面顧, 過了好久才出來.  

姊姊周六傍晚回高雄過節, 公司發了一盒月餅 (我也在周五去辦公室附近的吳記買了一盒 9 顆的綠豆椪, 540 元), 我們家沒有中秋烤肉習慣, 故中秋節就是吃月餅柚子賞月, 看 Netflix 而已. 因姊姊回台北高鐵為周二中秋節早上, 周一晚上我們就得回高雄, 所以周六晚上等菁菁下班就回鄉下了. 

2021年9月18日 星期六

重看花甲男孩轉大人

最近藝壇又損失了一員, 曾抱回兩座金鐘視帝獎座的本土劇資深演員龍劭華猝逝, 令人感到惋惜. 他生前作品我印象最深的是五年前看過的 "花甲男孩轉大人", 劇中他飾演鄭家二哥光煌, 這幾天在 Youtube 找出來重看了一遍, 真的好好看 (好好笑但有時又讓人噴淚), 植劇場這部作品我認為是台灣鄉土劇的經典. 

對於童年在三合院大家族中長大的我來說, 這部戲看起來格外親切呢. 可惜我家的三合院多年前早已被拆除, 宗族四散, 各自到自己分得的農地上蓋農舍了. 當兵前曾回去破落無人居住的三合院回憶童年時光, 可惜當時我還沒有照相機, 沒能留下最後影像. 之後祖宅土地被家族協議賣給建商蓋透天厝, 我的童年記憶就只留下模糊的映像了. 

2021年9月17日 星期五

好書 : 實戰 TensorFlow x Keras工作現場開發

此書借自市圖, 作者為日本人太田滿久等人, 主要介紹 MLP, CNN, 以及 Encoder-decoder 等 (不含 RNN 等), 內容簡要故篇幅不大, 前半部基礎部分有清晰圖解幫助初學者理解神經網路運算架構, 後半部則主要介紹利用 Encode-decode 進行圖片上色與生成等處理. 



Source : 博客來


雖然算是一本好書, 但日文原文書乃 2018 年出版, 使用的是 TensorFlow 1.5 版, 年代有點久遠了. 書中程式碼下載網址 :

2021年9月16日 星期四

Python 內建 GUI 模組 tkinter 測試 (十四) : Combobox 元件

今天繼續測試的 Combobox (下拉式選單) 元件. 

本系列之前的文章參考 :   


Combobox 元件 (下拉式選單) 與 Spinbox 類似, 也是一種複合元件, 由 Entry 與附捲軸之 Listbox 組成, 可讓使用者在下拉清單中選取一個選項,  也可以直接在 Entry 中輸入內容. 注意, 此元件僅在 ttk 中有, tk 中無此元件. 建立 Combobox 元件之語法如下 : 

Combobox(父容器, **參數列)



Combobox 元件常用參數如下表 : 


 Combobox 常用參數 說明
 width Combobox 輸入 Entry 的寬度 (字元數, 預設 20 個字元)
 height 設定下拉式選單最大顯示列數 (預設 20), 超出會顯示捲軸
 values 設定下拉式選單之選項, 可用字串之元組或串列 
 textvariable 與 Combobox 綁定之類別變數名稱 (通常用 StringVar)
 state 設定 Combobox 狀態 : NORMAL (預設)/DISABLED/READONLY


注意, state 參數也可以使用字串 "normal", "disabled", 或 "readonly". 

Combobox 元件常用方法如下 :


 Combobox 常用方法 說明
 get() 取得 Combobox 元件之現值
 set(option) 設定選項 option 為被選取
 current([index]) 設定索引=index 之項目被選取, 未傳入參數則傳回被選取項之索引


取得 Combobox 被選取之現值除了呼叫 Combobox 物件的 get() 方法外, 也可以呼叫與其 textvariable 參數綁定的類別變數之 get() 方法. 

建立 Combobox 元件時只要傳入 values 參數即可, 例如 : 


測試 1 : 預設的 Combobox 下拉式選單 [看原始碼]

import tkinter as tk
from tkinter import ttk

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

values_1=("岳不群", "左冷禪", "定逸師太", "任盈盈", "令狐沖") 
combobox_1=ttk.Combobox(win, values=values_1)
combobox_1.pack()
values_2=["張無忌", "趙敏", "楊不悔", "朱九貞", "周芷若"]
combobox_2=ttk.Combobox(win, values=values_2)
combobox_2.pack()
win.mainloop()

此例的兩個下拉式選單分別以 list 與 tuple 形式傳入 values 參數, 結果如下 : 




此例僅設定 values 參數, 預設 Entry 寬度為 20 個字元, 看來有點寬, 這可以用 width 參數設定, 下面範例同時也指定了 height 參數來限制最大顯示列數 :


測試 2 : 設定 width (輸入區寬度) 與 height (清單顯示列數) 參數 [看原始碼]

import tkinter as tk
from tkinter import ttk

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

values_1=("岳不群", "左冷禪", "定逸師太", "任盈盈", "令狐沖") 
combobox_1=ttk.Combobox(win, values=values_1, width=10, height=3)
combobox_1.pack()
values_2=["張無忌", "趙敏", "楊不悔", "朱九貞", "周芷若"]
combobox_2=ttk.Combobox(win, values=values_2, width=10, height=3)
combobox_2.pack()
win.mainloop()

此例添加了 width 與 height 參數, 結果如下 : 




可見下拉式清單只顯示三個選項, 這時清單會自動出現捲軸, 方便點選其他位顯示之選項. 

如果要取得被選取的選項可呼叫 Combobox 物件的 get() 方法, 呼叫 set() 方法則可指定某選項為被選取, 而呼叫 current() 則可傳回被選取選項之索引, 例如 :


測試 3 : 呼叫 get(), set(), 與 current() 方法存取被選取之選項 [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_value():
    label["text"]="選取 : " + combobox.get() + \
                  " 索引 : " + str(combobox.current())        # 取得被選取選項與其索引

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

values=("Python", "Javascript", "R", "Julia", "PHP")
combobox=ttk.Combobox(win, values=values)
combobox.pack()
combobox.set("Python")        # 設定被選取選項
ttk.Button(win, text="確定", command=show_value).pack()
label=ttk.Label(win)
label.pack()
win.mainloop()

此例在建立 Combobox 物件後呼叫其 set() 方法將選項 "Python" 設定為預設被選取項目. 按下確定鈕時其 command 觸發按鈕事件, 呼叫 Combobox 的 get() 方法取得被選取之選項, current() 方法則傳回該項目之索引, 結果如下 :





除了呼叫 Combobox 物件的 get() 方法取得被選取之選項外, 還可以利用 textvariable 參數綁定一個 StringVar 類別變數, 透過其 get() 方法也可以取得被選擇之選項, 而且呼叫類別變數的 set() 亦可設定 Combobox 的預設選項, 例如 :


測試 4 : 用 textvariable 綁定類別變數來存取 Combobox [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_value():
    label["text"]=var.get()        # 呼叫類別變數的 get() 方法取得被選取項目

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

var=tk.StringVar()          # 與 Combobox 綁定之類別變數
var.set("Python")            # 設定預設被選取選項
values=("Python", "Javascript", "R", "Julia", "PHP")
combobox=ttk.Combobox(win, values=values, textvariable=var)    # 綁定類別變數
combobox.pack()
ttk.Button(win, text="確定", command=show_value).pack()
label=ttk.Label(win)
label.pack()
win.mainloop()

此例利用 textvariable 參數綁定一個類別變數 var, 呼叫其 set() 方法可設定被選取之項目, 呼叫 get() 則可取得被選取項目, 結果如下 :





由於 Combobox 沒有像 Spinbox 那樣提供 command 參數, 故上面範例都需要一個按鈕來觸發事件. 不過可以利用 Tkinter 的事件處理機制, 透過元件的 bind() 方法替 ComboboxSelected 事件綁定一個處理函式, 就可以在選取時觸發事件, 例如 :


測試 5 : 呼叫 bind() 方法綁定 ComboSelected 事件處理函式 [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_value(value):
    label["text"]=var.get()

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

var=tk.StringVar()
var.set("Python")
values=("Python", "Javascript", "R", "Julia", "PHP")
combobox=ttk.Combobox(win, values=values, textvariable=var)
combobox.bind('<<ComboboxSelected>>', show_value)
combobox.pack()
label=ttk.Label(win)
label.pack()
win.mainloop()

此例呼叫 Combobox 物件的 bind() 方法為 ComboSelected 事件綁定處理函式, 當使用者選取項目時就會觸發此事件, 不需透過額外的按鈕, 結果如下 :





Tkinter 元件之事件處理參考 :


下面範例測試 state 參數, 預設 "normal" 表示 Combobox 除了可從下拉式選單中選擇項目外, 也可以直接在 Entry 中直接輸入清單以外的任何內容; 若設為 "readonly" 表示只能從下拉式選單中挑選項目, 不能直接輸入; 若設為 "disabled" 則表示禁能, 連下拉式選單也無法使用, 例如 :


測試 6 : 將 state 參數設為 "readonly" [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_value_1():
    label_1["text"]=combobox_1.get()
def show_value_2():
    label_2["text"]=combobox_2.get()

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

values=("Python", "Javascript", "R", "Julia", "PHP")
combobox_1=ttk.Combobox(win, values=values)
combobox_1.pack()
ttk.Button(win, text="確定", command=show_value_1).pack()
label_1=ttk.Label(win)
label_1.pack()
combobox_2=ttk.Combobox(win, values=values, state="readonly")   # 無法直接輸入
combobox_2.pack()
ttk.Button(win, text="確定", command=show_value_2).pack()
label_2=ttk.Label(win)
label_2.pack()
win.mainloop()

此例有兩個 Combobox 元件, combobox_1 未設定 state 參數, 預設為 "normal", 故可輸入可挑選; 但 combobox_2 則設為 "readonly", 無法輸入只能挑選, 結果如下 :




可見點上面的 Combobox 可以輸入任何內容, 但下面的 Combobox 因為是唯讀, 故一點其 Entry 馬上就跳出選單, 無法輸入內容. 

參考 :


銀河與星斗

這學期正常開學後, 又可以在車上聽菁菁播放的新歌了, 最近聽到一首很溫柔的歌 :





此曲不獨旋律溫柔宛如清風拂過, 詞也寫得甚好, 歌者苡慧是馬來西亞人 :


2021年9月15日 星期三

Python 內建 GUI 模組 tkinter 測試 (十三) : Spinbox 元件

本篇測試 tkinter 的 Spinbox (數值微調器) 元件. 

本系列之前的文章參考 :   


Spinbox 元件用來在指定的範圍內選取數值, 事實上它是由一個 Entry 元件與兩個按鈕組成的複合元件, 按上下鈕可讓 Entry 元件中的數值以既定的步階上下變動, 在 tk 與 ttk 中都有提供此元件. 建立 Spinbox 元件之語法如下 : 

Spinbox(父容器, **參數列)



Spinbox 元件常用參數如下表 : 


 Spinbox 常用參數 說明
 from_ Spinbox之起始值 (無預設值, 必須設定)
 to  Spinbox之結束值 (無預設值, 必須設定)
 increment 按 Spinbox 上下鈕時數值的增減步階值 (預紹 1 或 1 格)
 format 數值格式字串, 例如 "%10.4f" 表共 10 個數字浮點數含 4 位小數
 command 按 Spinbox 上下鈕時要呼叫之函式名稱
 textvariable 與 Spinbox 綁定之類別變數名稱 (IntVar, DoubleVar, 或 StringVar)
 width Spinbox 數值顯示區的寬度 (字元數, 預設 20 個字元)
 wrap 按向上鈕到最大值時是否變最小值, 反之亦然=True/False (預設)
 values 指定非數值選項的 tuple 或 list (比 from_, to, 與 increment 優先)
 state 設定 Spinbox 狀態=NORMAL (預設)/DISABLED/READONLY


與 Scale 元件不同的是, Spinbox 的參數 from_ 與 to 無預設值, 故必須自行設定才能使用, 建立物件時不指定雖然不會出現錯誤, 但按上下鈕沒有反應, 無法使用. 

Spinbox 元件常用方法如下表 : 


 Spinbox 常用方法 說明
 get() 傳回 Spinbox 的現值 (字串, 即使內容是數值)
 invoke(element) 模擬按上下鍵動作 (element="buttonup"/"buttondown") (ttk 不支援)


Spinbox 元件若沒有設定 from_ 與 to 參數雖然會顯示元件, 但沒有作用, 例如 : 


測試 1 : 未設定 from_ 與 to 參數沒有作用 [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_selection():
    result_label["text"]=var.get()

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

spinbox_tk=tk.Spinbox(win)     # 建立 tk 版 Spinbox
spinbox_tk.pack()
spinbox_ttk=ttk.Spinbox(win)   # 建立 ttk 版 Spinbox
spinbox_ttk.pack()
win.mainloop()

此例分別建立 tk 與 ttk 版的 Spinbox 物件, 但在建立物件時未傳入 from_ 與 to 參數, 建立後也未使用 [] 運算子或 config() 方法設定, 雖然可順利執行, 但是按上下鍵並無作用, 結果如下 :




可見下方 ttk 版的上下鍵按鈕較大較寬, 按 tk 版的上下鈕無反應, 按 ttk 版的則固定顯示 0, 就算在 Entry 欄位輸入數值後按上下鍵也無作用. Entry 輸入的寬度預設 20 個字元. 數值型 Spinbox 要正常可用需傳入 from_ 與 to 兩個參數, 例如 : 


測試 2 : 數值型 Spinbox 需設定 from_, to 與 width 參數 [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_selection():
    result_label["text"]=var.get()

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

spinbox_tk=tk.Spinbox(win, from_=-10, to=10, width=10)      # 建立 tk 版 Spinbox
spinbox_tk.pack()
spinbox_ttk=ttk.Spinbox(win, from_=-10, to=10, width=10)    # 建立 ttk 版 Spinbox
spinbox_ttk.pack()
win.mainloop()

此例在建立 Spinbox 物件時傳入數值範圍參數 from_ 與 to 使其有作用, 這樣按上下鍵時數字就會以預設的 increment=1 上下跳動. 另外還用 width 參數設定 Entry 輸入欄位寬度為 10 個字元 (預設 20), 結果如下 : 




tk 版的 Spinbox 預設顯示 to 參數的數值, ttk 版的預設不顯示, 但按上下鍵就會顯示 to 參數之值. 按上下鍵時預設增減量為 1, 但可用 increment 參數設定, 例如 :


測試 3 : 用 increment 參數設定增減步階值 (正=順向) [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_selection():
    result_label["text"]=var.get()

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

spinbox_tk=tk.Spinbox(win, from_=-10, to=10, increment=2)     # 順向增減步階值=2
spinbox_tk.pack()
spinbox_ttk=ttk.Spinbox(win, from_=-10, to=10, increment=2)   # 順向增減步階值=2
spinbox_ttk.pack()
win.mainloop()

此例以 increment 參數設定增減步階為 2, 故按上下鍵時每次上下跳兩格, 按向上鍵會按 -10, -8, -6, ... 次序變化 (都顯示偶數值), 結果如下 : 




上例 increment 為正, 表示按向上鍵數值增加, 按向下鍵數值減少 (順向); 但 increment 也可以設為負值, 這樣方向與數值就會變成逆向關係, 即按向上鍵數值減少, 按向下鍵數值增加, 例如 : 


測試 4 : 用 increment 參數設定增減步階值 (負=逆向) [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_selection():
    result_label["text"]=var.get()

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

spinbox_tk=tk.Spinbox(win, from_=-10, to=10, increment=-2)      # 逆向增減步階值=-2
spinbox_tk.pack()
spinbox_ttk=ttk.Spinbox(win, from_=-10, to=10, increment=-2)    # 逆向增減步階值=-2
spinbox_ttk.pack()
win.mainloop()

此例將 increment 設為 -2, 因此一開始只能按向下鍵 (因為按向上鍵就超出範圍了), 其值會從 -10 變 -8, -6, ... 因為減少 -2 即加 2. 然後按向上鍵數值反而減少, 因為加 -2 即減 2 之意, 結果如下 : 




不過這種逆向邏輯與常識相反, 故 increment 還是用正值為宜. 

下面範例測試 wrap 參數, 此參數用來設定當按上下鍵到達數值上下限 (即 from_ 與 to 所限定之邊界) 時是否要反折到另一邊, 設為 True 的話值會從 from_ 變成 to, 或從 to 變成 from_, 亦即數值變化變成一個環, 而非碰頂後停住不動. 此 wrap 參數預設值為 False (不反折), 即到達上下限時再按值也不變, 例如 : 


測試 5 : 用 wrap 參數設定到達上下限時數值是否要反折 [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_selection():
    result_label["text"]=var.get()

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

spinbox_tk=tk.Spinbox(win, from_=-10, to=10, wrap=True)       # 到達上下限時反折
spinbox_tk.pack()
spinbox_ttk=ttk.Spinbox(win, from_=-10, to=10, wrap=True)     # 到達上下限時反折
spinbox_ttk.pack()
win.mainloop()

此例將 wrap 參數設為 True, 當按向上鍵到達 10 時, 再按就變成 -10, 即反折到 to 去; 同理按向下鍵到達 -10 時, 再按一次就變成 10, 即反折到 from 去, 結果如下 : 




下面範例測試 format 參數, 此參數值為一個表示數值的格式字串, 用法參考 print() 函式, 例如 "%10.4f" 表示數值格式為最多 10 個數字, 其中有 4 位小數, 亦即整數部分最多 6 位數, 例如 :


測試 6 : 用 format 參數設定數值格式 [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_selection():
    result_label["text"]=var.get()

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

spinbox_tk=tk.Spinbox(win, from_=-1, to=1, increment=0.0005)
spinbox_tk["format"]="%5.4f"               # 設定數值格式為 4 位小數, 1 位整數
spinbox_tk.pack()
spinbox_ttk=ttk.Spinbox(win, from_=-1, to=1, increment=0.0005)
spinbox_ttk.config(format="%5.4f")      # 設定數值格式為 4 位小數, 1 位整數
spinbox_ttk.pack()
win.mainloop()

此例使用 [] 與 config() 兩種方式設定 Spinbox 物件之 format 參數, "%5.4f" 表示小數為 4 位數, 故整數為 1 位數, 這是配合 from_=-1, to=1, 與 increment=0.0005 之設定, 結果如下 :




Spinbox 元件的 command 參數用來將上下鍵的按鈕事件綁定到一個事件處理函式, 在此函式中可呼叫 Spinbox 物件的 get() 方法以取得微調器之現值, 例如 : 


測試 7 : 用 get() 方法取得微調器之值 [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_value_tk():
    label_tk["text"]=spinbox_tk.get()              # 取得微調器之值
def show_value_ttk():
    label_ttk.config(text=spinbox_ttk.get())    # 取得微調器之值

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

spinbox_tk=tk.Spinbox(win, from_=-10, to=10, command=show_value_tk)
spinbox_tk.pack()
label_tk=tk.Label(win)
label_tk.pack()
spinbox_ttk=ttk.Spinbox(win, from_=-10, to=10, command=show_value_ttk)
spinbox_ttk.pack()
label_ttk=ttk.Label(win)
label_ttk.pack()
win.mainloop()

此例添加了 Label 元件來顯示 Spinbox 的現值, Spinbox 則用 command 參數設定了按鈕事件處理函式, 當按上下鍵時會呼叫 Spinbox 的 get() 方法傳回現值, 用來設定 Label 的文字, tk 版使用 [] 運算子, 而 ttk 版則使用 config(), 兩種方法都可以, 結果如下 : 




注意, 即使微調器的內容是數值, 但 get() 方法傳回的是個字串. 除了值接呼叫 Spinbox 物件的 get() 取得其值外, 也可以用 textvariable 參數綁定一個類別變數來存取微調器, 這種方式除了可以c取得 Spinbox 之值, 也可以用來設定初始值, 例如 : 


測試 8 : 用 textvariable 參數綁定類別變數以動態存取微調器 [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_value_tk():
    label_tk.config(text=var_tk.get())    # 透過類別變數取得 Spinbox 現值
def show_value_ttk():
    label_ttk["text"]=var_ttk.get()            # 透過類別變數取得 Spinbox 現值

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

var_tk=tk.StringVar()
var_tk.set(5)                    # 設定微調器初始值
spinbox_tk=tk.Spinbox(win,
                      from_=-10,
                      to=10,
                      textvariable=var_tk,    
                      command=show_value_tk)    # 綁定類別變數
spinbox_tk.pack()
label_tk=tk.Label(win)
label_tk.pack()

var_ttk=tk.StringVar()
var_ttk.set(7)                    # 設定微調器初始值
spinbox_ttk=ttk.Spinbox(win,
                        from_=-10,
                        to=10,
                        textvariable=var_ttk,    
                        command=show_value_ttk)    # 綁定類別變數
spinbox_ttk.pack()
label_ttk=ttk.Label(win)
label_ttk.pack()
win.mainloop()

此例 Spinbox 元件經由 textvariable 參數綁定了類別變數, 呼叫類別變數的 set() 方法即可設定 Spinbox 的初始值, 呼叫 get() 方法則可取得微調器現值, 結果如下 : 




原本 ttk 的微調器一開始預設是不顯示值的 (tk 則是預設顯示 from_ 之值), 用類別變數的 set() 設定預設值後, 程式一執行就會顯示初始值了. 

除了作為數值微調器外, Spinbox 也可以當選項微調器使用, 意即 Entry 輸入欄位內容是選項字串而非數值, 按上下鈕時選項會上下跳動, 這時 from_, to 與 increment 這三個參數失效 (被 override), 改由 values 參數 (選項字串之 list 或 tuple) 決定微調器的值, 例如 : 


測試 9 : 用 values 參數設定選項 (字串) [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_value_tk():
    label_tk["text"]=spinbox_tk.get()
def show_value_ttk():
    label_ttk.config(text=spinbox_ttk.get())

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

values_tk=("岳不群", "左冷禪", "定逸師太", "任盈盈", "令狐沖")     # 使用元組
spinbox_tk=tk.Spinbox(win, from_=-10, to=10, values=values_tk)       # 設定選項
spinbox_tk["command"]=show_value_tk
spinbox_tk.pack()
label_tk=tk.Label(win)
label_tk.pack()
values_ttk=["張無忌", "趙敏", "楊不悔", "朱九貞", "周芷若"]              # 使用串列
spinbox_ttk=ttk.Spinbox(win, from_=-10, to=10, values=values_ttk)       # 設定選項
spinbox_ttk["command"]=show_value_ttk
spinbox_ttk.pack()
label_ttk=ttk.Label(win)
label_ttk.pack()
win.mainloop()

此例分別使用元組與串列儲存選項, 然後設定為 values 參數之值, 結果如下 :




按上下鍵即可挑選選項作為 Spinbox 之值, 相當於 Radiobutton 的單選功能. 除了字串選項外, values 當然也可以用數值串列, 這樣按上下鍵時就會在這些值之間跳動, 例如 :


測試 10 : 用 values 參數設定選項 (數值) [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_value_tk():
    label_tk["text"]=spinbox_tk.get()
def show_value_ttk():
    label_ttk.config(text=spinbox_ttk.get())

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

values_tk=(1, 3, 5, 7, 9, 11, 13, 15)       # 數值選項
spinbox_tk=tk.Spinbox(win, values=values_tk)
spinbox_tk["command"]=show_value_tk
spinbox_tk.pack()
label_tk=tk.Label(win)
label_tk.pack()
values_ttk=[1.2, 4.3, 8.7, 9.3, 11.1, 13.2, 34.1, 78.9]     # 數值選項
spinbox_ttk=ttk.Spinbox(win, values=values_ttk)
spinbox_ttk["command"]=show_value_ttk
spinbox_ttk.pack()
label_ttk=ttk.Label(win)
label_ttk.pack()
win.mainloop()

結果如下 :




Spinbox 物件的 invoke() 方法可用來模擬按上鍵 (傳入 "buttomup") 與按下鍵 (傳入 "buttondown") 的動作, 但此方法僅 tk 可用, ttk 不支援, 例如 : 


測試 11 : 呼叫 invoke() 方法模擬按上下鍵之動作 [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_value_tk():
    label_tk["text"]=spinbox_tk.get()
def show_value_ttk():
    label_ttk.config(text=spinbox_ttk.get())
def move_up_tk():
    spinbox_tk.invoke("buttonup")             # 模擬按向上鍵動作
def move_down_tk():
    spinbox_tk.invoke("buttondown")        # 模擬按向下鍵動作

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

values_tk=("岳不群", "左冷禪", "定逸師太", "任盈盈", "令狐沖")
spinbox_tk=tk.Spinbox(win, from_=-10, to=10, values=values_tk)
spinbox_tk["command"]=show_value_tk
spinbox_tk.pack()
tk.Button(win, text="上", command=move_up_tk).pack()
tk.Button(win, text="下", command=move_down_tk).pack()
label_tk=tk.Label(win)
label_tk.pack()
win.mainloop()

此例以兩個 Button 元件來模擬 Spinbox 的上下鍵, 按下時會呼叫 invoke(), 結果如下 :




最後來測試 state 參數, 此參數預設為 "normal" 或 tk.NORMAL, 此狀態下 Spinbox 的 Entry 除了可按上下鍵來自動填入跳動的數值或選項外, 也可以直接輸入內容, 這時呼叫 get() 取值時會是輸入之內容. 如果設為 "readonly" 或 tk.READONLY, 則 Entry 欄位無法直接輸入內容, 只能按上下鍵改變內容, 如果設為 "disabled" 或 tk.DISABED 則連上下鍵也動不了, 被禁能了, 例如 : 


測試 12 : 用 state 參數設定 Spinbox 之狀態 [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_value_tk():
    label_tk["text"]=spinbox_tk.get()
def show_value_ttk():
    label_ttk["text"]=spinbox_ttk.get()    

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

spinbox_tk=tk.Spinbox(win, from_=-10, to=10)       # 預設 NORMAL
spinbox_tk.pack()
tk.Button(win, text="確定", command=show_value_tk).pack()            
label_tk=tk.Label(win)
label_tk.pack()
spinbox_ttk=ttk.Spinbox(win, from_=-10, to=10, state="readonly")    # 設為唯讀狀態
spinbox_ttk.pack()
ttk.Button(win, text="確定", command=show_value_ttk).pack()
label_ttk=ttk.Label(win)
label_ttk.pack()
win.mainloop()

此例 tk 版的微調器預設為 NORMAL 狀態, 故既可按上下鍵改變內容, 也可以直接在 Entry 中輸入內容, 按下確定鈕呼叫 get() 方法將取得輸入之值. 而 ttk 版的 state 被設為 "readonly", 故無法於 Entry 中直接輸入內容, 只能按上下鍵, 結果如下 : 




可見 tk 版的 Spinbox 若按上下鍵應取得整數, 但直接輸入浮點數按確定即取得輸入之浮點數. 

2021年9月14日 星期二

曲肖冰的倆倆相忘

之前在看 2019 年版倚天屠龍記時, 張無忌與小昭被成昆困在明教密道時, 小昭唱了一首歌給無忌聽, 這首歌旋律非常悅耳, 詞也非常寫意, 演唱者為中國歌手曲肖冰 :





此曲原唱者為辛曉琪, 是為 1994 年台視倚天屠龍記片尾曲所唱 :





我聽過倆人唱腔後, 覺得曲肖冰翻唱得更好聽ㄟ. 


機器學習筆記 : 基本概念

自 2018 年開始接觸機器學習後, 三年下來仍然是一知半解. 最近從母校借來的 "精通機器學習 (第二版)" 因被預約即將歸還, 但書卻沒看多少, 只好將前三章閱讀筆記整理如下. 


Source : 博客來


此書譯自 Oreilly 2019 年出版的原文書第二版 :  



Source : 博客來


之前也曾從母校借過此書原文第一版 (僅 16 章), 新版內容修訂擴充為 19 章. 與原文書價格相比, 翻譯本真的便宜多多, 且中文版閱讀速度可以更快.

此書屬於實作類, 沒有演算法方程式的理論推導, 而是簡單介紹概念後直接給出演算式, 接著列出 Python 實作, 用的是準生產框架 (scikit-learn 與 TensorFlow/Keras), 不是用來演繹理論的玩具版本 Python 程式. 本書 Jupyter 範例可從 GitHub 下載 :


閱讀此書的先備知識包括 :
  • Numpy + Pandas + Matplotlib
  • 微積分 + 線性代數 + 機率統計
手上還有其它幾本參考書也是很不錯, 可交互參照學習 :


此書內容主要分成兩大部分, 第一部分介紹 Scikit-Learn 套件 (淺水區), 第二部分則是 TensorFlow 與 Keras (深水區). 但作者提醒不要匆忙地跳入深水區, 因為大部分的機器學習問題都可以用比較簡單的淺水區技術例如隨機森林等解決, 比較複雜的問題例如影像辨識, 語音辨識, 與自然語言處理才需要使用深度學習技術. 
 
摘要整理前兩章筆記如下 : 

CH1 : 
  1. 深度神經網路是人類大腦的極度簡化模型, 深度學習一詞源自 Geoffrey Hinton 於 2006 年發表的一篇論文 "A Fast Learning Algorithm for Deep Belief Nets", 此論文介紹如何訓練深度神經網路使其能以 >%98 的準確度辨識手寫數字, 此論文重新喚起科學社群對神經網路的興趣. 
  2. 機器學習是屬於計算機科學的一門科學及藝術, 可讓計算機用資料來學習. Arther Samuel 於 1959 年為機器學習所下的定義是 : "機器學習是一種學習領域, 可賦予電腦學習的能力, 且不需要明確地將規則編寫為程式". 
  3. 雖然深度學習無疑是機器學習中最令人興奮的領域, 但也不要匆忙跳入深水區, 因為大部分的問題都可以用比較簡單的技術例如隨機森林等來解決. 深度學習非常適合用來處理複雜的問題, 例如影像辨識, 語音辨識, 或自然語言處理. 
  4. 計算機傳統上是以編寫含有複雜規則的軟體 (rule-based) 來處理問題, 但有些問題 (例如語音與影像辨識) 並沒有很確切的規則或演算法來編寫程式, 而是需要從資料中動態學習其規則.
  5. 機器學習的分類 :
    (1). 監督學習 vs 非監督學習 vs 半監督 vs 強化 (是否需要人類監督)
    (2). 線上學習 vs 批次學習 (是否可在運行中不斷學習)
    (3). 基於實例學習 vs 基於模型學習 (只拿新資料與已知資料比較, 或需建構預測模型)
  6. 機器學習適合處理的問題 :
    (1). 傳統解決方案需要許多微調與使用大量規則去處理的問題.
    (2). 使用傳統方法無法得到良好解答之複雜問題.
    (3). 波動的環境, 因為機器學習系統可以適應新資料.
    (4). 需要深入了解的複雜問題或有大量資料的問題.
  7. 機器學習的應用範例 :
    (1). 生產線產品圖片自動分類 (可用 CNN)
    (2). 辨識 CT 掃描圖片中的腫瘤部位  (可用 CNN)
    (3). 新聞自動分類 (可用 RNN, CNN, 或 Transformer 等 NLP 技術)
    (4). 自動標示論壇中的冒犯言論 (可用 RNN, CNN, 或 Transformer 等 NLP 技術)
    (5). 自動摘要長篇文章 (可用 RNN, CNN, 或 Transformer 等 NLP 技術)
    (6). 建立聊天機器人或數位助理 (使用 NLP 與詢答系統)
    (7). 預測公司營收 (使用回歸模型)
    (8). 建立語音回應 App (可用 RNN, CNN, 或 Transformer 等 NLP 技術)
    (9). 偵測信用卡詐騙 
    (10). 依據顧客消費紀錄協助制訂行銷策略 (分群技術 & 推薦系統)
    (11). 建立遊戲中的智慧機器人 (例如 AlphaGo 使用強化學習)
  8. 典型的監督式學習任務 (task) 是分類, 例如分辨垃圾信件. 另外一個任務是依據特徵來預測目標 (target) 的數值, 稱為廻歸. 不過有些廻歸演算法也可以用來分類, 反之亦然. 例如 Logistic 回歸演算法經常被拿來做分類用. 
  9. 在機器學習中, 屬性 (attribute) 指的是資料類型 (例如 gender), 而特徵 (feature) 則視情況而有不同意思, 但通常是指屬性加上其值 (例如 gender='female'), 屬性與特徵這兩個詞常常被混用.  
  10. 最重要的幾個監督式學習演算法 : 
    (1). K-近鄰
    (2). 線性回歸
    (3). Logistic 回歸
    (4). 支援向量機 (SVM)
    (5). 決策樹與隨機森林
    (6). 神經網路 (例如深度信念網路 deep belief network 為半監督式學習)
  11. 在無監督式學習中, 訓練資料是沒有標籤的. 最重要的無監督學習演算法 :
    (1). 分群 : K-Means, DBSCAN, 階層式分群 (HCA)
    (2). 異常與新穎檢測 : one-class SVM, 孤立森林 (isolation forest)
    (3). 視覺化與降維 : 主成分分析 (PCA), Kernel PCA, 局部線性嵌入 (LLE), t-隨機近鄰嵌入 (t-SNE)
    (4). 關聯規則學習 : 先驗, Eclat
    所謂新穎檢測 (novelty detection) 是檢測與訓練組的所有實例不一樣的新實例. 
  12. 降維 (dimensionality reduction) 目的是在不損失過多資訊的前題下簡化資料, 其中一種作法是將許多相關的特徵合併為一個, 例如汽車里程數與其車齡有緊密關係, 故降維演算法會將其合併為一個特徵, 以示汽車之損耗, 稱為特徵擷取 (feature extraction).
  13. 批次學習系統與線上學習的差別是, 系統能不能用持續傳入的資料來逐漸學習. 批次系統無法漸進學習, 必須先用所有可用資料來訓練, 通常需要大量時間與計算資源, 因此通常是離線的. 批次學習先訓練再於運營環境啟動它, 當它開始運行時就不再學習了, 只利用它學過的東西完成任務. 如果要讓批次系統認識新資料, 必須用新舊完整資料重新訓練, 然後停止系統換上新的模型. 有些任務 (例如預測股價) 因為新資料不斷出現, 因此頻繁地重新訓練系統所費不眥, 甚至因為資料量太大而無法使用批次學習系統.
  14. 線上學習系統可以在收到資料時立刻來學習, 資料彙分別依序傳入或以小批次 (mini batch) 的方式傳入, 每一個訓練步驟既快速又便宜. 線上學習適合需要持續接收資料串流的任務 (例如預測股價), 也適合在有限的計算資源下使用, 在學習過新資料實例後便不再需要它們, 因此可節省大量儲存空間. 
  15. 線上學習有一個重要參數叫做學習速度 (learning rate), 表示系統適應不斷變動的資料速度有多快. 此學習率若設得很高, 系統會很快適應新資料, 但往往也會快速忘掉舊資料. 反之若設得低, 系統的慣性大, 學習速度慢, 但也對新資料的雜訊 (不具代表性的資料點) 較不敏感. 線上學習有一個很大的挑戰是, 進入系統的不良資料會讓系統的效能逐漸下滑, 為了降低這種風險, 必須監視密切系統, 一旦發現系統性能下降立即關閉線上學習; 或者需要監視輸入資料 (例如使用異常檢測演算法) 並處理異常資料.  
  16. 機器學習系統還可以用其類推 (inference) 所學之方式來分類. 類推的方式主要有兩種 : 一是基於實例 (instance=based) 的學習, 一是基於模型 (model-based) 的學習. 基於實例的學習就是系統用死記硬背的方式學習樣本, 將新案例與學過的舊案例比較, 根據相似度 (similarity) 來類推新案例. 而基於模型的學習則是用一組樣本來建立這些樣本的模型, 然後再使用這些模型來類推新案例.  

2021年9月13日 星期一

小森林-冬春篇

今天在 Youtube 看到 "越哥說電影:小森林-冬春篇", 這部電影的夏秋篇與冬春篇我都有買 DVD, 那是幾年前去高雄電影館看過後在誠品買到的. 但我看了此介紹卻覺得怎麼與我所看的 DVD 有點不同, 難道是我記憶衰退了嗎? 





我一段時間想到就會重看這兩部電影, 看素顏清純的橋本愛 (市子) 將山野隨採的野菜食材做成可口的菜餚實在很療癒! 市子的感慨也讓人心有戚戚焉, 例如她採了很多馬尾菜, 烹調後卻只有一小碗, 花了這麼多時間與精力, 最後就只得到這麼點兒, "就像人生一樣, 即便盡力而為, 得到的也只是那麼一丁點兒, 還是有很多遺憾的地方". 

紀子的爺爺聽她一邊劈材一邊說上司的壞話忍不住罵道 : "你以為你是誰啊! 如果你在背後說別人不老實, 哪只說明你也不老實!", 是不是也讓你啞口無言呢? 

"小時候我們認為快樂, 媽媽, 美食最重要, 長大後卻覺得這想法很幼稚. 直到人生的最後, 猛然發現小時候的我們是對的. 那些最重要的東西, 我們一開始就已經擁有."

聽說日本東北地方的景色很美麗, 我以往只到過四國與關東關西一帶, 以後有機會能去了一定要去那裏看看. 


2021-09-15 補充 :

劇中演出紀子的演員是新生代的松岡茉優

機器學習筆記 : 使用 urllib 下載資料集的問題

今天在讀 "精通機器學習" 這本書的第二章時看到作者介紹下載資料集的方法, 他使用 urllib 模組撰寫了一個函式來簡化整個資料集程序, 範例程式碼如下 : 

import os
import tarfile
import urllib      # 應改成 import.urllib.request 

DOWNLOAD_ROOT = "https://raw.githubusercontent.com/ageron/handson-ml2/master/"
HOUSING_PATH = os.path.join("datasets", "housing")
HOUSING_URL = DOWNLOAD_ROOT + "datasets/housing/housing.tgz"

def fetch_housing_data(housing_url=HOUSING_URL, housing_path=HOUSING_PATH):
    os.makedirs(housing_path, exist_ok=True)
    tgz_path = os.path.join(housing_path, "housing.tgz")
    urllib.request.urlretrieve(housing_url, tgz_path)
    housing_tgz = tarfile.open(tgz_path)
    housing_tgz.extractall(path=housing_path)
    housing_tgz.close()

fetch_dataset()

此程式下載的是一份位於 GitHub 上的房屋資訊資料集壓縮檔 housing.tgz, 故需要用到 tarfile 模組進行解壓縮. 其中 DOWNLOAD_ROOT 儲存目標檔案的路徑,  HOUSING_URL 是目標檔案的完整網址, HOUSING_PATH 則是下載後的儲存位置, 此處指定放在程式所在目錄底下的 dataset/housing 子目錄. 

不過我實際測試確出現如下錯誤 (我使用的是 Python 3.7.2) :

>>> %Run urllib_download_dataset.py
Traceback (most recent call last):
  File "D:\test\python\urllib_download_dataset.py", line 17, in <module>
    fetch_housing_data()
  File "D:\test\python\urllib_download_dataset.py", line 12, in fetch_housing_data
    urllib.request.urlretrieve(housing_url, tgz_path)
AttributeError: module 'urllib' has no attribute 'request'   

我找到下面這篇 Stackoverflow 上的討論, 原來是 urllib 匯入方式要改為 import urllib.request (可能是版本問題), urllib 須明確匯入所需要的部分, 而不是匯入根模組 :


"With packages, like this, you sometimes need to explicitly import the piece you want. That way, the urllib module doesn't have to load everything up just because you wanted one small part."
 
我就只是將上面程式的第三行改成 import urllib.request 即可順利執行, 在 dataset/housing 底下找到下載的 housing.tgz 與解壓縮後的 housing.csv 檔 :




完整的程式如下 : 

import os
import tarfile
import urllib.request    

DOWNLOAD_ROOT = "https://raw.githubusercontent.com/ageron/handson-ml2/master/"
HOUSING_PATH = os.path.join("datasets", "housing")
HOUSING_URL = DOWNLOAD_ROOT + "datasets/housing/housing.tgz"

def fetch_housing_data(housing_url=HOUSING_URL, housing_path=HOUSING_PATH):
    os.makedirs(housing_path, exist_ok=True)
    tgz_path = os.path.join(housing_path, "housing.tgz")
    urllib.request.urlretrieve(housing_url, tgz_path)
    housing_tgz = tarfile.open(tgz_path)
    housing_tgz.extractall(path=housing_path)
    housing_tgz.close()

fetch_housing_data()

以後要下載其他資料集只要更改 DOWNLOAD_ROOT 與 HOUSING_URL 即可. 

好書 : 人工智慧與深度學習-理論與 Python 實踐

此書借自市圖, 係東吳資管系黃日鉦教授所著, 全書理論性較強, 但每章末尾則有 Python 程式實例, 對各種機器學習演算法有簡明公式推導與歸納整理, 看來是從多年教學資料彙整而得, 篇幅不大故顯得很精煉, 可說是極精華之作 :



Source : 博客來


特別是序言後面的八張彩圖, 描繪了各種目標函數與機率分配圖形, 以及神經元與機器學習演算法如 LSTM 與 Attention 等運作原理之圖解, 非常有價值. 

最後一章強化學習摘要如下 :
  1. 強化學習是一種特殊的學習過程, 與監督與非監督學習截然不同, 強化學習開始時沒有任何資料可供訓練與學習, 它是利用代理人 (agent) 對環境 (environment) 以嘗試錯誤法 (try-and-error) 來收集樣本資料, 據此來獲得最佳的結果. 代理人從與環境的互動中獲取報酬 (rewards), 強化學習的目標是讓代理人透過不斷試誤學習最佳化策略, 經由探索環境得到未來最大期望累積報酬 (expected cumulative rewards).





    狀態是指, 可以提供給代理人關於環境的所有訊息, 包括過去所有訊息之摘要, 故狀態具有馬可夫鏈的性質. 
  2. 強化學習假設代理人與環境互動時, 環境如何回應代理人的行動是由我們未知的模型所決定, 代理人可以在某個狀態 s 採取特定行動 a 以轉換到另一個狀態, 而狀態的轉換是由狀態間的轉換機率 (transition probabilities) 所決定, 整個強化學習可視為一個馬可夫鏈過程, 因此強化學習可以視為如下的軌跡 (trajectory) :
    s0, a0, r1, s1, a1, r2, s2, a2, r3, .... rt+1, ....
  3. 在深度強化學習中, 若資訊是不完整或未知, 稱為 model-free 強化學習; 反之若資訊完全已知則稱為 model-based 強化學習, 這可用動態規劃來得到最佳解. 其中 model-free 強化學習又可分為 value-based 與 policy-based 這兩大類, 其細類如下圖所示 :




    Model-free 強化學習都是直接從 episode 中學習, 不需要馬可夫鏈的轉換機率與報酬等資訊. 蒙地卡羅 (Monte-Carlo) 法是從完整的 episode 中進行參數更新, 而 TD (temporal difference, 拔靴法) 則是從 episode 中進行抽樣 (不是完整), 亦即可從不完整的 episode 中進行學習. TD 法又可分 on-policy 與 off-policy 兩種, 差別為代理人是否為實際的學習者, 是的話為 on-policy, 否的話即為 off-policy. Off-policy 的主要方法為 Q-Learning. 以政策為基礎的方法分為 DFO/evolution 與政策梯度 (policy gradient) 兩種, 前者針對不可微分函數, 後者 (policy gradient) 直接學習最佳化策略, 不需透過價值函數. 
  4. 馬可夫決策過程 (Markov Decision Process, MDP) 是一種離散時間的隨機控制過程, 結果的產出是部分隨機與部分決策者控制所形成. 強化學習的問題可以用馬可夫決策過程來表示, 因為所有強化學習的狀態都有馬可夫性質, 亦即每一個狀態發生的機率都只受前一期狀態的影響, 而不受更早的狀態影響. 
  5. 一個 episode 可以表示為 (s, a, s', r), 即代理人在狀態 s 決定採取行動 a, 此行動導致進入狀態 s', 並獲得報酬 r. 
  6. 報酬函數定義了強化學習問題之目的, 在每一個時間, 環境都會給代理人特定的報酬 (可能正也可能負), 代理人的目標則是決定如何行動以使整體報酬最大化. 故報酬是代理人政策改變之依據, 代理人會選擇能使報酬最高的那個政策. 但報酬是一個目前狀態下的概念, 若要考慮整體或長時間報酬, 必須考慮狀態的價值函數. 
  7. 一般求解馬可夫決策過程 (MDP) 有兩個目的 :
    (1). 在給定政策下估計狀態的價值函數及狀態行動價值 (又稱預測目的). 
    (2). 估計狀態的價值函數及狀態行動價值來求得最佳化政策 (又稱為控制目的)
  8. 政策為代理人的行為函數, 指導代理人如何在特定時間及給定的狀態下採取行動. 強化學習中一般而言設定政策是隨機的, 因此 MDP 下的政策只依據當時的狀態, 與過去的歷史政策無關, 故可將政策視為定態 (stationary). 
  9. 在強化學習中會使用行為政策 (behavior policy) 來產生訓練的資料, 而目標政策即為代理人想要使用的政策, 當行為政策與目標政策相同時稱為 on-policy, 否則稱為 off-policy. 
  10. 狀態價值函數 (又稱 Q 函數) 是用來衡量一個狀態多好, 或是一組狀態行動下可獲得多少報酬的指標. 報酬是一次的狀態行動下所獲得的立即性回饋, 而價值函數則是在長期行動下所得之平均報酬概念. 
  11. 在強化學習中, 已知的資訊包括目前狀態, 可行動方案, 與過去報酬. 未知資訊為機率轉換模型與報酬結構. 通常會假設機率轉換模型滿足馬可夫鏈轉換模型, 以及報酬結構為 episode (s, a, s') 的固定報酬. 強化學習的目的之一是預測, 利用預測來估計特定的政策, 看看其長期報酬是否為最大, 即求出價值函數的最大值. 
  12. Bellman 方程式是價值函數 (Q 函數) 的一種表示法, 它將價值函數分解為目前的報酬加上未來折現後的報酬, 其求解方法有三種 : 動態規劃, 蒙地卡羅法, 與 TD 法. 在計算 Bellman 方程式最佳解時, 為了避免搜尋空間過大導致的維度詛咒 (curse of dimensionality) 問題, 必須使用 Q-Learning 與 SARSA 方法來進行 Q 值計算, Q-Learning 為 off-policy 方法, 是以 one-step (一步) 的方式緩步改善 Q 值. 理論上 Q-Learning 學習中, 只要計算 Q(s, a) 的次數夠多且學習率夠小, 基本上不論如何選擇行動均會收斂到最佳政策. 然而 Q-Learning 與 SARSA 都有在訊訓練時容易出現震盪的問題, 這是因為一個小的行動改變, 都有可能會造成估計的行動價值產生劇烈的變化. 
  13.  Q-Learning 雖然是強化學習中最經典的一種方法, 但在實務上存在許多問題, 例如高估行動價值等. 此外, 因為 Q-Learning 是一種表格方法, 它是根據過劇狀態來更新 Q 值, 若某個狀債未曾出現過, Q-Learning 就無法解決, 亦即 Q-Learning 沒有預測能力. 而 DQN (Deep Q-Learning Network) 則可解決此問題, 它結合了深度神經網路與 Q 函數, 將 Q-Learning 的學習視為一個回歸問題, 故可找到一個函數來近似 Q 函數, 透過計算其誤差函數之梯度下降來更新參數, 將 Q-Learning 原本的 on-policy 學習轉成 off-policy 學習>