中野智文のブログ

データ・マエショリストのメモ

python ライブラリの gcloud(google.cloud) の storage の blob_name とは

背景

python ライブラリの gcloud(google.cloud) の storage の get_blob において、

https://googlecloudplatform.github.io/google-cloud-python/0.20.0/storage-buckets.html#google.cloud.storage.bucket.Bucket.get_blob

とあるが、試してみると、google cloud storage 上の

gs://my-bucket に、/path/to/blob.txt

は存在するのに、なぜか、 None が返ってくる。ほえ?

blob_name には 始めのスラッシュはいりません

すなわち、blob_name

path/to/blob.txt

ということです。だったらそのように例を(次のように)

>>> from google.cloud import storage
>>> client = storage.Client()
>>> bucket = client.get_bucket('my-bucket')
>>> print(bucket.get_blob('path/to/blob.txt'))
<Blob: my-bucket, path/to/blob.txt>
>>> print(bucket.get_blob('does-not-exist.txt'))
None

と書いてよ、と思う。

sklearn にて `class_weight` が `balanced` か「なし」の探索

背景

sklearn の GridSearchCV はパラメータサーチに利用される。

sklearn.model_selection.GridSearchCV — scikit-learn 0.19.0 documentation

ところが、パラメータなし、の設定例はみつからない。

sklearn.linear_model.LogisticRegression — scikit-learn 0.19.0 documentation

には、dict"balanced" かと書いてある。

パラメータなしは、None

class_weightbalanced か「なし」の探索

"class_weight": ["balanced", None]

Arduino (Uno R3互換)を Mac でLチカくらいまで。

準備

購入したもの

amzn.to

マニュアル

CD 内の K4 というマニュアル/K4/k4 Japanese tutorial.doc

ダウンロード

Arduino - Software

インストール

本体の準備

本体(UNO R3)をUSBケーブルで接続。緑LEDの点灯を確認。

ソフトのセットアップ

  • 上記のファイルをダウンロード後、解凍し、起動。
  • 次にメニュー、ツール→ボード→Arduino/Genuino Uno を選択。
  • 次にメニューの、ツール→シリアルポート→/dev/cu.usbmodemFD131 (Arduino/Genuino Uno) を選択
  • 次にメニューの、ツール→ボード情報を取得 で無事表示されれば準備完了。

Lチカ

Lチカとは、LEDチカチカの略であるが、次のようにまず example から blink を選ぶ

f:id:nakano-tomofumi:20171007215924p:plain

そして、画像のように、左から二個目のボタンを押すと

f:id:nakano-tomofumi:20171007224244p:plain

USBケーブルを通じて、Arduino に書き込まれ、リスタートして、Lチカが始まる。 もしかしたら、初期状態でもLEDが1秒毎に点滅していたので、Lチカが動いていたのかもしれない。

その他

チュートリアルはマニュアルが雑で、画像が間違ったポート番号に刺さっていたりとか普通にあるので注意した方がいいし、 マニュアル中のコードは word 上で貼り付けられているので、インデントが死んでいるし、// のコメント行の最後に改行した後に書かれなければならない、} が書かれていたりするので、 そのままだとコンパイルできないことがあったりするが、C言語をやったことがある人なら、それくらいのノイズは大したことなく、こなせることであろう。

CDの K4 の中に、code というフォルダがあるので、その中にまともなコードが有るかと思ったら、pde ってなんだろう。

シンギュラリティは永久機関のようなもの

背景

社員旅行の夜、シンギュラリティについて質問された。 それまであまりよく考えたことがなかったけど、このときに自分の中でかなり整理された。

無限に賢くなる?

シンギュラリティの主張は、

  • AIがもっと賢いAIを作る。
  • これを繰り返す。
  • 最終的に圧倒的に人より賢いAIが作られる。

確かにこれを聞けばすごくAIは賢くなる気がする。

賢さとは何か?

賢さとは何なのか。考えてみる。

  • 計算スピード。すでにコンピュータのほうが早い。でも人間より賢いわけではない。
  • 記憶力。コンピュータの方が多く記憶できるし、早く記憶できる。でも人間より賢いわけではない。

賢さの要素となるものは既に人間より上回っているにも関わらず、賢いとはいえないわけである。

学習の性能とその上限

今シンギュラリティが話題となっている背景に、深層学習のような機械学習のブレイクスルーがあるので、(機械)学習の枠組みで賢さを考える。 (機械)学習の評価尺度に正解率というものがある。特に重要なのが、汎化性能とよばれる未知の問題に対する正解率である。

この未知の問題に対する正解率には実は上限がある。どんなに優れた機械学習手法においてもこの上限を超えることはできない。

例えば、

  • サイコロの目の数の予測

であるが、どのように優れた機械学習の手法を用いても正解率は6分の1を超えることはできない。

無限に賢くなる手法というのが提案されたとしても(事実Boostingは「訓練事例に対しては」無限に正解率が上がる)、アキレスと亀のように上限を超えることはできない。

賢い人が教えたらもっと賢い人になるか?

機械学習は新しいアルゴリズムを生成するものではない。だから、機械学習によって直にもっと賢い機械学習ができるわけではない。 ただし、機械学習のパラメータを効率よくチューニングするような話(メタ学習とよばれる)もないわけではない。 また機械学習アルゴリズムを一つの推論とみなすとき、強化学習のような方法で効率よく学習が出来るようになるかもしれない。 ところが、強化学習やメタ学習による機械学習手法が代表的な一手法として確立してない現状をみると、 この強化学習やメタ学習にブレイクスルーがなければ、将来的にもその可能性は低いと考えられる。

そもそも賢い人が教えたらもっと賢い人になるという前提自体が根拠が不明である。賢い人自身がもっと賢くなることはあっても。

計算複雑性理論は越えられない

また例えそうだったとしても、計算複雑性理論の上限を超えることは出来ないだろう。もちろん量子コンピュータなどの新しい可能性もあるが、賢さのみでハードウェアを構築することは出来ないし、 人間もそうだが、どんなに賢くても無限のリソースが手に入るわけでもない。物理的な制約まで賢さが克服する根拠はない。

結局、シンギュラリティは計算機科学における永久機関

結局、シンギュラリティに到達するには様々な制約があり、それが克服される技術的・科学的な根拠はまだない。 よって、個人的には永久機関のようなものと考えている。

json ぽいデータを引き抜くクソクエリ

背景

json に対応していない mysql 5.7 未満のシステムに、json で値が格納されている。

対応

クソにはクソで。 LOCATE と、SUBSTRING を駆使し、抽出を試みる。 ここに書かれているは方法は、「一般的に」JSONをパースして抽出する方法でなく、利用範囲が限られるアドホックな方法であること(だからクソ)に注意されたい。

戦略

SUBSTR は、

SUBSTRING(文字列, 開始位置, 文字数)

で開始位置から文字数分、文字列を抽出できる。よって、 抽出したい文字列の開始位置抽出したい文字列の文字数 が分かればいいことになる。

多くの場合は、抽出対象となる文字列は ..., <key>: <value>, ... みたいな形式になっており、抽出したい文字列自体(ここでは、<value> とする)は不定で、<key> をヒントに、「<key> の後のコロンの後に続く文字列」であることが多い。ここでは、これを例として話をすすめる。

抽出したい文字列の開始位置

とりあえず、 の開始位置であるが、

LOCATE("<key>:", 非抽出カラム名)

で取れる。よってこれに <key>: の長さを追加すれば、「の後のコロンの後に続く文字列」の開始位置が取れることなる。 この長さは、 丁寧にSQLLENGTH("<key>:") と計算してもいいし、自分で数えても良い。 空白の有無はそこはデータ依存によるが、後で TRIM を使うのであれば、空白が含まれていても問題ないだろう。

よって抽出したい文字列の開始位置は、

LOCATE("<key>:", 被抽出カラム名) + LENGTH("<key>:")

となる。

抽出したい文字列の文字数

抽出したい文字列の文字数は次のように求められる。

抽出したい文字列の文字数 = (抽出したい文字列の先頭からの文字列における)抽出したい文字列の次の文字列の位置 -1

である。ここで、 抽出したい文字列の先頭からの文字列 は次のようして求まる。

SUBSTRING(被抽出カラム名, LOCATE("<key>:", 被抽出カラム名) + LENGTH("<key>:")) 

よって、LOCATEを使い、 (抽出したい文字列の先頭からの文字列における)抽出したい文字列の次の文字列の位置 は、

LOCATE(",", SUBSTRING(被抽出カラム名, LOCATE("<key>:", 被抽出カラム名) + LENGTH("<key>:")))

となる。

まとめると

最後にすべてまとめると、

SUBSTRING(被抽出カラム名, 抽出したい文字列の開始位置, 抽出したい文字列の文字数)

に、抽出したい文字列の開始位置 を代入すると、

SUBSTRING(被抽出カラム名, LOCATE("<key>:", 被抽出カラム名) + LENGTH("<key>:"), 抽出したい文字列の文字数)

となり、ここに、抽出したい文字列の文字数 を代入すると、

SUBSTRING(被抽出カラム名, LOCATE("<key>:", 被抽出カラム名) + LENGTH("<key>:"), LOCATE(",", SUBSTRING(被抽出カラム名, LOCATE("<key>:", 被抽出カラム名) + LENGTH("<key>:")))-1)

となる。クソクエリの完成である。

まとめ

テンプレートを作った。

SUBSTRING(column,LOCATE(prefix, column)+LENGTH(prefix),LOCATE(suffix,SUBSTRING(column,LOCATE(prefix, column)+LENGTH(prefix)))-1)

ここで、column: 被抽出カラム名、prefix: 抽出したい文字列の前に来る文字列、suffix: 抽出したい文字列の次に来る文字列 である。

ただし、これは抽出したい文字列の次に来る文字列が固定されている場合に限定されるクエリである。最後にカンマがあったりなかったりするとうまくいかない。クソクエリだからである。

luigi で パラメータで与えられた日付をtimedelta を使って増減させたい

背景

luigi にて、パラメータで与えられた日付に対して、固定日分前(例えば一週間など)を求めたい。

from datetime import timedelta
import luigi


class MyTask(luigi.Task):
    date = luigi.DateParameter()
    start_date = date - timedelta(days=7)

と書くと、次のようなエラー。

unsupported operand type(s) for -: 'DateParameter' and 'datetime.timedelta'

確かに、date はDateParameterなので、エラーの通りなのだが。

stackoverflow.com

での、回答者のコメントでは、 “DateParameter returns a value that is a python date. ” といってるので、半分信じてしまった。 ちなみにこのQAは役立たない。なぜならデフォルト値の計算をしたいわけではないから。

クラス変数とインスタンス変数の違い

関数の中の、self.date はクラス変数と思っていたが、間違いだ。これはインスタンス変数だ。

厄介なことに、 self.date の表記は python 的にはクラス変数でもインスタンス変数にもなるらしい。 インスタンス変数として「初期化」すれば、クラス変数は隠蔽され以後インスタンス変数として扱われる。 ただ参照するだけならば、クラス変数として扱われる。

d.hatena.ne.jp

結局、self.dateインスタンス変数ということは、親のクラスのインスタンス初期化の処理などで、クラス変数の設定通りの動作が行われ、self.date というインスタンス変数に値がセットされたのだろう。

解決

from datetime import timedelta
import luigi


class MyTask(luigi.Task):
    date = luigi.DateParameter()

    def requires(self):
        start_date = self.date - timedelta(days=7)    

もし、固定の日でなく、パラメータで与えたいのであれば、TimeDeltaParameter を使おう。

http://luigi.readthedocs.io/en/stable/api/luigi.parameter.html#luigi.parameter.TimeDeltaParameter

luigi の RangeDaily をコードで使う

背景

範囲日の繰り返し DateIntervalParameter

指定した範囲の日の処理を指定したい場合は、チュートリアルには次のようなコード例がある。

http://luigi.readthedocs.io/en/stable/example_top_artists.html#step-1b-running-this-in-hadoop

    def requires(self):
        return [StreamsHdfs(date) for date in self.date_interval]

この date_interval はこれ以降説明など出てこない。もっと細かい仕様を調べたい場合は、 DateIntervalParameter のドキュメントを読む必要がある。

http://luigi.readthedocs.io/en/stable/api/luigi.parameter.html#luigi.parameter.DateIntervalParameter

何かちょっとドキュメントとして読みにくい。また DateIntervalParameter はタスクのパラメータであるので、 本当にほしいのは、渡された別のパラメータから日付の範囲を生成する DateInterval のような気がするのだが、

http://luigi.readthedocs.io/en/stable/api/luigi.date_interval.html#luigi.date_interval.DateInterval

ドキュメントは徐々に何も書かれていなくなってくる。

範囲日の繰り返し RangeDaily

ところで、luigi コマンド上で上記のような範囲指定を行いたい場合コードが書けないので、タスクのコードをDateParameterから、DateIntervalParameterに変えるとか、 それを呼び出す範囲日指定のラッパータスクを作成しなければならない。 そこでコマンドレベルでも特にコードを書かずに上記のような範囲の実行を行う一般的なラッパータスク RangeDaily が用意されている。

Luigi Patterns — Luigi 2.6.2 documentation

luigi --module all_reports RangeDaily --of AllReportsV2 --start 2014-10-31 --stop 2014-12-25

本題

RangeDaily はコードでも使えるよ、という話。RangeDaily であればドキュメントは割とあるのと、広範囲を誤って指定しても実行されないような仕組みがあるので、 そういった意味で比較的安全に範囲日を指定できるというメリットがある。

コード例

import luigi
from luigi.tools.range import RangeDaily
from my_sub_task import MySubTask

class MyTask(luigi.Task):
    date = luigi.DateParameter()
    start_date = luigi.DateParameter()
    opt1 = luigi.Parameter()
    opt2 = luigi.Parameter()

    def requires(self):
        return [RangeDaily(of=MySubTask,
                           start=self.start_date,
                           stop=self.date,
                           task_limit=100,
                           days_back=200,
                           of_params={"opt1": self.opt1,
                                      "opt2": self.opt2})]

関数の outputrun は必要。

task_limit days_back は安全のためのオプションで、それぞれデフォルトでは 50 と100 に設定されている。 詳しくは次で確認できる。

http://luigi.readthedocs.io/en/stable/api/luigi.tools.range.html#luigi.tools.range.RangeBase

http://luigi.readthedocs.io/en/stable/api/luigi.tools.range.html#luigi.tools.range.RangeDailyBase

of_paramsdate 以外のパラメータを渡すための、dict。 注意しなくてはいけないのは、 date型などを直接この op_params の value に入れると、JSON 化できないというエラーとなる。 文字列に変換してから入れる必要があるが、文字列に変換すると今度は DateParameter の方が日付型に変換してくれないというオチ。 (そんなときは無理してこの RagenDaily を使う必要はない)

コマンドライン

コマンドラインで呼び出す場合は次のようになる。

luigi --module my_task MyTask --start-date 2017-07-22  --date 2017-07-25 --opt1=aaa opt2=bbb --workers=5

--workers のオプションはここに来る。(RangeDailyのオプションではないので)。 また 今回のコードにする話に限ったことではないが、--stop はその日を含まないので注意。

(おまけ)その日を含むコードを実行

require にその日を追加する方法。

    def requires(self):
        return [MySubTask(date=self.date,
                          opt1=self.opt1,
                          opt2=self.opt2),
                RangeDaily(of=MySubTask,
                           start=self.start_date,
                           stop=self.date,
                           task_limit=100,
                           days_back=200,
                           of_params={"opt1": self.opt1,
                                      "opt2": self.opt2})]

stop に一日後を指定する方法。

timedelta を、 import する必要がある。

from datetime import timedelta # (←追加)
...
(中略)
...

    def requires(self):
        stop_date = self.date + timedelta(days=1)   
        return [RangeDaily(of=MySubTask,
                           start=self.start_date,
                           stop=stop_date,
                           task_limit=100,
                           days_back=200,
                           of_params={"opt1": self.opt1,
                                      "opt2": self.opt2})]