Ruby 言語が好きだ
僕が初めて Ruby という言語に触れたのは、今の会社に入社して 3 ヶ月ほど経った 2015 年の 6 月ごろでした(当時 Ruby は Version 2.2 が最新だったかな)。それ以降現在に至るまで、特に制約がない限り日常の業務はほぼ Ruby で書いています。それくらい僕の性分にマッチしています。一方、同僚の中には Ruby が好きじゃない人も多いのですが、その背景に Rubyist 特有の Going My Way な感じ(?)に対する生理的な嫌悪感をやんわりと感じるので(感じてばっかだな)、この記事では「Rubyist は嫌いでも Ruby のことは嫌いにならないで欲しい」というささやかな願いを込めて、Ruby 言語が優れていると感じるところを紹介したいと思います。
- 文法がエレガントである✨
- 細かいところはかなり自由である(がゆえに状況に応じて可読性を追求できる)👕
- その割に言語仕様は非常にシンプルである💡
- よくやる処理が標準ライブラリ関数として大体用意されている🏪
Ruby すごい
「Ruby すごいな」って改めて思った象徴的なコードを紹介します。
p [*1..15] .permutation(5) .find{ |x| [*1..21] - (1..5).sum([]){ |n| (x + x).each_cons(n).map(&:sum) } == [] }
このコードは、社内の有志によって催されたプログラミングコンテスト「『ビリヤード問題』を最小の文字数で解く」の優勝コードです。Ruby2.3 で書かれています。『ビリヤード問題』とは、森博嗣さんの小説「笑わない数学者」の中で実際に出題された問題です。 bookclub.kodansha.co.jp
さて、では、もう一つ問題をだそう。五つのビリヤードの玉を真珠のネックレスのように、リングに繋げてみるとしよう。玉には、それぞれナンバが書かれている。さて、この五つの玉のうち、いくつとっても良いが、隣どうし連続したものしか取れないとしよう。一つでも、二つでも、五つでも全部でも良い。しかし、離れているものはとれない。この条件で取った玉のナンバを足し合わせて、1から21までのすべての数ができるようにしたい。さあ、どのナンバの玉を、どのように並べてネックレスを作れば良いかな?
短く書くコンテストなので、たいていのコードはめちゃくちゃ読みにくくなるのですが、上のコードはなんとなく何をやっているのか読み解けませんか?
- 次のものを標準出力する(
p
)、次のものとは- 1 から 15 の整数を並べた配列を作り(
[*1..15]
) - 配列から 5 つ取り出して並べた配列を列挙し(
.permutation(5)
) - その中から以下を満たすものを見つける(
find
)、その条件とは({}
)- 「1 から 21 の整数を並べた配列」から、(
[*1..21]
) - 「取り出す数 n を 1 から 5 まで変えながら(
(1..5)
) - 以下の処理を行ったものを繋ぎ合わせた(
sum([])
)結果」を取り除く(-
)、その処理とは({}
)- 列挙された配列 x を 2 つ繋げた新しい配列(
(x + x)
)に対して、 - 隣り合う n 個の合計を求めて、その合計値が並んだ配列を作る(
.each_cons(n).map(&:sum)
)
- 列挙された配列 x を 2 つ繋げた新しい配列(
- その結果が、空であること(
== []
)
- 「1 から 21 の整数を並べた配列」から、(
- 1 から 15 の整数を並べた配列を作り(
すごいポイント
- 要素を取り出して並べて列挙するようなニッチな関数が、列挙可能なオブジェクト全てに標準で備わっているのがすごい
- メソッドの返すオブジェクトに対してメソッドを呼ぶ、いわゆるメソッドチェインで書かれているのに全然読める!(個人差があります)
文法がエレガントである
エレガントって何? って話なのですが、ここでいうエレガントさは人間にとっての親しみやすさのような意味です。
例えば、同じ処理を 10 回繰り返したいようなとき、例えば C89 では
{ size_t i; for(i = 0; i < 10; ++i) { /* Do Something */ } }
- 何回実行したか数えるカウンター(
i
)を用意して - 繰り返し処理の中で Do Something して
- 繰り返し処理を実行するたびにカウンター(
++i
)を増やして - 繰り返し処理の中で、実行回数が 10 に達したら繰り返しをやめる
みたいなコードを、明示的に書かなければなりません。繰り返し処理の内容だけ書いて、あとはいい感じに繰り返し処理やってほしくない?
例えば Python なら
for _ in range(10): # Do Something
- 0 から 10 までの範囲オブジェクトから一つずつ値を取り出して
- 取り出す度に Do Something する
こんな風に書けますが、これもちょっと回りくどいです。
Ruby なら
10.times do # Do Something end
やりたいことをそのまま書いたようなコードです。めっちゃエレガント✨✨✨
「えーーカウンターの数値使って処理したいじゃん」ですって?
10.times do |loop_count| # Do Something with using loop_count end
こういうこともできます。(使わないなら書かなくても良い、というところも Ruby らしいです)
この違いは何なのか
上の例であげた C89 や Python との違いは何なのかというと、10 という数値をただのデータとして取り扱うか、何らかの機能を持ったクラスのオブジェクトとして取り扱うかというところです。C89 にはオブジェクト(クラス)という概念はありませんが、Python にはクラスがあります。しかし Python では 10 のクラスである int はあくまでも「データ」であり、オブジェクト間でやりとりされるものでしかありません。Ruby の場合、10 は Integer というクラスのオブジェクトで、このクラスは自身の量に応じて与えられた処理を繰り返す機能を持っています。
Ruby の世界では全てがオブジェクトなのです。なんてシンプル✨✨
長くなったので続きはまた後日。
前回の更新からなんと 4 年経ってしまった
こんにちは。音声認識の研究を再開したいなあと思ってブログを開設して、TensorFlow のチュートリアル解読メモを投稿してから 4 年もの歳月が経過してしまいました…
その間に、二度の引っ越しがあり、勤め先の会社では部署異動があり、生活の様式も大きく変わりました。また、当時はプログラマ 2 年生で右も左もわからない状態でしたが、今ではもう 6 年生になり、プログラマという職業もだいぶ身体に馴染んできました。
4 年前は社内に Deep Learning 経験者が 1 名(当時 1 年生)しかおらず、僕も統計的機械学習はやっていたものの Deep は全く分からない状態でしたが、この 4 年間で「学生時代に研究で使っていた」という若手がたくさん増えたので、後輩から最新の研究動向を教えて貰いつつ、自分でも重要そうな論文を読んだりしてゆるっと時代の流れを追いかけている、そんな今日この頃です。
この 4 年間で、興味の対象や経験値も大きく変わりました。多く書いたプログラミング言語は、多い順でいうと、
- C89
- Ruby2.3
- C++11
- Python3.5
の 4 強ですね。特に Ruby 言語は本当に自分の性に合っていて、大好きな言語です。Ruby の素晴らしさについて語るエントリーを書きたいと思っています。
TensorFlowのチュートリアルスクリプトを理解する(続編)
翌々調べてみると
tensorflow/example/tutorial/mnist/fully_connected_feed.py
ちゃんとしたチュートリアルが存在していた。
色々とラップした関数があったけど、
今は一通り処理を追い駆けたいので全部展開してみました。
from __future__ import absolute_import from __future__ import division from __future__ import print_function from six.moves import xrange import time import math import tensorflow as tf
モジュールをインポートしてます。
from tensorflow.examples.tutorials.mnist import input_data NUM_CLASSES = 10 IMAGE_SIZE = 28 IMAGE_PIXELS = IMAGE_SIZE * IMAGE_SIZE
MNISTのデータを使うので、ここら辺もインポートしてます。
flags = tf.app.flags flags.DEFINE_float('learning_rate', 0.01, 'Initial learning rate.') flags.DEFINE_integer('max_steps', 2000, 'Number of steps to run trainer.') flags.DEFINE_integer('hidden1', 128, 'Number of units in hidden layer 1.') flags.DEFINE_integer('hidden2', 32, 'Number of units in hidden layer 2.') flags.DEFINE_integer('batch_size', 100, 'Batch size. Must divide evenly into the dataset sizes.') flags.DEFINE_string('train_dir', 'data', 'Directory to put the training data.') flags.DEFINE_boolean('fake_data', False, 'If true, uses fake data for unit testing.') FLAGS = flags.FLAGS
基本的なパラメータをFLAGとしてセットしてます。
def do_eval(sess, eval_correct, images_placeholder, labels_placeholder, data_set): true_count = 0 # Counts the number of correct predictions. steps_per_epoch = data_set.num_examples // FLAGS.batch_size num_examples = steps_per_epoch * FLAGS.batch_size for step in xrange(steps_per_epoch): images_feed, labels_feed = data_set.next_batch(FLAGS.batch_size, FLAGS.fake_data) feed_dict = { images_placeholder: images_feed, labels_placeholder: labels_feed, } true_count += sess.run(eval_correct, feed_dict=feed_dict) precision = true_count / num_examples print(' Num examples: %d Num correct: %d Precision @ 1: %0.04f' % (num_examples, true_count, precision))
画像識別率を評価する関数ですね。
演算子「//」は切り捨て除算だそうです。
「FLAGS.batch_size」のような形式で、先ほど設定したパラメータを取り出せるっぽいです。
「data_set.next_batch」は、データセットから指定したサイズのDNNの入力信号と教師信号を取り出してタプルで返してくれる便利メソッドです。
あと本筋とは離れますがxrangeは遅延評価されるレンジオブジェクトとのこと(参考)。
def run_training(): data_sets = input_data.read_data_sets(FLAGS.train_dir, FLAGS.fake_data) with tf.Graph().as_default(): ### Generate placeholders for the images and labels. images_placeholder = tf.placeholder(tf.float32, shape=(FLAGS.batch_size, IMAGE_PIXELS)) labels_placeholder = tf.placeholder(tf.int32, shape=(FLAGS.batch_size))
訓練データのためのメモリを確保してますね。
imagesはDNNの入力ベクトル、labelsはDNNの教師信号のインデックス、
一度に食わせるデータの数はbatch_sizeです。
#### Build a Graph that computes predictions from the inference model. # Hidden 1 with tf.name_scope('hidden1'): weights = tf.Variable(tf.truncated_normal( [IMAGE_PIXELS, FLAGS.hidden1], stddev=1.0 / math.sqrt(float(IMAGE_PIXELS)) ), name='weights') biases = tf.Variable(tf.zeros([FLAGS.hidden1]), name='biases') hidden1 = tf.nn.relu(tf.matmul(images_placeholder, weights) + biases) # Hidden 2 with tf.name_scope('hidden2'): weights = tf.Variable(tf.truncated_normal( [FLAGS.hidden1, FLAGS.hidden2], stddev=1.0 / math.sqrt(float(FLAGS.hidden1)) ), name='weights') biases = tf.Variable(tf.zeros([FLAGS.hidden2]), name='biases') hidden2 = tf.nn.relu(tf.matmul(hidden1, weights) + biases)
ここで出力層以外のネットワークを構築してます。
tf.name_scope('hidden1')のwithブロックの中で定義したtf.Variableは、
'hidden1'というコンテキストの中でのみ有効ということみたいです。
あと、チュートリアルのレベルで活性化関数に「ReLU」が使われているとは驚きでした。
# Linear with tf.name_scope('softmax_linear'): weights = tf.Variable(tf.truncated_normal( [FLAGS.hidden2, NUM_CLASSES], stddev=1.0 / math.sqrt(float(FLAGS.hidden2)) ), name='weights') biases = tf.Variable(tf.zeros([NUM_CLASSES]), name='biases') logits = tf.matmul(hidden2, weights) + biases labels_placeholder = tf.to_int64(labels_placeholder) cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits( logits, labels_placeholder, name='xentropy' ) loss = tf.reduce_mean(cross_entropy, name='xentropy_mean')
なぜここでint64にキャストしてるのかはちょっと謎ですが、
グラフに損失を計算するOperationを追加しています。
ところでlogitsってなんだろうと思って調べてみると、
ロジスティック関数の逆関数だそうです。
活性化関数がReLUの場合でもロジットになるのだろうか…?
tf.nn.sparse_softmax_cross_entropy_with_logitsは、
logitsとlabelベクトル間のソフトマックス・クロスエントロピーを計算する関数ですね。
続きはまた今度。
### Add to the Graph the Ops that calculate and apply gradients. # Add a scalar summary for the snapshot loss. tf.scalar_summary(loss.op.name, loss) # Create the gradient descent optimizer with the given learning rate. optimizer = tf.train.GradientDescentOptimizer(FLAGS.learning_rate) # Create a variable to track the global step. global_step = tf.Variable(0, name='global_step', trainable=False) # Use the optimizer to apply the gradients that minimize the loss # (and also increment the global step counter) as a single training step. train_op = optimizer.minimize(loss, global_step=global_step) # Add the Op to compare the logits to the labels during evaluation. correct = tf.nn.in_top_k(logits, labels_placeholder, 1) eval_correct = tf.reduce_sum(tf.cast(correct, tf.int32)) # Build the summary operation based on the TF collection of Summaries. summary_op = tf.merge_all_summaries() # Add the variable initializer Op. init = tf.initialize_all_variables() # Create a saver for writing training checkpoints. saver = tf.train.Saver() # Create a session for running Ops on the Graph. sess = tf.Session() # Instantiate a SummaryWriter to output summaries and the Graph. summary_writer = tf.train.SummaryWriter(FLAGS.train_dir, sess.graph) # And then after everything is built: # Run the Op to initialize the variables. sess.run(init) # Start the training loop. for step in xrange(FLAGS.max_steps): start_time = time.time() # Fill a feed dictionary with the actual set of images and labels # for this particular training step. images_feed, labels_feed = data_sets.train.next_batch(FLAGS.batch_size, FLAGS.fake_data) feed_dict = { images_placeholder: images_feed, labels_placeholder: labels_feed, } # Run one step of the model. The return values are the activations # from the `train_op` (which is discarded) and the `loss` Op. To # inspect the values of your Ops or variables, you may include them # in the list passed to sess.run() and the value tensors will be # returned in the tuple from the call. _, loss_value = sess.run([train_op, loss], feed_dict=feed_dict) duration = time.time() - start_time # Write the summaries and print an overview fairly often. if step % 100 == 0: # Print status to stdout. print('Step %d: loss = %.2f (%.3f sec)' % (step, loss_value, duration)) # Update the events file. summary_str = sess.run(summary_op, feed_dict=feed_dict) summary_writer.add_summary(summary_str, step) summary_writer.flush() # Save a checkpoint and evaluate the model periodically. if (step + 1) % 1000 == 0 or (step + 1) == FLAGS.max_steps: saver.save(sess, FLAGS.train_dir, global_step=step) # Evaluate against the training set. print('Training Data Eval:') do_eval(sess, eval_correct, images_placeholder, labels_placeholder, data_sets.train) # Evaluate against the validation set. print('Validation Data Eval:') do_eval(sess, eval_correct, images_placeholder, labels_placeholder, data_sets.validation) # Evaluate against the test set. print('Test Data Eval:') do_eval(sess, eval_correct, images_placeholder, labels_placeholder, data_sets.test) def main(_): run_training() if __name__ == '__main__': tf.app.run()
TensorFlowのチュートリアルスクリプトを理解する
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
このread_data_setsメソッドの実装は、
tensorflow/examples/tutorials/mnist/input_data.py
の中でインポートされている
tensorflow/examples/tutorials/mnist/mnist.py
の中にいます(ややこしい)。mnistにはDataSetというサンプル用の便利クラスのオブジェクトが返ります。カレントディレクトリにフォルダを作ってMNISTデータの圧縮ファイルがダウンロードされるので注意。
import tensorflow as tf
x = tf.placeholder(tf.float32, shape=[None, 784])
y_ = tf.placeholder(tf.float32, shape=[None, 10])
W = tf.Variable(tf.zeros([784,10]))
b = tf.Variable(tf.zeros([10]))
ここで入力ベクトルxおよび教師ベクトルy_を格納する領域の確保と、DNNの重みベクトルとバイアスベクトルをセットしている模様。重み行列が1つしか無いということは、もしかして単層のネットワークなのか…?
init_operations = tf.initialize_all_variables()
with tf.Session() as sess:
sess.run(init_operations)
ここがよく分からないところ。セッションをwithブロックで開いて、Sessionクラスのrunを実行しています。initialize_all_variablesの戻り値は、全ての変数に対する(Initialize?)Operationのようなのですが、なぜrunを介して実行する必要があるのかがよく分からない…。
参照)
tensorflow/python/ops/variables.py
tensorflow/python/ops/control_flow_ops.py
tensorflow/python/framework/ops.py
y = tf.nn.softmax(tf.matmul(x,W) + b)
入力に重みを掛けてバイアスを足してソフトマックス取ったのがy。あれ、活性化関数は…?
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y), reduction_indices=[1]))
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)
for i in range(1000):
batch = mnist.train.next_batch(50)
train_step.run(feed_dict={x: batch[0], y_: batch[1]})
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
print(accuracy.eval(feed_dict={x: mnist.test.images, y_: mnist.test.labels}))
後半はなんとなく何をやっているのか分かりますが、相互情報量とかもう忘れてしまったなあ。
うーむ。他のチュートリアルも見てみよう。
TensorFlowの使い方がよく分からない
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
import tensorflow as tf
x = tf.placeholder(tf.float32, shape=[None, 784])
y_ = tf.placeholder(tf.float32, shape=[None, 10])
W = tf.Variable(tf.zeros([784,10]))
b = tf.Variable(tf.zeros([10]))
init_operations = tf.initialize_all_variables()
with tf.Session() as sess:
sess.run(init_operations)
y = tf.nn.softmax(tf.matmul(x,W) + b)
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y), reduction_indices=[1]))
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)
for i in range(1000):
batch = mnist.train.next_batch(50)
train_step.run(feed_dict={x: batch[0], y_: batch[1]})
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
print(accuracy.eval(feed_dict={x: mnist.test.images, y_: mnist.test.labels}))
このpythonスクリプトは、TensorFlowのチュートリアルプログラムを少し弄ったものです。パッケージへのパスが通ってれば動くはずです(パスを通すのに少し苦労した…)。スクリプトの詳細については次のポストへ続きます。
このページを見ながら、チュートリアルを動かしてライブラリの中を少し見てみたものの、見通しがまだイマイチ。なんとなく分かった気になっているのは、
- tensorflowモジュールを介してDNNのノードや重みパラメータをセットし、
ハンドル的なオブジェクトを受けとる - それらのパラメータに対する操作をするためにはSessionを開く必要がある
- 開いたSessionはデフォルトセッションとしてセットされ、
デフォルトセッションを介して操作できる
(むしろセッションを明示する方法はないのだろうか…そういう使い方は想定していないのかも)
この場合ネットワークの構造ってどうなるんだろう。
音声認識システム作るには何が必要だっけ
もう1年以上も音声研究の分野から遠ざかっていた上に、学生時代の研究も終盤はかなりニッチなところを掘り下げていたので、音声認識システム全体がどうなっていたのかをおさらいするところから始めなくてはいけません。
統計的音声認識システムは誰かが発した「音声」と「音声に関する統計的なモデル」から、その誰かが「何と言おうとしてそう発音したのか」を推定するシステムなんですけど、そこら辺の話はきっと誰かがまとめてくれていると思うし、現時点でこの記事は自分用のメモとしての機能しか期待していないので色々割愛して、具体的な手順をメモするだけにとどめておこうと思います。
とりあえず、システムの仕様は以下。最初は楽したいので学生時代の研究のセッティングにかなり寄せています。
いわゆるDNN-HMMハイブリッドアプローチというやつです。個人的には、僕が大学院を卒業する頃に話題になりつつあった、DNNの活性化関数にReLUを使うのを試してみたいです。
次は、このシステムを構築するのにどういう作業が必要か。
- (ラベル付き)音声コーパスを用意する
- 音素バイグラムの生起確率を計算する
- (物理)トライフォンをリストアップする
- 音声コーパスから音響特徴量を抽出する
- 各音響特徴量をトライフォンの各状態に割り当てる
(状態はとりあえず時間で3等分) - ルールベースクラスタリングにより状態を共有する
- DNNの学習データを用意するため、特徴量系列の強制アライメントを行う
…ために、各状態の出力確率モデルをGMMとしてパラメータを学習、
学習したモデルを元にHMMの強制アライメントを行いパラメータを再学習、
これを繰り返す(これってEMアルゴリズムって言うんですかね?) - 学習したGMM-HMMを用いて特徴量系列の強制アライメントを行う
- 音響特徴量と対応する状態ベクトル(?)を与えてDNNのパラメータを学習する
- 学習したDNNに評価データの音響特徴量を入力し、
出力をHMMの各状態の出力確率としてデコーディング
…って感じになると思うんですが、全てのプロセスを実装したことは無いので、どこかで躓くような気はしています。あ、トライフォン間の遷移確率には音素バイグラムを使います。
DNNの構築には話題のTensorFlowを使ってみようかなと思っています。TensorFlowのフロントエンドはPythonで実装されているようなのですが、Pythonはほとんど触ったことがないのでその勉強もしながらやる形になりそうです。
ディープラーニングをはじめよう
こんばんは、WEAPOMです。
2015年に某IT系の会社にプログラマとして入社して早1年。VisualStudio、Xcode、EclipseなどのIDEは使わず(使えず)コーディングはTeraPad。扱える言語はMatlabとC、あとWeb系(HTML/CSS/JS)くらいのものだった僕も、なんとか仕事ができるくらいになってきました。ちなみに僕が今一番好きなテキストエディタはVimです。
そんな僕ですが、プログラミングスキル向上の目的も兼ねて某かのアプリケーションを作りたいなと最近思ってまして。大学、大学院時代の研究テーマが「音声認識システムの精度向上のための音響特徴量抽出手法」だったので、「ナウいフレームワークやライブラリと、音声認識システムの知識を使って、何かしらアプリケーションを作る」というのを当面の目標としてやっていこうかなと思っております。