雑記

参考文献

  1. Arturs Backurs, Piotr Indyk, Tal Wagner. Space and Time Efficient Kernel Density Estimation in High Dimensions. In NeauIPS 2019.(2022年5月11日参照).



[1] は大規模データにも適用できるカーネル密度推定の高速化を提案している。Reformer も使用している LSH を使用している。というか [1] とその先行手法 [Charikar and Siminelakis, 2017] がカーネルの LSHasing の先輩である。

雑記

仮に愛知県民の 20% がきのこの山の方が好きで、残る 80% はたけのこの里の方が好きだとする。
このとき、「きのこの山が好きである」のオッズは 0.2 / 0.8 = 0.25 になる。
さらに、「きのこの山が好きである」のロジットは log(0.25) = -1.39 になる。
これらはとりもなおさず以下が成り立つことを意味する。

 〈愛知県のきのこ派人口〉= 0.2〈愛知県の人口〉
 〈愛知県のきのこ派人口〉= 0.25〈愛知県のたけのこ派人口〉
 〈愛知県のきのこ派人口〉= exp( -1.39 )〈愛知県のたけのこ派人口〉

仮に静岡県では 60% の県民がきのこの山の方が好きであったとすると、このオッズとロジットは以下になる。

 〈静岡県きのこ派人口〉= 0.6〈静岡県の人口〉
 〈静岡県きのこ派人口〉= 1.5〈静岡県たけのこ派人口〉
 〈静岡県きのこ派人口〉= exp( 0.405 )〈静岡県たけのこ派人口〉

このとき、「愛知県民であること」が「きのこの山を好きである」要因になっているかを知りたい
(ただし世界には愛知県民と静岡県民しかいないものとする)。
そこで、個々人がきのこ派であるかのロジットを予測する線形回帰モデルを考える(ロジスティック回帰)。
つまり以下のモデルを考える。

 〈その人がきのこ派であるロジット〉= α + β〈その人が愛知県民であるか否か〉

この β の最尤推定量は -1.7918 になる(本記事末尾のスクリプト)。スクリプトでは愛知県の人口と静岡県の人口をつかっているが、何人ずつでも構わない(無論 p 値には影響する)。なぜなら静岡県民のきのこ派率のみが切片 α を決定し、その上で愛知県民のきのこ派率が β を決定するからである。

 〈愛知県民がきのこ派であるオッズ〉= exp(α + β)
 〈静岡県民がきのこ派であるオッズ〉= exp(α)

さらにこれより β の値 -1.7918 とは、愛知県民のオッズと静岡県民のオッズの比が exp(-1.7918) という意味になる。
実際 exp(-1.7918) とオッズ比は等しく 0.166666 である。

print(np.exp(-1.7918))  # 0.166666
print(0.25 / 1.5)  # 0.166666

逆にいうとオッズ比さえ 0.166666 ならば β は -1.7918 になる。
愛知県のきのこ派率が 0.333 で静岡県きのこ派率が 0.75 でも β は -1.7918 になる。
愛知県のきのこ派率が 0.11111 で静岡県きのこ派率が 0.42857 でも β は -1.7918 になる。
愛知県のきのこ派率が 0.02439 で静岡県きのこ派率が 0.130438 でも β は -1.7918 になる。
どれも「愛知県民であることが、きのこ派であるオッズを 0.166666 倍にする」ということになる。

しかし、「オッズを exp(β) 倍にする」といわれても解釈しにくいものである。
この例では「愛知県民であること」によって「きのこ派である確率」が削られる幅が 0.6 - 0.2 = 0.4 だが、
同じ β=-1.7918 でも削られる幅が 0.75 - 0.333 = 0.417 であることもあるし、
0.130438 - 0.02439 = 0.106048 であることもある。
これは元々愛知県民でないときにきのこ派である確率の水準がどれくらいかに依存する。
なので異なる応答変数に対するロジスティック回帰の β の値で効果の大きさを比較できないのはもちろん、
同じデータでの同じ応答変数に対するロジスティック回帰であっても異なる説明変数を含めたケースとは比較できないはずである。


import pandas as pd
import statsmodels.formula.api as smf
import statsmodels.api as sm

n_aichi = 7500000
n_shizuoka = 3700000
p_aichi = 0.2
p_shizuoka = 0.6

df = pd.DataFrame({
    'n': [
        p_aichi * n_aichi, (1 - p_aichi) * n_aichi,
        p_shizuoka * n_shizuoka, (1 - p_shizuoka) * n_shizuoka
    ],
    'kinoko': [1, 0, 1, 0],
    'aichi': [1, 1, 0, 0],
})
family = sm.families.Binomial()
result = smf.glm(
    formula='kinoko ~ aichi',
    data=df,
    family=sm.families.Binomial(),
    freq_weights=np.asarray(df['n']),
).fit()
print(result.summary())
==============================================================================
                 coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------
Intercept      0.4055      0.001    382.085      0.000       0.403       0.408
aichi         -1.7918      0.001  -1280.005      0.000      -1.795      -1.789
==============================================================================

雑記: Github Actions の Checkout はデフォルトでシャロ―クローンというだけ

参考文献

  1. GitHub Actions で変更があるときだけ git commit & push する(2022年5月9日参照).
  2. continuous integration - Git history in a Github Action - Stack Overflow(2022年5月9日参照).
  3. GitHub - actions/checkout: Action for checking out a repo(2022年5月9日参照).



GitHub Pages で Web ページをつくると思います。そうなるとどのページの最終更新日がいつというのを一覧表示したくなると思います。なので各ページの最終更新日を取得する create_index.sh をかきます(以下)。

cookipedia/create_index.sh at 8739615ed113341aefd690efc123d880478ed43c · CookieBox26/cookipedia · GitHub

コミット前に create_index.sh を実行すると意図通り全ページの最終更新日がトップページに反映されます(以下)。

Cookipedia

しかし、create_index.sh を手動で実行するのではなく、GitHub に push したら勝手に「create_index.sh を実行し」「適宜 commit する」とやってほしいと思います。なので GitHub Actions にこれをやらせます。リポジトリに以下のワークフロー定義ファイルを配置します(以下)。[1] を参考にしました。

cookipedia/action.yml at 8739615ed113341aefd690efc123d880478ed43c · CookieBox26/cookipedia · GitHub

そうすると push をトリガーに「create_index.sh を実行し」「適宜 commit する」とやってくれ……やってくれてはいるのですが、なぜか全ページの最終更新日が今日になってしまいます(以下)。

Update · CookieBox26/cookipedia@690a1bc · GitHub

この原因は以下(のはず)です。

  • create_index.sh は各ファイルの最終更新日を git log コマンドから取得している。
  • 他方、ワークフローで利用している actions/checkout@v3 は、明示的に fetch-depth を指定しなければ直近の commit 履歴しか clone しない [2] [3]。ので、git log コマンドで直近 1 件の commit しか参照できない。要するに、git log コマンドで各ファイルの真の最終コミット日を取得できない。

なので、ワークフロー定義ファイルで fetch-depth: 0(すべてのコミット履歴を取得)とすればいいはずです。ここまで調べておいてなんですが、コミットが増えるのでやっぱり create_index.sh は手動で実行したいと思います。


ちなみにコレスキー分解の記事は最終更新日が 4 月 10 日ですが、手元でリポジトリを普通に clone すれば以下のコマンドでこの日付を取得できます。

$ git clone https://github.com/CookieBox26/cookipedia.git
$ cd cookipedia/
$ git log -1 --format="%ad" --date=short ./articles/cholesky_decomposition.html
2022-04-10

しかし、--depth=1 で clone するとリポジトリへの一番最近のコミットでこのファイルがつくられた感じになります。

$ git clone https://github.com/CookieBox26/cookipedia.git --depth=1
$ cd cookipedia/
$ git log -1 --format="%ad" --date=short ./articles/cholesky_decomposition.html
2022-05-09

雑記: Tweepy による Twitter API v2 の利用

概要

Twitter API をつかおうとして Twitter Developer アカウントを作成して Project と App を作成すると思います。このとき色々なトークン等が画面上に出てきてどれが何に必要なのかわからないと思います。なので App の Keys and tokens タブにある ID/トークン/キー/シークレットと認証方法との対応をまとめると下の表になります [1]。

認証方法必要なID/トークン/キー/シークレット備考
〈1〉OAuth 2.0 Bearer Token 認証Bearer TokenApp を作成すると勝手に画面に出てくる。
〈2〉OAuth 1.0a User Context 認証API Key and Secret
Access Token and SecretApp の Keys and tokens タブで明示的に Generate をクリックして生成する必要がある(はず)。
〈3〉OAuth 2.0 Authorization Code Flow with PKCE (User Context) 認証Client IDUser authentication settings ※ を設定し終わると生成される。認証方法〈2〉を用いる場合であっても App を Read and write 以上の権限にしたい(Ex. ツイートをしたい)なら User authentication settings で権限を設定する必要があるので注意。

Twitter API を叩くのがもっぱら自分であれば Type of App は Automated App or bot とし、App permissions は適切な権限にし、Redirect URL と Website URL は何でもよいはずである(私は自分のツイッターアカウントのホーム画面にした)。
Client Secret

ではどの認証方法にすればいいのかということになりますが、Twitter API を叩くのがもっぱら自分であれば〈2〉、Twitter API を用いたアプリケーションを一般に公開するようなときは〈3〉になると思います。〈1〉でできることは少ないと思いますがこれで用が足りるなら〈1〉だと思います。操作の一部の例と認証方法の対応表は以下です [2] [3] [4] [5]。

操作 Twitter API v2 のインターフェースである tweepy.Client のメソッド 〈1〉〈2〉〈3〉
ツイートID(ブラウザ上でそのツイートのみを表示したときの URL の末端)からツイート内容を引く .get_tweet(ツイートID, user_auth=True/False) ⭕️⭕️⭕️
自分(App 作成者)のアカウントのユーザIDを引く .get_me() ⭕️⭕️
ユーザIDからその人のツイートを引く .get_users_tweets(ユーザID, user_auth=True/False) ⭕️※⭕️⭕️
自分(App アクセス者)のアカウントでツイートする .create_tweet(text='ツイート文章', user_auth=True/False) ⭕️⭕️
※ ただしユーザIDを引くのがこの認証ではできない。

参考文献

  1. Authentication — tweepy 4.9.0 documentation(2022年5月8日参照).
  2. Client — tweepy 4.9.0 documentation(2022年5月8日参照).
  3. GET /2/tweets/:id | Docs | Twitter Developer Platform(2022年5月8日参照).
  4. GET /2/users/me | Docs | Twitter Developer Platform(2022年5月8日参照).
  5. GET /2/users/:id/tweets | Docs | Twitter Developer Platform(2022年5月8日参照).
  6. POST /2/tweets | Docs | Twitter Developer Platform(2022年5月8日参照).


前提

以下では Python から各認証方法で実際に Twitter API を叩くコードを示しますが、そもそも Twitter Developer アカウントを生成して Project と App を作成しトークンなどを取得しておいてください(どのトークンがどこで出現するかはこの記事冒頭の表を参照)。また、手元の Python 環境に Tweepy をインストールしておいてください。

import tweepy

# 以下は App を作成する過程で勝手に生成されて画面に出てくる
API_KEY = 'xxxxxx'  #〈2〉
API_KEY_SECRET = 'xxxxxx'  #〈2〉
BEARER_TOKEN = 'xxxxxx'  #〈1〉

# 以下は作成した App の Keys and tokens タブで Access Token and Client を Generate すると生成される
ACCESS_TOKEN = 'xxxxxx'  #〈2〉
ACCESS_TOKEN_SECRET = 'xxxxxx'  #〈2〉

# 以下は User authentication settings を設定し終わると生成される
CLIENT_ID = 'xxxxxx'  #〈3〉
CLIENT_SECRET = 'xxxxxx'  #〈3〉


〈1〉OAuth 2.0 Bearer Token 認証の例

OAuth 2.0 Bearer Token 認証では以下のような操作ができます。ただし「ユーザIDからその人のツイートを引く」については、ユーザIDがこの認証では取得できないので〈2〉や〈3〉の認証で取得する必要があります。なお、ユーザIDとは Twitter 上で表示される「名前」や「ユーザー名(@xxx)」ではなく、API でしか取得できない ID です。

# OAuth 2.0 Bearer Token 認証情報付きのアクセスクライアント
client_oath_20_bearer_token = tweepy.Client(
    bearer_token=BEARER_TOKEN
)

# ツイートID(ブラウザ上でそのツイートのみを表示したときの URL の末端)からツイート内容を引く
# これは OAuth 2.0 Bearer Token 認証でできる
print('\n◆ ツイートIDからツイート内容を引く')
resp = client_oath_20_bearer_token.get_tweet('1522958148683788288')
print(type(resp.data))
print('id:', resp.data.id)
print('text:', resp.data.text)

# ユーザIDからその人のツイートを引く
# これは OAuth 2.0 Bearer Token 認証でできる(ただしユーザIDを引くのがこの認証ではできない)
print('\n◆ ユーザIDからその人のツイートを引く')
resp = client_oath_20_bearer_token.get_users_tweets(my_user_id, max_results=5)
print(type(resp.data), type(resp.data[0]))
print(len(resp.data), '件のツイートを取得しました')
for i, tweet in enumerate(resp.data[:3]):
    print(f'----- {i + 1} -----')
    print(tweet.data)
print('以下略')
◆ ツイートIDからツイート内容を引く
<class 'tweepy.tweet.Tweet'>
id: 1522958148683788288
text: 今日のクッキペディアです
https://t.co/2vWaGbvWIP

◆ ユーザIDからその人のツイートを引く
<class 'list'> <class 'tweepy.tweet.Tweet'>
5 件のツイートを取得しました
----- 1 -----
{'id': '1523153322991316992',
 'text': 'PKCE test'}
----- 2 -----
{'id': '1523135051642601472',
 'text': '昼食に鶏もも肉をつかわなければならなくてチキンピザは昨日つくりました'}
----- 3 -----
{'id': '1522958148683788288',
 'text': '今日のクッキペディアです\nhttps://t.co/2vWaGbvWIP'}
以下略


〈2〉OAuth 1.0a User Context 認証の例

OAuth 1.0a User Context 認証では先ほどの操作に加えてユーザIDの取得やツイートができます。ただしツイートするにはあらかじめ User authentication settings を設定しておくことが必要になります。また、先ほどの Bearer Token 認証でも可能であった操作については、メソッドの引数で明示的に user_auth=True として、トークン認証ではなく OAuth 1.0a ユーザ認証をさせないと失敗します(ただしこれは現実的にはアクセスクライアントにどちらの認証情報も設定しておけば済む話です)。

# OAuth 1.0a User Context 認証情報付きのアクセスクライアント
client_oath_10a_user_context = tweepy.Client(
    consumer_key=API_KEY,
    consumer_secret=API_KEY_SECRET,
    access_token=ACCESS_TOKEN,
    access_token_secret=ACCESS_TOKEN_SECRET
)

# ツイートID(ブラウザ上でそのツイートのみを表示したときの URL の末端)からツイート内容を引く
print('\n◆ ツイートIDからツイート内容を引く')
resp = client_oath_10a_user_context.get_tweet('1522958148683788288', user_auth=True)
print(type(resp.data))
print('id:', resp.data.id)
print('text:', resp.data.text)

# 自分のアカウントのユーザID(数字列でありアットマークの後ろの文字列ではない)を引く
# これは User Context 認証が必要である
print('\n◆ 自分のアカウントのユーザIDを引く')
resp = client_oath_10a_user_context.get_me()
print(type(resp.data))
# print('id:', resp.data.id)  # Twitter Developer に登録した本人しか許可されないので秘密
print('name:', resp.data.name)
my_user_id = resp.data.id  # 自分のユーザIDを取得

# ユーザIDからその人のツイートを引く
print('\n◆ ユーザIDからその人のツイートを引く')
resp = client_oath_10a_user_context.get_users_tweets(
    my_user_id, max_results=5, user_auth=True
)
print(type(resp.data), type(resp.data[0]))
print(len(resp.data), '件のツイートを取得しました')
for i, tweet in enumerate(resp.data[:3]):
    print(f'----- {i + 1} -----')
    print(tweet.data)
print('以下略')
    
# 自分のアカウントでツイートする
# これは User Context 認証が必要である
# かつあらかじめ User authentication settings が必要である
print('\n◆ 自分のアカウントでツイートする')
if True:
    resp = client_oath_10a_user_context.create_tweet(
        text='昼食に鶏もも肉をつかわなければならなくてチキンピザは昨日つくりました'
    )
    print(type(resp.data))
    print(resp.data)
◆ ツイートIDからツイート内容を引く
<class 'tweepy.tweet.Tweet'>
id: 1522958148683788288
text: 今日のクッキペディアです
https://t.co/2vWaGbvWIP

◆ 自分のアカウントのユーザIDを引く
<class 'tweepy.user.User'>
name: クッキー

◆ ユーザIDからその人のツイートを引く
<class 'list'> <class 'tweepy.tweet.Tweet'>
5 件のツイートを取得しました
----- 1 -----
{'id': '1523153322991316992',
 'text': 'PKCE test'}
----- 2 -----
{'id': '1523135051642601472',
 'text': '昼食に鶏もも肉をつかわなければならなくてチキンピザは昨日つくりました'}
----- 3 -----
{'id': '1522958148683788288',
 'text': '今日のクッキペディアです\nhttps://t.co/2vWaGbvWIP'}
以下略

◆ 自分のアカウントでツイートする
<class 'dict'>
{'id': '1523135051642601472',
 'text': '昼食に鶏もも肉をつかわなければならなくてチキンピザは昨日つくりました'}


〈3〉OAuth 2.0 Authorization Code Flow with PKCE (User Context) 認証の例

OAuth 2.0 Authorization Code Flow with PKCE (User Context) 認証でも OAuth 1.0a User Context 認証のときと同じ操作ができますが、操作の度に以下の手順を踏んでトークンを得る必要があります。

  • 認証画面の URL を生成する。
  • ブラウザでその URL にアクセスして、App が現在ログイン中のアカウントにアクセスすることを許可する。
  • リダイレクト先の URL をパラメータごとコピーして fetch_token に渡し、有効期限付きのトークンを得る。

リダイレクト先の URL を得るまで Python でできるかもしれないですが面倒なのでやっていません。以下のスクリプトは input で標準入力を待機します。なお、以下の redirect_url に "https://twitter.com/CookieBox26" を設定しても URL パラメータが付いていないのでトークンはもらえません。パラメータ付きのリダイレクト URL であっても、異なる OAuth2UserHandler が生成した認証画面のリダイレクト先であった場合はやっぱりトークンはもらえません。

なお、この認証方法の場合は、先ほどとは逆に user_auth=False として OAuth 1.0a ユーザ認証ではなくトークン認証にさせないと失敗します。

作成した App を個人利用する限りにおいてはこの認証方法をとる必要はないのではと思っています。

# 認証ハンドラを作成する
oauth2_user_handler = tweepy.OAuth2UserHandler(
    client_id=CLIENT_ID,
    redirect_uri="https://twitter.com/CookieBox26",  # 自分で設定したリダイレクト先 URL
    scope=["tweet.read", "users.read", "tweet.write"],  # 何を許可するか
    client_secret=CLIENT_SECRET  # only necessary if using a confidential client
)
# まず認証画面の URL を生成する
auth_url = oauth2_user_handler.get_authorization_url()
# 上の URL にアクセスして認証を許可してリダイレクト先の URL を(パラメータ付きで)改めて以下に入力する
print(auth_url)
redirect_url = input("Enter Redirect URL: ")
# 適切なパラメータ付きのリダイレクト先の URL を渡したとき一時的なトークンがもらえる
token = oauth2_user_handler.fetch_token(redirect_url)
print(token)
# アクセスクライアントを生成する
client_oath_20_user_context = tweepy.Client(bearer_token=token['access_token'])

print('\n◆ ツイートIDからツイート内容を引く')
resp = client_oath_20_user_context.get_tweet('1522958148683788288', user_auth=False)
print(type(resp.data))
print('id:', resp.data.id)
print('text:', resp.data.text)

print('\n◆ App 作成者のアカウントのユーザIDを引く')
resp = client_oath_20_user_context.get_me(user_auth=False)
print(type(resp.data))
# print('id:', resp.data.id)  # Twitter Developer に登録した本人しか許可されないので秘密
print('name:', resp.data.name)
my_user_id = resp.data.id  # ユーザIDを取得

# ユーザIDからその人のツイートを引く
print('\n◆ ユーザIDからその人のツイートを引く')
resp = client_oath_20_user_context.get_users_tweets(
    my_user_id, max_results=5, user_auth=False
)
print(type(resp.data), type(resp.data[0]))
print(len(resp.data), '件のツイートを取得しました')
for i, tweet in enumerate(resp.data[:3]):
    print(f'----- {i + 1} -----')
    print(tweet.data)
print('以下略')

print('\n◆ App アクセス者のアカウントでツイートする')
if True:
    resp = client_oath_20_user_context.create_tweet(text='PKCE test', user_auth=False)
    print(type(resp.data))
    print(resp.data)
◆ ツイートIDからツイート内容を引く
<class 'tweepy.tweet.Tweet'>
id: 1522958148683788288
text: 今日のクッキペディアです
https://t.co/2vWaGbvWIP

◆ App 作成者のアカウントのユーザIDを引く
<class 'tweepy.user.User'>
name: クッキー

◆ ユーザIDからその人のツイートを引く
<class 'list'> <class 'tweepy.tweet.Tweet'>
5 件のツイートを取得しました
----- 1 -----
{'id': '1523153322991316992',
 'text': 'PKCE test'}
----- 2 -----
{'id': '1523135051642601472',
 'text': '昼食に鶏もも肉をつかわなければならなくてチキンピザは昨日つくりました'}
----- 3 -----
{'id': '1522958148683788288',
 'text': '今日のクッキペディアです\nhttps://t.co/2vWaGbvWIP'}
以下略

◆ App アクセス者のアカウントでツイートする
<class 'dict'>
{'id': '1523153322991316992',
 'text': 'PKCE test'}

雑記

参考文献

  1. GitHub - lucidrains/performer-pytorch: An implementation of Performer, a linear attention-based transformer, in Pytorch(2022年5月7日参照).


performer_pytorch(v1.1.4)は以下のような構造をしているのがわかる。
FastAttention がどう Fast なのかが肝心である。
以下の確認スクリプトとは関係ないが、performer_pytorch.reversible.ReversibleSequence は reformer_pytorch にある同名のクラスと同じなのかわかっていない。

import torch
import torch.nn as nn
from performer_pytorch import Performer
from performer_pytorch.reversible import SequentialSequence
from performer_pytorch.performer_pytorch \
    import PreLayerNorm, SelfAttention, Chunk, FastAttention, FeedForward

model = Performer(
    dim=8,
    depth=3,
    heads=4,
    dim_head=6,
    causal=True
)
x = torch.randn(5, 128, 8)
y = model(x)
assert list(y.shape) == [5, 128, 8]

assert type(model.net) is SequentialSequence
assert type(model.net.layers) is nn.ModuleList
assert len(model.net.layers) == 3

for i in range(3):
    assert type(model.net.layers[i]) is nn.ModuleList
    assert len(model.net.layers[i]) == 2

    assert type(model.net.layers[i][0]) is PreLayerNorm
    assert type(model.net.layers[i][0].norm) is nn.LayerNorm
    assert type(model.net.layers[i][0].fn) is SelfAttention
    assert type(model.net.layers[i][0].fn.fast_attention) is FastAttention
    assert type(model.net.layers[i][0].fn.fast_attention.kernel_fn) is nn.ReLU
    assert type(model.net.layers[i][0].fn.to_q) is nn.Linear
    assert type(model.net.layers[i][0].fn.to_k) is nn.Linear
    assert type(model.net.layers[i][0].fn.to_v) is nn.Linear
    assert type(model.net.layers[i][0].fn.to_out) is nn.Linear
    assert type(model.net.layers[i][0].fn.dropout) is nn.Dropout

    assert type(model.net.layers[i][1]) is PreLayerNorm
    assert type(model.net.layers[i][1].norm) is nn.LayerNorm
    assert type(model.net.layers[i][1].fn) is Chunk
    assert type(model.net.layers[i][1].fn.fn) is FeedForward
    assert type(model.net.layers[i][1].fn.fn.w1) is nn.Linear
    assert type(model.net.layers[i][1].fn.fn.act) is nn.GELU
    assert type(model.net.layers[i][1].fn.fn.dropout) is nn.Dropout
    assert type(model.net.layers[i][1].fn.fn.w2) is nn.Linear