小説家になろうからルビを取ってきて漢字から読みを推論する

twitterで、タイトルみたいなのを見つけた。確かに、小説家になろうからルビをとって読みを推論すれば中二病的なイカす読みを取り出せるのではということでやってみました。

ルビを集め辞書を作成

クロールしてよしなに集める 単語総数は8932です。

word2vecで解析

元文字列は、基本的に単語(文のときもある)なので、単語として解析すると関係性を表現できないので、元文字列を文字に分解して その文字を、wordとしてword2vecを作成しました。

暗黒物質を例に
word => 暗黒, 物質
char => 暗, 黒, 物, 質

それぞれの、文字のベクトルの総和を素性としました。

読みの推論

以上の素性を用いて、一般的な学習機で分類を行いました。

結果

辞書に存在しないワードを選んでいます

"悪魔の左腕" => "レッドネイル"
"青空文書書式" => "エキスパート"
"国際宇宙ステーション" => "箱舟"

まとめ

割りとそれっぽい読みを取り出せた。 ただ、クロールしてきた単語の重複や、特殊文字を含んでいるもの削除するなどした結果、辞書が9000ワードくらいになっていた。そのため、word2vecに含まれていない文字があったり、推論したワードの読みの重複が目立ちました。 また、今回は、ワードの分類だったので、文字列生成のように驚きのある読みは出現しませんでした。次は、生成の方もやってみたい。

source

github.com

最近vimgolfやってる

最近はだいぶvimにも慣れてきて、下手にIDEを使うよりもvimを使う方が楽に感じるようになってきた。 ところが、友人の扱うvim技術を見ていると、どうやら、自分のvim使いはかなりよろしくないことがわかった。 そこで、一度しっかりと基本的なコマンドを覚えなおして、vimプラグインもちゃんと選択して、場合によってはvim scriptぐらいかけるようになりたいと思った。

その取っ掛かりとして、vimgolfをやってみようと思う。

vimgolf

http://www.vimgolf.com/

vimgolfとは、テキストが提供されてそれを目的の状態に修正する、そのときのキーストロークの長さをより短くする競技のようなものである。 例えば...

- One number per line -
-----------------------
2,3,5,7,
11,13,17,
19,23,29,

というテキストを提供されて

2
3
5
7
11
13
17
19
23
29

に修正するといったような具合である。

わかったこと

vimgolfでは、自分の提出したキーストロークよりも少し少ない数の提出者のキーストロークをみることができるので、それと自分のを見比べた。 結果、ほぼ、すべての課題であまり使ったこと無いコマンドが存在していた。

コマンド 意味
c 選択されている文字を削除してInsertModeに移行
r 選択されている文字を次に入力する文字に置換する
f fの次に入力する文字が次に出現する箇所にカーソルを移動
q 指定したキーストロークを登録し呼び出すことができる(マクロ)
. 直前に実行したコマンドを再実行
V 行全体を選択するVisualModeに移行

いくつか使ってなかった重要なコマンドを上げてみた。特に、qやc、fはかなり使われていた。実際、文字を新しく入力するよりも、修正することが多いので、削除して入力したり、似たような構造がいくつかあるような場合になマクロを組むと、一気に改善する場合が多かった。

逆に、修正という意味で利用していたvim sed(:%s)が、実際に使おうとすると、文字をすべて入力しないといけなくなるため、かなりキーストロークが伸びてしまい、vimgolfだとあんまり良い手段ではなかった、マクロで組んだほうがよかったりする。

これだけでも、かなり、vim使いが改善してきたように感じるので、これらのコマンドに慣れていきたい。

ざこ文字認識の話

文字認識システムを作りました。開発速度重視で具体的な手法については全く調べていません。

実行してみて動いたらそれを使うくらいの勢いなので、かなり間違ってると思うので、生暖かく見てやってください。

1. 文字の写真を撮影

テスト時はカメラで取ってました。

f:id:naxatoko:20171207103526j:plain

この画像は、テストに使った写真です。

2. 写真からエッジ検出

OpenCVさんのちからを借りて、Canny法を使いました。

import cv2
imgpath = "/path/to/img"
img = cv2.imread("/path/to/img", 0)

# lowより下は、エッジにはならない、highより上はエッジになるというしきい値を指定
cv2.Canny(img, low, high)  # => エッジ検出された画像データ

以下が、エッジを検出した結果の画像です。

f:id:naxatoko:20171207103543p:plain

3. 文字領域を抽出

文字領域とは、ある画像内で、文字が写っている一部分を抽出することです。

パッと見、隣接しているエッジ(白)ピクセルで、一文字と見ることができそうと感じたので、隣接する白ピクセルを走査し、 それで見つけたピクセルの最大のx,yと最少のx,yを抽出し、その座標を通る四角で囲われる部分を文字領域として認識しています。

実際には、見てもわかる通り、エッジは色が変わる境界線なので、Bや0のように、外側と内側が隣接しない場合があります。なので、少しだけ離れていても(間に黒ピクセルを含んでも)隣接していると判断する必要があり、調節が必要です。

以下は、その四角を画像に表示したものです。

f:id:naxatoko:20171207103613p:plain

4. 抽出した領域情報から写真内の文字領域を実際に抽出

f:id:naxatoko:20171207175349p:plainf:id:naxatoko:20171207175351p:plainf:id:naxatoko:20171207175356p:plain........

四角に沿って、画像を切り分けて抽出します。

5. 抽出した画像をうまい具合にリサイズ

今のままだと文字の画像のサイズが、それぞれ違うため、そのまま学習機に突っ込むわけには行きません。 全ての画像を等倍(縦横のピクセルの数が全て同じ)にしなければ次の学習したり認識したりする時に支障がでるので、リサイズします。 全て同じサイズならどんなサイズでも問題ありません。とりあえず、今回使った32x32にするために行った手順を紹介したいと思います。

まず、縦横の長い方を32に合わせて比率を保持したままリサイズします。

f:id:naxatoko:20171207175349p:plainf:id:naxatoko:20171207211155p:plain

元画像の比を保存しつつサイズを合わせたいので、短い方は両はじに黒ピクセルを追加します。ただし、元画像が中心に来るように追加する黒ピクセルの幅は両はじとも均等になるようにします。

f:id:naxatoko:20171207211155p:plainf:id:naxatoko:20171207211423p:plain わかりづらいかもしれませんが、左右に黒い空間ができていると思います。

6. リサイズした文字画像を機械学習を使って文字認識

trainデータはラボのメンバーに作成をお願いしました(一文字30文字づつで、全部で350文字ほど)。 そのtrainデータを上記の加工を施して学習機に学習させました。

modelはKNNとSVMで、グリッドサーチを行いパラメータ探索をして生成しました。

7. 結果

cross validationの結果を載せておきます。

  • KNN (K=1)
    • accuracy : 0.730
  • SVM
    • accuracy : 0.765

正直、このままでは使い物に成りませんが、ここから先は精度上げであって、実装ではないので、とりあえず良いかなと思います。

まとめ

2週間ほどの突貫作業でしたが、精度は置いておいて、意外とものに成りました。

実は、角度を変えた画像でtrainデータをかさ増ししたり、学習時に使うデータを加工してみたりした結果、accuracy 0.9くらいになったりしていたり、スマホで写真を取ると自動で認識を開始したりする機能も付けたりしてますが、それはそれでもう一記事くらいかけそうなのでここであえては書きません。

機械学習に入門してからかれこれ1年ほど立ちますが、こういった形でアウトプットできる機会がなかったので、非常に良い機会でした。また、自分のスキルアップを直に感じることができたのでモチベーションにもうまい具合に響いてくれると良いなと感じました。

以上です。ご読了ありがとうございます。

Elixirでチャットサーバを作ってみた

動機

Elixirを使ってスクレイピングとか、そんなことをやっていたのですが、ふとあんまりElixirらしい使い方では無いなと思いました。 それで、ElixirらしいといえばOTPなんかを使ったサーバサイドプログラミングだと思っているので、そんな感じのことをすればElixirのなんたるかが見えてくるのではないかと思ったので、軽めにチャットサーバなんかを作ってみようと思いました。

目的

  • チャットサーバの作成
  • チャンネル機能の実装
  • 各種チャットの情報を取得するコマンドの実装
  • OTPの学習(最初はホントに名前くらいしか知らなかった)注: 今回全く使いこなせてません。

OTP(Behavior)

とりあえずわかったこと

Behaviorとは、振る舞いという意味で、ここでは、サーバの振る舞いという意味。この振る舞いというのは、サーバを構築する上でよくでてくる実装をサーバのBehaviorとしていて、その振る舞いをテンプレート化しようっていうやつがらしい。なので、やってることは、簡単で細かいフレームワーク群を用意しているみたいな感じだった。

GenServer Behavior

  • Erlang VM上でプロセス間通信を行う際に行う処理のBehaviorの一つ
  • コールバック何かを使って、プロセス間通信の処理を隠蔽している

Supervisor Behavior

  • Supervisorが監視しているプロセスがクラッシュした場合、指定した戦略に基づいてプロセスを再起動する。
  • Supervisor Treeといった形で、プロセスをSuperviseして、更にそれをSuperviseして、Tree状にして管理するらしい

できていない点

  1. Behaviorを使って、他のVM上のプログラムとの通信を行うこと。
  2. クライアント/サーバシステムとして設計したが、クライアントとサーバを物理的に別のプログラムにした(同じVM上じゃなくした)ためGenServerのようなBehaviorで通信を行うことができなかった。
  3. プロセス化したクライアントアクセプタがうまく機能しなかったうえ、Superviseもうまく行かなった。

改善のアイディア

  • とりあえず、設計をもっとElixir(Erlang)に合わせて考える。
    • すごいErlangゆかいに学ぼう!を読め
  • Supervisor Treeの設計も念頭において設計をする
    • まず、Supervisorを使いこなせ
  • サンプルを見ているとサーバとクライアントを同じVM上に実装していたので、真似する。

    • どうやって、サーバとクライアントを物理的に離して接続してるのか全く想像がつかない
    • ElixirのNodeという機能を使うことで実現できるみたいです。Nodeについては、これから勉強します。
  • Elixirむずくね?

というわけで、チャットシステムの概要

f:id:naxatoko:20170721231808p:plain こんな感じで、サーバとクライアントを分けてます。 Processとかいうよくわかんない部分がありますが、実は、Reciverの部分に取り込まれており、実は、明確には分割されていません。 当初はchannelシステムなしで行こうと考えていたのでstate lessなシステムになると思っていたのですが、途中でchannelシステムを追加したため、思ったよりも状態が複雑になってしまい、そのへんがグダグダになってしまっいました。

システムの流れ

  1. クライアントから何らかのeventが送信される
  2. サーバがそのイベントを受信し、それに対応した処理を行う
  3. その結果、クライアントへ送信する必要があれば、eventを送信する
  4. クライアントは、eventを受け取ったらそれに応じた処理を行う

機能の解説

発言する以外の機能を用いる際は、:hogehogeといったようにすると、存在する機能の場合、hogehoge機能に対する処理が実行されます。

  1. channel機能

    • :channel_list
      • 現在サーバが管理しているチャンネル一覧を表示する
    • :now_channel
      • 現在、クライアントが所属しているチャンネルを表示する
    • :user_list
      • 現在所属しているチャンネルのユーザ一覧を表示する
    • :move arg1
      • arg1で指定したチャンネルに所属チャンネルを移動する
    • :create arg1
      • arg1で指定した名前のチャンネルを作成する
    • :delete arg1
      • arg1で指定した名前のチャンネルを削除する
  2. whisper機能

    • :whisper arg1 arg2
      • arg1で指定したユーザに対してarg2をメッセージとして送信
  3. say機能

    • arg1
      • 先頭にコマンドを指定しないで、入力すると、現在所属しているチャンネルにarg1を発言する

反省

  • Nodeの存在を知らなかったばっかりに、クライアントとのコネクションを:gen_tcpで持ってきてた。
    • Nodeの勉強します
  • Behaivior,OTPについて取っ掛かりをしっかりつかめたので、発展させていきたい
  • Supervisoruで管理できるプロセスは、基本的に何らかのBehaiviorに則ったプロセスのみなので、注意したい
  • すごいE本を読む

Source

Nodeの仕組みを知らなかったため、サーバとクライアントのソースが別れている;;

Rust で MPDクライアント作成

背景

私は、音楽再生にMPDを使っていて、クライアントはncmpcppを使っている。

でも、これはLinux機の話でwindows機だとiTunesを使っている。

個人的には、ncmpcppはキーボードだけで操作できるの良い点で、iTunesは、Albumで音楽を探せるのが非常に良いと思っている。

なので、その両方を兼ね備えた最強に見えるクライアントを作ろうと考えた。

開発環境

  • rust : 1.15.1
  • cargo : 0.16.0 - nightly
  • GUIライブラリ
    • GTK+3
    • gtk-rs : 0.1.2 feature : v3_16

 メインに考えている機能

1 それぞれの画面でのCUI操作

  • 音楽を選択して ‘a’ を押すと playlist に追加
  • playlist 画面で 選択した音楽を ’d' で plylist から削除
  • キーバインドの設定
  • GUIでの操作も可能

2 Album 画面

  • MPD内のアルバム全てをリストアップしてそのカバーアートを表示
  • 選択すると、そのアルバムの音楽リストを表示
  • カバーアートを音楽データから、あるいはネットから取得

現在の状況

  • 表示できるウィンドウ
    • playlist window( TreeView ) f:id:naxatoko:20170327222402p:plain
      • 現在再生している playlist の音楽データ(title, artist, album)を表示
      • 音楽を選択して ’d' を押すと playlist から音楽を削除
    • album window ( FlowBox ) f:id:naxatoko:20170327222410p:plain
      • アルバムのリストを表示
    • music directory window(mpd 内の音楽ディレクトリをそのまま表示) ( TreeView ) f:id:naxatoko:20170327222423p:plain
    • seek bar
      • 再生中の音楽の再生時間に応じて変化
      • 停止、再生などのボタンはあるが、何の動作もしない。

問題点

1 MPDのアルバム名取得(list album)で、アルバム名だけしか取得できないので、アルバムから音楽の取得がしにくい。

2 list album でアルバム名がかぶると一つにまとめられて表示されるので同じ名前のアルバムを表示できない。

3 album window でアルバムを選択すると、iTunes みたいに下の行に音楽リストを表示したいが、FlowBoxの特性上難しそう。

4 music directory window でMPDから音楽を取得するために、音楽のタイトルをファイル名にしている。

5 シークバーに gtk の Scale が ダサいのでProgressBarを使っているが、クリックなどのイベントがないので、時間操作ができない。

その他