読者です 読者をやめる 読者になる 読者になる

cloverrose's blog

Python, Machine learning, Emacs, CI/CD, Webアプリなど

Spark版xgboostでRank学習できるようにPR投げた

機械学習で有名なxgboostというライブラリに先日PRがマージされた!嬉しい!

[jvm-packages] call setGroup for ranking task by cloverrose · Pull Request #2066 · dmlc/xgboost · GitHub

PRを送った経緯

  • 会社の業務で検索のランキングモデルを作っていて、xgboostへの移行を検討している。(現在進行形)
  • 会社にはHadoopクラスタがあり、ログはHadoop上に乗っている
  • なのでHadoop上でログの整形→学習が完結するとかなりクール
  • Hadoop上で動くxgboostはYARN版とSpark版がある
    • YARN版はHadoopクラスタにライブラリを追加しないといけないなどの理由でSpark版の方を採用
  • xgboost4j-sparkにはRank学習をサポートしていなかった
  • ただxgboost4j-scalaまでたどればsetGroupメソッドが用意されているので、Sparkからメソッドを呼ぶようにすればよいことがわかった

自分がコードの意図をちゃんと理解していない部分が何個かあったためレビューをたくさんしていただいた。

変更自体は9月くらいに実装して、機械学習のコンペで使っていたのですが、ついにPRを投げてそれがマージされたのは嬉しかった。

ゆゆ式 Advent Calendar 2016 17日目 AR縁ちゃんを作りました

f:id:cloverrose:20161218000239p:plain

ゆゆ式アドベントカレンダー2016の17日目です。

www.adventar.org

今年自分はARで縁ちゃんを発見するアプリ、「AR縁ちゃん」を作りました。

AR縁ちゃんとはカメラの画像をリアルタイムで解析して、縁ちゃんの目っぽいもの(コンセント)があったら、そこに縁ちゃんを描画するアプリです。

動画を見ていただくとわかりやすいかと思います。

youtu.be

実装中のコードはGithubにあげてあります。まだ改善したい箇所があるのでぼちぼち更新します。

github.com

コードが完成したら版権を考慮してなんらかの形で公開したいと思います。

昨日は

hayashida_2ndさんのゆずこたち三人と猫ちゃんのかわいいイラストでした。

偶然だけど僕の飼ってる猫と毛色が同じだった!

今年もかわいいイラストや面白いゲーム、考察、まとめなど0:00が楽しみな毎日でした。

自分も0:00に投稿したかったのですがギリギリになってしまいました m(_)m

かわいい縁ちゃんのコンセント目について

gifmagazine.net

コンセント縁ちゃんにはこんなかわいいイラストがあります。

ゆかりちゃんの目がコンセントっぽいところを集めた動画もあります。

www.nicovideo.jp

余談

Androidのカメラ画像の四角形を認識したい!と同期のエンジニアに相談したらOpenCVでできそうだよ、と下記のサイトを紹介してもらったのがスタートになります。

OpenCV shape detection - PyImageSearch

具体的にはコンセントを認識したいと話したら縁ちゃんの目だなと察した友達がいました。

以下は技術メモになります。OpenCVJavaで使うサンプルがあまりないので他の人の役に立つと幸いです。

続きを読む

Android OpenCV face-detection 動かすメモ

ブログを色々参考にしながら進めたが結構苦戦したのでまとめておく。

環境

手順

0. Android StudioOpenCV for Android を導入する

OpenCV for AndroidをAndroid Studioに導入するメモ - Qiita をなぞる

うまく動いた。

1. NDK使わないバージョンのOpenCV (15-puzzle) を試す

AndroidStudio2.0でOpenCV3.1(sample編) - プログラミング好きな脳の引き出しをなぞる

これはうまく動いた。

注意点

  • Android端末にOpenCV Managerをインストールしておくこと
  • build.gradleが複数ある
2. NDK使うバージョンのOpenCV (face-detection) を試す

AndroidStudio2.0でOpenCV3.1(sample with NDK編) - プログラミング好きな脳の引き出し をなぞる

一部漏れがあったので差分だけ補う。エラーが出たら以下の差分を試して解消を試みる。

差分1. NDKをインストールする

Android Studioを開いて、File>Project Structureで開いたWindowにDownload NDKみたいなのがある。

差分2. gradle.propetiesに以下を追加。

android.useDeprecatedNdk=true

差分3. Application.mkを編集

APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions
APP_ABI := armeabi-v7a arm64-v8a
APP_PLATFORM := android-8

arm64-v8aを追加した

参考:http://sarl-tokyo.com/wordpress/?p=553

差分4. ライブラリをコピーする

cp -r ~/Downloads/OpenCV-android-sdk/sdk/native/libs ~/Desktop/face-detection/openCVLibrary310/src/main/jniLibs

参考:java - android Static Initialization opencv 3.0 Cannot load library "opencv_java3" - Stack Overflow

まとめ

OpenCV for Android 3.x系じゃ動かなかったから2.4.11を試したという記事もあるが3.1でちゃんと動くことが確認できた

Gist的なメモ

openCVLibrary310/build.gradle

apply plugin: 'com.android.library'

android {
    compileSdkVersion 21
    buildToolsVersion "25.0.0"

    defaultConfig {
        minSdkVersion 21
        targetSdkVersion 21
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }
}

openCVSamplefacedetection/build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 21
    buildToolsVersion "25.0.0"

    defaultConfig {
        applicationId "org.opencv.samples.facedetect"
        minSdkVersion 21
        targetSdkVersion 21

        ndk {
            moduleName "detection_based_tracker"
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }

    sourceSets.main {
        jni.srcDirs = [] // This prevents the auto generation of Android.mk
        jniLibs.srcDir 'src/main/libs' // This is not necessary unless you have precompiled libraries in your project.
    }

    task buildNative(type: Exec, description: 'Compile JNI source via NDK') {
        def ndkDir = "/Users/rose/Library/Android/sdk/ndk-bundle"
        commandLine "$ndkDir/ndk-build",
                '-C', file('src/main/jni').absolutePath, // Change src/main/jni the relative path to your jni source
                '-j', Runtime.runtime.availableProcessors(),
                'all',
                'NDK_DEBUG=1'
    }

    task cleanNative(type: Exec, description: 'Clean JNI object files') {
        def ndkDir = "/Users/rose/Library/Android/sdk/ndk-bundle"
        commandLine "$ndkDir/ndk-build",
                '-C', file('src/main/jni').absolutePath, // Change src/main/jni the relative path to your jni source
                'clean'
    }

    clean.dependsOn 'cleanNative'

    tasks.withType(JavaCompile) {
        compileTask -> compileTask.dependsOn buildNative
    }
}

dependencies {
    compile project(':openCVLibrary310')
}

build.gradle

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.2'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

gradle.propeties

android.useDeprecatedNdk=true

local.properties

## This file is automatically generated by Android Studio.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Sun Dec 04 18:10:43 JST 2016
ndk.dir=/Users/rose/Library/Android/sdk/ndk-bundle
sdk.dir=/Users/rose/Library/Android/sdk

settings.gradle

include ':openCVLibrary310'
include ':openCVSamplefacedetection'

Ubuntu ServerでX serverを動かす手順メモ

AWSのEC2のUbuntu Server (Ubuntu Server 14.04 LTS (HVM), SSD Volume Type - ami-a21529cc) でX serverを動かす手順メモ

sudo apt-get update && sudo apt-get upgrade && sudo reboot
sudo apt-get install xorg
sudo apt-get install python-qt4
sudo xinit &
export DISPLAY=:0.0

xinit動かすと error setting MTRR (base = 0xf0000000, size = 0x00100000, type = 1) Invalid argument (22) のエラー出るけどPyQt4はちゃんと動作してた。今のところ問題なさそう。

PyQt4でJavaScriptでレンダリングしてるWebページをScraping! CookieとUserAgentも扱うよ

JavaScriptレンダリングしてるWebページをScrapingしたい!!!

自分の環境

  • Virtualbox (Host OS Windows 7 64bit)
  • Guest OS Ubuntu16.04.1 LTS Desktop 64bit
  • Python 2.7.12
  • PyQt4は sudo apt-get install python-qt4 でインストール

実現方法

調べたら素晴らしいBlogが見つかった。

Ultimate guide for scraping JavaScript rendered web pages | IMPYTHONIST

BlogではPyQt4のWebKitを使ってJavaScript部分もレンダリングしたHTMLを取得する方法を紹介している。

(HTMLのParseに関する話もあるが、HTMLさえ取得できればBeautifulSoup使えばいい)

このBlogはコメント欄も有用な情報がたくさんあって、同じことをする別のライブラリやプロジェクトを示唆している人もいた。

ただやりたいことに対して複雑なライブラリを使いたくなかったので、このBlogの方法で実現してみた。

Blogの通りに動かすとちゃんと http://pycoders.com/archive/スクレイピングできた!!

タスク

だいたいやりたいことの大枠はBlogのソースコードで実現できた。

あとは細かい部分を詰めていくだけ。やりたいところはこの2つ

  • Cookieを扱いたい
  • UserAgentを扱いたい

Cookieを扱いたい

Blogのコメント欄にもあるけどQNetworkCookieJarを使えばいける

実現方法の詳細はこのSOがとても参考になった。

python - Permanent cookies with QWebKit -- where to get the QNetworkAccessManager? - Stack Overflow

手順

  1. EditThisCookie などを使ってCookie情報をJSONファイルに保存しておく
  2. QNetworkCookieを作る
  3. QNetworkCookieのListを作る
  4. QNetworkCookieJarを作る
  5. QWebPageにQNetworkCookieJarをセット .networkAccessManager().setCookieJar(cookiejar)

関連クラスの仕様

UserAgentを扱いたい

このSOが参考になった。

python - Setting useragent in QWebView - Stack Overflow

自分のUserAgentはここでわかるっぽい

手順

  1. What's My User Agent? - User Agent & Browser Tools などでCookieを調べる
  2. .userAgentForUrl に このCookieを返す関数をセットする

最終結果

# -*- coding:utf-8 -*-
import datetime
import json
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from PyQt4.QtWebKit import *
from PyQt4.QtNetwork import *


# 偽装したいUserAgent
USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
COOKIE_FILE = "cookie.json"


def customuseragent(url):
    u"""
    どのurlでも同じUserAgentを返す
    """
    return USER_AGENT


def make_cookiejar(filename):
    u"""
    JSON形式でExportされたCookieを読み込みQNetworkCookieJarを作る
    """
    with open(filename) as f:
        data = json.load(f)

    cookies = []
    for item in data:
        cookie = QNetworkCookie(item['name'], item['value'])
        if 'domain' in item:
            cookie.setDomain(item['domain'])
        if 'expirationDate' in item:
            cookie.setExpirationDate(datetime.datetime.fromtimestamp(item['expirationDate']))
        if 'hostOnly' in item:
            cookie.setHttpOnly(item['hostOnly'])
        if 'path' in item:
            cookie.setPath(item['path'])
        if 'secure' in item:
            cookie.setSecure(item['secure'])
        cookies.append(cookie)

    cookiejar = QNetworkCookieJar()
    cookiejar.setAllCookies(cookies)
    return cookiejar


#Take this class for granted.Just use result of rendering.
class Render(QWebPage):
    def __init__(self, url):
        self.app = QApplication(sys.argv)
        QWebPage.__init__(self)
        self.loadFinished.connect(self._loadFinished)

        self.userAgentForUrl = customuseragent
        cookiejar = make_cookiejar(COOKIE_FILE)
        self.networkAccessManager().setCookieJar(cookiejar)

        self.mainFrame().load(QUrl(url))
        self.app.exec_()

    def _loadFinished(self, result):
        self.frame = self.mainFrame()
        self.app.quit()


def main(url, filename):
    r = Render(url)
    result = r.frame.toHtml().toUtf8()
    with open(filename, 'w') as f:
        f.write(result)


if __name__ == '__main__':
    URL = 'http://pycoders.com/archive/'
    FILENAME = 'result.html'
    main(URL, FILENAME)

補足

Qtの独自の型があるけど

  • QByteArrayってなってるところはPythonのstr型でOK
  • QDateTimeってなってるところはPythonのDatetime型でOK
  • frame.toHtml()の結果をtoUtf8()してあげないと日本語が文字化けで?になってしまう

感想

3年前くらいにMacPyQt動かそうとしてメッチャ苦労したけどUbuntuでは超簡単に動いた。

わりとすぐにいいBlogにたどり着くことができてよかった。

PyQtのリファレンス体裁が汚すぎる…

Raspberry Pi3 起動に苦戦

昨日Raspberry Pi3 ModelBが届いた

www.amazon.co.jp

NOBESのダウンロードを30分位待って、よっしゃ遊ぶぞって思って、電源を挿したらPWRの赤色LEDは点滅するけど、モニターには何も映らない。

ぐぐったら、Raspberry Pi3からは電源として5V, 2.5Aのものを使う必要があることを知った。

あわててAmazonで注文した。 www.amazon.co.jp

次の日、アダプターが届いたので、よっしゃ遊ぶぞって思って、電源を挿したら(以下略)

www.raspberrypi.org によると

The Raspberry Pi's bootloader, built into the GPU and non-updateable, only has support for reading from FAT filesystems (both FAT16 and FAT32), and is unable to boot from an exFAT filesystem. So if you want to use NOOBS on a card that is 64GB or larger, you need to reformat it as FAT32 first before copying the NOOBS files to it.

ということで僕のSDXC64GB exFATにフォーマットされてるから読み込めないっぽい。ひとっ走りSDカード買いに行かないと。

pywebhdfsにHAとFederationをサポートするPRがマージされた

WebHDFSについて

WebHDFShdfsコマンドではなくREST APIにhttpでアクセスできる便利なもの。

Hoop(httpfs)とwebhdfsの違い - たごもりすメモとかが図もあってわかりやすいと思う。

背景1 (hdfsコマンドへの不満)

MapReduceなどを使って解析を行って、その結果を可視化するとかを行っていると、スクリプトの中で頻繁にHDFSにアクセスすることになった。

そしてShellスクリプトが量産されるわけだが、自分はPythonスクリプトを書きたかった。

最初はsubprocessの中でhdfsコマンドを叩いていたが、レイテンシが結構あるし、lsコマンドの結果をパースしてファイル名を取得する必要があって微妙だった。

そんな話をしたらWebHDFSがあるよと教えてもらった。

背景2 (WebHDFSとの出会い)

WebHDFSに触ってみたら、レイテンシがすごく短いし、JSONが返ってくるので扱いやすい。心が踊った。

テンションが上ってHDFSのWebUIみたいなことができるシェルインタプリタを作った。 できる処理はls, cat, cd, pwdでread/get的なものだけにしていた。

その時自分でWebHDFSのREST APIのうすいラッパーを実装した。

これがわりと必要十分な機能は揃っていたためチームでもこのapi.pyが使われるようになった。

背景3 (自作ラッパーの限界)

自作ラッパーはシェルインタプリタ用に遊びで作っていたので、実業務で利用されるとカバーしているAPIが少なかった。

特に新規ファイル作成とかは、1回NameNodeにリクエストを投げて、返ってきたDataNodeのURIに対して再度ファイル内容とともにリクエストを投げるという形式で、自分で全部実装するのはめんどくさすぎた。

また認証周りもめんどくさくて、APIに渡す引数が増えていきそうだった。

そこでOSSPythonのWebHDFSラッパを探すことにした。

ライブラリ探し

PythonからHDFSを操作する - 偏った言語信者の垂れ流しに辿り着いた。

そこでは2つのライブラリが比較されていた。2013年の比較だが参考になった。

実際、PYPIを見てもWebHDFSライブラリはリリースが2014-01-20と古く、もうメンテナンスされていない気がした。

そこでpywebhdfsを使うことにした。しかしこのライブラリではHAとFederationがサポートされていなかった。

HAとFederationについて

HA(High Availability)とはHadoopのNameNodeが単一障害点だった欠点を解消するために、ActiveとStandbyという2つ以上のNameNodeを起動しておき、ActiveなNameNodeが落ちたらStandbyだったNameNodeが自動でActiveに切り替わるという仕組みで、実際の業務でHadoopを使うなら必須な機能。

Federationは複数のNameNodeがメタ情報(ディレクトリ構造とか)を分担してメモリに保持する仕組み。貧弱なメモリのNameNodeで大規模なクラスタを管理しようとすると全てのメタ情報が載り切らない。この時/data/以下はNameNode1で、/user/以下はNameNode2でそれ以外はNameNode3でという感じで分割できる仕組み。

どちらも自分たちのプロジェクトでは使っている。

現状pywebhdfsは1つのNameNodeのホスト名を渡すので、Activeが落ちた時に、APIはずっとエラーを返すようになってしまい、それを外側で検知して切り替えたりしないといけない。

HAとFederationのサポートの実装

いろいろ考えたが、pywebhdfsのIssueにもHAのサポートが欲しいという声が上がっていたので、PRを投げることにした。

しばらくの土日はいろんなパターンでHA/Federationをサポートする仕組みを実装した。

最終的には、パスにマッチする正規表現とそれに該当するNameNodeのリスト(HAならActiveとStandby)を順序付きの辞書でAPIコンストラクタに渡すことにした。

順序付きの辞書にしたのは、それ以外のパス(.*)とは最後にマッチさせたいからだ。

実装にあたって気をつけたのは、HA/Federationを利用していないユーザーには今までと同じインターフェースを保つこと。

そしてPRを投げた。

support federation and HA by cloverrose · Pull Request #22 · pywebhdfs/pywebhdfs · GitHub

もらったレビューを反映して、今朝マージされた :)

1ヶ月待っていたので感慨深かった。

f:id:cloverrose:20150718113204p:plain

余談

HDFSのシェルインタプリタは自作のapi.pyではなく、自分のパッチが当たったpywebhdfsを使って実装し直して現在も気に入って使っています。

Pythonインタプリタを作るときにはreadlineをラップしたcmdという便利なものがあります。

TABで補完とかが簡単に実装できるし、入力読み取り→実行のループも勝手にやってくれます。初めて知ったけど、今後また何か作るときに使っていきたい。