; oi

2017年8月31日木曜日

AI×データ時代に必要な「知覚」能力を補う方法

今更ではあるが、安宅和人氏の著した以下の記事を拝読した。安宅氏の経験に基づいた幅広い視野と深い思考・洞察があり、大変参考になる良記事であった。本記事のみのPDFであれば、800円程度で購入できるので、人工知能やデータ分析に携わる方はもちろん、そうでなく今後の未来予測に関心がある幅広い方々も、是非一度ご覧になっていただきたい。

知性の核心は知覚にある
~AI×データ時代に人間が生み出す価値とは~
(2017年5月号 特集 知性を問う)
http://www.dhbr.net/articles/-/4784

本ポストでは、この安宅氏の記事の内容を自分の言葉でまとめると共に、私が本記事を拝読して考えたことを後半に備忘録として追記しておく。本記事を読むことで、近年流行ってきた様々な手法の意味が再整理できたように思う。

「知覚」は重要である

これからは、幅広く目を向け、得られた情報を統合して、情報の意味合いを理解し(=「知覚」)、解釈した内容を言語として表現できることが重要な意味を持つ。

ますます世の中の問題は複雑化していき、それを解くための方法はより一層高度化し、多様化していく。このような状況下では、解くべき問題を定義する力と、解決手法を領域横断的に組合せる力が必要となる。そして、不完全な情報が複雑に絡み合う状態から、取り組むべき課題を見極め、答えを出すべき問いを定義するという極めて高度な情報処理においても、問いに対して様々な領域の知識や知見を総動員して取捨選択し、意味のある組み合わせを見つけ、革新的な解を出すという情報処理においても、「知覚」する能力は必要不可欠となってくるのだ。

なぜ「知覚」が重要なのだろうか

では、なぜ「知覚」が必要不可欠であり、より重要となってくるのだろうか。
その理由としては、情報を統合して意味合いを理解する能力は、これまでの思考・経験に依存する部分が大きいことが挙げられる。得られた情報のみを基に考える場合、情報が不完全なことが多いため、情報が不足し論理的に導かれる帰結は少ない。よって、これまで知り得た暗黙的なルールや別の前提を踏まえて、情報を補完しながら意味合いを理解することでしか、結論が見出せないことが多くなってくる。

「知覚」の能力を身につけるためには

よって、様々なことを経験し、知覚を鍛えるトレーニングをした方が良い。具体的には、複雑な情報の要素を見極め、性質を理解し、要素間の関係性を把握することである。この訓練は、既知の慣れ親しんだ環境下では難しく、全く新しい環境下において新しい経験を行う際により鍛えられると思われる。その状況を正しく理解しないと適切な行動をとれず、生きていけないからだ。

「知覚」能力を補填するためには

以下は本記事には全く書かれていない内容であり私の考えに過ぎないが、より効率的に「知覚」する方法、「知覚」能力を補填する手段もあるように思う。

たとえば、個人レベルでは、既存のモデル(ことわざや物理モデルなど)を対象にあてはめ、類似点や相違点を認識する、アナロジー(類推)思考を行うことで、意味合いの理解や解釈、次の結果まで予測できる場合がある。また、複雑かつ不完全な情報で構成される対象をシステムと捉え、構造的に理解するシステムズアプローチと呼ばれる方法も、要素の洗い出しや、要素間の関係性の整理に役立ち、情報の意味合いを把握、表現、共有することが容易になる。

一方で、チームレベルでは、デザイン思考ベースのワークショップなどで、他者の知覚情報を上手に取り入れ、チームとしての「知覚」能力を高めることも考えられる。

しかしながら、このようなアプローチのベースには、やはり原体験・経験に基づく「知覚」が重要な意味を持つ。また、これらのアプローチの結果を解釈する上でも、高度な認識能力が要求されることを忘れてはいけない。

2017年7月17日月曜日

今後必要となる量子コンピュータ(量子アニーリング方式)に関する周辺知識まとめ

「量子コンピュータが人工知能を加速する」を読んだ内容と周辺知識を整理した。
近年、注目を浴びている量子コンピュータの実情と今後に興味のある方には、必読の一冊と思う。近いうちにちゃんと勉強する必要が出てくるであろう。


本書は、量子コンピュータの基礎原理の一つである「量子アニーリング」方式の理論を提唱した、西森教授、大関准教授による共著の一冊となっている。

量子アニーリング方式の量子コンピュータは、以前は研究の本流であった量子ゲート方式に比べ、今現在とてつもない脚光を浴びている。

理由を平たくまとめると、解ける問題の幅は狭いが、近年重要な人工知能を下支えする重要な問題に対して適用可能で、従来のコンピュータに比べ、超高速(1億倍)かつ低エネルギー(スパコン京の500分の1)で、比較的安定的に解くことができるのだ。

量子アニーリングとは、組み合わせ最適化問題を解く場合に、量子効果を用いる方法である。まず、目的関数をイジングモデル(2値変数とその関係性の関数)として表現する。この2値変数が上下の向きを持つスピンに相当し、それらが格子状の構造を持っているモデルである。格子状につながっている変数同士は同じ値を保持したほうが、エネルギーが低く安定した状態となる。
次に、このイジングモデルに対して、横磁場をかけることで、スピンを上か下の決まった向きではなく、上下の両方を重ね合わせた状態で持つことが可能となる。これを格子状のすべての変数に対して重ね合わせた状態で持つことで、あらゆる解候補をすべて重ね合わせた状態で持つことに相当する(下図左)。
その後、だんだんと横磁場を弱めていく(上か下かをフィックスさせていく)と同時に、格子状の関係性(相互作用)を強くすることで、重ね合わせの確率分布が変化(下図中央)し、同最適化問題の解を得ることができるのである(下図右)。

Fig.量子アニーリングの概要 (横軸は2値変数の組の取るいろいろな値の組(古典状態),縦軸の黒の曲線は目的関数の値,青の線は各配位の存在確率を表す。)引用元:西森教授の量子アニーリング説明ページ

簡単に、量子アニーリング方式と量子ゲート方式を比較すると以下のような違いとなる。
  • 量子アニーリング方式
    • 組合せ最適化問題に特化
    • 安定:エネルギーが低い状態のみを保持すれば良いため
    • ※カナダのベンチャー起業のD−Wave社が実装した量子コンピュータは同方式
  • 量子ゲート方式 
    • 理論的には汎用的に利用可能
    • 不安定:重ね合わせの状態を保持する必要があるため
詳細は、著者の西森教授による量子アニーリング説明ページをご覧いただきたい。
量子アニーリングの論文やイジングモデルの具体的な定式化例の説明がある。
加えて、下記2つの記事は包括的に量子アニーリングについて説明している
大変有益な記事であった。
  1. 「量子コンピュータが人工知能を加速する」を読んで、数式を使わずにPythonでその概要を説明してみた
    http://qiita.com/onhrs/items/aa0aa181c27743956689
  2. 物理のいらない量子アニーリング入門
    http://blog.brainpad.co.jp/entry/2017/04/20/160000
現実の組み合わせ最適化問題を量子アニーリング方式で扱うためには、イジングモデルという形式に落とし込む必要がある。イジングモデルについては、上記記事にも説明が書かれているが、より直感的な理解を求める方は、以下2つを参照されたい。
  1. Ising Modelを平易に解説してみる
    http://enakai00.hatenablog.com/entry/20150106/1420538321
  2. イジングモデル - KnowledgeBase - 岡山大学理論化学研究室
    http://theochem.chem.okayama-u.ac.jp/wiki/wiki.cgi/exp11?page=%A5%A4%A5%B8%A5%F3%A5%B0%A5%E2%A5%C7%A5%EB
また、私が驚いたのは、D−Wave社のような量子コンピュータのハードウェア企業に加えて、「組み合わせ最適化問題」を量子アニーリング方式で扱う「イジングモデル(量子ビットとその相互作用の組み合わせ)」に変換するのに特化したソフトウェア企業が現れているというのも驚きである。
彼らはhardware-agnostic platforms and services(ハードウェア非依存の基盤とサービス)を適用している。今後、D−Wave社以外の量子アニーリング方式のハードウェアが現れてきても、1QBit社のイジングモデルへの記述方法のナレッジは適用可能である。また、日本独自の量子コンピュータと称される、国立情報学研究所が開発中の「レーザーネットワーク方式」の量子コンピュータもイジングモデルを採用しているため、イジングモデルで記述した問題はそのまま解くことができる。今後、ますます重要な役割を担うであろう。

最後に、現状の日本の量子コンピュータに関する取り組みについては、以下を参照されたい。
  1. 日本独自の量子コンピュータ
    http://itpro.nikkeibp.co.jp/article/COLUMN/20140314/543707/?rt=nocnt
  2. 人工知能に必要な「量子コンピュータ」とは|日本の取り組みと必要スキル
    https://furien.jp/columns/267/
雑多ではあるが、本書を読んで調べた周辺知識のまとめは以上である。
今後も、量子コンピュータ界隈の動向から目を話せないことは間違いない。

2017年6月17日土曜日

今更聞けないデータ分析による因果関係を示し方〜いかにして因果関係を示すか?〜

本書の概要

データ分析やデータサイエンス領域が重要視されて久しいが、「今あるデータから何が言えるのか」「あることを証明するためには、どのようなデータをどのようにとれば良いのか」を十分に理解して分析できる人材は多くない。大量データから単なる平均や分散などの基本的な統計量を算出することや、流行りの機械学習にとりあえず食わせる作業をデータ分析と言っている人も多い。

本書には、政策設計や制度設計における意思決定のためのエビデンスとして、データから因果関係を示すためのデータ分析手法およびその考え方が記されている。データ分析を実施したことがない方や、とりあえずExcelやAccessで分析したことはあるけれど、科学的に因果関係を立証したことはない方などには最高の入門書となるだろう。ただし、本書はあくまで因果関係の考え方に主眼を置いているため、基本的な統計の知識の説明や機械学習などの説明はほとんど含まれない。

また、本書の特徴としては、本編には数式は登場せず、数式アレルギーの方でもすらすら読めるようになっているので、安心されたい。発展的な内容や数式については、後半に良書が紹介されているので、より深い勉強をされたい方はそちらを参照すると良いであろう。

Kindle版も存在する。

因果関係を示す分析手法

本書で紹介されている因果関係を示す分析手法の概要と弱み・強みを備忘のために記しておく。

  • RCT(ランダム化比較試験)
    • 因果関係をデータ分析によって明らかにする最良の方法
    • 介入グループと比較グループを用意し、介入グループにのみ介入を行い、加入後の両グループの差を平均介入効果=因果関係と見なす。
    • 介入がなかった場合、介入グループと比較グループの平均結果は同じになる必要がある→自己選抜バイアスをなくすなど、無作為にグループを分ける必要あり
Fig. Randomized Controlled Trialの概要

  • RDデザイン(回帰不連続設計法)
    • 自然実験手法(あたかも実験が起こったかのような状況を用いて因果関係を分析する)の一種
    • 既存のデータの中に存在する境界線を利用し、ある境界前後において介入要素となりうるもののみが非連続に変化する場合(その他の要素は連続的に変換する必要がある)、その境界線付近の前後でRCTと見なすことができる。
  • 集積分析
    • 自然実験手法(あたかも実験が起こったかのような状況を用いて因果関係を分析する)の一種
    • 既存のデータの中に、何らかのインセンティブのみが階段状で変化する場合(その他の要素は連続的に変化する必要がある)、インセンティブが大きく変わる境界点におけるデータを集積することで、インセンティブに反応したかどうかの因果関係を説明できる。
  • パネル・データ分析
    • 自然実験手法(あたかも実験が起こったかのような状況を用いて因果関係を分析する)の一種
    • 複数グループに対して、介入開始前のデータに「平行トレンドの仮定」が成り立つ場合、介入前後の複数グループ間の差を比較することで介入効果を説明できる。
Fig. Analysis of Panel Data 



分析手法の強みと弱みの整理

分析手法強み弱み
RCT
(ランダム化比較試験)
・因果関係を科学的に立証できる
→内的妥当性が非常に高い
・費用/労力/各機関の協力を要する
RDデザイン
(回帰不連続設計法)
・境界線を境に、介入要素のみが非連続であるという仮定が成り立つのであれば、既存のデータを基にあたかもRCTが起こっているようにみなせる・成り立つであろう根拠を示せるが、因果関係を科学的に立証できない
・境界線付近のデータにしか、因果関係を主張できない(境界線付近の主体に対する介入効果しか説明できない)
集積分析・境界線を境に、介入要素のみが階段状で変化し、他の要素は非連続で変化しないという仮定が成り立つのであれば、既存のデータを基にあたかもRCTが起こっているようにみなせる・成り立つであろう根拠を示せるが、因果関係を科学的に立証できない
・階段状に変化するインセンティブに反応する主体に対する因果関係しか主張できない
パネル・データ分析・介入が起こった前後のデータが介入グループと比較グループについて入手でき、介入グループと比較グループについて平行トレンドの仮定が成り立つ場合、介入グループに属する全ての主体に対して介入効果が説明できる
→外的妥当性は比較的高い
・成り立つであろう根拠を示せるが、因果関係を科学的に立証できない
・「平行トレンドの仮定」は非常に難しい仮定であり、成り立たない状況も多い
→他の手法に比べ、内的妥当性は劣る


2017年6月4日日曜日

標準一様分布に従う独立した2つの確率変数の大きい方の期待値の求め方(3)

一様分布の定義は過去のポストに記載しているので参照されたい。今回のポストでは、おまけとして、任意の連続一様分布の期待値を求める。

(おまけ)任意の連続一様分布の場合


まず、$Y$が大きい場合を考える。求める期待値は$Y$の期待値となることから、以下で表せる。なお、各確率変数は、$a$から$b$の間の一様分布(確率 $\frac{1}{b-a}$)に従うため、確率密度関数は、2変数の同時確率となり、定数$\frac{1}{(b-a)^2}$ をとる。

$$
\begin{eqnarray}
& &\int_a^b\int_x^b \frac{y}{(b-a) ^2} dydx\\
&=&\int_a^b\frac{(b^2-x^2)}{ 2(b-a) ^2 }dx\\
&=&\left[ \frac{3b^2x - x^3 }{ 6(b-a) ^2 } \right]_a^b\\
&=&\frac{ (2b^3-3ab^2+a^3) }{ 6(b-a) ^2 }\\
&=&\frac{ (2b+a)(b-a)^2 }{ 6(b-a) ^2 }\\
&=&\frac{ (2b+a) }{ 6 }\\
\end{eqnarray}
$$

$X$と$Y$は対称であるため、$X$が大きい場合も同様に$\frac{ (2b+a) }{ 6 }$となる。よって、求める期待値は、$\frac{ (2b+a) }{ 3 }$であり、0から1の標準一様分布もこの値を満たす。

その他:美しい解法

本問題について、より美しく解いていたQAサイトを発見したので、紹介する。

Expected value of maximum of two random variables from uniform distribution
https://math.stackexchange.com/questions/197299/expected-value-of-maximum-of-two-random-variables-from-uniform-distribution

すべての非負の確率変数$X$の期待値は以下で表せることをうまく利用した例である。
$$
\begin{eqnarray}
& &E[X]\\
&=&\int_0^\infty yf_X(y) dy\\
&=&\int_0^\infty f_X(y) \int_0^y1dxdy\\
&=&\int_0^\infty\int_0^yf_X(y)dxdy\\
&=&\int_0^\infty\int_x^\infty f_X(y)dydx\\
&=&\int_0^\infty P(X \geq x) dx\\
&=&\int_0^\infty 1 - P(X \leq x) dx\\
\end{eqnarray}
$$

なお、式(10)〜式(11)は積分範囲の指定の順序を交換している。詳細は、以下を参照されたい。

Expected value of a non-negative random variable
https://math.stackexchange.com/questions/958472/expected-value-of-a-non-negative-random-variable

加えて、2つの確率変数の最大値が特定の値$x$よりも小さくなる確率は、以下で表現できる。
$$
\begin{eqnarray}
P(max(X,Y) \leq x) = P(X \leq x)P(Y \leq x)
\end{eqnarray}
$$

よって、以下のように求められる。

$$
\begin{eqnarray}
& & E[max(X,Y)]\\
&=& \int_0^\infty 1 - P(max(X,Y) \leq x) dx\\
&=& \int_0^\infty 1 - P(X \leq x)P(Y \leq x) dx\\
&=& \int_0^1 1 - x^2 dx\\
&=&\left[ \frac{3x - x^3 }{ 3 } \right]_0^1\\
&=& \frac{ 2 }{ 3 }
\end{eqnarray}
$$

標準一様分布に従う独立した2つの確率変数の大きい方の期待値の求め方(2)

標準一様分布の定義は過去のポストに記載しているので参照されたい。今回のポストでは、重積分を用いて期待値を表現することで、期待値を求める。

重積分によって導出する場合


標準一様分布に従う2つの確率変数をそれぞれ$X,Y$とする。

まず、$Y$が大きい場合を考える。求める期待値は$Y$の期待値となることから、以下で表せる。なお、0から1の間の区間1の一様分布であるため、確率密度関数は定数1をとなることに注意されたい。

外側の積分範囲は小さい方$X$のとりうる範囲を表し、内側の積分範囲はその$X$に対して、大きい$Y$をとる範囲を表している。

$$
\begin{eqnarray}
& &\int_0^1\int_x^1 y dydx\\
&=&\int_0^1\frac{(1-x^2)}{ 2 }dx\\
&=&\left[ \frac{3x - x^3 }{ 6 } \right]_0^1\\
&=&\frac{ 1 }{ 3 }
\end{eqnarray}
$$

$X$と$Y$は対称であるため、$X$が大きい場合も同様に$\frac{ 1 }{ 3 }$となる。

すなわち、求める期待値は、$\frac{ 2 }{ 3 }$である。
なお、当然、離散確率変数の極限から導出した場合と同じ値をとる。

次回は一般化して、任意の連続一様分布の場合を考える。

標準一様分布に従う独立した2つの確率変数の大きい方の期待値の求め方(1)

連続一様分布の定義


連続一様分布の確率密度関数は以下の通りである。

$$
\begin{eqnarray}
f ( x )
 =
  \begin{cases}
    \frac{ 1 }{ b - a } & ( a \leq x \leq b ) \\
    0 & ( x \lt a \ or \ b \gt x )
  \end{cases}
\end{eqnarray}
$$
今回対象の標準一様分布は以下で定義される。
$$
\begin{eqnarray}
f ( x )
 =
  \begin{cases}
    1  & (0 \leq x \leq 1 ) \\
    0 & ( x \lt 0 \ or \ 1 \gt x )
  \end{cases}
\end{eqnarray}
$$

いくつかの方法で期待値を求めてみる。

  1. 離散一様分布の極限から導出する場合
  2. 重積分によって導出する場合
  3. (おまけ)任意の連続一様分布の場合


離散一様分布の極限から導出する場合


$[0,1]$の区間を$n$等分し、$0, \frac{ 1 }{ n },...,\frac{ k }{ n },...,\frac{ n }{ n }$ の$n+1$つの離散値を、それぞれ$\frac{ 1 }{ n+1 }$の確率でとる離散一様分布を考える。

この離散一様分布から、2つの確率変数$X,Y$をとる場合に、その大きい方の期待値は、以下の式で求められる。
$$
\small{2\sum_{k=0}^{n} \frac{ k }{ n }\cdot\frac{ 1 }{ 1+n }\cdot\frac{ 1+k }{ 1+n }-\sum_{k=0}^{n} \frac{ k }{ n }\cdot\frac{ 1 }{ 1+n }\cdot\frac{ 1 }{ 1+n }}
$$
簡単に解説すると、$X$が大きい方となり、その値が$\frac{ k }{ n }$をとる確率は、$\frac{ 1 }{ 1+n }\cdot\frac{ 1+k }{ 1+n }$である。この時、$Y$は、$\frac{ k }{ n }$以下の値をとる必要があることに注意されたい。次に、$Y$が大きい方となる場合も考慮し、$X=Y$となる重複する確率を除くと、求める期待値は、上記式で表現できる。

次に期待値の式を簡単にすると、以下の式(5)が得られる。
$$
\begin{eqnarray}
& &\small{2\sum_{k=0}^{n} \frac{ k }{ n }\cdot\frac{ 1 }{ 1+n }\cdot\frac{ 1+k }{ 1+n }-\sum_{k=0}^{n} \frac{ k }{ n }\cdot\frac{ 1 }{ 1+n }\cdot\frac{ 1 }{ 1+n }}\\
&=&\sum_{k=0}^{n} \frac{ 2k^2+k }{n(1+n)^2 }\\
&=&\sum_{k=1}^{n} \frac{ 2k^2+k }{n(1+n)^2 }\\
&=&\small{\frac{1}{n(1+n)^2}\cdot( 2\frac{n(n+1)(2n+1)}{6}+\frac{n(n+1)}{2})}\\
&=&\frac{4n+5}{6n+6}
\end{eqnarray}
$$

この$[0,1]$の区間を分割する変数である$n$を無限大の極限をとると、連続一様分布に従う独立した2つの確率変数の大きい方の期待値と等しいが得られる。
$$
\begin{eqnarray}
& & \lim_{ n \to \infty }\frac{4n+5}{6n+6}
&=& \lim_{ n \to \infty }\frac{4+\frac{5}{n}}{6+\frac{6}{n}}
&=&\frac{2}{3}
\end{eqnarray}
$$

次回は重積分を用いて、直接連続一様分布に従う独立した2つの確率変数の大きい方の期待値を考える

2017年4月18日火曜日

【SPRINT】Googleで培われた最速な問題解決プロセス

本書の概要

SPRINTとは、Googleの中で培われた短期間(5日間)で、問題を見極め、解決のためのアイデア評価を行い、答えを出す手法である。下記の図で示すように、通常ビジネスにおいては必要な、ビルドやローンチといったプロセスをスキップして、アイデアの良し悪しを学ぶことを可能とする。

Shortcut [the Design Sprintより引用]


本書は、SPRINTについての体系だった説明というよりは、具体的な実践方法が詰め込まれた一冊である。「リーンスタートアップ」よりも速く答えを出すための工夫が、「イシューからはじめよ」よりも実践的で現実に即した内容が盛り込まれている。

例えば、進行役が注意すべき点や、被験者の集め方に加え、文房具の準備の方法や、おすすめのランチのタイミングなど、実践知に基づき、実際にSPRINTを実施して問題解決するための細かいTipsまで記されている。加えて、本書末尾の付録には、SPRINT実施のための留意点をまとめたチェックシートがついており、実際に問題解決策のアイデア出し&評価を実施するためには、ぜひ参考にしたい内容だ(本書をそばに置きつつ、まずはSPRINTの試してみて、その効果を見定めるのはアリだと思う)。

しかしながら、本書は残念な点も多い。章の頭や途中に、方法の説明と前後する形で事例が記載されており、しかも、小説調や語り調で書かれているため、読みにくかったように思う。個人的には、洋書特有の緩い表現をを排して、見出しを構造化して事例は最後にまとめるなどすれば、より読みやすい書籍となったように思う。また、副題に「最速仕事術」とあるが、「仕事」という表現は少しもったいないように思う。本書でいう「仕事を最速に行うこと」は、チームとして問題の特定から、解決アイデアの評価を最速で行うことであり、一般的に「仕事」という言葉が指すことの多い、個人の決まった作業の効率化という内容とは一線を画すからである。

SPRINTの重要な考え方

SPRINTの中で特に重要と感じた考え方について列挙する。

  1. 役割とプロセスの明確化と合理化
    • 問題を深掘りする際の課題の洗い出しなどは、専門家を招聘して実施し、出来上がったプロトタイプの評価は被験者を集めヒアリング評価を行う。また、チームで集めたアイデアの素材は共有し、それを基にメンバは個人でソリューションを考え、スケッチにまとめる。またメンバは投票を行い、意思決定者が最終的にソリューション案を決定する。このように誰がどれを行うかが明確になっており、無駄がなく、合理的である。
  2. 問題の見極めとフォーカスを尊重
    • いきなりソリューションを考えるのではなく、まず問題をしっかりと特定し、チームとして認識し、明確なターゲット(どの顧客の何の瞬間)を定める。
  3. 解決策立案のための素材収集の効率化
    • 定義された問題を解決するための既存のアイデアを持ち寄って、デモを行いチームに共有する。チームは、共有された既存アイデアを改善させたり、組替えたりすることで新しいアイデアを醸成する。
  4. 解決策のアイデアは具体化
    • ブレインストーミングより個人のアイデア出しの方が優れていることは立証済であるため、個人でアイデア出しを行う。また、抽象的なアイデアの場合、感情や主観によって捉え方や評価が変化しやすいため、アイデア自体を公平公正に評価するため具体化を行い、スケッチにまとめる。
  5. アイデアの意思決定/合意形成の効率化/主観の排除
    • 「メンバの品評→メンバの投票→意思決定者の決断」というようにプロセスを決めておくことで、好き勝手に自由なタイミングで品評する無駄な時間を省くことができる。匿名にするなどして、アイデアを公平に客観的に評価できるような工夫をする。

SPRINTの実施内容

本書に記載されたSPRINTのキーワードを、備忘録として曜日ごとに整理しておく。

◼︎月曜日:問題を見極め、特定する ※いきなりソリューションから入らない
長期目標:プロジェクトの1年後に目指すところ
スプリントクエスチョン:スプリントの最後にどのような問いに答えられるか?
マップ:顧客が製品/サービスを利用する流れを整理したもの
専門家にきく:マップを確かめる、課題を知る
どうすればメモ:課題に対して、どうすれば〜という形でまとめる
ターゲットを決める:本SPRINTでどこにフォーカスすべきか?
 ・重要な顧客は誰か?
 ・体験の中で最も重要な瞬間はいつか?
 ・インパクトがある/重要そうなところ

◼︎火曜日:問題解決のアイデアを具体的なスケッチにする
光速デモ:改良と組替えに焦点をあてる。既存のアイデアで使えそうなものを3分でプレゼン。月曜からの宿題でも良い。

メモ: ベストな素材を集める
アイデア: なんでも書く
クレイジー8: アイデアのバリエーションを高速に洗い出す
ソリューションスケッチ:3コマでまとめる:全員が個別に作成する、抽象的なアイデアは誤って評価される、ブレストより個人の方が良いアイデアがでるのは証明済。

◼︎水曜日:解決策の決定とプロトタイプのストーリー作成
ソリューションスケッチの決定:投票して勝者を決める
ストーリーボード:プロトタイプの計画を立てる

◼︎木曜日:リアルなプロトタイプの作成
ファサード:完璧→必要最低限、長期品質→一時的なシミュレーション
プロトタイプ思考:
 ・短時間でリアルさを得る。
 ・時間をかけすぎない。

◼︎金曜日:生身の人間による評価
現実なユーザーによるテスト(5人):それ以上は費用対効果が微妙。

参考情報

The Design Sprint http://www.gv.com/sprint/

2017年4月2日日曜日

赤ちゃん用バケットハットの作り方

バケットハットの型紙に必要な情報

入力(作りたい帽子の大きさ)

トップクラウンの円周 $a$(cm)
サイドクラウンの下円の円周 $b$(cm)
サイドクラウンの下円の幅 $l_1$(cm)
ブリムの外側(下側)の円周 $c$(cm)
ブリムの幅 $l_2$(cm)
Center Line(cm)
Scale(cm)

出力(型紙作成に必要な情報)

$r_a$ 0 (cm)
$r_b$ 0 (cm)
$L_a$ 0 (cm)
$L_b$ 0 (cm)
$\phi_1$ 0 (°)
$r_c$ 0 (cm)
$L_b'$ 0 (cm)
$L_c$ 0 (cm)
$\phi_2$ 0 (°)

2017年3月12日日曜日

【続報】流行りの格安SIMに乗り換えてみた(iPhone SE 64GB SIMフリー版+FREETEL SIM for iPhone)

2016年6月に、流行りの格安SIMに乗り換えてみた(iPhone SE 64GB SIMフリー版+FREETEL SIM for iPhone)のポストで、格安SIMに乗り換える方法と乗り換えた場合の費用計算シミュレーションについて述べた。

今回は、2016年6月から2017年2月までのFREETELの利用実績を基に、想定利用金額を再設定し、費用計算シミュレーション結果を記載する。

利用料金の実績

まず、2016年6月から2017年2月までのFREETELの利用実績を以下のグラフに示す。
7月〜12月の6ヶ月間は、FREETELの「最大1年間0円キャンペーン」によって、1GB分の料金(499円)が差引かれている。
割引分499円を当該月に加算し9ヶ月の平均を算出すると、2,430(円/月)程度となった。

利用料金の内訳

次に利用明細(2016年12月分)を以下の表に示す。前回のポストでは、データ通信量(以下の表では基本使用量と記載)のみを考慮して費用計算を行っていた。しかしながら、実際利用してみると、LINEなどの無料通話、メールだけでは連絡できない場合が多々あったため、通話料およびSMS送信量が発生した。
また、データ通信料については、9ヶ月のうち7ヶ月が、3GB未満の利用であった。これは、そもそもデータ通信を行わないこととと、wifi環境が充実してきたためと考えられる。


ご利用明細
摘要ご利用月課税料金
◆基本使用料
使った分だけ安心プラン(ドコモ回線)for iPhone 音声通話付2016/12外税¥1,600
最大1年間0円キャンペーン第5弾( 6ヵ月)2016/12外税-¥499
◆通話料
国内音声通話料2016/11外税¥580
国内SMS送信料2016/11外税¥12
◆その他
ユニバーサルサービス料(1番号当たり3円の請求となります)2016/12外税¥3
小計(課税対象)¥1,696
消費税¥135
小計(内税/非課税)¥0
合計¥1,831

実績に基づく費用計算シミュレーション

最後に、利用実績に基づき、今後の累積利用料金をシミュレートしたグラフを以下に示す。
比較のため、以前利用していたSoftbankを使い続けていた場合(月額8,800円)の累積利用料金をグラフに追記している。FREETELのイニシャルコストについては、流行りの格安SIMに乗り換えてみた(iPhone SE 64GB SIMフリー版+FREETEL SIM for iPhone)のポストを参照されたい。
このグラフを見ればわかるように、2017年の5月にグラフの交点が存在し、約1年の利用で、イニシャルコストを回収できていることがわかる。2年間(2018年5月まで)利用できれば、76,440円((8,800円ー2,430円)×12ヶ月)費用を抑えられることになる。

まとめ

格安SIMに乗り換えてみた結果、実績値としても大手キャリアに比べて、費用を大きく抑えられることがわかった。「格安SIMはなんだか怪しそうだ、なんだか移行が面倒そう」と思っている方でも、本ポストをご覧いただいて一歩を踏み出していただければ幸いである。

2017年2月3日金曜日

LINEの自作スタンプを作ってみた(2)

1月6日(金)に申請したLINEスタンプ。
LINEの自作スタンプを作ってみた(1)

年末年始の関係で審査期間が通常より長くなるという記載があったものの、ちょうど1週間後の1月13日(金)に審査完了、無事に承認され、販売開始することができた。
審査完了の通知はLINEで連絡がくるため、タイムリーに状況把握することができてとても便利だった。

思った以上に簡単に完了したため、追加で別のスタンプも作成することにした。
今度は40個のスタンプである。




だんだんイラストソフトの操作も慣れてきて効率よく作成できるようになった。
出来上がったイラストがこちら。(下書きと順序はバラバラ)



40個もあると普段の会話に使えそうである。
来週ごろに使えるようになるのが楽しみだ。

2017年1月6日金曜日

LINEの自作スタンプを作ってみた(1)

以前から気になっていたLINEの自作スタンプ。
素人でも簡単にオリジナルスタンプを作成して販売できるとのことで、少し自由な時間が増えたことをきっかけに、初トライしてみた。

イラスト作成に使用したツールは以下の無料ソフトのみ。

フリー ペイントツール (Mac/Win 両対応) FireAlpaca [ ファイア アルパカ ]


そして以下の手順で行った。

①アイディア出し
 まずは普段の会話などで使えそうなワードをリストアップするところから始めた。
 以前はスタンプを42個作成する必要があったが、現在は8個から申請可能ということで、今回は16個を目指してアイディア出しを行った。

②キャラクター決定
 本来ここはこだわるべきところではあるが、イラスト作成に関してはド素人のため、自身で描きやすいキャラクターにせざるを得なかった。
 少なくとも16個は描かないといけないため、シンプルで表情や動きをつけやすい、ネコのキャラクターにした。




③下書き
 キャラクターが決まったので、①のアイディアを元に、構図や表情、ポーズなどを検討した。ここではレポート用紙に鉛筆で描くような簡単なものでOK。



④イラストデータ作成
 下書きした用紙をPCに取り込む。
 本来はスキャンすべきなのだが、プリンタを出すのが面倒だったのiPhoneで写真を撮り、画像を転送するかたちを取ったがそれで十分だった。取り込んだ画像を元に、firealpacaで清書、色付け、書き出しを行う。
 LINEスタンプのガイドラインが決まっているので、その画像サイズに合うように注意して作成する。

 出来上がったのがこちら!




⑤クリエイター登録
 LINE CREATERS MARKETに必要情報を入力し、登録する。
 

⑥スタンプをアップロード
 登録が完了するとスタンプをアップロードすることができる。
 タイトルや説明文などを先に入れる必要がある。

⑦審査のリクエスト
 無事すべて揃ったら審査をリクエストする。

現在、ここまで完了したところであり、無事承認が下りれば販売開始、ということになる。
また進展があれば続編を投稿したい。

続編を追記しました(2017/2/3)
LINEの自作スタンプを作ってみた(2)



2016年12月4日日曜日

今更聞けないEMアルゴリズムの解説〜潜在変数が連続変数の場合のEステップの説明〜

$\boldsymbol{z}_i$が連続変数の場合について、変分法によって確率分布$Q(\boldsymbol{z}_i) $を求める方法も追記しておく。

すなわち、以下を示す。
$$
\begin{eqnarray}
Q(\boldsymbol{z}_i)  &=& P( \boldsymbol{z}_i \mid \boldsymbol{x}_i, \boldsymbol{\theta})
\end{eqnarray}
$$

E-Stepの説明(潜在変数が連続変数の場合)

E-Stepでは、$\boldsymbol{\theta}$固定の下、尤度関数の下界の分布を最大化する。以下で尤度関数を変形し、下界を求める手順を示す。

$$
\begin{eqnarray}
\displaystyle \sum_{ i = 1 }^{ N } \ln P( \boldsymbol{x}_i \mid \boldsymbol{\theta}) &=& \displaystyle \sum_{ i = 1 }^{ N } \ln \displaystyle \int P( \boldsymbol{x}_i, \boldsymbol{z}_i \mid \boldsymbol{\theta}) d\boldsymbol{z}_i\\
&=& \displaystyle \sum_{ i = 1 }^{ N } \ln \displaystyle \int Q(\boldsymbol{z}_i)\frac{P( \boldsymbol{x}_i, \boldsymbol{z}_i \mid \boldsymbol{\theta})}{Q(\boldsymbol{z}_i)} d\boldsymbol{z}_i\\
&\geq&  \displaystyle \sum_{ i = 1 }^{ N }\displaystyle \int Q(\boldsymbol{z}_i) \ln \frac{P( \boldsymbol{x}_i, \boldsymbol{z}_i \mid \boldsymbol{\theta})}{Q(\boldsymbol{z}_i)}d\boldsymbol{z}_i
\end{eqnarray}
$$

(2)式から(3)式への変形は、$\boldsymbol{z}_i$の任意の確率分布$Q(\boldsymbol{z}_i) $でかけて割っただけである。この時、$Q(\boldsymbol{z}_i) $は何ら仮定をおいていないことに注意されたい。
(3)式から(4)式への変形は、Jensen's Inequalityを利用した。

この時、$\boldsymbol{\theta}$固定の下、下界(4)式の最大化を考える場合、変分法を用いれば良い。下界(4)式を$Q(\boldsymbol{z}_i) $の汎関数(関数の形を変化させると値が変化する関数。わかりやすい説明は「物理のかぎしっぽ(変分法1)」を参照されたい。)と捉え、変分法によって極値を求める。

特に、$Q = Q(\boldsymbol{z}_i) $とした時、$\boldsymbol{z}_i, Q$によって決まる、以下のようなシンプルな汎関数を考える。

$$
\begin{eqnarray}
\displaystyle \int f( \boldsymbol{z}_i, Q) d\boldsymbol{z}_i
\end{eqnarray}
$$

このシンプルな汎関数を求めるための、オイラー・ラグランジュ方程式は、以下で表せる。(その他の汎関数のオイラー・ラグランジュ方程式については、「物理のかぎしっぽ(変分法2)」を参照されたい。)

$$
\begin{eqnarray}
\frac{ \partial f }{ \partial Q }
\end{eqnarray}
$$

よって、下界(4)式の積分部分に注目し、$\int Q(\boldsymbol{z}_i) d\boldsymbol{z}_i =1$の制約を加えた汎関数は以下となる。

$$
\begin{eqnarray}
\displaystyle \int Q(\boldsymbol{z}_i) \ln \frac{P( \boldsymbol{x}_i, \boldsymbol{z}_i \mid \boldsymbol{\theta})}{Q(\boldsymbol{z}_i)}d\boldsymbol{z}_i - \lambda (1- \int Q(\boldsymbol{z}_i) d\boldsymbol{z}_i )
\end{eqnarray}
$$

これを$Q$で変分すると以下を得る。
$$
\begin{eqnarray}
\ln \frac{P( \boldsymbol{x}_i, \boldsymbol{z}_i \mid \boldsymbol{\theta})}{Q(\boldsymbol{z}_i)}+Q(\boldsymbol{z}_i) \cdot \frac{Q(\boldsymbol{z}_i)}{P( \boldsymbol{x}_i, \boldsymbol{z}_i \mid \boldsymbol{\theta})} \cdot \left[- \frac{P( \boldsymbol{x}_i, \boldsymbol{z}_i \mid \boldsymbol{\theta})}{Q(\boldsymbol{z}_i)^2} \right] + \lambda = 0
\end{eqnarray}
$$
$$
\begin{eqnarray}
\ln \frac{P( \boldsymbol{x}_i, \boldsymbol{z}_i \mid \boldsymbol{\theta})}{Q(\boldsymbol{z}_i)}  = -\lambda + 1
\end{eqnarray}
$$
$$
\begin{eqnarray}
Q(\boldsymbol{z}_i) = e^{\lambda - 1}P( \boldsymbol{x}_i, \boldsymbol{z}_i \mid \boldsymbol{\theta})
\end{eqnarray}
$$

$\int Q(\boldsymbol{z}_i) d\boldsymbol{z}_i =1$より、以下を得る。

$$
\begin{eqnarray}
Q(\boldsymbol{z}_i) &=& \frac{P( \boldsymbol{x}_i, \boldsymbol{z}_i \mid \boldsymbol{\theta})}{\int P( \boldsymbol{x}_i, \boldsymbol{z}_i \mid \boldsymbol{\theta}) d\boldsymbol{z}_i}\\
&=& \frac{P( \boldsymbol{x}_i, \boldsymbol{z}_i \mid \boldsymbol{\theta})}{P( \boldsymbol{x}_i \mid \boldsymbol{\theta}) }\\
&=& P( \boldsymbol{z}_i \mid \boldsymbol{x}_i, \boldsymbol{\theta})
\end{eqnarray}
$$

これは、離散分布を仮定し、EMアルゴリズムのE-STEPを説明した「今更聞けないEMアルゴリズムの解説」の(8)式と合致する。

2016年11月15日火曜日

話題のTensorFlow・LINEトーク履歴を用いて対話ボットを作ってみた(5)

ケーススタディ

前振りが長くなったが、開発したソースコードを用いて実際に「対話ボット」を学習し、実行した結果を示す。

学習データ

学習には以下の日常で頻繁に利用している家族とのLINEトークデータを用いた。
  • 全LINEトークデータ 17,796ペア(in/out)
  • trainデータ 16,017ペア(in/out)#全データの9割
  • devデータ 1,779ペア(in/out)#全データの1割

ディレクトリ(学習開始時)

学習開始時は、以下の3つのpythonファイルと2つのディレクトリを同階層に配置する。また、line_talk_dataディレクトリには、学習データとして作成した4種類のファイルを格納する。(学習後には中間生成物が各フォルダに生成される)

  • chatbot.py
  • data_utils.py
  • seq2seq_model.py
  • line_talk_data #学習データ格納用ディレクトリ
    • line_talk_train.out
    • line_talk_train.in
    • line_talk_dev.out
    • line_talk_dev.in
  • line_talk_train #学習結果のcheckpointデータ格納用ディレクトリ

学習の実行

python chatbot.py

実行後のコンソールを以下に示す。
chatbot$ python chatbot.py 
Preparing LINE talk data in line_talk_data
Creating vocabulary line_talk_data/vocab40000.out from data line_talk_data/line_talk_train.out
Creating vocabulary line_talk_data/vocab40000.in from data line_talk_data/line_talk_train.in
Tokenizing data in line_talk_data/line_talk_train.out
Tokenizing data in line_talk_data/line_talk_train.in
Tokenizing data in line_talk_data/line_talk_dev.out
Tokenizing data in line_talk_data/line_talk_dev.in
Creating 3 layers of 256 units.
Created model with fresh parameters.
Reading development and training data (limit: 0).
global step 100 learning rate 0.5000 step-time 0.66 perplexity 8820.34
  eval: bucket 0 perplexity 3683.12
  eval: bucket 1 perplexity 4728.98
  eval: bucket 2 perplexity 4118.81
  eval: bucket 3 perplexity 5504.88
以下のように、ある程度収束してきたところで、学習を切り上げる。 今回は以下のスペックのMac book pro にて8時間程度学習を行った。

CPU : 2.9 GHz Intel Core i5
メモリ: 16 GB 1867 MHz DDR3

この時、line_talk_trainディレクトリの中に、学習ステップに応じた .ckptファイルが蓄積されている。対話実行時に、蓄積された最新の.ckptファイルはリストアされる。
global step 17000 learning rate 0.3812 step-time 0.49 perplexity 30.66
  eval: bucket 0 perplexity 29.40
  eval: bucket 1 perplexity 45.61
  eval: bucket 2 perplexity 44.65
  eval: bucket 3 perplexity 85.85
global step 17100 learning rate 0.3812 step-time 0.51 perplexity 30.04
  eval: bucket 0 perplexity 113.59
  eval: bucket 1 perplexity 55.54
  eval: bucket 2 perplexity 39.45
  eval: bucket 3 perplexity 45.94

対話の実行(--decode)

python chatbot.py --decode

--decodeオプションをつけて実行することで、対話モードで実行できる。 以下に「対話ボット」の対話例を示す。 最新の.ckptファイルをリストアされ、対話モードに入っていることが確認出来る。
chatbot$ python chatbot.py --decode
Reading model parameters from line_talk_train/chatbot.ckpt-17600
> 

以下に対話例を示す。 ([Morpho]タグ行)は入力の形態素解析の結果を出力している。
> 今日は何時に帰る?
([Morpho]:今日 は 何 時 に 帰る ?)
00 : 00 くらい か なー !
> 今から帰りまーす
([Morpho]:今 から 帰り ま ー す)
今 から ます !
> おつかれー
([Morpho]:お つかれ ー)
[ スタンプ ]
> 退社しましたー!
([Morpho]:退社 し まし た ー !)
00 : 00 くらい か なー !
> お腹空いたのでゆっくり食べてます〜
([Morpho]:お腹 空い た ので ゆっくり 食べ て ます 〜)
たいしゃ

そこそこの対話ができていることがわかる。
「今日は何時に帰る?」という質問に対して、時刻を返答しようとしていることがわかる。パターンとしては上手く学習できているが、 実際に情報がないので正しい時刻を返せていないのは残念であるが。
「ゆっくり食べてます〜(先にご飯を食べています)」という情報に対して、遅れて「たいしゃ」したというのも、日常でよくあるやりとりを上手く学習できているといえる。
また、LINEトークのテキストデータのみを学習しているため、一部の返答が[スタンプ]になってしまっているのも仕方がない結果であろう。

まとめ

LINEトーク履歴のテキストデータを用いた「対話ボット」を開発してみた結果、個人のLINEトークの返し方を学習し、それっぽい回答をしてくれることがわかった。翻訳と違い、「良い」「悪い」の基準が極めて曖昧なため、評価が難しいのは事実であるが、可能性を感じる結果にはなった。

今後は、データ量を増加させ、時系列を加味して対話データの生成するなど、学習データの洗練を行いたい。また、今回ハイパーパラメータについても、計算量削減のため、デフォルトの設定よりもだいぶ小さい値を用いている。データ量の増加に合わせて、ハイパーパラメータの調整も併せて行うことで、より自然な対話ができるようになると思われる。

参考



話題のTensorFlow・LINEトーク履歴を用いて対話ボットを作ってみた(1)

話題のTensorFlow・LINEトーク履歴を用いて対話ボットを作ってみた(2)

話題のTensorFlow・LINEトーク履歴を用いて対話ボットを作ってみた(3)

話題のTensorFlow・LINEトーク履歴を用いて対話ボットを作ってみた(4)

話題のTensorFlow・LINEトーク履歴を用いて対話ボットを作ってみた(4)

2. 「対話ボット」学習ロジックの実装


参考2:Tensorflow: Sequence-to-Sequence Models に含まれる、models/rnn配下の以下の3つのソースコードを基に、「対話ボット」に必要な修正を加える。実行時のメインメソッドを含む translate.py をchatbot.pyとして修正を加えた。


No.srcdescription
1translate/seq2seq_model.pyNeural translation sequence-to-sequence model.
2translate/data_utils.pyHelper functions for preparing translation data.
3translate/translate.pyBinary that trains and runs the translation model.

学習ロジック関連で、プログラムに修正を加えた点は以下だけである。
2. data_utils.py に対して「(LINEトーク履歴から作成した)学習用データ」の読み込み部分にprepare_line_talk_dataメソッドを作成
3. chatbot.py に対してdata_utils.py内のprepare_line_talk_dataメソッドを呼び出すように修正

3. 「対話ボット」対話ロジックの実装

対話ロジック関連で、プログラムに修正を加えた点は以下だけである。
3. chatbot.py に対して、Decode時の日本語の形態素解析処理の追加

英仏翻訳のためのsequence-to-sequenceモデルに対して、これだけの修正を加えるだけで、「対話ボット」として利用可能となる。

最終的に、以下の3つのソースコードとdataディレクトリを同階層に配備するだけで動作する。

ソースコードを以下に示す。
[Source Code : data_utils.py]
# Copyright 2016 y-euda. All Rights Reserved.
# The following modifications are added based on tensorflow/models/rnn/translate/data_utils.py.
# - prepare_line_talk_data() is created to load LINE talk data text file.
#
#==============================================================================
# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================

"""Utilities for downloading data from WMT, tokenizing, vocabularies."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import gzip
import os
import re

from tensorflow.python.platform import gfile
import tensorflow as tf

# Special vocabulary symbols - we always put them at the start.
_PAD = b"_PAD"
_GO = b"_GO"
_EOS = b"_EOS"
_UNK = b"_UNK"
_START_VOCAB = [_PAD, _GO, _EOS, _UNK]

PAD_ID = 0
GO_ID = 1
EOS_ID = 2
UNK_ID = 3

# Regular expressions used to tokenize.
_WORD_SPLIT = re.compile(b"([.,!?\"':;)(])")
_DIGIT_RE = re.compile(br"\d")

def gunzip_file(gz_path, new_path):
  """Unzips from gz_path into new_path."""
  print("Unpacking %s to %s" % (gz_path, new_path))
  with gzip.open(gz_path, "rb") as gz_file:
    with open(new_path, "wb") as new_file:
      for line in gz_file:
        new_file.write(line)

def basic_tokenizer(sentence):
  """Very basic tokenizer: split the sentence into a list of tokens."""
  words = []
  for space_separated_fragment in sentence.strip().split():
    words.extend(_WORD_SPLIT.split(space_separated_fragment))
  return [w for w in words if w]


def create_vocabulary(vocabulary_path, data_path, max_vocabulary_size,
                      tokenizer=None, normalize_digits=True):
  """Create vocabulary file (if it does not exist yet) from data file.

  Data file is assumed to contain one sentence per line. Each sentence is
  tokenized and digits are normalized (if normalize_digits is set).
  Vocabulary contains the most-frequent tokens up to max_vocabulary_size.
  We write it to vocabulary_path in a one-token-per-line format, so that later
  token in the first line gets id=0, second line gets id=1, and so on.

  Args:
    vocabulary_path: path where the vocabulary will be created.
    data_path: data file that will be used to create vocabulary.
    max_vocabulary_size: limit on the size of the created vocabulary.
    tokenizer: a function to use to tokenize each data sentence;
      if None, basic_tokenizer will be used.
    normalize_digits: Boolean; if true, all digits are replaced by 0s.
  """
  if not gfile.Exists(vocabulary_path):
    print("Creating vocabulary %s from data %s" % (vocabulary_path, data_path))
    vocab = {}
    with gfile.GFile(data_path, mode="rb") as f:
      counter = 0
      for line in f:
        counter += 1
        if counter % 100000 == 0:
          print("  processing line %d" % counter)
        line = tf.compat.as_bytes(line)
        tokens = tokenizer(line) if tokenizer else basic_tokenizer(line)
        for w in tokens:
          word = _DIGIT_RE.sub(b"0", w) if normalize_digits else w
          if word in vocab:
            vocab[word] += 1
          else:
            vocab[word] = 1
      vocab_list = _START_VOCAB + sorted(vocab, key=vocab.get, reverse=True)
      if len(vocab_list) > max_vocabulary_size:
        vocab_list = vocab_list[:max_vocabulary_size]
      with gfile.GFile(vocabulary_path, mode="wb") as vocab_file:
        for w in vocab_list:
          vocab_file.write(w + b"\n")

def initialize_vocabulary(vocabulary_path):
  """Initialize vocabulary from file.

  We assume the vocabulary is stored one-item-per-line, so a file:
    dog
    cat
  will result in a vocabulary {"dog": 0, "cat": 1}, and this function will
  also return the reversed-vocabulary ["dog", "cat"].

  Args:
    vocabulary_path: path to the file containing the vocabulary.

  Returns:
    a pair: the vocabulary (a dictionary mapping string to integers), and
    the reversed vocabulary (a list, which reverses the vocabulary mapping).

  Raises:
    ValueError: if the provided vocabulary_path does not exist.
  """
  if gfile.Exists(vocabulary_path):
    rev_vocab = []
    with gfile.GFile(vocabulary_path, mode="rb") as f:
      rev_vocab.extend(f.readlines())
    rev_vocab = [line.strip() for line in rev_vocab]
    vocab = dict([(x, y) for (y, x) in enumerate(rev_vocab)])
    return vocab, rev_vocab
  else:
    raise ValueError("Vocabulary file %s not found.", vocabulary_path)


def sentence_to_token_ids(sentence, vocabulary,
                          tokenizer=None, normalize_digits=True):
  """Convert a string to list of integers representing token-ids.

  For example, a sentence "I have a dog" may become tokenized into
  ["I", "have", "a", "dog"] and with vocabulary {"I": 1, "have": 2,
  "a": 4, "dog": 7"} this function will return [1, 2, 4, 7].

  Args:
    sentence: the sentence in bytes format to convert to token-ids.
    vocabulary: a dictionary mapping tokens to integers.
    tokenizer: a function to use to tokenize each sentence;
      if None, basic_tokenizer will be used.
    normalize_digits: Boolean; if true, all digits are replaced by 0s.

  Returns:
    a list of integers, the token-ids for the sentence.
  """

  if tokenizer:
    words = tokenizer(sentence)
  else:
    words = basic_tokenizer(sentence)
  if not normalize_digits:
    return [vocabulary.get(w, UNK_ID) for w in words]
  # Normalize digits by 0 before looking words up in the vocabulary.
  return [vocabulary.get(_DIGIT_RE.sub(b"0", w), UNK_ID) for w in words]


def data_to_token_ids(data_path, target_path, vocabulary_path,
                      tokenizer=None, normalize_digits=True):
  """Tokenize data file and turn into token-ids using given vocabulary file.

  This function loads data line-by-line from data_path, calls the above
  sentence_to_token_ids, and saves the result to target_path. See comment
  for sentence_to_token_ids on the details of token-ids format.

  Args:
    data_path: path to the data file in one-sentence-per-line format.
    target_path: path where the file with token-ids will be created.
    vocabulary_path: path to the vocabulary file.
    tokenizer: a function to use to tokenize each sentence;
      if None, basic_tokenizer will be used.
    normalize_digits: Boolean; if true, all digits are replaced by 0s.
  """
  if not gfile.Exists(target_path):
    print("Tokenizing data in %s" % data_path)
    vocab, _ = initialize_vocabulary(vocabulary_path)
    with gfile.GFile(data_path, mode="rb") as data_file:
      with gfile.GFile(target_path, mode="w") as tokens_file:
        counter = 0
        for line in data_file:
          counter += 1
          if counter % 100000 == 0:
            print("  tokenizing line %d" % counter)
          token_ids = sentence_to_token_ids(line, vocab, tokenizer,
                                            normalize_digits)
          tokens_file.write(" ".join([str(tok) for tok in token_ids]) + "\n")

def prepare_line_talk_data(data_dir, in_vocabulary_size, out_vocabulary_size, tokenizer=None):
  """Get line talk data into data_dir, create vocabularies and tokenize data.

  Args:
    data_dir: directory in which the data sets will be stored.
    in_vocabulary_size: size of the Input vocabulary to create and use.
    out_vocabulary_size: size of the Output vocabulary to create and use.
    tokenizer: a function to use to tokenize each data sentence;
      if None, basic_tokenizer will be used.

  Returns:
    A tuple of 6 elements:
      (1) path to the token-ids for Input training data-set,
      (2) path to the token-ids for Output training data-set,
      (3) path to the token-ids for Input development data-set,
      (4) path to the token-ids for Output development data-set,
      (5) path to the Input vocabulary file,
      (6) path to the Output vocabulary file.
  """
  # Get line_talk data to the specified directory.
  train_path = os.path.join(data_dir, "line_talk_train")               
  dev_path = os.path.join(data_dir, "line_talk_dev")                     
  
  # Create vocabularies of the appropriate sizes.
  out_vocab_path = os.path.join(data_dir, "vocab%d.out" % out_vocabulary_size ) 
  in_vocab_path = os.path.join(data_dir, "vocab%d.in"  % in_vocabulary_size ) 
  create_vocabulary(out_vocab_path, train_path + ".out", out_vocabulary_size, tokenizer)
  create_vocabulary(in_vocab_path, train_path + ".in", in_vocabulary_size, tokenizer)

  # Create token ids for the training data.
  out_train_ids_path = train_path + (".ids%d.out" % out_vocabulary_size)
  in_train_ids_path = train_path + (".ids%d.in" % in_vocabulary_size)
  data_to_token_ids(train_path + ".out", out_train_ids_path, out_vocab_path, tokenizer)
  data_to_token_ids(train_path + ".in", in_train_ids_path, in_vocab_path, tokenizer)

  # Create token ids for the development data.
  out_dev_ids_path = dev_path + (".ids%d.out" % out_vocabulary_size)
  in_dev_ids_path = dev_path + (".ids%d.in" % in_vocabulary_size)
  data_to_token_ids(dev_path + ".out", out_dev_ids_path, out_vocab_path, tokenizer)
  data_to_token_ids(dev_path + ".in", in_dev_ids_path, in_vocab_path, tokenizer)

  return (in_train_ids_path, out_train_ids_path,
          in_dev_ids_path, out_dev_ids_path,
          in_vocab_path, out_vocab_path)

[Source Code : chatbot.py]
# Copyright 2016 y-euda. All Rights Reserved.
# The following modifications are added based on tensorflow/models/rnn/translate/translate.py.
# - train() is modified to load LINE talk data text file.
# - decode() is modifed for the input sentence in Japanese.
#
# ==============================================================================
# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================

"""Binary for training seq2seq models and decoding from them.

Running this program without --decode will download the line talk corpus into
the directory specified as --data_dir and tokenize it in a very basic way,
and then start training a model saving checkpoints to --train_dir.

Running with --decode starts an interactive loop so you can see how
the current checkpoint translates English sentences into French.

See the following papers for more information on neural translation models.
 * http://arxiv.org/abs/1409.3215
 * http://arxiv.org/abs/1409.0473
 * http://arxiv.org/abs/1412.2007
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import math
import os
import random
import sys
import time
import logging

import numpy as np
import tensorflow as tf


import data_utils as data_utils
import seq2seq_model as seq2seq_model

from janome.tokenizer import Tokenizer

tf.app.flags.DEFINE_float("learning_rate", 0.5, "Learning rate.")
tf.app.flags.DEFINE_float("learning_rate_decay_factor", 0.99,
                          "Learning rate decays by this much.")
tf.app.flags.DEFINE_float("max_gradient_norm", 5.0,
                          "Clip gradients to this norm.")
tf.app.flags.DEFINE_integer("batch_size", 4, #64
                            "Batch size to use during training.")
tf.app.flags.DEFINE_integer("size", 256, "Size of each model layer.") #1024
tf.app.flags.DEFINE_integer("num_layers", 3, "Number of layers in the model.") #3
tf.app.flags.DEFINE_integer("en_vocab_size", 40000, "English vocabulary size.") #40000
tf.app.flags.DEFINE_integer("fr_vocab_size", 40000, "French vocabulary size.") #40000
tf.app.flags.DEFINE_string("data_dir", "line_talk_data", "Data directory")#data
tf.app.flags.DEFINE_string("train_dir", "line_talk_train", "Training directory.")#train
tf.app.flags.DEFINE_integer("max_train_data_size", 0,
                            "Limit on the size of training data (0: no limit).")
tf.app.flags.DEFINE_integer("steps_per_checkpoint", 100,#200
                            "How many training steps to do per checkpoint.")
tf.app.flags.DEFINE_boolean("decode", False,
                            "Set to True for interactive decoding.")
tf.app.flags.DEFINE_boolean("self_test", False,
                            "Run a self-test if this is set to True.")
tf.app.flags.DEFINE_boolean("use_fp16", False,
                            "Train using fp16 instead of fp32.")

FLAGS = tf.app.flags.FLAGS

# We use a number of buckets and pad to the closest one for efficiency.
# See seq2seq_model.Seq2SeqModel for details of how they work.
_buckets = [(5, 10), (10, 15), (20, 25), (40, 50)]


def read_data(source_path, target_path, max_size=None):
  """Read data from source and target files and put into buckets.

  Args:
    source_path: path to the files with token-ids for the source language.
    target_path: path to the file with token-ids for the target language;
      it must be aligned with the source file: n-th line contains the desired
      output for n-th line from the source_path.
    max_size: maximum number of lines to read, all other will be ignored;
      if 0 or None, data files will be read completely (no limit).

  Returns:
    data_set: a list of length len(_buckets); data_set[n] contains a list of
      (source, target) pairs read from the provided data files that fit
      into the n-th bucket, i.e., such that len(source) < _buckets[n][0] and
      len(target) < _buckets[n][1]; source and target are lists of token-ids.
  """
  data_set = [[] for _ in _buckets]
  with tf.gfile.GFile(source_path, mode="r") as source_file:
    with tf.gfile.GFile(target_path, mode="r") as target_file:
      source, target = source_file.readline(), target_file.readline()
      counter = 0
      while source and target and (not max_size or counter < max_size):
        counter += 1
        if counter % 100000 == 0:
          print("  reading data line %d" % counter)
          sys.stdout.flush()
        source_ids = [int(x) for x in source.split()]
        target_ids = [int(x) for x in target.split()]
        target_ids.append(data_utils.EOS_ID)
        for bucket_id, (source_size, target_size) in enumerate(_buckets):
          if len(source_ids) < source_size and len(target_ids) < target_size:
            data_set[bucket_id].append([source_ids, target_ids])
            break
        source, target = source_file.readline(), target_file.readline()
  return data_set


def create_model(session, forward_only):
  """Create chat model and initialize or load parameters in session."""
  dtype = tf.float16 if FLAGS.use_fp16 else tf.float32
  model = seq2seq_model.Seq2SeqModel(
      FLAGS.en_vocab_size,
      FLAGS.fr_vocab_size,
      _buckets,
      FLAGS.size,
      FLAGS.num_layers,
      FLAGS.max_gradient_norm,
      FLAGS.batch_size,
      FLAGS.learning_rate,
      FLAGS.learning_rate_decay_factor,
      forward_only=forward_only,
      dtype=dtype)
  ckpt = tf.train.get_checkpoint_state(FLAGS.train_dir)
  if ckpt and tf.gfile.Exists(ckpt.model_checkpoint_path):
    print("Reading model parameters from %s" % ckpt.model_checkpoint_path)
    model.saver.restore(session, ckpt.model_checkpoint_path)
  else:
    print("Created model with fresh parameters.")
    session.run(tf.initialize_all_variables())
  return model

def train():
  """Train a in->out chat model using LINE talk data."""
  # Prepare line talk data.
  print("Preparing LINE talk data in %s" % FLAGS.data_dir)
  in_train, out_train, in_dev, out_dev, _, _ = data_utils.prepare_line_talk_data(
      FLAGS.data_dir, FLAGS.en_vocab_size, FLAGS.fr_vocab_size)

  with tf.Session() as sess:
    # Create model.
    print("Creating %d layers of %d units." % (FLAGS.num_layers, FLAGS.size))
    model = create_model(sess, False)

    # Read data into buckets and compute their sizes.
    print ("Reading development and training data (limit: %d)."
           % FLAGS.max_train_data_size)
    dev_set = read_data(in_dev, out_dev)
    train_set = read_data(in_train, out_train, FLAGS.max_train_data_size)
    train_bucket_sizes = [len(train_set[b]) for b in xrange(len(_buckets))]
    train_total_size = float(sum(train_bucket_sizes))

    # A bucket scale is a list of increasing numbers from 0 to 1 that we'll use
    # to select a bucket. Length of [scale[i], scale[i+1]] is proportional to
    # the size if i-th training bucket, as used later.
    train_buckets_scale = [sum(train_bucket_sizes[:i + 1]) / train_total_size
                           for i in xrange(len(train_bucket_sizes))]

    # This is the training loop.
    step_time, loss = 0.0, 0.0
    current_step = 0
    previous_losses = []
    while True:
      # Choose a bucket according to data distribution. We pick a random number
      # in [0, 1] and use the corresponding interval in train_buckets_scale.
      random_number_01 = np.random.random_sample()
      bucket_id = min([i for i in xrange(len(train_buckets_scale))
                       if train_buckets_scale[i] > random_number_01])

      # Get a batch and make a step.
      start_time = time.time()
      encoder_inputs, decoder_inputs, target_weights = model.get_batch(
          train_set, bucket_id)
      _, step_loss, _ = model.step(sess, encoder_inputs, decoder_inputs,
                                   target_weights, bucket_id, False)
      step_time += (time.time() - start_time) / FLAGS.steps_per_checkpoint
      loss += step_loss / FLAGS.steps_per_checkpoint
      current_step += 1

      # Once in a while, we save checkpoint, print statistics, and run evals.
      if current_step % FLAGS.steps_per_checkpoint == 0:
        # Print statistics for the previous epoch.
        perplexity = math.exp(float(loss)) if loss < 300 else float("inf")
        print ("global step %d learning rate %.4f step-time %.2f perplexity "
               "%.2f" % (model.global_step.eval(), model.learning_rate.eval(),
                         step_time, perplexity))
        # Decrease learning rate if no improvement was seen over last 3 times.
        if len(previous_losses) > 2 and loss > max(previous_losses[-3:]):
          sess.run(model.learning_rate_decay_op)
        previous_losses.append(loss)
        # Save checkpoint and zero timer and loss.
        checkpoint_path = os.path.join(FLAGS.train_dir, "chatbot.ckpt")
        model.saver.save(sess, checkpoint_path, global_step=model.global_step)
        step_time, loss = 0.0, 0.0
        # Run evals on development set and print their perplexity.
        for bucket_id in xrange(len(_buckets)):
          if len(dev_set[bucket_id]) == 0:
            print("  eval: empty bucket %d" % (bucket_id))
            continue
          encoder_inputs, decoder_inputs, target_weights = model.get_batch(
              dev_set, bucket_id)
          _, eval_loss, _ = model.step(sess, encoder_inputs, decoder_inputs,
                                       target_weights, bucket_id, True)
          eval_ppx = math.exp(float(eval_loss)) if eval_loss < 300 else float(
              "inf")
          print("  eval: bucket %d perplexity %.2f" % (bucket_id, eval_ppx))
        sys.stdout.flush()

#--decode --data_dir line_talk_data --train_dir line_talk_data
def decode():
  with tf.Session() as sess:
    # Create model and load parameters.
    model = create_model(sess, True)
    model.batch_size = 1  # We decode one sentence at a time.

    # Load vocabularies.
    en_vocab_path = os.path.join(FLAGS.data_dir,
                                 "vocab%d.in" % FLAGS.en_vocab_size)
    fr_vocab_path = os.path.join(FLAGS.data_dir,
                                 "vocab%d.out" % FLAGS.fr_vocab_size)
    en_vocab, _ = data_utils.initialize_vocabulary(en_vocab_path)
    _, rev_fr_vocab = data_utils.initialize_vocabulary(fr_vocab_path)

    # Decode from standard input.
    sys.stdout.write("> ")
    sys.stdout.flush()
    sentence = sys.stdin.readline()
    t = Tokenizer()
    tokens = t.tokenize(sentence.decode('utf-8')) 
    sentence = ' '.join([token.surface for token in tokens]).encode('utf-8') 
    print('([Morpho]:'+ sentence +')')

    while sentence:
      # Get token-ids for the input sentence.
      token_ids = data_utils.sentence_to_token_ids(tf.compat.as_bytes(sentence), en_vocab)
      # Which bucket does it belong to?
      bucket_id = len(_buckets) - 1
      for i, bucket in enumerate(_buckets):
        if bucket[0] >= len(token_ids):
          bucket_id = i
          break
      else:
        logging.warning("Sentence truncated: %s", sentence) 

      # Get a 1-element batch to feed the sentence to the model.
      encoder_inputs, decoder_inputs, target_weights = model.get_batch(
          {bucket_id: [(token_ids, [])]}, bucket_id)
      # Get output logits for the sentence.
      _, _, output_logits = model.step(sess, encoder_inputs, decoder_inputs,
                                       target_weights, bucket_id, True)
      # This is a greedy decoder - outputs are just argmaxes of output_logits.
      outputs = [int(np.argmax(logit, axis=1)) for logit in output_logits]
      # If there is an EOS symbol in outputs, cut them at that point.
      if data_utils.EOS_ID in outputs:
        outputs = outputs[:outputs.index(data_utils.EOS_ID)]
      # Print out French sentence corresponding to outputs.
      print(" ".join([tf.compat.as_str(rev_fr_vocab[output]) for output in outputs]))
      print("> ", end="")
      sys.stdout.flush()
      sentence = sys.stdin.readline()
      t = Tokenizer()
      tokens = t.tokenize(sentence.decode('utf-8')) 
      sentence = ' '.join([token.surface for token in tokens]).encode('utf-8') 
      print('([Morpho]:'+ sentence +')')


def self_test():
  """Test the translation model."""
  with tf.Session() as sess:
    print("Self-test for neural translation model.")
    # Create model with vocabularies of 10, 2 small buckets, 2 layers of 32.
    model = seq2seq_model.Seq2SeqModel(10, 10, [(3, 3), (6, 6)], 32, 2,
                                       5.0, 32, 0.3, 0.99, num_samples=8)
    sess.run(tf.initialize_all_variables())

    # Fake data set for both the (3, 3) and (6, 6) bucket.
    data_set = ([([1, 1], [2, 2]), ([3, 3], [4]), ([5], [6])],
                [([1, 1, 1, 1, 1], [2, 2, 2, 2, 2]), ([3, 3, 3], [5, 6])])
    for _ in xrange(5):  # Train the fake model for 5 steps.
      bucket_id = random.choice([0, 1])
      encoder_inputs, decoder_inputs, target_weights = model.get_batch(
          data_set, bucket_id)
      model.step(sess, encoder_inputs, decoder_inputs, target_weights,
                 bucket_id, False)

def main(_):
  if FLAGS.self_test:
    self_test()
  elif FLAGS.decode:
    decode()
  else:
    train()

if __name__ == "__main__":
  tf.app.run()


話題のTensorFlow・LINEトーク履歴を用いて対話ボットを作ってみた(1)


話題のTensorFlow・LINEトーク履歴を用いて対話ボットを作ってみた(2)

話題のTensorFlow・LINEトーク履歴を用いて対話ボットを作ってみた(3)

話題のTensorFlow・LINEトーク履歴を用いて対話ボットを作ってみた(5)