若舉辦人類vs.機器人(或AI)的「博學比賽」, 人類輸給機器人肯定是正常的. 因為論腦容量或是知識蘊藏量(記憶體), 以上皆非人腦的強項. 機器人取得知識的途徑, 除了跟人類一樣可以閱讀一般印刷媒體之外還包含對開放性資料庫的存取、支援REST protocol的API以及網路爬蟲程式. 尤其是後面三項, 在我們呼呼大睡之時, 機器人可以是不眠不休、大量且批次的默默工作. 亦就是說, Google、Amazon、Wiki、DataGov等強大的資料庫, 甚至是Facebook、Flickr、YouTube等媒體既然都可以是人腦的extension, 當然也都是機器人(或AI)的腦(所謂的「全球心智」), 只是看誰提取的快又準確罷了.
所幸, 人類目前還保有分析/發掘資料中更深層情報、掌握趨勢、洞見未來以及知識傳承與運用等優勢. 小孩從呀呀學語一路撞進入幼幼班, 便透過不斷地身體力行(調皮搗蛋)與嘗試錯誤(挨罵), 事實上即經過不斷地學習, 將之前所經歷(例如觸覺、聽覺與視覺訊息等刺激)逐漸與人類老祖宗智慧與抽象思維的結晶「文字符號」做鏈結, 並試著從這些抽象的文字符號中發現與累積「知識」.
知識的取得 – 透過OCR辨識
上一回「機器人駭客」的文章中, 我們大致示範了如何以Python搭配Tesseract來讀取以圖片格式儲存的文字資料. 以格林童話故事-青蛙王子為例(文章資料內容以JPEG格式儲存), 我們以Tesseract對下面這則以圖片記錄(story.jpg)的故事內容進行自動掃瞄與辨識.
只須在命令列中下指令, 辨識完成的內容會轉成文字檔儲存在result.txt, 準確率大概有90%. 雖然仍有不少錯誤, 但還是可以進行大量知識的閱讀與擷取. 將以圖形格式儲存的文章(例如JPEG、PNG或PDF等)轉換成純文字檔案格式的工作尤其適合交由機器人來做, 它們可以批次處理且執行的又快又好.
> Tesseract story.jpg result –l chi_tra |
知識的取得 – 透過開放資料庫
操作上比較不方便的是提供以CSV或XML等檔案下載方式的服務, 因為這種方式將需要大量「人為操作」的介入(資料蒐尋、篩選與過濾等)才能順利取得正確(感興趣的)的相關資料. 以「政府資料開放平臺」為例, 比方說原本想查詢「薪資行情」, 例如我們在瀏覽器的URL命令列中輸入https://data.gov.tw/datasets/search?qs=新資, 結果找到的前十來頁結果都不是想要的, 反而得到「年度綜稅之各類所得金額各級距申報統計表」之類的其他資訊!
沒關係, 為了示範如何透過強大的Pandas套件擷取知識, 我們還是將錯就錯一番, 分析一下「(103年度)綜稅各類所得金額各級距申報統計表」. 如下面程式碼所示, 透過read_csv即可將CSV檔讀入並自動建立好二維的表單物件(DataFrame). 因該表單的第一列只是檔頭描述, 我們可以透過skiprows=[list]方式在讀檔時把它濾除, 反之我們可以用nrows=1指定只讀取檔案內容的第一行.
# (103年度)綜稅各類所得金額各級距申報統計表 # read from file import pandas as pd csv='103_16-2_20170121015456.csv' # in local disk head=pd.read_csv(csv,nrows=1).columns[0] # header df=pd.read_csv(csv,skiprows=[0]) # skip the header # 各級距前十欄(行) df.ix[:,0:10] |
Pandas二維表單物件(DataFrame)的操作相當簡單且方便, 幾乎不用寫任何程式碼即可提取任意指定的列向量(row)、行向量(column)或是某個陣列的範圍.
然而, 若每次爬資料都要先經過存檔再讀檔, 這樣我們不被機器人(或AI)程式煩死才怪. 下面範例則是透過使用request與io套件的一些技巧, 讓機器人(或AI)程式可以直接透過URL讀檔.
# read from URL import pandas as pd import requests, io url='https://ws.fia.gov.tw/001/upload/ias/ias103/103_16-2_20170121015456.csv' html=requests.get(url).text head=pd.read_csv(io.StringIO(html),nrows=1).columns[0] # header df=pd.read_csv(io.StringIO(html),skiprows=[0]) # skip the header |
根據表單前三欄(df.ix[:,0:3])定義, 分別為各級距的「項目名稱」(在表單第零行, df.ix[:,0]), 各級距的「納稅單位」(在表單第一行, df.ix[:,1]), 與各級距的「所得合計」(在表單第二行, df.ix[:,0]). 下面範例即透過指定陣列索引的方式, 從原始表單推算出各級距: 「人數(納稅單位)百分比」、「年所得(萬)」與「納稅貢獻百分比」, 其中我們忽略各級距的「適用稅率」與最後真實能課到的「有效稅率」(因為表上我們看不見), 並扣除淨申報所得為零(無貢獻, NET=0).
首先, 我們把第一行各級距的「納稅單位」資料除以「所有納稅單位總合(df.ix[8,1])」以得到各級距「人數(納稅單位)百分比」. 其次, 我們把第二行各級距的「所得合計」除以各級距「納稅單位」並將單位換算成萬元以得到各級距「年所得(萬)」. 最後, 我們把第二行各級距的「所得合計」除以扣除淨所得為零(NET=0)的「實質貢獻所得總合(df.ix[8,2]-df.ix[0,2])」以得到各級距相對的「納稅貢獻百分比」.
# 從原始表單推算出 n:各級距人數百分比 p:各級距年所得(萬) c:各級距納稅百分比(扣除NET=0) for row in range(0,8): t=df.ix[row,0] # 各級距項目名稱 n=df.ix[row,1]*100/df.ix[8,1] # 各級距人數(納稅單位)百分比 p=df.ix[row,2]*0.1/df.ix[row,1] # 各級距年所得(萬) if row==0: # 各級距納稅百分比(扣除NET=0) c=0 else: c=df.ix[row,2]*100/(df.ix[8,2]-df.ix[0,2]) print("%10s % |
結果如下報表, 所呈現的數據蠻驚人也相當令人沮喪的! (有看出甚麼端倪嗎? 接下來, 我們將其視覺化)
我們曾在前幾回介(例如「機器人看圖說故事」與「機器人駭客」等)紹過Matplotlib套件強大的影像處理功能, 事實上它較為人廣泛使用的是類似Gnuplot的數據視覺化功能, 例如下面程式碼可以很簡單把前面的文字報表以棒狀圖來呈現. (因為Pyplot預設的字型較小, 我們可以透過fontsize參數來調整, 中文顯示的部份請Google參考網路上的說明)
import matplotlib.pyplot as plt plt.title('各申報所得級距佔總申報百分比') plt.xlabel(df.columns[0],fontsize=14) plt.ylabel(df.columns[1]+'佔全體%',fontsize=14) n=df.index.size-1 xtics=list(range(0,n)) # x index tick xticl=list(df.ix[0:7,0]) # x index label plt.xticks(xtics,xticl,rotation=45,fontsize=14) plt.bar(xtics,df.ix[0:7,1]*100/s,label="各級距%") plt.show() |
結果蠻令人訝異的, 淨所得為零(NET=0)的比例竟然佔所有申報單位的33.2%, 而申報所得級距在0-52萬的人數(納稅單位)百分比竟高達45.2%! 申報所得117萬以內的人數百分比佔了總體的91.8%. 再看看納稅貢獻度(相對於總體有效納稅額, 扣除NET=0), 申報所得級距在0-52萬的佔了整體納稅額的38.5%, 申報所得級距在440萬-500萬的納稅貢獻度最低, 只有1.1%(還比金字塔頂端的少, 呈反轉點).
就是說, 機器人(或AI)將回應我們: 整體而言台灣是個均貧的國家(91.8%申報單位其所得在117萬以內)? 所得級距在440萬-500萬的申報單位貢獻最少? 級距在1000萬以上佔整體0.2%申報單位的平均所得約4015萬是佔整體78.4%申報單位(級距在0-117萬以內不含淨所得為零)的平均所得54.6萬的74倍. (且賺的越少相對貢獻的越多? 若此表可以附上真實的「有效稅率」會看得更準些, 以免誤導讀者)
前面是以縱向(行)來看資料, 現在我們將資料以橫向(列)來看. 例如, 我們分析一下台灣整體申報單位的各項所得(各種收入方式)的分配百分比又是如何?
# 總和各項所得百分比 # df.ix[8,3:]*100/s plt.title('總合各項所得百分比') s=df.ix[8,2] n=len(df.columns[3:15]) plt.xlabel("所得種類",fontsize=14) plt.ylabel('佔總所得體%',fontsize=14) xtics=list(range(0,n)) xticl=list(df.columns[3:15]) plt.xticks(xtics,xticl,rotation=90,fontsize=14) plt.bar(xtics,df.ix[8,3:15]*100/s,label="各項所得%") plt.show() |
結果政府的稅收來源以「薪資所得」佔72.5%為最大宗, 其次為「股利所得」與「利息所得」分別佔15.1%與4.4%, 而「盈利所得」竟然只佔2%(公司節稅有方)? 那些佔總體33.2%的申報單位其淨所得為零自然是課不到稅了, 然而級距在0~117萬佔總體58.6%的申報單位則是躲不掉(其綜合所得總額貢獻百分比佔60.5%)?
接下來, 我們細部分析各個級距的所得分配百分比. 例如, 申報所得小於117萬(含NET=0)的三個級距主要是靠「薪資所得」, 約佔8成, 其次為「股利所得」約佔7%, 而且這三個族群(佔整體92%申報單位)的資產分配極為相似. 糟糕, 政府總體稅收來源的組成分配竟然跟這族群相似? (難怪喊窮)
plt.title('申報所得小於117萬級距各項所得分配百分比') plt.xlabel("所得種類",fontsize=14) plt.ylabel('所得分配%',fontsize=14) plt.xticks(xtics,xticl,rotation=90,fontsize=14) plt.plot(xtics,df.ix[0,3:15]*100/df.ix[0,2],label="NET=0 ") plt.plot(xtics,df.ix[1,3:15]*100/df.ix[1,2],label=" 0- 52") plt.plot(xtics,df.ix[2,3:15]*100/df.ix[2,2],label="52-117") plt.legend(fontsize=12) plt.show() |
相較前端三個申報所得小於117萬(含NET=0)的級距(佔92%申報單位), 富人(後端三個級距佔0.8%申報單位)的主要所得來源配置是倒轉的. 例如, 申報所得大於1000萬的級距主要是靠「股利所得」約佔73.4%, 其次為「薪資所得」約佔16.4%, 這富有的三個族群(只佔整體0.8%申報單位)的資產分配極為相似, 而且越富有的「股利所得」分配越高. (所以, 看出甚麼端倪了嗎? 不要再隨便聽信理專、基金、貨幣或保險業務的話了! 政府更應該學著向富人看齊, 調整所得分配才是)
plt.title('申報所得大於440萬級距各項所得分配百分比') plt.xlabel("所得種類",fontsize=14) plt.ylabel('所得分配%',fontsize=14) plt.xticks(xtics,xticl,rotation=90,fontsize=14) plt.plot(xtics,df.ix[5,3:15]*100/df.ix[5,2],label="440- 500") plt.plot(xtics,df.ix[6,3:15]*100/df.ix[6,2],label="500-1000") plt.plot(xtics,df.ix[7,3:15]*100/df.ix[7,2],label="1000以上") plt.legend(fontsize=12) plt.show() |
知識的取得 – 以REST API回傳JSON方式
import requests url='http://opendata.epa.gov.tw/webapi/api/rest/datastore/355000000I-000259?sort=SiteName&format=json' html=requests.get(url) html.encoding='utf-8' data=html.json() # grab data in JSON format data['result'].keys() # data was collected in 'records' records=data['result']['records'] size=len(records) print("%-10s %5s %5s %5s" % ('SiteName','AQI','PM10','PM2.5')) for i in range(0,size): site=records[i]['County']+records[i]['SiteName'] aqi=records[i]['AQI'] pm10=records[i]['PM10'] pm2p5=records[i]['PM2.5'] print("%-5s %5s %5s %5s" % (site,aqi,pm10,pm2p5)) |
我們剔除無記錄的監測站資料之後並將得到的68個測站資料做成圖表, 可以發現當時空氣品質與懸浮微粒較危險的三個地區為雲林縣麥寮、高雄市前鎮與南投縣竹山.
import matplotlib.pyplot as plt sList=[] aList=[] p1List=[] p2List=[] for i in range(0,size): site=records[i]['County']+records[i]['SiteName'] aqi=records[i]['AQI'] pm10=records[i]['PM10'] pm2p5=records[i]['PM2.5'] if pm2p5=='ND' or pm2p5=='' or pm10=='ND' or pm10=='': continue sList.append(site) aList.append(aqi) p1List.append(pm10) p2List.append(pm2p5) plt.title('全台各區實時空氣品質') plt.xlabel('全台各區',fontsize=14) plt.ylabel('空氣品質(AQI)',fontsize=14) xtics=list(range(0,len(sList))) plt.plot(xtics,aList,label="AQI") plt.plot(xtics,p1List,label="PM10") plt.plot(xtics,p2List,label="PM2.5") plt.legend() plt.show() |
請參考之前的案例「讓Alexa到ThingSpeak讀取溫度-發出HTTPS GET請求」, 因為都是透過REST protocol對Web Server發出GET請求以取得JSON回應, 所以這段程式碼若架在Amazon/AWS上, 就可輕易地以讓Alexa回報台灣各地區實時的空氣品質資訊了!
同理, 若想讓Alexa變得更博學, 可以回應我們更多科學、自然、地理、人文等生活常識, 也可以透過GET向Wiki提取資料. 筆者一連測了好幾個專有名詞或專業術語, 天啊還真難不倒它! (連鄭兆村目前就讀輔仁大學, 標槍擲出91.36公尺 破世大運紀錄都知道!)
import requests def wikiQuery(lang,keyWord): op='action=query&prop=extracts&format=json&exintro&explaintext&' search='generator=search&gsrlimit=1&gsrsearch='+keyWord wikiUrl='https://'+lang+'.wikipedia.org/w/api.php?'+op+search html=requests.get(wikiUrl) html.encoding='utf-8' data=html.json() # grab data in JSON format e='' for k in data['query']['pages'].keys(): if k=='-1': break e=data['query']['pages'][k]['extract'] return e # test query string In: ans=wikiQuery('zh','愛因斯坦'); print(ans) Out: 阿尔伯特·爱因斯坦,或譯亞伯特·爱因斯坦(德語:Albert Einstein, In: ans=wikiQuery('zh','人工智能'); print(ans) Out: 人工智能(英语:Artificial Intelligence, AI)亦稱機器智能,是指由人工製造出來的系統所表現出來的智能。通常人工智能是指通過普通電腦實現的智能。該詞同時也指研究這樣的智能系統是否能夠實現,以及如何實現的科學領域。... (略) In: ans=wikiQuery('en','wavelet transform'); print(ans) Out: In mathematics, a wavelet series is a representation of a square-integrable (real- or complex-valued) function by a certain orthonormal series generated by a wavelet. This article provides a formal, mathematical definition of an orthonormal wavelet and of the integral wavelet transform. ... (略) In: ans=wikiQuery('en','Arduino'); print(ans) Out: Arduino is an open source computer hardware and software company, project, and user community that designs and manufactures single-board microcontrollers and microcontroller kits for building digital devices and interactive objects that can sense and control objects in the physical world. ... (略) In: ans=wikiQuery('en','e=mc^2'); print(ans) Out: In physics, mass–energy equivalence states that anything having mass has an equivalent amount of energy and vice versa, with these fundamental quantities directly relating to one another by Albert Einstein's famous formula: ... (略) In: ans=wikiQuery('en','Tensor Flow'); print(ans) Out: TensorFlow is an open-source software library for machine learning across a range of tasks. It is a system for building and training neural networks to detect and decipher patterns and correlations, analogous to (but not the same as) human learning and reasoning. It is used for both research and production at Google, often replacing its closed-source predecessor, DistBelief. ... (略) In: ans=wikiQuery('zh','鄭兆村'); print(ans) Out: 鄭兆村( |
知識的取得 – 透過網路爬蟲
AI雖然比人類博學, 對「全球心智」的提取也比人類有效率, 但若只是透過上述的開放資料庫或Wiki API則無法回答簡單的數學運算(例如: 1+1=), 因此我們需要將AI連結到提供計算服務的伺服器.
若讀者非常勤奮的話可以自己寫一個只處理GET請求的Web Server然後把它架在Amazon/AWS上以提供簡易計算機的REST API, 或者可以跟筆者一樣偷懶直接用爬蟲爬現成(別人寫好)的計算機.
雖然目前Wiki或Google都沒有提供官方支援REST API的計算機, 但我們若直接在Google Chrome的搜尋列中直接輸入「123*456」這樣的字串, 可以發現Google搜尋引擎已經很聰明地把計算機結果放在第一位. 若使用Chrome瀏覽器, 在該搜尋結果的頁面下按F12則可以顯示該頁面的原始HTML資料標籤格式. (密密麻麻, 雖然Google以經幫我們圖形化與視覺化了, 但這仍然不是正常人可以閱讀的)
下面簡短的程式碼透過BeautifulSoup套件, 如此我們可以在透過request.get需求之後, 直接將讀取的頁面建立以HTML標籤為結構的巢狀式物件(sp). 這使我們在解析/編輯網頁的原始檔變得非常方便, 也漸漸能理解為何Python之所以命名為Python了! (真是爬蟲界之王啊!)
from bs4 import BeautifulSoup url='https://www.google.com.tw/search?q=' math='123*456' # replace op opMap={'+':'%2B','-':'%2D','*':'% for op in opMap.keys(): if math.find(op)>=0: math=math.replace(op,opMap[op]) break html=requests.get(url+math) sp=BeautifulSoup(html.text,'html.parser') |
我們可以直接取用BeautifulSoup對網頁建立的物件(sp), 包括隱藏的標籤屬性與顯示於瀏覽器的文字.
sp.title sp.head sp.body.h1 sp.body.h1.get_text() sp.body.h1.find_all(‘h |
接下來, 我們要找到該網頁提供計算功能的「答案」目標區原始標籤. 透過Chrom瀏覽器, 在網頁的「答案」目標區按滑鼠右鍵會彈出選單, 選擇「檢查」之後Chrom會展開目標區的原始檔案位置. 我們感興趣的計算結果被包在<h1>標籤裏頭, 其中id=“answer”, 而我們要的答案則藏在<h1>下的<a>標籤之中, .其中class=“ans”.
因此, 爬蟲程式透過搜尋目標<a>標籤且指定class名稱為”ans”的方式就可以很快的抓到該值.
tag=sp.find('a',class_="ans") tag.text |
我們將整個過程打包成一個函式calculatorAPI, 之後我們的機器人就可以使用該網站所提供的所有功能並回應一般的數值計算問題了(也可以很複雜), 只是反應慢了點! (本例只示範簡單的加減乘除運算, 其中運算符先透過opMap被置換為URL的編碼在對網站提出請求).
def calculatorAPI(math): url='https://www.calcatraz.com/calculator/?c=' opMap={'+':'%2B','-':'%2D','*':'% for op in opMap: math=math.replace(op,opMap[op]) html=requests.get(url+math) sp=BeautifulSoup(html.text,'html.parser') ans=sp.find('a',class_="ans").text return ans # test calculatorAPI('123456*345') Out: '42592000' calculatorAPI('123456/345) Out: '357.84' calculatorAPI(‘(12345+67)*(890-123)/31') Out: '307100' |
寫到這裡, 機器人(或AI)已經比許多正常人類還要博學. 可謂上知天文, 下知地理, 可以回應許多極專業的問題、新聞媒體報導甚至是八卦消息), 計算能力也勝過許多正常人類了! (普遍性的視覺能力還要再加強)
這不是AI啊!?
嗯. 若把AI比喻為登陸火星, 我們現在大概是在網購的四軸飛行器上加裝一個雲台與攝影鏡頭, 把視野拓展到兩公里 外吧!?
在以往機器學習或類神經網路領域, 我們在執行回歸預測或分群等算法時無論採監督或非監督式的訓練過程, 總會面臨所謂的「核函數(Kernel Function)」與關鍵「特徵選擇(Feature Selection)」的問題, 因為它可能還是得透過人類進行決策. 這類的算法(或程式)無法自行發掘資料深層的情報或產生洞見, 頂多只能算是執行代理人工作(可以很專精).
所幸, DeepLearning似乎有機會打破這樣的僵局. 我們用另外一個角度來理解類神經網路分群的訓練過程: 一階的神經網路就好比在解線性聯立方成組, 二階的神經網路就好比在做線性規畫有解凸邊形(Convex)的能力, 而三階的神經網路自然就具有解多個凸邊形聯集的問題(任意形狀). 透過高階神經網路鍵結(以模糊化、遺忘與雜訊等固化神經元)與傳遞, 它有機會自行得出相當於「非線性核函數(Kernel Function)」的求解效果.
一但連資料「關件的特徵」都能自行萃取並且做的比人類優秀(例如機器人視覺), 此時若能賦予機器人(或AI)如人類般的五感, 使其能自行持續監測外在刺激並主動試誤與學習(各種特徵抽象化與聯結), 不就跟培養真實的小孩相當嗎? 但這也是Stephen Hawking、Elon Musk與Bill Gates所擔心的, 因為機器人(或AI)或許有機會進一步將發掘到的知識抽象化以進行機器人(或AI)間彼此的溝通、推衍與傳承(或擴展成特有族群意識與文化)進而發明出更勝AI的智慧, 稱之為「科技奇點(Singularity)」.