2016年12月31日 星期六

我對 Team work 的定義

星期四那天,談了我對 Team work 的定義。我的做法是大家對等,不分你我高低;把所有選項曝露出來,然後找個大家都接受的方法,遇到問題一起面對。每個崗位都有各自的難處,只有協商才能找到出路。

而我的 Team work 是對應所有合作伙伴;不只是部門內的手足;不只是公司內的同事;連客戶 Vendor 都包含在內。我不是說我的方法很掂,但我很希望大家不是對立的局面。愛拼才會贏,但我追求的是雙贏。可能這個想法有點天真,但這個世界就是由天真及有能力的人帶領。

2016年12月30日 星期五

把 Mac OS X 當成伺服器


以前在家中建立好的 ESXi 伺服器,由於風扇太嘈,加上沒有多大效用,於是把它關上。後來加入了 QNAP TS-410 用來儲存檔案,方便家中的裝置能共享;用了一段時間,風扇也太吵,於是把它關上。到現在,想有一台電腦能抓取數據、監視家中情況、把網頁放回自己的電腦、重開得到 Apple 認可的 VPN 連線、Facebook Messenger 管家機械人...等等。於是拿了退役的 Mac mini,灌入 CentOS 作多用途伺服器。

CentOS 是裝好了,但缺了網絡卡的驅動程式,在苦無對策下只好放棄,改為用 Mac OS X Server。可是問題又來了,我的復原光碟不翼而飛,硬碟沒有復原的分割,根本無法復原。打算求助其他復原光碟,在公司又找不到,更得知原來復原光碟是跟機,未必能在我的 Mac mini 上執行;就像我拿 MacBook Air 的復原手指一樣,到安裝的一刻說「無法安裝」。幸好,在 iTunes 的「已購買項目」中能重新下載舊的 Mac OS X 版本。我選擇了 Mavericks。至於 Mac OS X Server 則沒有了。於是,我嘗試把 Mac OS X 當成伺服器來建立...。

首先是安裝網頁伺服器程式。原本 Mac OS X 已經有 Apache 裝好了,只要簡單啟動指令便可:
sudo apachectl start
需要留意的是,預設的網頁路徑是「/Library/WebServer/Documents」;我把它改為用戶的目錄下。

2016年12月29日 星期四

修復 Django 的「Error loading MySQLdb module」錯誤


另一個在 Django 上架時出現的錯誤是「Error loading MySQLdb module」。明明已經安裝好了 MySQL-Python 這個模組。原來是 WSGI 使用了 Python 2.7,而程式卻是在 Python 3.5 下執行。雖然我已把 Python 的預設版本設定為 Python 3.5,但在 Apache + WSGI 下卻是另一個設定。最主要要留意 httpd.conf 內關於 LoadModule 時 WSGI 的版本,像是預設值 mod_wsgi.so;而在我的情況卻是用 /etc/httpd/modules/mod_wsgi-py35.cpython-35m-x86_64-linux-gnu.so。

2016年12月28日 星期三

解決 Django 的「Populate isn't reentrant」問題


過去一星期在處理 Django 項目上線的工作,一直掉進錯誤的無限迴圈。處理了一個問題,又到另一個;處理了另一個,又到第二個;甚至乎回到起點。這個「Populate isn't reentrant」是其中一份子。花了半天時間,發現到問題發生的原因,是 Apache 在第一次執行 Django 項目時會載入一些程序,當要再次載入時,由於原本已經有程序在記憶體,便會出現這個錯誤。解決方法是重新啟動 Apache。

2016年12月27日 星期二

解決 Django 的「No module named settings」問題


Django 在開發上有它的優勢,但最近把開發好的項目上線時卻問題接腫而來。

有人說上面的「No module named settings」問題可以透過「sudo pip3.5 install mysql-python」來解決。但這樣做會引伸另一個「ConfigParser」問題。


主要原因是 Python 3 開始把「ConfigParser」改名為「configparser」,如果所使用的組件沒有好好支援 Python 3 的話,便會出現這個問題。我的解決方法是用「mysql-connector」取代「mysql-python」,指令是「sudp pip3.5 install mysql-connector」。

2016年12月22日 星期四

如何在 macOS 連接 ESXi 的虛擬機?


我在家中設定了一台伺服器,它運行的是 ESXi。裡面分別有 Windows XP, Windows 7 及 CentOS。要設定 ESXi 內的虛擬機,需要一個客戶端的軟件。之前遇到一個問題是,這個軟件只支援 Windows。現在找到了支持 macOS 的方法。比起 Windows 版本,雖然很不方面,但還是可以。

「VMware Remote Console」軟件支援 macOS。先下載並安裝好。在 ESXi 伺服器的頁面,有一個「Open the VMware Host Client」,點擊後會開出登入頁面。輸入帳號資料成功登入後,會在畫面左方看到 ESXi 內的虛擬機。在這個畫面可以修改虛擬機的設定。如要看虛擬機的畫面,只要點選虛擬機,在畫面右方點「Console」內的「Launch remote console」就能控掣虛擬機。

2016年12月21日 星期三

Merry X'mas


是日冬至,很多公司都會放早,而我的公司則在下午舉行聖誕聯歡會,還有抽獎環節。一共有十九份禮物。早前,人事部向同事收集禮物的意見,沒想到當中有四份是我的主意。包括:Apple Watch, iPod nano, HTC RE 相機, 宜家傢俬禮券。可惜,我一份也沒抽中。

回想過去五年的創業歷程,沒資源搞聯歡會,沒有抽獎活動,同事交換禮物還是有,但總覺得欠了甚麼。我喜歡在聖誕節對同事作出感謝。現在打工,錢比以前更不見使,沒甚麼錢可以買禮物給同事,只能物輕情義重。於是我買了一打日本版樽裝可口可樂,貪它的花球設計當成禮物,希望大家感受到我的小小心意。

2016年12月20日 星期二

自訂 Base64 碼表 PHP 程式

有些時候,為了避開中文字或符號而導致整爛資料的情況,我會用編碼方法去做。其中最常用的是自訂 Base64 碼表。同時,這個做法會感覺安全一點。我每個程式用的碼表都不同,為了省下構思碼表的時間,於是寫了以下程式:
<?php
//----------------------------------------------------------------------------------------
//  Generate Custom Base64 Mapping
//----------------------------------------------------------------------------------------
//  Platform: CentOS7 + PHP + Apache
//  Written by Pacess HO
//  Copyright 2016 Pacess Studio.  All rights reserved.
//----------------------------------------------------------------------------------------

header("Access-Control-Allow-Origin: https://home.pacess.com");
header("Access-Control-Allow-Methods: POST");

header("Content-type: text/html");
header("Cache-Control: no-cache, must-revalidate");
header("Expires: Tue, 10 Mar 1987 00:00:00 GMT");

date_default_timezone_set("Asia/Hong_Kong");
mb_internal_encoding("UTF-8");
ini_set("memory_limit", "-1");
set_time_limit(0);

//----------------------------------------------------------------------------------------
$default = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
$mapping = str_shuffle($default);

//----------------------------------------------------------------------------------------
//  Mapping output
echo($mapping);

?>

2016年12月16日 星期五

AGM 周年會議


今日是公司的周年會議,簡稱 AGM。我是第一年參加。由於公司有點規模,有不同的部門,但各自的工作範圍也不太了解。很多同事可能認得出對方的面孔,可是名字卻不太清楚。於是透過 AGM 去加深大家的認識。負責同事選了公司附近的無敵海景會議室,感覺開揚。

老闆問我以前的工作中有否 AGM。我第一個印象是公司的管理階級大會,於是回答了「有」。但細心一想,Gameone 當時沒有這樣的思想,沒有類似的大會;Lakoo 則會租用日營,管理人員出席腦振盪會議;U1 則曾到中山及台灣,同樣是管理人員出席的會議。沒有一家是全體員工出席。主要原因是三家遊戲公司的規模細,部門少,同事已經互相認識。我也在想,他日創業成功時,是否也應該有 AGM?而我目前的答案是「否」。我會選擇公司 Team Building 旅行。不過,相信再創業也不會有太多員工,空想一下而已。

2016年12月11日 星期日

互動 Facebook Live 留言


再次製作新的互動 Facebook Live 示範程式,已經是第四個。今次是播放 .mp4 影片,並在影片上添加飄雪效果。Facebook 反應一如以往地實時顯示在影片內;而在影片間會顯示即時的留言訊息,可以給用戶傳達愛意。

2016年12月10日 星期六

利用 Bash 程式下載 Packt 免費電子書及以 LINE 作為通知


八月時寫的「利用 Bash 程式下載 Packt 免費電子書」一直在公司都運作暢順;本來每天在下載後會自動上傳到 GitLab 及發送電郵通知給我;可是早幾天開始沒有收到電郵。除了因為公司的網絡不穩導致下載失敗外,原來 Google 把發送通知的電郵封鎖了。於是我改用 LINE 通知,把程式稍為修改一下:
#!/bin/bash

##----------------------------------------------------------------------------------------
##  Packt Free eBook Downloader
##----------------------------------------------------------------------------------------
##  Platform: CentOS7 + bash
##  Written by Pacess
##  Copyright 2016 Pacess Studio.  All rights reserved.
##----------------------------------------------------------------------------------------

##----------------------------------------------------------------------------------------
##  Update History:
##----------------------------------------------------------------------------------------
##  2016.08.25  Version 1.00
##  - Added auto commit to GitLab
##
##  2016.12.10  Version 1.10
##  - Removed download
##  - Removed auto commit to GitLab
##  - Removed email alert due to blocked by Google
##  - Added LINE alert
##----------------------------------------------------------------------------------------

##  Variables
userID="your@email.com"
password="password"

timeOut=5
noOfRetry=3
sleepTimeBetweenRequest=1

userAgent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36" 
downloadDirectory="/var/www/"
log="packt_free_ebook.log"
cookie="cookie.txt"

##  LINE-Bot related
lineUser="Uee8ff21b9d850419330cb5446fe66c9a"

##----------------------------------------------------------------------------------------
function PHLog  {
   echo "$1"
   echo "$(date '+%Y-%m-%d %H:%M:%S') $1" >> "$log"
}

##----------------------------------------------------------------------------------------
##  Program start
echo "$(date '+%Y-%m-%d %H:%M:%S')"
echo "-----------------------------------------------------------"
echo "--  Packt Free eBook Downloader Version 1.10             --"
echo "--  Written by Pacess                                    --"
echo "--  Copyright 2016 Pacess Studio.  All rights reserved.  --"
echo "-----------------------------------------------------------"
echo ""

##  Move to app directory first
cd /var/www/

##  Remove previous temp file
rm -f $cookie packt*.html

##----------------------------------------------------------------------------------------
##  Website login
PHLog "Logging in $userID..."
curl -s --retry $noOfRetry -m $timeOut -A "$userAgent" -b "$cookie" -c "$cookie" -d "email=$userID" -d "password=$password" -d "op=Login" -d "form_build_id=form-73ba86bbfb2a50719049129632c84810" -d "form_token=2f1d586bf7df196b77d0761709d03199" -d "form_id=packt_user_login_form" https://www.packtpub.com
errorCode=$?;  test "$errorCode" -ne "0" && { PHLog "curl exit error code: $errorCode";  exit; }

curl -s --retry $noOfRetry -m $timeOut -A "$userAgent" -b "$cookie" -c "$cookie" https://www.packtpub.com/packt/offers/free-learning > packt_daily.html
errorCode=$?;  test "$errorCode" -ne "0" && { PHLog "curl exit error code: $errorCode";  exit; }

##----------------------------------------------------------------------------------------
##  Extract claim URL
claim=$(grep -oE "freelearning-claim/[0-9]+/[0-9]+" packt_daily.html)
PHLog "Claim URL: $claim"

##  Extract book title, trim and remove invalid characters
title=$(grep "dotd-title" -A 2 packt_daily.html | tail -1 | sed 's/^[^0-9A-Za-z]*//;s/[\t ]*<\/h2>$//')
title="$(echo -e "${title}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' -e 's/[^A-Za-z0-9 ._-]//g')"
PHLog "eBook Found: $title"

##  Extract book ID
bookID=$(echo $claim | sed "s/.*\/\([0-9]*\)\/.*/\1/")
echo "";

##  Claim ebook now
curl -s --retry $noOfRetry -m $timeOut -A "$userAgent" -b "$cookie" -c "$cookie" -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' -H 'Accept-Encoding: gzip, deflate' -H 'Accept-Language: en-US,en;q=0.5' -H 'Connection: keep-alive' -H 'Host: www.packtpub.com' -H 'Referer: https://www.packtpub.com/packt/offers/free-learning' "https://www.packtpub.com/$claim"
errorCode=$?;  test "$errorCode" -ne "0" && { PHLog "curl exit error code: $errorCode";  exit; }

##----------------------------------------------------------------------------------------
##  Send LINE alert
PHLog "Sending LINE alert..."
curl -X POST -H 'Content-Type:application/json' -H 'Authorization: Bearer s8zJezZGperi5WKRaTaQViB3jXNAgJxkDu+JxlEOfUt2jkRg/EILEZhm2oH1famWe3/kiQu95DlOIQ0zF4sr9vaycPSik63B9UW5gsBCccANZ/eg4DztSr2/7sZVgL/tfskAH2Ni+kwdB04t89O/w15fpcyilFU=' \
-d '{"to":"'"$lineUser"'",
    "messages":[
        {
            "type":"text",
            "text":"A Packt ebook ['"$title"'] have been redeemed to '"$userID"'.  Thanks!"
        }
    ]}' https://api.line.me/v2/bot/message/push

##----------------------------------------------------------------------------------------
PHLog ""

2016年12月4日 星期日

有生意係咪真係唔做?

過去兩個月,有些事情一直卡在心內。其中一樣是:究竟有生意係咪真係唔做?

站過老闆的崗位,可以說,只要計過有錢賺,又不是傷天害理的工作,只要信心過半,我是一定會做。唔理是不是公司的方向、唔理是 Product 或是 Project。還是會接了後再解決。有時甚至為了客戶關係、未來利益、面子工程,蝕本也要做,輸少有時真係當贏。曾經,我也很理直氣壯地認為,不是公司方向的不接、賺果雞碎的不接、客人麻煩的不接。既然選擇 Product 的道路,應該要破斧沉舟,專注一點。當時我沒有出錢,只出力,話說得特別輕鬆,特別容易。問題是,甚麼都不接,人工開支從哪裡來?除非是有更好的機會,或是千幾萬未開頭,錢可以慢慢地燒。否則,還是要乖乖認命。我被合作的拍檔薰陶了。是我向現實低頭嗎?是我成熟了嗎?我找不到答案...

2016年12月3日 星期六

Facebook Live 遊戲(二)


繼上星期開發的 Facebook Live 遊戲 後,今日再編寫多一個以「抽獎」為題的互動 Facebook Live 遊戲。加入其餘兩個負面的反應,不過所有反應都只有相同動作,就是轉動輪盤。效果不錯,很期待有商戶使用。

2016年12月2日 星期五

Pacess Studio 臉書專頁


幾個月前在 Facebook 建立了一個專頁。專頁以我第一次正式掛牌賣 apps 的公司名稱命名,叫「Pacess Studio」。它是我用過的公司名稱中感覺最好的一個。最近才開始貼文,也邀請了部份朋友關注,得整理得成熟一點。我需要一個正式見得人的商標。過去兩星期學習了一點 Illustrator 的畫法,因此用它製作出 Pacess Studio 的商標。以往我用 Photoshop 來達成,但商標圖案還是用 Illustrator 畫的才夠銳利。

原本圖案如下。後來想給人一點關於「Digital」數碼的感覺,所以才加了線路板的線紋。我很滿意現在的版本,看上去帶點日式味道;而且設計可愛,帶點瑪利奧的感覺,更有點像機械人,非常配合我的範疇。


但看了又看,太多黑白位置,於是把花盤變成紅色。也不錯。夠搶眼,我喜歡:

2016年11月30日 星期三

Raspberry Pi 的 Webcam 影像移位重疊問題


早幾天,我用 Python 編寫了一個偵測移動物的程式。它是在 Raspberry Pi 上配合 Webcam 執行。當發現移動物時,除了把影像儲存外,還會經電郵傳送給我,好讓我知道家中的情況。可是,我經常收到錯誤的警報。原因是 Webcam 傳到 Python 的影像中,有時會上一幀跟下一幀移位重疊。由於動作偵測是以畫面變化來作準,移位重疊令到這個準則成立,觸發誤報。起初以為是 Python 對於處理記憶體物件是以指針來處理,程式工作得慢而導致下一幀已寫進記憶體,可是改用 .copy() 後問題依舊,似乎是鏡頭或是驅動程式的問題。有待解決。
##----------------------------------------------------------------------------------------
##  Motion Detector for Webcam
##----------------------------------------------------------------------------------------
##  Written by Pacess HO
##  Platform : Python3 + OpenCV3
##  Date : 2016.Nov.25
##  Copyright 2016 Pacess Studio.  All rights reserved.
##----------------------------------------------------------------------------------------

import smtplib
import imutils
import time
import cv2

# from msvcrt import getch
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart

##----------------------------------------------------------------------------------------
##  Main program start
print("\nMotion Detector for Webcam\n\n")
print("Warming up, please wait...\n")
camera = cv2.VideoCapture(0)
time.sleep(2)

##  Loop over the frames of the video
skipCount = 0
lastStatus = False
thisStatus = False
masterFrame = None
print("Start capturing...\n")
while True:

   ##----------------------------------------------------------------------------------------
   ##  Grab the current frame
   grabbed = camera.grab()
 
   ##  If the frame could not be grabbed, then we have reached the end of the video
   if not grabbed:
      continue
 
   if skipCount > 0:
      skipCount = skipCount-1
      if skipCount == 0:
         print("Count down completed, resume capture.")
      continue

   ##----------------------------------------------------------------------------------------
   ##  Convert frame to grayscale, and blur it
   flag, frame = camera.retrieve()
   if not flag:
      continue

   currentFrame = frame.copy()
   gray = cv2.cvtColor(currentFrame, cv2.COLOR_BGR2GRAY)
   gray = cv2.GaussianBlur(gray, (21, 21), 0)
 
   # if the first frame is None, initialize it
   if masterFrame is None:
      masterFrame = gray
      continue

   ##----------------------------------------------------------------------------------------
   ##  Compute the absolute difference between the current frame and
   frameDelta = cv2.absdiff(masterFrame, gray)
   threshold = cv2.threshold(frameDelta, 68, 255, cv2.THRESH_BINARY)[1]

   threshold = cv2.dilate(threshold, None, iterations=2)
   (_, contourArray, _) = cv2.findContours(threshold.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) 

   ##  Loop over the contours
   lastStatus = thisStatus
   thisStatus = False
   for contour in contourArray:

      ##  If the contour is too small, ignore it
      difference = cv2.contourArea(contour)
      if difference < 10000:
         continue
 
      thisStatus = True

      ##  Compute the bounding box for the contour
      (x, y, w, h) = cv2.boundingRect(contour)
      cv2.rectangle(currentFrame, (x, y), (x+w, y+h), (0, 255, 0), 2)

   ##----------------------------------------------------------------------------------------
   ##  Save frame and send email alert
   if thisStatus == True:

      masterFrame = gray

      ##  Save frame
      id = time.strftime("%Y%m%d%H%M%S")
      filename = "./capture/capture_"+str(id)+".jpg"
      cv2.imwrite(filename, currentFrame)
      print("Capture: "+filename)

      ##  Sending email notification
      email = MIMEMultipart()
      email["Subject"] = "Raspberry Pi Motion Alert"
      email["From"] = "pacess@pacess.com"
      email["To"] = "pacess@pacess.com"

      with open(filename, "rb") as filePointer:
         image = MIMEImage(filePointer.read())
      email.attach(image)

      smtp = smtplib.SMTP("smtp.mail.yahoo.com", 587)
      smtp.ehlo()
      smtp.starttls()
      smtp.login("pacess@yahoo.com", "12345678")
      smtp.sendmail(email["From"], email["To"], email.as_string())
      smtp.quit()

      ##  Skip some frames to prevent sending too much image
      print("Email sent, wait for a while...")
      skipCount = 100

##----------------------------------------------------------------------------------------
##  Cleanup the camera and close any open windows
camera.release()
cv2.destroyAllWindows()

2016年11月26日 星期六

Facebook Live 遊戲


還在玩 Facebook Live 投票?看看我的 Facebook Live 遊戲!

自從研發了「即時 Facebook 互動投票」後,這兩個星期經常在 Facebook 看到不同的品牌應用起來。我在想,現在已經玩得很爛,已經很悶;下一步可以做些甚麼呢?想到了用戶給予反應可以當作是按鍵,基於這個想法,便可以發展成為遊戲。問題是每個用戶只能按鍵一次,那麼遊戲要如何玩?於是我想到了煙花遊戲。

這個應用應該是香港首個,甚至是全球第一。我急不及待,花了一整天去完成程式及相關的美術後期製作。其實還有其他的想法,但以一人之力去完成編程及繪圖,所花的時間很多;而且這是概念驗證,上面的已經很有水準。

2016年11月23日 星期三

用 Google Prediction API 預測股價

今日試另一樣新東西,用 Google Prediction API 預測股價。我在想,外國應該有很多高人試過,既然沒甚麼消息,想必準繩度沒有提高多少。雖然如此,也想體驗當中樂趣。不過,我運用了過習過的玄學知識滲入其中,外國人不太懂的中國哲理,隨時能讓我比他們更快找到箇中奧妙。

首先編寫了一個 PHP 程式讀出《股票經理》的股票數據,再加入升級了的天干地支 API 數值,生產出 0005.HK 五年的 .csv 資料集。然後把它上傳到 Google Cloud Storage;再把上傳了的 .csv 檔案供給 Google APIs Explorer 建立預測模型。


生成模型需要一點時間,但 Google Cloub Platform 比 IBM Watson 快出幾倍。執行 predition.trainedmodels.get 指令能查看訓練模型的狀態。第一次訓練時出現「trainingStatus: ERROR」錯誤,發現訓練用的 .csv 不能有標籤或檔頭之類東西。


修正過後,重新上傳 .csv 並進行訓練。約十秒時間模型便完成了。


利用「prediction.trainedmodels.predict」指令及參數便能預測結果。由於我的 .csv 包含了價錢、年、月、日、時數據,而第一個必須為預測的內容,亦即是價錢;所以輸入的參數便是年、月、日、時。下圖是當刻的預測結果,跟市價差不多,好像能拿來作為參考。我再做了兩次預測,結果是今日股價下跌到 $59.74,但明天中午回升至 $60.28。能否作準,明天自有結果。好緊張 p(^_^)q

2016年11月22日 星期二

用 Google Cloud Vision API 辨認 Captcha


準備好 Captcha 資料集後,下一步是上傳到 Google Cloud Storage。

接著用 APIs Explorer 進行測試,可惜失敗率很高。看來用 Google Cloud Vision API 來對抗 Captcha 的方法不行,得找另一個方案。

2016年11月21日 星期一

用 Google Cloud Vision API 辨認文字


今日繼續嘗試 Google Cloud Vision API v1 我在 Apple 網頁抓了上面的圖片,把它上傳到 Google Cloud Storage。然後用 APIs Explorer 進行文字辨認。
Request
POST https://vision.googleapis.com/v1/images:annotate?key={YOUR_API_KEY}
{
   "requests": [{
      "features": [{
         "type": "TEXT_DETECTION"
      }],
      "image": {
         "source": {
            "gcsImageUri": "gs://dummy/macbook_pro_en.png"
         }
      }
   }]
}

ResponseResponse
{
   "responses": [{
      "textAnnotations": [{
         "locale": "la",
         "description": "MacBook Pro\nA touch of genius.\n",
         "boundingPoly": {
            "vertices": [{
               "x": 126,
               "y": 47
            }, {
               "x": 410,
               "y": 47
            }, {
               "x": 410,
               "y": 122
            }, {
               "x": 126,
               "y": 122
            }]
         }
      }, {
         "description": "MacBook",
         "boundingPoly": {
            "vertices": [{
               "x": 175,
               "y": 47
            }, {
               "x": 306,
               "y": 47
            }, {
               "x": 306,
               "y": 73
            }, {
               "x": 175,
               "y": 73
            }]
         }
      }, {
         "description": "Pro",
         "boundingPoly": {
            "vertices": [{
               "x": 320,
               "y": 47
            }, {
               "x": 362,
               "y": 47
            }, {
               "x": 362,
               "y": 73
            }, {
               "x": 320,
               "y": 73
            }]
         }
      }, {
         "description": "A",
         "boundingPoly": {
            "vertices": [{
               "x": 126,
               "y": 79
            }, {
               "x": 141,
               "y": 79
            }, {
               "x": 141,
               "y": 122
            }, {
               "x": 126,
               "y": 122
            }]
         }
      }, {
         "description": "touch",
         "boundingPoly": {
            "vertices": [{
               "x": 157,
               "y": 79
            }, {
               "x": 243,
               "y": 79
            }, {
               "x": 243,
               "y": 122
            }, {
               "x": 157,
               "y": 122
            }]
         }
      }, {
         "description": "of",
         "boundingPoly": {
            "vertices": [{
               "x": 258,
               "y": 79
            }, {
               "x": 286,
               "y": 79
            }, {
               "x": 286,
               "y": 122
            }, {
               "x": 258,
               "y": 122
            }]
         }
      }, {
         "description": "genius.",
         "boundingPoly": {
            "vertices": [{
               "x": 296,
               "y": 79
            }, {
               "x": 410,
               "y": 79
            }, {
               "x": 410,
               "y": 122
            }, {
               "x": 296,
               "y": 122
            }]
         }
      }]
   }]
}

英文文字認得不錯,那中文又如何?我也試了一次。效果很好:
Request
POST https://vision.googleapis.com/v1/images:annotate?key={YOUR_API_KEY}
{
   "requests": [{
      "features": [{
         "type": "TEXT_DETECTION"
      }],
      "image": {
         "source": {
            "gcsImageUri": "gs://dummy/macbook_pro_tc.png"
         }
      }
   }]
}

Response
{
   "responses": [{
      "textAnnotations": [{
         "locale": "zh-Hant",
         "description": "MacBook Pro\n天才橫溢,一觸而發\n亢。\no\n",
         "boundingPoly": {
            "vertices": [{
               "x": 110,
               "y": 52
            }, {
               "x": 426,
               "y": 52
            }, {
               "x": 426,
               "y": 115
            }, {
               "x": 110,
               "y": 115
            }]
         }
      }, {
         "description": "MacBook",
         "boundingPoly": {
            "vertices": [{
               "x": 171,
               "y": 52
            }, {
               "x": 300,
               "y": 52
            }, {
               "x": 300,
               "y": 75
            }, {
               "x": 171,
               "y": 75
            }]
         }
      }, {
         "description": "Pro",
         "boundingPoly": {
            "vertices": [{
               "x": 311,
               "y": 52
            }, {
               "x": 355,
               "y": 52
            }, {
               "x": 355,
               "y": 75
            }, {
               "x": 311,
               "y": 75
            }]
         }
      }, {
         "description": "天才",
         "boundingPoly": {
            "vertices": [{
               "x": 110,
               "y": 82
            }, {
               "x": 176,
               "y": 82
            }, {
               "x": 176,
               "y": 115
            }, {
               "x": 110,
               "y": 115
            }]
         }
      }, {
         "description": "橫溢",
         "boundingPoly": {
            "vertices": [{
               "x": 178,
               "y": 82
            }, {
               "x": 244,
               "y": 82
            }, {
               "x": 244,
               "y": 114
            }, {
               "x": 178,
               "y": 114
            }]
         }
      }, {
         "description": ",",
         "boundingPoly": {
            "vertices": [{
               "x": 254,
               "y": 96
            }, {
               "x": 258,
               "y": 96
            }, {
               "x": 258,
               "y": 104
            }, {
               "x": 254,
               "y": 104
            }]
         }
      }, {
         "description": "一",
         "boundingPoly": {
            "vertices": [{
               "x": 277,
               "y": 96
            }, {
               "x": 307,
               "y": 96
            }, {
               "x": 307,
               "y": 99
            }, {
               "x": 277,
               "y": 99
            }]
         }
      }, {
         "description": "觸",
         "boundingPoly": {
            "vertices": [{
               "x": 311,
               "y": 83
            }, {
               "x": 342,
               "y": 83
            }, {
               "x": 342,
               "y": 114
            }, {
               "x": 311,
               "y": 114
            }]
         }
      }, {
         "description": "而",
         "boundingPoly": {
            "vertices": [{
               "x": 345,
               "y": 84
            }, {
               "x": 376,
               "y": 84
            }, {
               "x": 376,
               "y": 114
            }, {
               "x": 345,
               "y": 114
            }]
         }
      }, {
         "description": "發",
         "boundingPoly": {
            "vertices": [{
               "x": 380,
               "y": 83
            }, {
               "x": 410,
               "y": 83
            }, {
               "x": 410,
               "y": 114
            }, {
               "x": 380,
               "y": 114
            }]
         }
      }, {
         "description": "亢",
         "boundingPoly": {
            "vertices": [{
               "x": 395,
               "y": 91
            }, {
               "x": 409,
               "y": 92
            }, {
               "x": 409,
               "y": 102
            }, {
               "x": 395,
               "y": 101
            }]
         }
      }, {
         "description": "。",
         "boundingPoly": {
            "vertices": [{
               "x": 419,
               "y": 94
            }, {
               "x": 426,
               "y": 94
            }, {
               "x": 426,
               "y": 102
            }, {
               "x": 419,
               "y": 102
            }]
         }
      }, {
         "description": "o",
         "boundingPoly": {
            "vertices": [{
               "x": 419,
               "y": 95
            }, {
               "x": 425,
               "y": 95
            }, {
               "x": 425,
               "y": 103
            }, {
               "x": 419,
               "y": 103
            }]
         }
      }]
   }]
}

2016年11月20日 星期日

準備 Captcha 資料集


2015 年 2 月,嘗試過 Captcha 解碼,但失敗了。最近不斷吸收機器學習方面的知識,坊間也有用 Google Cloud Vision API 對 Google Captcha 的方案,我也想試試自己在這方面的能力。但在這之前,我需要有一些 Captcha 圖案及標籤,用來訓練大腦。最簡單的方法就是用程式來自行生產。我用 PHP 寫了兩個程序:

第一個是生成 Captcha 圖像及標籤的程式:
<?php
//----------------------------------------------------------------------------------------
//  Captcha Generator
//----------------------------------------------------------------------------------------
//  Platform: CentOS7 + PHP + Apache
//  Written by Pacess HO
//  Copyright 2016 Pacess Studio.  All rights reserved.
//----------------------------------------------------------------------------------------

header("Access-Control-Allow-Origin: https://home.pacess.com");
header("Access-Control-Allow-Methods: POST");

date_default_timezone_set("Asia/Hong_Kong");
mb_internal_encoding("UTF-8");
ini_set("memory_limit", "-1");
set_time_limit(0);

session_start();

//----------------------------------------------------------------------------------------
require_once "./securimage/securimage.php";

//========================================================================================
if ($_REQUEST["code"] == "1")  {

   header("Content-Type: text/html");
   $codeArray = $_SESSION["securimage_code_disp"];
   echo("Captcha:".$codeArray["default"]);
   exit(0);
}

$securimage = new Securimage();
$securimage->show();

?>

第二個是讀取圖像並把它以標籤作為檔名的程式:
<?php
//----------------------------------------------------------------------------------------
//  Captcha Dataset Generator
//----------------------------------------------------------------------------------------
//  Platform: CentOS7 + PHP + Apache
//  Written by Pacess HO
//  Copyright 2016 Pacess Studio.  All rights reserved.
//----------------------------------------------------------------------------------------

header("Content-type: text/html");
header("Cache-Control: no-cache, must-revalidate");
header("Expires: Tue, 10 Mar 1987 00:00:00 GMT");

date_default_timezone_set("Asia/Hong_Kong");
mb_internal_encoding("UTF-8");
ini_set("memory_limit", "-1");
set_time_limit(0);

//----------------------------------------------------------------------------------------
$path = "./files/";
$cookieFile = $path."_cookie.txt";
$count = 1;

//========================================================================================
//  Main program
if (isset($_REQUEST["count"]))  {$count = intval($_REQUEST["count"]);}
for ($i=0; $i<$count; $i++)  {

   //  Get a Captcha image
   $curl = curl_init();
   curl_setopt($curl, CURLOPT_URL, "http://sitachan.local/captcha/getCode.php");
   curl_setopt($curl, CURLOPT_POST, 1);
   curl_setopt($curl, CURLOPT_POSTFIELDS, "code=0");
   curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
   curl_setopt($curl, CURLOPT_COOKIEJAR, $cookieFile); 
   curl_setopt($curl, CURLOPT_COOKIEFILE, $cookieFile); 
   $pngContent = curl_exec($curl);
   curl_close($curl);

   //  Get a Captcha value
   $curl = curl_init();
   curl_setopt($curl, CURLOPT_URL, "http://sitachan.local/captcha/getCode.php");
   curl_setopt($curl, CURLOPT_POST, 1);
   curl_setopt($curl, CURLOPT_POSTFIELDS, "code=1");
   curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
   curl_setopt($curl, CURLOPT_COOKIEJAR, $cookieFile); 
   curl_setopt($curl, CURLOPT_COOKIEFILE, $cookieFile); 
   $string = curl_exec($curl);
   curl_close($curl);

   //  String: "Captcha: nYY6FF"
   $array = explode(":", $string);
   $code = $array[1];
   if (strlen($code) == 0)  {$code = "default";}
   $filename = $code.".png";

   //  Save image
   $filePath = $path.$filename;
   $file = fopen($filePath, "w");
   fwrite($file, $pngContent);
   fclose($file);

   //----------------------------------------------------------------------------------------
   //  Output
   echo("<img src='$filePath' />");
   echo("Image size: ".strlen($pngContent));
   echo("String: $string");
   echo("Filename: $filename");
}
?>

2016年11月19日 星期六

初試 Google Cloud Natural Language API


除了 Google Prediction API 能依照留言分類成正評及負評外,Google 還有 Google Cloud Natural Language API 可以達成。用 APIs Explorer 進行測試,可惜暫時還未支援正體中文...。

2016年11月15日 星期二

用 Google Prediction API 來判斷留言者的情緒


繼 IBM Watson 後,今日嘗試了另一個 Machine Learning Framework: Google Preduction API。同樣地,希望能用它來做 Sentiment Analysis,判斷用戶的留言是正評,還是負評。要測試 Google Prediction API 有六個步驟:

1. 在 Google Cloud Console 啟動 Google Prediction API

2. 準備訓練數據。我選用了 https://inclass.kaggle.com/c/si650winter11/data 內的資料。

3. 上傳到 Google Cloud Storage

4. 執行訓練程序:
Request
POST https://www.googleapis.com/prediction/v1.6/projects/dummy-c15ed/trainedmodels?key={YOUR_API_KEY}
{
   "id": "sentiment",
   "storageDataLocation": "dummy-c15ed.appspot.com/sentiment_training.txt"
}
 
Response
{
   "kind": "prediction#training",
   "id": "sentiment",
   "selfLink": "https://www.googleapis.com/prediction/v1.6/projects/dummy-c15ed/trainedmodels/sentiment",
   "storageDataLocation": "dummy-c15ed.appspot.com/sentiment_training.txt"
}

5. 檢查訓練狀態:
Request
GET https://www.googleapis.com/prediction/v1.6/projects/dummy-c15ed/trainedmodels/sentiment?key={YOUR_API_KEY}
 
Response
{
   "kind": "prediction#training",
   "id": "sentiment",
   "selfLink": "https://www.googleapis.com/prediction/v1.6/projects/dummy-c15ed/trainedmodels/sentiment",
   "created": "2016-11-15T07:15:34.690Z",
   "trainingStatus": "RUNNING"
}

直至完成:
Request
GET https://www.googleapis.com/prediction/v1.6/projects/dummy-c15ed/trainedmodels/sentiment?key={YOUR_API_KEY}
 
Response
{
   "kind": "prediction#training",
   "id": "sentiment",
   "selfLink": "https://www.googleapis.com/prediction/v1.6/projects/dummy-c15ed/trainedmodels/sentiment",
   "created": "2016-11-15T07:15:34.690Z",
   "trainingComplete": "2016-11-15T07:16:26.026Z",
   "modelInfo": {
      "numberInstances": "7085",
      "modelType": "classification",
      "numberLabels": "2",
      "classificationAccuracy": "0.98"
   },
   "trainingStatus": "DONE"
}

6. 輸入新的留言進行測試:
Request
POST https://www.googleapis.com/prediction/v1.6/projects/dummy-c15ed/trainedmodels/sentiment/predict?key={YOUR_API_KEY}
{
   "input": {
      "csvInstance": [
         "This is really a poor product, waste my time!"
      ]
   }
}
 
Response
{
   "kind": "prediction#output",
   "id": "sentiment",
   "selfLink": "https://www.googleapis.com/prediction/v1.6/projects/dummy-c15ed/trainedmodels/sentiment/predict",
   "outputLabel": "Negative",
   "outputMulti": [
      {
         "label": "Positive",
         "score": "0.353047"
      }, {
         "label": "Negative",
         "score": "0.646953"
      }
   ]
}
Request
POST https://www.googleapis.com/prediction/v1.6/projects/dummy-c15ed/trainedmodels/sentiment/predict?key={YOUR_API_KEY}
{
   "input": {
      "csvInstance": [
         "Pretty cool!  I love it!"
      ]
   }
}
 
Response
{
   "kind": "prediction#output",
   "id": "sentiment",
   "selfLink": "https://www.googleapis.com/prediction/v1.6/projects/dummy-c15ed/trainedmodels/sentiment/predict",
   "outputLabel": "Positive",
   "outputMulti": [
      {
         "label": "Positive",
         "score": "0.998411"
      }, {
         "label": "Negative",
         "score": "0.001589"
      }
   ]
}

由於使用的是英語素材進行訓練,所以測試也要以英文進行。看來效果不錯。下一步是要找出中文素材。

2016年11月13日 星期日

在 Facebook Live 直播時插入即時用戶反應


昨晚花了數個小時開發了一個名為「Interactive Facebook Live」的示範程式。程式以 Facebook Javascript SDK + Graph API + Velocity.JS + 能把畫面輸出到 Facebook Live 的直播軟件去達成。製作這個示範,是希望在進行 Facebook Live 直播時,能即時把當刻的用戶反應包含在直播內容中,相信這樣更有趣味。

首先,當然是建立 HTML 檔案,加入 Facebook Javascript SDK。我是取得旗下專頁其中一個貼文的用戶反應,所以要加入 Facebook 登入,好讓 SDK 能取得 Token 來繼續往後的查詢工作。有了登入,之後就是利用 Graph API 取得用戶反應數據,得到的是一個 Array 數組,進行解讀說行,這個工作很簡單。為了畫面更加生動,於是加入 Velocity.JS 來表現彈一彈的動畫。這樣,網頁部份基本上完成了。只要把網頁的畫面經過 Facebook Live 直播軟件直播出去,就能達成這個「Interactive Facebook Live」的示範。大家不妨到我的專頁 https://www.facebook.com/Pacess-Studio-901621989960199/?fref=ts 看看。

2016年11月10日 星期四

IBM Watson 平台用後感


IBM Watson 平台的 30 天試用已經過了一半,得趕緊在到期前完成測試。

作為依懶社交平台生存的生意,要是能用人工智能把用戶的評語作出分析,可以讓企業能快速應對,同時也能減輕工作量。Personality Insights 看來幫到忙。設定好「Service Credentials」後就能把生成的用戶名稱及密碼用在 API 接口。看完教學文件後嘗試連接,得出 404 錯誤碼。明明跟足教學,卻出現錯誤,實在奇怪。於是再找其他辦法,卻發現:

在文件一指出用:
https://gateway.watsonplatform.net/personality-insights/api

但在文件二就叫用:
https://gateway.watsonplatform.net/personality-insights/api/v3

而在文件三則用:
https://gateway.watsonplatform.net/personality-insights/api/v3/profile

多個文件版本,唯有逐一測試。最終發現前兩個地址傳回 404 錯誤碼,根本不能運作。意味著文件內容過時及誤導。文件東一份西一份,就算正確的那份文件,也沒有對傳回值作完整的說明,實在非常差勁。以下是成功的呼叫指令:

curl -X POST --user b7654abc-a1b2-4567-abcd-1234567890cd:NWes1Ncdk2CR --header "Content-Type:text/plain;charset=utf-8" --data-binary "@profile.txt" "https://gateway.watsonplatform.net/personality-insights/api/v3/profile?version=2016-10-20&consumption_preferences=true&raw_scores=true"

2016年11月9日 星期三

讓 Google Data Studio 接入 AWS 伺服器

同事用 Google Data Studio 連接到 AWS 上的數據庫時出現問題。經過一輪的追查後,發現是防火牆只容許指定的電腦接入。只要加入 Google 的伺服器地址就能解決問題。可是,Google 那麼多伺服器,究竟用哪個地址呢?

於是我在登入 SSH 後,用「watch -n 5 tcpdump -i eth0 -s 1500 port 3306」指令監聽著接入的訊號,待同事用 Google Data Studio 接入,找出了地址是「74.125.72.161」。我估計 Google 有多台伺服器,決定讓「74.125.72.0」到「74.125.72.255」都通過防火牆。在 AWS 上輸入 CIDR 格式「74.125.72.0/24」即可。

2016年11月8日 星期二

Google Hack





今日利用幾招 Google 搜尋方法去找 Sita 陳僖儀的資料,找到一些關於她的 PDF,當中有幾個包含了我沒有看過的圖片。我用的指令是「filetype:pdf "陳僖儀"」。Google Hack 的秘技如下:

2016年11月5日 星期六

未來人類不用工作


這個星期花了四天時間看完 Elon Musk 的傳記,是我讀得最快的一本書。原因是內容吸引,而且某些方面跟自己的想法十分接近,很有共鳴。

恰巧,昨天看到一篇 Elon Musk 的報導,指他預計未來基於自動化及機械人取代勞力,工作種類變少,政府會為平民進行補貼。這個構思跟我早幾個月前的想法不約而同都很相似。我的思路是: 自動化及機械人取代勞力,糧食產出提升,政府能確保人民得到溫飽。人類不用耕種,可以有更多時間發展興趣,提升國家競爭力。同時也因為得到溫飽,有些人不去工作。所以國家會提高成為人民的水平門檻,逐漸排除低學歷低智商的市民,留下高學歷高智商的人,生產有質素的下一代。在排除了低下的一群後,會有更多的資源分配給留下來的人,再次提升人民的生活水平。

其實 Elon Musk 在書中有提過需要保持高質素市民的人口,將這個想法套用到他的說法上,就有可能跟我上面所說的事情差不多。

2016年11月4日 星期五

Natural Language Processing With Python


近兩個星期,一有時間便會學習「Machine Learning」方面的事情。由於還沒有具體的成果,所以沒有作出分享。其中一個目標是開發一套軟件去聽取用戶在 Facebook 及其他社交平台的意見,然後把意見轉化為情緒指標。看看滿意的有多少百份比、憤怒的有多少百份比、各指標的變化及趨勢。這有利我們把活動及貼文做得更好;而且不用瀏覽所有留言才得悉用戶的大概想法。要達到以上目標,「Natural Language Processing」應該幫到忙,不過對於處理中文來說,還是跟英文很不一樣。在 YouTube 上有一系列的教學,這個是我目前在觀看的。這位作者還有很多圍繞 Python 方面的教學影片,之前看關於股市走勢的也有他份,十分利害。

2016年10月25日 星期二

F1/10 自走車


機械人圈子的朋友今日在臉書貼上這個影片,吸引了我的眼球。原因是兩年前開始,我已經想打造一台這樣的車。不過價錢一直是個障礙。在 http://f1tenth.org/index 這個網頁有充足的說明指導大家如何建立一台自走車。同時也有部件清單、訂購網調及價錢。希望今晚的萬聖節金多寶六合彩中獎便能如願以償。

2016年10月23日 星期日

Programming for Finance with Python and Quantopian and Zipline


昨天介紹的 Quantopian 是從這條影片中得知。這個清單講解了利用 Python 及 Quantopian 平台進行股票買賣的策略及編程,有興趣的朋友不妨參考一下。

2016年10月22日 星期六

Quantopian


今日在學習 Machine Learning + Finance 時,發現到一個叫 Quantopian 的網站。我被它吸引住了。原因它是免費使用; 它是一個 iPython 的平台,可以在網上編寫程式並執行; 它包含了機器學習所而的程式庫,隨時能夠調用; 它包含股市投資常用的功能,如: SMD,非常容易使用; 它包含了 Python 的 Pandas 繪圖庫,在網頁執行自訂的 Python 程式時,同時能繪畫圖表; 它包含了美國 8000 隻的股票資料,及 670 個魔方,方便實驗自己的買入賣出演算法。

2016年10月21日 星期五

在 macOS Sierra 安裝 TensorFlow

八號風球懸掛,最好在家中繼續進行研究。早幾天在公司的 CentOS 伺服器安裝了 TensorFlow,今日在家中的 MacBook Pro 進行安裝。首先,準備好需要的工具程式:
  • sudo easy_install pip
  • sudo easy_install --upgrade six
     

    下載並安裝 CPU 版的 TensorFlow:
  • export TF_BINARY_URL=https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-0.11.0rc0-py2-none-any.whl
  • sudo pip install --upgrade $TF_BINARY_URL
     

    測試一下 TensorFlow 是否順利安裝:
  • python -c 'import os; import inspect; import tensorflow; print(os.path.dirname(inspect.getfile(tensorflow)))'
  • 2016年10月20日 星期四

    天干地支 API


    一直都想把八字及大數據結合一起看看效果。於是用 PHP 編寫了一個能輸入洋曆,輸出天干地支的接口,方便日後不同的應用程式使用。這類的轉換程式寫了幾遍,有 J2ME 的 Java 版、有 iOS 的 Objective-C 版、有網頁的 PHP 版。要自己編寫有一定難度,也要有相關知識,甚至是萬年曆行內的計算公式。例如要計算出節氣的日期及時間、子初換日、立春換年...等才能準確計算出來。現在這個部份已經完成,之後便是利用天干地支的值來訓練機器學習中的決策樹,看看能否找出一點模式,從而估計股票的升跌。

    2016年10月19日 星期三

    利用 TensorFlow 辨認花朵圖片


    這兩天在研究《機器學習》,看了 IBM Watson、百度 Paddle、Google TensorFlow。無意中在 YouTube 找到了七段關於 TensorFlow 的教學。每條影片少於 10 分鐘,但所講的內容非常實用濃縮。由於還未得到 IBM Watson 的試用帳號,而百度 Paddle 在安裝上又遇到阻滯,於是選了 TensorFlow 來優先研究。其中 TensorFlow Playground 很實用,可以即時看到不同層次的學習過程,也可以隨意加減層數及關節數,讓我更容易理解當中發生的事情。


    利用 Inception v3 把 ImageNet 的 Neural Network,重新訓練最後一層的 Bottleneck,花了一小時多才學習完成。準確度達 93%。讓 TensorFlow 能辨認出玫瑰、雛菊、蒲公英、鬱金香、太陽花的照片。訓練完成後,隨意在網上找來兩張相片,兩張都成功被辨認出來。對於今次的監督式學習,TensorFlow 真的很容易使用。

    2016年10月18日 星期二

    股票經理:LINE-Bot


    上周花了點精神去在沒有準備的情況下原價購入 PlayStation VR 而使得《股票經理》的通知部份只完成了八成。今日重拾工作。基上上週編寫了 LINE-Bot 程式,我很想把《股票經理》及其他 Chat-Bot 結合在一起。除了 LINE-Bot 外,我還希望加入 Facebook Messenger Bot 及 Slack-Bot。於是在結構上作出了改動。第一個會被開發出來的將會是 LINE-Bot。希望能在本週內完成。

    2016年10月17日 星期一

    Slack Bot


    既然做開 LINE Bot,那麼也學習一下 Slack Bot。

    Slack Bot 跟 LINE Bot 及 Facebook Messenger Bot 都是用 Webhook 的方法。它的結構比教簡單,同樣是 php://input 輸入,但不是使用 JSON 格式,反而是平常的 Query 內容,即 a=1&b=2&c=3 之類。輸出則是 JSON,內容只有 {"text"=>"Got it!"} 就行。說明文件寫得不夠清楚,介面也沒有足夠的指引,花了很多時間才找到用的是「Outgoing Webhook」而不是「BOT」。

    2016年10月16日 星期日

    PS Move 入手


    中午時分借到 PS4 回來,現在只欠 PS Move。朋友得悉後,立即通知我星期五他在藍田的遊戲店購買到美版 PS Move,還給我聯絡電話。我立即致電查詢。美版 PS Move 沒有紙盒包裝,也沒有手繩附送,價錢為 HK$230 一支。較行版 HK$298 便宜一點。兩支計起來,在我的 HK$500 預算之內。得出 HK$500 預算是因為 Log-on 的 All-in-one 同捆裝比我的版本貴 HK$500,差別就在於兩支 PS Move。知道有貨後立即到藍田購買。既然硬件已齊備,那麼也是時候需要購買一套 VR 遊戲。另一位朋友推介《VR Worlds》,所以我選了它。

    由 PS3 年代起,周邊產品都是要接駁 PlayStation 來充電,其他的 USB 充電器是無法替它們充電。回家後邊玩 PSVR 邊替 PS Move 充電。由於 PSVR 佔用了一個 USB 接口,剩下一個 USB 要充兩支 PS Move 電,於是加上 USB Hub。但其中一支充不到電,不過仍然能繼續遊戲。還以為是鋰電乾塘充不起的緣故。睡覺前,我把 PS4 轉為睡眠模式,拿走 PSVR 的 USB 接線,兩個 USB 接口替兩支 PS Move 充電,兩支都是慢閃,意味著能成功充電。

    2016年10月15日 星期六

    PSVR 的 HDMI 輸入


    昨天拿到 PSVR 後,嘗試過把 MacBook Pro 的 HDMI 輸出接到 PSVR。驚喜地能順利把 MacBook 的畫面投射到 PSVR 中,還有 VR 效果。看出來的畫面就像自己置身大戲院內,而且是包場那種。前方是大大的銀幕,當頭部移動時,銀幕還是會停留在原來那個方向。

    今天我嘗試做差不多的事情。家中沒有支援 3D 效果電視機,但卻有 3D 藍光碟。電視機是三星,遙控很容易壞掉,但還是能看,不至於要換台新。我的 PS3 是有四個 USB 埠,能支援 PS2 那種機,它是支援 3D 藍光碟。要是 PSVR 能播放立體電影,那就完美!連接好所需硬體後,畫面傳來是不支援的畫面。查看說明書,發現 PSVR 是沒有視差效果,所以不能做出立體影像,相信是由於成本及利潤的關係。十分可惜...。

    2016年10月14日 星期五

    PlayStation VR 入手


    一直有留意 PlayStation VR 的動向。昨天首賣,知道公司附近的 Log-on 將會有 All-in-one 同捆裝,很適合我這種甚麼配備也沒有的玩家。心想首賣應該貨源充足,身邊亦沒有很多人期待 PSVR,會很容易買到吧。於是午飯時到 Log-on 一趟,打算購買。一點十五分到達時,沒有人影,心知不妙。看看專櫃的告示,原來已經售罄...。我低估了 PSVR 的受歡迎程度。

    晚上時分,同事會到深水埗,於是託她幫忙看看高登商場有沒有現貨。現貨是有,但那時價錢已經是 +HK$1000~HK$1500。我沒有 PS4,不用著急。打算等下月 PS4 Pro 推出時才跟 PSVR 一起購買。


    朋友在我的面書知道情況,剛巧他是 PSVR 的熱血粉絲,早前已經預訂了數套 PSVR 以備朋友之需。他知道我是用家而不是炒家; 加上我在 Gameone 工作時認識他,他了解到 PSVR 對我開發上有幫助,於是以原價給我一套。機會十分難得。我立即要了,並在今天一起到銅鑼灣的 Sony Store 取貨。


    很多謝那位朋友的鼎力襄助,我才能輕易地拿到一台 PSVR!

    2016年10月13日 星期四

    LINE-BOT


    今天看到關於用 LINE Bot 來監測伺服器狀態的報導,才得悉原來 LINE 在年初已加入 Bot 的行列。於是我也嘗試製作一個示範程式。

    首先,要有一個 LINE 帳號,然後到 https://business.line.me/en/ 登入後的 Accounts 頁登記一個商業帳號。商業帳號可以看成是一個頻道。啟動 Webhook 並輸入帶 SSL 的伺服器程式網址。在 Server IP Whitelist 中輸入伺服器的 IP 地址。


    然後跟據 https://developers.line.me/messaging-api/overview 的通訊格式進行開發。整個結構都很簡單,用 file_get_contents("php://input"); 讀取傳進來的 JSON 內容,解釋,再生成回覆用的 JSON 內容到 https://api.line.me/v2/bot/message/reply 就能成功發送回覆內容。

    2016年10月12日 星期三

    股票經理:預測黃金交差


    把昨天構思好的公式以 PHP 語言寫出來,直接在終端機執行。從畫面輸出可以看到準確率不錯。井號的日子代表預計三天內會出現買入訊號。Intersect 數值代表預計還有多少天會出現買入訊號。Cross 字眼代表真正出現買入訊號的日子。畢竟預測跟發生是兩碼子的事,無可避免會出現像 2016-06-13 的誤判。然而,最終是人腦作守門員,買入與否可因應當時複雜的情況而作出判斷。

    2016年10月11日 星期二

    股票經理:預測黃金交差公式


    要預早幾天找出兩條線的交差時間,我想起初中曾讀過關於斜度及伸延線的數學。突然覺得自己很有記憶力,不過只限往事。近幾天發生的事還是很容易遺忘。

    在網上找點資料加上自行推算,再拿來印證,得出正確的計算方法。首先,我假設一條線是均等向下,另一條線是均等向上,方便計算。在實際情況可以用數天的平均值、也可以帶比重的平均值、甚至直接拿昨天今天的斜度計算。但要留意是用百份比,而不是股價。我的做法是先把兩線在同一天的值相減,如上圖 10 月 3 日的 86 及 26,相減得出 60 作為分子。把上升線的斜度減去下降線的斜度,即是 5-(-7) = 12 作為分母。60/12 = 5 便代表五天後會出現交差,亦即是 10 月 8 日。如圖所見,數字正確。再拿另一天來印證,以 10 月 7 日為例。(66-54)/12 = 1 代表一天後出現交差,亦即是 10 月 8 日。

    2016年10月10日 星期一

    股票經理:索取 2008-2016 數據


    星期六跟伯父午餐時,我向他請教了三件事。我索取了由 2010-01-01 起的恒生指數數據,個人認為六年的數據足夠計算 200 天平均線好幾年,也能充份測試買入賣出策略。為了確認我的思路正確,所以向伯父查詢。他指股市有週期,一個週期約五至八年,所以保留六年數據已經可以。但既然有可能是八年,那我得索取更多的數據。到目前為止只用了 16MB 空間,不是太多。反正索取數據的程式已在,簡單調校到較前的日子便能自動進行補充,何樂而不為。於是今天把數據提前到 2008 年。選這年是因為當年股市非常動盪,應該很有參考價值。

    第二個問題是,在網上搜尋過關於對股票市場應用機器學習的文章,其中一篇指浪費時間,將會徒勞無功。就這個疑問向伯父請教。他指歷史不斷重演,只要捉到週期便能把握掙錢機會。雖然本人覺得利用舊數據去預測未來不會準確,但研究一下無妨。要是能找到突破點,我便能改善家人的生活。

    第三個問題就是如何判定賣出時間。可惜的是他指要賣出自動化,需要看很多數據,編程起來很複雜。