出たとこデータサイエンス

アラサーでデータサイエンティストになったエンジニアが、覚えたことを書きなぐるためのブログ

「全ての知識を20字でまとめる」書評 アウトプットの時間が取れないと悩む人必読の書

「学習したことをアウトプットする癖をつければ、吸収は圧倒的に早くなる」ということは、技術職なら必ず一度は聞いたことがあるだろう。
しかし、アウトプットの有効性について理解していても、実際にはその通りにできず、インプットのみの学習に留まってしまうと感じている人は多いのではないだろうか。私もその一人だ。
今日はそのような悩みを抱える人の助けになれば良いと思い、「全ての知識を20字でまとめる」という本を紹介したい。

アウトプットするのはしんどい

世の戦闘力の高い方々はアウトプットしろアウトプットしろと言うが、そもそもインプットするだけでも大変なことだ。
会社から帰ってきて、酒を呑むのもtwitterをダラダラ眺めるのも我慢し、本を開き、疲れた脳に新しい事柄を叩き込むのは、プロとして飯を食うなら当たり前のことであっても、決して楽なことではない。
これに加えて、その内容をアウトプットするとなると、負荷は飛躍的に増大する。
「そんな時間や労力を、いったいどうしたら捻出できるのだろう?」と首を傾げながら、結局インプット偏重型の学習から脱することができない人は多いのではないだろうか。
私もこのようにブログを持っているが、更新頻度は低く、アウトプットといえばtwitterで気づきをツイートする程度に留まってしまっている。

アウトプットに時間がかかる理由

そもそもアウトプットにはなぜ時間がかかるのか。自分が業務で分析の報告資料を作成している時のことを思い出すと、自分が知っていることを他者が理解可能なように整理する作業に殆どの時間が費やされていたことに気づく。
文章や資料を作成した経験のある人なら誰しも理解していることだが、成果物を簡潔にしようとすればするほど、その整理にはより時間がかかる。
しかし時間が惜しいからと自分の頭を整理するプロセスをいい加減にすると、実作業は道標のない山道を登るがごとく迷走し、より多くの時間が失われてしまう(最悪、完成しない)。
結局アウトプット高速化の肝は、知識を整理するプロセスをいかに妥当かつ素早く行うか、という点にかかっている。

「全ての知識を20字でまとめる」

素早く知識を整理し、できるだけ少ない時間でアウトプットする術を身に着けたい。
そのようなモノグサ心課題意識を持って手にとったのが、この「全ての知識を20字でまとめる」だ。
twitterの140字制限にいつも苦しんでいる身からすると、20字という短さは驚愕だ。
しかしこの本には、様々な知識を20字でまとめるためのフレームワークが記されている
試しに、この本の内容を自分で20字程度でまとめてみると、次のようになる。

具体的問題を念頭に置けば知識の再構成は容易(21字)

20字といいつつ21字になってしまっているが、3字までは誤差として許されるようだ。
著者曰く、このような簡潔な一文を量産するスキルを磨けば、学習が定着しやすくなり、他者にとって有益なアウトプットも出せるようになるのだと言う。

3つの疑問詞を意識しながら学習する

では、どのような切り口で攻めればこのような簡潔なまとめができるようになるのだろうか?
この本では、WHY、WHAT、HOWという3つの疑問詞を意識することが鍵だと言う。
トヨタに勤めていた著者は、説明力の高いデキル上司を観察するうちに、「『分かる』とは3つの疑問詞に答えられること」だという結論を得たとのことだ。

WHY なぜ学ぶ必要があるのか?

学習者は、学習を始める前に「この学習を行うのは、どのような問題を解決するためなのか」ということを強く意識しなければならない。
何となく本を買い、何となく読み、何となく満足する。このような学習は「消費型」の学習であり、得るものは少ないと筆者は指摘する。
逆に、特定の問題を意識して本を読むことで、必要のない情報をバッサリ切り落とすことができ、20字という非常に小さな枠へとまとめることができるようになる。

WHAT 何を学んだのか?

学習の目的を明瞭に定めたら、次はその問題の解決策を本文から探る。
問題発生の背景にある真因は何なのか、それを解消する方法は何なのか。目的を念頭に置くことで、枝葉末節にとらわれずに必要な部分だけを吸収することができる。

HOW どのように活かすのか?

学習し終えたら、その学習内容を「WHY」で定めた問題に対してどのように活かしていくかを考える。
この際、抽象的な表現ではなく、動作を規定するような具体的な言葉として書き出すことが重要だという。

「すべての知識を20字でまとめる」を20字でまとめる

試しにこの本の示す方法に従って、この本自身を20字でまとめる作業を行ってみた。 この本では学習事項をまとめるためのフレームワークがいくつか示されているが、重要なポイントは下記の2つだ。

  • WHY、WHAT、HOWの3つの疑問詞に対し、それぞれ2,3程度の回答を書く
  • 上記の回答を元に、学んだことを20字で要約する

Markdownでまとめると下記のようになる。

  • 3つの疑問詞
    • WHY?
      • 学んだことを高速にアウトプットしたい
      • 高速なアウトプットのために、知識を素早く整理する術を身に着けたい
    • WHAT?
      • 具体的な問題に適用すれば、知識を再構成することは容易である
      • 学ぶ目的(WHY)を設定し、その問題設定に従って内容(WHAT)を把握し、自分の具体的な動作(HOW)に落とし込むことが重要
    • HOW?
      • 本を読んだ際は、「3つの疑問詞」「20字まとめ」の2つのアウトプットを実践する
      • 上記のアウトプットを種として、ブログに書評を書く
  • 20字でまとめると?
    • 具体的問題を念頭に置けば知識の再構成は容易

個人的な感想

20字は、実際に書いてみると分かるがあまりに短い(世界一短い詩の形式である俳句は17字)。
逆に言うと、この短さにまとめることができるということは、学習対象のエッセンスを抽出できているということが言えると思う。
また、3つの疑問詞に従って情報を整理するという手法自体は、業務で報告書を作る時等に常に意識していたことだったので意外性はなかったが、日々の学習全てに対してそのフレームワークを適用するという発想はなかった。
時間がかかりがちな知識の再整理という作業を高速で行うためのヒントは、多く得られたと感じる。

実際に20字まとめをベースにしてこの記事を書いたが、今までよりも執筆中に書き方に悩む時間はかなり減った。
(その割に自分で読んでいてこの記事の質が高くないなと思うのは、「WHY?」の掘り下げがいまいち浅かったからだと思う)
今後もこの20字まとめを実践し、インプット→アウトプットのサイクルを高速にしていきたい。

SQLのウィンドウ関数で粒度の異なるグルーピングを同時に行う

背景

SQLで分析をしていると、粒度の異なるグルーピングを同時に行いたくなる場合がある。
例えば何らかの割合を出すために「分子はカラムAとカラムBでGROUP BYしてSUM、分母はカラムBだけでGROUP BYしてSUMしたい」という場合がある。
このような場合に、教科書どおりにGROUP BY句を使うだけではうまくいかないことが多い。
この記事では、ウィンドウ関数を用いてそのような集計をパパっと行う方法を問題形式で学ぶ。

問題

設定

あなたはとあるデジタルコンテンツ販売企業のアナリストであり、このたびマンガ部門の分析を受け持つことになった。
この部門では、将来的にユーザーごとにパーソナライズされたオススメ機能を実装することを考えている
しかし今はまだそのリソースがないため、当面は「性別ごとに人気のあるカテゴリをメールでオススメする」というアナログな手法を採用することになった。

分析に使えるテーブルは販売テーブル(sales)、ユーザーマスタ(users)、商品マスタ(items)の3つで、ER図は下記の通りだ。

f:id:mizuwan:20181226082136p:plain


また、サンプルデータはGithubにあげておいたので、データに対して実際にクエリを叩きたい方はこちらをお好きなDBに入れてご利用いただきたい。

問題1

上記のような状況下で、マネージャーから下記のように依頼された。

  • 性別ごとのカテゴリ別構成比を出してほしい
  • 計算方法としては、性別ごとに総販売個数に占める各カテゴリの販売個数の割合を算出してほしい
  • 成果物は、SQLで算出した下図左のような表と、それを元にした右図のようなグラフ

f:id:mizuwan:20181225215124p:plain

それでは、早速集計してみよう。
正しいクエリが書ければ、上記の表と同じ結果が返ってくるはずだ。

マネージャーの言った「性別ごとのカテゴリ別販売構成比」の定義は、下記のようになるだろう。

{ \displaystyle
性別ごとのカテゴリ別販売構成比 = \frac{当該性別の当該カテゴリの販売個数}{当該性別の販売個数}
}

ここで、分母と分子とで集計の粒度が異なることが分かるだろうか。分子は「性別+カテゴリ」で、分母は「性別」で、それぞれグルーピングする必要がある。
GROUP BYに指定できるカラムの組み合わせは一つなので、教科書どおりにGROUP BY句を使用しても、このような集計は行うことができない。
では、どうするか。やり方にはいくつか考えられるが、最も簡潔なのがウィンドウ関数を使う方法だ。

-- 性別ごとの販売構成比
SELECT DISTINCT
   sex
   , category
   , COUNT(sales_id) OVER(PARTITION BY sex, category)
       / COUNT(sales_id) OVER(PARTITION BY sex) AS category_rate
FROM
   ${sales_table}
   INNER JOIN ${user_table}    USING(user_id)
   INNER JOIN ${item_table}    USING(item_id)
ORDER BY
   sex
   , category
;

このクエリのポイントは3つある。

  1. GROUP BY句を使わず、ウィンドウ関数を使う
  2. ウィンドウ関数のOVER句にはORDER BYを書かない
  3. SELECT句にDISTINCTを書く

一つ一つ解説する。

1. GROUP BY句を使わず、ウィンドウ関数を使う

GROUP BY句はグルーピングの粒度を1つしか指定できないが、ウィンドウ関数は一つのSELECT句の中に異なる粒度のグルーピングを共存させることができる(PARTITION BYで指定するカラムを変えれば良い)。
こうすることで、分母と分子で異なる粒度で集計をすることができる。

2. ウィンドウ関数のOVER句にはORDER BYを書かない

通常、ウィンドウ関数は累積和等の順序の概念のある計算を行うために利用される。
しかし、ウィンドウ関数のOVER句にORDER BYを書かないと、通常のGROUP BY句と同様に順序を意識しない計算をしてくれる。
つまり、次の2つのSQLは等価になる。

  • ウィンドウ関数を使う場合
SELECT DISTINCT
    user_id
    , COUNT(sales_id) OVER(PARTITION BY user_id)
FROM
    sales
;
  • 通常のGROUP BY句を利用する場合
SELECT
    user_id
    , COUNT(sales_id)
FROM
    sales
GROUP BY
    user_id
;

3. SELECT句にDISTINCTを書く

ウィンドウ関数はGROUP BY句と異なり、レコードの集約をしない。
そのため、DISTINCTをつけないと元のレコード数と同じレコード数の結果が返ってきて冗長になる。

問題2

それでは、異なる粒度のグルーピングをもう1問やってみよう。
問題1で計算した「性別ごとのカテゴリ別販売構成比」の表とグラフを再掲すると、下記のようになる。

f:id:mizuwan:20181225215124p:plain

これを見ると、男性でも女性でも少女漫画が一番人気という風に見える。
ここで「なるほど、最近の男性の間では少女漫画が流行っているんだな」などと早合点してはいけない。
直観に反する結果が出た時は、必ずドリルダウンして本当にそうなっているのかを検証するべきだ。
確認が不十分なまま報告を行うと、後で恥をかくことも多い。
試しに、性別ごとの販売個数から一歩ドリルダウンし、ユーザーごとの販売個数を男性に関して見てみよう。

  • クエリ
-- 男性のカテゴリ別販売構成比
SELECT
   user_id
   , category
   , COUNT(sales_id) AS sales_cnt
FROM
   sales
   INNER JOIN users  USING(user_id)
   INNER JOIN items  USING(item_id)
WHERE
   sex = ‘男’
GROUP BY
   user_id
   , category
ORDER BY
   user_id
   , category
;
  • 結果 f:id:mizuwan:20181225215137p:plain

これを見ると、ユーザーu00001だけが猛烈な勢いで少女漫画を読んでいることが分かる。
そもそも他のユーザーがたかだかマンガを3~4冊しか購入しないのに対し、このユーザーだけ7冊も買っているので、集計結果に対して与える影響は極めて大きい。
この外れ値のせいで、危うく「少女漫画が男性に人気」という誤った解釈を出すところだった。
この例は極端だが、一部の並外れた行動をするユーザーによって全体の集計結果が引っ張られるというのは、分析をしているとよく起こることだ。

このことを踏まえると、現状の定義は性別ごとの各カテゴリの人気を図る指標としては不十分であると言える。
購入数の多い一部ユーザーに引っ張られないために、「ユーザーごとに販売構成比を出してから、その値を性別ごとに平均する」というアプローチを取ることにしよう。
イメージとしては、下図のようになる(値はダミー)。

f:id:mizuwan:20181226080212p:plain

例えば男性における少年漫画の販売構成比を出したい場合、男性の少年漫画の面積(青色部分)の総和を、男性ユーザーの棒グラフの総面積で割れば良い
このような方法なら、個人ごとの分母の差に結果が引きずられない。

さて、このような結果を出すクエリを書くにはどうすれば良いだろうか?
先に答えだけ掲載すると、下記のようになる。

f:id:mizuwan:20181226081555p:plain

実際に集計をしてみよう。
「ユーザーごとの集計」「性別ごとの集計」という2段階の集計を経る必要があるので、WITH句を使って段階的にクエリを書くのが良いだろう。
それぞれの段階において、異なる粒度のグルーピングのテクニックを使うことになる。
解答は下記の通りだ。

-- 性別ごとのカテゴリ別販売構成比
WITH

-- ユーザーごとの販売構成比
category_rate_per_user as (
   SELECT DISTINCT
       user_id
       , sex
       , category
       , COUNT(sales_id) OVER(PARTITION BY user_id, category)
           / COUNT(sales_id) OVER(PARTITION BY user_id) AS category_rate
   FROM
       sales
       INNER JOIN users   USING(user_id)
       INNER JOIN items    USING(item_id)
)

-- 性別ごとに平均
, category_rate_avg as (
   SELECT DISTINCT
       sex
       , category
       , SUM(category_rate) OVER(PARTITION BY sex, category)
           / SUM(category_rate) OVER(PARTITION BY sex) as category_rate
    FROM category_rate_per_user
    ORDER BY
        sex
        , category
)

SELECT
    *
FROM
    category_rate_avg
;

このようにウィンドウ関数をグルーピングに使うと集計の幅が広がるので、ぜひ実践してみていただきたい。

BigQueryの分割テーブルをちょっとだけ完全に理解する

新しい会社に来て初めてGoogle BigQueryに触っているので、新しく学んだ概念や機能を備忘録として記していきたい。
今日のテーマは分割テーブル。

分割テーブをざっくり要約すると……

  • 分割テーブルは、巨大なテーブルに対するクエリのパフォーマンスを上げる手段
  • 分割テーブルは、見た目上は一つのテーブルだが、実際にはレコードの日付(時間)によって複数の場所に分散して格納されている
  • 分割テーブルを作るには、通常のテーブル作成手順+αでOK
  • 分割テーブルにクエリを投げる際、日付(時間)で絞り込みをかけることでクエリのパフォーマンスが向上する

分割テーブルの概要

分割テーブルを使う目的

BigQueryにクエリを投げる際、意図した集計結果が素早く返ってくることは非常に重要だ。
いくらビッグデータに特化したBigQueryといえども、レコードが数億、数十億と蓄積されていくと、やはりパフォーマンスは悪化していく。
特にアプリの行動ログやサイトの閲覧ログは途轍もないデータ量のことが多いので、愚直に集計するとエディタの前で結果を待つ時間が長くなり、分析にスピード感が出ない。

クエリが遅くなる原因の一つは、テーブルが巨大すぎてスキャンに時間がかかるところにある。
実際に必要なデータが1ヶ月分だとしても、期間の始まりと終わりを特定するためには余計なレコードまで舐める必要があり、スキャンにかかる時間はテーブルのレコード量に依存してしまう。

f:id:mizuwan:20181216162946p:plain

分割テーブルの仕組み

上記の問題を解決(または緩和)する手段の一つが、分割テーブルだ。
分割テーブルは、見かけ上は一つのテーブルとして扱うことができる。
しかし実際には、レコードは日付ごとに別々の場所(これをパーティションという)に格納されている。
こうすることで、WHERE句で日付で絞り込んだ際に、スキャン範囲を特定のパーティションに限定することが可能になり、結果的にクエリが速くなる。
分析では普通「昨日のアプリ起動数」や「先月のコンバージョン数」のように集計期間を限定するので、特定期間のパーティションだけをスキャンするこの仕様は、短気な分析者にとっては非常に恩恵が大きい。

f:id:mizuwan:20181216162918p:plain

分割テーブルの作り方

では早速、分割テーブルを作ってみる。
今回は例として下記のような簡単な構造を持った購買ログを分割テーブルとして作ってみたい。

salesテーブル

カラム名 説明
id STRING 売上一つごとに一意なID
user_id STRING 購買者のID
item_id STRING 商品のID
sales_time TIMESTAMP 購入時刻

分割テーブルを作る際に決めなければならないのは、「何を基準にパーティションを分割するか」だ。
具体的には

  1. レコード挿入日で分割する
  2. TIMESTAMP型またはDATE型のカラムで分割する

という2つの方法から選ぶことになる。
売上がリアルタイムでテーブルに書き込まれる前提ならレコード挿入日で分割すれば良いし、一度バッチを噛ませてから翌日に一気にレコードが挿入される等、ビジネス上意味のある時間とレコード挿入時間にギャップが生じる場合はカラムで分割する形の方が良いだろう。
大事なのは「集計クエリのWHERE句で範囲を絞り込む時に使用する日付を指定する」こと。

Web UIでテーブルを作る際は、次図のように「No Partition」と書かれたドロップダウンリストをクリック。
その上で、レコード挿入日で分割する際は「Partition by ingestion time」を、カラムで分割する場合は「Partition by field」以下のカラム名をクリックすればよい。

f:id:mizuwan:20181216163028p:plain

また分割テーブルを作成する際にオプションで、クエリでWHERE句で日付の絞り込みを行うことを強制することができる。
せっかく分割テーブルを作っても日付の絞り込みをしなければ結局レコードを片っ端から読みに行ってしまうので、分割テーブルの利点を活かすためにもチェックをつけておくと良いだろう
(余談だが、Treasure Dataではtime列での絞り込みをかけないと警告が出る仕様になっていた。)
方法は下記の通り、「Requiring partition filter」チェックをつけるだけ。

f:id:mizuwan:20181216163047p:plain

分割テーブルへのクエリの書き方

分割テーブルにクエリを投げる際に気をつけるべき点は1点だけで、日付による絞り込みを必ず行うことである。
テーブル作成時にレコード挿入日での分割を選択した場合は、_PARTITIONDATEという隠れカラムが生成されているので、このカラムで絞り込みを行う。

select
    id
    , user_id
    , item_id
    , sales_time
from
    sales
where
    _PARTITIONDATE between '2018-01-01' and '2018-01-31' 
;

テーブル作成時にカラムでの分割を選択した場合は、単にそのカラムをWHERE句に書けばよい。

select
    id
    , user_id
    , item_id
    , sales_time
from
    sales
where
    sales_time >= '2018-01-01'
    and sales_time < '2018-02-01'
;

なお、絞り込みに使うカラム(上記でいう_PARITIONDATEsales_time)をWHERE句に書く場合、比較演算子の左側にはそのカラムを単独で書かなければ分割テーブルの恩恵が得られないので注意。
ここらへんの詳しい仕様は公式ドキュメントを参照のこと。

物体検知系のネットワークの解説リンクまとめ

物体検知問題を解くための様々なネットワークについて調べていたので、備忘録も兼ねて参考になったリンクをまとめておく。

全般

下記の記事に目を通しておけば、たいていのネットワークの知識は網羅できる。

Deep Learningによる一般物体検出アルゴリズムの紹介

各ネットワークの参考リンク

R-CNN

  • 2012年のILSVRCで圧勝したモデル
  • 物体の候補の選定、CNNによる画像特徴量の抽出、SVMによる分類という3つのモデルを複合している
  • 選定した候補ごとにCNNにかけるので、非常に重い
  • モデルそれぞれ別々に学習しなければならず、チューニングしづらい

R-CNNの原理とここ数年の流れ

SPPNet

  • 選定した候補ごとにCNNにかける代わりに、1回の畳込みで可変の領域の特徴量を抽出する
  • 特徴量抽出まではR-CNNに比べて非常に速くなったが、相変わらず一つのネットワークではないためチューニングしづらい

物体検出の実装を目指す-SPPnetについて

Fast R-CNN

  • RoI Poolingという、多様なサイズの畳込みを行うプーリング操作を行い、領域候補の特徴量抽出を高速化
  • バウンディングボックスと分類のそれぞれを同時に学習することで、領域候補提示より後の一つのネットワークでの学習を可能とした

論文紹介: Fast R-CNN&Faster R-CNN
what is ROI layer in fast rcnn?

Faster R-CNN

  • 領域候補の提案をRPN(Region Proposal Network)というネットワークで実現し、全ての層を一つのネットワークにすることができた

私がわかりにくいと思った「Faster RCNN」のポイントの解説
論文紹介: Fast R-CNN&Faster R-CNN

YOLO

  • 画像をセルに分割し、セルごとにバウンディングボックスの提案を行う
  • 各セルに対し、各クラスである条件付き確率を割り当てる

オブジェクト検出 YOLO YOLO — You only look once, real time object detection explained

SSD

  • サイズの異なるfeature mapを多数抽出し、それぞれにバウンディングボックスを提案させる

リアルタイム物体検出向けニューラルネット、SSD(Single Shot Multi Detector)及びその派生モデルの解説

RetinaNet

  • Negativeだとすぐ分かるようなバウンディングボックスに小さな重みをつけるような損失関数を使うことで、価値ある教師データでのみ学習できるようになり、学習効率が向上

[DL輪読会]Focal Loss for Dense Object Detection

CNN系ネットワーク(AlexNet,VGG,ResNet等)の解説リンクまとめ

画像分類問題を解くためのCNN系の様々なネットワークについて調べていたので、備忘録も兼ねて参考になったリンクをまとめておく。

全般

下記2つの記事に目を通しておけば、たいていのネットワークの知識は網羅できる。
特に前者は記述量に圧倒される。一読の価値あり。
後者はあっさりめなので、さっと軽い気持ちで読める。

畳み込みニューラルネットワークの最新研究動向 (〜2017)
Neural Network Architectures

各ネットワークの参考リンク

LeNet5

  • 1944年にYann LeCunが発表した最初のCNN
  • 畳み込みのフィルタをパラメータとして学習させるというCNNのアイディアはここで生まれた

定番のConvolutional Neural Networkをゼロから理解する
Convolutional Neural Networkとは何なのか

AlexNet

  • 2012年にAlex Krizhevskyが発表したLeNetを大幅に改良したネットワーク
  • ImageNetの画像分類コンテストで2位以下に大差をつけて圧勝し、深層学習が画像認識に有効であることを初めて広く世に知らしめた
  • 活性化関数にReLU関数を利用したり、Dropoutにより過学習の抑制したりといった、後世でも利用される手法を生み出した

Understanding Alexnet

VGG

  • オックスフォード大学が発表したモデル
  • 9x9や11x11のサイズのフィルターを使用する代わりに、3x3のフィルターを何層にも積み重ねて使用

http://cedro3.com/ai/mini-vgg-net/

Network-in-network

  • 線形な畳み込み層の代わりに小さなニューラルネットワーク(MLPConvと呼ぶ)を挿入することで、非線形な特徴を抽出しやすくした
  • 出力層にて、過学習しやすい全結合層の代わりに、クラス数と同じチャンネル数の層を置き、各チャンネルの平均をもって各クラスのスコアとする形とした

Network In Network architecture: The beginning of Inception
Network In Networkを動かしてみた

GoogLeNet

  • 色々なサイズのフィルタをパラレルに適用し、大きなフィルタと同等の精度を得るInceptionという考え方を踏襲
  • 各フィルタに入力を手渡す前に1x1畳み込みで次元削減している

GoogleNet

ResNet

  • Microsoftが2015年に発表
  • 層を深くしすぎると起きる勾配消失問題を、Shortut Connection(重み層の後の活性化関数に、重み層を経る前の値も一緒に入力)という考え方で解決した
    [Residual Network(ResNet)の理解とチューニングのベストプラクティス]

(https://deepage.net/deep_learning/2016/11/30/resnet.html)

Squeeze Net

  • 同じ精度のAlexNetに比べて3倍高速で500倍省メモリ
  • Fire Moduleという、1x1の畳み込み→活性化関数→1x1の畳み込みor3x3の畳み込み→活性化関数という一連の流れを、何層にも積み重ねたもの

Notes on SqueezeNet

「はじめよう位相空間」感想 ー 挫折しない位相空間の学び方

情報幾何を勉強するための前準備として位相空間を学び直していたところ、「はじめよう位相空間」という痒いところに手が届く教科書に巡り会えたので、紹介する。
学生の頃は位相空間に手も足も出なかった私でも大まかなイメージを掴むことができたので、位相空間をこれから勉強する人、一度挫折した人には非常にオススメ。

はじめよう位相空間

はじめよう位相空間

位相空間は何故学びづらいのか

位相空間論は、専門的な数学を勉強する上で必須の知識にもかかわらず、抽象的で挫折率が高い科目として悪名高い。
学生の頃の私も、位相空間の定義を見るたびに教科書をそっ閉じしていたクチで、殆ど何も理解できなかった。

位相空間論の困難さの原因は色々とあるのだが、私は次の2つが特に大きいと思う。

  1. 連続写像同相写像のイメージが湧きづらい
  2. 距離空間位相空間のギャップが甚だしい

1. 連続写像同相写像のイメージが湧きづらい

位相空間というのは、かいつまんで言うと連続写像同相写像の定義できる空間のことだ。最初に連続写像同相写像のイメージを持っておくことは、後の学習で大いに助けになる
にもかかわらず、連続写像同相写像の厳密な定義は、予備知識なしにそれだけ読んでも到底イメージが湧きづらい
連続や同相のイメージなしに記号の渦の中を突き進んでも、いったいそれが何を表象しているのか見当がつかないため、途中で挫折する羽目になる。

2. 距離空間位相空間のギャップが甚だしい

距離空間の定義は、「距離」という日常に根ざした概念に依拠しているため、ある程度直感的に把握しやすい。
しかし位相空間の定義となると、「開集合族」というなんだかよく得体の知れないフワフワしたもので記述されており、お化けを相手にしているかのような不気味さがある。
この「距離」と「開集合族」という2つの概念の乖離のために、位相空間がこの世のものではないかのように感じてしまって学習に身が入らない。

「はじめよう位相空間」は上記の問題にいかに応えているか

「はじめよう位相空間」は、上記の2つの問題に見事に応えている。

1. 連続写像同相写像のイメージが明瞭

この本では全編を通じて写像を図形の変換に対応させて説明しており、連続写像同相写像は次のように紹介される

  • 連続写像とは、図形を破らない変換
  • 同相写像とは、図形を破ったり貼り付けたりしない変換

この図形的なイメージのおかげで、読者は「連続or同相とは何か」という抽象的な問いを「図形を破るor貼り付けるとは何か」という具体的な問に置き換えて読むことができるようになっており、迷子にならずに済む

2. 距離空間から位相空間へのシームレスな接続

連続・同相の定義は、距離空間では比較的直観的に定義することができる。
これは、距離という概念が我々にとって馴染みがありイメージがつきやすいからだと思う。
しかし実は、連続・同相の概念は、直接的に距離を使わなくても記述することができる(具体的には「同相写像とは、開集合を開集合に移す写像」という形で書ける)
この「距離から開集合へ」というパラダイムシフトこそ、位相空間を理解する上での最大のキモかつ難所だと思うのだが、この本ではそのギャップを埋めるのに2章を費やしており、説明も丁寧だ。
位相空間を開集合族の振る舞いによって定義する方法が、非常に自然なものであることが理解できるよう、最大限の配慮がなされていると思う。

読む上での留意点

予備知識

この本を読む上で必要な知識は殆どないが、ε-δ論法による連続関数の定義を復習しておくとスムーズに読める。

読み進め方

時間があれば1ページずつ丹念に読めばよいと思うが、私は手っ取り早く大要をつかみたかったので、問題や定理の証明は全てすっ飛ばして1日で読んだ
そういう雑な読み方をしても、本文が充実しているために全体のストーリーは十分に理解できた。
2週目は、きちんと落ち着いて1行ずつ咀嚼していきたい。

発展的な学習

本文にも書いてあるが、この本は位相空間の詳細な性質を追うよりは直観的なイメージを掴むところに重きを置いているため、この本をマスターしたら別の本格的な教科書(松坂本とか内田本とか)を読むことが必要と思われる。

感想

苦手意識の強かった集合位相論に対し、何とか取っ組める自信がついてきたので、いずれは多様体微分幾何、最終的には情報幾何を勉強していきたい。

はじめよう位相空間

はじめよう位相空間

TensorFlowでRNN(LSTM)実装

初めてRNN(LSTM)を実装したので備忘録として。

目標

※ LSTMの理論的説明はこちらを御覧ください。

方針

  • MNISTの各画像を、上から1行ずつスキャンし、時系列データとしてLSTMに入力
  • LSTMの最後の中間層の次の全結合層を出力層とする

コード

from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf

#mnistデータを格納したオブジェクトを呼び出す
mnist = input_data.read_data_sets("../data/mnist",
                                  one_hot=True)

def seq_mnist():
    
    # ログのリスト
    summaries = []
    
    # データのサイズ
    num_image = 784 # 画像のピクセル数
    num_class = 10  # 正解のクラス数
    num_input = 28  # RNNの入力長
    num_seq = 28    # RNNの時間長

    # データの定義
    with tf.name_scope("data"):
        
        # 入力画像
        x = tf.placeholder(tf.float32,
                           [None, num_image])
        
        # 正解ラベル
        y = tf.placeholder(tf.float32,
                           [None, num_class])

    # LSTM
    with tf.name_scope("lstm"):
        
        # 画像を1行ずつ読み込みよう整形
        input = tf.reshape(x,
                           [-1, num_seq, num_input])
        
        # ユニット数128個のLSTMセル
        stacked_cells = [tf.nn.rnn_cell.LSTMCell(num_units=128) for _ in range(2)]
        cell = tf.nn.rnn_cell.MultiRNNCell(cells=stacked_cells)
        out_lstm, states = tf.nn.dynamic_rnn(cell=cell,
                                             inputs=input,
                                             dtype=tf.float32)
        
        # 最後の層を取得
        out_lstm_list = tf.unstack(out_lstm,
                                   axis=1)
        lstm_last = out_lstm_list[-1]
        
    # 出力層
    with tf.name_scope("out"):
        
        # 出力層定義
        w = tf.Variable(tf.truncated_normal([128, 10],
                                            stddev=0.1))
        b = tf.Variable(tf.zeros([10]))
        out = tf.nn.softmax(tf.matmul(lstm_last, w) + b)
        
    # 訓練
    with tf.name_scope("train"):
        
        # 誤差
        loss = tf.reduce_mean(-tf.reduce_sum(y * tf.log(out),
                                             axis=[1]))
        
        # ログ出力
        summaries.append(tf.summary.scalar("loss",
                                           loss))        
        
        # 訓練
        train_step = tf.train.GradientDescentOptimizer(0.1).minimize(loss)
        
        # 評価
    with tf.name_scope("evaluation"):

        # 正解率
        correct = tf.equal(tf.argmax(out, 1),
                           tf.argmax(y, 1))
        accuracy = tf.reduce_mean(tf.cast(correct,
                                          tf.float32))
        
        # ログ出力
        summaries.append(tf.summary.scalar("accuracy",
                                           accuracy))        
        
    # 初期化
    init = tf.global_variables_initializer()
    
    with tf.Session() as sess:
        
        # 初期化
        sess.run(init)
        
        # ログをひとまとめにする設定
        summary_op = tf.summary.merge(summaries)

        # ログの保管場所
        summary_writer = tf.summary.FileWriter("../logs",
                                               sess.graph)        
        
        # テストデータ
        test_images, test_labels = mnist.test.images, mnist.test.labels
        
        # 学習
        for i in range(1000):
            step = i + 1
            train_images, train_labels = mnist.train.next_batch(50)
            sess.run(train_step,
                     feed_dict={x: train_images,
                                y: train_labels})
            
            # 定期的に正解率確認
            if step % 100 == 0:
                
                # ログのテキスト取得
                summary_text, acc_val = sess.run([summary_op, accuracy],
                                                 feed_dict={x: test_images,
                                                           y: test_labels})

                # ログに書き出し
                summary_writer.add_summary(summary_text,
                                           step)                
                
                # 正解率表示
                acc_val = sess.run(accuracy,
                                   feed_dict={x: train_images,
                                              y: train_labels})
                print("step: {} acc: {:.3}".format(step,
                                                   acc_val))
                
seq_mnist()

TensorBoardの出力

Accuracy

f:id:mizuwan:20180702221759p:plain

Loss

f:id:mizuwan:20180702221808p:plain

グラフ

f:id:mizuwan:20180702221825p:plain

TensorFlowの関数を使うと非常に短いコードで実装できるが、中で何が起きているかはよく分からない。