おれの技術日記

元はJava+SQLがメインのエンジニア、フロントエンドは軽くかじった程度で苦手。最近忘れっぽいので覚えたことをいろいろメモするためにブログ開始。

Machene Learning Crash Course 2 - Descending into ML

教師あり学習(Supervised ML)においてモデルを作るというと、そのモデルは
 y' = b + w_1x_1 + w_2x_2 + w_3x_3 + ・・・ + w_nx_n
という式で表される(nはfeatureの数)ので、

  • モデルに含めるfeatureを決定する
  • トレーニングデータ(labeled examples)を使ってw(weight)とb(bias)を決定する

というステップになる。例えば前回の気温とアイスクリームの売れ行きの例で言えば、featureは気温のみなので
 y' = b + w_1x_1
となる。

そして、このモデルと実際のデータの乖離をloss(損失)という。下図で矢印の大きさがloss(すいませんこのimage、本家からお借りしました)。左のモデルよりも右のモデルのほうが損失が少ないのでよりよいモデルであるといえる。
f:id:kuniaki12:20180710162314p:plain


モデルの損失を最小化する手法は損失関数と呼ばれ(多分)、いろいろな種類があるけれどここでは二乗損失(squared less, L2 loss)がチラッと紹介されている。この損失関数は平均二乗誤差(mean square error, MSE)を最小化するようにするということなんだろう、

 MSE = \frac{1}{N} \sum_{(x,y)\in D} (y - prediction(x))^2

という禍々しい数式が書いてあるけれど、これはよく見ると単純に
featureの各値についてモデルで算出されたpredictionと実際の値の差分を二乗して、その平均を算出するというだけの話だった。

ちなみにこの方式で計算すると、前回のアイスクリームの売上と気温の関係式は
y' = 1.217582418x1 + 30.16923077
というモデルが構築できた。モデル構築というと物々しいけれど、単純にExcelの関数でちょいちょい計算しただけ(汗)。

Machine Learning Crash Course 1 - Framing

一念発起して、Googleが無償公開しているMachine Learning Crash Courseに挑戦しつつその記録をここに残すことにした。
英語で学ぶのは億劫ではあるものの、仕事の半分くらいは結局英語なのでMLに関しても英語で説明・議論できなければ意味がないので、その意味ではこの領域の英語も合わせて学べる点で一石二鳥(やる気さえ続けば)。

というわけで、まずは一番最初の章、Framingから。
ここは簡単な用語の説明がほとんどなので簡単。

  • Label:yと呼ばれる、予測対象の値
  • Feature:x1-xn、入力変数。
  • Example:DBで言うところのレコード、前はsampleって聞いたような気がしたけど・・・
    • Labeled example:labelとfeatureの両方を持ったexample、機械学習ではトレーニングデータとも呼ばれる
    • Unlabeled example:featureのみを持ったexample、このレコードに関してlabelはモデルを使って予測される
  • Model:labelとfeatureの関連を定義したもの
    • Training:モデルを構築すること
    • Inference:モデルを使ってlabelを予測すること、ちなみに予測されたlabelはトレーニングデータに含まれるlabelと区別するためにy'と表記される
  • Regression Model:回帰モデル、連続した値を予測するのに使用される
  • Classification Model:分類モデル、非連続の値を予測するのに使用される

ちなみにトレーニングデータ(labeled example)を用いてモデルを構築し、それをもとに未知のlabelを予測する仕組みをsupervised ML(教師あり学習)という。

例えば気温とアイスクリームの売れ行きの関連を表現するとして、高校で習ったy=ax+bみたいな式を考えた時に(実際はy'=b + wxと書く)過去のトレンドからだいたい以下のような図を見いだせたとすると、
f:id:kuniaki12:20180710170901p:plain

  • Label:アイスクリームの売れ行き
  • Feature:気温
  • Labeled example:個々の青い点
  • Unlabeled example:未来の売上(例えば明日は25度だから60個くらい売れそうだな、みたいな)
  • Model:回帰直線

となる。

このあたりの用語は数学と統計と機械学習で微妙に異なっていて、更に英語となるとややこしいけど、まあ慣れかな。

おまけ、データはこんな感じ。

気温 売上
18 53
19 51
20 52
21 56
22 58
23 58
24 61
25 63
26 63
27 63
28 63
29 68
30 67
31 64

Gmailでメールの振り分けを設定する方法

Outlookに慣れた人がGmailを仕事で使おうとすると戸惑う場合が多いと思う。なんせフォルダという概念がない。代わりにラベルというものがあるようなんだけれど「ラベル?おれはそういうのじゃなくてフォルダ分けしたいだけなんだけど」と思ったりする。ラベルとフォルダの違いは、ひとつのメールに対してラベルなら複数設定ができるがひとつのメールは複数のフォルダに所属できないということだ。

自分の場合はOutlookでは基本的なこんな感じで振り分けを行う。

  • XXXプロジェクトのメーリングリストあては「XXXプロジェクト」というフォルダに
  • 日本チーム全体のメールは「日本チーム」というフォルダに
  • システムから自動配信されるエラー通知メールなんかはエラー通知に
  • YYYサービスのメルマガとかほぼゴミみたいなメールは「ゴミ箱一歩手前」に

すると受信トレイに残ったものは従来のルールに当てはまらないものなので必ず目を通すべきものとなるので、受信トレイは常に空であるのが正常という状態を作る。(「to me」みたいな自分宛てのフォルダを作ることはしない)

この、「どのフォルダにも属さないメールのみを一覧表示する」というのがGmailでできなくて悩ましかった点なのだが、ようやくやり方を発見したので記録しておく。

1.ラベルの自動付加を設定する

まずは各メールにラベルが適切に付加されるよう設定をする。ラベルをつけたいメールを開いて右上のメニューから「メールの自動振り分け設定」を選択する。
f:id:kuniaki12:20180105134955p:plain:w300
その上で、ポップアップ下部の「この検索条件でフィルタを設定」をクリックし、
f:id:kuniaki12:20180105135214p:plain:w300
上から5番目にある「ラベルを付ける」にチェックを入れて最下部の「フィルタを作成」をクリックする。
f:id:kuniaki12:20180105135312p:plain:w300

2.マルチ受信トレイを有効化する

右上ギアメニューより「設定」>「Labs」を選択すると中段あたりに「マルチ受信トレイ」というものがあるので、これを有効化して「変更を保存」ボタンをクリックする。
f:id:kuniaki12:20180105135909p:plain:w400

次に右上ギアメニューより「設定」>「マルチ受信トレイ」を選び、パネル0の検索キーワードに"has:nouserlabels"と入れる。(パネル1の値は消してしまってOK)
f:id:kuniaki12:20180105140409p:plain:w400

3.振り分けルールを管理する

振り分けルールを追加しすぎてよくわからなくなってきた場合は右上ギアメニューより「設定」>「フィルタとブロック中のアドレス」を見ることで一覧で確認できる。


これで、一番上にはなんのラベルもついていないメールのみが表示される。(≒Outlookで言うところの受信フォルダ)
ちなみにこの"has:nouserlabels"というのはGmailがサポートしている種々の検索クエリのうちの一つで、それ以外にも以下のリンクのように数多くのクエリが存在するのでこれを使いこなすとかなり便利そうではある。
support.google.com

あと、フィルタとか振り分けとかUI上で用語が異なるのがちょっと気になる。

バスケット分析(併売分析)のためのSQL

いわゆる売上トランザクションテーブル、こんなデータから

OrderID Date ProductID ・・・
0000001 2017-07-01 A001 ・・・
0000001 2017-07-01 A002 ・・・
0000002 2017-07-02 A003 ・・・
0000003 2017-07-02 A001 ・・・
0000003 2017-07-02 A002 ・・・
0000003 2017-07-02 A004 ・・・

ある商品を買った人がほかの商品をどれだけ併売しているかを調べるためのこんなテーブルを作る。

YearMonth MainProduct SubProduct # of Purchase
2017-07 A001 A002 2
2017-07 A001 A004 1
2017-07 A002 A001 2
2017-07 A004 A001 1


こんな感じでSQLを書く。書いてしまうと普通のクエリなんだけど、これがひらめいたときは結構感動した。

SELECT
   date_format(a.Date, '%Y-%m') YearMonth
   ,a.ProductID MainProduct
   ,b.ProductID SubProduct
   ,count(*) `# of Purchase`
FROM
   Sales_T a
   ,Sales_T b 
WHERE
   a.OrderID = b.OrderID 
   and a.ProductID <> b.ProductID
GROUP BY
   date_format(a.Date, '%Y-%m')
   ,a.ProductID
   ,b.ProductID

月次累計を求めるSQL

こんなデータがあるときに

Date Sales
2017-07-01 110
2017-07-02 120
2017-07-03 130
・・・ ・・・
2017-07-31 410
2017-08-01 100
2017-08-02 110

ひとつカラムを追加して、月次累計(Monthly Running Total)を求めたい場合に

Date Sales MRT_Sales
2017-07-01 110 110
2017-07-02 120 230
2017-07-02 130 360
・・・ ・・・ ・・・
2017-07-31 410 8060
2017-08-01 100 100
2017-08-02 110 210


こんな感じでSQLを書く。ここで、GROUP BY 1,2はGROUP BY t1.Date,t1.Salesと同じ意味。

SELECT
   t1.Date
   ,t1.Sales
   ,sum(t2.Sales) RunningTotal_Sales
FROM 
   Daily_Sales t1
   ,Daily_Sales t2 
WHERE
   year(t1.Date)=year(t2.Date) 
   AND month(t1.Date)=month(t2.Date) 
   AND t1.Date >= t2.Date
GROUP BY
   1,2


ここでなぜsum(t2.Sales)が月次累計になるかというと、以下のように集計を外したSQLを書くと

SELECT
   t1.Date
   ,t1.Sales
   ,t2.Sales t2_Sales
FROM 
   Daily_Sales t1
   ,Daily_Sales t2 
WHERE
   year(t1.Date)=year(t2.Date) 
   AND month(t1.Date)=month(t2.Date) 
   AND t1.Date >= t2.Date

こんな感じの結果セットが取得できる。(1日目は1レコード、2日目は2レコード、・・・31日目は31レコード)

Date Sales t2_Sales
2017-07-01 110 110
2017-07-02 120 110 ←7/1の値
2017-07-02 120 120
2017-07-03 130 110 ←7/1の値
2017-07-03 130 120 ←7/2の値
2017-07-03 130 130
・・・ ・・・ ・・・
2017-08-01 100 100
2017-08-02 110 100 ←8/1の値
2017-08-02 110 110

これに対してsum(t2.Sales) GROUP BY t1.Date,t1.Salesをするので、月次累計となる。
もし日ごと製品カテゴリごとの売上というテーブルがあった場合は、同様にSELECT句・WHERE句・GROUP BY句に製品カテゴリを含めればOK。

MySQLでカレンダーテーブルを作る

自分がよく使うSQLをライブラリ的にここに残しておく。

こんな感じでSQLを書くと

SELECT
   @date:=date(date_format(CURRENT_DATE(),'%Y-%m-01')) as Date
UNION ALL
SELECT
   @date:=DATE_ADD(@date, INTERVAL 1 DAY)
FROM
   `テーブル名`
WHERE
   @date < last_day(CURRENT_DATE())

こんな感じで結果セットが取れる。

Date
2017-08-01
2017-08-02
・・・
2017-08-31

この時、SQLの中で参照するテーブルはどんな構造になっていてもかまわないけれど、ここで取得する日付範囲よりも多いレコードを持っている必要がある。
例えば上の例でいえば`テーブル名`は30レコード以上持っている必要がある。

JavaScript再入門4 - call, apply, bind

JavaScript再入門3 - thisは何を意味するか - おれの技術日記の続き。

例によってこちらにお世話になります。
最強オブジェクト指向言語 JavaScript 再入門!

7. call/applyを使ってthisをコントロールしつつ関数をコールする。

前回のブログの中で、thisというのはコンテキストによって可変で関数実行時に決定されるということがわかった。
このthisをコントロールしつつ関数を呼び出すのがcallとapply。この二つは記述方式こそ違うものの、やっていることは本質的に同じ。
・関数内でのthisが何か
・呼び出すときの引数は何か
というのを指定しつつその関数を実行する。

callとapplyは以下のようなシンタックスで使用される。ここでobjectは関数内でのthisにあたり、arg1以降は関数を呼び出す時に渡される引数となる。call関数は可変長引数であるのに対して、apply関数はArrayを第二引数にとる。

call(object, arg1 ,arg2...)
apply(object, [arg1,arg2,...])

例えば以下のようなコードの場合、func1は3回呼び出され全く同じ結果となる。

function func1(){
    console.log("123");
}
func1();//123が表示される
func1.call();//123が表示される
func1.apply();//123が表示される

以下のようなコードの場合、最初のコールではthisがwindowオブジェクトになってしまうがwindowオブジェクトにname属性は存在しないためthis.nameは空になる(undefinedにならないのはなんでだろう・・・)。一方でcall・applyを通じてコールする時に引数にobjを渡しているので、this=objとなりname属性の値がログ出力される。

function func1(){
	console.log("log:" + this.name);
}
var obj = {
	name:"obj_name",
}
func1();//log:だけがコンソール出力される
func1.call(obj);//log:obj_nameがコンソール出力される
func1.apply(obj);//log:obj_nameがコンソール出力される

これはfunc1に引数が存在する場合の呼び出し方。callでもapplyでも、可変長引数なのか配列を渡すのかの違いだけで結果は一緒。

function func1(param1, param2){
	console.log("log:" + this.name + ":" + param1 + param2);
}
var obj = {
	name:"obj_name",
}
func1.call(obj, "this is ", "test");//log:obj_name:this is testがコンソール出力される
func1.apply(obj, ["this is ", "test"]);//log:obj_name:this is testがコンソール出力される

8. bindを使って関数内のthisを固定化する。

通常は関数とオブジェクトには何の関連もなく、関数が呼び出されたときにその状況によってthisの値が決定される。しかしbind関数を使えば実際の関数呼び出しの前にthisを固定化することができる。

以下のコードではfunc2はobjとbindされているためthisの値が存在するが、func1はbindされていないのでthis.nameが空となる。

function func1(param1, param2){
	console.log("log:" + this.name + ":" + param1 + param2);
}
var obj = {
	name:"obj_name",
}

var func2 = func1.bind(obj);
func2("this is ", "test");//log:obj_name:this is testがコンソール出力される
func1("this is ", "test");//log::this is testがコンソール出力される

という仕様は理解したものの、実際どういうところで使うんだろう・・・・大規模なJavaScriptでの開発とかやってみたい。

依然として今後の課題

  1. Apply関数って何?→thisをコントロールするためのものらしい
  2. (function() {})();とかいう記法→即時関数というらしい
  3. thisのスコープの考え方