2017年12月23日土曜日

【第1回 Cpaw AI competition】乱入レポート

この記事は、さくらインターネット Advent Calendar 2017 23日目の記事です。
22日目の記事: 日本初の競技機械学習大会!【第1回 Cpaw AI competition】開催レポート
24日目の記事: (追記予定)

こんにちわ。 hasegaw こと、さくらインターネット 高火力コンピューティングの長谷川です。

今回は、22日の記事にて紹介された第1回Cpaw AI Conpetitionについて、続きってわけでもないのですが、続編をお届けします。同コンペティションを開催したCpawの伊東さんは高火力コンピューティングにチームでアルバイトしてくれていまして、ある日のこと。

「AIコンペティションを開催したくて、場所を探しています」
「え?ウチ使えるよ」

ということで、さくらインターネットの西新宿セミナールームで開催することになりました。当日は台風が接近していたこともあり、参加者が少なくなってしまうのではないかと心配したのですが、悪天候のなか歩留まり6割というのは最近の機械学習系のイベントとしては良い意味で裏切られた気持ちでした。



先の記事のとおり、 Cpaw AI Competition では、さくらのクラウド 12コア・96GBメモリのインスタンス(Ubuntu 16.04)を用いて、与えられたデータセットに対する分類器を生成、分類結果を Web サイトから提出します。提出したスコアはそのまま 1% == 1ポイントとして加算されます。つまり65.2% の分類器をつくれば 65 ポイント獲得できるわけですね。

競技時間は 13:30-18:00 のおよそ 4.5 時間。さくらインターネットの社員(わたし含めて2名)は会場運営支援で現地にいたのですが、 4 時間ボケっとしているのは、ちょっと暇です。と、いうわけで。

「伊東くん、私にも問題解かせてよ」

とお願いして、他の参加者と同じように問題を解いてみることにしました。下記は、実際に問題を解いた手順を再現したものですが、当時書いたコードは既に失ってしまったので、書き直した「再現コード」で解説します。


■ 芸能人ブログ記事の分類問題→絶望

最初に手を出したのは芸能人ブログの分類問題(entertainer_blog)です。データセットは Cpaw オリジナルのもので(本稿執筆のために Cpaw から提供を受けていますが)、 Cpaw の皆さんがあらかじめスクレイプ、1記事1テキストファイルに変換されたものです。ブログエントリの教師データを用いて分類器を訓練し、クラスラベルが与えられていないブログエントリを分類することが目標です。

entertainer_blog/train/
entertainer_blog/train/mamoru_miyano
entertainer_blog/train/mamoru_miyano/10.txt
entertainer_blog/train/mamoru_miyano/1000.txt
entertainer_blog/train/mamoru_miyano/1005.txt
entertainer_blog/train/mamoru_miyano/1021.txt
entertainer_blog/train/sano_hinako
entertainer_blog/train/sano_hinako/1004.txt
entertainer_blog/train/sano_hinako/1008.txt
entertainer_blog/train/sano_hinako/1012.txt
entertainer_blog/train/sano_hinako/1014.txt
entertainer_blog/train/shinozaki_ai
entertainer_blog/train/shinozaki_ai/1003.txt
entertainer_blog/train/shinozaki_ai/1006.txt
entertainer_blog/train/shinozaki_ai/1010.txt
entertainer_blog/train/shinozaki_ai/1018.txt
entertainer_blog/train/shinozaki_ai/102.txt
....

データファイルの内容の例


訓練用データセットは下記のとおりの構成でした。

訓練用データ(ラベルあり)
461 shinozaki_ai
509 uesaka_sumire
397 mamoru_miyano
210 sano_hinako
合計 1577

テスト用データ(ラベルなし;スコア獲得用)
合計 1972

データセットを数ファイル覗いてみると、これらの芸能人が誰なのか正直全然わからないですけども、クラスによっては特定の締め方があったり、顔文字の傾向があったり、ということがわかりました。今回は、このデータセットを手軽に処理する目的で Jubatus を用いることにしました。 Jubatus の場合 ngram を用いた特徴量への変換などが標準で備わっていますので、今回のようなデータセットなら、簡単に「とりあえず」食わせられるからです。

wget http://download.jubat.us/apt/ubuntu/xenial/binary/pool/jubatus_1.0.6-1_amd64.deb
apt install ./jubatus_1.0.6-1_amd64.deb
pip3 install --user jubatus


今回は分類問題なので Jubatus のなかでも jubaclassifier を使用します。以下のようなファイルを作成します。 n=6 にしましたが、ここには強い理由はありません(とりあえずノリで決めました)。既存のサンプルを参考に、設定ファイルに指定して jubaclassifier を起動します。

jubaclassifier はポート 9199 で待ち受けており、 Python で記述する学習&予測プログラムから利用します。


jubaclassifier --configpath jubaclassifier.json


そして訓練用データセットがクラスラベルごとにディレクトリで分けられていること、データセットサイズに対してインスタンスのメモリ容量が十分なことから、下記関数でデータセットを読み込むことにしました。

次のコードが Jubatus を用いてデータセットを分類し、Cpaw AI Competition での提出用CSVフォーマットで stdout に出力するソースコードです。

ここまで準備ができたら、実際にデータセットをロードして、 jubaclassifier を用いてオンライン学習し、テストデータを分類します。ここで、標準出力に表示される結果の例を示します。

0.txt,uesaka_sumire
1.txt,shinozaki_ai
2.txt,uesaka_sumire
3.txt,uesaka_sumire
4.txt,mamoru_miyano
5.txt,mamoru_miyano
6.txt,shinozaki_ai
7.txt,shinozaki_ai
8.txt,uesaka_sumire
9.txt,shinozaki_ai

このデータをコンペティションの採点システムに提出すると、得られていた精度はなんと 30% 以下!!4クラス分類だと完全ランダムでも25%前後になるはずですから、いっそのこと全部 shinozaki_ai ラベルで提出した場合のスコアと大差がない。まったく駄目!!

正直、高火力コンピューティングをお客様に紹介して回ったり、プレゼンして回ったりする立場の私がコレなのは本当に恥ずかしてお客様などに顔向けできないなぁ、と思い、動揺し、死にたくなりました。いっそのこと Cpaw に貸してあげた採点サーバーに root で乗り込んでいって DROP DATABASE クエリを叩き込んでしまいたいという気持ちになりましたが、個人的な感情でそんなことをするわけにもいきません。実は、当日は ngram の n=2 で精度が得られなかったため順番に増やしていって 6 まで上げたのですが、これだけで、精度はあがりません。

一旦あきらめて、とりあえず他のデータセットに取り組むことにします。

■ 泣きそうになりながら、マルウェアURLの分類問題

次に取り組んだのは、マルウェアURLの分類問題です。下記URLで取得できる、URLデータセットが先と同じように1サンプル1テキストファイルとして用意されており、オリジナルデータからラベルが取り除かれたテストデータが正常なURLか、グレイゾーンなURLかを判断することが目標です。

Malware URL
http://malwareurls.joxeankoret.com/

データセットのディレクトリ/ファイル構造
train/normal/
train/normal//1007.txt
train/normal//101.txt
train/normal//1011.txt
train/normal//1013.txt
train/malicious/
train/malicious//0.txt
train/malicious//1.txt
train/malicious//10.txt
train/malicious//100.txt
...

あるデータファイルの中身の例


テキストファイルの中が芸能人のブログ記事からURLにかわり、ラベルが変わっただけで、基本的にデータフォーマットは一緒です。

というわけで、遠慮なく先程の dataset.py と classifier.py に突っ込んでみます。 URL は基本的にアルファベットや数字の集合で、N の値はそこそこ大きい必要があるでしょう。なので N はとりあえず、引き続き 6 ぐらいにしておきます。

先程起動した jubaclassifier は芸能人ブログの学習データを持っているので、ここでいったん同プロセスを再起動しておきます(もっといい方法があるのでしょうか?使い慣れているわけではないので、よくわかっていません)。malware_urls.py を実行した結果がこちら。

採点サーバに送ると、またもや精度は 60% にも及びません(2クラス分類なので全く駄目な結果ということです)。もうだめだー!!!逃げたい!!!

そう思っていたところで、 Cpaw 運営側から採点システムの不具合があり、正確な採点がされていないので,データを再提出するようにという指示。先の芸能人ブログ分類、マルウェアURL分類の結果を再提出した結果、こんな感じになりました。

entertainer_blog: 精度 94.6%
malware_urls: 精度 83.7%

なんだぁ〜〜〜〜。ちゃんと分類できてるんじゃん!本当に焦って泣きそうでしたよ!!よかった。

■ 調子に乗りながら、マルウェア分析の分類問題

次はマルウェア分析問題にとりかかっていきます。このデータセットは下記サイトから入手できる MalwareTrainingSets が先と同様のフォーマット(ただしJSON)に変換されているものです。

Free Malware Training Datasets for Machine Learning
https://github.com/marcoramilli/MalwareTrainingSets

データセットのディレクトリ/ファイル構造
train/APT1/1046.json
train/APT1/1051.json
train/APT1/1063.json
train/APT1/1065.json
train/Crypto/0.json
train/Crypto/100.json
train/Crypto/1000.json
train/Crypto/1002.json
train/Locker/1004.json
train/Locker/1012.json
train/Locker/1037.json
train/Locker/1048.json
train/Zeus/1.json
train/Zeus/10.json
train/Zeus/1001.json

train/Zeus/1005.json
...

あるデータファイルの中身の例


Cpawからデータセットのフォーマットが事実上先程と同じなので、遠慮なく分類器にかけていきます。もしかすると、当日では、JSONを解析して特定の値のみを評価したかもしれません(よく憶えていません)。

得られた結果は以下のとおり。
malware_analysis: 精度 99.6%

実はこのデータセットはある見方をするとルールベースで 100% を達成することができました。しかし、それでも追加のコードを書いたりせずに機械学習で殴っておけば 99% の精度が出せる点が、機械学習のメリットだと思います。

さてここまで entertainer_blog 94.6%, malware_urls 83.7%, malware_analysis 99.6% の精度が出せたので、私のスコアは 94+83+99 で合計 276 となり、二番手グループに大躍進です!! (会場スポンサーですけど)


■ ニューラルネットワークで"ファッションMNI?T"

さてさて!

気分がよくなってきたところで、次のデータセットに取り組んでいきましょう。ここまではテキストのデータセットばかりを選んできましたが、残っているデータセットは「ファッションMNI?T」と「古代文字」のふたつ。どちらかを先に試すか?と思ったら、まずは前者かな、と思いました。 MNIST といえば 28x28 の 10 クラス分類問題で、名前から想像するに同じような要領でいけるのではないか、と思い、データセットを覗いてみることに。



これもボーナス問題ですね!オリジナルの Fashion MNIST データセットでは黒塗りマスクがないのですが、今回のは黒塗りマスクのイタズラがされているデータでした。

これはイケる、という確信を得ます。MNIST同様、28x28のモノクロサイズの10クラス分類。データセットをひと目みるとわかりますが、全ての画像に同一パターンの黒塗りがかかっています。畳み込みニューラルネットワークを使用するとこの黒塗りエッジ部分などが邪魔をするかもしれませんが、このデータを単純に線形結合で入力する場合は、オリジナルのファッション MNIST を 14x14 にリサイズされた状態である、と解釈できるのではないでしょうか。もう、これは 14x14 の MNIST にしか、私には見えません。

この問題をどう解くか、ですが Cpaw AI Competition のレギュレーションでは、イベントで提供された 12コア96GB を使いまくっていいので(畳み込んでいるとあっという間に時間が過ぎてしまいそうですが)線形結合で殴るぐらいの計算リソースと残り時間はあります。また、畳み込むと上にかぶっているマスクが邪魔になるだろうでしょうから、結果が予測できません。なので、線形結合モデルでいくことにします。

とりあえず、Chainerフレームワークをインストールします。

pip3 install chainer

データセットはこれまで通り各クラスがディレクトリで分けられて提供されていましたが、テキストファイルではなく画像なので、データセット読み込みを実装します。普段は cv2.imread() のほうが使い慣れているのですが、cv2モジュールがすぐインポートできなかったので、今回はPILを使ってみました。

以下が Chainer を使ってニューラルネットワークを訓練し、テストデータを予測するためのコードの例です。

このコードを実行し、得られた結果を採点サーバに送信したところ 81% の精度が得られ、私は大会で暫定首位に躍り出ました。(会場スポンサーですけど)


■ これは引っ掛けデータセットな「古代文字」

さて、残りは古代文字データセットです。このデータセットは下記URLから入手できる古代文字画像が、一文字ごと、画像ファイルに分割された状態で提供されました。

文学オープンデータ共同利用センター / Center for Open Data in the Humanities
http://codh.rois.ac.jp/



正直、このデータセットはちらっと見るだけでも 13:30-18:00 のコンペティションの中で扱うデータセットとしては非常に重いです。ファッションMNISTのようにある程度の正規化がされているわけでもなく、さすがに畳み込みニューラルネットワークなどのアプローチで、きちんと回さないと、精度が出ないのではないか?という気がします。

とはいえ、この段階で残り1時間ほど。チャレンジできるところまでチャレンジしましょう。(会場スポンサーですけど)


■ 突然のカーテンコール

……と思ったところで、運営からその時点の暫定順位の発表があり、運営の伊東さんから下記の案内がありました。

「あ、ちなみにこの hasegaw って人は会場スタッフなので表彰対象外です」

というわけで、私の AI Competition は散って終わったのでした。



なお、今回のブログ記事執筆のために作成しなおしたコードでは、下記のスコアが得られました。

entertainer_blog: 精度 94.6%
malware_urls: 精度 83.7%
malware_analysis: 精度 99.6%
fashion_mnist: 精度 81.9%

小数点以下切り捨ての合計スコアは357ですので、当日このスコアが出せていたら、トップに踊りでて Google Home mini を勝ち取っていたかもしれません。😭

後日自分で購入した Google Home mini

もちろん、今回はたまたまデータセットと私のとった手段がうまくはまってスコアが伸びただけしかありません。会場には、スコアを伸ばすことを重きにおくのではなく、フルスクラッチで機械学習アルゴリズムを実装するところからスコアを積み上げていた方もいらっしゃるようで、本当に頭がさがります。

次の機会には、もし会場スポンサーの立場であっても、きちんと参加登録してコンペティションに臨んでみたいと思います。


■ 終わりに

機械学習って、聞いたことがあるけど未だ試していなかったり、もしくはちょっと勉強してみたという方も増えてきているのではないでしょうか。

私も、もともと「機械学習やディープラーニングってすごいなぁ」と思いながら、 IkaLog というスプラトゥーンの画像認識ソフトを作っていく過程で、意図せず、各種機械学習アルゴリズムのパワーを体感して、そこから Coursera の Machine Learning コースを受講した程度の知識しかありません。1年ほどスプラトゥーンの画像データに取り組んだ経験がある以外は、機械学習について凡人レベル、もしくはそこに毛が生えた程度の知識しかありません。

しかし機械学習は、ちょっとモチベーションがあると、その状況なりの使い道や楽しみ方が見つかります。また、今回のような問題で7割8割の精度が出せれば、業務上でも自分の仕事の手間を削減したり、ちょっとした業務効率化で効いたりするものです。さくらインターネットでも、機械学習未経験のデータセンター現地スタッフが独学で Jubatus を用いたオペレーション業務の効率化にも取り組んだりもしています。

Cpaw では、今後とも同様のオンラインイベントやオフラインイベントを開催すると伺っています。機会があれば、このようなイベントにぜひとも参加してみてください。

■ おまけ

過去に参加したアドベントカレンダー

2017年9月4日月曜日

Python 向け OpenCV 3.3.0 (Win32) ビルド済みライブラリのビルドオプション

下記サイトから入手可能な opencv_python-3.3.0-cp36-cp36m-win32.whl の cv2.getBuildInformation() の結果。



General configuration for OpenCV 3.3.0 =====================================

  Platform:
    Timestamp:                   2017-08-05T02:46:14Z
    Host:                        Windows 10.0.15063 AMD64
    CMake:                       3.7.2
    CMake generator:             Visual Studio 14 2015
    CMake build tool:            C:/Program Files (x86)/MSBuild/14.0/bin/MSBuild.exe
    MSVC:                        1900

  CPU/HW features:
    Baseline:                    SSE SSE2
      requested:                 SSE2
      required:                  SSE2
    Dispatched code generation:  SSE4_1 SSE4_2 FP16 AVX AVX2
      requested:                 SSE4_1 SSE4_2 AVX FP16 AVX2
      SSE4_1 (2 files):          + SSE3 SSSE3 SSE4_1
      SSE4_2 (1 files):          + SSE3 SSSE3 SSE4_1 POPCNT SSE4_2
      FP16 (1 files):            + SSE3 SSSE3 SSE4_1 POPCNT SSE4_2 FP16 AVX
      AVX (5 files):             + SSE3 SSSE3 SSE4_1 POPCNT SSE4_2 AVX
      AVX2 (7 files):            + SSE3 SSSE3 SSE4_1 POPCNT SSE4_2 FP16 FMA3 AVX AVX2

  C/C++:
    Built as dynamic libs?:      YES
    C++11:                       YES
    C++ Compiler:                C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/cl.exe  (ver 19.0.24218.1)
    C++ flags (Release):         /DWIN32 /D_WINDOWS /W4 /GR  /EHa  /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE /D _SCL_SECURE_NO_WARNINGS /Gy /bigobj /Oi /fp:fast   /arch:SSE /arch:SSE2 /wd4251 /wd4324 /wd4275 /wd4589 /MP12  /MD /O2 /Ob2 /DNDEBUG  /Zi
    C++ flags (Debug):           /DWIN32 /D_WINDOWS /W4 /GR  /EHa  /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE /D _SCL_SECURE_NO_WARNINGS /Gy /bigobj /Oi /fp:fast   /arch:SSE /arch:SSE2 /wd4251 /wd4324 /wd4275 /wd4589 /MP12  /D_DEBUG /MDd /Zi /Ob0 /Od /RTC1
    C Compiler:                  C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/cl.exe
    C flags (Release):           /DWIN32 /D_WINDOWS /W3  /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE /D _SCL_SECURE_NO_WARNINGS /Gy /bigobj /Oi /fp:fast   /arch:SSE /arch:SSE2   /MP12  /MD /O2 /Ob2 /DNDEBUG  /Zi
    C flags (Debug):             /DWIN32 /D_WINDOWS /W3  /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE /D _SCL_SECURE_NO_WARNINGS /Gy /bigobj /Oi /fp:fast   /arch:SSE /arch:SSE2   /MP12  /D_DEBUG /MDd /Zi /Ob0 /Od /RTC1
    Linker flags (Release):      /machine:X86  /INCREMENTAL:NO  /debug
    Linker flags (Debug):        /machine:X86  /debug /INCREMENTAL
    ccache:                      NO
    Precompiled headers:         YES
    Extra dependencies:          comctl32 gdi32 ole32 setupapi ws2_32 vfw32 glu32 opengl32
    3rdparty dependencies:

  OpenCV modules:
    To be built:                 core flann imgproc ml objdetect photo video dnn imgcodecs shape videoio highgui superres ts features2d calib3d stitching videostab python3
    Disabled:                    python2 world
    Disabled by dependency:      -
    Unavailable:                 cudaarithm cudabgsegm cudacodec cudafeatures2d cudafilters cudaimgproc cudalegacy cudaobjdetect cudaoptflow cudastereo cudawarping cudev java viz

  Windows RT support:            NO

  GUI:
    QT:                          NO
    Win32 UI:                    YES
    OpenGL support:              YES (glu32 opengl32)
    VTK support:                 NO

  Media I/O:
    ZLib:                        build (ver 1.2.8)
    JPEG:                        build (ver 90)
    WEBP:                        build (ver encoder: 0x020e)
    PNG:                         build (ver 1.6.24)
    TIFF:                        build (ver 42 - 4.0.2)
    JPEG 2000:                   build (ver 1.900.1)
    OpenEXR:                     build (ver 1.7.1)
    GDAL:                        NO
    GDCM:                        NO

  Video I/O:
    Video for Windows:           YES
    DC1394 1.x:                  NO
    DC1394 2.x:                  NO
    FFMPEG:                      YES (prebuilt binaries)
      avcodec:                   YES (ver 57.89.100)
      avformat:                  YES (ver 57.71.100)
      avutil:                    YES (ver 55.58.100)
      swscale:                   YES (ver 4.6.100)
      avresample:                YES (ver 3.5.0)
    GStreamer:                   NO
    OpenNI:                      NO
    OpenNI PrimeSensor Modules:  NO
    OpenNI2:                     NO
    PvAPI:                       NO
    GigEVisionSDK:               NO
    DirectShow:                  YES
    Media Foundation:            NO
    XIMEA:                       NO
    Intel PerC:                  NO

  Parallel framework:            Concurrency

  Trace:                         YES (with Intel ITT)

  Other third-party libraries:
    Use Intel IPP:               2017.0.2 [2017.0.2]
               at:               D:/Build/OpenCV/opencv-3.3.0-vc14-x32-py36/3rdparty/ippicv/ippicv_win
    Use Intel IPP IW:            prebuilt binaries (2017.0.2)
    Use Intel IPP Async:         NO
    Use Lapack:                  NO
    Use Eigen:                   NO
    Use Cuda:                    NO
    Use OpenCL:                  NO
    Use OpenVX:                  NO
    Use custom HAL:              NO

  Python 2:
    Interpreter:                 X:/Python27/python.exe (ver 2.7.13)

  Python 3:
    Interpreter:                 X:/Python36-x32/python.exe (ver 3.6.2)
    Libraries:                   X:/Python36-x32/libs/Python36.lib (ver 3.6.2)
    numpy:                       X:/Python36-x32/lib/site-packages/numpy/core/include (ver 1.13.1)
    packages path:               X:/Python36-x32/Lib/site-packages

  Python (for build):            X:/Python27/python.exe

  Java:
    ant:                         NO
    JNI:                         X:/Java18/include X:/Java18/include/win32 X:/Java18/include
    Java wrappers:               NO
    Java tests:                  NO

  Matlab:                        NO

  Documentation:
    Doxygen:                     NO

  Tests and samples:
    Tests:                       YES
    Performance tests:           YES
    C/C++ Examples:              NO

  Install path:                  D:/Build/OpenCV/opencv-3.3.0-vc14-x32-py36/install

  cvconfig.h is in:              D:/Build/OpenCV/opencv-3.3.0-vc14-x32-py36
-----------------------------------------------------------------

2017年2月8日水曜日

ブートイメージからカーネル、rootfs、configを取り出す

image.ub ファイルからカーネル、rootfs、/proc/configを取り出す

SDSoC 2016.3に含まれる ZYBO 向け Software Platform からカーネル、 rootfs、/proc/config を取り出したかったので取り出した。

そもそも image.ub ってなんだ


ARMで使えるブートローダu-bootが扱うイメージフォーマットらしい。

$ mkimage -l image.ub
FIT description: PetaLinux arm uImage with single Linux kernel and FDT blob
Created:         Fri Nov 18 04:07:34 2016
 Image 0 (kernel@1)
  Description:  PetaLinux Kernel
  Created:      Fri Nov 18 04:07:34 2016
  Type:         Kernel Image
  Compression:  gzip compressed
  Data Size:    8747883 Bytes = 8542.85 kB = 8.34 MB
  Architecture: ARM
  OS:           Linux
  Load Address: 0x00008000
  Entry Point:  0x00008000
  Hash algo:    crc32
  Hash value:   252654ca
 Image 1 (fdt@1)
  Description:  Flattened Device Tree blob
  Created:      Fri Nov 18 04:07:34 2016
  Type:         Flat Device Tree
  Compression:  uncompressed
  Data Size:    13962 Bytes = 13.63 kB = 0.01 MB
  Architecture: ARM
  Hash algo:    crc32
  Hash value:   66629950
 Default Configuration: 'conf@1'
 Configuration 0 (conf@1)
  Description:  PetaLinux Boot Linux kernel with FDT blob
  Kernel:       kernel@1
  FDT:          fdt@1


カーネルを取り出す


まずこのイメージからカーネルを取り出す。 mkimage -l の結果から gzip compressed であることがわかっているので、 magic を頼りに探すと 240バイト目から gzip データがあることがわかる。 mkimage の結果からファイルは 8747883 バイトであることがわかる。

$ grep -P -a -b  --only-matching $'\x1F\x8B\x08' image.ub
240:

なので

$ dd if=image.ub of=linux.bin.gz bs=1 skip=240 count=8747883
8747883+0 records in
8747883+0 records out
8747883 bytes (8.7 MB, 8.3 MiB) copied, 6.91697 s, 1.3 MB/s

$ file linux.bin.gz
linux.bin.gz: gzip compressed data, was "linux.bin",
  last modified: Thu Nov 17 19:07:33 2016, max compression, from Unix


カーネルの中から gzip データを取り出す


さらにこのアーカイブの中にrootfsが含まれている。cpioフォーマットで入っているかと思い、cpioフォーマットのヘッダである"070707"をgrepしたら発見... と思ったがハズレだった。これはcpioアーカイブじゃなくてcpioアーカイブを探すプログラムが参照している文字列っぽい。ということは...

$ grep -P -a -b  --only-matching $'\x1F\x8B\x08' linux.bin
6278680:
8588960:
9923536:

gzip のヘッダらしきものが3つ見つかった。まずひとつ目は...

$ dd if=linux.bin skip=6278680 bs=1 | gzip -dc > hoge

gzip: stdin: decompression OK, trailing garbage ignored

toor@gpusv:~/zynq7/extract_initramfs$ file hoge
hoge: Linux make config build file, ASCII text

ASCIIだと!?

$ head hoge
#
# Automatically generated file; DO NOT EDIT.
# Linux/arm 4.6.0 Kernel Configuration
#
CONFIG_ARM=y
CONFIG_ARM_HAS_SG_CHAIN=y
CONFIG_MIGHT_HAVE_PCI=y
CONFIG_SYS_SUPPORTS_APM_EMULATION=y
CONFIG_HAVE_PROC_CPU=y
CONFIG_NO_IOPORT_MAP=y

...config.gz だった。まあ、ついでなので保存。

$ mv hoge config


では次。

$ dd if=linux.bin skip=8588960 bs=1 | gzip -dc > hoge
gzip: stdin: decompression OK, trailing garbage ignored

toor@gpusv:~/zynq7/extract_initramfs$ file hoge
hoge: ASCII cpio archive (SVR4 with no CRC)

toor@gpusv:~/zynq7/extract_initramfs$ cat hoge | cpio -it | head
cpio: Substituting `.' for empty member name
.
dev
dev/pts
etc
etc/opkg
etc/opkg/arch
etc/rpm
etc/rpm/sysinfo
etc/rpm-postinsts
etc/network

今回は間違いなくinitrdイメージだ。

$ mv hoge initrd


目的な達成したけど、3つめはなんじゃらほい。
dd if=linux.bin skip=9923536 bs=1 | gzip -dc > hoge
gzip: stdin has flags 0xce -- not supported


これは有効なgzipじゃなかった。"070707" 同様に gzip をデコードするルーチンが参照している定数かも。

2017年1月19日木曜日

Oculus Rift CV1 に近視レンズを組み込んだ


Oculus Rift CV1 用のレンズアダプターで近視レンズを組み込んだので、久々にブログにまとめようかと。

皆さんの VR ライフ満喫していますか?

実は... 私はあまり満喫できていませんでした。眼鏡男子の私にとって Oculus Rift CV1 の利用それほど手軽ではなかったからです。

普段、私は乱視矯正・近視矯正の両方が入った眼鏡をかけています。例外的に、冬にスノーボードを楽しむ時にはワンデータイプのコンタクトレンズを使っています。このため、 Oculus Rift CV1 で何か VR コンテンツを楽しむにあたっても、 Lucky's Tale 程度のコンテンツなら裸眼で問題ないとはいえ、 EVE: Valkyrie にょうな UI であったり、細かい文字を読み取らないといけないようなコンテンツではコンタクトレンズを装着しないと楽しめない状況でした。また、私が利用しているのはワンデータイプのコンタクトレンズです。ふらっと Oculus Rift で遊ぶときにワンデーコンタクトを消費するとなると、心理的なハードルはさらに高くなります。

CV1 用レンズアダプターを入手!

そんなことを思っていたら、Oculus Rift CV1 ヘッドセットにメガネ用のレンズを装着し近視矯正を可能にするアダプターの 3D モデルデータを見つけました。

このレンズアダプターは Oculus Rift CV1 にぴったりとはまるようにデザインされていて、レンズには、一般的な眼鏡用のレンズが利用できます。当初より公開されている、オリジナル版のアダプターでは、Zenni Optical の #450021 用に作られた、直径 40mm の円型の眼鏡用レンズを装着できます。

これをどこかでプリントしてもらうか、プリントしてもらうなら送料ケチるために複数オーダーしたほうがいいかなとか色々考えつつ、手を出せていなかったのですが……。もう半年以上前に見つけたこのレンズアダプターですが、最近 3D プリンターを購入したという知人がこのアダプターをプリントしてくれたので、ついに実物を手に入れることができました。


写真は見やすい白い樹脂でプリントしたものを掲載しましたが、実際に CV1 に取り付けたものは黒色のアダプターです。

レンズを入手するための方法は?

そして、次のチャレンジは、「眼鏡のレンズをどうするか」です。今回入手したアダプターは先のとおり Zenni Optical #450021 と互換性があります。このアダプターに対応するレンズを入手する方法として、まずは下記2パターンが考えられます。


  1. 同フレームのみ購入して、レンズは日本で作る
  2. 同フレームとあわせて、レンズも購入する

ずっと悩んでいたのですが、今回は結局、後者「レンズを購入したらフレームもついてくる」という形で入手することにしました。

はじめての通販眼鏡オーダー

次の問題は、いままで眼鏡購入は街のメガネ屋に依頼して作ってもらっていて、通販で眼鏡のレンズなんて買ったことがない、という点です。このため、過去に購入したコンタクトレンズや眼鏡のパラメータを参考にオーダーすることになります。

コンタクトレンズの度数でオーケー

先述のとおり、私は近視矯正用のコンタクトレンズを使用しているので、そのコンタクトレンズを見ることで自分が必要とする近視矯正のパラメータがわかります(というか何百回もコンタクトレンズを使っているので、近視の強さはもう憶えてしまっています)。手元のコンタクトレンズによる近視矯正のパラメータは下記のとおりでした。

  • 左目 -3.25
  • 右目 -2.75

私のコンタクトレンズは乱視矯正タイプではありません。コンタクトレンズは乱視矯正が入るとモノが分厚くなります。個人差がありますが、私の場合、装着感が悪くなり、外れやすくなります。コンタクトレンズを購入するにあたって色々相談したり試した結果、「乱視矯正をせず、近視矯正だけにする」ことにしました(ただし近視矯正を多少強めに入れています)。過去12年ほど、ずっと同じ度数でワンデータイプのコンタクトレンズを使っておりこのコンタクトレンズで遠くにいる仲間の姿、顔や、トラック、遠くの看板など、アウトドアで必要十分な視界が得られることがわかっており、CV1でも問題ありません。このコンタクトレンズと同じ度数の眼鏡レンズを CV1 に入れれば問題ないはずです。

IPDは、眼球と眼球の距離

IPDは左右の眼球の中心の距離を示すパラメータのようです。コンタクトレンズは眼球自体に装着するので IPD の値がわからないのですが、 Ocuus Rift CV1 のセッティングでは 62mm がもっともはっきりして見えるほか、定規で自分の眼球の距離を測ってみてもおよそ 65mm ぐらいでした。「指定できるIPDの値がジャストでない場合は狭い方の値を選べ」ということですし、 CV1 で 62mm という数値が出ていたので、今回は 62mm で作成することにしました。

眼鏡購入時の伝票やカルテからも、必要となる値はわかるはず

眼鏡を持っていても自分の左右の目の矯正パラメータを憶えていることはそうそう無いと思いますが、眼鏡を作ってもらったときの資料が残っていれば、そこに矯正のパラメータが書かれているので、確認することができます。もしくは、眼鏡を作った眼科やお店に問い合わせれば、カルテが残っているはずです。

私がオーダーしたレンズのパラメータは?

先述の近視矯正のみであれば、Zenni Optical の眼鏡オーダー時に下記のパラメータでオーダーすれば十分です。CYL、Axisは乱視矯正の場合のパラメータですので、近視矯正だけの場合はゼロで問題ないのだと思われます。



一般的に眼鏡のレンズは矯正がつよいほどレンズが分厚くなり、高価なレンズほど値段が安くなります。つまり安価なレンズで矯正を強くするとレンズが太くなります。今回、私は 1.57 Mid-Index Single Vision のレンズでオーダーしました。

Oculus Rift CV1への装着感、見え方など含めて、私の度数ではこの設定で特に問題はなさそうです。近視が私より大幅に強い人は、薄い購入レンズを選ぶか悩まれるかもしれません。

コーティングは下記のみにしました。どうせ VR 用ですし。
  • Anti‐Reflection Coating
  • Standard anti‐reflective coating

価格は?

レンズ(+フレーム)代金は14.90ドル、日本への送料が9.95ドルでした。クレジットカードへの最終的な請求金額は下記のとおり、3000円弱でした。



配達されてきた

オーダーしたレンズ(+フレーム)は香港で製造され、日本に向けて出荷されたようです。封筒に眼鏡ケースを入れて送られてきました。(写真は後でとったもので、レンズは取り外し済みです)



ちなみに常用している日本ブランドの眼鏡と比べると、ノーズパッドのは幅に大きな差があります。日本人は鼻が低いですもんねー。



出来上がった眼鏡の状態で見え方に問題がないかを確認しても、とくに問題はありませんでした。

重要! レンズを外す前に

レンズを外す前に、コーティングを痛めないようなメンディングテープや付箋をレンズにはり、上下がわかるようにしておくことをお勧めします。
  • 右目レンズか、左目レンズか
  • レンズのどの部分が上か(円形レンズなので方向がわからなくなる)
特に、乱視矯正レンズだと方向は絶対に大事なので、やはり方向がわからなくならないよう、特に注意してください。

実は、私の場合この眼鏡に対応できる精密ドライバーが手元になかったので、普段お世話になっている眼鏡ショップでレンズを外してもらいました(店舗によっては嫌がられるでしょうから、これが当たり前だとは思わないでください!)。この際、どちらが上であったか等を示す印をつけないまま外してもらった都合、購入時点でどの部分が上だったかは分からなくなっており、反省点です。(IPDがおかしいことになっているかも)

CV1への装着

まず、眼鏡のレンズ2枚を装着します。レンズアダプターに必要以上の力をかけないように注意しながらはめこみます。一度はめ込んでしまえば、アダプターからレンズがすぐにポロッと落ちることはないでしょう。少しゆるく感じるかもしれませんが、CV1に組み込めば、もっと安定します。

CV1の顔に当たるスポンジ部分は、その樹脂フレームと一緒に、Rift本体から分離できます。液晶側(スポンジがついていない方)からレンズアダプタの下のレール部分をはめこみます。

Thingverse に投稿されている写真より

そして、同パーツとレンズアダプタをOculus Rift CV1本体にはめ込めば取り付け完了です。

利用感

映像の見え方としてはバッチリです。

普段利用している眼鏡を外さないといけないのはまだまだ不便ではあるのですが、それでもコンタクトレンズなしで CV1 を装着すれば VR 内で表示された小さな文字まできちんと読めるので大変便利です。実はまだ Touch をオーダーしていないのですが、今後 Touch を入手するのが楽しみになりました。

まとめ

普段から視力矯正が必要な人が VR ヘッドセットをかぶるのはちょっと大変です。でも、 CV1 に今回紹介したレンズアダプター、そして安く作れるレンズを組み合わせることで、 VR ライフがより身近なものになります。

私のような、眼鏡を常用しており、結果として Oculus Rift CV1 から遠ざかってしまっているオーナーの方もいるかと思います。そんな方には本アダプターがとても役にたつかとと思います。