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

TeXで強制改行コマンドの後に角括弧をつけるとエラーを吐く理由と対策について

はじめに

はまった上にこれについて書いてある記事なども見つからなかったので備忘録的に残します.

強制改行(\\)の後に角括弧([])を使うと謎のエラーが

LaTeXを書いている時に

[1] http://hoge.hoge.com \\
[2] http://fuga.fuga.com

の部分でエラーが発生しました. しかも,エラーの内容は???でした.

! Illegal unit of measure (pt inserted).

このエラーはTeXで定義されていない寸法単位が使われている時に出るエラーです.
寸法単位なんかどこにも出てきていないしエラーとは関係なさそうに見えます.
この症状は環境が異なっても起こりました.自分は以下の環境で試しました.

  • TeXLive 2015
    • e-pTeX + dvipdfmx
    • LuaTeX

問題は強制改行(\\)のオプション引数

強制改行もTeXのマクロの一つです.よく調べてみると強制改行はオプション引数を取るみたいです.

\\[10pt]

などとすると行間を10pt空けて改行してくれるみたいです.
つまり強制改行のオプション引数は行間の幅を指定する為のもので
強制改行後の角括弧はオプション引数としての括弧として認識されてしまったということです.
角括弧の中の数字には寸法単位がついていない為先述したような謎のエラーが出たという訳です.

なぜ,強制改行(\\)と角括弧([])は違う行にあるのにこのような症状が出るのか

これにはTeX組版としてのルールが絡んできます. 今回影響したTeXの性質として次のような性質があります.

  • (空行以外の)改行文字は空白文字になる
  • 行頭と行末にある空白文字・タブは全て無視される

これにより強制改行と角括弧の間の改行文字が無視されたという訳です.

解決策

これを解決するには強制改行の後に\relaxというコマンドを入れます. つまり以下のようにすれば解決します.

[1] http://hoge.hoge.com \\ \relax
[2] http://fuga.fuga.com

\relaxは「なにもしない」コマンドです.しかし,これが大いに役に立ちます.
\relaxはオプション引数を取らないので角括弧がオプション引数としての括弧として認識されないからです.

最後に

今回はこうなる理由が知りたかったので合わせて解決も書きましたが
本当は強制改行は極力使わない方が良いと思います.
今回の解決策を施したところで1行目が段落の始まりで字下げを行っているので2行目と行頭が揃っていません.
字下げを行わないコマンドもありますがそれならverbatim環境などを使った方がまだいいと思います.
一番良いのは参考文献の記述を人力でやるのではなくthebibliography環境などを使うことだと思います.

LaTeXの表を生成できるサイトTables Generator

はじめに

LaTeXで表を作成するのはとても面倒くさいですよね.
表の部分だけExcelを使いたいと思う人も多いのではないでしょうか.
そこでこんなサイトを見つけました.

www.tablesgenerator.com

このサイトはマウスで作成した表からLaTeXのコードを生成するWebアプリです.

使ってみる

下図にある赤枠の表を編集して青枠の「Generate」ボタンを押すと緑枠内にLaTeXのコードを生成してくれます.
試しに生成されたコードをコピペしてPDFを生成してみます.

f:id:muscle_keisuke:20160702145552p:plain

赤枠の表と同じような表ができあがりました.

f:id:muscle_keisuke:20160702151903j:plain

表を作ってみる

次にサンプル以外の表を生成してみます. 今回は次の表を作っていきます.

f:id:muscle_keisuke:20160702162503j:plain

最初にサンプルの表を消します.

f:id:muscle_keisuke:20160702153425p:plain

次に作る表のサイズを決めます.今回は3x3の表を作ります.

f:id:muscle_keisuke:20160702153145p:plain

そうすると,プレビューに空の表ができたと思います.これを編集していきます. 1行1列目から文字を入れていきます.表の編集したいマスをダブルクリックすると文字が打てます.

f:id:muscle_keisuke:20160702162516p:plain

他のマスも同様に編集していきます.
次にマウスを表の上でドラッグしてマスを全選択します.
そして,図のマウスが指しているボタンを押すとすべてのマスに罫線が引かれます.

f:id:muscle_keisuke:20160702162527p:plain

次に各マス内の文字列をセンタリングします.
全選択はされたままなので図のマウスで指しているボタンを押すと
全てのマス内の文字列がセンタリングされます.

f:id:muscle_keisuke:20160702162536p:plain

これで完成です.後は「Generate」ボタンを押すとLaTeXのコードを吐き出してくれます.
以下のようなコードができれば完成です.

\begin{table}[]
\centering
\caption{My caption}
\label{my-label}
\begin{tabular}{|c|c|c|}
\hline&&\\ \hline&&\\ \hline&&\\ \hline
\end{tabular}
\end{table}

キャプションは自動で付きますので扱いは自由にしてください.

色をつけたり,更に複雑な表も作れる

この他にも更に複雑な表も作れます.
直感で操作できますのでいろいろ試してみてください.
LaTeXでは色をつけたりする際には追加のスタイルファイルが必要ですが,
Tables Generatorではコードを生成する時に必要なパッケージをコメントで案内してくれます.

f:id:muscle_keisuke:20160702163847p:plain

Excelで作って読み込むこともできる

ExcelLibreOffice,CSVなど,別のデータから表を読み出すこともできます.

f:id:muscle_keisuke:20160702164602p:plain

  • Import CSV file... CSVファイルを読み表を生成
  • Paste table data... Office系のソフトで作った表をコピペすることで表を生成
  • From LaTeX code... LaTeXソースコードから表を生成
  • Save table... 編集途中の表を保存する
  • Load table... 保存した編集途中の表を読み込む

また,ここでは紹介しませんでしたがLaTeXだけでなく,
HTMLやMarkdownのコードも生成することができます.

まとめ

マスが多い表ほど,このサイトは重宝すると思います.
時間的コストはOfficeで作るのとではそれほど変わらないと思います.

LaTeXで載せたプログラムソースがページを跨ぐ場合の対処法について

はじめに

私はとある大学に通う情報系の学生なのですが,レポートをLaTeXで書くことが多々あります.
情報系の演習ではレポートにプログラムのソースコードを載せることがあります.
しかし,ソースコードが長くて1ページに収まりきらないこともよくあります.
その時,LaTeXでははみ出した部分のソースを正常に表示してくれないことがあります.
はみ出した部分は自動的に改ページしてくれないのです.
そんな時の対処法をここでは環境,コマンド別に紹介していきます.

今回対象となる環境,コマンド

  • itembox + verbatim
  • listing
  • minted

    itembox + verbatim

    私が1年生の時に受講していた演習においてレポートにソースを載せる際はこの方法でした.
    最初からある環境のみを使用しているので簡単です.
    しかし,ソースがページを跨ぐ場合ははみ出てしまい,次のページに表示してくれません.
    他の学生は一旦itemboxを切って,次のページで新しいitemboxで囲んだりしたりしてました.
    実はあるパッケージを導入すればページを跨いだ場合でも自動的にitemboxを改ページしてくれるのです.

    breakitembox

    itemboxに代わる,breakitemboxという環境を使えばページを跨いだ場合でもきちんと対処してくれます.

    導入方法

  • 以下のパッケージを全てダウンロードする.
    • itembkbx.sty
    • itembbox.sty
    • emathC.sty
    • jquote.sty
    • eclbkbox.sty
  • \usepackage{itembkbx}で読み込む.
  • 使う

パッケージはCTANにはないのでgithubからダウンロードしましょう.
github.com
パッケージを全てダウンロードしたらそれらを全て読み込める状態にします.

texファイルと同じディレクトリに置く

breakitemboxを使いたいtexソースと同じディレクトリに先程ダウンロードしたパッケージを置きます.

$TEXMFHOMEに置く

$TEXMFHOMEの場所を把握します.

kpsewhich -var-value $TEXMFHOME

パスが返ってくるのでその中にtexディレクトリを作ります. デフォルトでは以下のようになっていると思います.

~/texmf/tex

その直下にパッケージを置きます. 置いたら次のコマンドで更新します.

mktexlsr [$TEMFHOMEが設定されているパス] # ex) mktexlsr ~/texmf

使い方

使い方はitemboxとさほど変わりません.

\begin{breakitembox}[l]{見出し}
    \begin{verbatim}
        #include<stdio.h>
        int main(void){
            printf(Hello World!\n);
            return 0;
        }
    \end{verbatim}
\end{breakitembox}

このようにすると以下のような結果が得られます. f:id:muscle_keisuke:20160211170919p:plain
行数が多いプログラムはファイルから読み込みましょう.
verbatimパッケージを読み込んでverbatiminputコマンドを使います.

\begin{breakitembox}[l]{見出し}
   \verbatiminput{test.c}
\end{breakitembox}

また,ページを跨ぐ場合にはこんな感じになります. f:id:muscle_keisuke:20160211173512p:plain f:id:muscle_keisuke:20160211173508p:plain

listing

listingはbreakitemboxとは違いソースコードを載せる専用のパッケージです. こちらはデフォルトでページを跨いだ時の処理をしてくれる便利なパッケージです.
使用するパッケージはlistings,jlistingの2つです.
listingsはtexlive2015の時点では最初から入っているようなのでダウンロードはする必要ありません.
ない人はCTANからダウンロードしましょう.
CTAN: Package listings
jlistingはlistingsに日本語対応させる為のパッケージなので入れておきましょう.こちらからダウンロードします.
Listings - MyTeXpert
ダウンロードしたら$TEXMFHOMEに入れて使ってみます.

使い方

まずはプリアンブル部にlistingの設定を書きます.

今回はlisting中心の記事ではないので以下をコピペして使ってください.

\usepackage{listings,jlisting}
\usepackage{color}
\lstset{%
  language={C},
  basicstyle={\small},%
  identifierstyle={\small},%
  commentstyle={\small\itshape\color[rgb]{0,0.5,0}},%
  keywordstyle={\small\bfseries\color[rgb]{0,0,1}},%
  ndkeywordstyle={\small},%
  stringstyle={\small\ttfamily\color[rgb]{1,0,1}},
  frame={tb},
  breaklines=true,
  columns=[l]{fullflexible},%
  numbers=left,%
  xrightmargin=0zw,%
  xleftmargin=3zw,%
  numberstyle={\scriptsize},%
  stepnumber=1,
  numbersep=1zw,%
  lineskip=-0.5ex%
}

それでは実際に使っていきます.

\begin{lstlisting}[caption=test,label=test]
   #include<stdio.h>
   int main(void){
       printf("Hello World!\n");
       return 0;
   }
\end{lstlisting}

このように出力されます.
f:id:muscle_keisuke:20160211184701p:plain
ページをまたいでも大丈夫です. f:id:muscle_keisuke:20160211185216p:plain
ソースコードをファイルから読む場合は以下のようにしましょう.

\lstinputlisting[caption=aaa,label=aaa]{test.c}

minted

このパッケージもまたソースコードを載せるためのパッケージです.
導入方法は過去の記事にあるのでそちらを参考にしてください.
muscle-keisuke.hatenablog.com
mintedもlisting同様ページを跨いだ時に適切な処理をしてくれます.

\begin{minted}{c}
   #include<stdio.h>
   int main(void){
       printf("Hello World!\n");
       return 0;
   }
\end{minted}

出力は以下の通りです.
f:id:muscle_keisuke:20160211194141p:plain
ページを跨ぐ時でもこのように適切に処理してくれます.
f:id:muscle_keisuke:20160211193806p:plain

注意点

mintedは確かにデフォルトでページを跨ぐ処理をやってくれるのですが,ある条件下ではそれが働かなくなります.それは
- キャプションをつけるためにlisting環境を使った時,
- ソースコードを載せる部分の背景色を指定した時
です.
このいずれかの条件が入っている時出力は次のようになります.
f:id:muscle_keisuke:20160211205932p:plain
ページ末尾にあるページ番号を超えてコードが出力されているのが分かります.
これを防ぐために次の環境,コマンドを使います.
- mdframed …背景色が使えない代わりの枠組み
- captionof …listing環境が使えない -> キャプションを付加できない よってその代わり
mdframed環境はmdframedパッケージで,captionofコマンドはcaptionパッケージで読み込みます.どちらもtexlive2015には標準で入っているはずですが,ない場合はCTANから
CTAN: Package mdframed
CTAN: Package caption
使い方は以下の通りです.

\definecolor{bg}{rgb}{0.95,0.95,0.95}
\begin{mdframed}[
    backgroundcolor=bg,
    topline=false,
    bottomline=false,
    leftline=false,
    rightline=false]
    \begin{minted}{c}
        #include<stdio.h>
        int main(void){
            printf("Hello World!\n");
            return 0;
        }
    \end{minted}
\end{mdframed}
\captionof{listing}{テストキャプション}

最初の行でdefinecolorコマンドを使っていますが,これは背景色を定義しています.definecolorコマンドはcolorパッケージの中ですので読み込むのを忘れずにしてください.
minted環境をmdframed環境で囲んでください.
mdframed環境のオプション引数ですが,
backgroundcolor=定義したもしくはデフォルトで定義されている色
で背景色を設定できます.
その後の引数は単純にmdframedの枠線を出力しない為の設定です.
(mintedでも枠線を定義できるので重複する)
下から二番目のところでcaptionofコマンドを使ってキャプションを設定しています.第二引数にキャプションの文を書きます.
第一引数のlistingという部分はキャプションの種類です.主に使うものを以下にまとめます.
- listing …ソースコードなどを載せる時のキャプション
- figure …グラフや図を載せる時のキャプション
- table …表を載せる時のキャプション
という感じです.
captionofコマンドではなく通常のcaptionコマンドでつけたキャプションとの番号のずれはなくそこらへんはうまくやってくれます.
実行すると以下のように出力されます.
f:id:muscle_keisuke:20160211212414p:plain
ページを跨いだ場合でも大丈夫です.
f:id:muscle_keisuke:20160211222351p:plain

まとめ

itembox+verbatimを使って長いソースコードをぶつ切りにしてた人は
breakitembox+verbatiminputを使いましょう.
また,listingやmintedを使えばデフォルトでページを跨ぐ処理を適切に行います.
ただし,mintedでページを跨がない場合はmdframedとcaptionofを駆使して解決しましょう.