7rpn’s blog: うわああああな日常

好きなことをつらつらと。AIとかで面白いことをしたい。

名大で一番リア充が入る学部はどこだ? Project OxfordのEmotion APIを試す

突然ですが,名古屋大学一番リア充が多く入る学部ってどこだと思います?経済?教育?

というわけで,今日は名大で一番リア充が入る学部を調べてみました。

どうやって調べる?

名大に在学している学生全員「あなたはリア充ですか?」 って聞いて,統計取ればいけるはず...

ただそれは現実的にはキビシい,っていうか無理。

というわけで,Project Oxfordを試してみました。

Project Oxfordとは?

Microsoftが出しているAPI群で,写真に写っている顔から性別や感情などの認識ができます。 一時期流行った写真から年齢を表示するサービスもこれ。

そのAPI群の一つである,感情認識をするためのEmotion APIというものを使いました。 顔を自動で判別して,そこから感情を測定する事が可能です。(happiness 80%, sadness 10%...みたいな)

f:id:s7rpn:20151202232802p:plain:w300

技術的にはopencvとCNNでいけるかな?

というわけで感情測定

名大の一学年全員の顔を感情測定に突っ込みました。
リソースは入学記念アルバム2014から。写真使ってしまった人ごめんなさい。笑

f:id:s7rpn:20151202233641j:plain:w300

結果から一番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のみのグラフをとってみる。

f:id:s7rpn:20151202233100p:plain:w600

...名大で一番リア充が入る学部は文学部だった。

最下位は法学部。


私ですか?もうすぐ潰れる予定の情文です。
こんどは卒業アルバムでやりますかね。


関連:
全国の道の傾斜マップを作った。 - 7rpn’s blog: うわああああな日常

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出てほしいなあーー まぁ多分出なさそうですけどね...


関連:
名大で一番リア充が入る学部はどこだ? Project OxfordのEmotion APIを試す

Googleが出した囲碁ソフト「AlphaGo」の論文を翻訳して解説してみる。

誰も知らない夜景の名所へ。岩屋堂公園-岩巣山展望台を歩く

愛知県で今一番Hotな紅葉狩りスポットと言われている,岩屋堂公園へ行ってきました。 もちろん愛用のGM1を持って。

岩屋堂公園はYahooで"愛知県 紅葉"と検索すると二番目に出てくるくらいなので,愛知県の人には有名なんですかね。
ただ団体客が多すぎだと思います。今回は一人で行ったのですが超肩身が狭かった。次行くときは誰かと行こうと思いました。

瀬戸のかなり山奥に位置していて,歩きで来ている人は多分皆無だと思われます。ほぼ車で来ている人ばかりで,数名が自転車やバイクって感じ。
行った19日時点で紅葉はかなりピーク過ぎている感がありました。今後紅葉を見たいと思ったら京都とか南に行くしかないのか笑

とりあえず,一人旅にしては楽しかったのでざっとハイライトでも書いていきたいと思います。

紅葉

f:id:s7rpn:20151124011004j:plain:w600

紅葉が綺麗。
ただ結構散っていました。先日の雨にやられたんですかね。

でも今日ここへ来た理由は紅葉狩りだけではないんですよね。なのでちょっとくらい紅葉が散っていても気にしない事にしました。
ちなみにライトアップ後にも紅葉の写真を取ったので,あとからまた出てきます。

他の見どころはどこかなって探していると,滝が見られるらしいので滝へ。
何も感想がない。滝。

f:id:s7rpn:20151124010411j:plain:w600

展望台への道のり

実は今日岩屋堂公園へ来たのは,紅葉が見たかったってのもありますが,一番の目的は夕焼けを見に来たんです。 なんとなく夕焼けが綺麗になりそうな空だったので。

というわけで,本日のメインとなるであろう展望台へ。
”滝側からの道は初心者によろしくない,入り口側を勧める”みたいな看板があったけど気にせず進みます。足場がめちゃくちゃ悪いけど頑張るしかない。
展望台へ向かってずんずん進みます。 f:id:s7rpn:20151124011625j:plain:w600

f:id:s7rpn:20151124011729j:plain:w600

途中で遭難したのかと勘違いするレベルで道が崩れてたりしていました。
これよりもっと道っぽくない山をいくらでも登ったことがあるので,まぁ最初はいけるっしょって感じでした。 ただ辺りが暗くなって来ると余裕もなくなり少し不安げに。
結構これやばいっすよと思いながら登ってました。真っ暗になったら帰れる自信はないので。

f:id:s7rpn:20151124011812j:plain:w600
予想が当たって夕焼けがめっちゃ綺麗そうな予感。
ただ展望台にはまだ辿り着きません。

f:id:s7rpn:20151124012541j:plain:w600
上ってたらすごい場所に出ました。木を掴みながら岩を上ると一気に視界が開けたんです。
視界が270度くらい開けてて,山が低い位置ずーっとに広がっているっていう。すごい感動。 もちろん岩から落ちたらただじゃ済まない感じです。
写真じゃよく分からないけどマジで高ぶりました。

f:id:s7rpn:20151124014058j:plain

そしてその場所から見る夕焼け。やばい。ここまで来た甲斐がありました。
展望台へ急ぎます。

そして,展望台へ

やっと念願の展望台につきました。
夕焼けが綺麗で,時間が経つのも忘れてぼーっと夕焼けに見入ってました。
展望台には自分以外誰もいなくて,心行くまで景色を独り占めできます。笑

というわけで,今日一番の景色 はこれですね。
これまで色々な夜景の名所に行ってきましたが,ここまで感動できる風景はなかなかないと思います。
瀬戸から名古屋まで遠くに広がる夜景,山の先には綺麗な夕焼け,眼下には紅葉のライトアップ。 f:id:s7rpn:20151124013438j:plain 最高じゃないですか。

山下り

ただ自分は大きな過ちを犯しました。
夕焼けと夜景を楽しむってことは,もう日没直前ってことです。てことは,真っ暗な山道を下って行かければならないわけです。
正直,真っ暗な山道を自分は下れるか怪しいと思いました。遭難まっしぐらかもしれません。
ライトはiPhoneのフラッシュだけです。しかもさっき先日熊が出没したとかいう看板もありました。

基本夜に山を下るときは来た道を戻るのが鉄則で,通った事のない道をいきなり通るのは危険だと考えられてます。たださっき来た道を下るのはちょっとつらい感じかもしれない。
もう一つの道が舗装されている事を祈って,登って来た道とは違う道で下山する事にしました。

山を駆け下りる

まったく舗装されていない!!酷い道だ
f:id:s7rpn:20151124013754j:plain:w300
無我夢中で駆け下りました。一寸先は闇なので速度出しすぎると落ちて死ぬ
途中岩登ったりした気がするんだけどあれ正しい道なんだよね?

明かりが見えたときはとても安心しました。

夜の山道は危険だ

山道を降りきったら,こんな看板が立っていました。
f:id:s7rpn:20151124015229j:plain:w600
いや聞いてないし。向こうの道から登ったんですけど。

でもこんな看板が立ってるってことは,よっぽど無謀な試みをする人以外はあの綺麗な夜景が見れないんだなぁと思うと,ちょっともったいない気もします。

ただ,正直熟練者じゃないと真っ暗な山道を歩くのはやめた方が良いと思います。
一歩間違えたら誰も助けにこない山道で遭難とかありそうだし,次からは一人で夜の山道を歩く事は避けたいですね。
それでももし行くとしたらiPhoneのライトじゃかなりで心細いので,良いライトを持って行きましょう。笑

ライトアップ

降りた先はどこだここ?って感じでしたが,ちゃんと岩屋堂公園の中に戻って来れていました。 紅葉のライトアップが行われていて,とても綺麗でしたね。

f:id:s7rpn:20151124024120j:plain

f:id:s7rpn:20151124015839j:plain

f:id:s7rpn:20151124022158j:plain

岩屋堂公園名物の橋。ここの景色の写真を大砲みたいなレンズ付けたカメラガチ勢が撮りまくってました。
自分もほぼ同じアングルですが,この写真結構好きです。偶然縦にぶれてしまい,それがなんとなくノスタルジック。

いい日でした。

RubyのTwitter gemを使ってTwitterのタイムラインを取得する。もっとたくさん。

前回タイムラインを可能な限り昔に遡って取得しようとしたのですが,twitterの仕様かそれほど昔まで遡れませんでした。
タイムラインが遡れないのなら,followerのつぶやきを一人ずつ遡って後から統合すれば超昔まで遡れるタイムラインになるんじゃね?って思って組みなおすことにしました。

require "twitter"

#とりあえず雛形
client = Twitter::REST::Client.new do |config|
    config.consumer_key = "hogeeeeeeeeeeeeeee"
    config.consumer_secret = "hugaaaaaaaaaaaaaaaaaa"
    config.access_token =  "fooooooooooooooooo"
    config.access_token_secret = "piyooooooooooooo"
end

#friend一人ずつ,遡れるまでtweetを取得
client.friend_ids("#{あなたのscreen_name}").each_slice(100).each do |slice|
    client.users(slice).each do |friend|
        bef_id,max_id = -1, client.user_timeline(friend.screen_name).first.id
        loop do
            client.user_timeline(friend.screen_name,{max_id: max_id,count: 200}).each do |tweet|
                puts tweet.user.name
                puts tweet.user.screen_name
                puts tweet.full_text
                print "Fav: #{tweet.favorite_count}, Retweet: #{tweet.retweet_count}\n"
                max_id = tweet.id unless tweet.retweeted?
            end
            sleep 60
            break if max_id == bef_id
            bef_if = max_id
        end
    end
end

一日走らせ続けて100ユーザー分くらいを取得できます。体感で数年分は遡れてるはず。もしかしたら全部いけてるかな?
sleep 60より短くしても大丈夫な気もするけどチキンなので試してないです。笑

それでは,RubyでよいTwitterライフを!

GM1のキットレンズを分解した。

LUMIX G VARIO 12-32mm/F3.5-5.6がぶっ壊れました
私のミラーレスの愛機,GM1に付属のキットレンズです。

まあ自分が壊したんですけれどね。
飲み会で写真撮ろうとして,カメラを棚に置いてセルフタイマーをセットした次の瞬間,手を滑らせてカメラが床へ転落
一瞬で酔いも醒めました。"がっちゃん"という派手な音とともに,曲がって収納できなくなったレンズ。 ズーム部分が一切回せなくなったわけです。

電気屋に持って行ったら修理費用は一万5000円らしい。高すぎ。自分で直してやれ,ってことで分解してみました。
結論から言うと,クソ難しいです。小型化を突き詰めたレンズなので,内部もものすごく精密でした。 分解でドライヤー(≠ドライバー)なんて初めて使ったから。

結局,沈胴機構を動かせるようにしないとすべてばらすことができなく(どうしようもない壊れかたしてた),直せずに元に戻しました。

f:id:s7rpn:20151112211023j:plain:w600
今見ても酷い壊れ方ですね。

分解レポート

必要な道具

  • ドライバー
  • ドライヤー
  • ラジオペンチ
  • 0.3mmのシャープペン

早速分解の詳しい手順を書いて行こうと思います。分解系の記事書くのは初めてかつ手順がうろ覚えなので分かりにくいかもしれませんが,まぁ大目に見てあげてください。

1. 金属部分の抑えと裏蓋を外す

ドライバーを使って,裏蓋部分の黒い場所と銀色の場所を外しましょう。
普通にネジを外せば難なく外せます。 f:id:s7rpn:20151112212815j:plain:w600

2. 後玉部分の基盤をバラす

ケーブルを止めている部分のネジを外して,ほかに押さえているネジを全部外しましょう。 f:id:s7rpn:20151112214002j:plain:w600

f:id:s7rpn:20151112214445j:plain:w600

3. ドライヤーを使って,ローレット加工されている金属部分を外す

このレンズのローレット加工部には,熱可逆性の接着剤が使われています。(つまり熱で粘着力を失う接着剤) ドライヤーでゆっくりローレット加工されている部分を暖めましょう。完全に暖めたら外すことができます。
黒い部分に触ると手が汚れるので注意しましょう。笑
これから先にやった方がいいかもしれない。

価格コムとか見てるとこの外装部分が外れるっていう初期不良が流行っているみたいですが,おおかた暑い車の中に放置したとかうっかり暖めてしまったっていうのが原因だと思ってます。

f:id:s7rpn:20151112211800j:plain:w600

4. 現れたネジを外す

接着剤で金属部分を外すとネジが隠れているので,そのネジを外しましょう。これで後ろ玉部分一式が外れる準備が整いました。

5 .上部レンズ部分を外す

ケーブルが基盤にピンで接続されていますが,これをラジオペンチ(もしくはピンセット)などで外しましょう。 あとはネジを外して,上部のレンズ部分(いわゆる後玉部分)一式が外れます。 f:id:s7rpn:20151112214542j:plain:w600

6 .沈胴させてレンズ部分を取り外す

んだと思います,多分…

これで一通り分解が終了すると思います。手振れ補正部分は今回触りませんでしたが,かなり精密っぽそうなので分解したら即戻すの不可能,とかになりそう。笑
自分は沈胴機構自体が死んでたので,この先へ進めませんでした。 だれかレベルの高い分解師さんいたらこの先よろしくお願いします!笑

あと戻すときにケーブルをピンに接続するために,細い棒が必要です。 持ってなかったので0.3mmのシャープペンで代用しました。
今思うと縫い針とかでやれば良かったかもしれない。

接着剤部分はまたドライヤーで暖め直して,くっつけてあげればおっけー。
価格コムとかではテープでとめるとか接着剤でつけるとかいう方法が流行ってますが,その辺は好みですかね。
自分は取り返しのつかない事はしたくないので接着剤は怖いです。笑

分解難易度は?

半端じゃない難易度だと思います。PSPの10倍は難しかった。iPad Airと同じくらいかな。いやそれは言い過ぎか。

まぁ誰も分解していないものなので,試行錯誤が必要な分,難しく感じたってのはあるかもしれません。

このレンズにまつわるあれこれ

高級感あふれる金属デザイン

分解して分かったんですが,外観の金属部分はすべて(熱可逆の)接着剤でとめてあるだけです。

高級感にあふれる金属外装を採用したデザイン

このレンズのパナソニック公式の売り文句です。接着剤でとめた高級感()

まぁ小さいだけあって中身はすごく詰まってますね。
レンズなのに演算回路とか必要なのかと思ったり。

レンズの写りごごち

レンズの筒部分が曲がってるだけあって,すごい絵が撮れます(泣)。中心にだけフォーカス可能で,周辺はかなりのボケ。

f:id:s7rpn:20151112215006j:plain:w600
作例はこんな感じ。取りたい被写体を浮かび上がらせるのに良いかなぁ。
誰か5000円くらいでこのレンズ買ってください。笑

壊れていないこのレンズの写りは多くレビューがある通りです。
可もなく不可もないって感じで,コンパクトさだけを突き詰めたレンズですね。 写りはミラーレスのクオリティは確保していますよって感じ。
もう少し解像するといいなぁとも思うけどこの小ささなら無い物ねだりかな。

キットレンズで取った作例を他の記事でアップしているのでぜひ見ていってください。

まぁ直せないと分かったことですし,アマゾンでレンズ買います。 Leica 15mmにするか同じレンズのままにするか超迷い中。

RubyのTwitter gemを使ってTwitterのタイムラインを取得する。たくさん。

Twitter gemを使って自分のタイムラインを遡れるところまで全部取得したいなーと思った。

まぁググっても良い記事がヒットしなかったんですけどね。 検索して出て来たやつのほとんどがタイムラインを20件だけ取得して満足してる記事だったっていう

client.home_timeline.each do |tweet|
    puts tweet.full_text
    puts "Fav: #{tweet.favorite_count}, Retweet: #{tweet.retweet_count}"
end

みんなこれ。うける
自分はグーグルを使ったサーチ能力が低いんですかね。

俺はもっと過去にまで遡ってタイムラインを取得したいんだよ!!!!
というわけでドンピシャ記事が見つからなかったのでブログに書き残しておきます。

実装

とりあえず雛形。お約束。

require "twitter"

client = Twitter::REST::Client.new do |config|
  config.consumer_key = "hoooooooooo"
  config.consumer_secret = "huuuuuuuu"
  config.access_token = "heeeeeeeee"
  config.access_token_secret = "haaaaaaaaaaaa"
end

ここまではみんな一緒。

たくさん取得したい場合,client.home_timelineに引数を与えて色々やるみたい。
ていうわけで,永遠にタイムラインを取得し続けたかったので以下のような感じで書きました。

max_id = client.home_timeline.first.id
hoge.times do
    client.home_timeline(max_id: max_id,count: 200).each do |tweet|
        puts tweet.user.name
        puts tweet.full_text
        puts "Fav: #{tweet.favorite_count}, Retweet: #{tweet.retweet_count}"
        max_id = tweet.id unless tweet.retweeted?
    end
    sleep 60
end

風の噂でタイムライン取得の制限が15分で15回って聞いていたので,一応sleep 60してあります。
hogeは好きな数字で。5.timesなら5*200でtweetが1000件返ってきます。

max_idでタイムラインを読み込み始めるツイートを指定して,順に昔へ遡って行く感じ。

まぁノリで書いたので適当なところ調節してあげてください。
ていうか見つけられなかっただけでもっと良い方法もありそうな気がします。

追記:
この手法だとTwitterの仕様なのか数週間程度しか遡れません。Timelineってそんな遡れない物なのか。
ユーザーごとに個別にツイートを読んでいく方がたくさん読めそうなので,プログラム自体を書き直す予定です。

=> 数年分遡れるように書き直しました!
RubyのTwitter gemを使ってTwitterのタイムラインを取得する。もっとたくさん。

プログレスバーを簡単に。each_with_animというRubyのgemを作りました。

RubyCUI上でループ時のアニメーションが楽に作れる,"each_with_anim"というgemを作りました!
require "each_with_anim"するだけでArrayとかHashとかにeach_with_animメソッドが追加されます。
ループが終了する残り時間も推測してくれる機能付き。 Rubyのプログラムを書くとき,これがあるとちょっと便利。

f:id:s7rpn:20151104115536g:plain

使い方

each_with_animはeachと同じように使えます。
eachと同じ振る舞いをしつつ,ループの進行状況を表すアニメーションが追加されます。

require "each_with_anim"

array = ["huga", "hoge", "foo", ... ]
array.each_with_anim do |elem|
  #something
end

f:id:s7rpn:20151104115536g:plain

with_indexとかもeachと同じように使えます。
あとeach_with_animationと書いてもeach_with_animと同じ働きをします。

array = (1..3000).to_a
array.each_with_animation.with_index do |elem,i|
  #something
end

同様に,引数を取ってアニメーションを変えることができます。今後増やして行く予定。

list = %w(hage hoge foo) * 100
list.each_with_anim(1) do |elem|
  #something
end

f:id:s7rpn:20151104112443g:plain

インストール方法

$ gem install each_with_anim

で引っ張って来て,

require "each_with_anim"

requireするだけ。
あとは好きなようにHashでもArrayでも.each_with_animしちゃってください。

現状の問題

each_with_animに渡すブロック内でprintすると表示がバグります。
解決策を絶賛探し中。

list = %w(hage hoge foo) * 10
list.each_with_anim do |elem|
    puts "piyoo"
end

Rubyプログレスバー系で一番見た目が美しく,簡単で便利なものを目指しました。
初めてライブラリ作ったのでまぁお手柔らかにお願いします笑

時間がかかるようなeach処理のとき,例えばActiveRecordとかで大量のデータを書き込むときとか,これがあるとちょっと便利だと思います。

License

The gem is available as open source under the terms of the MIT License.

自由に使ってください。