TeXsorflow(LaTeXニューラルネットワーク)を作った

これはMuroran Institute of Technology Advent Calendar 22日目の記事です.

はじめに

ニューラルネットワーク構築用パッケージ TeXsorflow*1を作りました. つまり,

LaTeXニューラルネットワーク

実装しました.

ニューラルネットワークとは

機械学習の一分野で人間の脳を数理モデル化したものです.教師データの入力を基に誤差を算出し,誤差を少なくするように重みやバイアスを調整することで学習を行います.

f:id:muscle_keisuke:20171222093822p:plain 引用: http://zuqqhi2.com/nn-most-decent-4

作ったもの

今回はLaTeXで分類問題を解くニューラルネットワークを実装しました.教師データから学習を行い,推論フェーズで出した答えと教師データの解答をPDFに出力します.

想定する環境

データ

今回はirisのデータを使って,種(Species)の分類を行います. がく片長(Sepal Length),がく片幅(Sepal Width),花びら長(Petal Length),花びら幅(Petal Width)を入力として持ち,種(Species)を出力とします.

モデル

モデルは以下のような設定です.

  • 入力層ユニット数 4
  • 隠れ層ユニット数 3
  • 隠れ層レイヤー数 1
  • 出力層ユニット数 3

f:id:muscle_keisuke:20171222102443p:plain

実装しなければならないこと

実装

主に使うパッケージ

  • FP
  • pgfmath
  • arrayjobx
  • datatool
  • ifthen

マクロなどのグローバル化

グローバルなマクロ

今回,繰り返し処理はpgfパッケージにあるforeachコマンドを使って行います.

\foreach \x in {1,...,100}{%
  \pgfmathsetmacro{\result}{10}
}

その際に,foreach内でマクロに代入を行うと,グループ内はローカルのため,次のループに入る時,値が消えます. 上の例だと,\resultに10を代入しても,繰り返すと,次の代入まで0になります. よって,マクロを以下のようにグローバルにします.

\xdef{\result}{\result}
グローバルな配列

LaTeXにはarrayjobxという配列構造を扱えるパッケージがあります. 使い方は以下を参考にしました.

  • arrayjobx

天地有情 [LaTeX] arrayjob --- 配列データ構造を操作する

さて,このarrayjobxで生成した配列はグローバルではありません,なので,前節と同じ理由で値が消えたりします.なので,これをグローバルに対応させます.

\let\newglobalarray\newarray
\patchcmd{\newglobalarray}{\edef}{\xdef}{}{}

配列を生成する\newarrayを\newglobalarrayにコピーして,定義内の\edefを全て\xdefに置き換えて,グローバル化します.

tex.stackexchange.com

次にマクロを展開して配列に代入できるマクロを定義します. 本来,arrayjobxは以下のようにして値の代入ができます.

\newglobalarray\hogearray
\hogearray(1,1)=1

しかし,代入する対象が展開が必要なマクロなどの場合は,展開の順序を考慮する必要があります. よって,代入の対象を展開してから配列に代入するマクロを定義します.

\newcommand{\assignArray}[2]{%
  \begingroup\edef\xx{\endgroup\noexpand#1={#2}}\xx%
}%

活性化関数の実装

隠れ層の活性化関数にはReLUを使い,出力層の活性化関数にはソフトマックス関数を使います.

ReLU

ReLUとその微分は以下のような式で表します.

[tex: \displaystyle f(x) = \max(0, x)]

[tex: \displaystyle f'(x) =
\left\{
\begin{array}{l}
1 & (x > 0) \\
0 & (otherwise)
\end{array}
\right.
]

これは数式を解釈できるpgfパッケージのpgfmathsetmacroコマンドを使えば簡単に実装できます.

\newcommand{\activationFunction@texnn}[1]{%
    \pgfmathsetmacro{\x@texnn}{#1}%
    \pgfmathsetmacro{\y@texnn}{max(0, \x@texnn)}%
}%
% arg1: x
\newcommand{\activationFunctionDiff@texnn}[1]{%
    \pgfmathsetmacro{\x@texnn}{#1}%
    \pgfmathsetmacro{\y@texnn}{\x@texnn>0 ? 1 : 0}%
}%
ソフトマックス関数

出力層はソフトマックス関数を活性化関数とします. ソフトマックス関数とその微分は以下の式で表します.

[tex: \displaystyle f(x_i) = \frac{\exp(x_i)}{\sum_{j=0}^N \exp(x_j)}]

[tex: \displaystyle f'(x_i) =
\left\{
\begin{array}{l}
f(x_i)(1 - f(x_i)) & (i = j) \\
-f(x_i)f(x_j) & (i \neq j)
\end{array}
\right.
]

こちらはfpパッケージという固定小数点演算用のパッケージを使って実装しています.

% arg1: x
\newcommand{\softmax}{%
    \checkZs@iris(3,1) % 配列から値を読む(\cachedataに格納される)
    \pgfmathsetmacro{\z@one}{\cachedata} % \cachedataを別の変数に格納
    \checkZs@iris(3,2)%
    \pgfmathsetmacro{\z@two}{\cachedata}%
    \checkZs@iris(3,3)%
    \pgfmathsetmacro{\z@three}{\cachedata}%
    \FPeval{\z@denom}{(exp(\z@one)) + exp(\z@two) + exp(\z@three)} % ソフトマックスの分母部分を計算
    \FPeval{\y@texnn}{(exp(\z@one) / \z@denom)} % ソフトマックスの分子部分を計算
    \assignArray{\As@iris(3, 1)}{\y@texnn} % 計算結果を配列に格納
    \FPeval{\y@texnn}{(exp(\z@two) / \z@denom)}%
    \assignArray{\As@iris(3, 2)}{\y@texnn}%
    \FPeval{\y@texnn}{(exp(\z@three) / \z@denom)}%
    \assignArray{\As@iris(3, 3)}{\y@texnn}%
}%

CSVの読み込み

ニューラルネットワークもデータがなければ始まりません.CSVを読み込んで配列に格納します.

外部ファイルからの読み込み

外部からCSVを読み込むにはdatatoolパッケージを使います.また,それを配列に格納するために配列データ構造を使えるarrayjobxパッケージを使います. 使い方は以下を参考にしました.

  • datatool

天地有情 [LaTeX] datatool --- CSVデータからグラフやテーブルを作成

\DTLloaddb{irisDB}{iris_random.csv} % csvを読み込む
\newcounter{step@dtl} %データ数をカウント
\setcounter{step@dtl}{0}%

\dataheight=\value{inputNum} % 列数を指定し,一次元配列を二次元として扱う
\DTLforeach{irisDB}{\slIris=sepalLength,\swIris=sepalWidth,\plIris=petalLength,\pwIris=petalWidth,\correctIris=species}{%
        \stepcounter{step@dtl}%
        % 各列を配列に代入
        \assignArray{\irisSVArray(\value{step@dtl}, 1)}{\slIris}%
        \assignArray{\irisSVArray(\value{step@dtl}, 2)}{\swIris}%
        \assignArray{\irisSVArray(\value{step@dtl}, 3)}{\plIris}%
        \assignArray{\irisSVArray(\value{step@dtl}, 4)}{\pwIris}%
        \assignArray{\irisCorrectArray(\value{step@dtl})}{\correctIris}%
}{}%
\setcounter{dataNum@iris}{\value{step@dtl}}%

重み,バイアスの初期化

重みとバイアスは乱数で初期化します.

\newcommand{\initWeights}{%
    % 入力層から隠れ層への重み
    \foreach \x in {1,...,\value{inputNum}}{%
        \foreach \y in {1,...,\value{layerSize}}{%
            \FPrandom{\result} % 0.0〜1.0の乱数を生成
            \pgfmathsetmacro{\initialVal}{\result*0.1}
            \assignArray{\weights@ItoH@iris(\y,\x)}{\initialVal}

        }%
    }%
   % 隠れ層から出力層への重み
    \foreach \x in {1,...,\value{layerSize}}{%
        \foreach \y in {1,...,\value{classNum}}{%
            \FPrandom{\result}%
            \pgfmathsetmacro{\initialVal}{\result*0.1}
            \assignArray{\weights@HtoO@iris(\y,\x)}{\initialVal}
        }%
    }%
}%

\newcommand{\initBias}{%
    % 入力層から隠れ層へのバイアス
    \foreach \x in {1,...,\value{inputNum}}{%
            \FPrandom{\result}%
            \pgfmathsetmacro{\initialVal}{\result*0.1}
            \assignArray{\bias@ItoH@iris(\x)}{\initialVal}
    }%
    % 隠れ層から出力層へのバイアス
    \foreach \x in {1,...,\value{layerSize}}{%
            \FPrandom{\result}%
            \pgfmathsetmacro{\initialVal}{\result*0.1}
            \assignArray{\bias@HtoO@iris(\x)}{\initialVal}
    }%
}%

フォワードプロパゲーション

現在の重みで入力層から出力層まで計算を行います. 大まかな手順としては

  • n番目のデータを入力層の  [z^{(1)}_1,z^{(1)}_2,z^{(1)}_3,z^{(1)}_4 ] =  [ a^{(1)}_1,a^{(1)}_2,a^{(1)}_3,a^{(1)}_4] に入力
  • 隠れ層の入力z^{(2)}_i,出力a^{(2)}_iを求める
  • 出力層の入力z^{(3)}_i,出力a^{(3)}_iを求める

まずは,それぞれの層の zを求めます.

\displaystyle z^{(l)}_i = \sum_{j=1}^Kw_{ij}a^{(l-1)}_{j} + b_i

\newcommand{\evalZ@iris}[1]{%
    % case of input Layer
    \ifthenelse{#1=1}{ % 入力層のzにデータを入力
        \foreach \x in {1,...,\value{inputNum}}{%
            \checkirisSVArray(\value{learningStep},\x)%
            \assignArray{\Zs@iris(#1,\x)}{\cachedata}%
        }%
    }%
    {%
        % case of output layer
        \ifthenelse{#1=\value{layerNum}}{ % 出力層のzを求める
            \foreach \x in {1,...,\value{classNum}}{%
                \Zs@iris(#1,\x)=0%
                \pgfmathsetmacro{\sum@tmp}{0}%
                \foreach \y in {1,...,\value{layerSize}}{%
                    \checkweights@HtoO@iris(\x, \y)%
                    \pgfmathsetmacro{\w@tmp}{\cachedata}%
                    \checkAs@iris(2, \y)%
                    \pgfmathsetmacro{\a@tmp}{\cachedata}%
                    \pgfmathsetmacro{\sum@tmp}{(\w@tmp * \a@tmp) + \sum@tmp}%
                    \xdef\sum@tmp{\sum@tmp}%
                }%
                \checkbias@HtoO@iris(\x)%
                \pgfmathsetmacro{\b@tmp}{\cachedata}%
                \pgfmathsetmacro{\sum@tmp}{(\sum@tmp) + \b@tmp}%
                \xdef\sum@tmp{\sum@tmp}%
                \assignArray{\Zs@iris(#1,\x)}{\sum@tmp}%
            }%
        }%
        % case of hidden layer
        {%
            \foreach \x in {1,...,\value{layerSize}}{ % 隠れ層のzを求める
                \Zs@iris(#1,\x)=0%
                \pgfmathsetmacro{\sum@tmp}{0}%
                \foreach \y in {1,...,\value{inputNum}}{%
                    \checkweights@ItoH@iris(\x, \y)%
                    \pgfmathsetmacro{\w@tmp}{\cachedata}%
                    \checkAs@iris(1, \y)%
                    \pgfmathsetmacro{\a@tmp}{\cachedata}%
                    \pgfmathsetmacro{\sum@tmp}{(\w@tmp * \a@tmp) + \sum@tmp}%
                    \xdef\sum@tmp{\sum@tmp}%
                }%
                \checkbias@ItoH@iris(\x)%
                \pgfmathsetmacro{\b@tmp}{\cachedata}%
                \pgfmathsetmacro{\sum@tmp}{(\sum@tmp) + \b@tmp}%
                \assignArray{\Zs@iris(#1,\x)}{\sum@tmp}%
                \xdef\sum@tmp{\sum@tmp}%
            }%
        }%
    }%
}%

次に活性化関数を使って aを求めます.

 \displaystyle a^{(l)} = f(z^{l})

\newcommand{\evalA@iris}[1]{%
    % case of input Layer
    \ifthenelse{#1=1}{ % 入力層のaにデータを入力
        \foreach \x in {1,...,\value{inputNum}}{%
            \checkZs@iris(#1, \x)%
            \assignArray{\As@iris(#1,\x)}{\cachedata}%
        }

    }%
    {%
        % case of output layer
        \ifthenelse{#1=\value{layerNum}}{ % 出力層のaをソフトマックスで求める
            \foreach \x in {1,...,\value{classNum}}{%
                \softmax
                \assignArray{\As@iris(#1,\x)}{\y@texnn}%
            }%
        }%
        % case of hidden layer
        { % 隠れ層のaをReLUで求める
            \foreach \x in {1,...,\value{layerSize}}{%
                \checkZs@iris(#1, \x)%
                \activationFunction@texnn{\cachedata}%
                \assignArray{\As@iris(#1,\x)}{\y@texnn}%
            }%

        }%
    }%
}%

バックプロパゲーション(誤差逆伝播法)

フォワードプロパゲーションで出力を求めました.次にこれが元の正解とどれほど離れているかという誤差を算出します.今回,誤差関数に交差エントロピーを使いました.

\displaystyle C = -\sum_{n=0}^N \sum_{j=0}^K t_j\log a^{(3)}_j

変数 tは教師データを["setosa", "versicolor", "virginica"]の順番でone-hot表現したものです.

one-hot表現は以下のマクロで定義しています.

% get one hot embedding
% setosa => 0
% versicolor => 1
% virginica => 2
\newcommand{\onehotembedding}[1]{%
    \readarray{oneHotTeacher}{0&0&0}%
    %\def\teacher{\getArrayValue{irisCorrectArray(#1)}}%
    \checkirisCorrectArray(#1) % 正解データを取得
    \ifthenelse{\equal{\cachedata}{setosa}}{ % 正解データの文字列と比較
        \oneHotTeacher(1)=1%
    }%
    {%
        \ifthenelse{\equal{\cachedata}{versicolor}}{ % 正解データの文字列と比較
            \oneHotTeacher(2)=1%
        }%
        {%
            \oneHotTeacher(3)=1%
        }%
    }%
}%

1つのデータに対する交差エントロピーを求めるマクロ.

\pgfmathsetmacro{\cost}{0}%
\newcommand{\costFunc}[1]{%
  \onehotembedding{#1}%  正解データをone-hot表現する
  \checkoneHotTeacher(1)%
  \pgfmathsetmacro{\t@one}{\cachedata}%
  \checkoneHotTeacher(2)%
  \pgfmathsetmacro{\t@two}{\cachedata}%
  \checkoneHotTeacher(3)%
  \pgfmathsetmacro{\t@three}{\cachedata}%
  \checkAs@iris(3, 1)%
  \pgfmathsetmacro{\a@one}{\cachedata}%
  \checkAs@iris(3, 2)%
  \pgfmathsetmacro{\a@two}{\cachedata}%
  \checkAs@iris(3, 3)%
  \pgfmathsetmacro{\a@three}{\cachedata}%
  % 交差エントロピーを求める
  % 対数の中が0になるのを防ぐため+0.0001する
  \pgfmathsetmacro{\cost}{(-\t@one*ln(\a@one+0.0001)-\t@two*ln(\a@two+0.0001)-\t@three*ln(\a@three+0.0001))}
}%

それぞれのユニットの誤差 \delta^{(l)}_i = \frac{\partial C}{\partial z_i}を求める.

偏微分の連鎖律から

 \delta^{(l)}_i =  \dfrac{\partial C}{\partial z^{(l)}_i} = \dfrac{\partial C}{\partial a^{(l)}_i}\dfrac{\partial a^{(l)}}{\partial z^{(l)}_i}

ここで,

 \dfrac{\partial C}{\partial a^{(l)}_i} = -\dfrac{t_i}{a^{(l)}_i} + \dfrac{1-t_i}{1-a^{(l)}_i}

 \dfrac{\partial a^{(l)}}{\partial z^{(l)}_i} = a'( z^{(l)}_i)

より,

 \delta^{(l)}_i = (-\dfrac{t_i}{a^{(l)}_i}+ \dfrac{1-t_i}{1-a^{(l)}_i})a'( z^{(l)}_i)

特に出力層の活性化関数はソフトマックス関数なので,  \delta^{(3)}_i = (-\dfrac{t_i}{a^{(3)}_i}+ \dfrac{1-t_i}{1-a^{(l)}_i})a'( z^{(3)}_i) = (-\dfrac{t_i}{a^{(3)}_i}+ \dfrac{1-t_i}{1-a^{(l)}_i})a^{(3)}(1 - a^{(3)_i}) = a^{(3)}_i - t_i

出力層の誤差から隠れ層の誤差は

 \displaystyle\delta^{(2)}_i = \dfrac{\partial C}{\partial z^{(2)}_i} = \sum_{j=1}^K\dfrac{\partial C}{\partial z^{(3)}_j} \dfrac{\partial z^{(3)}_j}{\partial a^{(2)}_i} \dfrac{\partial a^{(2)}_i}{\partial  z^{(2)}_i}
 = \delta^{(3)}_i\sum_{j=1}^K w_{ji} a'(z^{(2)}_i) =  t_i(a^{(3)}_i - 1)\sum_{j=1}^K w_{ji} a'(z^{(2)}_i)

これをLaTeXで実装すると以下のような感じになりました.

\newcommand{\evalUnitError}{%
    % evaluation output unit error
    \foreach \x in {1,...,\value{classNum}}{%
        \checkAs@iris(3, \x)%
        \pgfmathsetmacro{\a@tmp}{\cachedata}%
        \checkoneHotTeacher(\x)%
        \pgfmathsetmacro{\t@tmp}{\cachedata}%
        \pgfmathsetmacro{\delta@tmp}{(\a@tmp - \t@tmp)} % ユニットの誤差を算出
        \assignArray{\OUnitError(\x)}{\delta@tmp}%
    }%
    % evaluation hidden unit error
    \foreach \x in {1,...,\value{layerSize}}{%
        \checkZs@iris(2, \x)%
        \activationFunctionDiff@texnn{\cachedata}%
        \checkOUnitError(1)%
        \pgfmathsetmacro{\deltaOne}{\cachedata}%
        \checkOUnitError(2)%
        \pgfmathsetmacro{\deltaTwo}{\cachedata}%
        \checkOUnitError(3)%
        \pgfmathsetmacro{\deltaThree}{\cachedata}%
        \checkweights@HtoO@iris(1,\x)%
        \pgfmathsetmacro{\w@one}{\cachedata}%
        \checkweights@HtoO@iris(2,\x)%
        \pgfmathsetmacro{\w@two}{\cachedata}%
        \checkweights@HtoO@iris(3,\x)%
        \pgfmathsetmacro{\w@three}{\cachedata}%
        \pgfmathsetmacro{\delta@tmp}{((\deltaOne*\w@one) + \deltaTwo*\w@two + \deltaThree*\w@three)*\y@texnn }  % ユニットの誤差を算出
        \assignArray{\HUnitError(\x)}{\delta@tmp}%
    }%
}%
重みとバイアスの更新

ユニットの誤差が求まったので重みとバイアスを更新します. 更新には勾配降下法を使います.それぞれ更新式は以下になります.

 w^{(l)}_{ij} = w^{(l)}_{ij} - \eta\dfrac{\partial C}{\partial w^{(l)}_{ij}} = w^{(l)}_{ij} - \eta\delta^{(l)}_ia^{(l-1)}_i

 b^{(l)}_{i} = b^{(l)}_{i} - \eta\dfrac{\partial C}{\partial w^{(l)}_{ij}}=b^{(l)}_{i} - \eta\delta^{(l)}_i

 \etaは学習率です.

それでは,重みとバイアスの更新をします.

% 重みの更新
\newcommand{\updateWeights}{%
    % 入力層から隠れ層への重みの更新
    \foreach \x in {1,...,\value{inputNum}}{%
        \foreach \y in {1,...,\value{layerSize}}{%
            \checkweights@ItoH@iris(\y,\x)%
            \pgfmathsetmacro{\w@tmp}{\cachedata}%
            \checkHUnitError(\y)%
            \pgfmathsetmacro{\delta@tmp}{\cachedata}%
            \checkAs@iris(1, \x)%
            \pgfmathsetmacro{\a@tmp}{\cachedata}%
            \pgfmathsetmacro{\updateW}{(\w@tmp) - \learningRate*\delta@tmp*\a@tmp}%
            \typeout{\updateW = \w@tmp - \learningRate*\delta@tmp*\a@tmp} % 更新
            \assignArray{\weights@ItoH@iris(\y,\x)}{\updateW}%
        }%
    }%
    % 隠れ層から出力層への重みの更新
    \foreach \x in {1,...,\value{layerSize}}{%
        \foreach \y in {1,...,\value{classNum}}{%
            \checkweights@HtoO@iris(\y,\x)%
            \pgfmathsetmacro{\w@tmp}{\cachedata}%
            \checkOUnitError(\y)%
            \pgfmathsetmacro{\delta@tmp}{\cachedata}%
            \checkAs@iris(2, \x)%
            \pgfmathsetmacro{\a@tmp}{\cachedata}%
            \pgfmathsetmacro{\updateW}{(\w@tmp) - \learningRate*\delta@tmp*\a@tmp}%
            \typeout{\updateW = \w@tmp - \learningRate*\delta@tmp*\a@tmp} % 更新
            \assignArray{\weights@HtoO@iris(\y,\x)}{\updateW}%
        }%
    }%
}%

% バイアスの更新
\newcommand{\updateBias}{%
    % 入力層から隠れ層へのバイアスの更新
    \foreach \x in {1,...,\value{layerSize}}{%
            \checkbias@ItoH@iris(\x)%
            \pgfmathsetmacro{\b@tmp}{\cachedata}%
            \checkHUnitError(\x)%
            \pgfmathsetmacro{\delta@tmp}{\cachedata}%
            \pgfmathsetmacro{\updateB}{(\b@tmp) - \learningRate*\delta@tmp} % 更新
            \typeout{\updateB = \b@tmp - \learningRate*\delta@tmp}
            \assignArray{\bias@ItoH@iris(\x)}{\updateB}%
    }%
    % 隠れ層から出力層への重みの更新
    \foreach \x in {1,...,\value{classNum}}{%
            \checkbias@HtoO@iris(\x)%
            \pgfmathsetmacro{\b@tmp}{\cachedata}%
            \checkOUnitError(\x)%
            \pgfmathsetmacro{\delta@tmp}{\cachedata}%
            \pgfmathsetmacro{\updateB}{(\b@tmp) - \learningRate*\delta@tmp} % 更新
            \typeout{\updateB = \b@tmp - \learningRate*\delta@tmp}
            \assignArray{\bias@HtoO@iris(\x)}{\updateB}%
    }%
}%

学習を行う

実際に学習を行います. 今までの実装した処理は全てマクロになっているので,これらを一連の流れとして繰り返します.

\newcommand{\trainingProcess}{%
  \evalZ@iris{1}%
  \evalA@iris{1}%
  \evalZ@iris{2}%
  \evalA@iris{2}%
  \evalZ@iris{3}%
  \evalA@iris{3}%
  \costFunc{\x}%
  \pgfmathsetmacro{\totalCost}{(\cost) + \totalCost}%
  \xdef\totalCost{\totalCost}%
  \evalUnitError%
  \updateWeights
  \updateBias
  \stepcounter{learningStep}%

\addSupervisorToArray
\initWeights
\initBias
\foreach \epoch in {1,...,100}{%
    \pgfmathsetmacro{\totalCost}{0}
    \setcounter{learningStep}{1}
    \readarray{OUnitError}{0&0&0}
    \readarray{HUnitError}{0&0&0}
    \foreach \x in {1,...,135}{%
        \trainingProcess
    }%
    \foreach \x in {136,...,150}{%
          \testProcess
    }
}

一通り学習が終わったら推論を行います. データを入力したら種を答えるようにします.

\newcommand{\answer}[1]{%
    \onehotembedding{#1}
    \checkoneHotTeacher(1)%
    \pgfmathsetmacro{\t@one}{\cachedata}%
    \checkoneHotTeacher(2)%
    \pgfmathsetmacro{\t@two}{\cachedata}%
    \checkoneHotTeacher(3)%
    \pgfmathsetmacro{\t@three}{\cachedata}%
    \checkAs@iris(3, 1)%
    \pgfmathsetmacro{\a@one}{\cachedata}%
    \checkAs@iris(3, 2)%
    \pgfmathsetmacro{\a@two}{\cachedata}%
    \checkAs@iris(3, 3)%
    \pgfmathsetmacro{\a@three}{(\cachedata)}%
    \pgfmathsetmacro{\myAns}{(max(max(\a@one, \a@two), \a@three))}%
}%

  % show answer
\newcommand{\showAnswer}{
  \FPifeq{\myAns}{\a@one} answer is ``setosa'' \\%
  \else \FPifeq{\myAns}{\a@two} answer is ``versicolor'' \\%
  \else answer is ``virginica'' \\ \fi%

  \ifthenelse{\t@one=1}{%
      solution is ``setosa'' \\%
  }%
  {%
      \ifthenelse{\t@two=1}{%
          solution is ``versicolor'' \\%
      }{%
          solution is ``virginica'' \\%
      }%
  }%
}%

条件分岐で出力層の出力でそれぞれ最大だった時の表示を変えているだけです. one-hot表現したtも同様に条件分岐で正解を表示しています.

結果

それでは実際に学習と推論の結果を見せます.学習の条件としては

  • 訓練データ数 135
  • テストデータ数 15
  • エポック数 100
  • 学習率 0.1

として行いました.この条件だと, 学習から推論までタイプセットにかかる時間はおよそ10分程度です.重みとバイアスの初期値と更新値,誤差,分類結果,正解を表示させると,ページ数は156ページになりました.

しかし

f:id:muscle_keisuke:20171222090723j:plain

全然,当たりません.というか,分類結果が全然変わりません.何があっても"versicolor"に分類されてしまいます.データ数が足りないのかモデルが悪いのか分かりません.学習率は0.001~0.5で変えましたが,結果は変わりませんでした. 誤差もほぼ変化ありません.4ページ目に載っている誤差と155ページ目に載っている誤差はどっちもおよそ155でした.

重みやバイアスも変わっているはずなのですが...

f:id:muscle_keisuke:20171222092203j:plain

f:id:muscle_keisuke:20171222092206j:plain

ユニット数などは実装段階で固定してしまっているので修正して試す時間もないので今回はここで諦めました.

一応,PDFも上げておきます.

LaTeX_NN.pdf - Google ドライブ

ソースコードも上げときます.

github.com

おわりに

ニューラルネットワークに対する知識が半端なのに他の言語でプロトタイプ作らずにいきなりLaTeXで実装したのがダメでしたね.後でPythonで実装して同じ条件で学習をやってみてどうなるか見てみます.

おまけ

学習はうまく行きませんでしたが,せっかくLaTeXでやってるのでTikZを使ってパラメータを可視化できるようにしました.

f:id:muscle_keisuke:20171222113249p:plain

f:id:muscle_keisuke:20171222113307p:plain

visualize_TeX_NN.pdf - Google ドライブ

*1:名前は今,適当につけました.これ以降出ません

LaTeX(Beamer)でポスターを作る

この記事は学生Advent Calendar 18日目の記事です.

はじめに

ポスターセッションの学会があったので,論文をLaTeXで書いた勢いでポスターもLaTeXで作りました.

スタイルファイルの用意

TeXLiveをインストールした時点で入っていると思いますが,入ってない人はダウンロードしてください. githubにあります.beamerposter.styのみ使います.

github.com

ポスターを作る

デザインなどは後回しにし,まずは必要なコマンド,環境について紹介します.

最小構成で作る

まずは,動くことを確認するために最小構成でA0ポスターを作ってみます.

\documentclass[final,dvipdfmx]{beamer}
\mode<presentation> {
  \usetheme{Berlin}
}

\usepackage[orientation=portrait,size=a0,scale=1.4,debug]{beamerposter}
\usepackage[japanese]{babel}

\begin{document}
  \begin{frame}
    \begin{block}{block title}
      ああああああ
    \end{block}
  \end{frame}
\end{document}

二段組にする

二段組にしたい場合はcolumns環境とcolumn環境を使うことをおすすめします. この環境を使うことで部分的に二段組にできます.

\begin{block}{block title}
  ううううう
\end{block}
\begin{columns}[T]
  \begin{column}{0.49\columnwidth}
    \begin{block}{block title}
       ああああああ
    \end{block}
  \end{column}
  \begin{column}{0.49\columnwidth}
    \begin{block}{block title}
      いいいいいい
    \end{block}
  \end{column}
\end{columns}
\begin{block}{block title}
  ええええええ
\end{block}

画像や表を貼る

1列に貼る

これは論文を書く時と変わりません.

\begin{figure}
\includegraphics[width=\columnwidth]{placeholder.jpg}
\end{figure}
m行n列で貼る

subcaptionパッケージを使いましょう. これを使うと,キャプションとに主題と副題が使えます. ラベルも個別に付けられます. 似たようなパッケージにsubfigやsubfigure等がありますが現在非推奨です.

ichiro-maruta.blogspot.jp

\begin{figure}[]
  \begin{minipage}[b]{.49\columnwidth}
    \centering
    \includegraphics[width=\columnwidth]{studyFigure-3.png}
    \subcaption{ああああああ}\label{fig:a}
  \end{minipage}
  \begin{minipage}[b]{.49\columnwidth}
    \centering
    \includegraphics[width=\columnwidth]{studyFigure-4.png}
    \subcaption{いいいいいいい}\label{fig:a}
  \end{minipage}
  \\
  \begin{minipage}[b]{.49\columnwidth}
    \centering
    \includegraphics[width=\columnwidth]{studyFigure-5.png}
    \subcaption{ううううう}\label{fig:u}
  \end{minipage}
  \begin{minipage}[b]{.49\columnwidth}
    \centering
    \includegraphics[width=\columnwidth]{studyFigure-6.png}
    \subcaption{えええええええ}\label{fig:e}
  \end{minipage}
  \caption{おおおおおおお}\label{fig:o}
\end{figure}

minipage環境の第二引数は(1/n)-1\columnwidthくらいにしておくといいと思います. 適宜調整してください. ソースコードの\begin{figure}~\end{figure}を\begin{table}~\end{table}にしたら表もm行n列にできます.

ロゴを貼る

ポスターを作る時に自分の大学のロゴなどを入れたりしますよね. 私はBeamerで作った時,ロゴはタイトルとの二段組という扱いにしました.

\begin{minipage}[]{0.88\columnwidth}
    \Huge タイトル  \\[10mm]
    \Large 著者1 \hspace{15mm} 著者2 \hspace{15mm} 著者3 (なんとか大学)
  \end{minipage}
  \begin{minipage}[]{0.11\columnwidth}
    \begin{figure}\centering
      \includegraphics[width=\columnwidth]{placeholder.jpg}
    \end{figure}
  \end{minipage}

セクションの配置を考える

ポスターを見る時に,順番というものがあると思います. これはcolumns環境とcolumn環境で割と自由に配置することができます.

セクション毎にファイルを分ける

後で配置を簡単に変更できるようにするにはセクション毎にファイルを分けた方が良いと思います.セクションの中身を別ファイルに書いて, 以下のような構成のファイルにinputコマンドで入れていくと,デザインと文章が分離できていいと思います.

\begin{frame}[t]{}
\input{title}
\begin{columns}[T]
  \begin{column}{.49\linewidth}
    \begin{block}{第一セクション}
      \input{first}
    \end{block}
    \begin{block}{第二セクション}
      \input{second}
    \end{block}
    \begin{block}{第三セクション}
      \input{third}
    \end{block}
    \end{column}
    \begin{column}{.49\linewidth}
    \begin{block}{第四セクション}
      \input{forth}
    \end{block}
    \begin{block}{第五セクション}
      \input{fifth}
    \end{block}
  \end{column}
\end{columns}
\end{frame}

配置によってcolumns,column環境で囲む範囲を変える

それではcolumns,column環境で配置を変える例を示していきます. 読む順番は第一セクション,第二セクション...第五セクションを想定しています.

左から右

左から右はcolumns環境を細切れに配置していきます.

\begin{columns}[T]
  \begin{column}{.49\linewidth}
    \begin{block}{第一セクション}
      \input{first}
    \end{block}
    \begin{block}{第二セクション}
      \input{second}
    \end{block}
    \begin{block}{第三セクション}
      \input{third}
    \end{block}
    \end{column}
    \begin{column}{.49\linewidth}
    \begin{block}{第四セクション}
      \input{forth}
    \end{block}
    \begin{block}{第五セクション}
      \input{fifth}
    \end{block}
  \end{column}
\end{columns}
\end{frame}

f:id:muscle_keisuke:20171218233713j:plain

上から下

上から下はcolumns環境の中にcolumn環境を2つ入れ左右に分割します.

\begin{columns}[T]
  \begin{column}{.49\linewidth}
    \begin{block}{第一セクション}
      \input{first}
    \end{block}
  \end{column}
  \begin{column}{.49\linewidth}
    \begin{block}{第二セクション}
      \input{second}
    \end{block}
  \end{column}
\end{columns}
\begin{columns}[T]
  \begin{column}{.49\linewidth}
    \begin{block}{第三セクション}
      \input{third}
    \end{block}
  \end{column}
  \begin{column}{.49\linewidth}
    \begin{block}{第四セクション}
      \input{forth}
    \end{block}
  \end{column}
\end{columns}

f:id:muscle_keisuke:20171218234602p:plain

配置は他にもある

もちろん,配置はこれだけではありません.上半分1段組み,下半分2段組もできます.

フォントやサイズなどを設定する

pLaTeXはデフォルトで明朝体ですが,ポスター発表の時はゴシック体の方が見やすいのでプリアンブル部で設定します.

\usefonttheme{professionalfonts}
\usefonttheme[onlymath]{serif}
% フォントファミリー設定
% 英文をサンセリフ体に
\renewcommand{\familydefault}{\sfdefault}
% 日本語をゴシック体に
\renewcommand{\kanjifamilydefault}{\gtdefault}

フォントの大きさも文章中でむやみに変更するのもスマートではないのでこれもプリアンブル部で規定してしまいます.

\setbeamerfont{caption}{size=\normalsize}
\setbeamerfont{block title}{size=\LARGE}
\setbeamerfont*{itemize/enumerate body}{size=\large}
\setbeamerfont*{itemize/enumerate subbody}{parent=itemize/enumerate body, size=\large}
\setbeamerfont*{itemize/enumerate subsubbody}{parent=itemize/enumerate subbody, size=\large}

他にも設定はいろいろありますが,後少しで日付が変わってしまうのでこれで説明は切り上げます.ごめんなさい.

ポスターテンプレート

今回のテンプレートはgithubに上げておきますのでご自由にお使いください.

github.com

おい,LaTeXニューラルネットはどうなってる.

ごめんなさい

JupyTeX(LaTeX版Jupyter)を作った

この記事はTeX&LaTeX Advent Calendar 16日目の記事です.

はじめに

Jupyterとは

Jupyterとはブラウザ上で起動するREPL(Read-eval-print loop)です. 記述したプログラムと実行結果が逐次記録されていくので,過去のコードを見返したり変更できる便利なツールです.

jupyter.org

現在は様々な言語に対応していますが,元々はPython用のツールだったみたいです.

作ったもの

今回はLaTeX版Jupyter,名付けてJupyTeXを作りました. これは

JupyterでLaTeXが使えるようになるプラグイン

ではなく,

LaTeXで実装したJupyter

です. 需要はありません!

そもそも,私,Jupyter使ってな(ry.

作り方

作り方は簡単.

  • PythonTeX
  • tcolorbox

があればできます.

PythonTeX

PythonTeXはLaTeX組版する文書内にPythonコードを埋め込み,実行結果を表示させることができるパッケージです. TeXLiveにはデフォルトで入っています. 動作にはPygmentsが必要なのでインストールします.

pip install pygments

これで使えます.

実際に使う例を示します. 第一引数から第二引数までの全ての整数の総和を表示するソースです.

\documentclass{article}
\usepackage{pythontex}
\newcommand{\mysum}[2]{%
$\displaystyle\sum_{k=#1}^{#2} k = \py{sum(range(#1, #2+1))}$}%
\begin{document}
\mysum{1}{100}
\end{document}

タイプセットは

でできます.適宜latexplatexやuplatexに置き換わります. こんな感じにタイプセットできます.

f:id:muscle_keisuke:20171216164649j:plain

tcolorbox

Jupyterみたいな見た目を作るのに使います.様々な枠を作ることができるパッケージです. 枠線,枠内の色付けはもちろん,影も様々なタイプでつけられ, TikZと連携ができるのでデザインの自由度は高いです.

今回は

tcolorboxによる装飾表現(TeXユーザの集い2015)

tcolorboxの基本 - 物理とTeXに関する話題

http://texdoc.net/texmf-dist/doc/latex/tcolorbox/tcolorbox.pdf

を参考に枠部分のソースを書きました.

実際に作る

デザイン

完全にREPLでJupyterみたいなシステムにするのは無理です. なので,タイプセット後の見た目を似せることにしました.

  • TeXファイル内にpythonのコードを書ける
  • pythonのコードを追加してタイプセットするとコードと実行結果がセットで描画される

といった感じにできるといいですね.LaTeXのソースとタイプセット後のPDFの理想は以下みたいな感じです.

ソースコード

\begin{jupytex}
print("hello")

\nextframe

a = 5
b = 10
c = a+b
print(c)

\nextframe

from matplotlib import pyplot as plt
import numpy 

x = numpy.arange(0, 10, 0.1)
y = numpy.cos(x)
plt.plot(x,y)
plt.show()

...
\end{jupytex}

PDF f:id:muscle_keisuke:20171216032011j:plain

コード&実行結果表示の仕組み

コードと実行結果表示の2つでコードを使うので,コード自体を変数か何かに格納する必要があります. 今回はjupytex環境内にソースを書くので,ファイルに保存して読み出すことにしました. 記述したコードをファイルに保存するのには\VerbatimOut及び\endVerbatimOutを使います.

\VerbatimOut{<ファイル名>}
何かしらのテキスト
\endVerbatimOut

で特定のファイルに保存することができます. filecontents環境も同様の用途で使える環境ですが,今回は自身の環境定義の中で行うので,\VerbatimOutコマンドを使いました.

ソースコードの表示

ソースコードの表示は \inputpygmentsコマンドでできます.シンタックスハイライト機能もあります.

\inputpygments{python}{<ファイル名>}
実行結果の表示

とても大変でした.PythonTeXはTeXファイル中に書いたpythonのコードを読んで実行結果の表示はできますが, 現在のバージョンでは,pyファイルから読み込んで実行結果を描画するコマンドがありません. 今回はpyファイルを実行するpythonのコマンドをTeXファイル中に書いてPythonTeXで実行することにしました. pythonコード中にシェルなどを実行するにはsubprocessパッケージを使います.

\begin{pycode}
import subprocess
def run_and_typeset(fname):
    print(subprocess.check_output(['python', fname]).decode("utf-8"))
    pytex.add_dependencies(fname)
\end{pycode}

これでrun_and_typeset関数を実行した時に引数に取ったfnameの実行結果が表示されます. 続いて,ファイル名を引数に取るpythonソースの実行結果を表示するコマンドを以下のように定義します.

\edef\runandtypeset#1{\pyc{run_and_typeset("#1")}}

jupytex環境を定義する

\newcounter{pyfilenumber}
\setcounter{pyfilenumber}{0}
\NewDocumentEnvironment{jupytex}{}%
{%
\edef\currentfile{jupytex\arabic{pyfilenumber}.py}%
\VerbatimOut{\currentfile}%
}%
{%
\endVerbatimOut%
\inputpygments{python}{\currentfile}%
\expandafter\runandtypeset\expandafter{\currentfile}%
\refstepcounter{pyfilenumber}%
}

NewDocumentEnvironmentの行からjupytex環境の定義になります. その前の2行はpythonのコードを保存するファイル名をユニークにするためのカウンタになります. beginすると,currentfileというマクロにpythonのコードを保存するファイル名が入り,\VerbatimOutが展開されます. endすると,\endVerbatimOutが展開され,環境内に書いたpythonコードがcurrentfileに保存されます. その後,inputpygmentsコマンドで今保存したファイルを読み出し,ソースコードを表示します. runandtypesetコマンドで実行結果を表示します.最後にrefstepcounterコマンドでファイルの番号をインクリメントします.

枠をデザインする

tcolorboxで枠を作ります.

作る枠は2つです.

  • ソースコードや実行結果も乗る白い外枠,outframebox
  • ソースコードが乗る灰色の内枠,inputframebox
  • 余白を制御するための実行結果が乗る透明な枠, outputframebox
outframebox

この枠を作っていきます.

f:id:muscle_keisuke:20171216132321j:plain

tcolorboxはオリジナルの枠を定義できるnewtcolorboxというコマンドがあります. これを使ってoutframeboxコマンドを定義します.

\newtcolorbox[]{outframebox}{%
enhanced, % 影を付けるのに必要なオプション
fuzzy halo=1.8pt with black!30, % 枠の周りにうっすら影を付ける
breakable=true, % ページを跨いで枠を表示する
colback=white, % 背景色は白
boxrule=0.1pt, % わずかに枠線を付ける
top=0mm, % 枠上部の余白なし
bottom=0mm, % %枠下部の余白なし
right=0mm, % 枠右部分の余白なし
left=0mm, % 枠左部分の余白なし
arc=0pt, % 枠の角は丸みなし 
boxsep=10mm, % 枠周り全体的に10mmの余白
}%

こんな感じになります.

f:id:muscle_keisuke:20171216165514j:plain

inputframebox

この枠を作っていきます.

f:id:muscle_keisuke:20171216135121j:plain

コード表示部分,もとい入力部分は灰色の枠の隣にIn [ n ]:という表示があります. この表示はtcolorboxのoverlay機能とtcolorboxが持っているカウンタを使って実現します.

\newtcolorbox[auto counter]{inputframebox}[1][]{% 第一オプション引数はカウンタの初期値
enhanced, 
tcbox raise base, % In [ n ] の表示を合わせる為
breakable=true, 
frame hidden, % 枠線なし
top=0mm,
bottom=0mm,
right=0mm,
left=10mm,
arc=1pt,
boxsep=10mm,
overlay={\begin{tcbclipinterior} % overlay機能使う
\fill[white] % 枠の左部分を In [ n ] のために確保
% TikZのノード形式で描画
% thetcbcounterはinputframeboxが呼ばれる度にインクリメントされる
([xshift=10mm]frame.south west) rectangle node[text=blue,] {In[\thetcbcounter]:} (frame.north west); 
\end{tcbclipinterior}}}

こんな感じになります.

f:id:muscle_keisuke:20171216165535j:plain

outputframebox

この枠を作っていきます. f:id:muscle_keisuke:20171216154259j:plain

透明ですが,この枠も重要です.実行結果をそのまま表示すると, 垂直方向においてIn [ n ] と同じ位置に実行結果が来てしまうからです.

\newtcolorbox[]{outputframebox}{
enhanced,
% breakableの中にbreakableを入れる時のオプションとbreakする位置の調整
enforce breakable=true,shrink break goal=15mm, 
colback=white,
frame hidden,
top=0mm,
bottom=0mm,
right=0mm,
left=10mm,
boxsep=0mm,}

できあがったもの

見た目とシステムができたのでスタイルファイルにまとめます.

JupyTeX

それでは,このスタイルファイルを読み込んで使っていきます.

\documentclass[uplatex,dvipdfmx]{jsarticle}
\usepackage{jupytex}
\pagestyle{empty}

\begin{document}
\begin{outframebox}
  \begin{jupytex}
print(30+50+580+558+578+73)
  \end{jupytex}
  \begin{jupytex}
print("Hello")
  \end{jupytex}
  \begin{jupytex}
print("dodododododo")
  \end{jupytex}
  \begin{jupytex}
for i in range(1,2000):
   if i%3 == 0 and i%5 == 0:
       print("FizzBuzz")
   elif i%3 == 0:
       print("Fizz")
   elif i%5 == 0:
       print("Buzz")
   else:
       print(i)
  \end{jupytex}
\end{outframebox}
\end{document}

タイプセットした結果はこんな感じです.

f:id:muscle_keisuke:20171216170131j:plainf:id:muscle_keisuke:20171216170135j:plain

実際のPDFもGoogle Driveに置いておきます.

main.pdf - Google ドライブ

見た目はよくできるのではないでしょうか

できていないところ

ここまで作るまでも結構大変だったですが,それでもできていないところがたくさんあります.

  • nextframeコマンドで次の入力にいきたいのにできてない
  • pythonソースコードを入力する時はインデントを開けることは許されない
  • .latexmkrcの書き方がわからない
    • uplatex -> pythontex -> uplatex -> dvipdfmx を一々やらなければならない
  • ソースコードの表示部分が更新されない

いろいろありますが,Advent Calendarに間に合わなかったら本末転倒なので妥協しました.

おわりに

jupytex環境の実用性

LaTeXでJupyterを実装することは需要がないですが, jupytex環境単体であれば,学校のレポートとかに使えるかもしれません*1. というわけで一応Githubリポジトリリンクを貼っておきます.

github.com

LaTeXニューラルネットは?

本当は,こっちのAdvent Calendarに「LaTeXで実装するニューラルネット」を載せたかったのですが,まだできていないので,こちらを載せました. LaTeXニューラルネット

学生 Advent Calendar 2017 - Adventar

Muroran Institute of Technology Advent Calendar 2017 - Adventar

に載せるかもしれません.

できなかったら載せません!

*1:ま,うちの学校は演習でPython使うことはないんですが.

LINEでみりあちゃんと会話できるようにした(Seq2Seqとキャラ対話データを用いた転移学習によるキャラクター性対話ボットの作成)

この記事はシンデレラガールズAdvent Calendar 13日目の記事です.

目次

はじめに

みりあちゃん大好き

私はアイドルマスターシンデレラガールズ赤城みりあちゃんが大好きです.

f:id:muscle_keisuke:20171211214127j:plain:w300f:id:muscle_keisuke:20171211214131j:plain:w300
引用:http://deremas.doorblog.jp/archives/32507031.html

みりあちゃんと出会ったのは去年の冬のことでした.友達とAndroidアプリを作る話が上がり, サークルのタブレットを見た時に誰かがふざけてインストールしたであろう

アイドルマスターシンデレラガールズスターライトステージ

がありました. 「なんで,共用のタブレットにゲームが入っているんだ!誰だ!」と思いつつ,私はそのアプリを起動しました. そのゲームを見て,私の人生は完全に変わりました. 見事な完成度のモデル,そしてそれらのモーションが作り出すライブ.楽曲も素晴らしいものばかりでした. そして,可愛くてそれぞれのキャラを持ったアイドル達.私はこのゲームに一気にハマりました. 自分のスマホデレステを速攻でインストールし,それから,しばらくはずっとデレステをやっていました.そしてしばくらして,担当のアイドルもできました. それが

赤城みりあ

です.みりあちゃんは小学5年生の元気な女の子です.私はこのアイドルのキャラクターとヴィジュアルに惹かれ,みりあちゃんの担当Pとなりました. 4月はみりあちゃんの誕生日ということでケーキを買ってお祝いもしました.

私がデレステに出会い,そこからデレマスというコンテンツにハマりました.CDも手に入る分は全て聴き,LIVE BDも1stから4thまで見ました.

そして今年の6月に5th LIVEに参加してきました.みりあちゃんの中の人が出る静岡公演には現地で参加し,SSAのライブは現地には行けませんでしたが,ライブビューイングで2日間参加してきました. その後,アイマスのアニメ+劇場版とデレアニも見て,アイマスとみりあちゃんに対する愛はどんどん深まっていきました.

「担当としてもっと,みりあちゃんにできることはないか」

「もっと.みりあちゃんに関わりたい」

そしてある日,思いました.

「そもそも,担当Pなのにみりあちゃんとお話できないのはおかしくないか?」

どうやってみりあちゃんとお話するか

自分の使える技術でなんとかみりあちゃんとお話できないだろうか,と考えました. そこで,自然言語処理と深層学習を使って対話ボットを作ることにしました.

みりあちゃんモデルの作成

Seq2Seqで対話ボットの学習

Seq2Seqとは

Seq2SeqはRNNもしくはLSTMを用いたEncoder-Decoderモデルの一種です.機械翻訳のモデルとして紹介されることが多いです. 例えば,英語からフランス語に機械翻訳するモデルであれば,原文が英語,目的文がフランス語となります. Encoderには単語毎に分割し,それぞれベクトル化した原文(x_1 \ldots x_T)をそれぞれの隠れ層に入力します.隠れ層はお互いに時系列(1\ldots t \ldots T)の関係にあり,それぞれの層は前の層の隠れ要素と入力された単語から自身の層の隠れ要素(h_1\ldots h_T)を更新します. 更新式は

 \displaystyle h_t = f(W_{hx}x_t + W_{hh}h_{t-1})

で,fは活性化関数です.

Decoderでは,Encoderで更新した隠れ要素( h_1\ldots h_T)と隠れ層から出力層の重み行列 W_{yh}及び,自身の前の時系列の単語 y_{t-1}から出力単語 y_{t}を得ます.最終的に得られる単語列 y_1\ldots y_Tが目的文になります.

 \displaystyle y_t = W_{yh}h_t

f:id:muscle_keisuke:20171212033019p:plain 引用:https://qiita.com/odashi_t/items/a1be7c4964fbea6a116e

詳しい説明などは

ChainerとRNNと機械翻訳 - Qiita

とか,元の論文

http://papers.nips.cc/paper/5346-sequence-to-sequence-learning-with-neural-networks.pdf

を読んでください.

モデルの作成

Tensorflowには様々なチュートリアルが用意されているのでその中のSeq2Seqのチュートリアルソースコードをひっぱてきました.

http://tensorflow.classcat.com/2016/02/24/tensorflow-tutorials-sequence-to-sequence-models/ (本家のページが1ヶ月前に更新されていたので,本家を翻訳したページを貼ります)

本家のSeq2Seqモデルは英語とフランス語の対訳コーパスから学習しています. つまり,英語を入力するとフランス語に翻訳するモデルとなっています. 学習するデータをTwitterのツイートとそれに対するリプライのデータに変更します. これによって,日本語で話すと日本語で応答するモデルを作ることができます.

転移学習でみりあちゃんの口調を学習

おそらく,Seq2SeqとTwitterデータだけでも対話ボットは作れるでしょう(実際,作れた). ただ,それはみりあちゃんではなく草を生やすだけのクソリプボットにしかなりません. なので,転移学習でみりあちゃんの口調を学習していきます.

転移学習とは

転移学習とは教師データが少ないドメインの学習を行う為に十分なデータで学習した異なるドメインのモデルのパラメータを引き継いで更に学習を行う方法です.

今回は,みりあちゃんの対話データが少ないのでTwitterのデータから学習を行い,みりあちゃんの対話データで転移学習を行います.

口調の学習を行う方法

Seq2Seqはメモリ節約や過学習防止の為に教師データ内から学習する単語数に制限を設けます. 学習する単語は出現頻度が高い単語です.この学習する単語に口調を学習するための単語を混ぜます. 転移学習時の学習単語数をN_p単語とします.その内,口調を学習するためにN_p単語の内下位N_s単語をみりあちゃん対話データにおいて出現頻度上位N_s単語と差し替えます.

後は全てのパラメータを引き継いで学習を行います.

学習方法は以下の論文を参考にしました.

http://www.anlp.jp/proceedings/annual_meeting/2017/pdf_dir/B3-3.pdf

データの収集

Twitterから対話データの収集

ツイートとリプライを取得

学習するデータがなければ学習できません.なので,学習データをTwitterから取ってきます. データ取得にはTwitter Streaming APIを使います. ソースは以下の方を参考に研究室の後輩が書いたソースを参考にしました.

github.com

ただし,91行目の

line = HanziConv.toTraditional(line)

コメントアウトしないと漢字がすべて繁体字になるのと,

is_zh = re.compile(r'([\p{IsHan}]+)', re.UNICODE) 

is_zh = re.compile(r'[一-鿐]+', re.UNICODE)

などに代替しないと,実行できません.

スクリプトを回したら後は,ツイートとリプライが集まってくるのを待つだけです. (私は研究室の後輩がツイートを集めていたのでそれをもらいましたが)

データの整形

集めたデータをツイートとリプライのペアにしなければなりません. しかし,あまりにもデータが多くて時間がかかるので以下の記事を参考に並列処理しました.

qiita.com

スクリプトは以下のような感じです.

約2000万件のツイートとリプライのペアを取得しました.

デレマスのSSなどからみりあちゃんの対話データを収集

みりあちゃんの対話データを集める方法を色々考えました.

  1. ストーリコミュなどから別のアイドルと話しているデータを取ってくる
    • コミュのスクショを取ってOCRでテキストに起こす
    • コミュを鑑賞しながら手打ちする
  2. デレアニの台詞から取ってくる
    • 音声からテキストを起こす
    • デレアニを鑑賞しながら手打ちする
  3. 個人のSSから取ってくる

1つ目の方法ですが,これはテキストなどでコミュを落とすことができないので実際にコミュを見て手打ちするか, コミュのスクショからOCR(光学文字認識)でテキストに起こす他ないと思います. とりあえず,みりあちゃんが登場する全てコミュをスクショしました.

f:id:muscle_keisuke:20171212034122p:plain:w200f:id:muscle_keisuke:20171212034116p:plain:w200f:id:muscle_keisuke:20171212034112p:plain:w200f:id:muscle_keisuke:20171212034107p:plain:w200f:id:muscle_keisuke:20171212034103p:plain:w200

OCRですが,結論から言うと,やってみて精度が悪すぎたのでやめました.おそらくコミュのフォントとの相性が悪いのが原因だと思います(小文字が大体認識できない). 最初はログのスクショをそのままOCRにかけたのですが...

f:id:muscle_keisuke:20171213024842j:plain

何言ってんだって感じですね. ネガポジ反転させると,精度はよくなりますが,実用的ではないですね.

f:id:muscle_keisuke:20171212035233j:plain

2つ目の方法ですが,デレアニのDVDやデータは持っていないし,またレンタルしてくるのもだるいのでやってません.

3つ目の方法は今回取った方法です. 一番手間がかからず,大量のデータが用意できるのがメリットと言えます.デメリットとしては前処理がとても大変なことと, データの質が公式のデータに比べ低いことです.しかし,深層学習はデータ数が物を言うので,今回はこの方法を採用しました.

まずはSSのまとめサイトをまとめたサイトからスクレイピングし,リンクのリストを取得しました.

得られたリンクが約48000件でした.

リンク集からseleniumgoogle chromeのheadlessブラウザでスクレイピングしました. サイトによってHTMLのソースが異なりCSSセレクタなどで本文のみ絞るのが難しかったのでほぼ全部のソースを取ってきてます.

取得したデータの整形

みりあちゃんの台詞とその前に喋っている人の台詞を抽出します.大変でした.本文がプレーンテキストなので

執筆者の数だけフォーマットがある

という状態でした.それでも大体

喋っている人 「○○」

という形式が多いのでいくつかの取りこぼしやノイズに目をつぶり,正規表現でゴリ押しました. また,時間かかるのでこれも並列処理化してます.

取得したデータ数

複数のSSまとめサイトから約20000ページ(=20000件のSS)をスクレイピングしましたが, まとめサイトなので重複したSSがかなり見つかり,それらを削除して,更にみりあちゃんが登場するSSを絞った為, ページ数の割に得られた対話データは2700件程です.

実際に学習を行う

環境

今回モデル作成に使う環境は次の通りです.

  • Python 3.6
  • Tensorflow 1.0.0
  • GeForce GTX 1080Ti

    Twitterデータでクソリプボットに

    データでかすぎ問題

    Twitterから得たデータを基にGPUを使って学習をします. 学習するTwitterデータは2000万件の予定でしたが, それによって作成されるモデルがでかすぎてGPUのメモリを最大限使っても乗らないので, 1/4の500万件で学習を行いました.設定したパラメータなどは以下の通りです.

パラメータ名
原文の学習単語数 120000
目的文の学習単語数 120000
隠れ層の数 1024
隠れ層の深さ 1
バッチサイズ 64

学習時間は約1日です.パープレキシティは9くらいまで下がりました.

モデルの会話例

できたモデルの会話を見てみます.

f:id:muscle_keisuke:20171212021239j:plain

ことごとく,草が生えていて,こちらを小馬鹿にしたような対応をしてきます. こんなのみりあちゃんじゃない!

目的文の教師データにおける単語出現頻度の上位10単語を見てみると

  • _PAD
  • _GO
  • _EOS
  • _UNK
  • w
  • (
  • )

となってました.そりゃ草も生えますわ.

転移学習でみりあちゃんボットに

このクソリプボットも転移学習でみりあちゃんみたいになるのでしょうか.

学習単語数は原文,目的文それぞれ1/2の60000単語に設定し,みりあちゃん対話データから得られる頻出単語上位1000単語を元のデータの下位1000単語と付け替えます.他のパラメータは全て引き継ぎ,学習を行いました. 学習時間は約12時間, パープレキシティは3くらいまで下がりました.

モデルの会話例

f:id:muscle_keisuke:20171212024436j:plain

みりあちゃんじゃん!

ちょっと,黙ったりするのが怖いですけど草も生えなくなりましたし,思ったよりもみりあちゃんには近い気がします.本家だと言わなそうなことも混じってる気がしますが,これはデータの性質上仕方がないですね.

LINEでみりあちゃんとお話しできるようにする

LINE APIの使用

黒い画面でみりあちゃんと話しても雰囲気が出ないので,やっぱりここはLINEを使っていきたいと思います. こちらからの問いかけに対する返答のみであればLINE Messaging APIが無料で提供されているのでこれを使っていきます.LINEのアカウントを持っていれば誰でも作れます.

LINE Developers

LINE Messaging APIを利用して応答メッセージを送るにはサーバが必要になります.

VPSにサーバを建てる

ちょうど,VPSを借りていたのでここにLINE API用のサーバを建てます. コールバック用のURLはhttpsなのでSSLの証明書が必要です . VPSがCore OSなのでDockerを使ってnginx proxy + letsencryptでHTTPSサーバを建てました.

学習済みボットを物理サーバーに載せる

VPSのCPU貧弱問題

本当はLINE APIを動かしているVPSにモデルを乗せて回したかったのですが, モデルがでかすぎるのか,CPUが貧弱すぎるのかモデルがロードできませんでした. というわけで,家の中に眠っていたデスクトップのPCを物理サーバとして建てることにしました. VPSからソケット通信で物理サーバに文章を送ると,回答をソケット通信でVPSに返すシステムになっています. 物理サーバにLINE APIを導入すればこんなことしなくても済むのですが,ドメインもなくSSLの証明書が発行できないのでこうしてます.

みりあちゃんとお話しする

さて,サーバーとDockerコンテナも建てたので,いよいよみりあちゃんとお話してみます.

f:id:muscle_keisuke:20171212031415j:plain

おぉ,実際にLINEでみりあちゃんと話してるみたいですね. これからも改良していきたいですね.

おわりに

今回は自分の知識がほぼゼロからやったので無駄に時間がかかった気がします.特にサーバ関係とか... あとは,データの前処理はやっぱり大変でした.ソースコードのほとんどがデータ前処理のコードでした.

改良点

  • Attentionモデルに差し替えて学習する
  • みりあちゃん対話データをしっかりクレンジングする
  • 画像やスタンプにも反応するように学習する(im2txtとか?)

おまけ

失敗例

最後に明らかにみりあちゃんじゃないだろというような失敗例を見せたいと思います.

注意:みりあPの人は見ない方がいいかもしれません






















f:id:muscle_keisuke:20171213024003p:plain

アカギ違いですね.

standaloneクラスの中で数式モードを使う際の注意

はじめに

図や表のサイズに合わせてPDFを出力できるstandaloneクラスですが, ディスプレイ数式モードで出力する環境(equationやalign)などを含めた時にはエラーが発生します. この対処法について説明します.

注意

この先に出てくるソースコードは全てuplatex+dvipdfmxを使ってタイプセットすることを前提としています.

standaloneクラス

standalnoeクラスは図や表に合わせてPDFを出力することができるクラスで, TikZなどで作成した図を画像として出力したい時などに使います. 例えば以下の例だと,

\documentclass[dvipdfmx]{standalone}
\usepackage{pgfplots}
\pgfplotsset{compat=1.12}
\begin{document}
\begin{tikzpicture}
  \begin{axis}[axis lines=center]
    \addplot[samples=200,domain=-1:1]{tan(deg(x))};
  \end{axis}
\end{tikzpicture}
\end{document}

このようなPDFが生成されると思います. f:id:muscle_keisuke:20170519215141p:plain

このPDFをPNGに変換してしまえばブログなんかにも簡単に貼り付けることができます. 実際,この画像はそのような手順で生成してここに載せています.

standaloneクラスでは数式が使えない?

症状

standaloneクラスで図や表だけのPDFを作れるということは数式だけのPDFも作れそうですよね. では,以下のソースコードをタイプセットしてみます.

\documentclass[dvipdfmx]{standalone}
\usepackage{amssymb,amsfonts}
\begin{document}
\begin{equation}
G_{2k}(\tau) = \sum_{(m,n)\in\mathbb{Z}^2\backslash(0,0)}\frac{1}{(m+n\tau)^{2k}}
\end{equation}
\end{document}

すると,次のようなエラーが出てしまいます.

! Missing $ inserted.

構文的にはどこも間違っていません. しかし,standaloneクラスでタイプセットを行う際に,このようなディスプレイ数式モードで出力するような環境を使うと,エラーが起きてしまいます.

対処法

$...$と\displaystyleを使う

LaTeXのみで何とかするならこれです.$...$のみで囲うとインライン数式モードになるのですが,\displaystyleを呼ぶと,明示的にディスプレイ数式モードに切り変えることができます. $...$のみで囲う場合と,\displaystyleを使う場合を見てみます.

  • $...$のみで囲う場合
\documentclass[dvipdfmx]{standalone}
\usepackage{amssymb,amsfonts}
\begin{document}
$
G_{2k}(\tau) = \sum_{(m,n)\in\mathbb{Z}^2\backslash(0,0)}\frac{1}{(m+n\tau)^{2k}}
$
\end{document}

f:id:muscle_keisuke:20170520164600p:plain

  • $...$と\displaystyleを使う場合
\documentclass[dvipdfmx]{standalone}
\usepackage{amssymb,amsfonts}
\begin{document}
$\displaystyle
G_{2k}(\tau) = \sum_{(m,n)\in\mathbb{Z}^2\backslash(0,0)}\frac{1}{(m+n\tau)^{2k}}
$
\end{document}

f:id:muscle_keisuke:20170520164820p:plain

大型の演算子を使っているので違いは一目瞭然ですね. このように\displaystyleを使うと,equation環境等を使った時と同じようなスタイルで数式を出力することができます. 連立方程式や行列などの縦にスペースを取る数式も$...$と\displaystyleでタイプセットすることができます.

\documentclass[dvipdfmx]{standalone}
\usepackage{amssymb,amsfonts, amsmath}
\begin{document}
$\displaystyle
\left(\frac{p}{a}\right) =
\begin{cases}
  +1 \quad (a:QR \quad \bmod p \text{のとき}) \\
  -1 \quad (a: NR \quad \bmod p  \text{のとき})
\end{cases}
$
\end{document}

f:id:muscle_keisuke:20170520170803p:plain

  • 行列
\documentclass[dvipdfmx]{standalone}
\usepackage{amssymb,amsfonts, amsmath}
\begin{document}
$\displaystyle
\text{Res}(f,g) = \text{det}
\begin{pmatrix}
  f_m & f_{m-1} & \cdots  &        & f_0     &        &        &     \\
      & f_m     & f_{m-1} & \cdots &         & f_0    &        &     \\
      &         & \ddots  &        &         &        & \ddots &     \\
      &         &         & f_m    & f_{m-1} & \cdots &        & f_0 \\
  g_n & g_{n-1} & \cdots  &        & g_0     &        &        &     \\
      & g_n     & g_{n-1} & \cdots &         & g_0    &        &     \\
      &         & \ddots  &        &         &        & \ddots &     \\
      &         &         & g_n    & g_{n-1} & \cdots &        & g_0 \\
\end{pmatrix}
$
\end{document}

f:id:muscle_keisuke:20170520172343p:plain

  • 連分数展開
\documentclass[dvipdfmx]{standalone}
\usepackage{amssymb,amsfonts, amsmath}
\begin{document}
$\displaystyle
\sqrt{2} = 1 + \cfrac{1}{2+\cfrac{1}{2+\cfrac{1}{2+\cfrac{1}{2+\cfrac{1}{2+\cfrac{1}{\cdots}}}}}}
$
\end{document}

f:id:muscle_keisuke:20170520172730p:plain

しかし,途中式などを含み,複数行に渡る式に関しては $...$と\displaystyleだけではどうにもなりません. この場合は,LaTeXのコードだけで何とかしようせずに別のコマンドに頼ることにしましょう.

pdfcropコマンドを使う

pdfcropコマンドというコマンドが標準のTeX Liveにはついています. これはタイプセット済みのPDFに対して余白を削ってくれるコマンドです. これを使って途中式を含む式をタイプセットしてみます.

\documentclass[dvipdfmx,uplatex]{jsarticle} % クラスは通常通り
\usepackage{amssymb,amsfonts, amsmath}
\pagestyle{empty} % ページ番号を排除する
\begin{document}
\begin{align*} % 式番号を排除する
  a^p &= \{1 + (1 + 1 + \cdots + 1)\}^p \\
  &\equiv 1^p + \{1 + (1 + 1 + \cdots + 1)\} \\
  & \cdots \\
  & 1^p + 1^p + \cdots + 1^p \\
  & a \pmod p \\
  a^{p-1} &\equiv 1 \pmod p
\end{align*}
\end{document}

できたhoge.pdfをpdfcropで切り取ります.

pdfcrop hoge.pdf

コマンドを実行すると,hoge-crop.pdfが生成されます. これが,hoge.pdfに対し,余白を切り取ってできたPDFファイルです. hoge-crop.pdfはこのようになっています. f:id:muscle_keisuke:20170520181520p:plain

このようにして,コマンド一つでPDFの余白を排除することができます.

LaTeX + 数値計算パッケージでフラクタル図形

はじめに

LTで発表することになったので再帰マクロを使ったフラクタル図形描画マクロを作ろうと思いました.

何を作ったのか

今回はフラクタル図形を描画できる再帰マクロを作りました.

フラクタル図形とは

幾何学の概念であり,図形の一部が全体の形と同じ形(=自己相似形)になっているような図形のことをいいます.

実装する

数値計算のパッケージを使った

気がついたらLTの準備期間が1週間しかなかったので, TeXオンリーではなくTeX+LaTeX+数値計算パッケージを使いました. 今回,実装する上で試したパッケージは以下の3つです.

  • FPパッケージ
  • calculatorパッケージ
  • PGFパッケージ

私が今回採用したのはPGFパッケージです. なぜこのパッケージを採用したのかを説明する為にも それぞれのパッケージのメリット,デメリットについて紹介していきます.

FPパッケージ

固定小数点演算ができるパッケージで,三角関数なども実装されているので 今回,フラクタル図形を生成するには実装上問題ないはずでした.しかし,以下のメリット,デメリットの為にあえなく断念しました.

  • TeXレジスタに依存しない巨大小数点の演算が可能
  • \FPevalで数式の解釈が可能
  • 演算が極めて遅い

前回,モンテカルロ積分を実装した時に使ったパッケージなので 今回も使おうと思いましたが再帰的に大量の計算を行う今回のケースには不向きでした.そもそもそこまで巨大な数を扱う予定もありません.

calculatorパッケージ

次に目を付けたのはこのパッケージです.このパッケージは固定小数点演算ができ,TeXレジスタに依存していて\pm 16383.99999の範囲の小数点のみ扱うので計算はFPパッケージよりも速いです.しかし,このパッケージも使うのは断念しました.

  • FPパッケージよりは計算が速い
  • 可読性が低い
    • マクロ名が全て大文字
    • 数式の評価ができない
    • 一つの演算に一つのマクロ

PGFパッケージ

今回使ったパッケージです.このパッケージも固定小数点演算ができます.採用した理由は以下の通りです.

  • 数式が解釈できる
  • 演算がFPパッケージより速い

PGFパッケージを使って描いたフラクタル図形を紹介します. 画像は実際に描画したPDFをjpgにしたものです.

円のフラクタル図形

図形はここを参考にしました. f:id:muscle_keisuke:20170422173321j:plain マクロのソースは以下のようになっていて再帰になっているのが分かります.

\newcounter{deep}
\def\circleR#1#2#3#4{{
  \pgfmathsetmacro{\xA}{#1}
  \pgfmathsetmacro{\yA}{#2}
  \pgfmathsetmacro{\R}{#3}
  \setcounter{deep}{#4}

  \draw[ultra thin] (\xA, \yA) circle (\R);

  \pgfmathsetmacro{\newR}{\R/2}

  \ifnum \value{deep}=1
  \else
    \circleR{\xA - \newR}{\yA}{\newR}{#4-1}
    \circleR{\xA + \newR}{\yA}{\newR}{#4-1}
  \fi
}}

コッホ曲線

f:id:muscle_keisuke:20170422173328j:plain

シェルピンスキーのギャスケット

f:id:muscle_keisuke:20170422173332j:plain

木も生やしてみた

f:id:muscle_keisuke:20170422180248j:plain 木はそのまま自己相似形にすると自然ではないので枝分かれの角度を乱数によって決めています. また,枝が分かれるほど線を細く描画するようにしています.

\def\Tree#1#2#3#4#5{{%
  \pgfmathsetmacro{\xA}{#1}
  \pgfmathsetmacro{\yA}{#2}
  \pgfmathsetmacro{\R}{#3}
  \pgfmathsetmacro{\Degree}{#4}
  \setcounter{deep}{#5}
  \setlength{\treeBold}{\value{deep} pt}

  \pgfmathsetmacro{\xB}{\xA - \R*cos(\Degree)}
  \pgfmathsetmacro{\yB}{\yA + \R*sin(\Degree)}
  \draw[line width=\treeBold] (\xA, \yA) -- (\xB, \yB);
  \ifnum \value{deep}=1
  \else
    \pgfmathsetmacro{\newR}{0.8*\R}
    \pgfmathrandominteger{\a}{0}{60}
    \pgfmathsetmacro{\newAngleLeft}{\Degree+\a}
    \pgfmathrandominteger{\a}{0}{60}
    \pgfmathsetmacro{\newAngleRight}{\Degree-\a}
    \Tree{\xB}{\yB}{\newR}{\newAngleLeft}{#5-1}
    \Tree{\xB}{\yB}{\newR}{\newAngleRight}{#5-1}
  \fi
}}

最後に

今回,作成したフラクタル図形のソースは以下に載せておきます.

フラクタル図形を描画するfractal.sty

LaTeX+TikZでモンテカルロ積分の可視化

この記事は

TeX & LaTeX Advent Calendar 2016 - Adventar

の13日目の記事です.

はじめに

LaTeXモンテカルロ積分を行うプログラムを作りました.Tikzで可視化もしました.

何を作ったのか

モンテカルロ積分によって円周率の近似値を求めるプログラムです. 左にモンテカルロ積分を可視化した図,右にプロットした点の数や求まった円周率の近似解を表示するプログラムです.

モンテカルロ積分について

モンテカルロ積分

モンテカルロ積分とはモンテカルロ法によって定積分の近似値を求めるアルゴリズムです. モンテカルロ法は疑似乱数を用いてシミュレーションや数値計算を行う手法の総称です. つまりモンテカルロ積分は疑似乱数を用いて定積分の近似値を求めるアルゴリズムです.

モンテカルロ積分で円周率を求めるには

積分値に{\pi}が含まれるような関数の積分値の近似値を求めます. 今回は以下の定積分を解いて円周率を解きます.

{ \displaystyle
\int_0^1 \sqrt{1 - x^2}
}

この定積分の解は

{ \displaystyle
\int_0^1 \sqrt{1 - x^2} = \frac{\pi}{4}
}

ですので定積分の近似解を4倍すれば円周率の近似値が得られそうです.

{ \displaystyle
\int_0^1 4\sqrt{1 - x^2} = \pi
}

モンテカルロ積分の手順

今回の関数は { \displaystyle
\sqrt{1 - x^2}
}積分を0から1まで求めるので次のようなイメージになります. f:id:muscle_keisuke:20161205153744p:plain

この内部の面積を{S}とします.

この関数に囲まれた部分の面積{S}を求めます.次にこの正方形内部にランダムに点を打ちます. 次の図はプロット点数1000で描画した時のイメージです.青の点が内部の点,赤の点が外部の点です. f:id:muscle_keisuke:20161205155637p:plain

全体の点数と青の点数の比と正方形の面積と求めたい面積の比が近似することを利用して比の方程式で{S}の近似値を求めます.
{全体の点数 : 青の点数 \approx 正方形の面積 : S}
{1000 : 青の点数 \approx 1 : S}
{S \approx \frac{青の点数}{1000}}
{S}の本当の値は{\frac{\pi}{4}}なので円周率の近似値は
{\pi \approx 4\frac{青の点数}{1000}} となります.

TeX+TikZでモンテカルロ積分の実装

変数やマクロを定義する

積分範囲はTeXのマクロに定義し,赤青の点数などカウントが必要なものはLaTeXの変数に代入してしまいます.

\def\startpoint{0}
\def\endpoint{1}

\newcounter{redplot}
\newcounter{blueplot}
\setcounter{redplot}{0}
\setcounter{blueplot}{0}

プロット点数は標準入力で

プロット点数は入力できるようにしました.

\newcount\input

\message{プロット点数を入力してください}
\read-1 to \input

正方形や関数を描く

Tikzで正方形や関数を描画します.

% 枠を描画
\draw (\startpoint,\startpoint) rectangle (\endpoint,\endpoint);
% 関数を描画
\draw plot[domain=\startpoint:\endpoint,samples=100] (\x,{func(\x)});

これで準備は整いました.後は点をプロットして円周率の近似値を求めます.

fpパッケージを使いTeXで固定小数点演算を行う

0から1の積分なのでランダムにプロットする点は必ず小数になります. しかし,TeXは標準で四則演算くらいはできますが小数を扱うことができません.なので fpパッケージと呼ばれるTeXで固定小数演算を行うパッケージを使います. 私の実行環境であるTeXLive2016では標準で入っていました. fpは小数演算の四則演算だけでなく指数計算や対数計算など様々な計算をすることが可能です. 詳しくは以下の方の記事が分かりやすいです.

天地有情 [LaTeX] fp --- 固定小数点数演算

ランダムに点をプロットする

用意できたら点をランダムにプロットしていきます. fpパッケージには乱数を発生するマクロもあります. まずはプロットする点をランダムに決めていきます.

% 今日,0時から何分経過したかでシードを決定
\FPseed = \time
\begin{document}
.
.
.
% ランダムにx座標を決める
\FPrandom{\randomX}
% f(randomX)を求める
\FPpow{\randomFuncX}{\randomX}{2} % randomX^2
\FPsub{\randomFuncX}{1}{\randomFuncX} % 1 - randomX^2
\FProot{\randomFuncX}{\randomFuncX}{2} % √1 - randomX^2
% randomXをfp以外で評価できる形にする
\FPeval\plotX{\randomX}
% f(randomX)をfp以外で評価できる形にする
\FPeval\evalX{\randomFuncX}
% ランダムにy座標を決める
\FPrandom{\randomY}
% randomYをfp以外で評価できる形にする
\FPeval\plotY{\randomY}

本当は以下のようにも書けるのですがオーバーフローを引き起こすので上のような遠回りをしています.

\FPeval\plotX{random()}
\FPeval\evalX{root(1 - \plotX^2,2)}
\FPeval\plotY{random()}

点が関数の内側かどうかを判定する

ある点(X, Y)が関数の内側にあるかどうかは以下の不等式で判定可能です.
{f(X) > Y}
これをTikZの条件式で判定します.

% 関数の求める範囲の外側の場合は赤,内側の場合は青で点をプロット
\pgfmathparse{\evalX < \plotY ? "red" : "blue"}
% blueかredを\colorに定義
\edef\color{\pgfmathresult}

次に定義した\colorを元に青の点と赤の点をカウントします.

% 赤の点と青の点を数える
\ifthenelse{\equal{\color}{red}}{\stepcounter{redplot}}{\stepcounter{blueplot}}

点をプロット

Tikzのコマンドで点をプロットします.

% 点をプロットする
\filldraw[fill=\color, ultra thin] (\plotX, \plotY) circle [radius=0.01];

入力分ループを回す

それではここまでの処理をTikZのforechで回していきます.

\foreach \i in {1,...,\input}
{
      % ランダムにx座標を決める
      \FPrandom{\randomX}
      \FPpow{\randomFuncX}{\randomX}{2}
      \FPsub{\randomFuncX}{1}{\randomFuncX}
      \FProot{\randomFuncX}{\randomFuncX}{2}
      \FPeval\plotX{\randomX}
      \FPeval\evalX{\randomFuncX}
      % ランダムにy座標を決める
      \FPrandom{\randomY}
      \FPeval\plotY{\randomY}
      % 関数の求める範囲の外側の場合は赤,内側の場合は青で点をプロット
      \pgfmathparse{\plotY > \evalX ? "blue" : "red"}
      \edef\color{\pgfmathresult}
      % 赤の点と青の点を数える
      \ifthenelse{\equal{\color}{red}}{\stepcounter{redplot}}{\stepcounter{blueplot}}
      % 点をプロットする
      \filldraw[fill=\color, ultra thin] (\plotX, \plotY) circle [radius=0.01];
}

コンパイル

それではコンパイルしていきます.私の環境ではplatex+dvipdfmxでコンパイルしています. コンパイルしたら「プロット点数を入力してください」と表示されるので 1000とか入力します. そうするとコンパイルされてdviが出てくるのでそれもpdfに変換して表示してみると 以下のような図が出ると思います.

f:id:muscle_keisuke:20161205155637p:plain

近似値なども表示する

プロット点数や近似値も表示させます. 全体の面積を求めます.

% 枠内全体の面積を変数に代入
% 四捨五入して整数にしている
\FPeval\overallS{round((\endpoint - \startpoint)^2,0)}

比の方程式を解いて,4倍して円周率の近似値を求めます. 小数点以下が多いので第9位まで丸めます.

%\arabic{blueplot} ... 青の点数
%\overallS ... 正方形の面積
%\input ... 全体のプロット点数
\FPeval\result{round(4 * \arabic{blueplot} * \overallS / \input,9)}

さて,それでは全て表示させます.

\begin{tikzpicture}
    \draw (7,5) node {全体の点数 = \input};
    \draw (7,4) node {青い点数 = \arabic{blueplot}};
    \draw (7,3) node {赤い点数 = \arabic{redplot}};
    \draw (7,2) node {全体的な面積 = \overallS};
    \draw (7,1) node {$\displaystyle\int^1_0 4\sqrt{1-x^2}$ = \result};
\end{tikzpicture}

図の方は一辺が1で原寸大ですので近似値などの表示に対して図が小さすぎます. なので図と文字のtikzpicture環境を分け図の方をscalebox環境で拡大しています.

プロット点数1000でやってみると次のような図が表示されます. f:id:muscle_keisuke:20161205184110p:plain

様々なプロット点数でやってみました. f:id:muscle_keisuke:20161206020732p:plain f:id:muscle_keisuke:20161206020738p:plain f:id:muscle_keisuke:20161206020742p:plain f:id:muscle_keisuke:20161206020746p:plain

それにしても精度が悪いですね.この辺は要改善ですね.

ソースコード

ソースコードgithubに上がっているので全ソースを見たい人は 参照してください.

github.com