スプーキーズのちょっとTech。

SPOOKIES社内のより技工的な、専門的なブログページです。

Are you Spookies?? 機械学習で顔の画像認識をしよう

最近、アニメを見まくっているIoT開発部の西村です。
こんにちは。
感動シーンでナミダしている僕の横で、犬がうるせーなって顔してるのがたまらないです。
どうぞよろしくお願い致します。

課題・モチベーション

ということで 人の顔を機械学習で覚えさせ、メンバーが来たかもよ君にバージョンアップだ!

Spookiesメンバーの顔画像認識パターンモデルを作ってみる

顔画像の収集

毎日顔を合わせて一緒に仕事してますが、いざメンバーの顔写真が必要となっても、あまり持っていないもんです。 携帯で何枚か撮ったかもしれませんが、そんなに数は無いですよね。

合宿等の写真を拾い集める

会社の共有フォルダーに、合宿の写真が入ってました。

  • 沖縄合宿
  • 和歌山合宿
  • 淡路島合宿

こちらを使いましょう。

DLしてフォルダを開くと、こんな写真がいっぱいあります。

f:id:spookies-nishimura:20171014200226p:plain

続きを読む

プログラミングをやってみよう

プログラミングに興味はあるけど何やったらいいの?

こんにちは、ももです。

スプーキーズで働きはじめて1ヶ月。

ちょっとプログラミングの話もしてみたいなと思っておりました。

ただ、スーパープログラマではないひよっこですので技術的なことはまだまだです。

そこで今回は、プログラミング初心者である私がどんなサービスを利用してきたのかご紹介したいと思います。

私は、基本的にはWEBで提供されているものを利用していました。

プログラミングをやってみたいけど...

何したらいいかわからない

どんなことするの?

状態の方はまずは無料のWEBプログラミング学習サービスを利用してみましょう。

有名なものは

・ドットインストール

https://dotinstall.com/

・Progate

prog-8.com

・CODEPREP

codeprep.jp

でしょうか

私はこの3つをまんべんなく利用しています。

こちらは基本無料で、興味が湧いてきたら、月額の有料会員に切り替えが可能になります。

私は、ドットインストールとCODEPREPは無料会員ですが、Progateは有料会員です。

3つもあるけどどれがいいの?と思われるかもしれませんが

オススメはProgateとCODEPREPです。

ProgateかCODEPREPから始めると、プログラミングに必要な環境が整った状態ですので

簡単にプログラミングを体験することができます。初心者にはわかりやすいと思います。

私はとてもわかりやすかったです。

無料の部分でまずはプログラミングってどんな感じか試してみましょう。

触ってみると自分がプログラミングが好きか嫌いかもわかるのではないかと思います。

次に、ドットインストールですが、こちらは一回3分の動画をみる形の学習スタイルになります。

私がドットインストールに大変お世話になったのは、開発環境の構築です。

ドットインストールのいいところって隣でプログラミングを教えてくれてる感じがあるんですよね。

実際に画面上のどの部分を選択するのだとか、動作がとてもわかりやすいです。

私は、いろんなものを試したかったのでまんべんなくスタイルですが

一つに絞ってやるのも、もちろん人それぞれだと思います。

プログラミングに慣れてくるときっとWEBサービスだけでは、物足りなくなってくると思います。

そんな時がくれば、興味のある言語を選んで参考書を買ってみましょう!

もっと楽しい世界が広がると思いますよ!!私もそうでした

ちなみにスプーキーズでは、会社の参考書の貸し出しも行っていて自由に借りて、勉強できる制度も整っています。

プログラミングに興味があって勉強する意欲のある方!ぜひ、一緒にスプーキーズで働いてみませんか?

スプーキーズでは、一緒に働く仲間を募集しています!

f:id:spookies_momo:20170928191623j:plain

JavaScriptを高速化する5つの手法

こんにちは、モリタです。

JSを高速化 する

f:id:moritanian:20170926151527p:plain:w160

nativeに比べて遅いと言われるjsですがパフォーマンスチューニングによりずいぶん改善しているようです。しかしながらWebGLなどまだまだ高速化が必要な分野も数多くあります。そこで、今回はjavascriptを高速化する手法を簡単に見ていきたいと思います。

Typed Array

動的な配列であるjsの通常の配列に対し、固定長の配列であるTypedArrayが用意されています。低機能ですが、各要素に高速でアクセスする事ができます。以下のようにまずサイズを決めてバッファを確保してからTypedArrayを生成します。 canvasの getImageData () で取得できる画像データもTypedArrayのようですね。

var buffer = new ArrayBuffer(8); // 8byteのサイズのバッファ確保
var arr   = new Int32Array(buffer); // 32bit(4byte)のint配列
console.log(arr); // [0, 0]

WebAssembly

動的型付け言語であるJsは実行時に型チェックや型変換が行われ、それがネイティブに比べて遅くなる原因のひとつになっていました。そこでMozillaからasm.jsという手法が提案されました。asm.jsでは全ての変数が静的な型付けを行います。TypedArrayの考え方をさらに推し進めたものと言えるでしょう。

このasm.jsに対して各ブラウザベンダーが協力して策定しているバイナリフォーマットの規格がwebAssemblyです。 webAssemblyを利用するにはc, c++ コードをEmscriptenツールによって.wasmバイナリに変換するのが一般的なようです。  各ブラウザの実装状況は以下のようにデスクトップではほぼすべてのブラウザ最新で実装されています。 f:id:moritanian:20170926114309p:plain

また、unityのwebGLビルドでもオプション設定でwebAssemblyを使用できるようです。

WebWorker

jsは本来シングルスレッドで動作しますが、webWorkerを利用することでマルチスレッド動作を実現できます。 以下のように new Worker("ファイル名") で実行することができます。 また、postMessage() , onmessage() でスレッド間でデータをやり取りできます。

main.js

var myWorker = new Worker("worker.js"); // worker.js  を別スレッドで実行
 myWorker.postMessage([first.value,second.value]); // Sending message as an array to the worker
myWorker.onmessage = function(e) {
        result.textContent = e.data;
        console.log('Message received from worker');
    };

worker.js

onmessage = function(e) {
  console.log('Message received from main script');
  var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
  console.log('Posting message back to main script');
  postMessage(workerResult);
}

参考

github.com

gpgpu

gpgpuとは本来画像処理に使われるgpuの計算資源を画像処理以外の演算に使うことです。gpuは行列計算など計算密度が大き(条件分岐などが少ない)く、並列度が大きい演算に関してCPUに比べて性能を発揮します。最近は機械学習が利用例としてホットですね。 gpgpuを利用するために以下のライブラリがあります。 gpu.rocks

github.com

gpu.jsでは以下のように記述できます。

const gpu = new GPU();
const multiplyMatrix = gpu.createKernel(function(a, b) {
  var sum = 0;
  for (var i = 0; i < 512; i++) {
    sum += a[this.thread.y][i] * b[i][this.thread.x];
  }
  return sum;
}).setOutput([512, 512]);

const c = multiplyMatrix(a, b);

Simd

こちらも並列化により高速化を実現する試みの一種で命令セットレベルの並列化を実現します。 以下ではベクトル演算を並列化しています。

var a = SIMD.Float32x4(1, 2, 3, 4);
var b = SIMD.Float32x4(5, 6, 7, 8);
var c = SIMD.Float32x4.add(a,b); // Float32x4[6,8,10,12]

残念ながらEcmascriptでまだドラフト段階のためリリース版ブラウザの実装はまだのようです。

番外編

javasciptで演算子オーバーロードしたい

高速化とは全く関係ありませんがみなさんも一度はjsでも演算子オーバーロードしたいと思ったことがあるはずです。いや、そんなことはないですね。 少し気になったので調べてみたのですが結論としては、 【Javascript】演算子をオーバーロードしたい話 - Qiita にある通り普通のやり方では実現できずコードを構文解析して演算子オーバーロードしている箇所を書き換える必要があるようです。 そこで実際に実装してライブラリ化してみました!

GitHub - moritanian/minamike.js: このライブラリに過度な期待はしないでください

f:id:moritanian:20170926124329p:plain

このライブラリでCのstd::cout 風に記述するとこのようになります

const cout = "std::cout";
String.prototype.__operators__ = {
    "<<" : function(s1, s2){
        console.log(s2);
    }
  };
 
 
 cout << "Hello World!";

まあ、全く需要なさそうですが。

それではよきjsライフを!

Web Audio APIを用いた自動作曲

こんにちは、ロックです!

今回は先々週ぐらいに社内で行った勉強会の内容となります。

ブログを書く手番が回ってきたので、書いていきたいと思います。

概要

  • 日頃からの思いとして、音楽に携わるプログラミングも興味があった
  • 最近AIがブーム

ということで、プログラミングを通じて作曲できないかな?

さらにAIブームに乗って、作曲の自動化とか行けそうではないだろうか。

何を使って実現する?

普段の趣味ではC言語なので、C言語ベースでやろうと思いましたが、

Webで簡単に音を出せるらしいので、そちらで実現していきます。

Web Audio API

HTML5 で音関連も強化されているようです。

ノード同士を繋げていく仕組み

仕組み解説(音量操作の例を通して)

1. 音を出す

音の発生源(node)出力(node) に繋げた(connect)状態で再生(start)を行うと音が聞こえてくる。

f:id:ishiyamacocoa:20170919164857p:plain

2. 音量を調整してみる

音量(node)を間に挟むことで音量を調節できる。

f:id:ishiyamacocoa:20170919164901p:plain

3. 複数音を鳴らしたい!

新しい音の発生源2(node)音量(node)に繋げればok

f:id:ishiyamacocoa:20170919164905p:plain

4. 音の発生源1だけ少し音量を上げたいなあ

音の発生源ごとに音量(node)を作り、全体音量(node)と繋げるようにするとok

マスターボリュームと個別のボリューム!!

f:id:ishiyamacocoa:20170919164909p:plain

以上のようにノードを繋げていく。

音の発生源から出力の間にいろいろ挟むことで楽しくなる。

音程を求める

440Hzが世界標準でラ(A)の音

※例えばドの音は262Hz

周波数を求める式として MIDI tuning standard というもの存在する

2note_number - 69/12・440Hz

※note_number = MIDI のノート番号(0 ~ 127)

ノート番号で言うと、69が440Hzのラ(A)の音である

参考URL :MIDIノート番号と音名、周波数の対応表

無限に鳴らす

音も出せるようになり、音程も操れるようになったので、無限に鳴らせれば任務完了です。

今回は javascript を使っているので、 setInterval() でok

ちょっと音楽っぽくする

和音というものがあります。

いわゆる ドミソ

ピアノで言う左手(コード、ベース)

音楽といえばメロディもありますよね

ランランララランランラン♪

ピアノで言う右手(メロディ、コード)

和音

  • メジャーコード
  • マイナーコード

を選べるようにしておきました。

メジャーコードを選んだら全てメジャーコードに

マイナーコードを選んだら全てマイナーコードになります。

メロディ

和音は3つの音で構成されています(4つとかもあるが)

メロディはこの和音に出てくる音の3つのうちからランダムで流れるようにしてます。

これだけで案外メロディっぽくなるものなのです。

設定項目

  • スケール(今回は全て長調)
  • 和音(メジャーかマイナーを選べる)
  • オクターブ
  • BPM(3桁まで入力可能)

見た目

autoMusic_Image.png (4.3 kB)

実際のものがこちら

autoMusic.html (4.0 kB)

Webに公開する力がなかったので生ファイル。

DLしてブラウザで開くと見れる(はず)

展望

  • コード進行を考えるともっと音楽っぽくなる
  • 対応スケールを増やす
  • パーカッションとかを入れる(楽器を増やせるようにする)

反省

  • AI関係なくなってしまった(一晩でどうにかなるものでもなかった)

先日見かけた自動作曲記事

『Scalaで自動作曲の練習』を社内勉強会で話した - Sexually Knowing

参考サイト(Web Audio API)

Web Audio API - Web API | MDN

HTML5 の Web Audio API で音楽してみる | CYOKODOG

Getting Started with Web Audio API - HTML5 Rocks

ゲームAI同士を対戦させてみる

初めまして。新人?アルバイトのzeosuttです。


秋です。

皆でゲームAIを持ち寄って対戦したい季節ですね。

何のゲームにしましょうか。

そうですね、簡単に実装できるので、〇×ゲームにしましょう。

f:id:spookies_yano:20170919125328p:plain
実は、両者が最善を尽くすと必ず引き分けになる

さて、どんなに強いAIを作っても、それを対戦させる仕組みがなくては意味がありませんね。

この記事では、AI同士を対戦させるための仕組みを作ってみます。

AIの作成自体は主題ではありません。

人対人

まずは、人間同士で対戦ができるようにしましょう。

#include <stdbool.h>
#include <stdio.h>
#include <string.h>

const char *sign = "ox";   // 〇/× は o/x で表現

// 手を表現する構造体
typedef struct {
    int i, j;
} Choice;

// 盤面を初期化する
void init_field(char field[3][4]) {
    for (int i = 0; i < 3; i++) {
        strcpy(field[i], "...");
    }
}

// 指定されたストリームに盤面を送る
void send_field(const char field[3][4], FILE *fp) {
    for (int i = 0; i < 3; i++) {
        fprintf(fp, "%s\n", field[i]);
    }
}

// 指定されたストリームから手を受け取る
void receive_from_ai(Choice *pchoice, FILE *fp) {
    fscanf(fp, "%d %d", &pchoice->i, &pchoice->j);
}

// 手が有効か?
bool is_valid(const Choice *pchoice, const char field[3][4]) {
    const int i = pchoice->i, j = pchoice->j;
    return i >= 0 && i < 3 && j >= 0 && j < 3 && field[i][j] == '.';
}

// 手をシミュレートする
void simulate(int player, const Choice *pchoice, char field[3][4]) {
    field[pchoice->i][pchoice->j] = sign[player];
}

// 指定されたプレイヤーが勝利しているか?
bool is_win(int player, const char field[3][4]) {
    // 手抜きだけど怒らないで
    return field[0][0] == field[0][1] && field[0][1] == field[0][2] && field[0][2] == sign[player]
        || field[1][0] == field[1][1] && field[1][1] == field[1][2] && field[1][2] == sign[player]
        || field[2][0] == field[2][1] && field[2][1] == field[2][2] && field[2][2] == sign[player]
        || field[0][0] == field[1][0] && field[1][0] == field[2][0] && field[2][0] == sign[player]
        || field[0][1] == field[1][1] && field[1][1] == field[2][1] && field[2][1] == sign[player]
        || field[0][2] == field[1][2] && field[1][2] == field[2][2] && field[2][2] == sign[player]
        || field[0][0] == field[1][1] && field[1][1] == field[2][2] && field[2][2] == sign[player]
        || field[0][2] == field[1][1] && field[1][1] == field[2][0] && field[2][0] == sign[player];
}

// 盤面を表示する
void print_field(const char field[3][4]) {
    send_field(field, stdout);
}

// 手を表示する
void print_choice(const Choice *pchoice) {
    printf("choice: %d %d\n", pchoice->i, pchoice->j);
}

int main(void) {
    char field[3][4];
    int turn;

    init_field(field);

    print_field(field);
    for (turn = 0; turn < 9; turn++) {
        const int player = turn & 1;
        Choice choice;

        printf("AI%d(%c)'s turn\n", player + 1, sign[player]);

        // 一時的に標準入力を指定
        receive_from_ai(&choice, stdin);

        print_choice(&choice);

        if (!is_valid(&choice, field)) {
            const int oppo = player ^ 1;
            puts("invalid input");
            printf("AI%d(%c) win!\n", oppo + 1, sign[oppo]);
            break;
        }

        simulate(player, &choice, field);

        print_field(field);

        if (is_win(player, field)) {
            printf("AI%d(%c) win!\n", player + 1, sign[player]);
            break;
        }
    }
    if (turn == 9) {
        puts("draw...");
    }

    return 0;
}

はい。できました。

座標はi jの形式で入力してください。

例えば、左上なら0 0、その下なら1 0となります。

AI対AI

〇×ゲーム自体のコードは書けました。

後は、AIと通信をするように修正すれば完成です。

ここで、AIには、次の例のようなフォーマットで入力を与えることとします。

o
...
.o.
..x

最初の行は、自分が〇と×のどちらであるかを表します。

2行目以降は盤面の状態を表します。

また、AI作成者の負担を軽減するためにも、AIからは標準入出力でやり取りできるようにしましょう。

AI実装例

先に、このプログラムの入出力仕様に沿って、簡単なAIを実装しますね。

このプログラムの理解の一助となるほか、AI作成未経験者にとっては、入力を受け取り、手を考え、出力するという、AIの基本的な流れの理解にも役立つかもしれません。

#include <stdbool.h>
#include <stdio.h>

typedef struct {
    int i, j;
} Choice;

// 入力を受け取る
bool input(char *pmark, char field[3][4]) {
    if (scanf(" %c", pmark) == EOF) {
        return false;
    }

    for (int i = 0; i < 3; i++) {
        scanf("%s", field[i]);
    }

    return true;
}

// 手を考える
bool think(Choice *pchoice, int mark, const char field[3][4]) {
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            if (field[i][j] == '.') {
                // 普通は、各手を何らかの基準で評価し、最も評価値が高いものを選ぶ
                // しかし、今回の主題はAI作成ではないため、ここは手を抜く
                *pchoice = (Choice){i, j};
                return true;
            }
        }
    }

    return false;
}

// 手を出力する
void print_choice(const Choice *pchoice) {
    printf("%d %d\n", pchoice->i, pchoice->j);
}

int main(void) {
    char mark, field[3][4];

    while (input(&mark, field)) {
        Choice choice;

        if (think(&choice, mark, field)) {
            print_choice(&choice);
        } else {
            // 有効な手がない場合の処理(今回は起こり得ない)
            puts("-1 -1");
        }

        fflush(stdout);
    }

    return 0;
}

左上から順に走査し、〇も×も書かれていない座標があれば、それを出力するAIです。

雑魚です。

対戦プログラムの修正

では、〇×ゲームのコードをAIと通信するように修正しましょう。

まず、以下を追加します。

#include <stdlib.h>
#include <unistd.h>

// コマンドライン引数のバリデーション
void validate(int argc, const char * const *argv) {
    if (argc != 3) {
        fprintf(stderr, "usage: %s AI1 AI2\n", argv[0]);
        exit(EXIT_FAILURE);
    }
}

// AIと通信をする準備をする
void prepare_ai(FILE *fp_to_ai[2], FILE *fp_from_ai[2], const char * const path_to_ai[2]) {
    for (int i = 0; i < 2; i++) {
        int pipefd_to_ai[2], pipefd_from_ai[2];

        pipe(pipefd_to_ai);
        pipe(pipefd_from_ai);

        if (fork() == 0) {
            // 標準入出力を繋ぎ変えた上で、プロセスをAIで置き換える

            close(pipefd_to_ai[1]);
            dup2(pipefd_to_ai[0], STDIN_FILENO);
            close(pipefd_to_ai[0]);

            close(pipefd_from_ai[0]);
            dup2(pipefd_from_ai[1], STDOUT_FILENO);
            close(pipefd_from_ai[1]);

            execlp(path_to_ai[i], path_to_ai[i], (char *)NULL);

            // AIの実行に失敗した場合はエラー
            fprintf(stderr, "%s: no such file\n", path_to_ai[i]);
            exit(EXIT_FAILURE);
        }

        close(pipefd_to_ai[0]);
        close(pipefd_from_ai[1]);

        // AIと高水準入出力関数でやりとりできるようにしておく
        fp_to_ai[i] = fdopen(pipefd_to_ai[1], "w");
        fp_from_ai[i] = fdopen(pipefd_from_ai[0], "r");
    }
}

// 指定されたストリームに、AIへの入力にあたるデータを送る
void send_to_ai(int player, const char field[3][4], FILE *fp) {
    fprintf(fp, "%c\n", sign[player]);
    send_field(field, fp);
    fflush(fp);
}

prepare_ai()がこの記事の肝です。

やっていることは単純で、

  1. pipe()でAIとやり取りするためのパイプを作成し、

  2. 後ほど子プロセスをAIに置き換える、という意図を持ってfork()し、

  3. 子プロセスの標準入出力をdup2()で先ほどのパイプに書き換え、

  4. 最後に子プロセスをexec()でAIに置き換えて

いるだけです。

各システムコール等の詳細はmanを読んでください。

なお、言うまでもなく、Windowsでは動作しません。

最後に、main()を次のように修正します。

65c117,120
< int main(void) {
---
> int main(int argc, char **argv) {
>    validate(argc, (const char **)argv);
> 
>    FILE *fp_to_ai[2], *fp_from_ai[2];
68a124,125
>    prepare_ai(fp_to_ai, fp_from_ai, (const char **)argv + 1);
> 
78,79c135,136
<       // 一時的に標準入力を指定
<       receive_from_ai(&choice, stdin);
---
>        send_to_ai(player, field, fp_to_ai[player]);
>        receive_from_ai(&choice, fp_from_ai[player]);

完成です。

$ ./tic_tac_toe ./ai1 ./ai2 のようにして実行してください。

公平性の問題やセキュリティの問題がありますが、とりあえずこれでおkとしましょう。

お疲れ様でした。