Stimulator

機械学習とか好きな技術話とかエンジニア的な話とかを書く

MacへのJupyter導入からextensionと設定メモ

- はじめに -

業務PCがWinからMacになりまして、Jupyter notebookしたいので自分の設定とextensionの導入までやったメモ。

あとChrome拡張使ってCSSを書き換えている話とか。


最初に参考資料を示しておくと、どのネット記事よりも以下extensionのGithubリポジトリのREADMEが分かりやすい。


Pythonやpip、バージョン管理環境に合わせて適宜読み替えて。

この記事書いた時点

Mac OSX Sierra 10.12
pyenv上でPython3環境構築済


 

- JupyterをVivaldiで起動するまで -

jupytera 本体の導入はpip

sudo pip install jupyter

インストールが終わったらconfigファイルを作る

jupyter notebook --generate-config
sudo vim ~/.jupyter/jupyter_notebook_config.py

Windowsだと、マシンのデフォルトブラウザで開いてくれてた気がしたけど、MacだとSafariでnotebookが開いてしまう。
Vivaldiなるブラウザを常用しているので以下設定。

c.NotebookApp.browser =u'/Applications/Vivaldi.app/Contents/MacOS/Vivaldi %s'

Chromeだと多分こう。

c.NotebookApp.browser = u'/Applications/Google/ Chrome.app/Contents/MacOS/Google\ Chrome %s'

ブラウザへのパスは適宜書き換える。

python - Launch IPython notebook with selected browser - Stack Overflow
Jupyterをブラウザ指定して新しいウィンドウで開く【なれない日記20160709】 - けつあご日記

jupyter常用者なのでbashrcにはaliasの指定をしている。

alias ju='jupyter notebook'


 

- jupyter extension -

extension導入は前述の通りREADMEを見ながら。
GitHub - ipython-contrib/jupyter_contrib_nbextensions: A collection of various notebook extensions for Jupyter

sudo pip install -e jupyter_contrib_nbextensions
jupyter contrib nbextension install --user

上のコマンドだけとか、gitからcloneしてsetup.py動かせば導入終わりみたいなネット記事が多い理由は謎。(多分上だけだと、nbextensions見に行っても404 Not Foundだと思うんだけど…)

2つめのnbextension installでブラウザ向けのJSやCSSが入るのだけど、highlight_selected_wordが上手く導入できてないっぽくて死んだ。

NotADirectoryError: [Errno 20] Not a directory: '/usr/local/lib/python3.6/site-packages/jupyter_highlight_selected_word-0.0.11-py3.6.egg/jupyter_highlight_selected_word/static/highlight_selected_word'

highlight_selected_wordだけpipで再インストー

sudo pip uninstall jupyter_highlight_selected_word
sudo pip install jupyter_highlight_selected_word
jupyter contrib nbextension install --user

http://localhost:8888/nbextensions を見に行けばextensionのオン・オフができる。

エディタ内で色々開閉できる「Codefolding in Editor」「Codefolding」、メニューに拡張ショートカットを追加する「Nbextensions edit menu item」「Nbextensions dashboard tab」、Vimキーバインド使う用の「Select CodeMirror Keymap」、PEPの下僕として78文字超えたくないので「Ruler」を使っているのでONにしておわり。


 

- StylishでMonokai風にする -

JupyterはそもそもThemeを使う機能を備えているが、Jupyterを動かすサーバ等環境が変わったら一々設定しないといけないのと、コーディング中にも設定見直したいのでChrome拡張を使ってCSSを書き換えている。

以前までStylishを使っていたのだけど、情報送信の話があったでちょっと考えもの。
Webブラウザアドオン「Styish」、ユーザーデータの収集を始めて騒動に | スラド IT

今はCSSとJSのシンプルな拡張は全て自前。
Stylish以外で簡易なのだとStylus、Stylistあたりか。

一応StylishのユーザグループにJupyter用のCSSが公開されてたりして、これを土台に一部書き換えて使っている。

 

書き換えてMonokaiっぽくする文字周りはこんな感じ。白文字がfffなので適宜。

div.output_stderr {background-color: #050505;}
div.output_stderr pre {
color: #509050; 
font-size: 12px;}
.cm-s-ipython .CodeMirror-matchingbracket { text-decoration: underline; color: #c3c3c3 !important; }
.CodeMirror { color: #c3c3c3 !important; }
.cm-s-default .cm-link {color: #3974dd;}
.cm-s-default .cm-string {color: #de846c;}
.cm-s-default .cm-header {color: #1090f0;}
.cm-s-ipython div.CodeMirror-selected {background: #3C4555 !important;}
.cm-s-ipython .CodeMirror-gutters {background: #39414F; border: 0px; border-radius:0px;}
.cm-s-ipython .CodeMirror-linenumber {color: #5A647B !important; font-size: 11pt;}
.cm-s-ipython .CodeMirror-cursor {border-left: 2px solid #0095ff !important;}
.cm-s-ipython span.cm-comment {color: #6E7C95; font-style: normal !important;}
.cm-s-ipython span.cm-atom {color: #CAA6EC;}
.cm-s-ipython span.cm-number {color: #ae81ff;}
.cm-s-ipython span.cm-property {color: #fff;}
.cm-s-ipython span.cm-attribute {color: #E39194;}
.cm-s-ipython span.cm-keyword {color: #f92672; font-weight: normal;}
.cm-s-ipython span.cm-string {color: #e6db74; font-weight: normal;}
.cm-s-ipython span.cm-operator {color: #f92672; font-weight: normal;}
.cm-s-ipython span.cm-builtin {color: #66d9ef; font-weight: normal;}
.cm-s-ipython span.cm-boolean {color: #E39194;}
.cm-s-ipython span.cm-variable {color: #fff;}
.cm-s-ipython span.cm-variable-2 {color: #fd971f;}
.cm-s-ipython span.cm-error {background: rgba(191, 97, 106, .3) !important;}
.cm-s-ipython span.cm-tag {color: #CAA6EC;}
.cm-s-ipython span.cm-link {color: #E39194;}
.cm-s-ipython span.cm-storage {color: #CAA6EC;}
.cm-s-ipython span.cm-entity {color: #E39194;}
.cm-s-ipython span.cm-class {color: #E5DEA5;}
.cm-s-ipython span.cm-support {color: #77ABE7;}
.cm-s-ipython span.cm-qualifier {color: #77ABE7;}
.cm-s-ipython span.cm-property {color: #fff;}

あとextensionで導入したCodefoldingの三角のアレの位置とかを修正

.CodeMirror-foldgutter-open, .CodeMirror-foldgutter-folded { margin-left: -4px; }

跡は幅を100% にしたりしてこんな感じ

f:id:vaaaaaanquish:20170715184749p:plain

拡張なのでCSSで指定しにくいところの書き換えはできないけどまあ概ね満足。
コードもグラフ出力も黒背景の方が僕は好きです。


 

- おわりに -

extensionは導入はpyenvやAnaconda等のベース環境によってたまに失敗してるイメージがあるんだけど、なんかもっとこう絶対優勝できるようになりたい。

あと便利なextensionあったら知りたいところです。
はてブコメントでよしなに。

Jupyter生活は快適でサイコー。

PythonでWebスクレイピングする時の知見をまとめておく

- はじめに -

最近はWebスクレイピングにお熱である。

趣味の機械学習のデータセット集めに利用したり、自身のカードの情報や各アカウントの支払い状況をスクレイピングしてスプレッドシートで管理したりしている。

最近この手の記事は多くあるものの「~してみた」から抜けた記事が見当たらないので、大規模に処理する場合も含めた大きめの記事として知見をまとめておく。


- 知見まとめ -

最初に言うまでもないが、Pythonのバージョンは3系を選択すべきである。

2系ではユニコードの問題に悩まされ、小学生にもバカにされる。

とにかくencodingに悩まされる事を防ぎたければ3.x系。


 

requests

pip install requests

標準的なHTTPライブラリ。
HTTPを使うならrequestsにするのが吉。

Python 2.x系では標準としてurllibとurllib2があり、urllibはPython 3.xでは廃止され、urllib2もurllib.requestとなった。そのurllibをメインページ(http://requests-docs-ja.readthedocs.io/en/latest/)で「APIがまともに使えません」「Python的ではない」とまで言うのがrequestsというライブラリである。

それら以外にもurllib3(requests内部でも使われている)やhttplib、http.clientなど多数HTTPライブラリがあるのがPythonの現状。

PythonのHTTPライブラリ urllib, urllib2, urllib3, httplib, httplib2 … | スラド
日本語リファレンスには書いてない話:urllibとurllib2の違いってなんだ « DailyHckr

メインページで自ら「人が使いやすいように作られた、エレガントでシンプルなPythonのHTTPライブラリ」と言い切るクールなやつ。

スクレイピングに関してのみ挙げておくと以下が簡易に行えるのがメリット。

HTTPリクエストかける時は、timeoutとheaderをかけて実行するのが吉。
timeoutに関しては30secを設定する等のネット記事が多々見られる。
以下によればtimeoutはページのロード時間を含まないので、ガッツリWebスクレイピングをかけるならボトルネックにならないよう短くしておいて大丈夫。ちなみにデフォルトでは60sec。
http://requests-docs-ja.readthedocs.io/en/latest/user/quickstart/#timeouts
headerには自身の正しいブラウザとOSをUser-Agentとして設定しておきましょう。
UserAgentString.com - unknown version

import requests
headers = {"User-Agent": "hoge"}
resp = requests.get(URL, timeout=1, headers=headers)
# textでunicode, contentでstr
print(resp.text)

requestsではオレオレ証明書等を利用したWebサービスやお固めのWebページを取得しようとすると以下のようなエラーがでる場合がある。

Caused by NewConnectionError('<urllib3.connection.VerifiedHTTPSConnection object at 0x7fe861db6908>: Failed to establish a new connection: [Errno 111] Connection refused',))
[CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:749)
UNKNOWN_PROTOCOL

SSL関連についてはverifyなるフラグが設定できるので以下のように。

requests.get(URL, timeout=1, headers=headers, verify=False)

その他post, deleteなどのメソッドの他、oauth認証やストリーム接続も用意されている。

このやり方でHTMLを取得した場合文字化けする可能性があるが、そちらについては後述する。

Python for iOSなどではデフォルトでrequestsがインテグレーションされている。良い。

あとはHTTP処理をガツガツ回す前にDNSの設定をしていた方が良い。

理由としては以下


BeautifulSoup

pip install beautifulsoup4

「pip install beautifulsoup」だとbeautifulsoup3が入ってしまう。
所々module名が変わっていたりいる。違いはこの辺りを参照。
Beautiful Soup Documentation — Beautiful Soup 4.4.0 documentation https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.html#porting-code-to-bs4

HTMLの構造解析をした上で、HTMLを綺麗に整形、問題のある点の修正等を行ってくれる。
簡易な所では、headタグがbodyタグの中にあったら丁寧に外出ししてくれるとか。
XML, RSS等にも対応している。

名前の元ネタは「Alice in Wonderland」に出てくる美味しいスープの歌らしい。
Alice in Wonderland: 1999 Professor Tortoise - YouTube

一番シンプルなところだと以下。

import requests
from bs4 import BeautifulSoup
resp = requests.get(URL)
soup = BeautifulSoup(resp.text)
# aタグの取得
print(soup.get("a"))
# 整形後のhtml表示
print(soup.prettify())

ガッツリスクリプトにして回す時は、解析器を選択しておくと吉。

  • html.parser
    • デフォルトで付いてくるやつ
    • 本当に些細なミスでも落ちるし中身がPythonなので遅い
    • Pythonのバージョンに依存して中身も違う
  • lxml
    • C言語の高速実装な解析器
    • 最近のWebの複雑な構造や動的な物に少し弱い
    • apt-get install libxml2-dev libxslt1-dev python-dev
    • apt-get install python-lxml
  • html5lib
    • html5の規則に対応
    • ブラウザで表示するのとほぼ同じメソッド
    • かなり重い
    • pip install html5lib

Beautiful Soup 4.x では parser を明示指定しよう - AWS / PHP / Python ちょいメモ http://hideharaaws.hatenablog.com/entry/2016/05/06/175056
Beautiful Soup Documentation — Beautiful Soup 4.4.0 documentation

パーサの良し悪しを考えるとlxmlでチャレンジしてダメならhtml5libを試すのが良さそう。

try:
    soup = BeautifulSoup(html, "lxml")
except:
    soup = BeautifulSoup(html, "html5lib")


parseして情報を得るだけならlxml単体でも可能である(lxmlはそもそもそういうパッケージ)。
lxml単体でやるメリットとしてlxml.html.fromstringでDOMのツリー構造を得られるとか、make_links_absolute()、rewrite_links()によって全てのリンクを相対パスに書き換えられたりとかが標準で実装されている事がある。
単体であればかなり、高速なので簡易なスクレイピング(特定のページ内からの取得など)ならBeautifulSoupを使わずlxmlで十分である。


Mechanize

上記のrequestsとBeautifulSoupくらいの機能ならMechanizeで十分間に合ったりする。
Mechanizeは元々PerlによるWebスクレイピングツールで、ブラウザをエミュレートして操作する事ができる。

最低限「何かにログインしてフォームを入力して情報を出してスクレイピング」ならMechanizeでよい。
(JavaScriptは動かないっぽい)

mechanize http://wwwsearch.sourceforge.net/mechanize/

pip install mechanize
import mechanize

# ブラウザをエミュレート
browser = mechanize.Browser()
response = browser.open(URL)
#HTMLを表示
print(response.read())

urllib2ベースな使い方もできる。

response = mechanize.urlopen("http://www.example.com/")
print(response.read())

デフォルトでrobots.txtスクレイピング禁止事項を読み込んでエラーを返してくれるのもありがたい。
(外す時はbrowser.set_handle_robots(False)とする)
BrowserのaddheadersでHeaderの追加、その他cacheの設定もできる。

標準的な要素指定だけでなく、form()やsubmit()ができるのがエミュレートしてるmechanizeの利点でもある。

pythonモジュールmechanizeでWeb上の作業を自動化する | TRIVIAL TECHNOLOGIES 4 @ats のイクメン日記
Python/mechanize - kuro-tech http://wiki.kurokobo.com/index.php?Python%2Fmechanize


PyQuery

mechanizeに似た、BeautifulSoup等より簡単にWebから情報を取得、かつjQueryライクなAPIを提供するパッケージとしてPyQueryがある。

導入はpipでできる。

pip install pyquery
from pyquery import PyQuery

pq = PyQuery(url='hogehoge.com')
print(pq)

引数にhtml形式のテキストを投げても良いし、urlパラメータにurlを投げればWebサイトを取得する事ができる。

# リンク(aタグ)の所得
for elem in pq.find('a'):
    q = PyQuery(elem)
    print(q.text())

かなり簡単。
普段からjQuery書いてる人はこれが親しみやすいかも。

中身で使われているパーサはlxmlなのでlxmlのインストールも忘れずに。

【python】ウェブスクレイピングで社内ツールを作る | 技術広場

ライブラリ:PyQuery - Life with Python
pyqueryでHTMLからデータを抽出 - 唯物是真 @Scaled_Wurm

 

Selenium.webdriverとPhantomJS

requests、MechanizeだけではJavaScript等動的な表示が進むタイプのWebページは上手く取得できないので、ブラウザ経由でhtmlを取得する。
そのためにSeleniumとブラウザを直接Pythonから触る方法利用する。

Selenium

Selenium with PythonSelenium Python Bindings 2 documentation http://selenium-python.readthedocs.io/

Seleniumはブラウザオートメーションツールで、そもそもWebサービスのテスト用の統合ツール。
Webスクレイピング用ではなく、その為のメソッドが豊富な訳ではないが、今のところこれしかないのでみんな使っているという感じはある。
Pythonバインディングもあってwebdriver経由でブラウザを操作することができる。

Selenium何とかっていうツールがやたら色々あるのはどういうわけなのか | 品質向上ブログ http://blog.trident-qa.com/2013/05/so-many-seleniums/


導入はpipでできる

pip install selenium

 

PhantomJS

FireFox等の既存ブラウザも操作できなくないが、いちいちGUI開くと重いとかいうアレがあるのでPhantomJSなるWebkitベースのヘッドレスブラウザを使う。
PhantomJS | PhantomJS

ヘッドレスブラウザは別に他にもあるのだけれど、正直これしかないからみんな使っているというアレ (かなりバギーである)。
使った感想だと次点でCasperJSが良い(Seleniumからの操作もできるが中身がPhantomJSなのであまり変わりはないが)。

headless browserメモ - なっく日報 http://yukidarake.hateblo.jp/entry/2014/09/12/204813

各headlessブラウザをまとめたRepositoryも
github.com

Seleniumから利用できる、各ブラウザの関係性等は今のところ以下の記事がわかりやすくまとまっている。
qiita.com

最近jsによる動的なWebページがめちゃくちゃ多い事もあって、スクレイピングでのJS実行環境は作るべきではある。
(専用の色々一緒に作りたい人居ないかな…)


PhantomJSはWindows環境下だと少し面倒(PATHとかexeプロセスの処理とか)。
Linux環境なら以下で導入できる。

wget -O /tmp/phantomjs-2.1.1-linux-x86_64.tar.bz2 https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2
cd /tmp
bzip2 -dc /tmp/phantomjs-2.1.1-linux-x86_64.tar.bz2 | tar xvf -
sudo mv /tmp/phantomjs-2.1.1-linux-x86_64/bin/phantomjs /usr/local/bin/
phantomjs --version

Win環境ならexeを落としてきてPATHを通せばある程度は動く。
Thread環境で動かすとゾンビプロセスになりやすいので注意が必要。

 

SeleniumとPhontomJSを利用した実装

Selenium経由でPhantomJSを利用する最もベターな実装は以下

from selenium import webdriver
driver = webdriver.PhantomJS()
driver.get(url)
# htmlの表示
print(driver.page_source)

前述のrequestsと同様timeoutを設定する必要がある。
細かくセットするなら以下のようにセットできる。
デフォルトは全て30秒となっている。

# PhantomJS側のtimeoutを直接設定する
driver = webdriver.PhantomJS(
             desired_capabilities={
                 'phantomjs.page.settings.resourceTimeout': str(30)})
# 非同期なJSが動き続ける最大秒
driver.set_script_timeout = 30
# リクエストに使う最大秒
driver.timeout = 30
# 要素が見つかるまでの待機時間
driver.implicitly_wait = 30
# HTMLファイル群を読み込むためのtimeout
driver.set_page_load_timeout = 30

色々なWeb記事を見ると「ページを読み込むまでtime.sleep()を設定しよう!」というものがあるが、selenium.webdriver.supportを使う事で、ページのロード待機や最大TIMEOUTを設定できるので、上記詳細設定が不要な場合はこちらを利用する。

from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.ui import WebDriverWait
from selenium import webdriver
driver = webdriver.PhantomJS()

# timeoutする時間をdriverに設定
driver_wait = WebDriverWait(driver, 10)
# Webページの取得
driver = driver.get(url)
# ページロードが終わるまで待つ
driver_wait.until(ec.presence_of_all_elements_located)


ページのロードが終わると、Webページがリダイレクトされ別のサイトに飛んでいる場合がある。
urlを適宜確認して、リダイレクトが発生してないか確認する実装が必要である。

driver.current_url


ここまでで「じゃあrequests要ら無くない?」となるが、seleniumのwebdriverはstatus_code取得が実装されていない問題がある。
スクレイピングだと問題なのだが「本来Seleniumはテストツールであり、幾回かHTTP通信を行っている。そのため完全なstatus_codeの取得およびそれに伴った改修は難しい」といった議論が既にある。
WebDriver lacks HTTP response header and status code methods · Issue #141 · SeleniumHQ/selenium-google-code-issue-archive · GitHub
How to get status code by using selenium.py (python code) - Stack Overflow

そこで後述するようにstatus_codeの取得やencodingの取得をrequestsで別途行ったりするのが良さそう。

また、PhantomJSを実行する際には、requests同様User-Agentの設定等を行っておくと吉。
desired_capabilitiesにブラウザ設定を、service_argsで引数を設定できる。
スクレイピングで重要そうな設定は以下のような感じ。

# Mozillaを指定, WebDriverにデフォルトのGhostDriverではなくFirefox後継のmarionetteを利用する
dcap = {
    "phantomjs.page.settings.userAgent": "hoge",
    "marionette": true}
# 引数の設定
args = ["--ignore-ssl-errors=true",   # sslエラーの許容
        "--ssl-protocol=any",         # sslプロトコルはよしなに
        "--proxy=hogehoge:0000",      # Proxyサーバがあるなら指定
        "--disk-cache=false",         # キャッシュを残さない
        "--load-images=false",        # 画像をロードしない(文書のスクレイピング等画像不要の場合に)
        "--script-encoding=utf-8",    # 既に特定済みの文字エンコードがあるなら(デフォutf-8)
        "--output-encoding=utf-8"     # 出力する際の文字エンコード(デフォutf-8)
]

driver = webdriver.PhantomJS(desired_capabilities=dcap, service_args=args)

中でも引数に利用する--script-encoding辺りが厄介で、scriptさえencodingできずWebスクレイピングできないといった場合を見越して事前にセットする必要がある。
ここのencodingの取得の為に、別途requestsで投げるのが良いのではないかなと思う。

ちなみに利用できる引数のリストはここ。
Command Line Interface | PhantomJS
他にもログ出力の設定やweb-securityの指定ができる。


あとPhantomJSにはdriver.close()なるメソッドがあり、これによってdriverのProcessを殺す事ができる。
クローズしないとメモリをガバガバ食っていくのでちゃんとクローズしましょう。

driver.close()

PhantomJSを利用していると多々あるのが、driver.close()の失敗、driver.get()が全てErrorに、といったProcessのゾンビ化の問題である。
普通にコマンドでProcess killを定期的に実行するだけだが、まあまあ面倒。

pgrep -f 'phantom' | xargs kill


呼応してheadless Chromeなるものもあって、開発が停止されたもんだと思ったら最近日本語記事が更新されてビビる。
ヘッドレス Chrome ことはじめ | Web | Google Developers https://developers.google.com/web/updates/2017/04/headless-chrome?hl=ja

そして油断している間にPhantomJSのメンテナが辞める。
www.infoq.com


そしてheadless Chromeを使う記事も自分で書きました。
(PhantomJSよりこっちが良いです...)
vaaaaaanquish.hatenablog.com

今ならこのあたりが分かりやすいです。
Headless Chrome をさわってみた | CYOKODOG


urllib.parse

記事の最初の方でrequests辺りにけちょんけちょんに書かれたurlibだが、Python3以降、urlib.parseにurlparse, urljoinなるWebスクレイピングに必要な関数が統合されたので一応書いておく。

21.8. urllib.parse — URL を解析して構成要素にする — Python 3.6.1 ドキュメント

urlparse, urljoinはよしなにURLの操作をしてくれる。
例えば、URLのドメイン部が欲しい場合は以下のように書ける。

from urllib.parse import urlparse
# httpを先頭に付与しないと上手くいかない場合がある
if not url.startswith("http"):
    url = "http://" + url

parsed_url = urlparse(url)
domain = parsed_url.netloc

paramsを切り離さないurlsplitもあるので適宜活用すると吉。

他にもURLをよしなに接続したい場合
http://hogehoge.com/main/と/main/index.htmlをよしなに繋ぎたいなど)

url = urljoin("http://hogehoge.com/main/", "/main/index.html")
print(url)
# $ http://hogehoge.com/main/index.html


この辺りurlib以外でもっと便利なのあったら知りたいなあと思う。


chardet, cchardet

Webスクレイピングをしている際に非常に厄介なのが多岐に渡る「エンコーディングの種類」である。
utf-8以外のWebサイトみんな地獄で呪ってやるという気持ちになる時もある。
cp932なMicrosoftIBMも絶対許さんという気持ちになる時もある。


ちなみにrequestsには一応、encodingの推定メソッドがある。
デフォルトではHTTPヘッダに基いて推定されるようだが、ヘッダに文字が入っている場合に、Content-Typeのエンコーディング、タグのエンコーディングを無視してエンコーディングをISO-8859-1としてしまう(RFC 2616のデフォルト値である)。
Quickstart — Requests 2.18.1 documentation
Python3でrequests使っても日本語文字化けしないようにするには - TPDN launch pad

requestsには代替としてapparent_encodingなるメソッドが用意されている。

import requests

response = requests.get(URL)
print(response.encoding)                       # Content-Typeに文字が入っていた場合ISO-8859-1
response.encoding = response.apparent_encoding #apparent_encodingに置き換え
print(response.text)                           #文字化けしない

apparent_encodingの中身はchardetで実装された文字エンコーディング推定。
requests/models.py at bd3cf95e34aa49c8d764c899672048df107e0d70 · requests/requests · GitHub
GitHub - chardet/chardet: Python 2/3 compatible character encoding detector.


chardetはよくできたエンコーディング推定器であるのだが、中身がPythonで処理が重い。
Webスクレイピングの速度に耐えきれない程重い場合があるので、C実装のcChardetなるライブラリを利用すると吉。
github.com

import cchardet

resp = requests.get(URL)
ccencoding = cchardet.detect(resp.content)["encoding"]
if len(ccencoding):
    # 小文字じゃないと適応できない場合あり
    resp.encoding = ccencoding.lower()
    encoding = ccencoding
# 文字化けしない
print(resp.text, encoding)


以下にもあるように、Python2系の頃はエンコーディングに関して色々なパッケージがあったようだが、ユニコード問題も解決し今のところはcchardet一強かなという感じ。
Python でエンコーディングを判定する | 傀儡師の館.Python - 楽天ブログ
ppkf で日本語の文字コード判別 | 傀儡師の館.Python - 楽天ブログ

上記記事内の中であれば「easy_install pykf」「pip install cssutils」は成功する。
pykf プロジェクト日本語トップページ - OSDN
encutils - cTHEdot

どちらも以下のように文字コードが判定できる

import encutils
import pykf

encutils.tryEncodings(html)
pykf.guess(html)

pykfは文字コード変換ツールであるので、利用の仕方によっては強い面があるが、若干重いのが欠点である。
機種依存文字などに対応してるのは良い。
機種依存文字対策でpykfをインストールしてみた – BTY備忘録

encutilsはgetEncodingInfoがCSSの中身を見に行っているため、Webサイトならかなり高い確率で正答する。
parseString等のメソッドがあり、CSS特化のパッケージではあるものの場合によっては採用できる。
 

これまでの事も含めて、requestsでstatus_codeの確認とcchardetによるencodingの検出(ISO-8859-1以外のContent-Typeを利用しつつ)をして、WebDriverに渡しましょうというコード。

import requests
import cchardet
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.ui import WebDriverWait
from selenium import webdriver

URL = "http://hogehoge.com"

# requests
headers = {"User-Agent": "hoge"}
resp = requests.get(URL, timeout=1, headers=headers, verify=False)

# status_codeの確認
if resp.status_code != 200:
    print("200じゃない時の処理")

# encodingの検出
encoding = resp.encoding
ccencoding = cchardet.detect(resp.content)["encoding"]
if len(ccencoding) > 0 and encoding != "ISO-8859-1":
    resp.encoding = ccencoding.lower()
    encoding = ccencoding

# WebDriver
dcap = {
    "phantomjs.page.settings.userAgent": "hoge",
    "marionette": true}
args = ["--ignore-ssl-errors=true", "--ssl-protocol=any", "--proxy=hogehoge:0000",
        "--disk-cache=false", "--load-images=false", "--output-encoding=utf-8",
        "--script-encoding={}".format(encoding)]
driver = webdriver.PhantomJS(desired_capabilities=dcap, service_args=args)
# GET
driver_wait = WebDriverWait(driver, 10)
driver = driver.get(URL)
driver_wait.until(ec.presence_of_all_elements_located)

# 引数でoutput-encodingを指定していなければ > html = driver.page_source.decode(encoding)
# 大体文字化けせずJSをうまいこと回して取得できる
html = driver.page_source


# 解析に使いたい時は
from bs4 import BeautifulSoup
try:
    soup = BeautifulSoup(html, "lxml")
except:
    soup = BeautifulSoup(html, "html5lib")
print(soup.get)

多分これが一番つよいと思います。


timeout_decorator

上記のような設定をしても、複数のWebスクレイピングを回している時にはSocketの不足やHTTPメソッドが返ってこない等によるPythonのフリーズが発生する。
PhantomJSにせよHTTP通信を使っているメソッドは、通信状態によって設定したtimeoutを無視して周り続けたりゾンビプロセスになってしまう場合がある。
デコレータで関数にタイムアウトを実装できるtimeout-decorator使う。

導入はpip

pip install timeout-decorator


デコレータでtimeout_decoratorを付けてあげると良さげ。

@timeout_decorator.timeout(5, timeout_exception=StopIteration)
def print_while():
    while(1):
        print(".")

try:
    print_while()
except StopIteration as es:
    print(es, "Stopped")

Windowsなる不自由なOSだと(多分bash.exe経由したりすると) module 'signal' has no attribute 'SIGALRM' で使えそうにないが、概ね良さそう。


 

joblib, multiprocess, asyncio, threading

PythonでWebスクレイピングするなら並列、もしくは非同期処理が必要になる事がある。
一般的に想像される"並列処理"ならjoblib, multiprosessing、"非同期処理"ならasyncioを使うと大体良い(正確にはどちらもできる)。
それぞれの違い、及びasyncioの手引はicoxfog417氏の下記記事が最も分かりやすいと思います。
Pythonにおける非同期処理: asyncio逆引きリファレンス - Qiita http://qiita.com/icoxfog417/items/07cbf5110ca82629aca0

asyncioはPython3.4から標準化されたパッケージで、最初こそ不安定さでissueだらけだったのだが大分安定的に並列処理できるようになった模様。
ただ、未だ若干の記述のしにくさ、拡張のしにくさがある。

同様にPythonの標準モジュールにthreadingがあるが、1つ1つ継承したクラスを作ったりと若干記述が面倒である。
マルチプロセッシング(multiprocessing) - Python | Welcome to underground

asyncio, threadingのような非同期処理によるWebスクレイピングするのであれば、その辺りを担ったパッケージaiohttp, grequests, requests-futuresを使うのが吉(後述)。

multiprocessing

既に作成済みの関数群を並列化するならjoblibかmultiprocessingが良さそう
Python並列処理(multiprocessingとJoblib) - Qiita

multiprocessingであれば標準module
ごく簡単な Python multiprocessing の使い方 - Librabuch

以下のようによしなに

from multiprocessing import Pool, cpu_count
import requests

def main(x):
    """スクレイピング処理."""
    return requests.get(x)

c = cpu_count()
pool = Pool(c)
l = [p.get() for p in [pool.apply_async(main, args=(x,)) for x in URL]]
c.close

もしくは

from multiprocessing import Pool
import multiprocessing as multi

p = Pool(multi.cpu_count())
p.map(main, url_list)
p.close()

この場合引数がリストになってないといけないので、リストのリストのような状態になる場合がある。

より実践的にPoolを作ってQueue,Pipeで処理するなら以下が参考になる。
マルチプロセッシング(multiprocessing) - Python | Welcome to underground
multiprocessingのあれこれ - Monthly Hacker's Blog

multiprocessingはclose()しないとガバガバメモリを食い続けるので気をつける必要がある。

joblib

joblibはpipで導入

pip install joblib

こちらの方が記述が簡単であり、子プロセスの自動削除など使いやすさがある。
n_jobsに-1を指定すれば使えるだけプロセスを使ってくれるし、1にすれば1Threadで普通に処理できるのでテストもしやすい。

from joblib import Parallel, delayed
Parallel(n_jobs=-1)(delayed(main)(x) for x in url_list)


ただjoblibは反応しなくなる事がまれにあるかなと使っていて感じた。
(多分各プロセスのマージが重たい)


以下記事ではmultiprocessingの方が高速という話もある。(真偽不明)
Python並列処理(multiprocessingとJoblib) - Qiita


 

aiohttp, grequests, requests-futures

非同期なHTTP通信を扱うライブラリ群はまあ色々ある。

aiohttp

aiohttpはasyncioを用いながらrequestsに似たAPIを使って情報を取得するライブラリ。
記述が簡単で、基本的なrequestsらしい操作に対してデコレータの設置とyieldを書いてやればよい。

リクエストの時間をコントロールするにはsemaphoreを使う。
Synchronizationで同期中にあるコルーチンが動く数を制限する事ができる。

GitHub - aio-libs/aiohttp: Async http client/server framework (asyncio)
Migration to 2.x — aiohttp 2.3.0-a0 documentation

最もシンプルな例を書くと

import aiohttp
import syncio

@asyncio.coroutine
def get_url(url):
    session = aiohttp.ClientSession()
    response = yield from session.request('GET', url)
    html = yield from response.content.read()
    # htmlを表示
    print(html)
    # セッションはちゃんとcloseする
    response.close()
    yield from session.close()

url_list= ['hoge', 'piyo', 'koko']
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait([curl(url) for url in url_list]))

少し制限される部分があるが、適切にsyncioを振り分けられるので良い。
パラメータが少し少ないのがネックではあるが、asyncioの中でPhantomJSの起動と停止を書いてやるだけで、よしなにやってくれる非同期処理が可能である。

以下gitのサンプルはめちゃくちゃ充実している。
aiohttp/examples at master · aio-libs/aiohttp · GitHub

日本語の記事は以下の記事くらい。
(もしかしたら今後ちゃんと書くかも)
asyncioを用いたpythonの高速なスクレイピング | プログラミング | POSTD

 

grequests

grequestsは非同期ネットワーク処理が可能なgeventにrequestsを乗せたライブラリ。
GitHub - kennethreitz/grequests: Requests + Gevent = <3

pip install grequests

かなり簡易に非同期なrequestsを扱える

import grequests

url_list = ["hoge", "piyo", ...]
header = {}
response = (grequests.get(url, headers=header) for url in url_list)
for x in grequests.map(response, size=3):
    print(x.text)

はあ簡単。
sizeで処理数の指定もできるし、基本的にrequestsと同様に扱える。
JSの実行が不要ならこれで十分そうですね。
返るリストの失敗した箇所はNONEな注意すれば、requestsと使い方が同じで慣れやすい。

まだまだ全然開発途上という感じなのと、日本語の記事はキツいのしかないので今後また別で記事書くかも。


 

requests-futures

requests-futuresはgrequestsと違って標準moduleの中の並列処理のconcurrent.futuresに乗っかっているライブラリの1つ。
GitHub - ross/requests-futures: Asynchronous Python HTTP Requests for Humans using Futures
requests-futures 0.9.7 : Python Package Index


pipで導入できる。

pip install requests_futures

例えば以下のように書くだけで非同期処理が実行できる。

from requests_futures.sessions import FuturesSession

session = FuturesSession()
header = {}
one = session.get("hogehoge.com", headers=header)
two = session.get("hogehoge.com", headers=header)

response_one = one.result()
print(response_one.content)
response_two = two.result()
print(response_two.content)

人によってはgrequestsより直感的かもしれない。
grequests同様、中身がrequestsなので引数の管理もしやすい。
例えばだがresponse_twoの取得に失敗した場合、response_twoが「NameError: name 'response_two' is not defined」となってしまうのでそこだけ注意したい。


 

scrapy

色々書いたが、同じサービスで形式がほぼ決まっている(例えば特定のニュースサイトの記事をグルグルスクレイピングしたい, 機械学習向けの画像データを収集したい)ような場合は、既にScrapyなるフレームワークがあるのでそちらを利用した方が早い。

多分このフレームワーク1つで本が書ける程なので、ここでは紹介程度にしておきたい。

Scrapyの思想があまり好きになれないというのもありますが(HTMLを保存せず解析するため負荷が大きい, JSの実行は結局独Spiderを自前, あーだこーだ...)。

Scrapy CloudなるScrapy設定をdeployすれば勝手に回してくれるサービスがあったり、ScrapyだけでQiita連載書いてる人が居たりする。

シンプルにScrapy入門は良いと思うので読むと良いと思います。
Scrapy入門(1) - Qiita

2016年5月にPython3に対応したのですが、それまで2系でのネット記事や書籍出版が多く行われているので、注意が必要。
Scrapy | A Fast and Powerful Scraping and Web Crawling Framework

アーキテクチャが図としてあるので概念と紹介だけ。
https://doc.scrapy.org/en/latest/_images/scrapy_architecture_02.png
Architecture overview — Scrapy 1.4.0 documentation

大きくは「Engine」「Scheduler」「Downloader」「Spiders」「Item Pipeline」の5つの仕組みが動いています。

Engineが各コンポーネントのフロー制御を行い、Schedulerがスケジューリングとキューイングを行う。
DownloaderとSpidersが主にスクレイピングを行う部分で、Custom Classを適宜設定する事が可能。
Item Pipelineに流し込んでDBやtext、画像で保存するアーキテクチャ

別々のコンポーネントをそれぞれ動かす事で高速な処理を可能にしており、Spidersをちょろっと書けばすぐスクレイピングを始められる事も含め非常に良くできている。

しかし、パラメータの数はゆうに100を超えるので調整がなかなか難しい。
また。サーバへの負荷が主にSleepでの調整だったり(別途記述しようと思えばできるが)と、JSを動かすにはwebdriver周りを結局自前で書くことになるので、大きめのフレームワークらしい欠点は備えている。
(便利かつデフォルト設定でよければすぐ回せるので自分も機械学習用の画像集めとかには使っています)


 

SimpleHTTPServerとThreadを使ったunittest

Webスクレイピングはコードが肥大化しやすく、かつ複雑化しやすいです。
なのでテストコードを書いておくのが良いでしょう。

内容については、以下に別途記事を書きました。

vaaaaaanquish.hatenablog.com


 

法律の話

重要なのは著作権法と動産不法侵入。
以下にまとめておく。

  • 著作権法
    • 47条の7 情報解析のための複製等
      • 情報解析のためならデータを複製していいがマナーを守れ
    • 47条の6 送信可能化された情報の送信元識別符号の検索のための複製等
    • 施行令第7条の5 送信可能化された情報の収集、整理及び提供の基準
      • プログラムにより自動的に収集、整理して他の規定にかかってたらちゃんと削除
    • 基本的にWeb上で公開されているものは著作者のものであり知的財産
    • データ分析に使用する場合は違法ダウンロード系でなければOK
    • データベースにするにしても最小限でスキーマが分からないレベルで
    • スクレイピングした情報をそのまま公開するのはほぼアウトだと思ったほうが良さそう
    • セーフハーバー保護やDMCAの下利用できるものもある
    • 画像は商標などもあるので注意
  • 動産不法侵入
    • サーバへのアクセスで抵触しかねない基準と対策
      • 同意の欠如 -> Webページが提示する条項を守ろう
      • 実害 -> サーバへの負荷等を考えよう
      • 意図性 -> プログラムを書いたなら意図がある
    • 1秒に1リクエストあれば良いんじゃないかという資料がある

一応個人情報保護士なる資格を取るにあたって勉強したが、なかなかこの辺は難しいと感じる。
難しいのは判断基準。
分析に使う情報取得は権利的にOKだが「機械学習データこんな感じですと集めたデータをアップロード」辺りはアウトっぽい。
言うなれば「節度と倫理を守ろう」だ…


Serverの負荷は「1秒に1回リクエスト論」に対してよく「岡崎市立中央図書館事件」が議題に上がる。
2010年に図書館のDBに1秒Sleep入れてアクセスしたが(図書館のDBのバグが問題で)負荷になってしまい警察に呼ばれた人の話。
控えめに言って、この時のインターネットの盛り上がりはすごくてTwitter2chも以下のブログの虜だったので実際一回は全体眺めるのを勧めたい。
(不謹慎ながら端から見てる分にはクソ面白かった…)
librahack.jp


後企業だとeBayとBidder's Edgeの話とか。
1997年にメタオークションサイトを作るためebayサイトを1日100,000回叩いたら動産不法侵入になった話(Wikiがある)。
eBay v. Bidder's Edge - Wikipedia https://en.wikipedia.org/wiki/EBay_v._Bidder%27s_Edge
最終的に2001年に金で和解したらしいけど、その時の金額は個人で払えるレベルのアレじゃないっぽい。


正直コーディングする前に設計上で他の作業と並行させるとか、出来る工夫は沢山あると思うのですべき。
スクレイピングだけ切り離すと突然家に警察が来てもおかしくないなというグレーなやつ。


技術的なルールもあるのでまとめておく。これは遵守したいところ。

  • 技術的なスクレイピングルール
    • robots.txtやrobots metaタグを守る
      • クローラへの指示が示されている(アクセス禁止のパスとか)
      • 見つけたら守る必要がある
      • 読まなければ営業妨害になる可能性
    • HTMLにPragma:No-cacheとなっていたらデータ収集しない
    • aタグにrel="nofollow"が設定されていたら辿らない
    • User-agentなどをきちんと設定しIP偽装等をやめよう


また、以下の人もなかなか。
Webスクレイピングの注意事項一覧 - Qiita


書籍

関連して読んだ書籍とか

日本の書籍

Pythonクローリング&スクレイピング -データ収集・解析のための実践開発ガイド-


正直基本的な事だけならこれ一冊で良くねと思えるバイブルっぽい良書。
Pythonの基本的な事もそうだが、Unixコマンドも多々解説しており、基本的な事が出来るようになる流れで書いてある。
(まずはwgetしてgrepしてjqで~とか、Pythonならやりたい事別にこうしてとか~)

robotsに対する処理とか、負荷のかけかたも実践的なコードがある。

一応解析のやり方としてAPIの利用や、自然言語処理技術の紹介、SPARQLのBigQueryの利用方法等が1章分ある。

この本の良い所はScrapyを1章まるまる使って解説している所にあると思う。
あのクソ多いパラメータを重要な所だけ抜き取って書いてくれてるので、Reference読む前に読んでおくと楽。
Scrapyの中がどう動いてるか理解できたのはこの本のおかげ。

クローラの継続実行に関する解説もあり、Linuxへの理解もちょっと深まる。


 

実践 Webスクレイピング&クローリング-オープンデータ時代の収集・整形テクニック

実践 Webスクレイピング&クローリング-オープンデータ時代の収集・整形テクニック

実践 Webスクレイピング&クローリング-オープンデータ時代の収集・整形テクニック

2章まで、Pyhonの使い方(インストールからprint関数の利用、四則演算とかそういうレベルから)が書いてあって、かつ2章までで書籍の4割くらいを使っている。

スクレイピングに関する話は中の数章で、WebAPIの利用、SPARQL、スクレイピングサービスのkimonoの紹介という感じ。
加えて法律とrobot.txtに関する話がちょこっと程度。

最後4割はExcelAWKコマンド、Pandasの使い方が載ってる。

あまりに知見が少なく、説明も痒いので正直かなり厳しい。
著者の情報を見るにそれなりのエンジニアっぽいコミュニティや受賞を持っているようだが果たしてこれは…という感想。


Pythonによるスクレイピング&機械学習 開発テクニック

「絶対これ機械学習エンジニアが書いたでしょ…」ってなる。

2章までが主にスクレイピングの話で、確かにrequests+BS4+Selenium+PhantomJSを使った実践的なコードと、cronの操作、WebAPIの話が書かれている。
(本当に基本的な所までが連ねてある感じで書いてある)

残りの6~7割程度が本当に機械学習の話しか載っていない。
sklearnからTensorFlow、MecabとCabochaによる自然言語処理ベイズマルコフ連鎖の実装、OCRやCNNでの画像認識…とかなりの実践機械学習本。

スクレイピングの知見含めて正直Qiita等で間に合うレベル感の内容なので、「PythonでのWebスクレイピングから機械学習を使った分析まで全体感を見ながら通しで勉強したい!」という人には向いているかも。

スクレイピングとは機械学習とはなんぞやという人が読めば、確かに世界が広がりそうな流れの書籍にはなっている。
あと、サンプルコードがダウンロードできるようになってるのも良い点。


 

PythonによるWebスクレイピング

PythonによるWebスクレイピング

PythonによるWebスクレイピング

Web Scraping with Python: Collecting Data from the Modern Web

Web Scraping with Python: Collecting Data from the Modern Web

Pythonを勉強した後、スクレイピングをとりあえず試したいという人向けなレベル感の書籍。

PythonでのCSVJSONの扱い方とか、GoogleTwitterAPIの使い方、PythonMySQLの解説、NLTKによる自然言語処理による分析、Tesseract OCRによる画像から文字抽出する部分辺りはスクレイピング本としては余計に感じた。
requestsとBeautifulSoupを扱っているが、めちゃくちゃ有益な知見とまではいかなかった(Qiitaやブログ記事でカバーできそう)。

順を追って色々勉強したいという人向けである。

良いところを挙げると、本記事には乗せていないPySocksを用いた、Torによるスクレイピングの匿名化の話等が書いてある。
GitHub - Anorov/PySocks: A SOCKS proxy client and wrapper for Python.
また、ハニーポットをどういう風に避けるかみたいな話もある。

付録の事案紹介や倫理の部分は丁寧で、実際に弁護士に伺った部分があるとの事。
事例が国内外解説してあって「日本法とアメリカ法は違うのでおおよそでも良いから読んだほうが良い」とのこと。

英語版も似たような内容。
英語版筆者が元々Webスクレイピングより分析の仕事をしていたようで、まあそこに寄ったのかなって。


洋書

Web Scraping with Python

Web Scraping with Python (Community Experience Distilled)

Web Scraping with Python (Community Experience Distilled)

実践というより、Webに関する知見な書籍。
ただPythonがある程度書ける前提を持った人向けの書籍で、その分レベルは実践に近い(しかしPython2系)。

cacheを使って帯域幅を削減する方法や、スクレイピングの並列化、リンク走査の話辺りはかなり良い。
また、クッキーやブラウザレンダラの話は知見として有益な部分が多い。
それらのコードも載っているのだがPython2系なのが辛いところ。

あと「Pythonクローリング&スクレイピング」と同様、6章まるまるScrapyの話をふんだんに書いてくれているので、Scrapyの設計を理解するには使えそう(しかしPython2系)。

Pythonコードは少なめだが丁寧ではあると思う(しかしPython2系)。
以外と知見が散りばめられているので、値段にしては有益な書。

 

Python Web Scraping - Second Edition

Python Web Scraping - Second Edition

Python Web Scraping - Second Edition

多分邦書洋書含めた中でも最も実践的で丁寧な良書。

基本的なrequestからBS4, PhanotomJSとScrapyの解説が載っているだけでなく、キャッシュの利用や発生しがちなExceptionの解説、SeleniumPyQtによるGUI操作にまで及んでいる。
また、robot.txtのparseや、python-wohoisで取得先の情報見ましょうとか、「普段みんなスクレイピング周辺でどんな事してるの?」という疑問に対して痒い所まで手が届いた良い書籍。

またコードがGithubにあるのも良い。
GitHub - PacktPublishing/Python-Web-Scraping-Second-Edition: Python Web Scraping Second Edition by Packt

 

Create a web crawler in Python

Amazon CAPTCHA

URLだけ一応貼っておく。
文書の前半がPythonのインストール、エラー処理、四則演算といった内容。後半は本当にQiita以下の内容。

筆者を見ると、Qiita以下のクソ電子書籍を適当に出しまくってるオッサンらしいので本当クソ。
こういう本全部Amazonに報告するスクリプト書きたい。


 

おわりに

なんか色々書いてるうちに本が書けるくらいの量になってしまった…

Referenceも結構読んだので、それぞれのライブラリ毎にまた記事が書けると良いなと思っている気がします。

Seleniumに変わるスクレイピング向けのPythonモジュール作りたい人居ないかな~。

他で簡素なまとめは以下が分かりやすいかも

Python3でクロールしようと思って調べたこと | mwSoft


あと他にこの書籍とかツールマジいいよ!ってやつあれば教えてくださいな。
 

 

追記:長すぎてアレという意見が多かったのでAdventCalender立てたり別個で記事書いたりしていきます…はい…各位も参加お願いします…


 
追記2 : User-Agentについて「UA偽装をやめよう」「よしなにUA書こう」という背反した記述があると指摘がフレンズからありました。

「確かに」以外の感想が得られなかったので修正しました。
User-Agentに自身の正しい情報を乗せるだけでなく、自身の連絡先等乗せてスクレイピングするくらいが紳士的ですね。

以下のような話も読んでおくとよさそう。

指摘あるまで気付けなかったので文章構成についても技術についても精進したいと思う次第です。


 
追記3 : !!



追記4 : 結構Python2系が小学生にバカにされる話に関するコメントが多いので元ネタを…

togetter.com
 


 

Pythonのhttp.serverを利用してWebスクレイピングのunittestを書く

- はじめに -

「Webスクレイピングで情報を収集する」という内容は多い。

しかし、Webスクレイピングのコードは肥大化しやすいだけでなく、細かな変更が多くなる。
テストを書いて変更の影響をちゃんと見ておく必要性が高い。

unittestとhttp.serverを使ったテストの実装についてメモしておく。


参考:python - How to stop BaseHTTPServer.serve_forever() in a BaseHTTPRequestHandler subclass? - Stack Overflow


- http.server -

http.serverはPython 2.xではSimpleHTTPServerと呼ばれていたもの。
(http.serverよりSimpleHTTPServerの方がググラビリティ高いかも)

Webサービス等の開発用にローカルサーバとして利用している人も多い。

21.22. http.server — HTTP サーバ — Python 3.6.1 ドキュメント


コマンドでの実行も可能だが、Pythonからだとハンドラを指定したHTTPServerを以下のように記述し、簡易にサーバを立てる事ができる。

from http.server import HTTPServer, SimpleHTTPRequestHandler

httpd = HTTPServer(("localhost", 8888), SimpleHTTPRequestHandler)
httpd.serve_forever()

これによって http://localhost:8888 に、ローカルでのServer環境が整う。


 

- unittestにかませる -

serve_foreverによって起動されるサーバ(SocketServer.py)は以下のようにWhileで実行されている。

def serve_forever(self):
    """Handle one request at a time until doomsday."""
    while 1:
        self.handle_request()

なのでwrapperとなるclassを書いてやって、server.shutdown及びserver.server_closeを実行できるようによしなに書いておけば良い。

また、HTTPServerが動いているThreadでPythonのコードを動かすのは少し困難なので、別のThreadで動かしてやる。


別ThreadでHTTPServerを実行し、requestを使ってアクセスするunittestは以下のような感じ。

from http.server import HTTPServer, SimpleHTTPRequestHandler
import threading
import unittest

HOST = "localhost"
PORT = 8888

class StoppableHTTPServer(HTTPServer):
    """
    ThreadでSimpleHTTPServerを動かすためのwrapper class.
    Ctrl + Cで終了されるとThreadだけが死んで残る.
    KeyboardInterruptはpassする.
    """
    def run(self):
        try:
            self.serve_forever()
        except KeyboardInterrupt:
            pass
        finally:
            self.server_close()

class TestWebScraping(unittest.TestCase):
    def setUp(self):
        """Use unittest setUp method."""
        self.server = StoppableHTTPServer((HOST, PORT), SimpleHTTPRequestHandler)
        self.url = "http://{}:{}/test_case/index.html".format(HOST, PORT)
        self.thread = threading.Thread(None, self.server.run)
        self.thread.start()

    def test_requests_get(self):
        """
        requestsモジュールでURLを叩くunittest.
        contentが返ってくるか.
        """
        r = requests.get(self.url)
        self.assertEqual(type(resp.content), str)

    def tearDown(self):
        """Use unittest tearDown method."""
        self.server.shutdown()
        self.thread.join()

unittestのコードと同ディレクトリにから./test_case/index.htmlが設置されていれば良い。

最後はServerを終了してthreadをjoinするようになってる。


後はテスト内容に合わせてhtmlを修正したり複数ファイルを付与してく。

JSやPHPの実行もできるのでよしなにテストができる。


 

- おわりに -

BaseHTTPServerとかTCPServerとかTCP扱える枠組みなら多分似たような事ができると思います。

他にも良さげなやり方があったら知りたい。


 

実践 Python 3

実践 Python 3

Pythonでseleniumとheadless chromeでWebページの情報を取得する (Ubuntu 16.04)

- はじめに -

Chrome 59が正式版となりheadless版も正式に動き始めました。めでたい。

New in Chrome 59  |  Web  |  Google Developers

headless chromeUbuntuに導入してPythonから触ったという記事です。

端的に導入だけです。

こんな記事もあったので。

www.infoq.com


- インストール -

まずPythonからの起動に必要なchromedriverを取得しておきます。
apt-getでも入りますが最新版が欲しいので以下のように。
(記事書いた当時で最新版は2.29)

wget https://chromedriver.storage.googleapis.com/2.29/chromedriver_linux64.zip
unzip chromedriver_linux64.zip
sudo mv chromedriver /usr/local/bin/


依存ライブラリとなるlibappindicator1をインストールしておきます。
事前にやらないとハマるっぽいので、先走ったら後述で入れたChromeをアンインストールしてやり直すのが良いです。

sudo apt-get install libappindicator1


Chromedebパッケージを取得してきます。
公式ページに行くと、小さな文字で「Google Chrome をインストールすると Google レポジトリが追加され、Google Chrome がシステムで自動更新されます。Google のレポジトリを追加したくない場合は、パッケージをインストールする前に「sudo touch /etc/default/google-chrome」を実行してください。」と書かれています。
スクレイピング等ガンガン回したい場合などはやっておくべきでしょう。(やらなくてもOK)

sudo touch /etc/default/google-chrome
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb


以下でインストールします。

sudo dpkg -i google-chrome-stable_current_amd64.deb


この時以下のようなエラーが出るかもしれません。

Errors were encountered while processing:
google-chrome-stable


その時はapt-getでこうしてこう。

sudo apt-get install -f
sudo dpkg -i google-chrome-stable_current_amd64.deb


インストールおわりです。


- Pythonで実行 -

Python3.xを使います。2系は小学生にバカにされるからです。
seleniumのwebdriver配下にchrome.optionsというのがあるのでそれを利用して、--headlessを指定します。
gpuが載ってない場合は--disable-gpuも指定しておきます。

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument('--headless')
options.add_argument('--disable-gpu')
driver = webdriver.Chrome(chrome_options=options)
driver.get('hogehoge.com')
print(driver.page_source)

後はPhontomJS等と同じように使えます。


driver.save_screenshotしてみたら以下のように

f:id:vaaaaaanquish:20170606194222p:plain



以下曰くPythonだとNotificationのPopUpが出るので設定しておいた方が良いっぽい(未確認)。



あと多分デフォルトだとUbuntuのフォントが足りず文字化けする。
私は導入済み。以下参考。


- おわりに -

今までPythonでWebスクレイピングというとPhontomJSなるブラウザが圧倒的シェアでした。

しかしPhontomJSもなかなかバギーで、プロセスがゾンビ化するなどの問題を抱えていました。

headless chromeに関する日本語ページが更新されるなど、期待が高まっていたここ半年です。

このままheadless chromeが希望の星となる事を期待しています。


最近はWebテストスクレイピングにお熱なので、いつかそこで溜まった知見も記事にしたいと思っています。

人工知能学会全国大会に参加した #jsai2017

- はじめに -

JSAI2017 – 2017年度 人工知能学会全国大会(第31回) に参加した。

今まで学会への参加は学生発表、個人の聴講参加だけだったが、今回は企業ブースでの参加となった。

あまり聴講への参加は出来てないがメモ程度に気になったものをまとめておく。


- 聴講 -

企業ブース出展者での参加では1企業につき2名分のみ聴講証が渡されていたため、6割は会社の犬として自社の説明やステッカー配りなどをしていた。

以下には会社の犬時間以外で聴講し気になった所のみまとめておく。

会社の犬なので、基礎研究より産業応用の部分が多いかもしれない。

05月23日

DNNによるRDF上の単語間の関係の予測を見たかったのだが、部屋に着いた時には質疑だった。

Deep Learning系やチュートリアルセッションはどこも激混みで、大変であった。


DNN圧縮時のパラメータと圧縮後の精度, 大きさの関係

DNN圧縮時のパラメータと圧縮後の精度, 大きさの関係

Deep CompressionなるDeep Neural Netの圧縮手法に関する発表。
元論文にあるK-meansではなくX-meansを使っていた。
そうしたら、パラメータを設定する時の基準の知見が得られたよという内容。
クラスタ数を自動推定するX-means法を調べてみた - Qiita


「DNNの圧縮に枝刈りを使う」というのは恥ずかしながら初めて知った手法で、Deep Neural Networksの重み量子化と共有, ハフマン符号化をやれば上手くいくらしい。前提となる論文は以下。

Deep Compression: Compressing Deep Neural Networks with Pruning, Trained Quantization and Huffman Coding

先に上記Deep Compressionの話を書いておくと、所謂「枝刈り」と同じ概念で、ニューロン間を接続しているノードを狩ってパラメータ数削減、計算量圧縮を図るというもの。論文ではVGGモデルが49分の1にまで圧縮され、高速化されながら同等の精度を保っている。

まずは閾値を取って重み行列を疎行列化してしまう。
疎行列となった重み行列をK-meansでまとめて クラスタのインデックスとcentroid行列に分けて扱いやすくする(量子化)。
そうした場合、追加の学習をしないとDNN全体の精度が落ちた状態になるが、K-meansで求めたクラスタを使い、クラスタ毎の勾配を使いながら追加で学習を回す事で精度が担保される(Fine-Turning)。
その上で重みをハフマン符号化してやってコンピュータ最適にしてやる事で、速度と電力消費が圧倒的に改善されている。


本題の発表では、ハフマン符号化を使わず、圧縮後に再学習させない事を制約条件としていた。
(「まあそこまでやるのどう考えても実装と設計が面倒だよね…」と正直思った…)

本題はK-meansのパラメータ設定に関して基準が欲しいとの事で、パラメータを変更したいくつかのモデルとX-meansで試していた。

結果としては、量子化する際のクラスタ数が少ないと精度が下がるが、大きすぎるとモデルが大きくなるのであんまり意味なく、そうするとX-meansを使った場合に「小さすぎ」「大きくなりすぎ」問題が発生してあんまり良くなかったッスという感じだった。
ただ、大まかな基準は示されていたのと、再学習等の余地は大きそうだなと思った。
あと、圧縮方法について幾つか調べてDeep Learning with Limited Numerical Precision等を知れて良かった。

日本語だと以下を見つけたので気になれば。
ニューラルネットのメモリ消費を小さくする類の手法 - うどん記
introduction-to-deep-compression (SlideShare)

追記:


ニューラルネットワークの量子化についての最近の研究の進展と、その重要性 - SmartNews 開発者ブログ 良いです。




CNNへの競合学習の統合による表現学習の強化

CNNへの競合学習の統合による表現学習の強化

DNNの強みは入力データから認識に必要な情報の基底を獲得する表現学習(Representation Learning)にあるけど、今のCNNってあんまり表現学習に適してはいないよね。
じゃあSOMなんかに使われる競合学習みたいな教師なし学習と共存した学習手法があれば、表現学習可能なCNNって作れるよねという内容。

実は学生時代かなり似た研究をしていた時期があって、SOMとNNを接続して上手く学習させようとするCounter PropagationとかSOMを多層化して教師あり学習に応用するSOINNなんかを熱心に調べていた。
(結論から言うとこれはあまり上手くいかなかった...)

発表では、SOMで言うところの勝利ノードの出力(ユークリッド距離)とBack Propagationをシームレスに組み合わせ、SOMフィルターをいくつか作りその数が十分な場合において通常のCNNより良くなったというものだった。
f:id:vaaaaaanquish:20170527231824p:plain

教師なし学習が前提にあれば学習データが少ない状態からでも応用できる可能性があると示唆はしていたが、指摘の通り学習データが少ない場合にSOMのフィルター数を十分に確保できないので難しそう。

SOMって結構安定しないので、数確保したりSOMの研究みたいになりはじめてう~んってなっちゃうんだよね…としみじみ思った。
SOM自体がそれなりの表現学習能力を持っていたと仮定するなら、後方に接続するネットワークもPre-trainingを十分に行ったものを使うとかすると良さそう。


05月24日

お仕事でも自然言語処理にお熱で、時間的にちょうど医療応用の部屋で自然言語処理系のセッションがあったので聞いた。

あとはせっかくなのでオーガナイズドセッションでLinked Dataの話を聞いた。


深層学習による医療テキストからの固有表現抽出器の開発とその性能評価

深層学習による医療テキストからの固有表現抽出器の開発とその性能評価

医療文書って非文法的で断片化した表現が多いので難しい。海外だと語彙サーバーのようなものがあったり標準的なツールがあるが、日本にはないので作りたい。
発表では、よくある双方向LSTM(Bi-LSTM)とCRFをつかった手法を適応し、既存システムに多いCPFベースより精度良く出せたという事だった。
課題は2つあり、疾患識別(ER)と疾患と医師の初見の分類(P/N分類)を1つのEnd-to-Endなモデルで行いたいらしい。

参考にしたのは多分以下(?)。

Bidirectional LSTM-CRF Models for Sequence Tagging

Bi-directional LSTMは文脈を過去(past)と未来(future)に分けて、過去->未来(未来->過去)と双方向に接続するLSTMの拡張モデル。
それらを系列ラベリングに適応するために確率的条件場(CRF)に適応するというのは最近の流れの一つである。

発表内ではBi-LSTMに対して、表層文字のみ入力する場合や辞書の活用等と比較していたが、あまり有意差があるようには見られなかった。
(なかなか分野が違うので、結果を見ても分からず…)

あと普遍的な課題として「コーパス量がもっとあれば」という話をしていて、やっぱり自然言語処理機械学習組み合わせるとどこもそうなるよなあ…と思った。

ついでにBi-LSTMについて調べてた時に見た把握しやすいURLを書いておく。
pythonでBidirectional LSTMを使った分類問題 - ゆるふわめも
ニューラルネットワークで時系列データの予測を行う - Qiita
Recurrent Neural Networks Tutorial, Part 1 – Introduction to RNNs – WildML


Sleep Pattern Visualization via Clustering on Sound Data

Sleep Pattern Visualization via Clustering on Sound Data

睡眠の改善のための分析をSequenced-based kernel SOMを使って視覚的に行ってみようという発表。
利用したのは、睡眠中の音声データで、スコアリングしていくと睡眠改善において音データは非常に重要だという事がわかる。

Sequenced-based kernel SOMは、KLSOM(Kullback-Leibler情報量を使ったSOM)を組み込んだKernel SOM(カーネル関数を使うSOM)、SbSOM(クラスタの時系列変化を可視化するためのモデル)を組み合わせたモデル。
これで見た。


結果を見れば分かるが、音声データが睡眠の情報と並列している事が視覚的にわかる(歯軋りの時間と悪い睡眠の時間が同じ等)。
f:id:vaaaaaanquish:20170527224123p:plain


SOMの時系列可視化はかなり有用そうなので自分でも何か使いたいなと思う。

英語の発表で、プレゼンを見ながらギリギリ追い付いたくらいなのでこの程度で。
質疑では、スマフォで録音した音声じゃなくてマイクにすれば~みたいな話がなされたが全く追い付けなかった(英語力が皆無)。


オーガナイズドセッション OS-7 意味と理解のコンピューティング (2)

オーガナイズドセッション OS-7 意味と理解のコンピューティング (2)

Linked DataやSPARQL、セマンティックWebの話を産業科学研究所の古崎先生がセッションしていた。
なかなか歴史のある話で面白かった。

後は宇宙航空研究開発機構宇宙科学研究所の人の宇宙研究で役立つ事はない話とか、雑誌取材の裏側の話とか。

後はおざき先生が人工知能学会、無限に楽しかったという話をしていて楽しそうだった。
DeepNetは夢があっていいですね。


05月25日

学生のポスターセッションがあったので、同じ部屋内にあった企業ブースは人が混み混みだった。

PFNの岡野原さんの公演を聞きに行きたかったが、こちらも激混みでダメだった…流石岡野原さん…


RDFデータベース構築によるユーザの気分に応じた観光スポット推薦システムの提案

RDFデータベース構築によるユーザの気分に応じた観光スポット推薦システムの提案

観光スポットの推薦システムをSPARQLとAndroid OS上で作成したよという内容。
推薦方法としては観光地データをTF-IDFに投げて、感情(喜べる, 泣ける,…,etc)で分けてユーザに推薦するという事だった。

指摘でもあったが、評価に利用したのがGoogle API等に比べた「新規性」なる軸であり、元となるデータベースが違うので怪しい部分があるが、感情によって推薦するシステムはかなり有意であるとは感じた。


価値観に基づく行列演算ベース情報推薦システムの提案

価値観に基づく行列演算ベース情報推薦システムの提案

商品推薦システムでよく使われる、行列計算を利用したMatrix Factorizationに価値観モデルを付与してやろうという話。

価値観モデルはある商品の属性に対するこだわりを定義するモデルで、簡易な数式ながら少ない評価数で安定したモデル化が可能となる事が知られている。
https://kaigi.org/jsai/webprogram/2017/pdf/310.pdf

Matrix Factorizationは予測評価値をユーザベクトルとアイテムベクトルとに分解し、行列計算と捉える事で、二乗誤差を使ったSGD等で最適化していく方法。
Matrix Factorization: A Simple Tutorial and Implementation in Python @ quuxlabs
MatrixFacorization を使った評価予測 ―アルゴリズムシリーズ 3―|サイバーエージェント 公式エンジニアブログ
Matrix Factorizationとは - Qiita

価値観モデルとなるベクトルを別途用意して、Matrix Factorizationしようという話。
従来の行列ベースのものと、手動でアイテムベクトルを設定したもの、SGDで最適化したものを比較していた。
SGDを回していくと精度が下がるなど少し疑念の残るグラフが示されていたが、価値観モデルを加えると初期の精度が良くなり、少ない評価で良い結果が出せる事が分かった。

指摘にもあった通りFctorization Machinesのような手法を使うともっと良くなりそうである。
Factorization Machinesについて調べてみた - Qiita

あと会場で調べてたらこんなんとか


行動経済学的な知見を用いた消費者の情報探索行動の予測とレコメンデーション法の開発

行動経済学的な知見を用いた消費者の情報探索行動の予測とレコメンデーション法の開発

あるWebサイト(中古車販売)のコンバージョンをRFやXGBoostを使って分析してみましたという内容。

価格帯や車種によって、特徴的なVCがあるという結果が見られた。

Google Analyticsの内容しか利用できなかったらしく、なかなか厳しそうだった。
分析データはやはり多い方が良い。


情報量に注目した推薦のための商品間関係性の分類法

情報量に注目した推薦のための商品間関係性の分類法

牛乳を買う理由にはクリームシチューを作る等の要因があり、同時に適した肉や野菜を買う。
つまり、商品関連性を見つければ次にユーザが買い物カゴに入れる商品が推定出来るのではという話。

条件付きエントロピー相互情報量を使い、「同時性・代替性(ある牛乳を買ったら他の牛乳を買わない)」「相補性(焼肉のタレを買ったら肉を買う)」「独立性(無関係な商品購入)」を定義し、次のネットショップにおける次の買い物商品を推定する。

結果を見ると、それなりに推定出来ながら、ネットショップなのでカップラーメンを複数種類まとめ買いされていたりとの課題は多かった。
加えて、ネットショップではUIによる差異が大きい事があると指摘されていた。確かにと思った。


- 聴講以外 -

はじめて企業ブースの中の人として参加してみて、自社説明や配布物配り、名刺交換をやった。
自社Tシャツを着た。完全に会社の犬だ。

学生時代は「なんで学会に企業ブースなんか出すんだろう」と思ってたけど、社会人になって企業ブースに企業側として参加してみて「なんで学会に企業ブースなんか出すんだろう」ってなった。

まあ冗談だが、ブランディング等の効果はあったと思う。
人工知能というか、機械学習にかなり注力しつつあってかぐるますたーガンガン雇ったりしてるので、機会としては良かった。
研究界隈の大ボスみたいな人が来て、クッソビビリながら自社説明するのとかスリリングで良かったです。
これからも弊社をよろしくお願いします。


個人としては幾人か昔から知っている人や、Twitterで知り合った人と挨拶出来たのが良かった。
挨拶してくれた皆さんありがとうございます。
挨拶できなかった人もまたいつか。


MicrosoftとPFNのMeetupではChainerの良い話が沢山聞けた。
海野さん相変わらず面白かったし、PaintChainerの心暖まる話も良かった。
MSの発表はde:codeで見たのと大体同じだったが、新しいSur◯aceをプレゼントする企画があってビビった。
色んな人に挨拶できたし、shi3zの激しい発言を聞けて良かった。


名古屋メシは味噌煮込みうどんが舌に合わなかった。
(有名な山本なんとか行ったんですがアレ美味いって言うんですか…)

でも、手羽先はめっちゃうまかったし会場近くに飲み屋があったのも良かった。

会場ちょっと狭くて廊下と階段なかなか辛かったけど、場所はめちゃ良かったよね。
美味しいご飯情報もネットで得られて良かった。


- おわりに -

知見が多く得られて、多くの人に会えて良かった。

ネットでは色々あったけどそっちはTwitterで。

あとこれ名古屋で一番ウケました。



 

DCGANで名刺のデザインを試みた

- はじめに -

社内ハッカソンと社内勉強会のネタとして、今更ながらGenerative Adversarial Networks*1 (GAN)とその応用とも言えるモデルであるDeep Convolutional Generative Adversarial Networks*2 (DCGAN)について調査し、実際に検証を行った。

この記事は、DCGANについていくらか調査、検証した部分について記述しておくものである。
なお、画像生成系のモデルは以前より話題になっていたため論文には目を通していたが、実際に触ったのは初めてである。

題材として「名刺」の画像をDCGANで生成する事を試みた。
その過程と結果を示す。


- GANとDCGAN -

DCGANはGANに対してConvolutional Neural Networks(CNN)を適応する構成手法のようなものである。

生成モデルにおけるGAN

GANは、2つの生成モデルを相互に学習させることで学習データが形成する分布をより汎化な状態として保持できるというもので、PRML的に言うと伝承サンプリング(ancestral sampling)を使う生成モデルの一種である。
伝承サンプリングはMCMCのようなサンプリングを使うモデルに対して、計算量が少なく済む事がメリット。中でもHelmholtz Machineや変分AutoEncoderに代表される生成モデルと推論モデルを同時に学習させる手法に比べ、よりシャープな自然画像を生成できるのがGANである。

GAN自体は利用する生成モデルについては限定していないが、2つのモデル間のJSダイバージェンスに対してmin-max最適化を行うように捉える事で、2つのモデルは相互に更新しあいながら学習させられ、またBackpropagationが適応できるので勾配の近似と計算量削減ができて良いよねという旨である。

GANの2つのモデルはDiscriminatorとGeneratorに分かれる。
学習におけるGeneratorの目的は、ランダムノイズからDiscriminatorが誤認識するようなGenerator用の入力を作れるようになること。
学習におけるDiscriminatorの目的は、学習データとGeneratorが作る入力を正しく判定できること。
この2つが相互に上手く学習できれば、Generatorはより学習データに近いものが生成できるはずという旨である。

後述するが実際には、この学習は難しく、大きなネットワークモデル等を利用する場合にはパラメータ設定ゲーとなる。

DCGAN

本題のDCGANは、GANにCNNを上手く適応させるため以下のような事を行っている。

  • Pooling layerを以下に置き換えてアップサンプリング
    • D : Strided convolution
    • G : fractional-strided convolution
  • Batch Normalizationを使う
    • 学習を早くできるし過学習をそれなりに防げる
  • 全結合の隠れレイヤーは取り除く
    • 全結合層を全部除くとの文献を見かけるが正確にはglobal average poolingに置き換える(?)である。「出力層手前では、全結合するのではなくて一つの特徴マップに一つのクラスが対応するように設計する」というのが正しそう。
  • Leaky ReLU関数を使う
    • D : 全てLeaky ReLU
    • G : 基本はReLUで出力層はtanh
    • x<=0でも学習が進み、かつ過学習しないように

こういう感じでGANにCNNを適応していくと、CNN使っても安定して上手いこと画像データの生成ができるよ!というのがDCGANの立ち位置だと思われる(?)。
今回後述の実験では利用してないが、他にも「DCGAN(もしくはGAN)で上手いこと画像を生成する方法」は多く論文としてまとめられていて、社内勉強会で上記内容を発表した際にいくつか教えて頂いたので記述しておく。

[1606.03498] Improved Techniques for Training GANs
Feature matching含むいくつかの最適化手法と半教師あり学習によって、画像の生成の成功確率が上がる。

[1606.00709] f-GAN: Training Generative Neural Samplers using Variational Divergence Minimization
GANで使うJSダイバージェンスをfダイバージェンスというものに置き換えましょうという論文。
KLダイバージェンス等とも比較している。

他にも、GeneratorがDiscriminatorを確実に騙せるような画像を学習によって得てしまい、出力画像が特定の画風に固定化してしまう問題に対して「バッチ重みを上手く全体に適応すると良いよ」等の指摘を頂いた。今後時間があれば試してみたい。


- DCGAN関連のプロダクト -

社内勉強会なので、プロダクトと一緒に紹介した。
若干雑だがせっかくなのでそのままコピペで以下にまとめておく。

その他

- DCGAN参考文献 (Web) -

Web上で学ぶ時に有益だと感じた文献。

関連研究を知る

周辺のモデルの流れを掴むには@beam2d氏が公開する以下の資料が分かりやすい。
https://www.slideshare.net/beam2d/learning-generator

生成モデルに関する論文をまとめた以下のような記事もあり、RBMやVAE関連で調べる際は参考になる。
(こちらは随時更新されているようだが誰が書いているか知らない)
memonone: 生成モデル(Generative Model)関連の論文まとめ

GAN系の最適化とか応用研究についてのスライド
[DL輪読会] GAN系の研究まとめ (NIPS2016とICLR2016が中心)

概要を掴む

@miztiさんのブログ。DCGANの概要がつかめる。
できるだけ丁寧にGANとDCGANを理解する - 午睡二時四十分

わかりやすくDCGANについて書いている。概要がつかめる
なんちゃって!DCGANでコンピュータがリアルな絵を描く - PlayGround

スライド。概要がつかめる。画が多い。
Deep Convolutional Generative Adversarial Networks - Nextremer勉強会資料

GANの更新規則についての記事。GIFが分かりやすい。
An Alternative Update Rule for Generative Adversarial Networks

数式に対する解釈が分かりやすいと思う。
[Survey]Generative Image Modeling using Style and Structure Adversarial Networks - Qiita

めちゃくちゃ丁寧にGANについて書かれている。多分Webだと一番丁寧。
はじめてのGAN


- DCGANで名刺画像生成に挑戦した -

事前準備

今回は社内ハッカソンと社内勉強会のための時間を使って、DCGANを試した。
主には以下の@mattya氏がchainerで書いたDCGANをcloneし、モデルとパラメータを調整した。

GitHub - mattya/chainer-DCGAN: Chainer implementation of Deep Convolutional Generative Adversarial Network

課題として、「DCGANは文字や文字構成を学習できるか」という事を見ておきたく、画像サイズが小さく定形な物があるという事で「名刺」を選択した。

学習データに利用した名刺画像はStanfordのデータセットや「名刺 サンプル」「Business Card」で検索してスクレイピングし、2万枚程集めた。
たまに「企業名 名刺」で検索すると山のように名刺を出してる所があって、日本語の企業一覧使って検索してみて包含したので、データセットとしては若干偏っているかもしれない。

名刺のサンプル画像には1枚の画像に数枚入っていたり、お洒落を意識した傾きがあったりするので、ラプラシアンフィルタを使ってエッジを取って、四角があったら名刺だろうといったコードを書いた。
以下を参考に(というかほぼコピペ)したが、2値化するよりHough変換した方が良かった気がする。
ホワイトボードの画像からポストイットを検出する - Qiita

増量して大体5万枚くらいになって、1週間程仕事終わりに目視で「あ~まあこれならええやろ」くらいの絞り込みを行った。
学習データとしては画像集めるだけだったので、まだマシだった。

元々、これをやろうと思い立ったきっかけは画像認識系のコンテストで、そちらのデータセットには名刺の画像と各情報の矩形位置まであったのを見て「これ使ってアップサンプリングすれば行けそうだな」と思っていて使いたかったんだけど、コンテストページ行ったらもうダウンロードできなかった…

名刺管理系のサービスは大体スクレイピング禁止なる項目が書かれてあるので、大丈夫そうなデータしか集められなかったのは事実。

また、名刺は縦横があるんだけど、全部横向きの物を目で選んだ。
理由は同時に学習できるか分からないのと、widthとheightが違うので合わせるのが大変そうなのと、サンプル名刺では等に横向きの物が大半だったから。
縦向きの名刺ダサいし仕方ないね。

名刺は日本語や英語だけでなく、色々な言語の名刺を入れた。
(じゃないと学習データが確保できなかった)
一応アラビア文字とかは目視で見つけた場合のみ外したはず。
あと画像サイズを縮小して、名刺の縦横比率の2倍の110*182にした。

学習とその結果

学習には絶対最強のp2.16xlargeを利用した。申請して2日くらいかかって使えるようになったと思う。構成はChainer周りのDockerでドッカーン。

会社からAWSサービスを使う時に補助が出たりするけど、4~5日程p2インスタンスを利用したら、会社補助上限を突き抜けて+3万円くらいが消えた。
事前準備とサーバへの画像アップロードも時間がかかったが、大体学習の時間。
方法としてはモデルをまず考えて、数時間回してみて期待できそうなら~という感じ。アナログ。
最初の画像が見れるまでの速度的にp2.16xlargみたいな高火力なやつ使った方が、実験はできると思う。
これから見せる画像は、インターネットにある名刺画像と俺の財布が作り出した結果だと思って見ていって欲しい。

学習初期

> い つ も の <
f:id:vaaaaaanquish:20170319191349p:plain:w200:h200

f:id:vaaaaaanquish:20170319191754p:plain:w200:h200

それっぽさがあるがこの辺りはまだ不安。

画像が生成されはじめる

50万回程が学習を回した段階
f:id:vaaaaaanquish:20170319191920p:plain:w200:h200

100万
f:id:vaaaaaanquish:20170319192040p:plain:w200:h200

この辺で少し安心できる

かなり名刺

330万程学習を回した時

f:id:vaaaaaanquish:20170319193627p:plain:w550:h400

いやこれ名刺っぽいなと思った。
こういうデザインの名刺見たことある。
企業アイコンがあって、よくわからん線が入ってて名前の横に役職とかがあって~というやつ。
ちょっと感動した。

学習過程をGIFにしてみた。

f:id:vaaaaaanquish:20170319215634g:plain

学習終盤では一度生成画像がリセットされ固定化されてしまっている。
これは、GeneratorがDiscriminatorを確実に騙せるような画像を学習によって得てしまった故の結果であり、先述したようにバッチ重みを上手く全体に適応する等の対策があるらしい。一応途中途中でモデルと出力は保存しておくのが良い。


- おわりに -

GANとDCGANについて調べ、ソースを見てモデル調整を行った。
また、そのモデルを用いて名刺画像の生成を試みた。

文字を上手く出して欲しかったが、あまり文字として読めるものにはならなかった。
後で学習データを眺めると画像によっては文字が潰れていたので、学習結果で思ったより文字が上手く出てなかった原因はここにもあるかもしれない。

また、学習データが少なかった事と背景が真っ赤だったりする名刺が混ざっていたために、一部名刺とは言えない画像が生成されるのがネックという感じだった。学習データは大事。

ただ、名刺で見たことあるような全体のデザインや、有り得そうなロゴマーク、背景等が生成できた。
DCGANは、画像内に描かれているものが文字であってもそれらの配置を学習し、生成する事ができるという事が分かった。


ちなみに明日は僕の誕生日です。やったね。