Hatena::Grouptumblr

saitamanodoruji

くこかー
saitamanodoruji.tumblr.com

2014-08-31

Exponential Endless Summer 2014

23:51 | Exponential Endless Summer 2014 - saitamanodoruji を含むブックマーク はてなブックマーク - Exponential Endless Summer 2014 - saitamanodoruji

要旨

Exponential Endless Summer (EES) を改訂しました. 第 4 版です.

今回は 2013-07-01 から 2014-06-31 までの Tumblr の Post ID を月毎に調べて指数関数でフィットし (Fig. 1), パラメータをスクリプトに追加しました.

Fig. 1 Tumblr の月毎の Post ID と指数関数によるフィット. 縦軸は対数. 青点が実測値, 赤実線がフィットカーブ (計算値). 16 の区間に分割してフィットしている.

http://f.st-hatena.com/images/fotolife/s/saitamanodoruji/20140830/20140830082545.png

今回新たにフィットをかけた 2013-07-01 以降の区間では, EES2014 の方が実測値に近い Post ID を算出できます (Fig. 2). 2013-07-01 より前の区間では EES2014 と EES2013.08 の関数は同じです.

Fig. 2 EES2014 と EES2013.08 の計算値の比較. Fig. 1 の 2011 年から 2014 年までを拡大したもの. 赤実線が EES2014 による計算, 赤破線が EES2013.08 による計算.

http://f.st-hatena.com/images/fotolife/s/saitamanodoruji/20140830/20140830085927.png

動作確認

EES2014 の性能を評価するために 10000 回の Post ID 選択を行い, 各月毎に選択回数を集計してヒストグラムを作成しました. 比較のために, 同様に EES2013.08 のヒストグラムも作成しました (Fig. 3). 2013-07 以降のポストが右肩下がりに選ばれにくくなってきていたところを修正できているのがわかります (Fig. 3 赤破線の円).

引いた Post ID に対する実際の日付及び時刻は http://www.tumblr.com/dashboard/2/{PostId} を取得して確認しており, 空の dashboard が返ってきた場合のデータは捨てています. Fig. 3 の EES2014 のヒストグラムで 2007-03 以前の Frequency が 2007-04 以後に比べて半分ほど小さいのは, 約 50 % の確率で空の dashboard が返ってきたことを意味しています. EES2013.08 では 2007-03 以前の Frequency が EES2014 ほど小さくないのは測定日の違いによる dashboard の底の変動の影響と考えています. タイミングによって古いポストが dashboard に現れたり現れなかったりする現象はよく見られます.

Fig. 3 EES2013.08 と EES2014 で引いた Post ID のヒストグラムの比較.

http://f.st-hatena.com/images/fotolife/s/saitamanodoruji/20140910/20140910185259.png

コード

// Exponential Endless Summer 2014
var draw = function(oldestID, newestID) {
  var oldestUTime = getUnixTimeFromPostID(oldestID);
  var newestUTime = getUnixTimeFromPostID(newestID);
  var drawnUTime = oldestUTime + Math.floor((newestUTime - oldestUTime) * Math.random());
  return getPostIDFromUnixTime(drawnUTime);
}

var getPostIDFromUnixTime = function(utime) {
  if ( utime < 1149465600000 ) {
    return;
  } else if ( utime === Number.POSITIVE_INFINITY ) {
    return Number.POSITIVE_INFINITY;
  }
  for (var i = 0; i < params.length; i++) {
    if ( utime <= params[i].upperBound ) {
      return Math.ceil(params[i].coefAlpha * Math.exp(params[i].coefBeta * (utime - 1136073600000)));
    }
  }
}

var getUnixTimeFromPostID = function(postID) {
  if ( postID < 1 ) { return; }
  for (var i = 0; i < params.length; i++) {
    if ( postID <= getPostIDFromUnixTime(params[i].upperBound) ) {
      return Math.floor(Math.log(postID/params[i].coefAlpha)/params[i].coefBeta) + 1136073600000;
    }
  }
}

var params = [
  // f_i(t) = Alpha_i * exp[Beta_i * (t - t_0)]
  // t: Unix time [msec]
  // t_0 = 1136073600000 [msec] (2006-01-01T00:00:00+09:00)
  {coefAlpha: 1.211 * Math.pow(10,  -1), coefBeta: 1.571 * Math.pow(10, -10), upperBound: 1160012789359},
  {coefAlpha: 3.619 * Math.pow(10, -13), coefBeta: 1.266 * Math.pow(10,  -9), upperBound: 1163269541924},
  {coefAlpha: 2.120 * Math.pow(10,   0), coefBeta: 1.846 * Math.pow(10, -10), upperBound: 1172120791893},
  {coefAlpha: 1.179 * Math.pow(10, -49), coefBeta: 3.331 * Math.pow(10,  -9), upperBound: 1173392561265},
  {coefAlpha: 4.702 * Math.pow(10,  -7), coefBeta: 7.024 * Math.pow(10, -10), upperBound: 1176405323219},
  {coefAlpha: 5.338 * Math.pow(10,   1), coefBeta: 2.425 * Math.pow(10, -10), upperBound: 1182930300850},
  {coefAlpha: 8.255 * Math.pow(10,   3), coefBeta: 1.349 * Math.pow(10, -10), upperBound: 1193299359828},
  {coefAlpha: 2.496 * Math.pow(10,   6), coefBeta: 3.509 * Math.pow(10, -11), upperBound: 1241622616806},
  {coefAlpha: 2.907 * Math.pow(10,   5), coefBeta: 5.546 * Math.pow(10, -11), upperBound: 1283088065116},
  {coefAlpha: 5.206 * Math.pow(10,   4), coefBeta: 6.716 * Math.pow(10, -11), upperBound: 1291075200000},
  {coefAlpha: 8.329 * Math.pow(10,   4), coefBeta: 6.521 * Math.pow(10, -11), upperBound: 1292241600000},
  {coefAlpha: 8.396 * Math.pow(10,   4), coefBeta: 6.541 * Math.pow(10, -11), upperBound: 1307576237376},
  {coefAlpha: 7.746 * Math.pow(10,   4), coefBeta: 6.588 * Math.pow(10, -11), upperBound: 1313586050187},
  {coefAlpha: 8.798 * Math.pow(10,   6), coefBeta: 3.922 * Math.pow(10, -11), upperBound: 1340572542928},
  {coefAlpha: 2.693 * Math.pow(10,   8), coefBeta: 2.249 * Math.pow(10, -11), upperBound: 1373049162025},
  {coefAlpha: 1.225 * Math.pow(10,   9), coefBeta: 1.610 * Math.pow(10, -11), upperBound: Number.POSITIVE_INFINITY},
]

使用例

http://saitamanodoruji.tumblr.com/exponentialEndlessSummer2014 にテストページを作りました.

謝辞

来年も夏が始まる頃に見直せるといいかなと思っています.

Exponential Endless Summer version 2013.08 - saitamanodoruji - たんぶら部 - Tumblove -

ダメでした.

夏、終っちゃうね。

the whole world is peaceful. (2014-08-29)

始まりました.

ありがとうございます.

追記 (2014-09-10)

Fig. 3 を EES2014 単独のヒストグラムから EES2014 と EES2013.08 の比較画像に差し替えて本文を編集しました.

2013-08-31

Exponential Endless Summer version 2013.08

23:59 | Exponential Endless Summer version 2013.08 - saitamanodoruji を含むブックマーク はてなブックマーク - Exponential Endless Summer version 2013.08 - saitamanodoruji

EES の第 3 版です.

2006 年後半から 2007 年前半のポスト選択がよりランダムになりました. 前回の更新に比べると影響は小さいです.

ポスト ID に対するフィッティングのやり直し

2006-06 から 2007-07 までの期間を細かく分割して, ポスト ID に対するフィッティングをやり直しました (Fig. 1, Fig. 2). フィッティングパラメータを Table 1 に示します.

Fig. 1 EES2013.08 と EES2013 の f(t) の比較

http://cdn-ak.f.st-hatena.com/images/fotolife/s/saitamanodoruji/20130831/20130831232537_original.png

Fig. 2 EES2013.08 と EES2013 の f(t) の比較 (拡大)

http://cdn-ak.f.st-hatena.com/images/fotolife/s/saitamanodoruji/20130831/20130831232538_original.png

Table. 1 EES2013.08 のフィッティングパラメータ. フィッティング関数は  f_i(t) = \alpha_i \exp\[\beta_i(t-t_0)\],\hspace{10}t_0=1136073600000\[msec\],\hspace{10}{\text{\rm{if} \it{t_{i-1}} < \it{t} \leq \it{t_i}}.

iα_iβ_it_it_i の決め方t_i の日付
11.211E-011.571E-10f_i = f_(i+1) となる t11600127893592006-10-05T10:46:29+09:00
23.619E-131.266E-09f_i = f_(i+1) となる t11632695419242006-11-12T03:25:41+09:00
32.120E+001.846E-10f_i = f_(i+1) となる t11721207918932007-02-22T14:06:31+09:00
41.179E-493.331E-09f_i = f_(i+1) となる t11733925612652007-03-09T07:22:41+09:00
54.702E-077.024E-10f_i = f_(i+1) となる t11764053232192007-04-13T04:15:23+09:00
65.338E+012.425E-10f_i = f_(i+1) となる t11829303008502007-06-27T16:45:00+09:00
78.255E+031.349E-10f_i = f_(i+1) となる t11932993598282007-10-25T17:02:39+09:00
82.496E+063.509E-11f_i = f_(i+1) となる t12416226168062009-05-07T00:10:16+09:00
92.907E+055.546E-11f_i = f_(i+1) となる t12830880651162010-08-29T22:21:05+09:00
105.206E+046.716E-11Post ID の不連続点12910752000002010-11-30T09:00:00+09:00
118.329E+046.521E-11Post ID の不連続点12922416000002010-12-13T21:00:00+09:00
128.396E+046.541E-11f_i = f_(i+1) となる t13075762373762011-06-09T08:37:17+09:00
137.746E+046.588E-11f_i = f_(i+1) となる t13135860501872011-08-17T22:00:50+09:00
148.798E+063.922E-11f_i = f_(i+1) となる t13405725429282012-06-25T06:15:42+09:00
152.693E+082.249E-11

EES2013 で A, B としていたパラメータ名を EES2013.08 で α, β に変えているのは, 桁あふれを避ける目的で t の原点をずらしたためです. α = A exp[B*t_0], β = B という関係があります. i = 8, ... , 15 については EES2013.08 と EES2013 で関数の形は同じですが, パラメータの互換性がありません.

ランダムさの評価

EES2013.08, EES2013 のそれぞれを使って 10000 回の Post ID 選択を行い, ヒストグラム化しました(Fig. 3). 2006-10 から 2007-07 の範囲のランダム性が向上しているのがわかります.

Fig. 3 EES2013.08 (上) 及び EES2013 (下) で引いた Post ID のヒストグラム

http://cdn-ak.f.st-hatena.com/images/fotolife/s/saitamanodoruji/20130831/20130831232539_original.png

2006-06 の度数が他の月に比べて 4 倍程度大きいです. これは 2006-06 から 2006-09 までの 4 ヶ月間ポスト ID が増えていないために, その 4 ヶ月間の時刻を選択した場合にすべて 2006-06 のポストを選んでしまうためです.

コード

ID の下限はだいたい 200000 よりも大きくした方がいいと思います.

// Exponential Endless Summer 2013.08
var draw = function(oldestID, newestID) {
  var oldestUTime = getUnixTimeFromPostID(oldestID);
  var newestUTime = getUnixTimeFromPostID(newestID);
  var drawnUTime = oldestUTime + Math.floor((newestUTime - oldestUTime) * Math.random());
  return getPostIDFromUnixTime(drawnUTime);
}

var getPostIDFromUnixTime = function(utime) {
  if ( utime < 1149465600000 ) {
    return;
  } else if ( utime === Number.POSITIVE_INFINITY ) {
    return Number.POSITIVE_INFINITY;
  }
  for (var i = 0; i < params.length; i++) {
    if ( utime <= params[i].upperBound ) {
      return Math.ceil(params[i].coefAlpha * Math.exp(params[i].coefBeta * (utime - 1136073600000)));
    }
  }
}

var getUnixTimeFromPostID = function(postID) {
  if ( postID < 1 ) { return; }
  for (var i = 0; i < params.length; i++) {
    if ( postID <= getPostIDFromUnixTime(params[i].upperBound) ) {
      return Math.floor(Math.log(postID/params[i].coefAlpha)/params[i].coefBeta) + 1136073600000;
    }
  }
}

var params = [
  // f_i(t) = Alpha_i * exp[Beta_i * (t - t_0)]
  // t: Unix time [msec]
  // t_0 = 1136073600000 [msec] (2006-01-01T00:00:00+09:00)
  {coefAlpha: 1.211 * Math.pow(10,  -1), coefBeta: 1.571 * Math.pow(10, -10), upperBound: 1160012789359},
  {coefAlpha: 3.619 * Math.pow(10, -13), coefBeta: 1.266 * Math.pow(10,  -9), upperBound: 1163269541924},
  {coefAlpha: 2.120 * Math.pow(10,   0), coefBeta: 1.846 * Math.pow(10, -10), upperBound: 1172120791893},
  {coefAlpha: 1.179 * Math.pow(10, -49), coefBeta: 3.331 * Math.pow(10,  -9), upperBound: 1173392561265},
  {coefAlpha: 4.702 * Math.pow(10,  -7), coefBeta: 7.024 * Math.pow(10, -10), upperBound: 1176405323219},
  {coefAlpha: 5.338 * Math.pow(10,   1), coefBeta: 2.425 * Math.pow(10, -10), upperBound: 1182930300850},
  {coefAlpha: 8.255 * Math.pow(10,   3), coefBeta: 1.349 * Math.pow(10, -10), upperBound: 1193299359828},
  {coefAlpha: 2.496 * Math.pow(10,   6), coefBeta: 3.509 * Math.pow(10, -11), upperBound: 1241622616806},
  {coefAlpha: 2.907 * Math.pow(10,   5), coefBeta: 5.546 * Math.pow(10, -11), upperBound: 1283088065116},
  {coefAlpha: 5.206 * Math.pow(10,   4), coefBeta: 6.716 * Math.pow(10, -11), upperBound: 1291075200000},
  {coefAlpha: 8.329 * Math.pow(10,   4), coefBeta: 6.521 * Math.pow(10, -11), upperBound: 1292241600000},
  {coefAlpha: 8.396 * Math.pow(10,   4), coefBeta: 6.541 * Math.pow(10, -11), upperBound: 1307576237376},
  {coefAlpha: 7.746 * Math.pow(10,   4), coefBeta: 6.588 * Math.pow(10, -11), upperBound: 1313586050187},
  {coefAlpha: 8.798 * Math.pow(10,   6), coefBeta: 3.922 * Math.pow(10, -11), upperBound: 1340572542928},
  {coefAlpha: 2.693 * Math.pow(10,   8), coefBeta: 2.249 * Math.pow(10, -11), upperBound: Number.POSITIVE_INFINITY},
]

http://saitamanodoruji.tumblr.com/exponentialEndlessSummer2013.08 にテストページを作りました.

2013-07-25

Exponential Endless Summer を改訂しました (version 2013)

06:15 | Exponential Endless Summer を改訂しました (version 2013) - saitamanodoruji を含むブックマーク はてなブックマーク - Exponential Endless Summer を改訂しました (version 2013) - saitamanodoruji

要旨

  • もっとランダムに Post ID を選べるようになりました.
  • 2010-11-30 が選ばれやすい現象が起きないようになりました.

JavaScript のコードは記事の最後に載せてあります.

Exponential Endless Summer とは

Exponential Endless Summer (EES) は Tumblr の Post ID をランダムに選ぶためのスクリプトです*1. Post ID の選び方が時間に関して等確率になるような機能を比較的少ないパラメータ*2で実現できます.

方法

EES は Post ID の上限と下限を引数として受け取り, その間から 1 つの Post ID を選び出します. この動作のために EES は 2 つの関数を必要とします. 初めにこれらの関数を準備します.

まず, Unix time  t \[msec\] 対 Post ID N のデータ*3t に関して n 個の区間に分割し, それぞれの区間についてデータに指数関数でフィッティングをかけて

f(t) = \{A_1 \exp(B_1t)\hspace{10}{\text{\rm{if} \it{t} \leq \it{t_1}}}\\{A_2 \exp(B_2t)\hspace{10}{\text{\rm{if} \it{t_1} < \it{t} \leq \it{t_2}}}\\{\hspace{20}\cdots}\\{A_n \exp(B_nt)\text{\hspace{10}\rm{if} \it{t_{n-1}} < \it{t}}}\hspace{30}\cdots\hspace{10}(1)

という関数を求めます. 境界値  t_i は原則として  f_i(t_i) = f_{i+1}(t_i) を満たすよう選びます*4. 例外は Post ID が不連続的に増加している場合 *5です. 必要なパラメータは 3n - 1 個です.

Fig. 1 Exponential Endless Summer の動作

http://cdn-ak.f.st-hatena.com/images/fotolife/s/saitamanodoruji/20130725/20130725045432_original.png

次に, 逆関数 t = f^{-1}(N) を (1) 式から次のように求めておきます.

f^{-1}(N) = \{{\frac{1}{B_1}\ln{\frac{N}{A_1}}\hspace{10}{\text{\rm{if} \it{N} \leq f(t_1)}}}\\{\frac{1}{B_2}\ln{\frac{N}{A_2}}\hspace{10}{\text{\rm{if} f(t_1) < \it{N} \leq f(t_2)}}}\\{\hspace{20}\cdots}\\{\frac{1}{B_n}\ln{\frac{N}{A_n}}\hspace{10}{\text{\rm{if} f(t_{n-1}) < \it{N}}}}\hspace{30}\cdots\hspace{10}(2).*6

EES は ff^{-1} を用いて, 以下の手順で Post ID を選びます (Fig. 1).

  1. Post ID の上限 N_{newest} と下限 N_{oldest}f^{-1} によって Unix time t_{newest}, t_{oldest} に変換する
  2. Math.random()t_{drawn} (t_{oldest} \leq t_{drawn} \leq t_{newest}) を選ぶ
  3. f を用いて t_{drawn} を Post ID N_{drawn} に変換する

去年のバージョンとその問題点

Fig. 2 Exponential Endless Summer 2012

http://cdn-ak.f.st-hatena.com/images/fotolife/s/saitamanodoruji/20130723/20130723052059_original.png

去年のバージョン (EES2012) ではデータを 2 つの区間に分割しました (n=2). それから 1 年ほどの間に Post ID の増え方が f_2(t) からかなり外れてきていて (Fig. 2), その影響で 2012-06 以降のポストは選ばれにくくなっています.

また特定の日付 (2010-11-30, 2010-12-13) が選ばれやすい現象も問題でした.

EES2013

今回はデータを 10 の区間に分割してフィッティングを行いました (n=10) (Fig. 3 left). 特に 2010-11-30, 2010-12-13 周辺については, これらの日付が選ばれやすい現象が起こらないように細かく分割しています (Fig. 3 right).

Table 1 にフィッティングパラメータをまとめました. f_5f_6 の境界である t_5 及び f_6f_7 の境界である t_6 には Post ID の不連続的な増加が存在する時刻を使っています.

Post ID の不連続的な増加は以下のような URL で確認できます. Dashboard では permalink の title 属性値に時刻が記載されています.

Fig. 3 Exponential Endless Summer 2013. (left) 全体図 (right) 2010-11 付近の拡大図
http://cdn-ak.f.st-hatena.com/images/fotolife/s/saitamanodoruji/20130723/20130723055505_original.png http://cdn-ak.f.st-hatena.com/images/fotolife/s/saitamanodoruji/20130723/20130723090539_original.png

Table. 1 EES2013 のフィッティングパラメータ. フィッティング関数は  f_i(t) = A_i \exp(B_it),\hspace{10}{\text{\rm{if} \it{t_{i-1}} < \it{t} \leq \it{t_i}}.

iA_iB_it_it_i の決め方t_i の日付
12.374E-2685.342E-101182114958836f_i = f_(i+1) となる t2007-06-18T06:15:58+09:00
22.292E-631.349E-101193135035175f_i = f_(i+1) となる t2007-10-23T19:23:55+09:00
31.214E-113.509E-111241622616806f_i = f_(i+1) となる t2009-05-07T00:10:16+09:00
41.259E-225.546E-111283088065116f_i = f_(i+1) となる t2010-08-29T22:21:05+09:00
53.805E-296.716E-111291075200000Post ID の不連続点2010-11-30T09:00:00+09:00
65.579E-286.521E-111292241600000Post ID の不連続点2010-12-13T21:00:00+09:00
74.482E-286.541E-111307576237375f_i = f_(i+1) となる t2011-06-09T08:37:17+09:00
82.424E-286.588E-111313586050187f_i = f_(i+1) となる t2011-08-17T22:00:50+09:00
93.923E-133.922E-111340572542928f_i = f_(i+1) となる t2012-06-25T06:15:42+09:00
102.157E-032.249E-11

ランダムさの評価

Endless Summer (taizooo's mod), EES2012, EES2013 の 3 つの手法を用いてそれぞれ 10000 回の Post ID 選択を行い, 引いた Post ID を元に Dashboard ( http://www.tumblr.com/2/{PostID} ) を開き, その一番上のポストの日付を記録して, 引いた回数を月毎に集計して作ったヒストグラムが Fig. 4 です. Dashboard が空だった場合にはそのデータは捨てています. Dashboard の取得に使ったアカウントの following を gist-6110816 に置きました *7.

EES2012 (Fig. 4b) の問題点であった最近のポストが選ばれにくい現象と 2010-11 のポストを選びやすい現象が EES2013 (Fig. 4c) では解消されています. また, 年末のポストがより選ばれやすい taizooo's mod (Fig. 4a) と 2007 年後半及び 2009 年から 2011 年にかけてのポストが選ばれやすい EES2012 に比べて, EES2013 は各月の選ばれる確率に偏りが少ないこともわかります.


Fig. 4 三種類の Endless Summer で引いた Post ID のヒストグラム. (a) taizooo's mod (b) EES2012 (c) EES2013
http://cdn-ak.f.st-hatena.com/images/fotolife/s/saitamanodoruji/20130724/20130724041516_original.png http://cdn-ak.f.st-hatena.com/images/fotolife/s/saitamanodoruji/20130724/20130724040830_original.png
http://cdn-ak.f.st-hatena.com/images/fotolife/s/saitamanodoruji/20130724/20130724040831_original.png

Unix time - Post ID 変換の精度の比較

Fig. 5 EES2012 と EES2013 の \Delta t = t_{dsbd} - t_{drawn} の分布の比較. サンプル数はそれぞれ 10000, 階級は日単位.

http://cdn-ak.f.st-hatena.com/images/fotolife/s/saitamanodoruji/20130725/20130725034929_original.png

EES2013 では Unix time - Post ID 変換の精度が上がりました.

10000 回の Post ID 選択をやって得たデータから \Delta t = t_{dsbd} - t_{drawn} のヒストグラムを作ったのが Fig. 5 です. ここで  t_{drawn} はランダムに選んだ Unix time, t_{dsbd}N = f(t_{drawn}) に対応する Dashboard (http://www.tumblr.com/dashboard/2/N) を開いたときに一番上にあるポストの Unix time です (Fig. 1).

つまり, ある Unix time tDashboard が見たいと思って EES2013 の f(t) を使ったときに, 実際に開かれた DashboardUnix time t_{dsbd}t からどれくらいズレているのかというのが \Delta t です.

\Delta t が 10 日以内である確率は約 95 %, 5 日以内である確率は約 67 % でした. \Delta t の分布は following によって変わるはずですが, ある程度 following が多ければ (Dashboard に表示される単位時間あたりのポスト数が多ければ) この値に近くなると思います.

コード

draw() に Post ID の下限と上限を与えると, その間からひとつ Post ID を選んで返します. http://www.tumblr.com/dashboard/2/{PostID} を開く場合には, Post ID がだいたい 700000 より小さくなるとポストが表示されない場合が出てくるので, 空の dsbd を開きたくない場合にはそのあたりを下限に設定するのが良いと思います. 個々のアカウントの following の状況によって Dashboard が空にならない最小の Post ID (Dashboard の底) が違うと思うので, それを調べて下限に設定するのが良いと思います*8.

getPostIDFromUnixTime(utime)f(t) に, getUnixTimeFromPostID(postID)f^{-1}(N) に対応しています.

// Exponential Endless Summer 2013
var draw = function(oldestID, newestID) {
  var oldestUTime = getUnixTimeFromPostID(oldestID);
  var newestUTime = getUnixTimeFromPostID(newestID);
  var drawnUTime = oldestUTime + Math.floor((newestUTime - oldestUTime) * Math.random());
  return getPostIDFromUnixTime(drawnUTime);
}

var getPostIDFromUnixTime = function(utime) {
  if (utime < 1149465600000) {
    return;
  } else if (utime === Number.POSITIVE_INFINITY) {
    return Number.POSITIVE_INFINITY;
  }
  for (var i = 0; i < params.length; i++) {
    if (utime <= params[i].upperBound) {
      return Math.ceil(params[i].coefA * Math.exp(params[i].coefB * utime));
    }
  }
}

var getUnixTimeFromPostID = function(postID) {
  if (postID < 1) { return; }
  for (var i = 0; i < params.length; i++) {
    if (postID <= getPostIDFromUnixTime(params[i].upperBound)) {
      return Math.floor(Math.log(postID/params[i].coefA)/params[i].coefB);
    }
  }
}

var params = [
  // f_i(t) = A_i * exp(B_i * t)
  {coefA: 2.374 * Math.pow(10, -268), coefB: 5.342 * Math.pow(10, -10), upperBound: 1182114958836},
  {coefA: 2.292 * Math.pow(10, -63), coefB: 1.349 * Math.pow(10, -10), upperBound: 1193135035175},
  {coefA: 1.214 * Math.pow(10, -11), coefB: 3.509 * Math.pow(10, -11), upperBound: 1241622616806},
  {coefA: 1.259 * Math.pow(10, -22), coefB: 5.546 * Math.pow(10, -11), upperBound: 1283088065116},
  {coefA: 3.805 * Math.pow(10, -29), coefB: 6.716 * Math.pow(10, -11), upperBound: 1291075200000},
  {coefA: 5.579 * Math.pow(10, -28), coefB: 6.521 * Math.pow(10, -11), upperBound: 1292241600000},
  {coefA: 4.482 * Math.pow(10, -28), coefB: 6.541 * Math.pow(10, -11), upperBound: 1307576237375},
  {coefA: 2.424 * Math.pow(10, -28), coefB: 6.588 * Math.pow(10, -11), upperBound: 1313586050187},
  {coefA: 3.923 * Math.pow(10, -13), coefB: 3.922 * Math.pow(10, -11), upperBound: 1340572542928},
  {coefA: 2.157 * Math.pow(10, -3), coefB: 2.249 * Math.pow(10, -11), upperBound: Number.POSITIVE_INFINITY},
]

使用例

http://saitamanodoruji.tumblr.com/exponentialEndlessSummer2013 にテストページを作りました.

GreasemonkeysharedObject を持たせていて, sharedObject.AutoPagerize.filter が使える環境があれば gist:2931824 にある Endless Summer on dsbd for Greasemonkey を試してみてください.

(追記: taizooo さんが bookmarklet 版 Endless Summer に EES2013 を入れてくれました. endless summer on dsbd - Hatena::Let)

(追記: mamemomonga さんが reblog machine 開発版に搭載してくれました. reblog machine開発版)

今後

来年も夏が始まる頃に見直せるといいかなと思っています. 梅雨明けと競う感じで.

(2013-07-31: ランダム選択のデータを集めた方法に関する記述を追加, \Delta t の分布に関する記述を追加, Post ID の下限の推奨値に関する記述を修正.)

*1:Endless Summer on dsbd の起源は http://taizooo.tumblr.com/post/60322576 .

*2:スクリプトに月毎の Post ID のデータを持たせると 2006-06 から 2013-07 までで 86 個のパラメータが必要になる. 今回のスクリプトが用いているパラメータは 29 個なのでそれよりは少ない.

*3:今回用いたデータは https://gist.github.com/saitamanodoruji/5883517 に置いた.

*4:2012 年のバージョンではこれが満たされてないので, Post ID に選ばれない区間ができてしまっていて良くなかった.

*5:2010-11-30, 2010-12-13 がこれにあたる.

*6:f(t) に不連続点がある場合には f^-1(N) は単射じゃないからその場合逆関数って呼んじゃいけない気がする. それから f^-1(N) は f(t) の不連続点に対応する部分を直線で補完しないと N = f{f^-1(N)} が満たされない場合が出てくるけど, これによる不具合は特に出ないと思うのでやってない.

*7:following を 400 以下にして david の最初のポストまで Dashboard で見えるようにしておけばよかった. Dashboard にポストの抜けがあるせいでヒストグラムの左端に近い部分が見えていない.

*8:このとき following が多いと Dashboard に表示されないアカウントができてポストの抜けが生じるので Dashboard の底が時間によって変化するはず. 2008 年 7 月の時点で Dashboard に抜けができない following の上限は 400 で順次増やすという公式な回答があったけど, いま自分のメインアカウントが following 458 で抜けがあるのであまり増えてないか 400 のままじゃないかと思う.

2012-08-26

LDRize を使って promotion の赤いピンをはずす

01:14 | LDRize を使って promotion の赤いピンをはずす - saitamanodoruji を含むブックマーク はてなブックマーク - LDRize を使って promotion の赤いピンをはずす - saitamanodoruji

play on tumblr に下のコードを加えて、Dashboard の一番上に出てくる有料ポストのアレを Shift + Enter で消せるようにした。

      addShortcutkey({
        key: "S-RET",
        description: 'tumblr.unpin',
        command: function() {
          try { execute( 'tumblr.unpin', execute('current-node')); } catch (e) { }
        }});

      addCommand({
        name: 'tumblr.unpin',
        command: function(stdin) {
          try {
            var unpin = $X('.//div[@title="Unpin"]', stdin[0])[0];
            if (unpin) click(unpin);
            return stdin;
          } catch (e) { }
          return stdin;
        }});

DiniDini2012/10/07 05:53Wham bam thank you, ma'am, my questinos are answered!

jayvxxdbpyjayvxxdbpy2012/10/07 18:15E9uo0h <a href="http://vqydxuthgphz.com/">vqydxuthgphz</a>

rfcakqleijrfcakqleij2012/10/09 01:17S3SRSc , [url=http://nifhnemmhygf.com/]nifhnemmhygf[/url], [link=http://uhfxridefptb.com/]uhfxridefptb[/link], http://cypiiwhythdh.com/

zgyesqkpzgyesqkp2012/10/10 02:40Tou6qw , [url=http://tdxisgbnnpby.com/]tdxisgbnnpby[/url], [link=http://gsucvpmnyexr.com/]gsucvpmnyexr[/link], http://ukkutmxuxekk.com/

2012-06-04

指数関数フィッティングでもう少しランダムに Endless Summer

11:17 | 指数関数フィッティングでもう少しランダムに Endless Summer - saitamanodoruji を含むブックマーク はてなブックマーク - 指数関数フィッティングでもう少しランダムに Endless Summer - saitamanodoruji



(2013-07-25 追記: 最新版は version 2013 です. Exponential Endless Summer を改訂しました (version 2013) - saitamanodoruji - たんぶら部 - Tumblove - )



Tumblr Dashboard のシャッフルをポスト ID のランダム選択でやると最近のばっかり出てくる。この問題への対策として taizooo の方法 があり、ReblogMachine に採用されている*1。taizooo の方法を使えばどの年からポストが選ばれる確率も全て等しくなる。ただ Tumblr の単位時間あたりのポスト数は常に増えているので年末に近いポストほど選ばれる確率が高い。そこで、もう少し時刻が偏らないようにポスト ID を引ける新しい方法を考えた。

方法

まず taizooo にならって http://kagurazakaundergroundresistance.tumblr.com/archive から半年ごとの Tumblr の総ポスト数 (= ポスト ID) のデータを取った。さらに 2007 年の 7 月以前については http://www.davidslog.com/archive を見て補完した。

プロット*2

Fig. 1
f:id:saitamanodoruji:20120604011642j:image

縦軸対数にする。

Fig. 2
f:id:saitamanodoruji:20120604013317j:image

直線っぽい。t の範囲を 2 つにわけて指数関数でフィッティング。

Fig. 3
f:id:saitamanodoruji:20120604014940p:image

Fig. 4
f:id:saitamanodoruji:20120604014941p:image

Fig. 5
f:id:saitamanodoruji:20120604021556p:image

関数を A\exp\[Bt\] の形になおして、

  N(t) = f(t) = \{{(3.120 \times 10^{-244}) \exp{\[(4.865 \times 10^{-10})t\]}\text{   \rm{if} \it{t} < 1183377600000}}\\{(7.768 \times 10^{-22}) \exp{\[(5.423 \times 10^{-11})t\]}\text{   \rm{if} 1183377600000 \leq t}}\.

ここで t \[\rm{msec}\]Unix time、N(t)t における総ポスト数。


f(t) は「この日、この時刻の Dashboard が見たい」という用途で使う場合には誤差が大きすぎて (2 ヶ月くらいずれたりする) あまり役に立たないが、Unix time t をランダムに選んだ上で f(t) を使ってポスト ID N を得る次のようなスクリプトでは十分使える。

function draw(oldestID, newestID) {
  var oldestUTime = getUnixTimeFromPostID(oldestID);
  var newestUTime = getUnixTimeFromPostID(newestID);
  var drawnUTime = oldestUTime + Math.floor((newestUTime - oldestUTime) * Math.random());
  return getPostIDFromUnixTime(drawnUTime);
}

function getPostIDFromUnixTime(utime) {
  if ( utime < 1149465600000) {
    return;
  } else if ( utime < 1183377600000 ) {
    return Math.ceil(3.12 * Math.pow(10, -244) * Math.exp(4.865 * Math.pow(10, -10) * utime));
  } else if ( utime >= 1183377600000 ) {
    return Math.ceil(7.768 * Math.pow(10, -22) * Math.exp(5.423 * Math.pow(10, -11) * utime));
  }
}

function getUnixTimeFromPostID(postID) {
  if ( postID < 1 ) {
    return;
  } else if ( postID < 4600108 ) {
    return Math.floor(Math.log(postID/(3.12 * Math.pow(10, -244)))/(4.865 * Math.pow(10, -10)));
  } else if ( postID >= 4600108) {
    return Math.floor(Math.log(postID/(7.768 * Math.pow(10, -22)))/(5.423 * Math.pow(10, -11)));
  }
}

draw() にポスト ID の下限と上限をわたすとその間からランダムにポスト ID をひとつ返してくれる。やっていることは

  1. f^{-1}(N) *3 で下限と上限の ポスト ID N_{oldest}, N_{newest}Unix time t_{oldest} \[\rm{msec}\], t_{newest} \[\rm{msec}\] に変換。
  2. Math.random()Unix time t \[\rm{msec}\] (t_{oldest} \leq t < t_{newest}) をランダムに選ぶ。
  3. f(t)t をポスト ID N に変換。

テストページを http://saitamanodoruji.tumblr.com/exponentialEndlessSummer においた。

Tumblr の総ポスト数が f(t) に完全に一致すると仮定した場合には、taizooo の方法では 1 月のポストに比べて 12 月のポストは 4.8 倍くらい選ばれやすくて、今回の方法ではその点が改良されたはず。ちゃんと評価するにはそれぞれの方法で得たポスト ID の Dashboard を実際に取得して時刻のデータをヒストグラムにするとかして確率分布を求めたらいいと思う。Endless Summer on Dashboard for Greasemonkey を改造して AutoPagerize が継ぎ足すたびに GM_setValue() で Preference に Dashboard の時刻を記録するようにすればリブログしながらデータを貯められそう。

pp2012/06/07 21:59pixitailで縦長の漫画がぼやけて見れないのをなんとかしろください