0%

打開Flutter web開發的方法並不困難,但在將手機app轉換(migration)到web app時會遇到許多轉換問題。在Flutter Engage的發佈會上官方有特別展示web migration要注意的地方。而使用Flutter開發跨平台應用程式時,第一個要考量到的就是package的跨平台兼容性。

使用pub.dev確認package兼容性

為了確保使用到的package在所有的平台都能正常的執行,專案在評估採用package時就必須確認是否兼容所有要發佈的平台,或是未來可能會發佈的平台。

剛起頭的專案在pub.dev尋找可採用的package時,就可以邊從資訊頁上方找到平台支援資訊。如果是從mobile轉移到web,就建議要掃過所有使用到的package,當然也可以直接把web app在debug模式跑起來就知道哪些不能用了。

dart:io不支援web的解決方法

dart:io可以協助處理File、Socket、HTTP與其他I/O任務,官方的Web FAQdart:io api都有特別寫到dart:io是不支援web app的。自己的經驗有遇過以下兩種情況有使用到dart:io須要特別注意:

1. 使用http處理http request

有些專案會使用dart:io的HttpClient處理跟Http server之間的request與response。但因為dart:io不支援web平台,所以官方建議使用http進行跨平台的http處理,在開發時也可以參考官方的Networking Cookbook有詳細的介紹。

2. 使用universal_platform檢查運行平台

一般跨平台專案會使用dart:io.Platform判斷當前的運行平台,但dart:io.Platform並不支援web,所以在運行於web環境時會出現Unsupported operation: Platfor._operationSystem錯誤。

1
2
3
4
5
6
7
8
9
import 'dart:io' show Platform;

if (Platform.isAndroid) {
// Do something on Android
} else if (Platform.isIOS) {
// Do something on iOS
} else {
// I want do something on web, but I only get Unsupported operation: Platform._operatingSystem
}

考量到web的情境下,另一個方法可以使用kIsWeb再搭配Theme.of與TargetPlatform就可以達到避開使用dart:io.Platform又能夠在建立widget時判斷當下的運行平台。

1
2
3
4
5
6
7
8
9
10
import 'package:flutter/foundation.dart' show kIsWeb, TargetPlatform;

final platform = Theme.of(context).platform;
if (kIsWeb) {
// Do something on Web
} else if (platform == TargetPlatform.android) {
// Do something on Android
} else if (platform == TargetPlatform.iOS) {
// Do something on iOS
}

但比較適合的方法為採用conditional import來解決。當dart:io能被import時採用dart:io.Platform判斷運行平台,無法被import的情況下判定為web。這種方式因為已經有被實作過了(universal_platform),所以可以考慮不用再自己實作。

使用conditional import在不同平台上運行不同程式片段

如果真的有一個重要的功能,但又不支援web該怎麼辦?像是上面提到只在mobile使用dart:io判斷運行平台,或是須要在mobile上用dart:io的HttpClient處理proxy問題,然後在web單純使用http處理與server的溝通。

若無法確保package跨平台兼容,相同feature在不同平台上要執行不同程式片段時,較簡單的方法是使用上述提到的平台判斷。例如使用universal_platform判斷當下運行的平台後,再執行於不同平台要執行的函式或是widget。

1
2
3
4
5
6
7
8
9
10
11
import 'package:universal_platform/universal_platform.dart';
import 'dart:js' // web platform need this, but it will cause Not Found Error on Android/iOS


if (UniversalPlatform.isWeb) {
// Do something or render widget on Web
} else if (UniversalPlatform.isAndroid) {
// Do something or render widget on Android
} else if (UniversalPlatform.isIOS) {
// Do something or render widget on iOS
}

但如果功能複雜度高且程式碼又很長的話,程式片段會變很長易讀性也會變差。另外若使用到只有web能用的package,也會造成在其他平台compile階段出現錯誤。這時會建議將不同平台要執行的程式片段拆成不同dart,並使用conditional import在開頭就決定要import哪個dart進來使用。

import “web_support_func.dart” if (dart.library.io) “non_web_support_func.dart”

以上是進行web migration馬上會遇到的跨平台兼容問題,除了專案本身使用到的第三方package可能不支援web平台外,要特別注意因為dart:io不支援web引發的http request還有platform判斷問題。如果真的有feature要在不同平台執行不同程式片段時,建議使用conditional import來設計程式碼。

在使用CanvasKit渲染器開發Flutter web app時,會在app啟動時從CDN服務下載CanvasKit,因此在採用CanvasKit渲染器的情況下預設是需要有連網的環境,否則app在啟動時會因為無法下載CanvasKit出現Failed to load resource: net::ERR_INTERNET_DISCONNECTED的錯誤訊息。

但如果是在有連網限制的開發環境下,雖然可以在開發環境使用html渲染器進行開發,並在佈署環境使用CanvasKit渲染器來達到最佳的操作效果,但因為兩種渲染器方法不同,所以渲染出來的效果還是有所差異,這會造成開發和佈署的版面不一致。

雖然預設需要連網才能使用CanvasKit,但目前版本(2.2.2)可以透過指定CanvasKit URL的方式置換掉預設的URL,而且也支援將CanvasKit與web檔案bundle在一起佈署,達到在離線或是有連網限制的環境下執行。但這個方法目前只支援profile與release模式,不支援debug模式

下載CanvasKit放入web資料夾

下載canvaskit.js與canvaskit.wasm並放進web資料夾,舉例來說目前版本使用的CanvasKit為0.25.1,可以先從CDN下載canvaskit.jscanvaskit.wasm。如果要使用profile模式就將兩個檔案在進/web/profiling/目錄內,要使用release模式的話直接放在/web/目錄。

修改url指到CanvasKit位置

透過--dart-define覆寫FLUTTER_WEB_CANVASKIT_URL環境變數,並指到下載的CanvasKit檔案位置。因為這裡的預設根目錄會是web/,所以如果用上述的檔案放法的話只要給/就行了。這樣在profile模式下會自動在/web/profiling/讀取CanvasKit,在release模式會自動在根目錄(預設輸出的local位置為/web/,實際佈署會存在於根目錄)讀取CanvasKit。

使用profile模式:

flutter run -d chrome --profile --dart-define=FLUTTER_WEB_CANVASKIT_URL=/

使用release模式:

flutter build web --dart-define=FLUTTER_WEB_CANVASKIT_URL=/

使用profile模式也可以透過在VS Code設定launch.json檔中的flutterMode與args參數,接著直接在VS Code執行啟動偵錯就可以進入profile模式。如果是使用release模式也可以透過dhttpd在本地端啟動web server測試。

1
2
3
4
5
6
7
8
9
10
11
// launch.json
"configurations": [
{
"name": "Flutter",
"type": "dart",
"request": "launch",
"program": "lib/main.dart",
"flutterMode": "profile",
"args": ["--dart-define=FLUTTER_WEB_CANVASKIT_URL=/"]
}
]

在專案中引用Roboto預設字型

如果app內Text相關widget沒有指定字型(fontFamily),或是雖然有指定字型但沒有將字型檔(.ttf/.otf)放進專案並在pubspec宣告,使用CanvasKit渲染器時會預設從fonts.google.com下載Roboto-Regular使用。

因此要先將Roboto放在專案並宣告在pubspec,在web啟動時就會自動使用專案內的字型檔而不會連到google下載字型。如果有使用到其它字型,可以參考官方的字型引用方法。

從google下載下來會有Rotobo全部的ttf檔,只要把Roboto-Regular放進來即可

接著在pubspec宣告引用

1
2
3
4
5
6
flutter:
uses-material-design: true
fonts:
- family: Roboto
fonts:
- asset: assets/fonts/Roboto-Regular.ttf

預設app啟動就會自動使用Roboto而不會連到google下載字型。

在Flutter官方提供更簡易的支援前,可以使用上述的三個步驟來達成在連網限制的環境上使用CanvasKit渲染器,在部署時只要將CanvasKit檔案一起部署即可。

參考資料:
Flutter issues - Can’t build web apps with CanvasKit without internet
Flutter issues - support bundling CanvasKit instead of CDN
Flutter issues - support specifying CanvasKit URL in debug builds

在Flutter正式將web支援放進stable channel後,現在只要從官網下載最新的stable版sdk,就可以直接開發與佈建web應用程式。這篇會紀錄Flutter web開發從前置準備到發佈前的環節,提供給有Flutter經驗而且要嘗試打開web支援的開發者。如果需要從頭開始建立Flutter的開發環境,可以依不同開發平台參考flutter.dev的說明。

前置準備

Windows開發為例,在完成Flutter開發環境與IDE設定後,就可以執行flutter doctor來協助診斷是否設定完全。其中可以看到開發web需要使用chrome所以會有一項檢查chrome是否有安裝。

flutter doctor

1
2
3
4
5
6
7
8
9
Running "flutter pub get" in flutter_tools...                       8.4s
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, 2.2.2, on Microsoft Windows [Version 10.0.19041.1052], locale zh-TW)
[√] Android toolchain - develop for Android devices (Android SDK version 30.0.2)
[√] Chrome - develop for the web
[√] Android Studio (version 4.0)
[√] VS Code (version 1.57.1)
[√] Connected device (2 available)
• No issues found!

其實Flutter也可以使用Edge,所以使用flutter devices來確定有連接到的device,如果有安裝Edge也會被偵測到。

flutter devices

1
2
3
4
2 connected devices:

Chrome (web) • chrome • web-javascript • Google Chrome 91.0.4472.106
Edge (web) • edge • web-javascript • Microsoft Edge 91.0.864.54

建立Flutter專案與打開web支援

如果是要從頭建立一個專案,可以直接使用flutter create [project name],建立的新專案就會帶有web資料夾,裡面會包含所有web需要的檔案像是index、manifest與icons。如果是已經存在的專案但要打開web支援,則可以使用flutter create [project path]就會自動產生web目錄。

flutter create [project name]

設定IDE(VSCode)

打開VSCode點擊右下角設定,就能夠選擇現在可使用的emulator,為了開發web所以這裡選擇chrome。如果不先選擇emulator在執行run debug時也會跳出來請你選擇。

如果需要在debug的時候帶進參數,可以在launch.json檔裡面寫入args。例如Flutter web預設使用的渲染器是Auto模式,即在桌上裝置使用CanvasKit,在行動裝置使用html。如果要指定渲染器都使用html的話,就可以寫進args。

運行debug模式

再來可以在IDE啟動偵錯就會開始debug模式,除了使用IDE啟動偵錯外也可以透過指令flutter run來運行debug模式。因為Flutter會運行dartdevc並對app進行compile將dart轉成javascript,所以第一次啟動debug模式的時間會比較長。web開發可以使用hot restart,Flutter只會對有更動到的部份recompile成javascript,而不需要從頭compile整個app。在IDE可以透過restart debug功能觸發hot restart,如果是使用command line可以按R觸發。

flutter run -d chrome

1
2
3
4
5
6
7
8
9
Launching lib\main.dart on Chrome in debug mode...
Waiting for connection from debug service on Chrome... 22.7s
This app is linked to the debug service: ws://127.0.0.1:50526/2kZvThD-K8w=/ws
Debug service listening on ws://127.0.0.1:50526/2kZvThD-K8w=/ws

Running with sound null safety

To hot restart changes while running, press "r" or "R".
For a more detailed help message, press "h". To quit, press "q".

建構web發佈版本

在開發完準備要發佈後,可以使用flutter build web指令來建構發佈版本。預設發佈時使用的渲染器是auto模式(即在桌上裝置使用canvaskit,在行動裝置使用html),如果要指定使用特定的渲染器,可以使用--web-renderer。

flutter build web --web-renderer [auto/html/canvaskit]

執行指令後Flutter會使用dart2js將app compile成一個單一的javascript檔案main.dart.js,發佈完後會在build目錄內產生web目錄,這整包資料會包含所有必要的檔案並且需要同時佈署。

使用dhttpd於本地端運行

最後在實際將整包web佈署到server上之前,可以使用dhttpd嘗試在本地端先運行app,檢視要發佈的app的功能與asset是否都正常。dhttpd安裝方法可以參考dhttpd在pub.dev的安裝方法,並在運行時指定build/web/目錄就可以在本地端運行要發佈的app,在本地端運行app檢查沒問題後就可以準備將web佈署到正式的server了。

dart pub global run dhttpd –path build/web/

1
Server started on port 8080

Flutter的web支援目前可以很容易的將自己開發好的app轉成web上線,而且設定到發佈其實也不困難,可以達成快速的支援跨平台的需求。但實際在使用時還是會遇到像是將app轉成web app所會遇到的migration問題,或是渲染器選擇使用跟canvaskit預設不支援offline模式問題,而且發佈完成的app本身會是一個PWA(Progressive Web Apps)需要設定manifest。這些問題會在實際實作的過程會遇到,有些自己踩過的坑也會再持續紀錄下來。

參考資料:
Fluter docs - Web

最近幾個月來工作的職務上有些轉換,加入到一個小規模的app開發團隊,並且嘗試導入Flutter開發一套codebase就能夠佈署到多個平台的跨平台app。其實網路上已經有非常多介紹Flutter的文章,而原生與跨平台的解決方案比較也很多人討論過,所以這篇會以一個初入Flutter的新手,到成功交付產品上線後,一直到採用Flutter Web的心路歷程分享。

老闆問:能不能弄個跨平台app

其實跨平台一直都存在並且持續有新的技術出現,雖然Flutter還很年輕,但是在這幾年的熱度也快速的竄升。我認為採用跨平台技術對於單純開發給公司內部使用的系統來說,最大的優點就是在人力的節省。在一個開發了web就會被問能不能放在行動裝置上,作了手機app就會被問能不能web也有的環境下,為了提供多個平台管道給使用者,一支系統難道不能只開發一次就好了嗎(彷彿老闆的聲音飄出來了)。

要開發多個平台可以使用的應用程式,除了能夠支援不同裝置尺寸的web應用程式外,如果需求面上又希望能保留行動裝置功能的支援,那Flutter自然是一個適合的選擇。

Flutter官方網站直接的介紹是這樣寫的:

Flutter is Google’s UI toolkit for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase.

Web Hit Stable Millestone

Android和iOS是Flutter最先開始穩定支援的平台,但終究web平台還是一個觸及度最高的管道,也許平板電腦不是每個人都有,但user們還是都用電腦上班。在專案達成mobile first的任務,接著要開始擴大觸及程度時,剛好迎來Flutter Engage在3月3號發佈了Flutter 2的更新。

Perhaps the single largest announcement in Flutter 2 is production-quality support for the web.

Flutter 2正式將Web支援放進stable channel,原本要打開Web支援還得使用beta channel才行,甚至官方也建議在web正式穩定支援前,要發佈成產品都需要仔細的確認執行起來是否有bug。當下還在觀望是否該導入Flutter Web才過沒多久,隨著Flutter 2的發佈後,馬上就在隔天拿起現有的Flutter專案進行POC,直到最近成功的將原本的app正式發佈給使用者使用。

從Flutter到Flutter Web

下面我分享三個如果今天從新手Flutter開發者,一路走到開發產品等級的Flutter Web可能會發生的心路歷程與重點學習資源:

  • 從零開始開始的Flutter菜鳥

身為第一次接觸Flutter的開發者,自己覺得還不算難上手,基本上跟著官方的文件把開發環境裝起來後,接著讀完stateless和stateful widget還有column和row的layout概念後,就可以作出簡單的切版畫面。

Flutter的widget其實很多,常常都是需要的時候邊查邊實作,通常第一步會先找是不是有Flutter已經提供的widget(像是drawer、search、bottom navigation等),如果沒有的話這裡推薦pub.dev還有Flutter Gems,這兩個網站的資源可以幫你找到很多你需要的功能。最後再上http加上json serialization,或是直接跟著cookbook作大致上就能接完後端了。

  • 一個好的狀態管理很重要

成功發佈完app的1.0.0版後,接著來的就是各種app的新功能需求,在追加越來越多的跳頁功能下,開始出現很多的渲染失敗或是改一改就出現畫面亂跳頁的bug。這時候才會發現真的需要一個好的狀態管理,領悟到沒有好好規畫狀態管理的app不止難以維護,更會造成app效能變差。如果這段是在趕鴨子上架的過程被忽略掉的,這時直接建議把Flutter狀態管理這篇讀完,至少用Provider好好設計每個widget的狀態變化與重新渲染的時機。

另外要注意在取後端資料時是否有使用FutureBuilder,FutureBuilder可以幫你確認取得async資料(通常是後端的一包資料)的各種狀態,例如在接到資料前可以先拋出一個自己準備有讀取效果的widget,並在取得資料後再使用這包資料渲染出要程現的widget(對Dashboard而言可能就是一些圖表)。

  • 打開web支援要怎麼作

Flutter web在開發上其實也不困難,而且可以在VSCode用debug模式就可以直接進行開發,Web support for Flutter官方有一個篇章在作介紹,如果是專案一開始就有打算作Web平台,那建議直接跟著文件說明就能夠建立Flutter Web應用程式

但如果今天是有一個已經開發好的app產品要打開Web平台的支援,通常就會遇到比較多的阻礙,當我以為一個彈指就能把web支援打開,但遇到的就是滿滿的error訊息。要將現有的app打開web支援,通常在開發上會遇到的問題我大致上分成三種。

  1. Responsive Design(響應式設計): 不管是從手機或是平板使用的app,如果沒有對螢幕尺寸作適當的處理,通常直接放到web都會直接跑版,官方文件也有一個篇章在介紹響應式設計

  2. Cross-Platform Support Package(跨平台支援套件): 在開發過程中會有一定程度的使用第三方的package,要注意是否有支援所需要的平台特別是web,通常可以在pub.get查到。

  3. Platform Dependency Feature(平台相依性功能): 因為不同平台還是會有差異存在,有些dart自帶的library本身就不支援,比如說web就不能使用dart:io相關的函式,因此在開發的時候需要特別注意。如果需要針對不同的平台用該平台才有支援的widget,通常會先用邏輯判斷目前使用者是使用哪個平台,再渲染該平台特有的widget。

以上是自己在投入開發Flutter到打開web支援的一些心得整理,開發過程也許遇到許多不同的問題,特別是在web支援的部份遇到了許多挑戰。雖然花了許多時間解決各種遇到的轉換問題,但確實可以達到透過single codebase在web和app渲染出一樣的畫面!

上一堂講到kernel logistic regression,並證明L2-regularized linear model都可以kernel化。這堂課會嘗試將kernel放進L2-regularized linear regression作出kernel ridge regression。

因為已經知道最佳的w會是βZ的線性組合,所以這裡一樣可以將w轉換為z的線性組合,當出現Z與Z乘積時就可以換成kernel,轉成透過kernel trick解β問題。

因為這是一個無條件的最佳化問題,所以可以求梯度為0,即對β微分等於0求出讓梯度為0的β最佳解。因為K一定大於0所以可以求出需要求解的反矩陣來解出最好的β。原本之前說的要作nonlinear的方法是先作transform轉換再作regression,而且現可以另一種方法即使用kernel來達成nonlinear regression。

比較linear ridge regression和kernel ridge regression,kernel ridge rgression具有非線性性質所以有更彈性的應用來解決較複雜的問題,但其缺點就是要付出較大的計算時間複雜度。

kernel ridge regression當然也可以拿來作classification,這個方法又稱為LSSVM(least-squares SVM)。在比較Soft-Margin SVM和LSSVM,兩者得到的邊界可能會差不多,但比起Soft-Margin裡面求出的α是稀疏的,前面在解kernel ridge時β沒有特別的限制,因此β會求出dense的解,因而得到比Soft-Margin更多的support vector。而support vector較多的情況下,會造成在預測時效率較差。所以再來的重點是如何讓β也可以和標準SVM一樣求出稀疏解。

這裡介紹到Tube Regression,比起原本的regression會計算每一點算與實際值的誤差,tube regression允許在一個範圍內,即落在tube內的點可以不計算error,而落在tube外的點除了計算與實際值的誤差外要再減掉tube的寬度,得到一種新的error計算方法。這種error被稱為ε-insensitive error,下一步會嘗試透過這個新的error來解出稀疏的β值。

在比較Tube Regression和Squared Regresion會發現,兩者在預測值和實際值接近時,error計算是比較接近的。但是在相差越來越大時,squared regression的error會成長的比較快,因此也比較容易受到雜訊的影響。

L2-Regularized Ture Regression在求解時可以使用任何unconstrain的方式,和之前SVM求hinge error時一樣會遇到max無法微分,雖然也可以使用reprsenter的方式作kernel化,但卻無法保證最後求出的是稀疏解。而標準的SVM一樣無法微分,但是可以被寫成QP問題求解,再透過求對偶問題達成kernel化,而且因為滿足KKT condition可以保證其具有稀疏性。因此這裡可以把L2-Regularized Tube Regression模擬成像原本的SVM再求解(加入C並將w0拆出b值)。

再進一步加入ξn來紀錄犯錯的程度,其中為了將限制式的絕對值拆掉,會得到upper tube violation和lower tube violation兩個ξn。這個式子就是一個Support Vector Regression(SVR)的標準式,而且也會符合QP問題。

這個SVR的標準式會有兩個參數,一個是C和之前的SVM一樣用來控制regularization和violation之間的trade-off。第二個是ε用來紀錄tube的寬度決定允許的犯錯範圍。

有了SVR的primal後,就可以引入Langrange multiplier來轉成對偶問題,所以這裡會引入兩個α來對應兩個ξn,再來就是重複之前課程的推導。

因為SVM和SVR的問題相似,所以可以發現兩者的對偶很相近,而且可以透過QP來解。

回到原本的問題,我們希望β可以是稀疏的,也就是β系數在某些情況要是0。可以發現如果資料是在tube內部ξ會為0,而ξ為0的情況下complementary slackness括號內部的值就不等於0,又因為α和括號內部其中要有一邊要為0,所以α必定要為0,也就是β會等於0。所以位在tube內部的β會為0,而tube外面或是邊界就是對w有貢獻的support vector,這就可以得到β要稀疏解。

到目前為止教過的linear model總共有5種,在分類問題上,除了最早教到的PLA/pocket方法,這幾講教的linear soft-margin SVM是透過找到具有soft-margin性質的超平面來解分類問題;在regression問題中除了前面講到的linear ridge regression,還引申出透過Tube與SVM概念所延伸出來的linear SVR。另外還有regularized logistic regression結合regularization作模型複雜度修正與maximum likelihood概念來處理分類問題。

當linear model具有regularized性質時都可以引入kernel的概念,而這6講的內容分別教了如何將SVM、ridge regression、SVR和logistic regression帶入kernel trick。其中第一排的PLA/pocket和linear SVR,因為其表現都沒有第二排linear soft-margin SVM和linear ridge regression來的好,所以實務上比較少用。第三排的kernel ridge regression和kernel logistic regression因為β解出來是不是稀疏解,所以實務上傾向使用第四排的SVR。

kernel是一個power的方法強化linear model來解更複雜的問題,但要注意的是要小心處理參數的選擇以避免發生overfit。

總結這一講的內容,教到將representer theorem應用在ridge regression上,並結合tube regression引申出SVR的primal形式,再透過求解對偶得到β的稀疏解。最後則比較到目前為止教到的線性模型,並且有嘗試使用kernel來解更複雜的問題時要小心使用,避免overfit。

參考資料:
Machine Learning Techniques 6

上一堂講到在允許違反部份邊界下,引入C當作懲罰值,將Hard-Margin SVM轉成Soft-Margin。而Soft-Margin的對偶問題和Hard-Margin非常相似,只差在對偶問題中的α有上限值C。這堂課會談如果將kernel trick引入logistic regression可以怎麼作。

回顧Soft-Margin SVM,裡面發生的違反邊界會被紀錄在ξn裡面,而ξn會是1-yn(W.Zn+b),因為會紀錄下與1的距離來表式違反的程度和嚴重性;相反的在沒有違反下ξn值為0。再來可以把ξn寫成另一個更簡單的式子,即為轉換成對1-yn(W.Zn+b)和0之間取最大值來算出ξn,並帶入SVM的最佳化式子中,將ξn轉成b和w算出來的結果。

這個轉換後的最佳式其實和之前教過的regularization非常相似,就是在求長度w控制複雜度之下加上regularization項次。所以從這裡可以看到,Soft-Margin SVM其實可以從regularization推導過來,但上一講會選擇從Hard-Margin推導過來的原因是因為這個式子並不是一個QP問題,而且在兩項取最大值時也會有微分求解問題。

在前面課程推導對偶問題時有提到SVM和regularization的相似地方,而這裡可以看到Soft-Margin SVM其實就和L2 regularization是相似的,所以SVM的large margin就是一個對regularization的實現。但其中細部的差異在於Soft-Margin會使用到特別的error表示,而C的大小即為控制regularization的程度(越大的C代表越小的regularization)。

在計算zero-one error時會是一個像階梯狀的函數,因為ys是正的error為0(猜的方向正確),ys如果是負的error為1(猜的方向錯誤)。而SVM的error會由兩個線段組成,在ys離1越遠error會越大,而ys為正時error為0,SVM的error為zero-one error的上限,所以Soft-Margin也是在間接的把zeor-one error作好。

如果將SVM和logistic regression比較的話,從圖上可以發現logistic regression會和SVM很接近,進一步將ys在正無限大和負無限大比較時,兩者的error也是相近的,所以SVM其實也很像在作L2-regularized,因為在加上error項後,其實error項和logistic regression的error項非常接近。

在比較三種兩元分類方法,最一開始教的PLA需要在線性可分的情況下才好作,如果在線性不可分要透過pocket也不好達成;logistic regression的函數有很好的最佳化性質,而且加上L2-regularized後還能對模型的複雜度有一定的保護;Soft-Margin SVM差異在於最佳化使用了QP,並且在有large margin的理論保證,其error的計算方式雖然和logistic regression不同,但確實非常接近,而且兩者都是在作最佳化zero-one error的上限值。因此regularized logistic regression其實就幾乎是在作Soft-Margin SVM。

在二元分類問題中可以有兩種簡易的方法,第一種是透過SVM找到最佳的b和w,再直接丟進logistic reression作出二元分類,但這樣的手法就缺少了點logistic regression像是maximum likelihood的特色;第二種則是將SVM找到的最佳b和w,放進logistic regression當作始初解,之後再作最佳化找到最佳解,但這樣和本來logistic regression用其他初始解再找最佳解的結果相同,而且解變動後也失去了SVM的特性。

如果要同時有兩種方法的特性,可以先作SVM找到分數,再加上放縮的A與平移的B,最後再放進logistic regression訓練,其意義為先透過SVM找到最佳的超平面,再透過logistic regression作放縮與平移調整到最佳。也就是先透過SVM當作轉換,再丟進logistic regression作兩階段的的學習。

透過這樣的方式就可得到SVM的Soft binary clasifier,因為引入縮放和平移所以會和原本的SVM會不太相同,在求解logistic regression時可以使用Gradient Descent即可。到這裡我們透過kernel SVM去推論logistic regression在z空間的近似解,但這裡不是真的在z空間解出logistic regression,而且透過SVM在z空間找近似解。

複習一下SVM能使用kernel的關鍵點,是在於將W.Z的內積問題,將W轉換成一堆Z的線性組合,成為求Z.Z內積問題,這時候就能讓kernel上場。在SVM中,W就是Zn的線性組合,組合的系數就是對偶問題的解;PLA的W也是Zn的線性組合,組合的系數取決定每次犯錯的修正,而logistic regression中的W也是Zn的線性組合,這些系數來自梯度下降最終求得的結果。所以這些方法應該都能夠應用kernel trick輕易的在取得z空間的解,最好的W即可透過這些Zn來表達出來。

老師在這裡用例子證明任何的L2-regularized線性模型皆可以應用kernel。

因此,在L2-regularized的logistic regression,就可以將W替換成Zn的線性組合,從求W轉成求所有β系數。在將Zn線數組合代進入取代W後,就能將Z.Z的部份使用kernel,接著就可以透過梯度下降來求最佳解,這就是kernel logistic regression。

如果看這個kernel logistic regreaaion,先從β的角度看起來就像是在求β的線性組合,而且其中還將kernel用來作轉換與正規化;如果是從w角度來看,即為藏有kernel轉換與L2正規化的線性模型。但要注意常常β解出來會有很多0。

總結這堂課從將Soft-Margin SVM解釋成使用hinge error的regularize model,再進一步和L2-regularized logistic regresion比較兩者的相似性,透過結合兩者建立two-level learning應用在Soft Binary Classification,最後真的證明logistic regression也可以應用kernel並求出z空間的解,但會付出解出來很多β會為0的代價。

參考資料:
Machine Learning Techniques 5

上一講教了透過kernel trick來處理dual SVM中的轉換與內積,並介紹了不同的kernel function,從線性到無限多維轉換的Gaussian,但SVM還是有可能會overfit。overfit有兩種可能的原因,第一種為使用了過度複雜的轉換,第二種則是堅持要把所有資料完全的切分開來。以上面這個例子來說,如果可以容忍有錯誤分類,但可以使用簡單的線性切分也可以得到不錯的邊界;相反的,如果使用比較複雜的轉換確實完美的將資料都區分正確,反而容易造成overfit。

但該如何解決這個問題呢?之前在講pocket演算法時,pocket會找到能分錯資料最少的超平面,但hard-margin SVM除了要求要全部分對之外,還要選w長度最小的超平面。所以如果可以讓SVM也能像pocket一樣容忍一些錯誤,即不管部份分錯的點,就能達到某種程度的條件放寬。這邊引入C參數來代表放寬條件的相對重要性,C較大時代表越重視不要犯錯越好,C越小時代表越重視w長度,即分對的資料的margin越寬越好。

但放寬後的新問題將會違反QP的條件(min為二次式,條件要為一次式),而且在允許犯錯的情況下,沒有辦法分辦到底犯錯的嚴重程度(小錯或是大錯)。為了解決這兩個問題,引入ξn來紀錄離想要的值(是否靠近1)有多近,即將犯了幾個錯誤轉成犯了多大的錯誤。除了可以解決無法分辨犯錯程度的問題,也可以讓問題符合QP。

如同前面說的,C可以用來控制large margin和margin violation,越大的C代表越重視分類的正確性,而越小的C代表margin可以比較大,但會造成某些資料分錯。

當我們將Hard-Margin轉成Soft-Margin後,再來就是將它轉成對偶問題,就可以如同Hard-Margin一樣引入不同不同維度轉換的kernel function。第一步就是帶入Lagrange,有別於原本的Hard-Margin只需要帶入一個α,因為這邊分成兩個部分,因此分別帶入α和β。再進一步將ξn作微分拿掉ξ,可以發現在最佳解的位置C會等於αn+βn,就可以將βn簡化成C-αn,又因前後兩項分別都有C-αn互相消掉,所以可以再將式子簡化。

到這邊可以發現內部要解的問題其實就和之前的Hard-Margin SVM相同,只差在外面的條件帶入了C的限制,所以這邊可以和之前一樣分別對b和w作微分來得到Soft-Margin SVM的對偶問題。

再仔細看一下Soft-Margin SVM的對偶問題,可以發現和Hard-Margin SVM的對偶問題幾乎一樣,只差在現在α會有一個上限值C,而這個C是來自引入β後得到的。

當我們把Gaussian SVM用在Soft-Margin上並調整不同的C值時,可以發現最左邊當C設成1的時候,會得到一個邊界但有部份的資料是分錯的;當C一直調到到100可以發現邊界會越來越複雜,但是分錯的資料會越來越少。其中最右邊的邊界雖然可以讓所有的資料分對,但也可以造成雜訊的容忍度下降,也就是容易造成overfit,因此C和γ值需要慎選。

介紹完Soft-Margin和kernel,參數該怎麼選呢?第一個方法可以嘗試使用cross validation的方式,並帶入不同的C和γ值來找到比較好的參數組合。

另一個有趣的方法則是使用Leave-One-Out CV,SVM在使用Leave-One-Out CV的error會小於等於support vector的比例(support vector的數量除上所有樣本數),其概念為即使少了non support vector(non-SV)的點,結果並不會影響到margin的計算。

所以也可以嘗使用support vector的數量來選擇模型,因此在作cross validation前可以透過檢查support vector的數量,去掉support vector數量較多的組合作模型的安全檢查。

總結這堂課介紹了soft-margin SVM,其核心概念在於不強求把所有的類別都分對,並加上犯錯的程度大小的懲罰項。在推導的部份,Soft-Margin和Hard-Margin結果幾乎一樣,只差在Soft-Margin在αn會存在上限值C。而SVM可以將資料分成三種,分別為non-SV、存在邊界上的free-SV與可能違反邊界的SV。最後在model selection可以使用cross-validation或是參數SV的數量來作選擇。

參考資料:
Machine Learning Techniques 4

上一講說明了dual SVM,好像可以讓SVM與高維度的z空間兩者可以脫鉤,但以計算的角度,仔細看會發現在整個求解過程中的Q矩陣還是會在z空間作內積。而在z空間的內積在運算可以拆成兩個部份,一個是先透過某個函式φ轉換到z空間,第二再進行轉換後的內積。但這樣的運算方法就會變成z空間的維度作內積。

透過這個polynomial transform可以發現,其實是能先在x維度作完內積,再作函式φ的轉換,這樣就可避免產生在z空間作內積的情況,透過這個簡化的方式計算內積稱為kernel function。

回到svm不管是在計算b和最佳的超平面gsvm時,都可以將kernel function應用在計算中。這種合併函式轉換和內積計算,來避免在在高級度空間進行內積計算的方法,又稱為kernel trick。

延續前面的Poly-2 kernel例子,在每項前面再加上不同的係數,就能再對計算進一步簡化。雖然不同的函式都是轉換到相同的維度,但是會計算出不一樣的內積得到不同的距離,所以得到的幾合意義和算出來的SVM margin也會不同。其中最常使用的kernel為加上根號2與γ值的General Poly-2 kernel。

舉例來說,中間為原本的Poly-2 kernel,而左右兩個為General Poly-2 kernel但代入不同的γ值,左邊帶很小的γ右邊帶很大的γ。可以看到得出的邊界是不同的,所以得出的支撐向量也不同(方框的點)。雖然三個kernel的邊界不同,margin的定義也不同,但這邊沒辦法說哪個邊界會比較好,因為三個kernel都可以把點分開。

前面提到的Poly-2可以再加入ζ參數,就會形成一個包含3個參數的一般型態Polynomial Kernel,包含了(1)γ控制x內積算完後的放縮程度,和(2)ζ控制常數項與乘上轉換係數如何對應(3)Q代表poly作幾次方的轉換。而且不管作了幾次方的轉換,都是透遇kernel而不會在高維空間計算。使用高次方的轉換仍然可能會有overfit的風險,但SVM會透過找到最大margin來避免overfit,所以透過kernel可以讓SVM達到避免ovefit,又可以使用較複雜的模型,同時不需要高度的計算複雜度。SVM結合polynomial kernel又稱為Polynomial kernel。

Polynomial kernel如果設定成1次轉換,γ設成1,ζ設成0的話,就等於是原本的linear kernel SVM。老師建議大家在使用SVM時,可以從linear SVM開始,如果linear就可以解決問題,就不需要作高次方的nonlinear轉換。

既然可以透過kernel trick來達成高維度轉換的運算,那是否有可能作到無限多維轉換呢?老師這裡透過證明可以發現,即使在無限多維的情況,使用高斯函數作轉換是可以辦到的。

所以高斯函數也是一種kernel trick的方法,讓資料映射到無限多維的空間後找到支撐向量和超平面。但其實際上的意義為他會產生以支撐向量為中心的高斯函數的線性組合,也常被稱為RBF(Radial Basis Function) kernel。

到目前為止,SVM可以辦到使用kernel trick作無限多維的轉換後,再透過最大margin找到超平面,而SVM裡面的最大margin計算,可以對無限多維的轉換有一定程度的保護。但要注意SVM仍然會有overfit的問題,當γ越調越大時會使原本Gaussan裡面的σ變小Gaussian就會變尖(γ=1/2σ^2),所以還是要小心γ參數的選用會決定SVM的表現,通常不建議使用太大的γ值。

前面介紹到許多的kernel,不同的kernel也有其好處和壞處

  1. Linear Kernel
    最簡單的kernel,就是不作任何的維度轉換,其好處就是最簡單安全。除了QP好解之外也具有可解釋性,因為是線性所以可以透過每個特徵的權重來看出特徵的重要性,應該是遇到問題時要先被拿來嘗試使用的kernel。但其壞處在於問題如果是非線性的,那只靠線性沒辦法很好的解決問題。
  1. Polynomial Kernel
    Polynomial的好處在經過非線性轉換後,會比linear限制還要少,而且可以透過設定Q來限制模型的維度。缺點在於Polynomial有3個要給定的參數較難選定,而且在計算值時QP不容易解,尤其是γ過大的時候,因此Polynomial比較適合用在心裡有假設而且使用比較小的Q。
  1. Gaussian Kernel
    Gaussian可以作到無限多維的轉換,所以會比Linear和Polynomial使用限制更少。而且Gaussian的數值會落在0到1之間,所以數值計算難度較低;又因為Gaussian只有一個參數要給定,比起Polynomial要選3個參數,Gaussian可以更容易作參數選擇。Gaussian的壞處在於被轉換到無限多維後,模型沒辦法容易的被解讀;另外他的速度會比單純使用線性還要慢,又因為轉換較複雜,因此使用的不好會造成overfit。

Kernel表示一種特徵的相似度,但是不是任何相似度都能用來當作Kernel呢?這邊老師給出必須要滿足對稱性還有postive semi-definite兩種條件才能當作Kernel。

這一講提到了如何透過kerlnel trick快速計算轉換和內積,避開最複雜的計算,另外也比較了不同的kernel的優點和缺點。

參考資料:
Machine Learning Techniques 3

前面第一堂課在介紹線性的SVM,透過二次規畫找到最大margin的支撐向量來建立更強健的模型。這堂課將會延讀svm並加上非線性轉換的方式,讓SVM不止可以控制模型複雜度,也能結合特微轉換來提高模型效果。

假設透過特徵轉換的方法將SVM轉為非線性,原本的x會被轉換到z空間,但又希望可以在解SVM最佳化問題時,能夠擺脫轉換到z空間高維度的依賴。即將非線性轉換轉成另一個對等問題,不管轉到幾維的空間,都只會有N個變數,變數數量不會和要轉換的維度的有關,這稱為原本SVM的對偶問題(dual problem)。

之前篇章在介紹regularization時,引入了Lagrange Multipliers的概念,將原本帶有W長度小於C的限制式,轉成加上Eaug後求最小值,其中λ會被乘上每個系數加進最佳化式子中。因為SVM也是一個有條件的最佳化問題,但是在引入λ時有別於regularization中被當作給定值,在SVM中會被當作未知的值來解最佳化問題。

為了把原本有限制式的SVM最佳化問題,在引入Lagrange Multiplier後轉成沒有限制式的問題,會將限制式移項乘上α(即λ在SVM文獻為α)後相加。但是將有限制式的最佳化的問題,轉成沒有限制式的最佳化的問題,是否能夠得到一樣的結果?老師在這裡舉了兩種例子來說明其實會得到一樣的結果。假設選到一組會違反原本限制式的B,W(即α乘上的項次會是正值),這種最終會在解最小值SVM問題時被逃汰;相反的,如果選到一組符合原本限制式的B,W(即α乘上的項次會負值),這種解反而在最後會被留下來。因此將限制式轉益沒有限制式的最佳化問題,其實只是把限制藏進求最大值的最佳化中。

在轉成無限制式的最佳化問題後,會發現假設在固定α之下,整個最佳化得出來的值會比任一的Lagrange值還要大,甚至取完最大值右邊的不等式也一樣成立,這個min和max互相交換稱為lagrange dual problem。

對於二次規畫,這個duel problem因為滿足強對偶關係,因此求出來的b,w,α值對於左右兩邊的式子都是最佳值,因此可以將原本左邊的最佳化問題,直接轉成解右邊的對偶問題來求最佳值。

再進一步化解最佳化問題,如果要得到內部式子的最佳值,因為其為無限制的最佳化問題,因此最佳解會產生在梯度為0,即內部式子微分為0。首先對b微分後加進原本的式子,因為加進去的條件值為0時為最佳解,所以不止不會影響原本求出的最佳解,也可以同時去掉b這個項次。

再來對w微分後加進原本的式子,可以將w轉換成α.y.z相乘,在去掉w項次後,也不需要對原本的w取最小值,並轉成一個只有對α作最佳化的問題,得到簡化版的對偶問題。

前面有提到如果要滿足對偶問題,即b,w,α的解對於左右兩邊的最佳化問題都是最佳值,需要滿足特定條件,這稱為KKT最佳化條件,再會來透過這些條件來解最佳的b,w。

再來先乘上-1先將最大值問題轉成最小值問題,再把平方向解開後,就可以將每個條件丟進QP來解出最佳值。

如果今天在滿足KKT條件下要找到b,w的最佳值,首先要找到最佳的w,因為只有一條條件和w相關所以很容易找到最佳值。在找最佳的b時,共有2個條件和b相關,在第2個條件可以發現如果在α大於0的情況下,y(W.z + b)即需要等於1才能滿足條件,而這個值就是原本要找的最佳解,因此在解對偶問題時,可以同時找到SVM的支稱向量。

既然證明在α大於0的情況下,可以找到支撐向量,相反的回到原本的SVM概念,其意義在於如果可以知道有哪些支稱向量,就可以找到最大的margin,其他的點皆可以不管。

SVM和PLA其實很相似都會找出區分不同類別資料的超平面,差別在於兩者著重的點是不同的,SVM是著重在使用支稱向量表現出來,而PLA是使用分類錯誤(犯錯)的點表現出來。

上一講介紹的原始SVM為Hard-Margin SVM,透過求特定放縮後的b,w最佳值,他和要轉換的空間維度有關,適合維度較小的問題;而這一講的SVM是引入Lagrange的SVM,透過找最佳的支稱向量和α來重構邊界,並且和資料量的大小有關,適合資料量小的問題。

但到目前為止仍然沒辦法完全擺脫處理高度空間維度轉換的問題,因為在QP求Q矩陣時,仍會碰到z向量內積的問題,z向量的維度等同於目前要解的維度。

這一講提到透過對偶問題來移除對d維度的依賴,並引入Lagrange與KKT條件,透過QP來解出最佳值,並發現解出的最佳值就是支稱向量,並可以用來建立最大margin。下一講將會說明如何真正擺脫和d維度相關的計算。

參考資料:
Machine Learning Techniques 2

機器學習技法是林軒田老師的開的機器學習後半堂課,主要在延續前面機器學習的基礎理論,並延申出不同的機器學習模型介紹。

而這堂課主要圍繞在特徵轉換上並分三個面向探討,分別為(1)如果處理大量且高複雜度的特徵轉換(2)找出具有預測性質的特徵來提升模型表現(3)找出資料中的隱藏特徵讓機器學習表現更好。

在線性可分的問題中,前面的課程有教過可以透過PLA或是POCKET來找到分開的超平面,但PLA在求解的過程中會得到很多種解,究竟怎麼樣的切線才會是比較好的切線呢?如果以雜訊的容忍度來看,當資料產生時可能會存在或多或少的雜訊(例如從實體感測器訊號收資料時,可能會有震盪或是偏移的現象),而雜訊是模型過擬合的因素之一。因此如果要讓模型對雜訊的容忍度最大,那麼就要讓超平面能夠離點越遠越好。例如最右邊的超平面能離點最遠,雜訊的容忍度(灰色區域)也最大。

換個方式來,這會是一個最佳化問題,而最好的線須要滿足兩大條件: (1)能作出最大邊界(Margin) (2)可以把不同的類別分對。其中灰色區域的算法,是把每個點和超平面計算距離後取最小距離。

距離的計算方法,可以想像在平面上有兩個點構成的向量,而w乘上平面上的向量為0,所以w為垂直於平面上的法向量。當有一個x要計算x和平面的距離,即為x和平面上任一個點構成的向量,並對垂直於平面的向量作投影,即為對w方向的投影。

因為要找的是一個可以區分出正確類別的分割超平面,所以可以拆掉距離的絕對值,變成乘上y大於0(即為分割正確時值皆會大於0,如果類別分錯乘上y會小於0)

為了簡化式子,假設將式子放縮到最小值會等於1,那margin就會簡化為成1/|w|,並從求margin的最小值,變成分數乘上y的最小值要等於1。而且因為其條件已經滿足大於0,所以可以再拿掉一條分數乘上y要大於0的限制式。

為了再簡化限制式條件,限制式條件可以再從最小值為1,放寬為分數乘上y大於等於1,而且這個放寬並不會違反原本的限制式。老師這裡舉了一個例子,假設找出來的解為大於等於1.126並且不滿足等於1的條件,這時候如果把b和w除上1.126作放縮讓他滿足原本的條件,會因為w變小而使得目標式變大。這個證明在說如果找出來的解不滿於原本等於1的條件下,就不會是最佳解,因此可以對限制式條件作放寬。最後再將最大化的問題轉為求最小值(倒數),得到最後的簡化的最佳化問題。

如果要找到最佳的平面,只要找到最靠近平面的點就行了,而這些點被稱為支撐向量(support vector),就像是這個超平面是由最靠近的點所支撐起來般,這也是SVM的概念。

因為svm要找到的最佳值是w的兩次函數,且限制式為b和w的一次式,有這樣的限制非常適合使用二次規畫作最佳化。

但為什麼svm可以作的好呢,老師這邊以兩個面向來說明使用svm會讓Ein和Eout越接近,且不容易overfit泛化性更佳。

之前在提到regularization的時候講到,為了讓Ein越小但又不希望造成overfit,於是加上w的限制條件限制其範圍。而svm剛好對調,svm是要讓w的長度越小且限制讓所有的類別的資料分對,所以svm和regularization是一體兩面,svm找出來的灰色區域讓為了容忍雜訊對模型的干擾。

假設平面上有三個點,如果是原本的pla,在任意切線上可以找到所有的類別組合(共八種)。但使用svm考量到需要維持最大特定margin區域情況,所以沒有辦法作出所有的類別排列組合。在vc維的介紹有講到,如果能作出的dichotomies越少,vc維就越小,Ein和Eout就會越接近,即泛化能力越好。

從上面兩個面向來看,svm可以帶來本質上泛化性更佳的好處,並且在加上特徵轉換的方法後,non-linear的svm可以同時辦到將Ein(即分對不同類別)與Eout(泛化能力)作好。

這一講主要在說明如何從邊界分類問題延申出最大margin提供更強健的方法來容忍雜訊,並作svm最大margin的最佳化式子推導,最後提到最大margin帶來本值上的好處在於可以提高更佳的泛化能力。

參考資料:
Machine Learning Techniques 1