; oi: 2016

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)

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

開発のために必要なこと

テキストベースの「対話ボット」の開発にあたって、最近劇的な精度向上によって話題になっているGoogle翻訳のベースの技術として利用されている、Googleが開発したライブラリである「Tensorflow」の”encoder-decoder sequence-to-sequence model”を用いる。Tensorflowの利用にあたっては、C++とPythonのAPIが用意されているが、現時点(2016/11/14)では、Python APIが最も開発が進んでおり、手軽なため、以後Pythonで開発する事を前提とする。

ちなみにTensorflowは、 Apache 2.0 open source license の下で、OSSとして公開されており、本モデル以外にも、チュートリアルと共に様々なDNN応用例が実装されている。

参考1:「Google翻訳が進化!? 精度が向上したと話題に(ディープラーニングによる新翻訳システムが導入されたとみられています。)2016年11月12日 10時23分 更新」
http://nlab.itmedia.co.jp/nl/articles/1611/12/news021.html

Fig. Google翻訳にるArtificial Intelligenceの日本語訳結果

参考2:Tensorflow: Sequence-to-Sequence Models
https://www.tensorflow.org/versions/r0.11/tutorials/seq2seq/index.html#sequence-to-sequence-models

参考2:Tensorflow: Sequence-to-Sequence Models のチュートリアルでは、同モデルを用いて英仏翻訳を行う例が紹介されている。英語の単語のシーケンスを入力とし、フランス語の単語のシーケンスを出力するモデルである。この構造を「対話ボット」に適用する場合、以下の作業が必要となる。

1. 「対話ボット」学習データの整備
2. 「対話ボット」学習ロジックの実装
3. 「対話ボット」対話ロジックの実装

次に各作業の詳細について説明する。

1.「対話ボット」学習データの整備

チュートリアルの英仏翻訳学習用のデータは、対訳データと呼ばれるデータである。これは、英語の文章に対して、翻訳結果となるフランス語の文章が対となった形で、それぞれのファイルに格納され、各行が対をなしているデータである。また、英語もフランス語も各単語が半角スペースで区切られ、単語のシーケンスとなっている。

これに対して、今回対象とする対話ボットは、LINEトーク履歴データ(日本語)を用いた「対話ボット」であるが故に以下が必要となる。

・対話を形成する日本語文対の作成

LINE のトークデータは以下の手順で簡単にテキストデータとしてエクスポート可能である。

 (iPhoneの場合)出力対象のトーク画面→「設定」→「トーク履歴を送信」

エクスポートされたテキストデータは、以下のような形式である。

 ファイル名 :[LINE]トーク相手名.txt

ex1.[LINE]田中太郎.txt

 ファイル内容:HH:MM¥tトーク者¥t発話内容

ex2.
08:40 太郎 おはようー
08:40 太郎 ございます!
08:41 太郎 [スタンプ]
09:00 花子 今日は起きれたんだね。
09:02 太郎 [スタンプ]
 :  :  :

このファイルを用いて入力文と出力文のペアを作成する。

LINEトークの特徴として、一方的に短文を何度も発話する場合が往々にしてあるため、シーケンスの単位として、連続して発話した一連の内容を1単位とした。簡単のため、発話時刻の間隔は考慮せず、連続で発話したすべてをまとめて一つの単位とした。

ex3.
太郎 おはようーございます![スタンプ]
花子 今日は起きれたんだね。
太郎 [スタンプ]
 : :

また、発話の順序性については、本来は発話時刻を考慮してセッションを考え、対話となっているペア生成する必要がある。しかし、今回は上の処理で一連の発話内容を1単位とした後、最初の発話者を入力担当、次の発話者を出力担当と割り振り、順々にペアを生成した。

ex4. 入力部分
おはようーございます![スタンプ]
[スタンプ]
 : 

ex5. 出力部分
今日は起きれたんだね。
 : 

・日本語文の分かち書き

日本語や中国語のような文章中に区切り文字が存在しない文章は、明示的なシーケンスを表現するために、形態素解析を行い、意味のある単位(=形態素)で文を分解する必要がある。

今回はPure Pythonで開発された形態素解析エンジンであるJanome(蛇の目)を用いる。

Janome (0.2.8)
http://mocobeta.github.io/janome/

以下にサンプルコードを示す。

from janome.tokenizer import Tokenizer
    with open('../data/line_talk.in', mode = 'w') as fw:
        t = Tokenizer()
        for line in inputs:
            tokens = t.tokenize(line) 
            line = ' '.join([token.surface for token in tokens]).encode('utf-8') + '\n'            
            fw.write(line)

分かち書きを行った入力部分、出力部分をそれぞれファイルに格納する。

ex6. line_talk.in
おはようーございます ! [スタンプ]
[スタンプ]
 : 

ex7. line_talk.out
今日 は 起き れ た ん だ ね 。
 : 

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

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

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

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

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

どのような「対話ボット」を作るか?

「いわゆるAI」がみたすべき重要な要件としては、知性や人格を感じることにある。画像認識の分野における一般物体認識の圧倒的な技術革新は、とても役に立つ高性能な機械という印象を脱しえない。今回は、「何の役にも立たなくても良いが、人格を感じるにはどうするか?」にフォーカスしてみようと思う。

そのように考えていった時に、現時点で最も簡単にコンピュータが自然に人格を表現する方法は文字列によるコミュニケーションであろう。対話の仕方はいくつかあるが、対話時の表情や発された言葉の音声などの複合的な情報を用いた対話の方がより人間らしさを感じることは言うまでもないが、人工知能で再現することはそのメディアの分だけコストがかかる。

よって今回は、最もシンプルに内容だけで人格を感じさせられる可能性がある対話ボットであるテキストによる対話ボットを考える。つまるところ、これは以下のチューリングテストで想定している「機械」そのものである。

人間の判定者が、一人の(別の)人間と一機の機械に対して通常の言語での会話を行う。このとき人間も機械も人間らしく見えるように対応するのである。これらの参加者はそれぞれ隔離されている。判定者は、機械の言葉を音声に変換する能力に左右されることなく、その知性を判定するために、会話はたとえばキーボードとディスプレイのみといった、文字のみでの交信に制限しておく。判定者が、機械と人間との確実な区別ができなかった場合、この機械はテストに合格したことになる。


(出典:Wikipedia https://ja.m.wikipedia.org/wiki/チューリング・テスト )

また、対象とする対話は、人格を感じさせられることを狙っているため、複数人の大量の対話履歴データを用いて学習させるよりも、ある特定の個人の対話履歴を用いて学習させることが望ましい、と考えた。

そこで、最も頻繁に利用しているコミュニケーションツールであるLINEの履歴データの活用を考えた。私のLINEトーク履歴データを用いて「対話ボット」を学習させ、LINE上で行なわれているコミュニケーションを再現できれば、私という人格をある程度再現できたことに相当する。

今回のやるべきことを整理すると「LINEのトーク履歴データを学習し、ある個人の人格を感じさせられるテキストベースの『対話ボット』を開発すること」である。

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

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

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

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

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

「人工知能といえば」



最近のAIの隆盛はすさまじい。アカデミアにおける研究活動は勿論のこと、政府、企業におけるAI関連組織の編成や、AI関連予算のニュースも数多く取り沙汰されている。

一応、機械学習をかじったことがある身としては、AIと持て囃されている技術の大部分が、データから何かを統計的に学習するという点で従来の機械学習となんら変わりはなく見えてしまい、真新しさはほとんど感じない。

唯一の大きな違いとしては、計算機パワーの増大や、多種多様な学習データの増加によって、ディープラーニングという技術が、画像認識などの特定領域において従来技術に圧勝し、脚光を浴びたことであり、これこそがブームの正体だと思っていた。

しかしながら、先日友人と何気なく人工知能の会話になった際に「AIといったら、SiriやWatsonみたいなやつでは?」と言われてしまった。

文理を問わず、機械学習や最適化について、これまで学んだことの無い多くの人からしてみれば、AIの指し示す領域は、やはり「知性や人格をそこに感じる存在」なのである。SF映画の影響を色濃く受けていると考えられるが、人間の代替であり、延いては感情を兼ね備えて友人や敵になりうる存在としても想起されるようだ。そのAIの中で利用されている要素技術が、単なる統計だろうと、機械学習だろうと、ディープラーニングだろうと全く関係ないのである。

このような世間が考える「いわゆるAI」と「技術領域としてのAI」に乖離を感じたため、今回は「いわゆるAI」に主眼をおいて機械学習を活用してみようと思うに至った。

そこで、題材として選んだのが「対話ボット」である。

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

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

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

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


2016年11月14日月曜日

ソースコードをキレイに表示させる方法(Blogger)

highlight.jsを用いてソースコードを表示させる方法を備忘録代わりに記す。

STEP1. ソースコードを表示させるStyleの選択

highlight.js demoの左下部分からブログのスタイルを選ぶ。本ページのソースコードを表示し、以下のようにHTMLからcss名を確認する。
<link rel="alternate stylesheet" title="Github" href="styles/github.css">

STEP2. Bloggerテンプレートの更新


以下をテンプレートHTMLの<head></head>部分の中に埋め込む。
<link href="http://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.2/styles/default.min.css" rel="stylesheet"/>
<script src="http://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.2/highlight.min.js"></script>;
<script>hljs.initHighlightingOnLoad();</script>

STEP3. ソースコードの表示サイズの変更

BloggerテンプレートのカスタマイズからBloggerテンプレートデザイナーを開き、上級者向けから、cssの追加を選択する。
pre {
    font-size : 12px
}

STEP4. ソースコードの埋め込み

最後に、投稿編集画面において、HTMLとしてソースコードを以下のタグで挟んで追加するだけである。
<pre><code>〜</pre></code>
また、言語の指定は以下のようにクラスを指定するだけで良い。
<pre><code class = "python">〜</pre></code>

例.pythonコードの場合
if __name__ == '__main__':
    ratio = 0.9
    train_in = []
    train_out = []
    dev_in = []
    dev_out = []
    with open('../data/line_talk.in', mode = 'r') as fr:
        for i,line in enumerate(fr):
            if i &lt num_lines*ratio:
                train_in.append(line)
            elif i &lt num_lines:
                dev_in.append(line)

とても簡単であった。

参考:
http://blog.ayihis.info/2014/12/highlightjsblogger.html
https://highlightjs.org/static/demo/
https://39life.net/highlightjs/
https://github.com/isagalaev/highlight.js/tree/master/src/styles
http://takachan.hatenablog.com/entry/2014/08/15/232154
http://www.blogger-customize.com/2013/11/css-customize.html
http://highlightjs.readthedocs.io/en/latest/index.html

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

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

トップクラウンの円周 $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 (°)

2016年10月11日火曜日

代表的な共役事前分布について

本稿では、代表的な共役事前分布の一覧を示す。

まずはじめに事後分布、尤度、事前分布の関係は以下の通りである。

事後分布 $\propto$ 尤度 $\times$ 事前分布

共役事前分布とは、事後分布の関数形と事前分布の関数形が同じになるような、尤度に対する事前分布のことである。

<参照>ベイズ推定における共役事前分布の重要性について

以下に、代表的な共役事前分布を示す。

ベータ分布

尤度関数が二項分布、ベルヌーイ分布の場合、ベータ分布が共役事前分布となる。

尤度関数:ベルヌーイ分布、二項分布


ベルヌーイ分布

$P(x\mid \lambda) = \lambda^x(1-\lambda)^{(1-x)}$ for $ x \in \{0,1\} $
Takes a single parameter $\lambda \in [0,1] $

二項分布

$P(x\mid \lambda, n) = {}_n \mathrm{ C }_x \lambda^x(1-\lambda)^{(n-x)}$
${}_n \mathrm{ C }_x = \frac{ n! }{ x! ( n - x )! }$
Takes parameters $\lambda \in [0,1]$ and $n \geq 0, n \in \mathbb{ Z }$

共役事前分布:ベータ分布

$$Beta(\lambda|a,b) = \frac{\Gamma(a+b)}{\Gamma(a)\Gamma(b)}\lambda^{a-1}(1-\lambda)^{b-1} $$

ディリクレ分布


尤度関数がカテゴリカル分布あるいは多項分布の場合、ディリクレ分布が共役事前分布となる。

尤度関数:カテゴリカル分布、多項分布


カテゴリカル分布

$P(x\mid \boldsymbol{\lambda}) = \displaystyle \prod_{ i = 0 }^K \lambda_i^{x_i} $
Takes $K$parameters $\lambda_i \in [0,1] $ where $\displaystyle \sum_{ i = 1 }^{ K } \lambda_i = 1$

多項分布

$$
P(x\mid \boldsymbol{\lambda}, n) =
\begin{cases}
\frac{ n! }{ x_1! x_2! \cdots x_k! } \displaystyle \prod_{ i = 1 }^K \lambda_i^{x_i} & (when \sum_{ i = 1 }^{ K } x_i = n ) \\
0 & ( otherwise )
\end{cases}
$$
Takes $K+1$ parameters $n$ where  $n \gt 0$, and $\lambda_1,...,\lambda_K$ where $\lambda_i \in [0,1] $ and $\displaystyle \sum_{ i = 1 }^{ K } \lambda_i = 1$

共役事前分布:ディリクレ分布

$$Dir(\boldsymbol{\lambda}|\boldsymbol{a})=\frac{\Gamma(\sum_{ i = 1 }^{ K } a_i)}{\prod_{ i = 1 }^{ K }\Gamma(a_i)}\prod_{ i = 1 }^{ K } \lambda_i^{a_i-1}$$
Takes $K$parameters $a_1,...,a_K$ where $a_i gt 0$
Takes $K$supports $\lambda_1,...,\lambda_K$ where $\lambda_i \in [0,1] $ and $\displaystyle \sum_{ i = 1 }^{ K } \lambda_i = 1$

その他

その他、有名な尤度関数と共役事前分布の関係としては以下のようなものがある。

【事後分布】正規分布, 【尤度関数】正規分布 ,【共役事前分布】正規分布
【事後分布】ガンマ分布, 【尤度関数】ポアソン分布 ,【共役事前分布】ガンマ分布
【事後分布】ガンマ分布, 【尤度関数】正規分布 ,【共役事前分布】ガンマ分布


2016年10月10日月曜日

今更聞けない「GRIT:やり抜く力」の重要性と身につけ方


概要

多くの有名企業が採用基準に加えるなど、最近注目を浴びている「GRIT:やり抜く力」について、豊富な事例・科学的な根拠に基づいたまとめられた一冊である。

学力の経済学などでも教育について統計的な根拠を用いて緻密な説明がなされていたが、本書でも多くの統計情報を基に論じられるため、科学的にGRITに関して理解が深まると同時に、それを裏付ける多くの偉人の具体的なエピソードはとても読み応えがある。


構成とおすすめの読み方

大きく分けて3つのパートから構成されている。

PART1は、GRITとは何か、GRITの重要性について、
PART2は、自分自身がGRITを伸ばすための方法について、
PART3は、自分以外のGRITを伸ばすための方法について、
それぞれ記されている。

そのため、自身がGRITを身につけたい、向上させたいと考える方は、PART1,2を中心に読むと良い。
また、子供や部下のGRITを伸ばしたいと考える親、教育者、上司のような方はPART1,3を中心に読むことをおすすめする。

どのようにGRITを伸ばすかなどの詳細については、実際に本書を手にとって読んで頂きたいが、今回は備忘録代わりにGRITの重要性についてまとめ、まずはじめに多くの人が悩むであろう「取り組むべきこと」の発見方法についてのみ深掘りする。

GRITとは?

本書において、GRITの定義をビシッと一言で記載されておらず、明確な定義はないのであるが、一言でまとめると以下になるであろう。

GRIT(やり抜く力)とは(長期的な目標に向けた)「情熱」と「粘り強さ」である

情熱とは、ある重要な目標を達成するために興味を持ち続け、練習し続けることである。熱心さ、夢中や熱中とは若干ニュアンスが異なり、一つのことにじっくりと長い間取り組む姿勢のことである。
また、粘り強さとは、困難や挫折があっても諦めずに、目的達成のために希望を持って努力をし続けることである。

本書の中で引用されていた300名の偉人を対象にして実施された調査によると、これらを持ち合わせた人が必ず偉業を達成できるとは限らないが、偉業を達成できた人の中でも特に大きな功績を挙げた人はGRITが優れていたとのことである。

なぜGRITが重要なのか?

それでは、なぜGRITが重要なのであろうか。

本書では、しばしば才能とGRITを対比させる形で論じられるが、才能とGRITがどのように偉業と関連しているかは、以下の式にて示される。

達成の方程式
スキル=才能×努力
達成=スキル×努力

$$ accomplishment= gift*grit^2 $$

つまるところ、偉業の達成は、才能に比例し、努力の二乗に比例するのである。2倍努力し続ける人は、4倍の業績をあげるということである。
よって、才能が突出していなくても、GRITを鍛え上げることで抜群の業績を出すことが可能となる。

しかしながら、なんでもかんでも必死にやり続ければよいというものではなく、ブレない目標、動機の持続性が必要であり、着実にスキルを向上させるためのカイゼンも必要であると本書では述べられている。

何に取り組むべきか?情熱はどのように抱くのか?


自分が何を取り組むべきか、自分が本当にやりたいことは何かをわかっている人は少ないのではないだろうか。重要な目標、目的を決めきれないまま、なんとなく学業や仕事をしている人に対して、道しるべとなる情報が記されていたので、整理しようと思う。

本書によると、深い情熱は「興味」と「目的」によって支えられるという。

興味を掘り下げる


興味の段階として、発見と発展の2つの段階があると述べている。

1つ目は、自分自身が何に興味を覚えるかを知る「発見」の段階であり、
2つ目は、その興味を持ち続け、興味をさらに掘り下げていく「発展」の段階だという。

発見の段階では、まずは自身の好き嫌いに着目し、とりあえず好きなことをスタートさせて見ることが重要であり、何をしている時間が最も楽しいかを知る必要がある。一朝一夕では自分の興味を発見できないのが普通であり、時間をかけてまずは方向性を探るところから始めてみようとのことだ。

私個人としては、この発見の段階において「本当に楽しい、心から興味がある」という確信は、「どのような条件、どのようなタイミングで得られるのか?」にとても関心があったが、残念ながらその辺にはあまり触れられていない。おそらく興味関心は、遺伝というよりは、個々人の原体験によって、決まるものではないかと思うのだが。

次に発展の段階であるが、興味のあることをさらに掘り下げていくフェーズである。誰でも新しいことに興味を抱くということに変わりはないが、GRITが弱い人は、全く違う新しさを持つことに目移りしてしまい長続きしないと言われれている。対して、GRITの強い人は、興味のあることの微妙な差異に新しさを覚えるらしい。興味のあることに常に疑問をもち、その答えを探し続けることで興味を掘り下げ、新しさを見出すのである。そして、その微妙な差異にさらに興味を覚え、情熱をドライブし続ける。

自分の身近にいるエキスパートも微妙な差異に気づく。他の人がしっかりとレビューしても気付かない、設計上の考慮漏れや検証方法の穴に瞬時に気づき、的確な質問をするケースを何度も見てきた。エキスパート自身が興味を掘り下げていく上で、そのような細部にも目を配って、考慮し続けてきたことであるから、いわゆる直観が働くのであろう。

目的を見出す


興味のあることに対して真剣に取り組むことで、自分の取り組んできたことに大きな目的や意義を見出すという。ポイントとしては、目的起点で興味のあることを見つけるのではなく、まず興味のあることを色々と模索し、興味に基づいて真剣に取り組み、それを改めて振り帰って目的を見出すことが一般的ということである。

社会的意義や、他者を助けたいという目的を思ってから物事に取り組むことも決して悪いことではない。しかしながら、ある研究成果によると、目的に加えて、その仕事自体への興味がある場合の方が、目的だけの場合に比べて、継続的に努めるのだという。

私個人としても、何か特別な経験がないといきなり目的を決めることは困難に思う。やはり興味のある分野をやってみて「目的を見出す」ことが自然な気がする。孔子も論語の中で、同様の段階を経ており、ひたすら学問を続けてみて、50歳にしてやっと真の目的(天命)を見出せたのだと思う。「とりあえず続けてみて」といった軽い思いで始めたわけではなかったかもしれないが、真剣に取り組んだ後だからこそ、見出せたのだろう。

子曰く、
吾れ十有五にして学に志ざす。
三十にして立つ。
四十にして惑わず。
五十にして天命を知る。
六十にして耳従う。
七十にして心の欲する所に従って、矩を踰えず。

さいごに

兼ねてから、自分自身のパフォーマンスは明らかにモチベーションに依存すると感じていたため、どうすればモチベーションが上がるのか、情熱が抱けるのだろうか、と思っていた。本書は、情熱を「興味」と「目的」にブレイクダウンし、科学的な根拠に基づいて、それぞれの向上の方法について論じられた良書であった。

何をすべきか悩んでいる方、成果が出ずに悩んでいる方などは是非ご一読いただきたい。

2016年6月12日日曜日

今更聞けない「モデリング」の重要性:モデリングをはじめて勉強する人におすすめの入門書4点

モデルを基にした思考方法は、社会人に必須のスキルの一つである。プレゼンテーションにおける表現や、多様なバックグラウンドを持つ他者とのコミュニケーションにも、モデリングの考え方の一つである、抽象化・単純化の思考は必須である。

今回は、近年ますます注目を浴びているモデリングの方法やモデルベースの思考法を習得し、実ビジネスに応用するための入門書を紹介する。

モデリングの方法

スーパープログラマーに学ぶ 最強シンプル思考術




本書は、モデルリングの入門書として最適な書籍といえよう。本書で取り扱うモデルは、様々なモデルの中でも最もシンプルな「四角」と「線」だけで構成されるモデルを扱っている。このモデルの書き方はシンプルであるが故に非常に汎用性の高いモデリングの方法となる。本書でモデリングの基礎を押さえておけば、その他の様々なモデリング手法、記法(UML、SysML、BPMN、OWLなど)を習得するための準備になることは想像に難くない。また、非常に多くの卑近なモデル例、それらのモデルの良い点、悪い点、さらには悪いモデルの改善プロセス、モデルの現実的な活用例までが丁寧に説明されている。

特筆すべきモデリングの基本は、同じ対象であっても、目的や視座によって、出来上がるモデルが変わることである。これはモデルの良し悪しとは別の次元の話である。どの側面にから対象を観察するかで、見方は変化するが、それはどれも間違っていない。射影する方向が異なるだけである。

もの・こと分析で成功するシンプルな仕事の構想法

対象をモデル化・シンプル化するための一つの方法として、モノとコトに分けて考える手法がある。そちらも参照して欲しい。

「モノとコトから考える仕事の本質とは」

モデルの活用


基本的なモデリングに慣れた後は、モデルの活用に向けて以下の書籍を読むことをお勧めする。

アナロジー思考




抽象化し、アナロジー(類推)思考を行うことは、誰でも多かれ少なかれ経験があるだろう。本書にはそのアナロジー思考に焦点を当て、最大限有効活用することで、新しいアイデアを生むための方法論が書かれている。対象物を、ある側面から見て本質的な部分に絞り、抽象的に構造化することで、類似の構造を持つケースからアイデアを借りてくることが可能になる。自分が理解している別の領域におけるノウハウを、類似の構造を持つ領域に適用することで容易に解決策が思いついたり、新しい商品のアイデアが思いついたりできるようになるのである。
アナロジー思考の根底にあるのは、やはりモデリングによるシンプル化の技術である。アナロジーを用いるためには、対象の特徴を捉え、余計な部分を除外し、シンプルにモデリングする必要がある。本質的な部分のみを適切なレベル感でモデリングできれば、他の領域からアイデアを持ち込んで適用する場合も効果的に適用できる。

学習する組織




言わずと知れたベストセラー経営書である。本書の前半は、システム思考について書かれている。システム思考の肝は、経営における重要な事象をモデリングし、中長期的変化、挙動パターンを予測することにある。人間は認知的限界から、線形の因果関係のみを短絡的に捉えがちであるが、その背後には、その状況を支配する非線形な因果関係を持つシステムが存在し、ダイナミックに複雑なパターンを生成しているのである。
システム思考においては、複雑な非線形の因果関係のうち、特に重要なフィードバックループにフォーカスし、モデルをよりリッチに表現する。フィードバックループとは、ある要素aが、別の要素bを引き起こすという、a->bという一方向の因果関係だけではなく、b−>aという逆向きの因果関係を考えるものである。それら二つの因果関係がある場合、人間が予測する結果よりはるかにバリエーションに富んだ挙動を示す。自己強化型のループの場合は、指数関数的に増加、減少したり、バランス型のループの場合には振動しながら減衰することもある。

一例を紹介する。Stock and flow diagram of New product adoption model(Wikipediaより引用)の場合、新製品の潜在的なユーザーが、実際に利用するまでの関係性を以下のようなフィードバックループを用いて、記述する。


上記モデルについて、シミュレーションを行うことで、以下のような、ユーザー増加に関する動的特性を観察できる。



現状見えている事象や出来事から、その背後に存在するシステムを見出すには、状況を引き起こしている要素を発見、選択し、フィードバックの関係性を含め、システムの構造を必要十分にモデリングするスキルが肝要となる。「スーパープログラマーに学ぶ 最強シンプル思考術」なども参考にしながら、モデリング自体に慣れ、必要がある。
フィードバックループを含むシステミックなモデルが描けるようになれば、上記のような動的な特性の把握が容易になり、挙動パターンの理解、システム自体の構造的な改革を行えるようになるだろう。

まとめ

今回は、あつかう対象が複雑化し、情報が氾濫する現代に必須のスキルである、モデリングの方法、モデルを基にした思考法に関する書籍を紹介した。今回紹介したようなシンプルに考える方法に興味を持って頂けたら幸いである。



2016年6月5日日曜日

最大限に不確実性を抑え込む仕事の進め方〜「なぜ、あなたの仕事は終わらないのか」を読んで〜






本書には、米国Microsoft本社にてWindows 95, Windows98の基本設計を行い「右クリック」「ダブルクリック」「ドラッグ&ドロップ」の概念を現在の形に仕立て上げた、中島聡さんの仕事論が記されている。中島さんの学生時代から現在の仕事に至るまでの様々な実体験をベースにして論じられるため、想像しやすく納得性がある。多くの為になるTipsが書かれていたが、本記事では要点を絞り、紹介する。

本書で推奨する仕事の進め方:最初の2割で仕事の8割を終わらせる


本書では以下の仕事の進め方を推奨している。

時間がある時にこそ、全力疾走で仕事し、締め切りが近づいたら流す

最初の2割で仕事の8割を終わらせる

これによるメリットは、いくつも記されているが、主要なものは以下の2つであろう。

仕事に余裕が生まれる

最初の2割で8割分の仕事を行うため、残りの8割の時間を使って、残り2割の仕事をすれば良い。これだけ余裕があると仕事を確実に完成させることができる。すべての仕事が締め切りにおわれている状況と比較するだけでも、仕事の品質が上がることが容易に想像できるであろう。

早い段階で延期リスクを上申できる

2割の時点で8割の仕事が終わっていなかった場合、仕事が延期する可能性が非常に高い旨を「2割の時期」に伝えられることである。これが8割の段階になって伝えられても、マネジメント側としてはどうしようもない場合が多いが、早い段階であれば再調整が可能な場合が多いのである。

いかに仕事の不確実性を抑え込むか?

この考え方を整理すると「早い段階で以下に仕事の不確実性を抑え込むか?」に集約されるであろう。本書は暗黙的に、不確実性を多分に含むクリエエイティブな知的活動を「仕事」として捉えている。コンビニのバイトや工場の生産ラインのようなルーティンワークは、ほとんど不確実性を含まないため、2割の期間で8割の仕事などできるはずがない(完全な体力勝負になるだけであろう)。

確かに、不確実性を多く含む活動の作業規模・作業時間の正確な見積をするには、とにかく早い段階で着手し、8割の仕事を進めてみることが最も効果的なのは間違いない。すでに見積の段階で着手し、8割の仕事を終えているのだから。

不確実性さえなくなってしまえば、あとはひたすら完成度を高める作業を余裕を持って行えばよく、精神的にも余裕があり、プロダクトの品質は安定的に向上する。

時間術の先には?


上述のような有益な時間術論を展開後、「いかにしてやりたいことに取り組むか?」といった熱い仕事論が待ち構える。時間術はやりたくないことを行う時間を最小化する手段でもあると述べ、時間術によって生まれた時間をやりたい仕事、やりたいことに費そう、という内容となる。そして最後に以下のメッセージで全体をまとめている。

「一度しかない人生、思い切り楽しもうぜ」

現実的な仕事の進め方から熱い仕事論まで非常に濃い内容の一冊であった。

2016年6月4日土曜日

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

本稿では、実際の大手キャリアを解約し、iPhone SE SIMフリー版とFREETEL SIMカードを購入し、利用するまでの手順について、自身の経験を基にまとめた。

不本意ながら、毎月大手キャリアに高額な通信料を支払っている人は大勢いるであろう。最近、格安SIMという単語はよく聞くが実際どうすればいいかわからない。大手キャリアから乗り換えたいけれど、乗り換え方がよく分からない。そのような何かしらの不安を抱える方々に参考になれば幸いである。

大きな流れは、以下の通りである。本稿は以下の流れに合わせて詳細を記す。また、最後に2年間分の費用の試算結果を加える。


  1. 格安SIM対応の端末の購入
  2. MNP予約番号の取得
  3. 格安SIMカードの購入
  4. APN設定


1. iPhone SE 64GB SIMフリー版の購入



SIMフリー版のiPhoneは、以下の2通りの買い方がある。というか2通りしかない。
(SIMフリー版のiPhoneはどの量販店、キャリア店舗でも購入できない)

 ・アップルオンラインショップ
   http://www.apple.com/jp/shop/buy-iphone/iphone-se
 ・Apple Store
   http://www.apple.com/jp/retail/

Apple Storeに直接赴いて購入する際は、その場で現在保有しているiPhoneの下取りが可能であり、下取分をiPhone SE購入代金から差し引いてもらえる。
しかしながら、Apple Storeでは、事前にiPhoneSEを予約することができないため、自由時間の多くない会社員の方は、オンラインショップにて予約し自宅に配送の方が現実的であろう。
オンラインショップで購入する場合も、後から郵送にて下取に出すことは可能である。後にApple Storeギフトカードが郵送される。
  
 ・Apple Renew プログラム
   http://www.apple.com/jp/iphone/trade-in/
   https://reuserecycle-apple-jp-asia.brightstar.com/is-bin/INTERSHOP.enfinity/WFS/BrightstarAPAC-JPAPPCON-Site

オンラインショップで予約・購入する場合は、在庫薄らしく、到着に1ヶ月程度はかかると思っておいた方が良い。私の場合は、手元にiPhoneが届くまでに25日を要した。

 ※ iPhone SE 64GB SIMフリー版の費用
   ーiPhone SE 64GB SIMフリー版 59,800円
   ーAppleCare+ 12,800円

その他の注意点として、量販店などで購入できるキャリア毎にSIMロックされたiPhoneは格安SIM(MVNO)利用のためにSIMロック解除手続きが必要となるので注意されたい。特にSoftBank系MVNOは存在しないため、SoftBankスマホでMVNOを利用したい場合、SIMロック解除が必須となる。(MVNOのほとんどがドコモ系)

2. MNP予約番号の取得

既存のiPhoneなどの電話番号を引き継ぎを行いたい場合は、MNP(Mobile Number Portability)予約番号の取得が必須となる。

SoftBankの場合、下記URLから電話で取得可能で、オペレータの方と5分程度の会話で非常にスムーズに取得できる。
電話が完了すると同時に、当該電話番号にSMSにてMNP予約番号、有効期限が送られてくる。
 http://faq.mb.softbank.jp/smart/detail.aspx?cid=369&id=369&categoryId=0&catParentName=&categoryName=

取得したMNP予約番号は、15日間有効である。また、引き継いだ時点をもって初めて、解約完了となるので、15日間MNPを行わない場合、今までどおりのキャリアに支払うこととなる。

 ※SoftBank解約時の費用
   ー 契約解除料  9,500円 (更新月でない場合)
   ー MNP転出料 3,000円

3. FREETEL SIMカードの購入

格安SIMカードの多くは、オンラインで購入可能であるが、MNP手続き完了後は既存のSIMカードは利用できないため、新しいSIMカードが届くまでは空白の期間となってしまう。
なので、できれば量販店に赴いて、MNP手続きをしてもらうと空白時間ほぼゼロで移行できるようになる。
私の場合は、ヨドバシカメラ秋葉原店にてFREETEL SIMカードを購入したが、1時間程度でMNP手続きが完了した。(この時点でSoftBank側も解約手続きを終えており、利用者は特に何もする必要はない。)

 ・その他のFREETEL取り扱い店舗は以下のURLを参照されたい。
  https://www.freetel.jp/shoplist/

 ・FREETEL SIMカード購入時に必要な書類、情報は以下の通りである。
    ー運転免許証
    ーMNP予約番号(2.で取得したもの)
    ー購入したiPhoneSE(購入したSIMカードをその場で挿入し、利用可)
    
  ・また、以下のことに注意されたい。
    ーMNP手続き完了後、既存のiPhoneに含まれたSIMカードは利用できなくなる。
    ー店舗の営業時間とは別に、MNP対応可能時間がある(ヨドバシ秋葉原店の場合は、9:30〜20:00のみ対応可)

 ※FREETEL SIMカード購入時の初期費用
   ー契約事務手数料(新規契約時) 3,000円

3'. 購入したFREETEL SIM カードについて

今回、私が購入したのは、以下のSIMカードである。
FREETEL SIMの最大の特徴は従量制であることだ。定額時の差分が無駄になってしまうのではなく、ひと月ごとに使った分だけ支払えば良いので、無駄なく利用できる。
また、iPhone専用の本SIMカードは、Apple Storeからのダウンロード時のパケットは対象外となるため、やたら多いアップデートも気兼ねなく行うことができる。

  ・FREETEL SIMカード料金(従量制)
   「使った分だけ安心プラン(ドコモ回線)」 for iPhone
   https://www.freetel.jp/price/iphone/



 


 



※FREETEL SIMカードの毎月の費用(実績を基に予測)
   ー月額使用料   2,220(円/月) ∵自分の4GB程度利用している(2016/2)
     (3GBに抑えれば、1,600(円/月)となる)


以下、「使った分だけ安心プラン」月額基本料金である。

種類
100MB
1GB
3GB
5GB
8GB
10GB
データ専用
¥299
¥499
¥900
¥1,520
¥2,140
¥2,470
データ専用+SMS
¥439
¥639
¥1,040
¥1,660
¥2,280
¥2,610
音声通話付
¥999
¥1,199
¥1,600
¥2,220
¥2,840
¥3,170

※表示価格は税抜きです。
※電話のみのお申し込みはできません。
※iPhoneをご利用の場合は「FREETEL SIM for iPhone」を使用することで、AppStoreダウンロード時に発生するパケットを課金しません。

4. APN設定

iPhone端末に手に入れたSIMカードを挿入するだけでは、まだインターネット通信はできない。最後に各SIMカード発行会社のAPNを設定する必要がある。APNとは、Access Point Name の略で接続先を表す識別子のことだ。大手キャリアにてiPhoneを購入した場合は、事前にこの設定を終えているため利用者はAPNについて気にする必要はない。今回の場合、FREETELのAPNをiPhone端末に設定する必要がある。

どのようにAPN設定するか?

APN設定といっても、何ら難しいことはない。WiFi接続可能な環境にて、各社が出しているAPN設定のためのプロファイルをダウンロード&インストールすれば良いだけである。(30秒程度)

詳細は、以下を参照されたい。

端末別APN設定方法|高速の通信速度と充実の料金プラン FREETEL(フリーテル)の格安SIMカード

※注意点としては、APN設定後の端末を「既存のiPhone端末バックアップ」から復元する場合、本設定が消えてしまう場合がある。私の場合もAPN設定が消えてしまったため、再度プロファイルのインストールを行った。

5. 費用計算

SIMフリー版 iPhoneと格安SIMカードを用いた場合の料金シミュレーションを行う。

まず、費用部分をまとめると以下のようになる。

・イニシャルコスト
 ※ iPhone SE 64GB SIMフリー版の費用
   ーiPhone SE 64GB SIMフリー版 59,800円
   ーAppleCare+ 12,800円
   
 ※SoftBank解約時の費用
   ー 契約解除料  9,500円 (更新月でない場合)
   ー MNP転出料 3,000円
   
 ※FREETEL SIMカード購入時の初期費用
   ー契約事務手数料(新規契約時) 3,000円

・ランニングコスト
 ※FREETEL SIMカードの毎月の費用(実績を基に予測)
   ー月額使用料   2,220(円/月) (4GB程度)
   
次に、今回の費用を2年間利用するとして、月額換算してみる。
 
 総イニシャルコスト: 88,100円
  → 88,100円 / 24ヶ月 = 3,671(円/月)
 
 となるので、元のランニングコストと合計し以下の金額となる。
 
  5,891(円/月) × 1.08 = 6,362(円/月) (税込)
  
これは、これまでの月額利用料に比べ、2500円以上安いため、2年間利用すると、60,000円以上の差となる。

また、2016年6月3日より、FREETELのキャンペーンも行われており、先ほどのランニングコストから、6ヶ月間1GB分の料金が差引かれ、以下の金額となる。

  1,721円(6ヶ月間のみ)

よって、さらに3,000円分お得になる。

 ・最大1年間0円キャンペーン
  https://www.freetel.jp/campaign/camp_20160603/?_ga=1.226356378.124820628.1465028243

6. 電波や通話品質について

格安SIMということで電波について気になる面はあったが、今現在何の問題もなく利用できている。混雑時や地方に行った際の電波状況については、再度追記していこうと思う。

7. まとめ

iPhone SE SIMフリー版とFREETEL SIMカードを購入し、利用するまでの手順についてまとめた。結果として、非常に簡単に乗り換えることができた。格安SIMは怖くも面倒でもなかった。

格安SIMに興味があるが、最初の一歩を踏み出せていない方々に本稿が参考になれば幸いである。

2017/3/12 追記:2016/6~2017/2までの利用実績を基に、累積利用料金について算出してみたので、併せて参照されたい。
【続報】流行りの格安SIMに乗り換えてみた(iPhone SE 64GB SIMフリー版+FREETEL SIM for iPhone)


 


 

お酒が進む!ポテサラ編

「家飲みを極める」を読んでさっそく再現してみたポテトサラダ。
   応用の利くレシピとは?〜家飲みを極める〜




記載されていた要素を整理してみると、

おつまみに最高なポテサラ=
 ホクホクじゃがいも×アクセント素材×オニオンスライス×コクを出す味付け×食感×風味

と考えられる。

今回は以下の組み合わせにしてみた。

 ホクホクじゃがいも×オイルサーディン×オニオンスライス×オリーブオイル&マスタード×アーモンド×バジル


レシピは以下のとおり。

①オニオンスライスを作る
詳しくは本を読んでいただきたいが、ポイントは
・辛みを抑えること
・シャキシャキ感を残すこと
の2点である。
繊維に沿って薄くスライスしたあと、1%の塩水に30分ほど浸しておく。
(10分でも可)

②ホクホクじゃがいもを作る
よく洗ったじゃがいもに十字の切れ込みを入れておく。(あとで皮が剥きやすくなる)
ラップでふんわり包み、電子レンジで3分加熱。このとき、レンジの真ん中ではなく、熱の伝わりやすい外側に置くこと。
じゃがいもをひっくり返してさらに2-3分加熱する。

じゃがいもの加熱についてはこちらを参照
応用の利くレシピとは?〜家飲みを極める〜

③じゃがいもの皮をむき、潰す
熱いうちに皮をむく。キッチンペーパーでくるみ、竹串などでむくとやりやすい。
ホクホクなので簡単に潰せる。

④オニオンスライスと味付け用材料を混ぜる
マヨネーズは大さじ2程度、オリーブオイル大さじ2、マスタード小さじ1、胡椒適量

⑤アクセント素材を混ぜる
オイルサーディン1/2缶程度を潰しながら混ぜ合わせる。

⑥盛り付け&仕上げ
アーモンドスライスをオーブンで軽く焼いて上に乗せる。
最後にバジルを振りかけて完成。


オニオンやオイルサーディンのアクセントが日本酒にピッタリだが、
オリーブオイルやバジル、マスタードの味付けが白ワインにもよく合う。


さらに、ポテサラの素材を変えれば無限にアレンジ可能なので今後も試していきたい。

アクセント素材
  →ベーコン、アンチョビ、ツナ、生ハム、スモークサーモンなど

コクを出す味付け
  →バジルペースト、ゴマ油など

食感
  →クルミ、ナッツ、揚げワンタンなど

風味
  →小ネギ、紫蘇、ハーブ、胡麻など


ちょっとした工夫で家飲みをより楽しくするために、是非この本を読んでいただきたい。


oi: 応用の利くレシピとは?〜家飲みを極める〜