GORMを使ってみた

goで使えるO/R Mapperは結構いろいろあるみたいで、とりあえず今日1日 gorpとGORMを使ってみた。

gorp
GitHub - go-gorp/gorp: Go Relational Persistence - an ORM-ish library for Go

GORM
Getting Started with GORM · GORM Guide

gorpはシンプルに使える、GORMは多機能、という印象。とりあえず今回はGORMについてまとめてみる。

ソースは以下に置いてあります。
hatena_blog/go/gorm at master · egawata/hatena_blog · GitHub

下準備

mysql サーバを立て、以下の準備を行う。

databaseの作成

mydb2 という名称のデータベースを作成。

CREATE DATABASE mydb2 DEFAULT CHARSET=utf8;

table の作成

GORMには関係テーブルを便利に扱う機能があるため、今回は2つテーブルを作成し関係を持たせるようにした。1人のMemberが、複数の Hobby を持つようにしてある。

CREATE TABLE member (
    id INTEGER NOT NULL AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    birthday VARCHAR(5),
    blood_type CHAR(2),
    created_at DATETIME,
    updated_at DATETIME,
    PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=UTF8;

CREATE TABLE hobby (
    id INTEGER NOT NULL AUTO_INCREMENT,
    member_id INTEGER NOT NULL,
    name VARCHAR(100) NOT NULL,
    created_at DATETIME,
    updated_at DATETIME,
    PRIMARY KEY (id),
    CONSTRAINT FOREIGN KEY (member_id) REFERENCES member(id)
) ENGINE=InnoDB DEFAULT CHARSET=UTF8;

ユーザの作成

user1 というユーザを作成した。

GRANT ALL ON mydb2.* TO user1@'localhost' IDENTIFIED BY 'password1';

コーディング

必要なパッケージの import

import (
	_ "github.com/go-sql-driver/mysql"
	"github.com/jinzhu/gorm"
)

mysqlから使用する場合、最低限以上の2つが必要。

データを格納する構造体の作成

type Member struct {
	Id        int64 `gorm:"primary_key"`
	Name      string
	Birthday  string
	BloodType string
	Hobbies   []Hobby
	CreatedAt time.Time
	UpdatedAt time.Time
}

type Hobby struct {
	Id        int64 `gorm:"primary_key"`
	MemberId  int64
	Name      string
	CreatedAt time.Time
	UpdatedAt time.Time
}
テーブル/カラム名と構造体メンバ名の関連

上記のような構造体を作成すると、DB上のテーブル、カラムと構造体との関連付けが自動的に行われる。デフォルトでは

  • テーブル名 = 構造体の型名の複数形 (例:Member -> members)
  • カラム名 = 構造体の中の各メンバ名をsnake_caseにしたもの (例: BloodType -> blood_type)

デフォルトの命名規則を変えることも可能。例えば今回はテーブル名についてはデフォルトと異なり複数形にしていないので、以下のように実際のテーブル名を返すメソッドを追加する。

func (m *Member) TableName() string {
	return "member"
}

func (h *Hobby) TableName() string {
	return "hobby"
}

カラム名を変更したい場合は、構造体のメンバ宣言に以下のようにタグをつける。(今回は使用しないけど)

    Name string `gorm:"column:another_name"`
Has-Many 関係の表現

上記の Member 構造体の中には、元のmember テーブルにはない Hobbies というメンバが定義されている。

	Hobbies   []Hobby

これは、1人のMemberが複数の Hobby を持つというテーブル間の関係を表現している。このあたりも GORM がいい感じに処理してくれる。

追加・更新・削除日時の自動更新

CreatedAt, UpdatedAt, DeleteAt という time.Time 型のメンバを追加すると、GORM側でここに追加・更新・削除日時を自動的に入るようになる。

DB接続

const (
	dsn = "user1:password1@tcp(127.0.0.1:3306)/mydb2?parseTime=true&loc=Asia%2FTokyo"
)

        db, err := gorm.Open("mysql", dsn)
	defer db.Close()

parseTime=true を入れておかないと、time.Time型のメンバに値を取り込む際にエラーとなってしまう。またデフォルトではタイムゾーン世界標準時となるので、指定したいのなら loc=Asia/Tokyo のようにする(URIエスケープが必要)。

レコード追加

	members := []Member{
		{Name: "ミク", Birthday: "10/19", BloodType: "AB", Hobbies: []Hobby{{Name: "ブログ"}, {Name: "ショッピング"}}},
		{Name: "マホ", Birthday: "1/8", BloodType: "AB", Hobbies: []Hobby{{Name: "漫画"}, {Name: "ゲーム"}}},
		{Name: "コヒメ", Birthday: "11/24", BloodType: "O", Hobbies: []Hobby{{Name: "ゲーム"}, {Name: "茶道"}}},
	}
	for _, member := range members {
		db.Create(&member)
	}

レコード追加はdb.Create()で行う。あらかじめ Member 構造体にデータを代入しておき、引数でそのアドレスを指定する。
Hobbies の値も同時に指定すると、member, hobby の両テーブルにレコードが挿入される。

すべてのレコードを取得

	var allMembers []Member
	db.Find(&allMembers)
	fmt.Println(allMembers)

結果を受けるMember型の空のスライスを用意しておき、db.Find()の引数でそのアドレスを渡す。

条件を指定してレコードを取得

	var miku Member
	db.Where("name = ?", "ミク").First(&miku)
	db.Model(&miku).Related(&miku.Hobbies)
	fmt.Printf("%#v\n", miku)

db.Where()で検索条件を指定。結果を受けるMember型の変数を用意しておき、First()の引数でそのアドレスを渡して結果を受け取る。
ただしこれだけだと member テーブルの値のみが取得され、関係テーブル(hobby)への問い合わせは発生しない。
db.Model().Related()を実行した時点で、関連テーブル(hobby)の情報が 取得できる。


GORMのサイトのマニュアルがなかなかきちんと説明してあるので、詳しくはそちらを参照するといいと思う。

goでmysqlを使うメモ

久しぶりだー

前回の日記が2014年11月3日…だいぶ放置してたなorz

goでmysqlを使ってみる

mysqlドライバをインストール

database/sqlmysqlドライバとして go-sql-mysql というのがある。まずはこれを go get でインストールする。
github.com

go get github.com/go-sql-driver/mysql

今回使うサンプル

接続先DB
データベース名 mydatabase
ユーザ user1
パスワード password1
テーブル(member)
id int(11) NO PRI NULL auto_increment
name varchar(100) NO NULL
birthday varchar(5) YES NULL
blood_type char(2) YES NULL
hobby varchar(100) YES NULL

準備ができたら早速コードを書いてみる。サンプル全体は以下に置いてあります。
hatena_blog/sample.go at master · egawata/hatena_blog · GitHub

レコードの内容を格納するstructの宣言

データの格納先はスカラー変数でも十分なのだが、一応見やすさのため member テーブルの列に対応した struct を用意しておく。

type Member struct {
    Id        int
    Name      string
    Birthday  string
    Bloodtype string
    Hobby     string
}

importする

最低限以下のパッケージが必要

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
)

go-sql-driver を import するが、mainパッケージから直接参照することはないので、先頭に _ をつけて blank import とする。

接続

sql.Open()を使う。

引数としてDSNを指定する必要があるが、フォーマットが若干ややこしい。

[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN]

ローカルホストで起動しているmysqlに接続するなら以下でOK。

username:password@/dbname

外部ホストのmysqlTCP接続して使う場合は以下のようなDSNになる。

user1:password1@tcp(192.168.1.10:3306)/mydatabase

go-sql-driverのドキュメントの下のほうに、様々なシチュエーションでのDSNの例が載っているので、迷ったら参考にするといいと思う。

結果的に以下のようなコードとなった。

var Db *sql.DB
var err error

Db, err = sql.Open("mysql", "user1:password1@tcp(127.0.0.1:3306)/mydatabase?charset=utf8")
defer Db.Close()

INSERT実行

prepareして

    stmt, err := Db.Prepare(`
        INSERT INTO member (name, birthday, blood_type, hobby)
        VALUES (?, ?, ?, ?)
    `)
    if err != nil {
        return
    }
    defer stmt.Close()

実行。

        ret, err = stmt.Exec(m.Name, m.Birthday, m.Bloodtype, m.Hobby)

(説明雑だな)


INSERTされた列のIDを知りたいときは、戻り値 ret(Result型) の LastInsertId() を実行すればよい。int64型で返ってくるので、必要であればキャストする。

id, err = ret.LastInsertId()
m.Id = int(id)

SELECT実行(単一レコードを取得)

database/sql には Query() と QueryRow() という2つの関数が用意されているが、結果が単一レコードであることを期待している場合は QueryRow() を使うとよい。

    member = Member{}

    err = Db.QueryRow(`
        SELECT id, name, birthday, blood_type, hobby
          FROM member
         WHERE id = ?
    `, id).Scan(&member.Id, &member.Name, &member.Birthday, &member.Bloodtype, &member.Hobby)

QueryRow() の引数はSQL文とバインド値。
Scan()の引数は結果列の値を格納する変数のアドレス。取得される列の数と一致している必要がある。

なお取得されうるレコードが2つ以上の場合は、Scan() で最初の1レコードの値のみが取得される。
逆に1行も取得されなかった場合は、Scan() は ErrNoRows を error として返す。このときのエラー処理を他のエラーと区別したい場合は以下のようにする。

    if err == sql.ErrNoRows {           //  見つからなかった
        log.Fatalln("そのIDのメンバーは存在しません")
    } else if err != nil {                      // それ以外のエラー
        log.Fatalln(err)
    }

SELECT実行(複数レコードを取得)

結果が複数レコードになることが期待される場合は Query() を使う。

func getAllMembers() (members []Member, err error) {
    rows, err := Db.Query("SELECT id, name, birthday, blood_type, hobby FROM member")
    if err != nil {
        return
    }

    for rows.Next() {
        m := Member{}
        err = rows.Scan(&m.Id, &m.Name, &m.Birthday, &m.Bloodtype, &m.Hobby)
        if err != nil {
            return
        }
        members = append(members, m)
    }
}

結果として返ってきた rows(*Rows型)を使い、rows.Next()でループさせ、rows.Scan()で各列の値を受け取る。

nnetを使ってコード解析作ってみた

以前このブログにも書いた通り、今年の夏に Coursera の Machine Learning の講座を受けた。これがきっかけで機械学習にすごく興味を持ち、何か自分でも面白いものを作ってやるぜって意気込んでた。オイラ腰が重くてなかなか手をつけられないでいたが、今回、特に自分の興味のあるところから攻めてみようってことで、音楽のコード解析をやってみた。


今回やること

とはいえいきなり大掛かりなものから始めるのは大変なので、まずはCMaj, DbMajの2種類のコードで演奏した音源を用意。これらをニューラルネットワークを使って正しく分類できるようにする。


プログラム一式とサンプルデータはgithubに置いてあります。


音源の準備

さて、一番始めに悩んだのがこれ。学習に十分な量の演奏データって10個?100個?いやもうちょっと欲しい気がする。しかしそれだけ大量の演奏データを準備するのは案外大変だ。


どうしようかと暫く悩んでいたのだが、ふと昔趣味で作曲をやっていたのを思い出した。そういや昔 YAMAHA の QY70 ってシーケンサ使ってなかったっけ?
これを使えば、内蔵されている128種類 x バリエーション数種類の演奏パターンを使って、任意のコード進行を演奏させることができる。これ使えばいいじゃん!

 

f:id:taegawa:20141103180311j:plain


というわけでQY70を押し入れから引っ張りだし、早速これを使ってCMaj, DbMajでの演奏パターン、各128種類をものの30分ほどで用意することができた。
音源を録音して切り出し、128パターン x 2コード = 256個の wav ファイルを生成した。


FFT -> データの整理

コード解析を行うには、どの音程(周波数)の音が鳴っているかを知る必要があるため、フーリエ解析を行う。音源の開始位置から0.2秒ほど後の位置を解析対象とした。開始直後の位置ではドラム系の音が強くノイジーになり、コード解析という観点からは音程という特徴をとらえにくいと考えたからだ。これで1演奏データあたり32768個のデータが得られる。
これらのうち意味のあるのは最初の16384個で、さらに高周波成分のデータについては約1Hzという細かい粒度で取得するのはあまり意味がないと考えた。データ量を少なくしたいという意図もあり、A0(27.5Hz)からA8(7040Hz)の間で、1オクターブあたり24の区間に分割し、それぞれの区間での音量の平均値を求めた。この結果、1パターンあたり191個の周波数成分データを含む解析結果ファイルを得た。

 

# すべての wav ファイルに対して実行
R --vanilla --slave --args do_patterns001.wav do_result_001.txt < createFftData.R

 

結果サンプル


トレーニングデータ、テストデータの生成

演奏データは1コードあたり128パターン用意しているが、これらをトレーニング用とテスト用に7:3の比率で振り分け、上で得られた解析結果ファイルをそれぞれ1つのファイルにまとめる。

perl merge_fft_data.pl

結果データは、トレーニング用、テスト用それぞれ /tmp/fft_result_train.txt, /tmp/fft_result_test.txt に出力した。

結果を出力する際、R の data frame として取り込みやすいようにヘッダ行をつけた。周波数成分データには x1 〜 x191 という名前をつける。さらに、これらがCMaj と DbMaj のどちらのコードを表すかを result という名前で追加する。CMaj, DbMaj に対し、それぞれ do, reb という値をつけた。


トレーニング実施

R の nnet というパッケージを使うと、簡単にニューラルネットワークを利用することができる。
まずトレーニングデータを読み込む。

 

dtrain <- read.table("/tmp/fft_result_train.txt", header=T)

 

で、トレーニング実行。パラメータはいろいろ変えて試してみるとよさそう。

 

> d.nnet <- nnet(result~., dtrain, size = 5, decay = 1, maxit=10000)
# weights: 966
initial value 299.267352
iter 10 value 117.659656
iter 20 value 111.959352
iter 30 value 88.714983
iter 40 value 64.720808
iter 50 value 62.994615
iter 60 value 62.094382
iter 70 value 54.606176
iter 80 value 48.565453
iter 90 value 44.998767
iter 100 value 44.173409
iter 110 value 42.210244
iter 120 value 37.521031
iter 130 value 36.635122
iter 140 value 35.544440
iter 150 value 34.254634
final value 34.178917
converged

 

 

検証

トレーニング結果を、テストデータ(82個)を使って検証する。

 

dtest <- read.table("/tmp/fft_result_test.txt", header=T)
table(dtest$result, round(predict(d.nnet, dtest[,-192]), 0))

0 1
do 38 3
reb 3 38

 

 

do-0, reb-1 が、正しくコードを判定できた数。正解率 92.68%ということはなかなか良い結果なのだろうか。まぁ7%の確率で判別ミスってのも痛いがw


まとめ

はじめてニューラルネットワークを学んだときは、仕組みが複雑すぎてなかなか手を出しづらいなぁという印象があった。しかし nnet のように、ニューラルネットワークを簡単に扱うためのパッケージも用意されており、中の仕組みを意識しなくても簡単に使える。こういうのを積極的に使って、音楽以外にも面白いこといろいろ実現できるんじゃないかなぁ、と思った。


今後の目標

とりあえず今回は2コードのみの分類を取り扱ったが、これを拡張して、最終的には3和音のレベルですべてのコードを判別できるようなものを作りたい。(ってか本当に欲しいのはそっちだw)


参考

 

rgl の plot3d で3Dグラフを描く

困った

眠れん。
仕方ないのでブログ書く←

rgl とは

rgl

rgl: 3D visualization device system (OpenGL)

R上で、OpenGLを使って高度な3Dグラフを描くためのパッケージ。インタラクティブな操作も可能で、3D表示させたものをマウスでぐりぐり回転できる。なかなか良い。

rgl のインストール

インストールは若干やっかいかもしれない。X11のライブラリが見つからないと怒られることがよくあるようだ。オイラもそこで少しハマった。Fedora では以下のコマンドを実行してX11関連のライブラリをインストール。

yum groupinstall "X Software Development"


その後通常通り rgl パッケージをインストールすればOK。

> install.packages('rgl')


rgl を使うには以下のようにライブラリをロードする。

library(rgl)

plot3d

plot3d というコマンドを使って3Dグラフを表示させることができる。


サイズの同じ vector を3つ作ってデータを入れる。

x <- 1:10
y <- seq(1, 19, 2)
z <- c(5, 3, 8, 9, 3, 7, 3, 2, 9, 5)


対応が分かりやすいようにデータフレームに入れてみる。

d <- data.frame(x, y, z)
d
x y z
1 1 1 5
2 2 3 3
3 3 5 8
4 4 7 9
5 5 9 3
6 6 11 7
7 7 13 3
8 8 15 2
9 9 17 9
10 10 19 5


これをプロットする。

plot3d(d$x, d$y, d$z)


デフォルトでは点で描かれる。ちょっと見づらければ球体にすることもできる。色もつけてみよう。

plot3d(d$x, d$y, d$z, type="s", col="green")



type で形状を、col で色を指定可能。またグラフが表示されているウィンドウ上でマウスを操作すると、いろいろな角度から見ることができる。

音声の解析に使ってみる

せっかくなのでもうちょっと実用的なものを表示してみたいと思う。CMajのコードを演奏した音源を用意し、その周波数成分が時間経過でどのように変化するかを見てみる。CMajといっても、ド・ミ・ソ のサイン波を単に合成したものではなく、QY70というシーケンサーで CMajのパターンを数秒間演奏させたものを使う。ドラムやベースなど様々な楽器で構成されているので、いわゆる「雑音」もそれなりに入っている。


プログラムとサンプル音源はここに置いてある。

R --vanilla --slave --args Cmajor.wav < fft3D.R




(画像クリックで拡大)


3Dだとわかりづらいが、result$times(0, 20, 40, 60) が時間(1 = 約0.01秒)、result$freqlist(0, 100, 200, 300) が周波数(Hz)、そして縦軸のresult$amp(0, 5e+06, ...) が音量を表す。4/4拍子なので、時間軸に沿って強い山の間に弱い山が3つほどあるのが何となく分かる。


プログラム内では、result というデータフレームに解析結果を格納し、それを以下で描画している。

plot3d(result$times, result$freqlist, result$amp, col=rainbow(numPreview * 3), type="l", lwd=0.5)

col=rainbow を指定すると色が虹色に変化しながら表示される。すぐ隣のフレームとの変化を見やすくするためにパラメータを微調整した(ので、numPreview * 3 という数値に深い意味はない)。


ところでプログラムの実行が終了するとグラフのウィンドウが消えてしまうので、最後に Sys.sleep(1000)とか入れてみたんだけど、もっといい方法ないですかね。w

Coursera の Machine Learning

Coursera というオンライン学習サイトに Machine Learning という講座がある。6月から受講開始して約3ヶ月。ようやくすべて終えることができた。受けて本当に良かったと思っているので、紹介したいと思う。


この講座を知ったのは、たしか5月くらいだったと思う。「機械学習」と聞いてはじめに思ったのは、オイラに理解できるわけないだろう、と。しかしちょうど音楽解析に興味を持ち始めた時期ということもあって、とりあえずやれるところまでやってみて、ダメだったらそのときやめればいい、って思ってた。


そして6月に講座が始まる。


…やっぱりかなり難しかったw


大体週に5-7時間の時間を、講座と課題の作成に取られた。仕事しながらこのくらいの時間を確保するのは、なかなかに大変だ。特にここ数カ月は仕事のほうがかなり佳境に入ってきてて、平日はほぼ家に帰って何もできない状態だった。仕方なく、休日にまとめて課題を進めることで、なんとか毎週の課題提出期限を守っていた。

ただ、時間確保が難しかったものの、講座の内容自体はすごく興味深いものだった。機械学習について何も知らなかったオイラが、その面白さにぐいぐい引きこまれていく。夢にまで見てうなされるほどになるw 機械学習なんてオイラには…と思っていたオイラが、気がついたらどっぷり興味を注ぐようになってしまっていた。


機械学習を学ぶのに、10週間という期間は短いのかもしれない。しかし機械学習の世界に興味を持ち、足を踏み入れる第一歩としてはすごく良い内容だったと思う。

コースの内容

コースは全体で10週。毎週1つか2つのトピックを扱う。内容は

  • Video Lectures
  • Review Questions
  • Programming Exercises

の3つからなる。まず Video Lecture で動画を見ながら講義を受ける。英語の講義なのだが、日本語の字幕を出すこともできる。翻訳の質もなかなか良い。"We" が「我ら」とか、「そんなことはやらん」とか、言い回しが多少変なところもあるがw 内容を理解する上では全く支障はない。英語のヒアリングが苦手という人でも、字幕を見れば大丈夫だろう。

講義の内容を理解したら、Review Questions を受ける。これは講義の内容をもとにした選択式の問題。コースを修了するためには、毎週設定された期限内にこのテストで平均8割をとらなくてはならない(期限を過ぎるとペナルティがつくが、一応評価はされる)。

もうひとつ、Programming Exercises というプログラミングの課題もある。これは、講義で学習したことを実際に手を動かしてプログラミングするというもの。これがなかなか面白かった。課題では Octave という言語を使用する。(MatlabライクとのことらしいがオイラはMatlab使ったことないから分からない)。Octave は初めて触ったのだが、まぁ普通のプログラミング言語を扱える人であればそれほどハードル高くないかも。この課題もオンラインで採点されて、一定以上の正解を出す必要がある。

※ Review Questions と Programming Exercises は英語の問題文のみ。ここだけ英語をたくさん読まされる。


機械学習は興味あるけど、自分とはあんまり縁がないし…そんなエンジニアの人にこそ、この講座をオススメしたい。あ、ちなみに無料です。


で、10週すべてクリアしたのはいいのだが、ただ覚えただけでは意味がないので、これを実世界で応用したいなぁなんて考えてる。特に音楽関連で何か活かせないかなぁと目論んでる最中。

あと、次はやっぱPRMLなんだろうか…(汗

tuneR を使ったMFCCの計算

ほぼ自分用の備忘録←

tuneR で MFCC を求める手順。バージョンは 1.2.1。

library(tuneR)

# wavファイル読み込み
# mp3ファイルの場合は readMP3()を使えばよい
wobj <- readWave(filename)

# melfcc() は現行では mono 音声しか扱えないので必要に応じて変換
wobjm <- mono(wobj)

mfcc <- melfcc(wobjm, 
           numcep = 20, 
           nbands = 36,  
           wintime = 0.023,
           hoptime = 0.023)

nbands はメルフィルタバンクの数。numcepは、離散コサイン変換した結果、低周波成分から数えていくつ分の係数を取得するかを指定。wintimeは1フレームの長さ(ms), hoptimeは、フレーム間の時間間隔(ms)。

結果は (フレーム数)x(numcep) の行列で返ってくる。1列目は各フレームの平均エネルギーを表し、特徴を表すのに有用ではないので切り捨てて19次元にする。