病みつきエンジニアブログ

機械学習、Python、Scala、JavaScript、などなど

「メールの添付ファイルにパスワードかけて、別メールでパスワードを送る」に言いたいこと

(2015/8/29追記)

最初に代案だけ書いておくと、(メールで送る程度の秘匿性のものは)「Proself」みたいな別プロトコルを使う、です。
メールパスワードでは、パスワードに規約をつけることもできません。
また、この話は企業等においてのルール化の話です。


もう何年前のネタなんだろうという感じでもあるのですが、2015年現在もこの慣習はなくなっていないように感じます。 実際社会人になってからも、残念なことに一度言われたことがありますし(受け売りでしか喋れなかったんだと思いますが)、自分もやったことがあります。

そこで、いくつかのケーススタディーから「この方法に意味があるのか」そして「どういう方式がセキュアか」という話をします。

メールのアカウントが漏洩するケース

例えば、POP3アカウントのIDとパスワードが流出して、アカウントに不正ログインされてしまったと仮定します。

このケースの場合は、添付ファイルを含むメールも、添付ファイルのパスワードを含むメールも流出していることになるので、意味がありません

これらケースでセキュアにするとしたら、ZIPはメールで、パスワードは電話や手紙・口頭(=別のプロトコル)で通知する必要があります。

メールを誤送信するケース

例えば、メールを誤送信する確率が、仮に1%だとします。

そのとき、(当たり前ですが)「添付ファイルつきのメール」を誤送信する確率も、1%です。したがって、「添付ファイルそのもの」を誤送信する確率は、全く変わっていません。しかも、パスワードを送るメールアドレスをコピペしてたりすると、結局確率は変わっていないわけです。

そこで問題になるのは、ZIPファイルのパスワードに意味があるのか、という問題です。意味がないとは言わないのですが、数文字程度の英数字とか、辞書に含まれる単語とかを使ってしまうと一瞬で解読されますし(ブルートフォースアタックや辞書攻撃)、パスワードはいずれ解読されるリスクがありますので、流出した時点でアウトです。例えば、暗号化(ハッシュ化)されたデータベースであっても、漏洩するとニュースリリースを出したりします。

パスワードの解読にかかる時間を見てみると、Password Recovery Speeds というサイトでは、英文字だけの場合、Class Eのコンピュータ(Workstation)で10桁のパスワードでも、16日で解読が終わると書いてあります。

したがって、このケースで問題ないのは、解読するほど価値が大きくない情報の場合です(トイレの鍵、という比喩ですね)。

一つ補足すると、ZIPファイルのパスワードを解読されたところで、不正アクセス防止法には該当しないのではないかと思いますので、法律による保護を求めるのは厳しいと思います(判例あるのかな・・・?)。

どうするのが良いか?

“慣習”方式は、あまり意味がないということをつらつらと書いてきました。私は、この“慣習”は止めるべきであるという意見です。

なぜならば、これをセキュアな方式だと勘違いする人が一定数いて、本当に大事なデータを上記の方法で送ってしまい、漏洩するリスクを高めるからです。そして、より良い代案があるからです。

このようなやり方をちゃんとやろうとすると、記号を含むような15桁とかのパスワードを設定して、別のプロトコル(電話とか)を使ってパスワードを通知するべきだ、となります。面倒くさいですし、ルール化するのは困難であると思います。

代案というのは、例えば、Proselfのようなパッケージを使うというやり方があります。Proselfはユーザーにセキュアなパスワードを強制できますし、ZIPよりもブルートフォースの速度が落ちますし、ログも残ります。(サーバー自体が脆弱である可能性は残りますが、責任の所在は集約されます)

さらに本当にセキュアにするのであれば、中間者攻撃のリスクも懸念して、暗号化ハードディスクで授受をする、という方法もあります(笑)

まとめ

以上をまとめると、 「メールの添付ファイルにパスワードかけて、別メールでパスワードを送る」という方式に対して、まずは「あまり安全ではない」ということを認識する必要があります。

その上で、適切な方法を選択すべき、ということでした!

スパースな行列のPearson相関係数

Scipyには、ピアソン相関係数を計算するための関数、scipy.stats.pearson というものがあるのですが、残念ながらスパースな行列(scipy.sparse)には対応していません。

実際、実装を見てみると(stats.py)、

mx = x.mean()
my = y.mean()
xm, ym = x - mx, y - my

という実装があり、単純に実装すると疎行列としては効率が悪くなります。 仮にxが疎行列だったとして、x - mx は要素がほぼ -mx な密行列になってしまうからです。

これは、定義の\sum_{i=1}^{n}{\left( x_i-\bar{x} \right)\left( y_i-\bar{y} \right)} に当たりますが、式変形をすると、陽に平均を引く必要がなくなります。(Wikipediaより引用)

f:id:yamitzky:20150527150315p:plain

ということで、これをコードに直して、

import numpy as np
import scipy.sparse

def pearsons(a, b):
    if not (scipy.sparse.issparse(a) and scipy.sparse.issparse(b)):
        raise ValueError("only sparse arrays are supported")
    if a.shape != b.shape:
        raise ValueError("shape of sparse arrays must be same")
    if a.shape[0] != 1 or b.shape[0] != 1:
        raise ValueError("size of dimention 1 must be one")
    n = a.shape[1]
    a_sum = a.sum()
    b_sum = b.sum()
    nmr = n * a.multiply(b).sum() - a_sum * b_sum
    return (nmr /
            np.sqrt(n * a.multiply(a).sum() - (a_sum) ** 2) /
            np.sqrt(n * b.multiply(b).sum() - (b_sum) ** 2))

実際に検証してみると、以下のように同じ値になります。

import scipy.sparse
from scipy.stats import pearsonr

a = scipy.sparse.csr_matrix([1, 2, 1, 0, 1])
b = scipy.sparse.csr_matrix([1, 0, 1, 4, 2])

print pearsons(a, b) # -0.93250480824031368
print scipy.stats.pearsonr(a.toarray()[0], b.toarray()[0])[0] # -0.93250480824031379

Pandas経由でHiveQLを実行してDataFrameに簡単に入れる方法

Hive経由で集計した値を、Pandasからスムーズに使うための方法を紹介します。 "スムーズ"に、というのは、「CSVを経由しない」と言い換えてもらって大丈夫です

準備

ライブラリとして、DropboxPyHive と Clouderaの impyla が必要です。

PyHiveを使っている理由は、必要な手続きが短いのと、PEP-0249に準拠しているからで、impylaを使っている理由は、as_pandasというユーティリティ関数を使いたいだけです。 なので、必須でないといえば必須でないです。

Anacondaを使っている場合は、下記の手順でインストールできます。

pip install impyla
conda install -c https://conda.binstar.org/blaze pyhive

コード

from pyhive import hive
from impala.util import as_pandas

conn = hive.connect(host="localhost")
cursor = conn.cursor()
cursor.execute("SELECT * FROM table")
df = as_pandas(cursor)

df.describe()

で、df にPandasのDataFrameオブジェクトが入ります(impylaの代わりにhiveになっているだけ)。 そんじゃーね!

アメブロでソースコードとかを投稿する方法、またはGithub Flavored Markdownで投稿する方法

お疲れ様です(?)

私の所属する会社には「アメーバブログ」というものがあり、せっかくなら愛着のある自社製品を使いたいところですが、残念なことに プログラマー向けの機能は全然足りません*1。ということで、弊社のプログラマーは、プログラミング系の話題ははてなブログに書いたり、github.ioに書いたり、qiitaに書いたりする人が多い。

しかし! 公式ブログはさすがにアメブロなので、アメブロでもソースコードを投稿しやすくする方法 というのを考案してみました

事前準備

アメブロの投稿画面を「パワーアップした新エディタ」にしてください

f:id:yamitzky:20140511124810p:plain

設定方法は、投稿画面の「パワーアップした新エディタを使おう」をクリックするか、「基本設定>記事投稿画面>新エディタ」です。

アメブロを使わずに書く

タイトルからして真っ向から反抗している感じがしますが、アメブロを使わずに書きます

ソースコードを綺麗に書けるエディタなら何でもいいですが、例えばGithub Flavored Markdownなエディタを使って、まず記事を書きます。

例えばGFMarkdownEditorとかMarkdown Editorとか、「Github Flavored Markdown javascript」で検索して出てくるものとかがお勧めです。もしくは、これらをForkして自分好みのエディターを作ろう!\(^o^)/*2

f:id:yamitzky:20140511125825p:plain

アメブロに貼り付け

記事を書いたら、エディタのプレビュー画面をコピーして、アメブロにペーストするだけです。

f:id:yamitzky:20140511131320p:plain

これでストレスなく、アメブロに技術ネタを投稿できるようになりました!

*1:ターゲットの問題がありますので、これは至極真っ当なことです

*2:本当はQiitaを使いたいんですが、コード上のcssがオリジナルであると想定されること、そしてそのcssが、Qiita上で使用することを前提に書かれていることを想定されると考えると、この目的に使うべきでないと考えます。

jedi-vimでanacondaのパッケージを補完させる

jedi-vimという、vimで(賢く)Pythonの補完などをしてくれるプラグインがあります。vimPython書くなら必須かも、というレベル。

当たり前(?)の話ですが、pipでインストールしたようなPythonのパッケージ群も、賢く補完してくれます。

しかし、デフォルトで使用するPythonが、システムのPython(/usr/bin/python)らしく、必然的にシステムのpipでインストールしたものしか補完してくれません。

そこで、anacondaのpipでインストールしたものなども補完できるようにしてみました。

やり方

やり方は単純で、~/.vim/ftplugin/python.vimの中に、以下のスクリプトを書くだけ。

python << EOF
import os
import sys

home = os.path.expanduser("~")
path = home + "/anaconda/lib/python2.7/site-packages"
if not path in sys.path:
  sys.path.insert(0, path)
EOF

要するに、anacondaのsite-packagesをsys.pathに追加しているだけです。同じ要領で、パッケージのディレクトリを増やすことも可能でしょう。

(おまけ) virtualenvを使う

virtualenvで管理している場合は(anacondaの場合そんなことできるのかしらんけど)、jmcantrell/vim-virtualenvを使うとvirtualenv環境のpythonが使われるらしい。試してないので知らないけども。

株式会社CyberZで働くことになりました/後輩の方々にお願い

株式会社サイバーエージェントに入社し、早期配属をすることができ、株式会社CyberZで働くことになりました。

CyberZは、サイバーエージェントの子会社で、スマホ向け広告効果計測ツールを作っている会社です。

CyberZで何をしたいかというと、ざっくりと言うとデータに関わる仕事(a.k.a. データサイエンティスト)をしたいな、と思っています。これから、サーバーサイドエンジニアとして成果をぐいぐい残していきながら、データに関する仕事をぐいぐいやりたい、です。だからこそ、CyberZを第一志望で配属希望を出しました。

あんまり話つながってないですが、ついでにお願いしておきますと、アドテクノロジー関連の情報収集の仕方をお伺いしたいです。海外でadtechとかadtechnologyとかで検索してもなかなか良い情報ソースを見つけることができていません・・・。特にニュースレター(メルマガ)なんかあると最高なんですが。

後輩の方々へお願い

すでに面識があって弊社(CA)に内々定をもらっている方、そして面識がなくても内々定もらっている方々へ、お願いというかアドバイスというか。

私はもともとサイバーエージェントの広告代理店部門のデータ分析チームで内定者アルバイトをしていました。したがって、こちらのデータ分析チームがいかに優秀な方々であるか、いかに一緒に働いていて楽しいか、というのを十分知っています(ここ宣伝ですよ!)。

逆の視点から言うと、一緒に働いてみないと、そこで働くことがどれほど素晴らしいか、というのを本当の意味で実感することはできません。もちろん、これから働く先の方と働くのは楽しいだろうな、と感じていますが*1、その確信度は内定者アルバイトをした方が高くなります(ここ、ニュアンスが難しい)。

また別の視点で見ると、弊社内定者は、内定者アルバイトという機会で、知る機会をもらっていることになります。

そこで私から、後輩の方々へお願いなのですが、どうか内定者アルバイトを2箇所以上でやってください。そしたらランチでも飲みでもおごるので、声をかけていただけると大変嬉しいです。

以上、NDAはちゃんと避けていると思うのですがどうでしょうか。。。

*1:だからこその第1希望です

Theanoを使ってPythonで行列演算とロジスティック回帰

TheanoというPython用のライブラリがあります。

ちょっと勉強したので、チュートリアルを日本語に翻訳しつつ、使い方とかを紹介します。

Theanoとは

まずはじめにTheanoとは、について。

Theanoはおそらく「テアノ」と読むのが多分正しいです。ピタゴラス(Pythagoras)の妻です。PythonとPythagorasをかけてるっぽいです。

何ができるかというと、簡単に言えば、行列演算ができます。以下の特徴を持っています(公式サイトより抜粋)

  • 実行スピードの最適化: Theano は g++などを使って式をコンパイルし、CPUやGPU操作に変換します(つまり、pure Pythonなコードよりも速い)
  • symbolicな微分: Theano は勾配を計算するために自動的にsymbolic graphsを組み立てます(訳注:つまり微分に便利だということ)
  • 安定な数値計算のための最適化: Theanoは不安定な数値計算を認識し、安定なアルゴリズムに変換します(訳注:例えばlogsumexpなどを言っていると思います)

なぜこれができるかというと、変数をシンボルとして扱うからだと思います。例えば、--xxにしたり、x * y / xyに変換することなどが明記されています。*1

この、変数を変数のまま保持しておくような考え方は、Theanoの設計を理解する上でちょっと重要な気もします。

また、Deep Learningとかニューラルネットの実装のためにpylearn2というライブラリが使われることもあるようですが、pylearn2はTheanoに依存しています。Theanoは、行列演算と微分の形になるNeural Networkと相性が良さそうですしね。

チュートリアル

(ロジスティック)シグモイドを造ります。

 s(x) = \frac{1}{1+e^{-x}}

で表せますから、これをそのまま数式にしてあげます。

まずは、必要な物をimportします。

import theano.tensor as T
from theano import function

次に、関数の引数に必要な変数を定義します。Theanoは型をちゃんと指定する必要があります(なんとなく、こうすることによって内部的に計算が高速になるメリットがあるんじゃないでしょうか)。

x = T.dscalar() # double型の数値
# x = T.dmatrix() # double型の行列

次に、シグモイド関数の計算式というか、関数の形を定義してあげます。

s = 1 / (1 + T.exp(-x))

このままでは、シグモイド関数sは呼び出し可能ではありません(s(0.5)みたいには呼び出せない)。ということで、呼び出し可能な関数を作ってあげます。

function 関数を使いますが、第1引数は、関数に必要な引数(ない場合は[])。第2引数は、「returnしてほしいもの」言い換えると関数の形とかです。

logistic = function([x], s)

ここで多分コンパイルが走ってる気がします。そしたらあとは呼び出すだけです。

logistic(0) # => array(0.5)

ここで、先ほどのxの型を「行列(dmatrix)」とかにしてあげると、呼び出し方も変わって、

logistic([[0]]) # => array([[0.5]])

となります。また、引数は行列なので、もちろん[[0, 1], [1.5, -1]]みたいな感じで行列を突っ込んで上げても良いです。

また、出力ももう少し拡張性が高いです。例えば、関数の形を配列に突っ込んであげたり。

logistic = function([x], [s, s])
logistic(0) # => [array(0.5), array(0.5)]

値の保持

値をどこかグロバール領域に保持しつつ計算することもできます(shared variables)。チュートリアルのサンプルでは、勾配法でロジスティック回帰を行っていますが、重みを保持し(つまりreturnしない)、ひたすら書き換えることによって実装しています。

このメリットは、グローバル変数と同じく共有しやすいことと(結果として表記上わかりやすくなる)、主に計算上のメリットだそうです。

shared variableを使うにはshared関数と、update引数を使います。

from theano import shared
state = shared(0) # 0は初期値
inc = T.iscalar('inc') # integer型のスカラー
accumulator = function([inc], state, updates=[(state, state+inc)])
# equals function([inc], state, updates={state: state+inc})

functionの引数のうち、2つめは必須ではありません。あくまで実行前の状態をreturnしてくれるというだけです(状態が確認しやすいだけ)。updateの引数は、各shared varibleに対する操作の詳細を示します。(書き込み先のshared variable, 関数の形)というタプルで指定します。まあ意味的には辞書みたいなものなので、dict型でもいいです。

乱数

乱数を使うこともできます。

from theano.tensor.shared_randomstreams import RandomStreams
srng = RandomStreams(seed=234)
rv_u = srng.uniform((2,2))
f = function([], rv_u)
f() # such as => array([[ 0.28179047,  0.23616647], [ 0.5958365 ,  0.1385743 ]])

例えば、box-muller変換を使った標準正規分布は、

rv_X = srng.uniform((1,2))
rv_Y = srng.uniform((1,2))
box_muller = T.sqrt(-2 * T.log(rv_X)) * T.cos(2 * math.pi * rv_Y)
normal = function([], box_muller)()
normal() # => array([[-0.53976774,  1.09561059]])

ただし、同じ乱数は式中で同じ値になるので注意が必要です。

function([], rv_X - rv_X)() #=> array([[ 0.,  0.]])

ロジスティック回帰の実装

説明に疲れてきたので詳細は割愛します、本家のチュートリアルを見てください。

こちらでは、L2正則化つきのロジスティック回帰が実装されています。具体的な微分式を与えず、

gw, gb = T.grad(cost, [w, b])

とすることで誤差の微分(勾配)を求めています。そして、

train = theano.function(
          inputs=[x,y],
          outputs=[prediction, xent],
          updates=((w, w - 0.1 * gw), (b, b - 0.1 * gb)))

という風に、updatesに((w, w - 0.1 * gw), (b, b - 0.1 * gb))としています。これは更新率ηを0.1として、shared variableのwをひたすら書き換える、という感じです。

また、このoutputs引数に指定された[prediction, xent]は全く使われていませんので、[]にしても動くと思います

参考文献

*1:実際にどれくらい認識して数値計算を安定化、もしくは省略してくれるのかはわからないので、結局自力で一番良い計算方法をコードに落としこむことが多いと思いますが。。。