Google+ Followers

2017年10月18日 星期三

[Firebase] Firebase 開發筆記 - FCM 與 Web app debug

之前已經小試身手了,在此把 Web app notification debug 訊息紀錄一下。

1. 關於發 push 要用的 Serverkey 擺在 Firebase -> Project -> Settings -> Project settings -> CLOUD MESSAGING -> Project credentials 的 Server key

2. 初始化 index.html ,請至 Firebase -> Project -> Overview -> Add Firebase to your web app,把他埋在 <head></head> 即可

<script src="https://www.gstatic.com/firebasejs/4.5.2/firebase.js"></script>
<script>
  // Initialize Firebase
  var config = {
    apiKey: "YourAPIKey",
    authDomain: "your-app.firebaseapp.com",
    databaseURL: "https://your-app.firebaseio.com",
    projectId: "your-app",
    storageBucket: "your-app.appspot.com",
    messagingSenderId: "your-app-id"
  };
  firebase.initializeApp(config);
</script>


3. 使用 const messaging = firebase.messaging(); 來操作,如要求收訊權限,取得後則進行要 FCM token:
<script>
const messaging = firebase.messaging();
messaging.requestPermission()
.then(function() {
console.log('Notification permission granted.');
try_to_get_token();
})
.catch(function(err) {
console.log('Unable to get permission to notify.', err);
});
function try_to_get_token() {
messaging.getToken()
.then(function(currentToken) {
if (currentToken) {
console.log("currentToken:", currentToken);
} else {
console.log('No Instance ID token available. Request permission to generate one.');
}
})
.catch(function(err) {
console.log('An error occurred while retrieving token. ', err);
});
}
</script>


4. 開發上請使用 Chrome browser ,想要重置 FCM token 可以在 chrome://settings/content/notifications 將指定網域清除

5. 碰到要不到 FCM token 錯誤訊息

browserErrorMessage: "Failed to register a ServiceWorker: A bad HTTP response code (404) was received when fetching the script."
code: "messaging/failed-serviceworker-registration"
message: "Messaging: We are unable to register the default service worker. Failed to register a ServiceWorker: A bad HTTP response code (404) was received when fetching the script. (messaging/failed-serviceworker-registration)."


解法一:在根目錄建置一個空的檔案,檔名為 firebase-messaging-sw.js (最方便的解法)

$ touch /path/www/document_root/firebase-messaging-sw.js

解法二:刻一個 ServiceWorker 處理 (彈性的解法)

$ touch sw.js (與 index.html 同層即可)
$ vim index.html
<script>
const messaging = firebase.messaging();

if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js').then(function(registration) {
console.log('ServiceWorker registration successful with scope: ', registration.scope);
messaging.useServiceWorker(registration);
//try_to_get_token();
messaging.requestPermission()
.then(function() {
console.log('Notification permission granted.');
try_to_get_token();
})
.catch(function(err) {
console.log('Unable to get permission to notify.', err);
});

}).catch(function(err) {
//registration failed :(
console.log('ServiceWorker registration failed: ', err);
});
}
</script>


6. 建置收訊息的部分:

前景:
<script>
const messaging = firebase.messaging();
messaging.onTokenRefresh(function() {
console.log("onTokenRefresh");
try_to_get_token();
});

messaging.onMessage(function(payload) {
console.log("Message received. ", payload);
});

if ('serviceWorker' in navigator) {
console.log("test 'serviceWorker' in navigator");
navigator.serviceWorker.register('sw.js').then(function(registration) {
console.log('ServiceWorker registration successful with scope: ', registration.scope);
messaging.useServiceWorker(registration);
//try_to_get_token();
messaging.requestPermission()
.then(function() {
console.log('Notification permission granted.');
try_to_get_token();
})
.catch(function(err) {
console.log('Unable to get permission to notify.', err);
});

}).catch(function(err) {
//registration failed :(
console.log('ServiceWorker registration failed: ', err);
});
}

function try_to_get_token() {
messaging.getToken()
.then(function(currentToken) {
if (currentToken) {
console.log("currentToken:", currentToken);
} else {
console.log('No Instance ID token available. Request permission to generate one.');
}
})
.catch(function(err) {
console.log('An error occurred while retrieving token. ', err);
});
}
</script>


背景(/firebase-messaging-sw.js 或是自訂的 ServiceWorker):

$ vim sw.js
// Import and configure the Firebase SDK
// These scripts are made available when the app is served or deployed on Firebase Hosting
// If you do not serve/host your project using Firebase Hosting see https://firebase.google.com/docs/web/setup
importScripts('https://www.gstatic.com/firebasejs/4.5.2/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/4.5.2/firebase-messaging.js');
importScripts('https://www.gstatic.com/firebasejs/4.5.2/firebase.js');

  // Initialize Firebase
  var config = {
    apiKey: "YourAppApiKey",
    authDomain: "your-app.firebaseapp.com",
    databaseURL: "https://your-app.firebaseio.com",
    projectId: "your-app",
    storageBucket: "your-app.appspot.com",
    messagingSenderId: "your-app-id"
  };
  firebase.initializeApp(config);

const messaging = firebase.messaging();

// If you would like to customize notifications that are received in the
// background (Web app is closed or not in browser focus) then you should
// implement this optional method.
// [START background_handler]
messaging.setBackgroundMessageHandler(function(payload) {
  console.log('[firebase-messaging-sw.js] Received background message ', payload);
  // Customize notification here
  const notificationTitle = 'Background Message Title';
  const notificationOptions = {
    body: 'Background Message body.',
    icon: '/firebase-logo.png'
  };

  return self.registration.showNotification(notificationTitle,
      notificationOptions);
});
// [END background_handler]


7. 背景工作補充 /firebase-messaging-sw.js (或是自訂的 ServiceWorker)

當 /firebase-messaging-sw.js (或是自訂的 ServiceWorker) 是空白時,這時 web app (chrome browser) 都到訊息時,只會提醒有訊息在背景更新,且前景都不會觸發 messaging.onMessage 的事件。

8. 若發現異常,例如 Web 前景有 token 又收不到訊息時,請留意背景工作是否有實作,建議把 chrome browser 重開,仍不行時,甚至在 chrome://settings/content/notifications 清除重新來過

9.善用 curl command 測試:

curl -X POST -H "Authorization: key=YourAppServerKey" -H "Content-Type: application/json" -d '{
  "notification": {
    "title": "Hello",
    "body": "World",
    "icon": "firebase-logo.png",
    "click_action": "http://localhost:8081"
  },
  "to": "FCM token"
}' "https://fcm.googleapis.com/fcm/send"


10. 若 user 移除接收,送訊息時會收到回饋:

{"multicast_id":####,"success":0,"failure":1,"canonical_ids":0,"results":[{"error":"NotRegistered"}]}

11. FCM 跟用戶有沒有登入(Google帳號)無關

2017年10月17日 星期二

[Firebase] Firebase 開發筆記 - Firebase Cloud Messaging (FCM) 初體驗、 Topic 管理與 Web notification

firebase-web-console

最近強者同事推薦使用 FCM ,看了一下,對於計費方式有點抖,若一切免費的話,真的超佛心 :P 由於支援潮流的訊息訂閱架構,基本上幾乎可以不用自行紀錄 notification token 了,用起來很直觀又方便。例如,想要個別通知時,可以每一個 user 給予一個 topic id (如 user id) ,就能夠不用紀錄 raw notification token 單獨發訊給對方!有一點點像用運算取代空間感。

當發送 push 給使用者後,且使用者取得訊息時,關注 Realtime db 的 Download 流量有變動,不知是不是真的要計算流量?若是的話,大約發送一則訊息算 2KB ,而免費版流量 10GB/month,約一個月可以發送 500萬則訊息。

firebase-rt-db-download

若真的要計算流量費的話,那 FCM 就不是免錢,接著要花錢則是 25 美金方案有 20GB/month ,接著更高級則是 $1/GB 計費。

Updated @ 2017/10/18: 與 Firebase 客服聯繫,確認 FCM 不佔用 Realtime Database 流量!並且一口氣發送 100 則後,流量也沒有大量增加,符合預期!超佛心。強者同事提醒,對於 Database Download 流量,可能是 Firebase dashboard/console 的資訊讀寫 ( https://firebase.google.com/docs/database/usage/billing - Firebase console data )

聊聊趣味的地方,關於 Topic 管理機制,當你有 FCM token 後,可以在 server site 幫 device 訂閱 topic 喔!這功能很便利,就像...你紀錄了一堆 GCM token / APNs token 後,自己過濾對象,再進行發送。然而,topic 概念在於"使用情境"知道後,直接刻在 app 端,只是突然新增使用情境時,就得變成新版 app user 可以接受,舊版則無法接受到訊息,這時 server site 若有記錄 FCM token 時,就可以代勞幫忙訂閱:

https://developers.google.com/instance-id/reference/server#manage_relationship_maps_for_multiple_app_instances

https://iid.googleapis.com/iid/v1:batchAdd
Content-Type:application/json
Authorization:key=API_KEY
{
   "to": "/topics/movies",
   "registration_tokens": ["nKctODamlM4:CKrh_PC8kIb7O...", "1uoasi24:9jsjwuw...", "798aywu:cba420..."],
}


同理,也可以取消訂閱:https://iid.googleapis.com/iid/v1:batchRemove

若 server site 是 node.js 的,可以直接用 SDK :https://firebase.google.com/docs/cloud-messaging/admin/manage-topic-subscriptions

最後,筆記一下 Web app 的部分:假設網站服務位置為 https://example.com/fcm/ ,預設都要在根目錄埋上 firebase-messaging-sw.js 檔案,可以空白。

$ touch /var/www/firebase-messaging-sw.js
$ vim /var/www/fcm/index.html
<html>
<head>
<script src="https://www.gstatic.com/firebasejs/4.5.2/firebase.js"></script>
<script>
// Initialize Firebase
var config = {
apiKey: "your-app-apikey",
authDomain: "your-app.firebaseapp.com",
databaseURL: "https://your-app.firebaseio.com",
projectId: "your-app",
storageBucket: "your-app.appspot.com",
messagingSenderId: "your-app-id"
};
firebase.initializeApp(config);
</script>
</head>
<body>
<script>
const messaging = firebase.messaging();

messaging.onTokenRefresh(function() {
messaging.getToken()
.then(function(refreshedToken) {
console.log('Token refreshed.', refreshedToken);
})
.catch(function(err) {
console.log('Unable to retrieve refreshed token ', err);
});
});

messaging.onMessage(function(payload) {
console.log("Message received. ", payload);
});

messaging.requestPermission()
.then(function() {
console.log('Notification permission granted.');
resetUI();
})
.catch(function(err) {
console.log('Unable to get permission to notify.', err);
});

function resetUI() {
console.log('resetUI');
messaging.getToken()
.then(function(currentToken) {
if (currentToken) {
console.log("currentToken:", currentToken);
} else {
console.log('No Instance ID token available. Request permission to generate one.');
}
})
.catch(function(err) {
console.log('An error occurred while retrieving token. ', err);
});
}
</script>
</body>
</html>


如此一來,用 chrome browser 瀏覽 https://example.com/fcm/ 會彈跳視窗詢問是否接收訊息,點擊接受後,在 dev tools 的 console 可以看到:

Notification permission granted.
resetUI
currentToken: thisClientFCMToken

如此一來,就可以對他發訊、設定此 client 去訂閱 topic 了:

$ curl -X POST -H "Authorization: key=YourFCMAppServerKey" -H "Content-Type: application/json" -d '{
  "notification": {
    "title": "Hello",
    "body": "World",
    "icon": "firebase-logo.png",
    "click_action": "http://localhost:8081"
  },
  "to": "thisClientFCMToken"
}' "https://fcm.googleapis.com/fcm/send"

$ curl -X POST -H "Authorization: key= YourFCMAppServerKey" -H "Content-Type: application/json" -d '{
  "registration_tokens":["thisClientFCMToken"],
  "to": "/topics/foo-bar"
}' "https://iid.googleapis.com/iid/v1:batchAdd"

$ curl -X POST -H "Authorization: key= YourFCMAppServerKey" -H "Content-Type: application/json" -d '{
  "notification": {
    "title": "Hello",
    "body": "World",
    "icon": "firebase-logo.png",
    "click_action": "http://localhost:8081"
  },
  "to": "/topics/foo-bar"
}' "https://fcm.googleapis.com/fcm/send"

2017年10月5日 星期四

台灣人力市場價

ptt-tech_job-M.1507055381.A.6A7

昨天看到大神按讚,剛好掃了這篇:[心得] 我的薪資歷程史 (續4) - 看板 Tech_Job - 批踢踢實業坊,感到健康又溫馨,所以來記錄一下。

我自己的心得...其實美國、中國和台灣的價目的確有很明顯的起薪差距,當你了解這個生態時,通常已深陷在某一個區域難以自拔,像是人已在矽谷、人在上海,或是人在台灣抱怨著數字 XD 台灣這邊大多都是看著鄉民的分享,且 PTT 有太多負面的情緒,且真正高薪者都忙得喘不過氣,要嘛工作,要嘛忙著訂機票排假期,誰跟你在那邊浪費時間處理負面心情,時間就是金錢啊!

在同業交流上,得到的資訊跟這位強者分享的極吻合。像是矽谷起薪好則 8~12萬鎂,六年資歷的資深等級拿到 30萬鎂的也有。而台灣的外商價也如上述對應,如大大的年資拿到一百八的數字,要超過也是有的,通常得搭配天時地利人和,拿了個二百五也不是不行。而這些是建立在生態圈,已穩健成長的公司,會願意花錢找強者,而完成A輪的新創,更是會花多錢來加速的公司成長的,因此,拿到高於行情時,要承擔的是面對收掉的風險,而拿到低於市場行情時,要想想自己的吃虧是為了什麼,想透了才會走得遠,最怕就是人云亦云,永遠不知幾兩重 Orz

回過頭來,我覺得台灣的人力價為了避免惡性競爭,大多都會說好一個範圍,太高就會碰到 HR 的思維:那其他成員該怎辦。而這樣是不是好事?也說不上來。只知道若一間公司起薪範圍屬於可接受的價碼,那剩下就好好珍惜,雖然跳槽是數字變化最快的方式(例如10%~50%)且不用等上一整年後才調薪,但公司若還有機會時,可跟這位大大一樣深耕,求一個不會虧待你的公司、有願景的公司,並且等待他起飛。

舉些對應例子:某強者當初離開 hTC 加入新創(當時股價還沒破千),他的新創老闆偶爾都會碎碎唸幾句“不好意思讓你少賺了”,但強者還是甘之如飴(在新創也過得很讚) ;前同事當初待在 hTC 趁勢賺了間中和房;負面的,則是分紅費用化,當時拿到 hTC 的股票繳的稅 > 死抱著股票的餘值。

總之,一切看緣分,強求不來啊,但時時努力是必要的,不要帶著負面心情、懷才不遇、惡習文化來圈地搞政治,重要的,求的是公司強盛後的分紅,而不是那份起薪啊

2017年9月30日 星期六

[Python] 機器學習筆記 - Feature Engineering 以鐵達尼號資料為例

之前都用一些工作經驗去分析一些資料,但不夠科學,有點僥倖的做事方式。如果碰到沒任何背景經驗的數據時,這時就慘了,就得回歸到靠數學統計了。

參考這篇:Titanic best working Classifier by Sina

整個過程十分享受如何用平均、標準差、去除雜訊、資料補齊、正規化、標籤數據化等等。讓我回想起之前寫的筆記,用了 LabelEncoder 等方式,結果...只要用 Pandas 搭配 map 架構就一口氣做光了 XD 並非 LabelEncoder 無用武之地,而是當你清楚資料屬性時,可以善用 Pandas 的架構去達成。而此例未用 OneHotEncoder 架構。

簡易筆記:

train = pd.read_csv('../input/train.csv', header = 0, dtype={'Age': np.float64})
test  = pd.read_csv('../input/test.csv' , header = 0, dtype={'Age': np.float64})
full_data = [train, test]

for dataset in full_data:
        dataset['Name_length'] = dataset['Name'].apply(len)
dataset['Has_Cabin'] = dataset["Cabin"].apply(lambda x: 0 if type(x) == float else 1)


用個 full_data = [train, test] 再搭配 "for dataset in full_data " 的好處是可以一口氣整理完 train/test dataset 的轉換,十方便利,之前完全沒想到這招。新增欄位就透過 pandas 架構直接添加,非常直觀,但沒想到可以一口氣搭配 apply 架構去處理,這樣程式超簡潔的:

dataset['Name_length'] = dataset['Name'].apply(len)
dataset['Has_Cabin'] = dataset["Cabin"].apply(lambda x: 0 if type(x) == float else 1)


另外,在字串處理時,可以搭配 replace 或 regular expression(apply) 做前置處理(正規化):

def get_title(name):
title_search = re.search(' ([A-Za-z]+)\.', name)
if title_search:
return title_search.group(1)
return ""

dataset['Title'] = dataset['Name'].apply(get_title)
#print(dataset['Title'].unique())
dataset['Title'] = dataset['Title'].replace(['Lady', 'Countess','Capt', 'Col','Don', 'Dr', 'Major', 'Rev', 'Sir', 'Jonkheer', 'Dona'], 'Rare')
dataset['Title'] = dataset['Title'].replace('Mlle', 'Miss')
dataset['Title'] = dataset['Title'].replace('Ms', 'Miss')
dataset['Title'] = dataset['Title'].replace('Mme', 'Mrs')
#print(dataset['Title'].unique())


整理完一輪後,把字串轉數據,就可以透過 map 來轉換,其中 fillna 則是把剩下沒對應到的都填 0 ,簡潔啊:

title_mapping = {"Mr": 1, "Miss": 2, "Mrs": 3, "Master": 4, "Rare": 5}
dataset['Title'] = dataset['Title'].map(title_mapping)
dataset['Title'] = dataset['Title'].fillna(0)


接著來處理年紀與票價的部分,由於偏隱私,容易無資料,這時看到作者就開始用亂數去填補年紀,並依照著標準差資訊來做,可以維持資料分布,高招!而票價則用中位數去填補:

age_avg = dataset['Age'].mean()
age_std = dataset['Age'].std()
age_null_count = dataset['Age'].isnull().sum()
age_null_random_list = np.random.randint(age_avg - age_std, age_avg + age_std, size=age_null_count)
#
# dataset['Age'][np.isnan(dataset['Age'])] = age_null_random_list
#
# SettingWithCopyWarning:
# A value is trying to be set on a copy of a slice from a DataFrame
# See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
#
dataset.loc[ dataset['Age'][np.isnan(dataset['Age'])].index , 'Age' ] = age_null_random_list

dataset.loc[ dataset['Age'] <= 16, 'Age'] = 0
dataset.loc[(dataset['Age'] > 16) & (dataset['Age'] <= 32), 'Age'] = 1
dataset.loc[(dataset['Age'] > 32) & (dataset['Age'] <= 48), 'Age'] = 2
dataset.loc[(dataset['Age'] > 48) & (dataset['Age'] <= 64), 'Age'] = 3
dataset.loc[ dataset['Age'] > 64, 'Age'] = 4 ;

dataset['Fare'] = dataset['Fare'].fillna(train['Fare'].median())
dataset.loc[ dataset['Fare'] <= 7.91, 'Fare'] = 0
dataset.loc[(dataset['Fare'] > 7.91) & (dataset['Fare'] <= 14.454), 'Fare'] = 1
dataset.loc[(dataset['Fare'] > 14.454) & (dataset['Fare'] <= 31), 'Fare']   = 2
dataset.loc[ dataset['Fare'] > 31, 'Fare'] = 3
dataset['Fare'] = dataset['Fare'].astype(int)


最後,再提一下新增屬性的部分,pandas 真的很方便,可以單純把某欄位的資訊計算一番,添加到新的欄位,也有透過 dataset.loc 來取得特定資料來重新設定:

dataset['Name_length'] = dataset['Name'].apply(len)
dataset['Has_Cabin'] = dataset["Cabin"].apply(lambda x: 0 if type(x) == float else 1)

dataset['IsAlone'] = 0
dataset.loc[dataset['FamilySize'] == 1, 'IsAlone'] = 1

dataset.loc[ dataset['Age'][np.isnan(dataset['Age'])].index , 'Age' ] = age_null_random_list

dataset.loc[ dataset['Age'] <= 16, 'Age'] = 0
dataset.loc[(dataset['Age'] > 16) & (dataset['Age'] <= 32), 'Age'] = 1


讀完這篇真是功力大增啊!以上是數據整理的流程,但是,還有個重點沒提到,那就是作者是整理完後,立馬看看某欄位跟目標欄位(Survived)的關係,才是真的靠數學來做事:

print (train[["Sex", "Survived"]].groupby(['Sex'], as_index=False).mean())
      Sex  Survived
0  female  0.742038
1    male  0.188908

print (train[['Title', 'Survived']].groupby(['Title'], as_index=False).mean())

    Title  Survived
0  Master  0.575000
1    Miss  0.702703
2      Mr  0.156673
3     Mrs  0.793651
4    Rare  0.347826


可以得知 Miss 跟 Mrs 的生存率超過七成!這才是該學的精神。對於常搭船的,大多很清楚一開始必然先讓婦幼先逃生,所以有背景就會先假定讓女性生存率高的分析方式,沒背景就得靠統計功力了

2017年9月27日 星期三

[Unix] 透過 jq 處理時間比較 @ macOS / jq-1.5

看到同事在處理 AWS AMI 的管理,用到了 jq 在過濾舊版資料來進行刪除,我也練了一下 jq 就順便筆記一下:

$ echo '{}' | jq 'now'
1506504885.298058
$ echo '{}' | jq 'now | gmtime '
[
  2017,
  8,
  27,
  9,
  34,
  47.25080108642578,
  3,
  269
]
$ echo '{}' | jq 'now | gmtime | todate'
"2017-09-27T09:35:00Z"
$ echo '{}' | jq 'now | gmtime | todate[:10]'
"2017-09-27"
$ echo '{}' | jq 'now | todate[:10]'
"2017-09-27"


得到三天前的時間:

$ echo '{}' | jq '(now - 86400*3) | gmtime | todate[:10]'
"2017-09-24"


假想一票資料:

$ echo '[{"StartTime": "2017-09-21T10:32:33.000Z"},{"StartTime": "2017-09-22T10:32:33.000Z"},{"StartTime": "2017-09-26T10:32:33.000Z"},{"StartTime": "2017-09-24T10:32:33.000Z"},{"StartTime": "2017-09-25T10:32:33.000Z"},{"StartTime": "2017-09-19T10:32:33.000Z"},{"StartTime": "2017-09-01T10:32:33.000Z"},{"StartTime": "2017-09-92T10:32:33.000Z"}]' | jq ''
[
  {
    "StartTime": "2017-09-21T10:32:33.000Z"
  },
  {
    "StartTime": "2017-09-22T10:32:33.000Z"
  },
  {
    "StartTime": "2017-09-26T10:32:33.000Z"
  },
  {
    "StartTime": "2017-09-24T10:32:33.000Z"
  },
  {
    "StartTime": "2017-09-25T10:32:33.000Z"
  },
  {
    "StartTime": "2017-09-19T10:32:33.000Z"
  },
  {
    "StartTime": "2017-09-01T10:32:33.000Z"
  },
  {
    "StartTime": "2017-09-92T10:32:33.000Z"
  }
]


挑出超過七天者(此乃字串比對):

$ echo '[{"StartTime": "2017-09-21T10:32:33.000Z"},{"StartTime": "2017-09-22T10:32:33.000Z"},{"StartTime": "2017-09-26T10:32:33.000Z"},{"StartTime": "2017-09-24T10:32:33.000Z"},{"StartTime": "2017-09-25T10:32:33.000Z"},{"StartTime": "2017-09-19T10:32:33.000Z"},{"StartTime": "2017-09-01T10:32:33.000Z"},{"StartTime": "2017-09-92T10:32:33.000Z"}]' | jq '.[] | if (.StartTime[0:10] < (now - (86400* 7) | todate[0:10]) ) then .StartTime else empty end '
"2017-09-19T10:32:33.000Z"
"2017-09-01T10:32:33.000Z"

2017年9月24日 星期日

[戲劇] 大王不容易



又有一陣子沒看戲,更別說是大陸戲劇了。被室友推坑看了一下,前面還滿新鮮的,但後面的滋味有點難述 XD 論其喜好,我比較喜歡前半段的安排,酷酷的女主角很正,男主角娘的很流暢。除此之外,也有發現一位台灣女演員 袁子芸 。我覺得古裝有種像做實驗時,控制變因似的,當大家穿一樣的服裝時,天生麗質的就會被凸顯出來。

說說別的吧,一開始吸引人的是男主角詮釋女主角個性的演技,查了一下 WIKI 才知道男主角 张逸杰 1999 年 9月 2 號出生,前幾天才剛滿18歲而已!女主角 白鹿 是 1994 年 9 月 23 號出生的。原來演戲的跟看戲的都是處女座 XD

看著百度百科,讓人有點感到恐怖,年輕時期就很努力地打拼了,時間啊。

2017年9月19日 星期二

[Python] 機器學習筆記 - 使用 Pandas 處理 CSV 格式、過濾資料與自身 Index 更新問題

使用 Pandas 套件進行分析資料時,它提供的功能包括便利的資料過濾:

import seaborn as sns
import pandas as pd

dataset = sns.load_dataset("tips")
print(dataset)
print(dataset.shape)
print(dataset.columns)

print("list total_bill > 30:")
print(dataset[ dataset['total_bill'] > 30 ] )


然而,預設 Pandas 會記錄原先的 raw index ,這也有不錯的功用,但有時希望照新的架構顯示,需要再多用 reset_index():

print("list total_bill > 30 and tip < 4:")
print(dataset[ (dataset['total_bill'] > 30) & (dataset['tip'] < 4) ] )

print("rebuild index:")
dataset = dataset[ (dataset['total_bill'] > 30) & (dataset['tip'] < 4) ]
dataset = dataset.reset_index()
print(dataset)


連續動作:

$ python pandas_study.py
     total_bill   tip     sex smoker   day    time  size
0         16.99  1.01  Female     No   Sun  Dinner     2
1         10.34  1.66    Male     No   Sun  Dinner     3
2         21.01  3.50    Male     No   Sun  Dinner     3
3         23.68  3.31    Male     No   Sun  Dinner     2
4         24.59  3.61  Female     No   Sun  Dinner     4
5         25.29  4.71    Male     No   Sun  Dinner     4
6          8.77  2.00    Male     No   Sun  Dinner     2
7         26.88  3.12    Male     No   Sun  Dinner     4
8         15.04  1.96    Male     No   Sun  Dinner     2
9         14.78  3.23    Male     No   Sun  Dinner     2
10        10.27  1.71    Male     No   Sun  Dinner     2
11        35.26  5.00  Female     No   Sun  Dinner     4
12        15.42  1.57    Male     No   Sun  Dinner     2
13        18.43  3.00    Male     No   Sun  Dinner     4
14        14.83  3.02  Female     No   Sun  Dinner     2
15        21.58  3.92    Male     No   Sun  Dinner     2
16        10.33  1.67  Female     No   Sun  Dinner     3
17        16.29  3.71    Male     No   Sun  Dinner     3
18        16.97  3.50  Female     No   Sun  Dinner     3
19        20.65  3.35    Male     No   Sat  Dinner     3
20        17.92  4.08    Male     No   Sat  Dinner     2
21        20.29  2.75  Female     No   Sat  Dinner     2
22        15.77  2.23  Female     No   Sat  Dinner     2
23        39.42  7.58    Male     No   Sat  Dinner     4
24        19.82  3.18    Male     No   Sat  Dinner     2
25        17.81  2.34    Male     No   Sat  Dinner     4
26        13.37  2.00    Male     No   Sat  Dinner     2
27        12.69  2.00    Male     No   Sat  Dinner     2
28        21.70  4.30    Male     No   Sat  Dinner     2
29        19.65  3.00  Female     No   Sat  Dinner     2
..          ...   ...     ...    ...   ...     ...   ...
214       28.17  6.50  Female    Yes   Sat  Dinner     3
215       12.90  1.10  Female    Yes   Sat  Dinner     2
216       28.15  3.00    Male    Yes   Sat  Dinner     5
217       11.59  1.50    Male    Yes   Sat  Dinner     2
218        7.74  1.44    Male    Yes   Sat  Dinner     2
219       30.14  3.09  Female    Yes   Sat  Dinner     4
220       12.16  2.20    Male    Yes   Fri   Lunch     2
221       13.42  3.48  Female    Yes   Fri   Lunch     2
222        8.58  1.92    Male    Yes   Fri   Lunch     1
223       15.98  3.00  Female     No   Fri   Lunch     3
224       13.42  1.58    Male    Yes   Fri   Lunch     2
225       16.27  2.50  Female    Yes   Fri   Lunch     2
226       10.09  2.00  Female    Yes   Fri   Lunch     2
227       20.45  3.00    Male     No   Sat  Dinner     4
228       13.28  2.72    Male     No   Sat  Dinner     2
229       22.12  2.88  Female    Yes   Sat  Dinner     2
230       24.01  2.00    Male    Yes   Sat  Dinner     4
231       15.69  3.00    Male    Yes   Sat  Dinner     3
232       11.61  3.39    Male     No   Sat  Dinner     2
233       10.77  1.47    Male     No   Sat  Dinner     2
234       15.53  3.00    Male    Yes   Sat  Dinner     2
235       10.07  1.25    Male     No   Sat  Dinner     2
236       12.60  1.00    Male    Yes   Sat  Dinner     2
237       32.83  1.17    Male    Yes   Sat  Dinner     2
238       35.83  4.67  Female     No   Sat  Dinner     3
239       29.03  5.92    Male     No   Sat  Dinner     3
240       27.18  2.00  Female    Yes   Sat  Dinner     2
241       22.67  2.00    Male    Yes   Sat  Dinner     2
242       17.82  1.75    Male     No   Sat  Dinner     2
243       18.78  3.00  Female     No  Thur  Dinner     2

[244 rows x 7 columns]
(244, 7)
Index(['total_bill', 'tip', 'sex', 'smoker', 'day', 'time', 'size'], dtype='object')
list total_bill > 30:
     total_bill    tip     sex smoker   day    time  size
11        35.26   5.00  Female     No   Sun  Dinner     4
23        39.42   7.58    Male     No   Sat  Dinner     4
39        31.27   5.00    Male     No   Sat  Dinner     3
44        30.40   5.60    Male     No   Sun  Dinner     4
47        32.40   6.00    Male     No   Sun  Dinner     4
52        34.81   5.20  Female     No   Sun  Dinner     4
56        38.01   3.00    Male    Yes   Sat  Dinner     4
59        48.27   6.73    Male     No   Sat  Dinner     4
83        32.68   5.00    Male    Yes  Thur   Lunch     2
85        34.83   5.17  Female     No  Thur   Lunch     4
95        40.17   4.73    Male    Yes   Fri  Dinner     4
102       44.30   2.50  Female    Yes   Sat  Dinner     3
112       38.07   4.00    Male     No   Sun  Dinner     3
141       34.30   6.70    Male     No  Thur   Lunch     6
142       41.19   5.00    Male     No  Thur   Lunch     5
156       48.17   5.00    Male     No   Sun  Dinner     6
167       31.71   4.50    Male     No   Sun  Dinner     4
170       50.81  10.00    Male    Yes   Sat  Dinner     3
173       31.85   3.18    Male    Yes   Sun  Dinner     2
175       32.90   3.11    Male    Yes   Sun  Dinner     2
179       34.63   3.55    Male    Yes   Sun  Dinner     2
180       34.65   3.68    Male    Yes   Sun  Dinner     4
182       45.35   3.50    Male    Yes   Sun  Dinner     3
184       40.55   3.00    Male    Yes   Sun  Dinner     2
187       30.46   2.00    Male    Yes   Sun  Dinner     5
197       43.11   5.00  Female    Yes  Thur   Lunch     4
207       38.73   3.00    Male    Yes   Sat  Dinner     4
210       30.06   2.00    Male    Yes   Sat  Dinner     3
212       48.33   9.00    Male     No   Sat  Dinner     4
219       30.14   3.09  Female    Yes   Sat  Dinner     4
237       32.83   1.17    Male    Yes   Sat  Dinner     2
238       35.83   4.67  Female     No   Sat  Dinner     3
list total_bill > 30 and tip < 4:
print index:
index: 56
index: 102
index: 173
index: 175
index: 179
index: 180
index: 182
index: 184
index: 187
index: 207
index: 210
index: 219
index: 237
rebuild index:
    index  total_bill   tip     sex smoker  day    time  size
0      56       38.01  3.00    Male    Yes  Sat  Dinner     4
1     102       44.30  2.50  Female    Yes  Sat  Dinner     3
2     173       31.85  3.18    Male    Yes  Sun  Dinner     2
3     175       32.90  3.11    Male    Yes  Sun  Dinner     2
4     179       34.63  3.55    Male    Yes  Sun  Dinner     2
5     180       34.65  3.68    Male    Yes  Sun  Dinner     4
6     182       45.35  3.50    Male    Yes  Sun  Dinner     3
7     184       40.55  3.00    Male    Yes  Sun  Dinner     2
8     187       30.46  2.00    Male    Yes  Sun  Dinner     5
9     207       38.73  3.00    Male    Yes  Sat  Dinner     4
10    210       30.06  2.00    Male    Yes  Sat  Dinner     3
11    219       30.14  3.09  Female    Yes  Sat  Dinner     4
12    237       32.83  1.17    Male    Yes  Sat  Dinner     2
print index:
index: 0
index: 1
index: 2
index: 3
index: 4
index: 5
index: 6
index: 7
index: 8
index: 9
index: 10
index: 11
index: 12

2017年9月15日 星期五

[Python] 機器學習筆記 - 使用 準確率/召回率 (Precision-Recall) 評估分析成果

研究所時,算是第一次接觸這名詞,老闆的研究領域是 Search Engines ,用來評估索引成果好不好。最近則打算用在機器學習的成果分析,卻想不起當時老闆用來解釋索引成果的案例,還是容易忘記 XD 網路上打滾一下,發現這篇寫的廣告投放實際案例很好懂,也不容易忘,建議可以逛一下:準確率(Precision)與召回率(Recall)

回到本文,單純紀錄如何用既有函式庫計算:

import numpy as np
from sklearn.metrics import average_precision_score, precision_score, recall_score

# Classification metrics can't handle a mix of binary and continuous targets
#y = np.array([0, 0, 1, 1])
#scores = np.array([0.1, 0.3, 0.2, 0.8])

y = [0, 0, 1, 1]
scores = [0, 1, 1, 1]

#print(precision_score(y, scores, average='macro'))
#print(recall_score(y, scores, average='macro'))
#print(average_precision_score(y, scores))
#import sys
#sys.exit(0)

print('precision: %0.2f, recall: %0.2f, score: %0.2f' % (
        precision_score(y, scores, average='macro'),
        recall_score(y, scores, average='macro'),
        average_precision_score(y, scores)
))


成果:

precision: 0.83, recall: 0.75, score: 0.67

需要更詳細的範例,請參考:http://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html

[Python] 機器學習筆記 - 使用 ROC 曲線 (receiver operating characteristic curve) 評估分析成果

roc curve

最近回想起兩年前走跳過的一場黑客松,當年的題目恰好是一個屬性的分類,就是一篇文章屬性給你,請告訴我它是不是 spam!所幸網路上還可以看到其他人的作品,逛了一下也順便研究別人的報告怎麼寫,其中有一組使用了 ROC 曲線來回報自己的分析成果,就來多多認識一下。

而 ROC 曲線是什麼?其實在 WIKI 或是 scikit-learn 文件(也引用WIKI資料)有很明確地解釋:
簡單的說,當畫出此圖後,若一開始就達左上角是最完美的,若一開始分析結果是斜線上方是好的,反之下方是差的。接下來,則是會去計算曲線下方的面積,產生一個介於 0~1 的數值,只要等於 0.5 就是跟隨機猜測一樣,代表此分析模型沒有預測價值;若大於 0.5 代表猜測是正向的,而小於 0.5 代表猜測的方向恰好相反;而 1 或 0 代表全部辨識正確或全部辨識錯誤。

因此,只需設法把模型預測結果畫一下 ROC 曲線,在算出個面積,就收工啦!

範例請參考 http://scikit-learn.org/stable/auto_examples/model_selection/plot_roc.html ,在此只筆記畫圖的部分:

import numpy as np
from sklearn.metrics import roc_curve, auc
y = np.array([0, 0, 1, 1])
scores = np.array([0.1, 0.4, 0.35, 0.8])
fpr, tpr, _ = roc_curve(y, scores)
roc_auc = auc(fpr, tpr)

import matplotlib as mpl
#mpl.use('Agg')
import matplotlib.pyplot as plt

fig = plt.figure()
lw = 2
plt.plot(fpr, tpr, color='darkorange', lw=lw, label='ROC curve (area = %0.2f)' % roc_auc)
plt.plot([0, 1], [0, 1], color='navy', lw=lw, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver operating characteristic example')
plt.legend(loc="lower right")
#fig.savefig('/tmp/roc.png')
plt.show()

2017年9月14日 星期四

[MySQL] 從 JSON 抽取資料建立虛擬欄位 @ MySQL 5.7

同事反映 query 很慢,除了改進 SQL 語法外,也小試身手,對 JSON 資料抽出來建立虛擬欄位跟索引,順便筆記一下。

CREATE TABLE `my_data` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `data` text,
  PRIMARY KEY (`id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


這邊 data column 是 text 型態,主因是這 DB server 從 5.6 升上來的 XD 且當初讓 data 有很多彈性,不一定是 json 格式。

但,要抽取成虛擬欄位時,建議要保持 data 是 json 格式,就先整理一下資料:

mysql> UPDATE my_data SET data = '{}' WHERE data IS NULL OR data = ''

假想 data 的數值為 {"keyword":"value"},因此抽出 keyword 虛擬欄位來用:

mysql> ALTER TABLE my_data ADD keyword VARCHAR(64) AS (JSON_UNQUOTE(data->>"$. keyword"));
mysql> ALTER TABLE my_data ADD INDEX (keyword);


如此一來,可以改對 keyword 欄位查詢了,可以再加快一點,而 table 狀態更新為:

CREATE TABLE `my_data ` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `data` text,
  `keyword` varchar(64) GENERATED ALWAYS AS (json_unquote(json_unquote(json_extract(`data`,'$. keyword')))) VIRTUAL,
  PRIMARY KEY (`id`),
  KEY `keyword ` (`keyword `),
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2017年8月31日 星期四

[Python] 機器學習筆記 - sklearn.preprocessing 之 LabelEncoder, OneHotEncoder

最近挑一些資料來練習分析,想要用矩陣乘法,第一個念頭就是用 Hash table 把 keyword 轉成數值,接著要符合ㄧ些數學式子,又把數值擴展成 nx1 維,直到強者大大推坑看一些文件,我才發現這種招數很常使用,都有 library/framework 可以直接套用,順便把之前隨手寫的程式架構整理一下

先說一下 Hash table 的用法,就單純掃過所有數值,對所有數值建立查表方式,可以掃過一輪資料時,順便把標記都處理完畢:

import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np

dataset = sns.load_dataset("tips")
print(dataset)
print(dataset.shape)
print(dataset.columns)
# Index(['total_bill', 'tip', 'sex', 'smoker', 'day', 'time', 'size'], dtype='object')

dataset_formated = None

# 土炮模式
fields_lookup = {}

print(dataset.columns)
for index, row in dataset.iterrows():
row_formated = np.empty([])

#for fieldname in dataset.columns:
for fieldname in [ 'day', 'smoker', 'time', 'sex', 'size' ]:
#print(field)

field_value = None
if fieldname not in fields_lookup:
fields_lookup[fieldname] = {}
if row[fieldname] not in fields_lookup[fieldname]:
fields_lookup[fieldname][ row[fieldname] ] = len(fields_lookup[fieldname])

# field value from hash table
field_data = np.zeros(1, dtype=np.int)
field_data[0] = fields_lookup[fieldname][ row[fieldname] ]

# handle row
row_formated = np.append( row_formated, field_data.reshape(1, -1) )

# handle data
if dataset_formated is None:
dataset_formated = np.zeros([ dataset.shape[0], row_formated.reshape(1, -1).size ], dtype=np.int)
dataset_formated[index] = row_formated.reshape(1, -1)
#print(row_formated)
#print(row_formated.reshape(1, -1))

print(fields_lookup)
print(dataset_formated)


如此一來就完成編碼,也完成資料格式轉換:

{'day': {'Sun': 0, 'Sat': 1, 'Thur': 2, 'Fri': 3}, 'smoker': {'No': 0, 'Yes': 1}, 'time': {'Dinner': 0, 'Lunch': 1}, 'sex': {'Female': 0, 'Male': 1}, 'size': {2: 0, 3: 1, 4: 2, 1: 3, 6: 4, 5: 5}}

然而,對於部分演算法可能拿編碼的整數進行運算,或是想要更精準把整數擴展成選擇結果,那解法就是擴展欄位,例如有 5 種結果,就擴展成 5 個欄位,選到的標 1 ,沒選到標 0,土炮處理方式就麻煩了點,需要先掃一次建立 hash table,接著第二次在重建數據:

# build hash table only
for index, row in dataset.iterrows():
#for fieldname in dataset.columns:
for fieldname in [ 'day', 'smoker', 'time', 'sex', 'size' ]:
#print(field)
if fieldname not in fields_lookup:
fields_lookup[fieldname] = {}
if row[fieldname] not in fields_lookup[fieldname]:
fields_lookup[fieldname][ row[fieldname] ] = len(fields_lookup[fieldname])

print(fields_lookup)

# build new matrix
for index, row in dataset.iterrows():
row_formated = np.empty([])

for fieldname in [ 'day', 'smoker', 'time', 'sex', 'size' ]:
# field value from hash table
field_data = np.zeros([len(fields_lookup[fieldname]), 1], dtype=np.int)
field_data[ fields_lookup[fieldname][row[fieldname]] ][0] = 1

# handle row
row_formated = np.append( row_formated, field_data.reshape(1, -1) )

#print(row_formated)

# handle data
if dataset_formated is None:
dataset_formated = np.zeros([ dataset.shape[0], row_formated.reshape(1, -1).size ], dtype=np.int)
dataset_formated[index] = row_formated.reshape(1, -1)
#print(row_formated)
#print(row_formated.reshape(1, -1))

print(dataset_formated)


回到常用的方式 - LabelEncoder:

from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OneHotEncoder

dataset_encode = dataset.copy()
labels = {}

for i, field in enumerate(dataset.columns):
if field == 'tip' or field == 'total_bill':
continue
labels[field] = list(set(dataset[field].unique()))
label_encoder = LabelEncoder()
label_encoder.fit(labels[field])

# original
#print(dataset_encode.iloc[:,i])
#print(dataset_encode[field])

# encode
#feature = label_encoder.transform(dataset_encode.iloc[:,i])
#feature = feature.reshape(dataset.shape[0], 1)
# https://stackoverflow.com/questions/24458645/label-encoding-across-multiple-columns-in-scikit-learn
dataset_encode[field] = label_encoder.fit_transform(dataset_encode[field])

#print(dataset_encode[field])

print(dataset_encode)


輸出:

     total_bill   tip  sex  smoker  day  time  size
0         16.99  1.01    0       0    2     0     1
1         10.34  1.66    1       0    2     0     2
2         21.01  3.50    1       0    2     0     2
3         23.68  3.31    1       0    2     0     1
4         24.59  3.61    0       0    2     0     3
5         25.29  4.71    1       0    2     0     3
6          8.77  2.00    1       0    2     0     1
7         26.88  3.12    1       0    2     0     3
8         15.04  1.96    1       0    2     0     1
9         14.78  3.23    1       0    2     0     1
10        10.27  1.71    1       0    2     0     1
11        35.26  5.00    0       0    2     0     3
12        15.42  1.57    1       0    2     0     1
13        18.43  3.00    1       0    2     0     3
14        14.83  3.02    0       0    2     0     1
15        21.58  3.92    1       0    2     0     1
16        10.33  1.67    0       0    2     0     2
17        16.29  3.71    1       0    2     0     2
18        16.97  3.50    0       0    2     0     2
19        20.65  3.35    1       0    1     0     2
20        17.92  4.08    1       0    1     0     1
21        20.29  2.75    0       0    1     0     1
22        15.77  2.23    0       0    1     0     1
23        39.42  7.58    1       0    1     0     3
24        19.82  3.18    1       0    1     0     1
25        17.81  2.34    1       0    1     0     3
26        13.37  2.00    1       0    1     0     1
27        12.69  2.00    1       0    1     0     1
28        21.70  4.30    1       0    1     0     1
29        19.65  3.00    0       0    1     0     1
..          ...   ...  ...     ...  ...   ...   ...
214       28.17  6.50    0       1    1     0     2
215       12.90  1.10    0       1    1     0     1
216       28.15  3.00    1       1    1     0     4
217       11.59  1.50    1       1    1     0     1
218        7.74  1.44    1       1    1     0     1
219       30.14  3.09    0       1    1     0     3
220       12.16  2.20    1       1    0     1     1
221       13.42  3.48    0       1    0     1     1
222        8.58  1.92    1       1    0     1     0
223       15.98  3.00    0       0    0     1     2
224       13.42  1.58    1       1    0     1     1
225       16.27  2.50    0       1    0     1     1
226       10.09  2.00    0       1    0     1     1
227       20.45  3.00    1       0    1     0     3
228       13.28  2.72    1       0    1     0     1
229       22.12  2.88    0       1    1     0     1
230       24.01  2.00    1       1    1     0     3
231       15.69  3.00    1       1    1     0     2
232       11.61  3.39    1       0    1     0     1
233       10.77  1.47    1       0    1     0     1
234       15.53  3.00    1       1    1     0     1
235       10.07  1.25    1       0    1     0     1
236       12.60  1.00    1       1    1     0     1
237       32.83  1.17    1       1    1     0     1
238       35.83  4.67    0       0    1     0     2
239       29.03  5.92    1       0    1     0     2
240       27.18  2.00    0       1    1     0     1
241       22.67  2.00    1       1    1     0     1
242       17.82  1.75    1       0    1     0     1
243       18.78  3.00    0       0    3     0     1

[244 rows x 7 columns]


若想保留之前的欄位,也可用添加新欄位的方式:

dataset_encode[field+"-encode"] = label_encoder.fit_transform(dataset_encode[field])

而 OneHotEncoder 則是因為產出的維度會變大,要再設法把新產出來的數值再添加回去:

from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OneHotEncoder

dataset_encode = dataset.copy()
labels = {}

for i, field in enumerate(dataset.columns):
if field == 'tip' or field == 'total_bill':
continue

# original
#print(dataset_encode.iloc[:,i])
#print(dataset_encode[field])

# LabelEncode
labels[field] = list(set(dataset[field].unique()))
label_encoder = LabelEncoder()
label_encoder.fit(labels[field])

# https://stackoverflow.com/questions/24458645/label-encoding-across-multiple-columns-in-scikit-learn
dataset_encode[field+"-LabelEncode"] = label_encoder.fit_transform(dataset_encode[field])
#dataset_encode[field] = label_encoder.fit_transform(dataset_encode[field])

# OneHotEncode
feature = label_encoder.transform(dataset_encode[field])
feature = feature.reshape(dataset.shape[0], 1)
# http://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.OneHotEncoder.html
onehot_encoder = OneHotEncoder(sparse=False,n_values=len(labels[field]))
onehot_result = onehot_encoder.fit_transform(feature)
#dataset_encode[field+"-OneHotEncode"] = onehot_encoder.fit_transform(feature)
#onehot_encoder[ ["A","B"] ] = onehot_result

for index in range(len(labels[field])):
dataset_encode[field+"-OneHotEncode-"+str(index)] = onehot_result[:,index]

#print(dataset_encode[field])

print(dataset_encode.head(5))


如此一來,結果會是這樣:

   total_bill   tip     sex smoker  day    time  size  sex-LabelEncode  \
0       16.99  1.01  Female     No  Sun  Dinner     2                0
1       10.34  1.66    Male     No  Sun  Dinner     3                1
2       21.01  3.50    Male     No  Sun  Dinner     3                1
3       23.68  3.31    Male     No  Sun  Dinner     2                1
4       24.59  3.61  Female     No  Sun  Dinner     4                0

   sex-OneHotEncode-0  sex-OneHotEncode-1         ...           \
0                 1.0                 0.0         ...          
1                 0.0                 1.0         ...          
2                 0.0                 1.0         ...          
3                 0.0                 1.0         ...          
4                 1.0                 0.0         ...          

   time-LabelEncode  time-OneHotEncode-0  time-OneHotEncode-1  \
0                 0                  1.0                  0.0
1                 0                  1.0                  0.0
2                 0                  1.0                  0.0
3                 0                  1.0                  0.0
4                 0                  1.0                  0.0

   size-LabelEncode  size-OneHotEncode-0  size-OneHotEncode-1  \
0                 1                  0.0                  1.0
1                 2                  0.0                  0.0
2                 2                  0.0                  0.0
3                 1                  0.0                  1.0
4                 3                  0.0                  0.0

   size-OneHotEncode-2  size-OneHotEncode-3  size-OneHotEncode-4  \
0                  0.0                  0.0                  0.0
1                  1.0                  0.0                  0.0
2                  1.0                  0.0                  0.0
3                  0.0                  0.0                  0.0
4                  0.0                  1.0                  0.0

   size-OneHotEncode-5
0                  0.0
1                  0.0
2                  0.0
3                  0.0
4                  0.0

2017年8月29日 星期二

[Python] 機器學習筆記 - 使用 seaborn 呈現資料狀態(數據視覺化)

這陣子被高手推坑 kaggle ,越看越有動力,就順勢把很久以前欠的知識技術順勢補一下:資料視覺化。

在數據分析的比賽,默認的資料多是 csv 格式,接著用 panda 讀取,再用 seaborn 和 matplotlib.pyplot 繪圖。透過第一步的視覺化,快速得知數據分佈狀態,甚至在

輸入資料:

     total_bill   tip     sex smoker   day    time  size
0         16.99  1.01  Female     No   Sun  Dinner     2
1         10.34  1.66    Male     No   Sun  Dinner     3
2         21.01  3.50    Male     No   Sun  Dinner     3
3         23.68  3.31    Male     No   Sun  Dinner     2
4         24.59  3.61  Female     No   Sun  Dinner     4
5         25.29  4.71    Male     No   Sun  Dinner     4
6          8.77  2.00    Male     No   Sun  Dinner     2
7         26.88  3.12    Male     No   Sun  Dinner     4
8         15.04  1.96    Male     No   Sun  Dinner     2
9         14.78  3.23    Male     No   Sun  Dinner     2
10        10.27  1.71    Male     No   Sun  Dinner     2
11        35.26  5.00  Female     No   Sun  Dinner     4
12        15.42  1.57    Male     No   Sun  Dinner     2
13        18.43  3.00    Male     No   Sun  Dinner     4
14        14.83  3.02  Female     No   Sun  Dinner     2
15        21.58  3.92    Male     No   Sun  Dinner     2
16        10.33  1.67  Female     No   Sun  Dinner     3
17        16.29  3.71    Male     No   Sun  Dinner     3
18        16.97  3.50  Female     No   Sun  Dinner     3
19        20.65  3.35    Male     No   Sat  Dinner     3
20        17.92  4.08    Male     No   Sat  Dinner     2
21        20.29  2.75  Female     No   Sat  Dinner     2
22        15.77  2.23  Female     No   Sat  Dinner     2
23        39.42  7.58    Male     No   Sat  Dinner     4
24        19.82  3.18    Male     No   Sat  Dinner     2
25        17.81  2.34    Male     No   Sat  Dinner     4
26        13.37  2.00    Male     No   Sat  Dinner     2
27        12.69  2.00    Male     No   Sat  Dinner     2
28        21.70  4.30    Male     No   Sat  Dinner     2
29        19.65  3.00  Female     No   Sat  Dinner     2
..          ...   ...     ...    ...   ...     ...   ...
214       28.17  6.50  Female    Yes   Sat  Dinner     3
215       12.90  1.10  Female    Yes   Sat  Dinner     2
216       28.15  3.00    Male    Yes   Sat  Dinner     5
217       11.59  1.50    Male    Yes   Sat  Dinner     2
218        7.74  1.44    Male    Yes   Sat  Dinner     2
219       30.14  3.09  Female    Yes   Sat  Dinner     4
220       12.16  2.20    Male    Yes   Fri   Lunch     2
221       13.42  3.48  Female    Yes   Fri   Lunch     2
222        8.58  1.92    Male    Yes   Fri   Lunch     1
223       15.98  3.00  Female     No   Fri   Lunch     3
224       13.42  1.58    Male    Yes   Fri   Lunch     2
225       16.27  2.50  Female    Yes   Fri   Lunch     2
226       10.09  2.00  Female    Yes   Fri   Lunch     2
227       20.45  3.00    Male     No   Sat  Dinner     4
228       13.28  2.72    Male     No   Sat  Dinner     2
229       22.12  2.88  Female    Yes   Sat  Dinner     2
230       24.01  2.00    Male    Yes   Sat  Dinner     4
231       15.69  3.00    Male    Yes   Sat  Dinner     3
232       11.61  3.39    Male     No   Sat  Dinner     2
233       10.77  1.47    Male     No   Sat  Dinner     2
234       15.53  3.00    Male    Yes   Sat  Dinner     2
235       10.07  1.25    Male     No   Sat  Dinner     2
236       12.60  1.00    Male    Yes   Sat  Dinner     2
237       32.83  1.17    Male    Yes   Sat  Dinner     2
238       35.83  4.67  Female     No   Sat  Dinner     3
239       29.03  5.92    Male     No   Sat  Dinner     3
240       27.18  2.00  Female    Yes   Sat  Dinner     2
241       22.67  2.00    Male    Yes   Sat  Dinner     2
242       17.82  1.75    Male     No   Sat  Dinner     2
243       18.78  3.00  Female     No  Thur  Dinner     2

[244 rows x 7 columns]


繪出資料分佈/密布圖:

sns1

繪出資料筆數:

sns2

繪出 log(1+x) 的效果(以 tip 為例):

sns3

兩兩欄位計算相關性,並繪出分佈圖:

sns4

程式碼:

import matplotlib.pyplot as plt
import seaborn as sns

tips = sns.load_dataset("tips")
print(tips)

# https://seaborn.pydata.org/generated/seaborn.violinplot.html
plt.figure('tips / total_bill')
plt.subplot(1,2,1)
sns.violinplot(data=tips, x='tip')

plt.subplot(1,2,2)
sns.violinplot(data=tips, x='total_bill')

# https://seaborn.pydata.org/generated/seaborn.countplot.html
plt.figure('sex / smoker')
plt.subplot(1,2,1)
sns.countplot(data=tips,x='sex')
plt.subplot(1,2,2)
sns.countplot(data=tips,x='smoker')

import numpy as np

plt.figure('log(1+x)')

# https://docs.scipy.org/doc/numpy/reference/generated/numpy.log1p.html
tips['tip'] = np.log1p(tips['tip'])
sns.violinplot(data=tips, x='tip')

# https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.corr.html
data_corr = tips.corr()
print(data_corr)

threshold = 0.5
corr_list = []
size = data_corr.shape[0]
for i in range(0,size):
for j in range(i+1,size):
if (data_corr.iloc[i,j] >= threshold and data_corr.iloc[i,j] < 1) or (data_corr.iloc[i,j] < 0 and data_corr.iloc[i,j] <= -threshold):
corr_list.append([data_corr.iloc[i,j],i,j])

s_corr_list = sorted(corr_list,key=lambda x: -abs(x[0]))
print(s_corr_list)

cols=data_corr.columns
for v,i,j in s_corr_list:
print ("%s and %s = %.2f" % (cols[i],cols[j],v))
sns.pairplot(tips, size=6, x_vars=cols[i],y_vars=cols[j] )

plt.show()


最後,有時資料內有垃圾需要清除後在繪圖,可以善用 pandas.DataFrame.dropna 來過濾資料:

# https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.dropna.html
# 繪圖時,刪除 nan 的資料
newdata = rawdata.dropna(axis=0)
sns.violinplot(newdata)

# https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.drop.html
# 繪圖時,只關注特定範圍的資料(此例:總額 < 20 都刪除不繪)

newdata = tips.drop(tips[ tips.total_bill < 20 ].index)
sns.violinplot(data=newdata, x='tip')

2017年8月26日 星期六

真愛每一天 = 過好每一天



最近工作上的事物又忙碌了起來,許多事交錯著,不只有 coding 的步調,還有跨單位的以及人性管理課題。此外,正逢台灣承辦世大運,社會新聞一樣熱熱鬧鬧著。

年過三十後,對於時間有種恐懼和焦慮感,很貴很貴,卻也無法改變什麼 XD 最近也小玩一下數據分析,跑個 SVC 就搞了很久,你瞧,機器不夠力導致時間成本又被耗光了?看著小自己快十歲的年輕人努力打拼著,又感受到身不由己的包袱了

今晚又看了 about time 休息了一會兒,看著片尾的點滴和配樂,好像又想透了什麼了。真愛每一天,其實就只要過好每一天,好好體驗每一天。看著劇中片段,靜下來唸一本書,讀讀典故,不為了爭論什麼。想起大一上沒帶電腦時,桌上擺了一本高中同學送的小書:溫一壺月光下酒,何時才會有閒再重新翻起這類說集呢?

把時間省下吧,別再爭論八卦;把時間省下吧,好好陪伴家人;把時間省下吧,去執行在意的事。永遠花在自己的人生步調上,肯定不留白的。

2017年8月19日 星期六

[Python] 機器學習筆記 - 透過 sklearn.svm 簡易的數據分析、機器學習萬用框架 @ macOS 10.12, Python36

svm_report

幾年前有幸參加過數據分析的黑客松,但是太耍廢了 XD 當下只用統計硬幹。最近有些閒情想好好認識一下 SVM 了。目前使用它的方式很粗淺 XD 就是把一堆 feature 湊個成 array 餵進去跑,接著就有報表可以看了(當初還人工去計算 precision / recall),回想起來真是青春啊

回過頭來,程式架構如下:

import numpy as np
import pandas as pd # 假設 input 是 csv 格式

# 讀取資料中
raw = pd.read_csv("input.csv")
# 可以得知有多少欄位可以用
print(raw.columns)

# 假設所有屬性都是可以有一對一的對應,全部把他們取代成整數,此為 HASH table 用來轉換而已
LOOK_FIELD = {}

# 假設 raw 有一萬筆資料
USE_DATA_COUNT = 10000 # or raw.size

# 將 raw 資料建置成 numpy array 架構

data_input = None
data_output = None

for index, row in raw.iterrows():

data_per_row = np.empty([])

# 將有興趣的欄位(feature)抽出來使用
for field_name in [
"csv_fieldname1",
"csv_fieldname2",
]:
field_data = np.zeros(1, dtype=np.int)
if field_name not in LOOK_FIELD:
LOOK_FIELD[field_name] = {}
if row[field_name] in LOOK_FIELD[field_name]:
field_data[0] = LOOK_FIELD[field_name][row[field_name]]
else:
field_data[0] = len(LOOK_FIELD[field_name])
LOOK_FIELD[field_name][row[field_name]] = field_data[0]
data_per_row = np.append(data_per_row, field_data.reshape(1, -1))

if data_input is None:
data_input = np.zeros([USE_DATA_COUNT, data_per_row.reshape(1, -1).size], dtype=np.float)
data_input[index] = data_per_row.reshape(1, -1)


result = np.zeros([1], dtype=np.int)

output_field_name = "csv_fieldname3"

# 將 結果 的欄位轉換成數值
if output_field_name not in LOOK_FIELD:
LOOK_FIELD[output_field_name] = {}
if row[output_field_name] in LOOK_FIELD[output_field_name]:
result[0] = LOOK_FIELD[output_field_name][ row[output_field_name] ]
else:
result[0] = len(LOOK_FIELD[output_field_name])
LOOK_FIELD[output_field_name][ row[output_field_name] ] = result[0]

if data_output is None:
data_output = np.zeros([USE_DATA_COUNT, result.reshape(1, ).size], dtype=np.int)
data_output[index] = result.reshape(1, )

# 支援只使用 USE_DATA_COUNT 筆資料
if index >= USE_DATA_COUNT - 1:
break

print(data_input)
print(data_output)
print(data_input.shape)
print(data_output.shape)

from sklearn import svm, metrics

classifier = svm.SVC()

# 使用 1/5 的資料來訓練
number_of_data_to_learn = int(USE_DATA_COUNT / 5) # or int(data_output.size/5)

# start to learn
classifier.fit(data_input[:number_of_data_to_learn], data_output[:number_of_data_to_learn])

# get the result
expected = data_output[number_of_data_to_learn:]
predicted = classifier.predict(data_input[number_of_data_to_learn:])

# get the report
print("Classification report for classifier %s:\n%s\n" % (classifier, metrics.classification_report(expected, predicted)))
print("Confusion matrix:\n%s" % metrics.confusion_matrix(expected, predicted))


透過上述的程式架構,未來就只要把資料轉成 csv ,挑挑 feature (csv_fieldname1, csv_fieldname2) 跟 output (csv_fieldname3) 欄位就可以快速看到成果了 XD 要唬人也可以 3 分鐘就弄出點東西。

2017年8月16日 星期三

[Python] 機器學習筆記 - 使用 matplotlib.pyplot 快速上手繪圖方式 @ macOS 10.11, 10.12 / Python36

最近想嘗試分析資料,看了一堆教學文都會看到繪圖的函式,如 plt.subplot, plt.plot, plt.show 等等,花了一點時間看看別人的範例,終於看懂了 XD 其實也沒有太複雜。

畫一個正弦波:

x=0-5,y=sin(x)

import numpy as np
import matplotlib.pyplot as plt

x = np.arange(0, 5, 0.1);
y = np.sin(x)
plt.plot(x, y)
plt.show()


其中 np.arange 的參數是 x 介於 0 ~ 5 ,並且以 0.1 間隔產生,所以真正產生的數字序列為:

[ 0.   0.1  0.2  0.3  0.4  0.5  0.6  0.7  0.8  0.9  1.   1.1  1.2  1.3  1.4
  1.5  1.6  1.7  1.8  1.9  2.   2.1  2.2  2.3  2.4  2.5  2.6  2.7  2.8  2.9
  3.   3.1  3.2  3.3  3.4  3.5  3.6  3.7  3.8  3.9  4.   4.1  4.2  4.3  4.4
  4.5  4.6  4.7  4.8  4.9]


接著,在定義 y 的數值是怎樣產生,就用 plt.plot 把它畫出跟展現出來。

同理,簡單畫出個 y = 2x + 3, -2 <= x <= 10

x=-2-10,y=2x+3

import numpy as np
import matplotlib.pyplot as plt

x = np.arange(-2, 10, 0.1);
y = x * 2 + 3
plt.plot(x, y)

plt.show()


以上就快速明瞭畫圖的方式了,接著談談一張圖多個小圖,或是一口氣產生多個圖的方式。

對於一張圖有多個小圖的部分,會使用到 plt.subplot ,他吃的參數有 3 欄,也有人喜歡給一個三個位數的整數給他:

plt.subplot( x, y, z ) 或 plt.subplot(xyz)

其中 x 代表 row,y 代表 column 而 z 代表 location (顯示位置)

例如,想要做出一張有三個小圖的圖表,就可以用 row=1, column=3,接著開始作畫:

draw3

import numpy as np
import matplotlib.pyplot as plt

# draw 1 picture
plt.subplot(1,3,1)

x = np.arange(0, 5, 0.1);
y = np.sin(x)
plt.plot(x, y)

# draw 2 picture
plt.subplot(1,3,2)

x = np.arange(-2, 10, 0.1);
y = x * 2 + 3
plt.plot(x, y)

# draw 3 picture
plt.subplot(1,3,3)

x = np.arange(-5, 5, 0.1)
y = np.tan(x)
plt.plot(x, y)

plt.show()


如何在程式內一次畫多張畫布,就是用 plt.figure 來處理了

import numpy as np
import matplotlib.pyplot as plt

# draw 1 picture
plt.figure(1)

x = np.arange(0, 5, 0.1);
y = np.sin(x)
plt.plot(x, y)

# draw 2 picture
plt.figure(2)

x = np.arange(-2, 10, 0.1);
y = x * 2 + 3
plt.plot(x, y)

# draw 3 picture
plt.figure(3)

x = np.arange(-5, 5, 0.1)
y = np.tan(x)
plt.plot(x, y)

plt.show()


結果就會產出三張圖。

最後,談談環境架設的部分,這次分別在 macOS 10.11 跟 macOS 10.12 嘗試過,兩邊都分別用 MacPorts 安裝 python 3.6 和 pip 套件,並從 XQuartz 網站下載視窗軟體。

連續動作:

1. 安裝 https://dl.bintray.com/xquartz/downloads/XQuartz-2.7.11.dmg 後重開機(很重要 XD)
2. 安裝 py36-pip py36-virtualenv 和 matplotlib 繪圖需要的函式庫 py36-tkinter
3. 使用 virtualenv 建置環境,並把缺的 py36-tkinter library 移入使用
4. 安裝 matplotlib、numpy 等常用工具
5. 收工,可以把玩繪圖了

$ sudo port install py36-pip py36-virtualenv py36-tkinter
$ virtualenv study
$ source study/bin/activate
$ cd study/lib/python3.6/site-packages
$ ln -s /opt/local/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/_tkinter.cpython-36m-darwin.so  .
$ cd -
$ vim ~/.matplotlib/matplotlibrc
backend: Tkagg
(study) $ pip install matplotlib
(study) $ python draw.py


錯誤處理:

import _tkinter # If this fails your Python may not be configured for Tk
ModuleNotFoundError: No module named '_tkinter'

$ sudo port install py36-tkinter
$ cd path-virtual-env-project/lib/python3.6/site-packages
$ ln -s /opt/local/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/_tkinter.cpython-36m-darwin.so  .


參考資料:

1. https://matplotlib.org/users/pyplot_tutorial.html
2. https://matplotlib.org/users/customizing.html

2017年7月17日 星期一

[Book] 你要如何衡量你的人生

之前被強者大大的塗鴉牆吸引到,仔細找了書來翻。有興趣的可以參考以下影片:




這些影片都提到了書中內容,有興趣可以看看(甚至快轉兩倍速節省時間也行),我覺得比較好玩的是這是以“企業管理”的角度去思索人生規劃,當我跟其他強者大大哈拉時,他們立馬就說:有沒有用“軟工”衡量人生 XD 接著就蹦出一堆搞笑的話題,例如可以 waterfall 嗎、可以 CI/CD 嗎?

回過頭來,每個人在不一樣的心境,看著一樣的文字,的確還是會有不一樣的想法跟心得,有人覺得囧星人已經總結了很多關鍵概念,不需在翻書,我自己看完書的心得,大概就像知道 QuickSort 跟實際在寫一次還是很不一樣的,可能碰到 bug 而多想了很事多練了一些經驗值。這次閱讀有不少共鳴是從工作上來的,包含企業經營的策略,卻又有一大卡車的領悟是跟小孩互動學習。

例如提到小時候要多跟小孩講話(字彙),雖然小孩不是很懂,但長期下來接受到的字彙刺激會很驚人的,讓人想起 Deep learning 的相關用詞:神經元連結。永遠都不要小看那一點點的刺激,就可能讓小孩 Machine Learning 系統有了更新穎的連結。

不知是不是年紀已到?累積一些工作經驗後,覺得這本書很對味,適合閒暇時翻翻,回顧一下工作上的營運策略,並想想人生在意的事,並付出行動去改變吧!

2017年6月30日 星期五

[BOOK] 创新工场CEO训练营教材:创业就是要细分垄断

创业就是要细分垄断

很久沒翻書了,一年可能翻不超過 3 本吧?! 這本書在五月中就看到的,那也沒特別有興趣,直到前陣子重感冒後,人厭厭的,只好找點東西刺激一下,開始亂找書來翻,於是乎就從天貓下單了,買了本實體書,不貴,才賣 25 RMB,貴反而在於台灣的快遞(22 RMB),結果在中國境內就等了 5 天在運輸,拿到書都 7 天了,早知道慢是慢在中國境內,就都改用集運了(11 RMB),整本書才 163 頁。

這本書的封面最大咖的是 李开复 ,在台灣的社群網站上褒貶不一,我以為會講很多,但只有一個章節,才 30頁(還有幾頁後記)。但開頭第一章說得不錯,提提人才的重要,也鼓勵老闆要分享股份,你是想當個 1000萬美元等級持有 60% 股份呢,還是 10億美金等級持有 20% 股份?所以介紹這本書給自己的老闆,可能也有點暗示要多給點股份吧 *誤*

接著則是 汪华(谷歌中国商务发展总部) 和 傅盛(360/可牛/金山/獵豹移動) 交錯的分享,講得自己的臉好像打了好幾次 XD 這本書醒腦的地方就是這兩位分享的論點,也代表身在台灣很難看清中國市場,更別說台灣幾乎進入已開發中的國家,各行各業的領頭羊早已建制公會築起堡壘,更別說巷弄超商已涵蓋了九成以上的生活必需服務。

我認為比較重要的是提及在一個想做的事業上,要選擇一個長遠有大需求市場的紅海,但起步是一個夠小夠養活自己的藍海,類似寫專利時,要有個 domain 足以讓專利申請通過 XDD 接著面對既有市場時,則是要想想自己提供的改善是否有五倍、十倍的效益,例如比既有產業多 5 倍的效率,單純一兩倍的效益可能不足以撼動原產業,當事業在 A 輪尾聲發現成長無法持續時,就該軸轉了,也代表很多公司無法募到 B 輪,實在是成長停滯了,未來越來越不光明。

這些思維很不錯,的確是要做大事的方向,透過這本書了解 VC 的思維,也間接讓自己的策略改觀,除此之外,這些思維站在找工作上也很受用的,也可用在評鑑公司的健康情況。

例如A輪尾之前,要確認事業營運是否持續高成長,像是營業額或是服務使用人數等,用這個概念去看台灣相關的新創,大概可以找到 cloudmosa 是很健康的,整個團隊人數不大(<30人),用戶數年年翻倍,更別說在 remote browser 的領域上也堪稱第一,而公司的大戰場就是更廣域的 mobile browser 領域,一堆科技文在評比 mobile browser 時,肯定都會提提 Puffin Web Browser。

整本書我都在享受 汪华 和 傅盛 提到的策略,雖然越看越有動力,但那些策略就像企業管理一樣,沒那個環境,就沒使出的機會,只能先醒腦一下,在看看 side project 可以在找哪些來試試了。此外,這本書尾聲還提及台灣人創業習慣(還用中國台灣這字眼)跟趨勢科技營運策略(當作傳產)打臉一下 XD 或許,這大概這本書沒翻成繁體進入台灣市場的主因吧?!

有興趣可以翻 Amazon CN 電子書 或是試試 天貓 吧,另外,百度閱讀 也可以線上試讀第一章。