PythonとRuby,始めるならどっち?
2016年の今だからこそ。
昔だったらスクリプト言語やり始めたい場合はPythonかRubyの好きな方を選べって感じでした。 でも現在では,好きな方を選べって返答はたぶん間違いになると思います。 じゃあどっちを選べば良いのかってお話。
どうしてこんな記事を書くのか
自分はRubyが好きです。でもPythonも使ってます。 そのうえで,世の中のPythonとRubyの比較記事ってどちらかに偏ったものが多いな,と思うわけです。
例えば。 Rubyが好きだからPythonにもmap使わせたり(あまり使わない),Pythonが好きだからRubyにもfor文使わせたり(まず使わない),そんな例ばかりです。 そんな書き方してたら自分の好きな言語が使いやすいに決まってるじゃないかっていう例を出しているので。
そういうわけであくまで中立を保ってる自分がこういう記事を書いてみたいな,と思ったわけです。
というわけで本題
どっちの言語からやるのが良いか?の返答として,やりたい内容に依る ってことを強く伝えたいです。
自分が両方の言語を使う際は,内容によってはっきりと使い分けています。 例えばWeb系。これは絶対Ruby。 対して機械学習系。これは絶対Python。
RubyでもPythonでもできることはRubyで,PythonでしかできないことをPythonでやってるって感じですかね。
どっちでもできる内容なら,日本語で書かれたWebサイトとかたくさんあるRubyを選ぶので。
そんな感じで使い分けをしています。
Rubyが得意とする分野
Rubyの産みの親が日本人のかたなので,日本語のドキュメントの多さ ではPythonを上回っていると思います。 最新の情報をかなり速い段階で追って行けるっていうのはありがたいです。英語も読めるけど日本語の方が格段に理解が速いので。
元祖WebフレームワークであるRailsが強いので,Web系は今でもRubyが強い印象。 RubyはPythonに比べて遊びの分野で強い印象があります。
Pythonが得意とする分野
主に学術分野です。この分野ではもはやPyhton一強なのではってぐらいPythonが強いです。
数値処理がなんでも高速でできるnumpy,化学計算用のライブラリが詰まったscipy,グラフ描写にmatplotlib,自然言語処理にNLTK,統計処理や機械学習にscikit-learnと学術系ライブラリの豊富さではほか言語の追随を許さないレベル。
Googleが出したDeep learning用のライブラリ(Tensorflow)もPythonですしね。この分野でのPythonの浸透力はかなりのものだと思います。
ていうか学術分野以外でもRubyと同レベルのライブラリが揃ってます。ライブラリの豊富さでは(Rubyも豊富ですが)Rubyを越えている感じ。ただ日本語のドキュメントが少ない。
Python2だと日本語操作で泥沼にはまるって経験を何度もしたので,自然言語処理とかはPython3を使いましょう。RubyでできるならRubyでも。
ランキングから両者を比較
というわけで主要なプログラミング言語ランキング。 こちらのサイトから引用させていただきました。
IEEE | TIOBE | Devpost | GitHub | RedMonk | |
---|---|---|---|---|---|
1位 | Java | Java | HTML/CSS | JavaScript | JavaScript |
2位 | C | C | JavaScript | Java | Java |
3位 | C++ | C++ | Python | Ruby | PHP |
4位 | Python | C# | Java | PHP | Python |
5位 | C# | Python | C / C++ | Python | C# |
6位 | R | Objective-C | PHP | CSS | C++ |
7位 | PHP | PHP | Objective-C | C++ | Ruby |
8位 | JavaScript | Visual Basic .NET | C# | C# | CSS |
9位 | Ruby | JavaScript | Swift | C | C |
10位 | Matlab | Perl | JSON | HTML | Objective-C |
Pythonは常に3位から5位をキープ。対してRubyはGithubでは3位だけれどもTIOBEではランク外だったり。大抵10位前後にいるイメージ。 Pythonは強いだけあるなぁって感じですね。ていうか自分もCとかJavaとか速い言語書けるようになるべきなのかって少し思ってしまう
Python一択では
ここまで見てると日本語ドキュメントがなくても困らない程度に英語が読めるなら正直Python一択なのでは... って感じになってしまうのでRuby好きの自分としてはRubyの株も上げておきたい
Rubyの良いところ
Rubyは「ストレスなくプログラミングを楽しむこと」 を重視して作られた言語だそうです。 ストレスなくプログラムが組める,これめっちゃ重要なポイントじゃないですかね。
Pythonだとエラー取れなくてこのやろうみたいな感じでプログラム組んでますが,Rubyだとエラーに引っかかることがかなり少ないです。 やってる内容もPythonの学術関係の方が難しいことが多いのでPythonのほうがエラー取れなくて当たり前だと思うんですけど,それでもやっぱりRubyのほうが日本人の自分にとっては楽に感じます。
これはプログラミングのモチベーションを維持するのに役立つのではないでしょうか。
日本語のドキュメントが多いことも相まって,ストレスが数倍少ないように感じます(個人差があります)。
あとPythonはもとが格好いい言語なので格好良く書くことを求められるわけでそれで苦労しますが,Rubyは何も考えなくてもそこそこ美しくなる感じもGood。これは偏見かも。
Pythonはfor for for,Rubyはeach map select
Pythonはただひとつの美しいやり方があるってスタンスなのに対して,Rubyは同じことでも色々な方法でできた方が美しいって考え方です。
プログラムをちょっと比較してみます。
Pythonはfor文がかなり強くて,配列操作ならとりあえずforしとけって感じ。Python3は2よりもさらにmap系の非推奨感が増してる感あります。
対してRubyは普段for文なんか使っている人見たことなくて(必要になる場面も稀にあります),each,map,selectみたいなメソッドを使い分ける感じです。
arr = [2, 6, 3, 8] for x in list: print x print( [x**2 for x in list] ) # => [4, 36, 9, 64] print( [x for x in list if x>4] ) # => [6, 8]
arr = [2, 6, 3, 8] arr.each do |x| print x end print list.map{|x| x**2} # => [4, 36, 9, 64] print list.select{|x| x>4} # => [6, 8]
まったく同じ内容を二つの言語で書いてみましたが,決定的に違う点があるのが見つけられますかね?
Pythonは右から左,Rubyは逆
Pythonは右から左に処理が流れることが多いんですけど,Rubyは逆なんですよね。 だから自分はRubyが好きになりました。文章を書くときと同じ感覚で流れるように左から右に書いて行けるので。Pythonだと行ったり来たりして流れるように書けないので思考が中断してしまうんですよね。ここは人の好き好きだと思います。
例えば,さっきの例での比較。
listから一つずつ要素をxとして取り出して,それを一つずつ2乗するっていう処理。
#Python [x**2 for x in list] #Ruby list.map{|x| x**2}
PythonとRubyが逆でしょ。思った通りに左から右に書けるRubyが自分には良いなぁって思います。使いやすい。
結論
まぁ結局始めるならPythonなんじゃないですかね。Pythonやっておけば他の言語が必要になることってほぼないのでは?ってくらい何でもできる印象があります。
それでもこの記事読んでてあーRuby良いかもって思った人はRubyだと思います。学術目的やる予定ないんだったらRubyだと思う。最終的にはどっちもやってしまえば良いよ。
やっぱり自分に合ってる方が良いよね,みたいな記事でした。
RubyとPerl比較: MIDIを読み込んで編集する
MIDIを編集したい。
MIDIをプログラム組んで編集したいなって思ったとき,これまでは過去に英華を極めたPerlでMIDI読み込みをやってました。時代的にPerlが一番強いときにMIDIもかなり流行ったと思うので。
それをRubyでやったら超楽にできちゃったやんっていう話。
とりあえずダイナミクスを可能な限り広げるスクリプトをPerlとRubyで書いてみました。 具体的には,MIDIの内容がベロシティ60-100の間の音しかない場合,それをベロシティ1-127の範囲に広げるって感じのスクリプト。
Perlから。
#!/usr/bin/perl use MIDI; use MIDI::Simple; use MIDI::Score; use Data::Dumper; use List::Util qw(max); use List::Util qw(min); local $Data::Dumper::Indent = 0; my $midi = shift; if( !defined($midi) ) { print "ABORTED!\nUsage:\n\$ perl midi.pl something.midi\ninput midi file."; die; } print $midi; print "\n"; my @s; my @channel; my @v_ch; my @mtrack; my $opus = MIDI::Opus->new({ "from_file" => $midi || die }); my $i = 0; foreach my $t ($opus->tracks) { if($t->events_r and @{$t->events_r}) { my($score_r, $ticks) = MIDI::Score::events_r_to_score_r( MIDI::Event::copy_structure( $t->events_r )); # event kara score he @notes = (); my $count = 0; while ($count < scalar(@$score_r)){ if(${$score_r}[$count]->[0]=~/note/){ print ${$score_r}[$count]->[5]; print "-"; print ${$score_r}[$count]->[4]; print "\n"; push(@notes,${$score_r}[$count]->[5]); } $count ++; } my $min_note = min @notes; my $max_note = max @notes; $count = 0; while ($count < scalar(@$score_r)){ if(${$score_r}[$count]->[0]=~/note/){ ${$score_r}[$count]->[5] = 1 + int ((${$score_r}[$count]->[5] - $min_note) * (126/($max_note-$min_note))); } $count ++; } my($events_r, $ticks2) = MIDI::Score::score_r_to_events_r(MIDI::Event::copy_structure( $score_r)); # score kara event he push @s,$events_r; my $x = MIDI::Track->new({ 'events' => $events_r }); push @v_ch , $x; $i++; push @ticks, $ticks; if(my $time_now) { unshift @{$t->events_r}, ['text_event', $time_now, "_"]; } } else { DEBUG > 1 and print " * Track is eventless. Skipping.\n"; } push @out_tracks, $t; } push @granularities, $opus->ticks; print $ticks[3]; my $o = MIDI::Opus->new( { 'format' => 1, 'ticks' => 480, 'tracks' => [ @v_ch ] } ); $o->write_to_file( 'output.mid' );
Perlだとこんな感じ。ざっと約80行。 正直Perlはわけわからないので酷い書き方してると思います。笑
これと同じ処理をRubyで書いたら。
require 'midilib' seq = MIDI::Sequence.new() File.open(ARGV[0], 'rb') do |file| seq.read(file) do |track, num_tracks, i| puts "read track #{i} of #{num_tracks}" end end seq.each do |track| notes = track.map{|event| event.velocity if MIDI::NoteEvent === event and event.note}.compact track.each do |event| if MIDI::NoteEvent === event and event.note puts event.velocity.to_s + "-" + event.note.to_s event.velocity = 1 + ((event.velocity - notes.min) * (126/(notes.max-notes.min))).to_i end end end File.open('output.midi', 'wb') { |file| seq.write(file) }
なんと20行!!びびる
Perlはよく知らないのでもっと行減らせるんだろうなーとは思ってますが,それでも4倍の差がでるのはすごいなーと思います。
まぁPerlやってる人が書いたら20行くらいに行減らせそうですけどね...
というかコードの行数とかどうでも良くて,自分にとっての分かりやすさが段違いです。
MIDIの読み込みでPerl以外の良い方法をこれまで知らなかったのでそのためだけにPerl使ってました。
が,ついにPerlともお別れの時が来たようです。
Processing vs D3.js
...ビジュアライジングしたい。
と思ったわけです。現代では直に感覚に訴えかけるようなビジュアライジングが溢れており,自分もやりたいなーと思ってしまうわけです。
そこで誰もが突き当たるのが,どうやってビジュアライズするか 問題。
ビジュアライジングっていわゆる可視化手法で,理解しづらいデータを視覚的に理解しやすい形に再構成することらしいです。
グラフ書くだけならExcelで何も問題ないのですが,やっぱり対話的に作りたいなーと思いますよね。マウスかざしたら詳細が表示されるとか。
てなるとやっぱりプログラム組まないといけないわけです。そしてプログラミングの手法に何を選ぶかってその後の効率にかなり響いてくるのでけっこう大事。
というわけで,代表的な可視化手法であるProcessingとD3.jsのどちらを選ぶか悩んでみました。とりあえずオライリー本は両方あります。比較するしかない。
Processingとは?
ご存知かなり古くからある可視化手法。MITで2001年に生まれたんだっけ?
Javaで記述します。PythonやJSもサポート。
一応processing-rubyってのがあるらしい。Rubyで書いたらprocessing用のJava吐いてくれるやつ。Rubyで書けるって聞くと飛びつきたくなります。笑
Processingのオライリー本が2008年初版なのでさすがに古さを感じます。そして書いてある内容がすごいお堅い。ていうかJavaで説明されるのがつらいなーって感じ。どうせJSかRubyでしか書かないと思うのに。
D3.jsとは?
データ・ドリブン・ドキュメントとかいうのの略語らしい。2011年ぐらいに作られたものだと思います。日本ではそれほど流行ってる印象がないですね。
記述方法はJavaScript一択。D3.js単体では図形を描写する能力すらなくて,HTMLの仕様に組み込まれた他の物(ていうかほぼSVG)を操作して図形を描写します。
D3.jsのオライリー本をProcessingのコントリビュータさんが書いているらしい。ちなみに2014年初版。書いてある内容はめちゃくちゃ面白いです。くだらないギャグが7割り増しらしい。動的型付けの欄はめちゃくちゃ笑いました。
このD3.js入門サイトの内容を大幅に改訂したもの。分量は桁違いに多いです。そして学びやすい。
両者の比較
比較ならとりあえずグーグルトレンドを参照するよね。笑 グラフを見る2013年くらいからD3.jsがProcessingを上回っている感じです。 やっぱり後発のD3.jsのほうが道具としてうまく練られてれているのでしょうか。
ツールの違いとして,Processingはセット一式なんですが,D3.jsは一つのツールのみなんですよね。
例えるなら,Processingはハサミ,のり,トンカチ,定規が揃ったツールボックス。
D3.jsはハサミだけ。あとはHTMLとかJavaScriptとかに丸投げ。この違いは結構大きいと思います。
とりあえずD3.jsを学んでみた
感想としては,良い。 Ruby好きだったら同じようにバリバリメソッドチェインしていけるから最高です。 Processingだったら数行数十行書かないといけないようなプログラムも繋げ続ければ一行で書けてしまう。もちろん見にくいので改行しますが。笑
とりあえずすべての建物を地図上に投影してみたり。東京。
D3.jsはツールの一つであるっていう立ち位置が非常にありがたいですね。Web系だったらある程度触った事あるので,そこにD3.jsっていうものを足すだけで十分なのは学習コストが非常に低くて良いです。それにWeb周りの学習にもなります。
実際自分は,ビジュアライズというよりネット上のベクター画像(SVG)操作用のライブラリとして使っている感が有ります。
ただし,一つできないことがあります。それは光るようなビジュアライズ。
光っているものをPCの画面上で実装する場合は加法混色で実装するのですが,SVGではまともに対応していません。二枚の画像を加法混色で重ねあわせることならできましたが,それ以上のことは自分の力不足かできませんでした。
多分こればかりはProcessingを使わないとできなさそうです。
つっても光らしたり3D描写したりっていうミーハーなことやりたいんだったらThree.jsとか学んだ方が近道かもしれませんが。
結局両方必要になるのでは?
まとめとして,まぁどちらの手法にも一長一短あるんだと思います。両方をある程度学んだ上で,こういうことやるならこっち,っていう選び方が出来るようになりたいなーと。
だから始めるのも自分に合った手法,自分がすんなり受け入れられそうな手法で始めるのが良いと思います。
例えば,Web周り触った事あるんだったら最初に手を出すのはD3.js一択だと思いますね。PHPやRubyでデータベース扱って,それをJSに渡して,ビジュアライジングするっていうのがシームレスにできます。
JavaやPythonの経験があるのならProcessingからってのも良いと思います。細かいところから作り込める伝統ある可視化手法ですし。
自分に合ったやり方から始めるのがやっぱり一番良いですよね。みたいな感じでした。
全国の道の傾斜マップを作った。
自転車とかで遠くへ行きたい時とか,事前に坂がきつい道とかを知っておけると良いなーって思いませんか。自分はよく思います。
でもそんな時に地図の等高線とか見ても何も役に立たないんですよね。笑
というわけで,全国の道の傾斜を表示する傾斜マップを作りました。Rubyで。
使い方
灰色ー赤色の道が傾斜がきつい道になります。水色は平坦。
きつい坂を登りたくない場合,赤色は避けてください。水色の道はかなり楽に走れます。
「場所を指定して移動」の欄に見たい場所を入力すると,そこへ自動へ移動してくれるようになっています。富士山,長野,みたいな場所を示す単語を入力してください。
スマホ等で表示した場合は現在地を取得するボタンがついています。現在地が取得できた場合,マップが自動で現在地に移動します。
注意
マップの傾斜情報が取得されている範囲ですが,一応全国表示取得しているはずです。
仕組みとしてはGoogleMapに道に色付けするAPIがなかったので,OpenStreetMapの全国の地図情報をサーバー側で持っておいてそれをGoogleMap上に表示するという結構エグいことをやっています。笑
Webサーバーが非常に非力なので,あまりアクセスが集中するとエラー吐かれたりすると思います。
回線が遅い場合でもエラーが頻発するかもしれません。 ただの個人サイトなのでお手柔らかにお願いします。
かなり要求スペック高いので,一昔前のスマホとかだときついかもしれません。推奨はiPhone5s以上,iPad Air 2以上って感じです。
もし需要があったらiOSやAndroidのネイティブアプリでも作る予定です。Webベースより数倍表示が速いだろうし。
というわけで
傾斜マップのページはこちら。->Cymap
無料運用しているため、アクセスした後起動するのに時間がかかります。
快適な自転車ライフをCymapと共に送っていただけたら幸いです。
あと名大周りってこう見ると坂がひどい...笑
画像の座標を取得するアプリ作った(Ruby+sinatra+D3.js+jQuery)
画像の一点をクリックしたら座標を取得してくれるようなそんなアプリがほしかった。
多分ドンピシャなソフトを探すより自分で作った方が速いだろう,と思ったので作りました。
RubyとHTMLを合わせても40行にも満たないミニアプリです。
画像をクリックするとその座標の位置情報をresult.txtに出力します。
require "sinatra" get "/" do erb :index end get "/place" do File.open("result.text", "a") { |f| f.write params["x"]+"-"+params["y"]+"\n" } end __END__ @@index <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <script type="text/javascript" src="d3.js"></script> <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript"> var w = window.innerWidth; var h = window.innerHeight; d3.select('body').append('div').append('svg').attr({'width': w, 'height': h}).append('image') .attr({ 'xlink:href': "hoge.png",//表示する画像のディレクトリを指定,public/hoge.pngがhoge.png扱いされるので注意 width: w, height: h, }); d3.select('svg').on("click",function(){ var x = d3.mouse(this)[0]; var y = d3.mouse(this)[1]; $.get("/place?x="+x+"&y="+y); }) </script> </head> <body></body> </html>
出力はこんな感じ。x軸の座標-y軸の座標。
283-214 323-213 346-253 411-253 446-253 512-253 577-253 639-253
使い方
使う人が果たして存在するのだろうか。Ruby環境があればgem install sinatraしてこのRubyファイル実行してください。Publicフォルダにd3.jsとjquery.min.jsを入れるの忘れずに。
取り急ぎ。
名大で一番リア充が入る学部はどこだ? Project OxfordのEmotion APIを試す
突然ですが,名古屋大学で一番リア充が多く入る学部ってどこだと思います?経済?教育?
というわけで,今日は名大で一番リア充が入る学部を調べてみました。
どうやって調べる?
名大に在学している学生全員に「あなたはリア充ですか?」 って聞いて,統計取ればいけるはず...
ただそれは現実的にはキビシい,っていうか無理。
というわけで,Project Oxfordを試してみました。
Project Oxfordとは?
Microsoftが出しているAPI群で,写真に写っている顔から性別や感情などの認識ができます。 一時期流行った写真から年齢を表示するサービスもこれ。
そのAPI群の一つである,感情認識をするためのEmotion APIというものを使いました。 顔を自動で判別して,そこから感情を測定する事が可能です。(happiness 80%, sadness 10%...みたいな)
技術的にはopencvとCNNでいけるかな?
というわけで感情測定
名大の一学年全員の顔を感情測定に突っ込みました。
リソースは入学記念アルバム2014から。写真使ってしまった人ごめんなさい。笑
結果から一番Happiness度が高い学部がリア充が多く入る学部だ!測定するしかない!
ちなみに自分は一番リア充な学部は教育,最下位は工学部だと考えました。
よければみなさんもどこが一番か考えてみてください。ちなみに,名大にある学部は,[文学,法学,経済,教育,情報文化,理学,医学,工学,農学]の計9学部です。
測定方法
ここは興味ない人は読み飛ばしてもらってかまいません。
APIを使うには,画像をバイナリ化してPostメソッドで送信するだけでいけるらしいです。ただRubyのコードがネット上には見当たらなかったので一応書いたコードを載せておきます。
require "net/http" require "uri" require "json" auth_key = "hoge" # 取得したauthkey request_url = "https://api.projectoxford.ai/emotion/v1.0/recognize" dir = "/hogedir/hoge.jpg" # 画像の置いてある場所 uri = URI.parse(request_url) puts uri.host puts uri.port req = Net::HTTP::Post.new(uri.request_uri) req["Content-Type"] = 'application/octet-stream' req["Host"] = 'api.projectoxford.ai' req["Ocp-Apim-Subscription-Key"] = auth_key data = File.binread(dir) puts "Data_dir: #{dir}" req.body = data res = Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') do |http| http.request(req) end puts res.code puts res.body
結果
というわけで結果がこちら。総勢1719人の感情を測定しました。
表ではある感情だった割合を示しています。suddnessが2.8だったら2.8%が悲しんでいたっていうこと。
学部 | anger | contempt | disgust | fear | happiness | neutral | sadness | surprise |
---|---|---|---|---|---|---|---|---|
文学 | 0.13 | 1.14 | 0.25 | 0.05 | 73.04 | 22.44 | 2.83 | 0.08 |
教育 | 0.04 | 0.68 | 0.42 | 0.03 | 67.56 | 25.33 | 5.4 | 0.5 |
法学 | 0.11 | 0.86 | 0.12 | 0.01 | 34.98 | 61.47 | 2.35 | 0.06 |
経済 | 0.14 | 0.82 | 0.11 | 0.01 | 48.66 | 48.54 | 1.57 | 0.11 |
情文 | 1.15 | 1.1 | 0.34 | 0.03 | 50.87 | 45.64 | 0.78 | 0.06 |
理学 | 0.14 | 1.17 | 0.4 | 0 | 49.03 | 47.26 | 1.93 | 0.03 |
医学 | 0.13 | 1.14 | 0.11 | 0.02 | 49.6 | 47.11 | 1.64 | 0.2 |
工学 | 0.25 | 1.35 | 0.87 | 0.05 | 40.94 | 51.85 | 4.58 | 0.06 |
農学 | 0.19 | 0.73 | 0.17 | 0.06 | 51.26 | 44.77 | 2.25 | 0.53 |
とりあえずHappinessのみのグラフをとってみる。
...名大で一番リア充が入る学部は文学部だった。
最下位は法学部。
私ですか?もうすぐ潰れる予定の情文です。
こんどは卒業アルバムでやりますかね。
Tensorflowのサンプルを理解してみる。(初心者向け)
Googleが出したTensorflow,盛り上がり具合がやばいですね。
githubのスター数とかを見ていると,スタンダードであるChainerとかCaffeとかを(盛り上がり具合だけは)軽く越えてしまった感じ。
というわけで,MNISTの(畳み込みしているほうの)サンプルをざっと読んでみました。
備忘録がてらメモしようかな,と思ってブログにしてみます。
自分は初心者なので,まぁそのレベルで色々注をつけて行こうと思います。
Tensorflowの良かったところ
最初に,Tensorflowの良かったところを。
大規模処理で並列化とかが楽らしいんですけど,正直よく分からないです笑
コードを見て思ったのは,必要な部分だけが厳密に書かれているなーと思いました。
低い次元の記述する必要のないものはしっかり隠して,必要な記述はちゃんと書かれているってのが自分みたいな初学者には良いと思います。Chainerとかだと大切な部分も結構略されてたりするので。
Deeplearningを研究でやっている友人とコードをおさらいした(というかほぼ教えてもらった)ので,復習のためにメモを残そうかな,と思ったわけです。
というわけでコード
インストール(Mac)
pip install https://storage.googleapis.com/tensorflow/mac/tensorflow-0.5.0-py2-none-any.whl
サンプル等のセットをダウンロード。
git clone https://github.com/tensorflow/tensorflow
今回見てみたのはtensorflow/tensorflow/models/image/mnistの中のconvolutional.pyです。
チュートリアルで使われているMNISTは畳み込みしてないただのニューラルネットですが,こっちはしてるっぽいので,より実用的かなーと思ったので。
途中で感想挟みつつ。
とりあえずモジュール部分。
from __future__ import absolute_import from __future__ import division from __future__ import print_function import gzip import os import sys import tensorflow.python.platform import numpy from six.moves import urllib from six.moves import xrange # pylint: disable=redefined-builtin import tensorflow as tf
Googleでもnumpy使ってるんだなぁとか思ったり。
Main部分から見ていきます。
def main(argv=None): # pylint: disable=unused-argument #自分自身のテスト #フェイクデータを読み込んでうまく行くか試す if FLAGS.self_test: print('Running self-test.') train_data, train_labels = fake_data(256) validation_data, validation_labels = fake_data(16) test_data, test_labels = fake_data(256) num_epochs = 1
ここまでで全体のテスト。
else: # テストじゃなければデータを読み込む train_data_filename = maybe_download('train-images-idx3-ubyte.gz') train_labels_filename = maybe_download('train-labels-idx1-ubyte.gz') test_data_filename = maybe_download('t10k-images-idx3-ubyte.gz') test_labels_filename = maybe_download('t10k-labels-idx1-ubyte.gz') # 画像をnumpy配列に展開 train_data = extract_data(train_data_filename, 60000) train_labels = extract_labels(train_labels_filename, 60000) test_data = extract_data(test_data_filename, 10000) test_labels = extract_labels(test_labels_filename, 10000) # 検証用のデータとトレーニング用のデータに分割 validation_data = train_data[:VALIDATION_SIZE, :, :, :] validation_labels = train_labels[:VALIDATION_SIZE] train_data = train_data[VALIDATION_SIZE:, :, :, :] train_labels = train_labels[VALIDATION_SIZE:] num_epochs = NUM_EPOCHS train_size = train_labels.shape[0] # 0次元目のサイズ取得
ここでデータのダウンロード。maybe_downloadでは学習用のMNISTのファイルがなかったらダウンロードする仕組みの関数。 ダウンロードしたデータをextract_data等の関数に渡してnumpyのArrayに変換しています。
extract_data関数の中身はこんな感じ。
def extract_data(filename, num_images): print('Extracting', filename) with gzip.open(filename) as bytestream: bytestream.read(16) buf = bytestream.read(IMAGE_SIZE * IMAGE_SIZE * num_images) data = numpy.frombuffer(buf, dtype=numpy.uint8).astype(numpy.float32) data = (data - (PIXEL_DEPTH / 2.0)) / PIXEL_DEPTH data = data.reshape(num_images, IMAGE_SIZE, IMAGE_SIZE, 1) return data
ここで0-255の色の濃さを-0.5から0.5までに変更しているっぽいです。
returnしているdataは画像の種類,x軸,y軸を格納したnumpy配列になってます。
つまりdata[0-60000個くらい][x軸0-31][y軸0-31]みたいな感じ。
MNISTのデータなので白黒です。色の次元はありません。
まぁこの辺は機械学習関係ないのでさらっと行きましょう。状況によって書き方変わるでしょうしね。
main関数に戻ります。
# バッチを入れるための入れ物を最初に作っておくみたい # 学習ごとにバッチの中身は変わるので train_data_node = tf.placeholder( tf.float32, shape=(BATCH_SIZE, IMAGE_SIZE, IMAGE_SIZE, NUM_CHANNELS)) train_labels_node = tf.placeholder(tf.float32, shape=(BATCH_SIZE, NUM_LABELS)) # 検証用だからconstとして固定しておく,これも入れ物 validation_data_node = tf.constant(validation_data) test_data_node = tf.constant(test_data) # モデルの重み部分の初期化,いわゆるwの部分 # {tf.initialize_all_variables().run()}を実行した際に初期化される conv1_weights = tf.Variable(# 畳み込み層の重み,Variableなので途中で学習に応じて変わる tf.truncated_normal([5, 5, NUM_CHANNELS, 32], # 5x5 filter, depth 32. stddev=0.1, # 標準偏差 seed=SEED)) conv1_biases = tf.Variable(tf.zeros([32])) # バイアス0埋め conv2_weights = tf.Variable( tf.truncated_normal([5, 5, 32, 64], stddev=0.1, seed=SEED)) conv2_biases = tf.Variable(tf.constant(0.1, shape=[64])) # バイアス0.1埋め fc1_weights = tf.Variable( # fully connected, depth 512. # 全結合層の重み tf.truncated_normal( [IMAGE_SIZE // 4 * IMAGE_SIZE // 4 * 64, 512], stddev=0.1, seed=SEED)) fc1_biases = tf.Variable(tf.constant(0.1, shape=[512])) fc2_weights = tf.Variable( tf.truncated_normal([512, NUM_LABELS], stddev=0.1, seed=SEED)) fc2_biases = tf.Variable(tf.constant(0.1, shape=[NUM_LABELS]))
モデルの定義をする前に初期化だけ先に定義して,それを(後から)モデルに突っ込むって感じです。 学習によって変化していく部分はVariableと定義して,変化しない部分はconstantと定義しているので見やすいですね。
バッチの入れ物だけを最初に作っているのが珍しいかなって思いました。Chainerとかでは学習中に分割してるみたいなコードの書き方をしていたので。 モデルの重みの初期化までしっかりやってるんだなーって感じ。 標準偏差0.1と決めてあるのも略しても良いのに書いてあるのがありがたい。 必要なところをしっかり厳密にって感じ。エンジニア魂を感じますね。
どうでも良いけど第二全結合層ってFC2って略すんですね。 あのウェブサイトもここから名前を取ったんですかね。 本当にどうでも良かった。
そしてモデルの定義。一番重要なネットワーク部分。
def model(data, train=False): # データとモデルの重みを二次元の畳み込み層に突っ込む conv = tf.nn.conv2d(data, conv1_weights, strides=[1, 1, 1, 1], # ストライド,畳み込む際に画像をいくつ飛ばしで処理して行くか padding='SAME') # ふちを付けて同じサイズの画像が出力されるようにしている # 活性化関数としてrelu通すよ relu = tf.nn.relu(tf.nn.bias_add(conv, conv1_biases)) #最大プーリング,画像を分割してその中で最も値が大きいものだけを残してあとは削る方法で画像を縮小 pool = tf.nn.max_pool(relu, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME') conv = tf.nn.conv2d(pool, conv2_weights, strides=[1, 1, 1, 1], padding='SAME') relu = tf.nn.relu(tf.nn.bias_add(conv, conv2_biases)) pool = tf.nn.max_pool(relu, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME') # 二次元画像を一次元に変更して全結合層へ渡す pool_shape = pool.get_shape().as_list() reshape = tf.reshape( pool, [pool_shape[0], pool_shape[1] * pool_shape[2] * pool_shape[3]]) hidden = tf.nn.relu(tf.matmul(reshape, fc1_weights) + fc1_biases) # トレーニング時はドロップアウトする,50% # ドロップアウトは使用するネットを毎バッチごとランダムで(今回では50%だけ)選んで学習させるとより最適化が進むってやつ if train: hidden = tf.nn.dropout(hidden, 0.5, seed=SEED) return tf.matmul(hidden, fc2_weights) + fc2_biases
画像から特徴を抽出しつつデータ量を減らし,それを最終的に縦に並べて普通の(linearな)ニューラルネットに突っ込んでる部分です。 ごくごく一般的な畳み込みネットだと思います。
ストライドが1ずつとか略しても問題ないこともしっかり書いてあるところがGoogleっぽい。 "tf.matmul(reshape, fc1_weights) + fc1_biases"の部分とか,実際の式に近い感じで見やすいですね。
そして,重み減衰やモメンタムなど,細かい部分の初期設定を以下で行います。
# Training computation: logits + cross-entropy loss. # logitsにモデルからの出力が返ってくる logits = model(train_data_node, True) # 結果を誤差関数に突っ込む loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits( logits, train_labels_node)) # 正規化,いわゆる重み減衰,これやっとくと効率よくなるよ regularizers = (tf.nn.l2_loss(fc1_weights) + tf.nn.l2_loss(fc1_biases) + tf.nn.l2_loss(fc2_weights) + tf.nn.l2_loss(fc2_biases)) # 正規化の強さパラメータを重みの二乗和にかけて,それを誤差関数に足す loss += 5e-4 * regularizers # バッチも毎回変わるからVariable扱い batch = tf.Variable(0) # Decay once per epoch, using an exponential schedule starting at 0.01. # 学習率の初期化,ある程度学習が進んだらより細かくモデルを形作っていくから学習量を減衰させるよ learning_rate = tf.train.exponential_decay( 0.01, # 学習率の初期設定 batch * BATCH_SIZE, # いまdatasetのいくつめか train_size, # 学習がどの程度進んだら学習量を減衰させるか 0.95, # 減衰させる量 staircase=True) # モメンタムの係数0.9で最適化する # モメンタムは,重みを修正する量に前回の修正量を加えるとより最適化されやすくなるってのらしい optimizer = tf.train.MomentumOptimizer(learning_rate, 0.9).minimize(loss, global_step=batch) # 学習時のデータをバッチごとに予測 train_prediction = tf.nn.softmax(logits) # 検証時とテストデータをモデルに突っ込んで予測 validation_prediction = tf.nn.softmax(model(validation_data_node)) test_prediction = tf.nn.softmax(model(test_data_node))
モデルから返って来た結果から,答えのラベルデータと比較して誤差を出しています。その後,重み減衰のために正規化を誤差に足しています。
その誤差をoptimizerで最小化することによってモデルを改善して学習しています。
ここまで来たらあとは実行するだけ。
Tensorflowは最初に初期設定を厳密に決めて,あとは実行するだけって仕組みなので実行部分のプログラムは非常に分かりやすいです。
以下が機械学習の実行部分。
# 計算するセッションを作るんだって,その後s.runで走らせるみたい with tf.Session() as s: # すべてのvariable部分を初期化 tf.initialize_all_variables().run() print('Initialized!') # Loop through training steps. # 学習データをループ回して学習します for step in xrange(num_epochs * train_size // BATCH_SIZE): # Compute the offset of the current minibatch in the data. # Note that we could use better randomization across epochs. # ephochsごとにより良いランダマイズの手法取れるよって書いてある? # numpyとかでパーミュテーションに渡した方が良くなるってこと? offset = (step * BATCH_SIZE) % (train_size - BATCH_SIZE) batch_data = train_data[offset:(offset + BATCH_SIZE), :, :, :] batch_labels = train_labels[offset:(offset + BATCH_SIZE)] # This dictionary maps the batch data (as a numpy array) to the # node in the graph is should be fed to. # バッチデータとラベルデータを辞書形式として入れ物に入れる feed_dict = {train_data_node: batch_data, train_labels_node: batch_labels} # 今までの設定を突っ込んで,機械学習を走らせる _, l, lr, predictions = s.run( [optimizer, loss, learning_rate, train_prediction], feed_dict=feed_dict) if step % 100 == 0: # 学習状況の表示 print('Epoch %.2f' % (float(step) * BATCH_SIZE / train_size)) print('Minibatch loss: %.3f, learning rate: %.6f' % (l, lr)) print('Minibatch error: %.1f%%' % error_rate(predictions, batch_labels)) print('Validation error: %.1f%%' % error_rate(validation_prediction.eval(), validation_labels)) sys.stdout.flush() # 結果の表示 test_error = error_rate(test_prediction.eval(), test_labels) print('Test error: %.1f%%' % test_error) if FLAGS.self_test: print('test_error', test_error) assert test_error == 0.0, 'expected 0.0 test_error, got %.2f' % ( test_error,)
最初に必要な定義を済ませているので,実際の学習部分の記述量がめちゃくちゃ少なく感じますね。
初学者なので間違っているところがあったら教えてもらえたらありがたいです。
で,結局Tensorflowってどうなの?
CaffeとかChainerとかを長らくやって来た友人的には,まだ早いっぽいです。情報が出そろってないので,様子見と言っていました。
現状公式サイトですらComing soonが結構ある状況なので,判断を下すには早すぎますよね。
自分は割とその友人を信用しているので,まぁ様子見したほうが良いのかなって感じです。
RubyのTensorflowが出たら飛びつくと思いますが,そうじゃなければChainerで良いかなって思います。
Tensorflowも自分の勉強に使いつつ,Chainerとどっちが良いのか見比べて行くつもりです。現状Chainerメイン。
メインって言えるほど使いこなせてないですけど笑
RubyのTensorflow出てほしいなあーー まぁ多分出なさそうですけどね...