卯月識別器の作成に挑戦した話
はじめに
機械学習とかDeepLearningとか、そういったワードを最近よく聞いてる気がするんですよ。
気になるのでそういうの触ってみたいなーとか思う訳ですよ。
僕は一応情報系の学科に所属する大学生で、パターン認識とかも少しくらいは勉強してる訳ですが、あまりプログラムとかは書いたことないなーって。
そんな中、今更ながら(?)ゼロから作るDeepLearningを読んでみたり、アニメキャラの識別をしている記事をいくつか読んだりして、「僕もなんかやってみたい!!!」ってなりました。
さて、プログラムを書く前に「まず何を識別しようか?」と考える訳ですが、やはりここは「推しキャラの識別に挑戦する」しかないでしょう。ちなみの僕の推しキャラはデレステの島村卯月です。卯月の笑顔はとても眩しくて、尊いです。
なので、顔画像に対してそれが島村卯月かそうでないかを判定するプログラムの作成にチャレンジしました。
勉強も兼ねて、Pythonで作りました。作成の流れは以下のようになりました。
- 卯月と卯月以外の顔画像を集める
- サイズの統一や明るさの調節など顔画像に前処理を行う
- CNNに顔画像をぶち込んで学習させる
また、学習結果を利用し卯月なら赤い枠を、そうでないなら青い枠を付けるプログラムを作成しました。完成したプログラムにいくつか画像を入れて試してみた結果、以下のようになりました。
訓練用のデータは全てアニメのスクショなので、アニメの卯月の情報からゲームの卯月を識別できていると考えれば、悪くないのかなという気はします。(もちろん、うまく識別できなかったケースもありますが…)
以下、作業の内容とかを少し書いていこうかなと思います。
顔画像の収集
機械学習で良い結果を出すにはたくさんのデータが必要らしいので、まずはアニメのスクショをたくさん撮り、そこから顔を切り取り卯月とそれ以外に分けます。ここで、大量のスクショから顔を切り取るという作業が地獄だという事に気付いたので、先人たちを参考にし、OpenCVを用いたアニメ顔識別器を利用してスクショから顔画像を切り取ってもらいました。
卯月かどうかの判定よりアニメ顔を見つける事の方が難しいのではないか。
こんな感じに切り取られた顔画像に対し、卯月と卯月以外に分ける作業を手作業で行いました。顔だと認識されていなかったり、顔以外のものが顔だと誤認識してるものもあるので、そういうのは自分で切り取ったり削除します。
プロデューサーと美城常務、顔認識されなさすぎる問題。(悲しいね)
最初は卯月とそれ以外で200枚と500枚くらい用意したのですが、これで学習させたらあまり上手くいかなかったので、最終的に550枚と1350枚くらいまで増やしました。ラブライブ!の人は6000枚以上、ごちうさの人は12000枚以上用意したらしいので、それに比べるとかなり少ないですね…けど、手作業で分類するの、結構疲れるんですよ…
プログラムに識別させるためにまず自分で識別してるの、なんだかなぁ。
顔画像の前処理
入力画像のサイズは統一する必要があるので、得られた顔画像を100*100の大きさに調節します。また、明るさの類は統一した方が良いのかなと思い、画素値の平均や標準偏差を揃えました。(この辺の処理は何が適切なのか正直良く分かっていない…いろんなパターンを試して比較するべきだった…)
学習
集めた顔画像を訓練用とテスト用に分け、実際に学習させます。Kerasというライブラリを用いてCNNを構築し学習させました。また、ちゃんと卯月を見分ける事ができるかどうか、ゲームの画像を何枚か用意し自分で与え、判定させて試したりします。上手くいかなかったら訓練用の顔画像を増やすべくアニメのスクショを撮りに行ったり、Kerasに用意されているデータの水増し機能を試してみたりしました。
以下、いろいろ試した過程です。
卯月識別器1号
記念すべき第1号です。データ数は卯月、卯月以外でそれぞれ200枚、500枚となっています。ゲームの画像を与えたところ、ラブレター卯月は卯月ではないと判定されました。道のりは険しそうです。
卯月識別器2号
データ数をそれぞれ350枚、950枚に増やしました。ところが、全て卯月じゃないと判定されてしまいました。
訓練データの正答率が100%となってるあたり、いわゆる過学習という奴でしょうか。やはりデータが少ないようです。
卯月識別器3号
OpenCVを使って顔画像を回転させ、データ数を3倍に増やしました。恒常卯月のみ卯月だと判定されました。
やはり、まだ足りないのでしょうか。頑張ってスクショ撮りましょう。(疲れるんだよなぁあの作業…)
卯月識別器4号
データ数をそれぞれ550枚、1350枚に増やしました。1号に比べるとだいぶ増えましたね。(まだまだ少ない感が拭えませんが…)
回転させてデータ数を3倍にした場合もそうでない場合も、恒常卯月しか卯月と判定してくれませんでした。
訓練用に含まれていないアニメopの卯月も1枚用意してみたんですが、それも卯月じゃないと言い出す始末。
お前は何を見て育ったんだ。
ループを回しすぎなのかなぁと思いループ数を減らしてみたら、天海春香が島村卯月だと判定されてしまいました。
ここで、最後の確認用のデータは手動で切り取っていたのですが、訓練データと同様にアニメ顔識別器で切り取った方が良いのではないか?と思いアニメ顔識別器を使って切り取って用いてみました。その結果、なんか精度が上がりました。訓練データの顔画像にはあまり髪の毛の部分が含まれていないので、そのせいで髪の毛を多く含んだラブレター卯月の顔画像は識別できていなかったのかなぁ…?(恒常佐久間まゆがうまく切り取られていなくて少し悲しかった。イーブイもダメだった。)
卯月識別器5号
データ数はそのままですが、Kerasにデータを水増しする機能が含まれているらしいので使ってみる事に。(良く分かっていない顔)
学習にかかる時間が増えているっぽいので、たぶんちゃんと水増しされています。最後の確認用のデータも増やしてみましたが、今までで一番いい感じに識別できています。そろそろ疲れてきたのでこの辺で一旦完成という事にして学習結果を保存しました。
卯月識別器の完成
得られた学習結果を利用し、画像が与えられたらアニメ顔認識器で顔を認識し、さらにそれが卯月であるかどうかを卯月識別器で判定するプログラムを作りました。記事の初めの方に載せた奴以外にもこんな感じの結果が得られました。顔認識がそもそもされていなかったり、されても卯月かどうかの判定に失敗していたりと、認識の難しさを感じる…
また、ゲームのイラストに関してはそこそこの精度でちゃんと卯月かどうか判定してくれるんですが、いろいろな絵師が描いた卯月については思ってた以上に精度が落ちますね…絵柄の違いが響いているのでしょうか。僕は絵柄とかによっては卯月かどうか判別しにくい場合があったりしますが、卯月識別器もそうなんですかね。いったい、何が「これは卯月である」と言わせているんですかね。
「卯月」とはいったい…(哲学)
感想
2,3日くらい卯月識別器と格闘していましたが、初めて挑戦したという事もあり、中々楽しかったですね。なんやかんやそれっぽく識別できてる感があるのもあり、いろいろな画像で試してみては「お~」って感じになってます。DeepLearningの類はデータがたくさん必要らしいですが、実際に動かしてみてそれを強く感じましたね…何はともあれ、使えるデータがたくさん必要だなと。今回はCNNの層の構成を変化させたり、画像の前処理などについてちゃんと調査したりしていないので、その辺についても調べていきたいですね。
また、今回いろいろ試してみて「まだまだ知識不足だな~」というのも強く感じたので、勉強も必要かなと。
気が向いたらソースコードどっかに上げたり改良版作ったりするかも…?
VisualStudio2017Communityの導入
VisualStudio 2017 Communityを入手してC言語(C++)でHelloWorld!!を表示するまでのメモ。
ダウンロードとインストール
まずは公式サイトからVisualStudio 2017 Communityのインストーラを入手。
すごい分かりにくいのは気のせいだろうか
ダウンロードしたらインストールすべく実行します。実行するとこんな感じの画面が出てくる。
今回はC言語(C++)を使いたいので、とりあえず「C++によるデスクトップ開発」にチェックを入れる。
完了すると再起動を要求されるので再起動。
再起動後、VisualStudio 2017が入っている事を確認。
実行するとサインインを要求される。
マイクロソフトのアカウントを持っているならサインイン、そうでなければアカウントを作りサインインする。
サインインに成功するとVisualStudio 2017が起動しこんな感じの画面が出る。
プロジェクトの作成からHelloWorld!!まで
試しにC言語(C++)を使ってコンソール上でHelloWorld!!を表示させる。
まずは「ファイル」→「新規作成」→「プロジェクト」を選択。
Visual C++の欄からWin32コンソールアプリケーションを選択。
名前とファイルの保存場所はご自由に。(今回名前はTest、保存場所はデスクトップ上にした。)
決定するとこんな感じの画面が出てくる。
次にソースコードを記述するためのソースファイルを追加する。
ソースファイルの箇所を右クリックし「追加」→「新しい項目」を選択。
C++ファイル(.cpp)を選択。ファイル名はご自由に。(今回はSource.cppのまま。)
すると、ソースコードが書けるようになるので次のコードを記述。
#include <stdio.h> int main() { printf("Hello World!!\n"); return 0; }
CtrlとF5を同時押しして実行すると「Hello World!!」が表示される(と思う)。
表示されたら成功。やったぜ。
コンソールが表示されない場合
CtrlとF5を同時押ししても黒い画面が出てこない場合は以下の操作を試してみて下さい。
まず、「プロジェクト」→「(プロジェクト名)のプロパティ」を選択。
左上の「構成」を「すべての構成」にした後、「構成プロパティ」→「リンカー」→「システム」→「サブシステム」の設定を「コンソール(/SUBSYSTEM:CONSOLE)」に変更。
設定を終了し再びCtrlとF5を同時押しして「Hello World!!」が表示されるか確認。
表示されたら成功。ダメだったら…僕にはよく分からないわ、ごめん。
後から他の機能を追加
後からC++以外(C#など)を利用したくなった場合の機能の追加方法のメモ。
今回はC#の機能を追加してみる。まず、VisualStudio Installerを起動。
「変更」を選択。
最初にインストールする時と同じ画面が出てくるので、追加したいものを選んで右下の「変更」を押す。
今回はC#の機能を追加したいので「.NETデスクトップ開発」を追加。
(追加インストール後に画面をキャプチャしたのでここに載っている画像は「閉じる」になっているが…)
すると追加のインストールが始まるので見守る。
終わったら「起動」でVisualStudio 2017を起動する。
C言語(C++)の時と同様にプロジェクトの新規作成を行うと、項目が増えている。
今回はVisual C#の中の「コンソールアプリ(.NetFramework)」を選択。
C#の場合最初からソースコードが少し書かれているので、適切な位置に以下の記述を追加。
Console.WriteLine("Hello World!!");
C++の時と同様にCtrlとF5を同時押しして実行。
表示されたら成功。
こんな感じで後から他の機能をいろいろ追加できる。
DXライブラリ使用時に終了後もプロセスが残る問題
事の始まり
DXライブラリを用いて通信(チャットプログラム)を試していたら、突然エラーが出るようになった。
exeを起動しっぱなしの状態で実行すると出てくるエラーだが、今回はちゃんと終了させてから再び実行したはず…
そこで、 タスクマネージャで調べてみると…
あれ、バックグラウンドでプロセスが残ってる…?(Chat.exeが試してるプログラム)
こいつを終了してやるとエラーが消えたので、やはり原因はこいつが残っている事にある。
しかし、なぜ残っていたのだろうか…
exeを直接実行しては終了を繰り返すとエライ事に
調べてみたところ、恐らくProcessMessage()やDxLib_End()を適切に実行するようなプログラムを書けていないのが原因である。
ダメなパターン
プロセスが残ってしまう今回のパターンでは次のようなコードを書いた。
#include <DxLib.h> //省略(他のインクルードやグローバル変数) int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { //省略(初期化やウィンドウサイズの設定など) //ZキーかXキーの入力があるまでループ(入力の受け付け) while (GetKey(KEY_INPUT_Z) == 0 && GetKey(KEY_INPUT_X) == 0) { KeyUpdate(); } //省略(他の処理) DxLib_End(); return 0; }
大部分を省略してしまったが、GetKey関数とKeyUpdate関数は自作関数で、それぞれキーの状態とキーの入力状態の更新を行う自作の関数だ。
このような無限ループを用意してしまうと、もし入力の受け付け部分の無限ループでウインドウ右上の×を押した際に、恐らくDxLib_End関数まで処理が進まない。
もっと言えばDXライブラリではProcessMessage関数を毎フレーム実行するようにしないといけない(詳しくは知らないが、そうしないと正しく動かない可能性がある)らしいので、その点も悪いと考えられる。
恐らくDXライブラリを用いるプログラムとしては0点である。
(多分)大丈夫なパターン
要するに、DXライブラリを用いる際は
- ループの時にProcessMessage関数を毎フレーム実行する
- 最終的にDxLib_End()を実行する
という事に気を付ければいいんだと思う。(多分)
てなわけで早速修正。
#include <DxLib.h> //省略(他のインクルードやグローバル変数) int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { //省略(初期化やウィンドウサイズの設定など) //ZキーかXキーの入力があるまでループ(入力の受け付け) while (ProcessMessage() == 0) { if (GetKey(KEY_INPUT_Z) == 0 && GetKey(KEY_INPUT_X) == 0) { KeyUpdate(); } else { break; } } //省略(他の処理) DxLib_End(); return 0; }
キー入力など毎フレーム受け付けるタイプのものを利用する時はProcessMessage関数を実行する。
こうやって書く事で×ボタンでウインドウが閉じられた際にProcessMessage関数が0以外を返すので、無限ループを抜けDxLib_End関数を実行して終了してくれるはず。
このように書き直したところ、終了後にプロセスが残ってなかったので多分これでいいと思う。
用いるライブラリの仕様は最低限把握しておこう。
ネットワークメモ
目次
ネットワーク
- ネットワークにおいて重要なことは他のコンピュータの情報を利用すること。(情報の共有化)
- パケット交換方式:データを細かく分けて送る方式
ネットワーク機器の会社が独自の通信ルールを定めると異なる会社の機器を用いた際に通信が上手く行えない。
そこで、異なる機種同士の接続を行うための標準化を進めた。
国際標準化機構(ISO)によって作られたネットワークの標準モデルをOSI参照モデルと呼び、このモデルでは通信機能を7階層に分割している。
また、米国国防高等研究計画局(DARPA)によって作られたTCP/IPモデルというものも存在し、このモデルでは通信機能を4階層に分割している。
いずれも通信ルールを定めるという意味で役割は同じ。
インターネットに使われている通信ルール(TCP/IPプロトコル)はTCP/IPモデルに沿っている。
OSI参照モデル | TCP/IPモデル |
---|---|
アプリケーション層 | アプリケーション層 |
プレゼンテーション層 | (アプリケーション層) |
セッション層 | (アプリケーション層) |
トランスポート層 | トランスポート層 |
ネットワーク層 | インターネット層 |
データリンク層 | インターフェース層 |
物理層 | (インターフェース層) |
TCP/IPモデルのアプリケーション層はOSI参照モデルでのアプリケーション層・プレゼンテーション層・セッション層を兼ねる。
TCP/IPモデルのインターフェース層はOSI参照モデルでのデータリンク層・物理層を兼ねる。
TCP/IPモデル | プロトコルの例 |
---|---|
アプリケーション層 | HTTP,DNS,SMTP,POP3 |
トランスポート層 | TCP,UDP |
インターネット層 | IP |
インターフェース層 | イーサネット,PPP,PPPoE |
以下TCP/IPモデルでの各層についての記述。
インターフェース層
- LAN:会社内や大学内など特定の狭い範囲で用いられるネットワーク
- イーサネット:LANで用いられるプロトコル。LANに繋がってる複数のコンピュータがやり取りを行う
- ハブ:複数のコンピュータを接続するための中継用の機器
複数のコンピュータがデータの送受信を行うので、例えば同時にデータ(電気信号)が通ると衝突して通信に失敗する。
→イーサネットではCSMA/CDという仕組みを用いて衝突を回避している
通常のハブの他、スイッチングハブが存在する。
通常のハブの場合、LAN内の他の特定のコンピュータへデータを送信すると接続されている全てのPCにデータが流れ、正しい送信先のコンピュータは流れてきたデータを受け取り、それ以外のコンピュータは自分宛じゃない事を確認して破棄している。
スイッチングハブの場合送信するデータの送信先を調べ該当するコンピュータにのみデータを流す。
WANはLANと異なり長距離の送信かつ通信事業者の提供するサービスを利用するため、PPPによって認証などが行われる。
PPPの認証機能をイーサネット上で利用するためにPPPoEというプロトコルが存在する。
インターネット層
複数のネットワーク(コンピュータのグループ)をつなぐ。
ネットワーク内の通信は外へは出ず、他のネットワークにデータを送る場合はルータを利用する。
- ルータ:ネットワーク同士を接続するのに使用する。また、ルーティングというデータの送信に適切な経路を決定する機能も持つ
インターフェース層ではMACアドレスを利用して次にデータを送る機器(中継を含む)を定めているのに対し、インターネット層ではIPアドレスを利用して最終的な送信先を定めている。
- IPアドレス:ネットワーク上の機器を識別するための数字。ネットワーク部とホスト部からなる
- サブネット:1つのネットワークをさらに複数の小さなネットワークに分割すること。識別用にホスト部の一部を利用する(サブネット部)
- サブネットマスク:どこまでがサブネット部なのかを示すための値。ネットワーク部とサブネット部のビットは1、残りのホスト部のビットを0とする
トランスポート層
ポート番号を利用して受信したデータがどのアプリケーションのものなのかを判別。
また、正しいデータをアプリケーションへ正しく送信する。
- ポート番号:各アプリケーションに割り当てられる番号
主要なサービスを提供するアプリケーション(メールの送信、Webサイトの情報送信)のポート番号は基本的に固定。
アプリケーション | ポート番号 |
---|---|
SMTP | 25 |
DNS | 53 |
HTTP | 80 |
POP3 | 83 |
他にもいろいろある。サービスを要求するアプリケーションは提供側や他のアプリケーションと被らない番号を設定する。
トランスポート層での主なプロトコルにはTCPとUDPがある。
TCPは送信先に確実にデータを送る事を意識しているプロトコルである。
送信先がデータを受信できるか確認(スリーハンドシェイク)を行った後にデータを送信する。
分割したデータに番号を割り振り順序制御や再送制御を行い信頼性を高める。
フロー制御を行い効率化を行う。
UDPはTCPとは逆に高速性を意識したプロトコルである。(TCPは信頼性を高める代わりに遅い)
少量のデータ送信やリアルタイム配信などの場合はこちらを用いる。
アプリケーション層
各アプリケーションに対し適切な通信サービスを行う。
Webサービスやメール受信サービスなどで異なるプロトコルが用意されている。
- クライアント・サーバ型:要求側(クライアント)と提供側(サーバ)に分かれてデータをやり取りする形式
以下代表的なプロトコルとその役割
Markdownで資料を作る
Markdownとは
Markdown(マークダウン)は、文書を記述するための軽量マークアップ言語のひとつである。本来はプレーンテキスト形式で手軽に書いた文書からHTMLを生成するために開発されたものである。
(Wikipediaより引用)
要するに、比較的手軽にメモとか資料が書けるんです。(雑)
Qiitaとかはてなブログの記事を書くのにも使えるっぽいです。
どんな感じのものができるの
最終的にHTMLに変換してブラウザで表示しますが、こんな感じになります。
用いるソフトウェアによってビジュアルは若干変化すると思います。
これは後述するSublime Textを用いてHTMLで出力したものです。
比較的シンプルな見た目で、ソースコードが結構綺麗に表示できるっぽい。
環境の導入
大まかな手順はこんな感じ
Sublime Textのインストール
この辺を参照
http://webmem.hatenablog.com/entry/sublime-text
Markdownを利用するためのパッケージのインストール
この辺を参照
http://webmem.hatenablog.com/entry/sublime-text-markdown
上のリンクを読みながら最終的にこの辺のパッケージを導入しておけば快適に扱えると思う。
- Japanize (日本語化)
- OmmiMarkupPreviewer (Markdownのプレビュー)
- Monokai Extended (Markdownのシンタックスハイライト)
- Markdown Extended (Markdown内のコードのシンタックスハイライト)
- Trailing Space (行末半角スペースの可視化)
- IMESupport (日本語入力を行いやすくする)
書き方
C言語やTeXなどと同様、書き方のルールがあるのでそれに従う。
書き方はこの辺を参照。QiitaでのMarkdown記法とかとは若干挙動が異なる場合がある?(よく分かってない顔
ちなみに、拡張子は[.md]となっている。
http://qiita.com/tbpgr/items/989c6badefff69377da7#%E8%A1%A8%E7%A4%BA%E4%BE%8B-10
http://cartman0.hatenablog.com/entry/2015/03/31/034758
終わりに
綺麗だからみんな使おう!(布教活動)