APIキー、OAuthクライアントID、サービスアカウントキーの違い:Google APIs
GoogleのサービスをAPI経由で利用する際、大きく3つの認証情報が登場します。「APIキー」、「OAuth2.0クライアントID」、「サービスアカウントキー」です。本投稿ではこれらの用途をざっくりと整理し、取得方法、Pythonでの使い方の違いを説明します。
まとめ
- Google APIsを使うには、下表の3ついずれかの認証情報が必要です
- APIでどういうデータに、どういう形でアクセスするかに依って使うべき認証情報が異なります
- いずれの認証情報もGoogle Cloud Consoleの
APIとサービス > 認証情報
から作成することができます
# | 必要となるもの | アクセス可能なデータ | 誰としてアクセスするか | 具体例 |
---|---|---|---|---|
1 | APIキー | 一般公開データ | 匿名ユーザ | YouTubeにある動画をアプリケーション経由で検索する |
2 | OAuth2.0クライアトID | 一般公開データ/ユーザーデータ | ユーザアカウント | あるユーザ(エンドユーザ)の代わりにユーザのGoogleドライブにアプリケーションを経由でデータを保存 |
3 | サービスアカウントキー | 一般公開データ/ユーザーデータ | サービスアカウント | 共同作業メンバのGoogleカレンダー情報にアプリケーションを経由してアクセスする |
基本事項のおさらい
認証情報について理解する上で知っておいたほうが良い基本事項についてまとめます。必要ない方は飛ばしてください。
APIの性質
API(Application Programming Interface)はサービスを外部のプログラムやアプリケーションから利用できるようにした窓口です。例えば、Google MapはGoogleのサービスの一つで、普通私たちユーザはブラウザやGoogle Map Appを自分で操作してGoogle Mapを利用します。一方で自身が開発するアプリの中でGoogle Map機能の一部を利用したい場合は、アプリの中のプログラムを介してGoogle Map機能を操作する必要があります。ここで利用されるのがAPIというプログラムから呼び出すインターフェースです。
一般にAPIはプログラムやアプリケーションから利用されます。
認証情報の役割
Google は許可されたアプリケーションからしかAPI利用をさせてくれません。そのため我々API利用者はGoogleアカウントを作成し、Google Cloud Console内でAPIを利用するアプリケーションの登録を行う必要があります。
つまりどの開発者が登録した、どのアプリケーションがAPIを叩いているのかをGoolge様にお知らせするのがAPIキーやOAuth2.0クライアトIDなど認証情報の役割の一つです。
またAPI利用は使用量の無料枠上限を超えると課金が発生します。API利用がどのアプリでどの程度発生しているかをGoogle様にお知らせするのも認証情報の役割です。課金管理はプロジェクト単位で行われるため認証情報はGoogleアカウント内のプロジェクトと紐付ける必要があります。これが認証情報を作成する際にプロジェクトが必要な理由です。
3つの認証情報は想定される利用シーンが異なる
APIキー、OAuth2.0クライアントID、サービスアカウントキーの3種類のいずれかがあればAPIを使うことが可能です。ただし三者は用途が異なり、用途にあった認証情報を選択するのが自然です。たとえば一般公開データにしかアクセスしないのに、OAuthクライアントIDを使う必要はありません。用途に応じた必要最低限の認証情報を使います。
以上を踏まえて3つの認証情報をそれぞれ見ていきましょう。
1. APIキー
まずはAPIキーについてです。Google Cloud ドキュメント:API キーの使用には次のように説明されています。
API キーは、プリンシパルなしでアプリケーションを識別する暗号化された単純な文字列です。一般公開データに匿名でアクセスする場合に便利で、割り当てや課金のために API リクエストをプロジェクトに関連付けるために使用されます。
YouTubeでの検索機能のように一般公開データにのみアクセスできれば十分な場合、利用者にユーザアカウントでログインしてもらうような必要はありません。このようなケースではAPIキーを使ってAPIを利用します。APIキーは暗号化された単純な文字列で、作成も取り扱いも比較的簡単です。
取得方法
事前にGoogle Cloud アカウント作成とプロジェクト作成が必要です。まだな方は以下のサイトなどを参考にして、アカウントとプロジェクト作成を行ってください。
Google アカウントを作成する(Gmail アドレスも同時に作成) | MAGELLAN BLOCKS
Google アカウントを作成する(Gmail アドレスも同時に作成) | MAGELLAN BLOCKS
プロジェクト作成が完了したら次の手順でAPIキーを作成します。
1. プロジェクトを選択
Google Cloud Consoleにログインして、APIを利用するプロジェクトを選択します。
APIとサービス > 認証情報
2. APIキーの作成
「認証情報の作成」をクリック
「APIキー」を選択
APIキーが表示されたボックスの右側にあるコピーマークをクリックし、閉じる
3. キーで利用するAPIを制限する(オプショナル)
作成直後の状態ではすべてのAPIの利用が可能となっているので、利用したいAPIのみに制限します
作成したAPIキーを選択
APIの制限 > キーを制限
Pythonでの利用例
例として、PythonからAPIキーを使ってYouTubeのsearchAPIを使うサンプルコードを掲載します。 下記コードのYOUTUBE_API_SERVICE_NAME
やYOUTUBE_API_VERSION
を変えるといろいろなAPIを使えるようになります。
from googleapiclient.discovery import build # 利用するAPIサービス YOUTUBE_API_SERVICE_NAME = 'youtube' YOUTUBE_API_VERSION = 'v3' # APIキーを指定 YOUTUBE_API_KEY = 'Your-API-KEY-here' # API のビルドと初期化 youtube = build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, developerKey=YOUTUBE_API_KEY) # YouTubeでキーワード検索(視聴数の多い順) q = 'Overjoyed' response = youtube.search().list(part='snippet', q=q, order='viewCount', type='video',).execute() response >>> # 出力 {'etag': 'QvSya4g44W8aUkApKibFr-o5JqA', 'items': [{'etag': 'Jw9SPInmYcoJg21LlKvbdDhk1IU', 'id': {'kind': 'youtube#video', 'videoId': '_a1LogyX9Uw'}, 'kind': 'youtube#searchResult', 'snippet': {'channelId': 'UCOo4Oc-PYSrzEeappVyoM-Q', 'channelTitle': 'beechoppers', 'description': 'please enjoy the music! music and ' 'lyrics by stevie wonder clip design by ' 'R. Norton.', 'liveBroadcastContent': 'none', ...
2.OAuth2.0クライントID
アプリ経由で利用者のGoogleドライブにファイルを保存をする場合など、アプリケーションがエンドユーザーに代わって Google Cloud APIs にアクセスします。このケースで利用するのがOAuth2.0クライアントIDです。OAuth2.0プロトコルを利用することで、アプリケーションが特定ユーザとしてサービスを利用します。その際に許可する権限の範囲(スコープ)も制限します。
OAuth2.0クライントIDは、OAuth2.0クライアントクレデンシャル(資格情報)というJSONファイルとしてダウンロードし利用することが多いと思います。OAuth2.0クライアントクレデンシャルには、OAuth2.0認証を利用するアプリの情報(クライアントID、クライアントシークレット)が含まれます。クレデンシャルはGoogle様にアプリ開発者がOAuth認証を行う資格があることを示します。
取得方法
OAuth認証は作成する同意画面の設定やアプリの種類など状況によって適切に設定する必要がありますが、本投稿ではテストとして最短で利用する手順を書いてみます。
1.同意画面の設定
OAuth認証では、認証時ユーザにログインをしてもらったり、許可してもらう権限の範囲を示す必要があるため、認証画面に表示する情報は重要です。以下はconnpassにTwitterアカウントでログインする際のOAuth認証画面です。画面にはconnpassのロゴなど表示され、どのアプリがアカウント情報を利用しようとしているかひと目で分かります。
このユーザに同意を求める部分の設定を「同意画面の設定」から行っていきます。今回はテストなのでなるべく手軽に行うことを目指します。
APIキー同様、まずGoogle Cloud Consoleにログインして、APIを利用するプロジェクトを選択します。
APIとサービス > OAuth同意画面を選択し、適当なアプリ名とメールアドレスを入力します
UserTypeを聞かれるので、今回は「外部」を選びます。「内部」は使ったことありませんが、G Suiteの組織内で利用するときに使うようです。
省略できる箇所は省略し、試すのに必要最低限な入力をして「保存して次へ」で進めます。入力が必要なのはメールアドレスのみです。実際にアプリを開発する際はスコープなど慎重に設定しましょう。
2.OAuthクライアントIDの作成
「+認証情報を作成」から「OAuthクライアントID」を選択します
アプリケーションの種類はここでは、デスクトップを選択しています
これでOAuthクライアントクレデンシャルが作成されました
「OK」を押して、作成されたIDの右にある↓矢印からJSONをダウンロードします。
これでOAuthクライアントIDを含む、OAuthクライアントクレデンシャルが発行されました。
Pythonでの利用例
例として、PythonからOAuthクレデンシャルを使ってYouTubeのplaylistAPIを使うサンプルコードを掲載します。以下はOAuth認証用の関数を定義をしています。この関数を使ってOAuth認証をし、その後ユーザデータにアクセスします。今回はYouTubeに自分用の再生リストを作成したいと思います。実行のコードを下部にあります。
'''OAuth認証用の関数''' import os import pickle import numpy as np import pandas as pd from googleapiclient.discovery import build from google_auth_oauthlib.flow import InstalledAppFlow from google.auth.transport.requests import Request def get_credentials(client_secret_file, scopes, token_storage_pkl='token.pickle'): '''google_auth_oauthlibを利用してOAuth2認証 下記URLのコードをほぼそのまま利用。Apache 2.0 https://developers.google.com/drive/api/v3/quickstart/python#step_1_turn_on_the_api_name ''' creds = None # token.pickleファイルにユーザのアクセス情報とトークンが保存される # ファイルは初回の認証フローで自動的に作成される if os.path.exists(token_storage_pkl): with open(token_storage_pkl, 'rb') as token: creds = pickle.load(token) # 有効なクレデンシャルがなければ、ユーザーにログインしてもらう if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: creds.refresh(Request()) else: flow = InstalledAppFlow.from_client_secrets_file( client_secret_file, scopes=scopes) creds = flow.run_local_server(port=0) # クレデンシャルを保存(次回以降の認証のため) with open(token_storage_pkl, 'wb') as token: pickle.dump(creds, token) return creds
以下のコードのCLIENT_SECRET_FILE
に先程ダウンロードしたJSONを指定してください。これを実行するとまずOAuth認証のためにブラウザが立ち上がり利用者にGoogleアカウントでのログインを求めます。
認証が終わると「再生リストテスト」という名で空の再生リストが作成されます。
'''OAuth認証と再生リストの作成''' # 利用するAPIサービス YOUTUBE_API_SERVICE_NAME = 'youtube' YOUTUBE_API_VERSION = 'v3' # OAuthのスコープとクレデンシャルファイル YOUTUBE_READ_WRITE_SCOPE = 'https://www.googleapis.com/auth/youtube' CLIENT_SECRET_FILE = 'client_secret.json' # OAuth認証:クレデンシャルを作成 creds = get_credentials( client_secret_file=CLIENT_SECRET_FILE, scopes=YOUTUBE_READ_WRITE_SCOPE ) # API のビルドと初期化 youtube_auth = build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, credentials=creds) # Add Playlist # This code creates a new, private playlist in the authorized user's channel. playlists_insert_response = youtube_auth.playlists().insert( part="snippet,status", body=dict( snippet=dict( title='再生リストテスト', description='APIで作成したプレイリスト' ), status=dict( privacyStatus="private" ) ) ).execute()
3. サービスアカウントキー
サービスアカウントはアカウントの一種です。アカウントなのでメールアドレスが割り当てられますが、アプリケーションが利用するアカウントなのでユーザアカウントとは異なり、ログインなどはできません。利用者がこのアプリケーション用のアカウントとデータを共有することで、プログラムからユーザデータへのアクセスが可能になります。APIはこのサービスアカウントの代わってサービスの操作を行います。
用途としてはたとえば、自分のGoogleカレンダーをサービスアカウントと共有し、アプリケーションからAPI経由で閲覧や編集するなどがあります。
サービスアカウントキーでの認証は、一般にサービスアカウントクレデンシャルというJSONファイルを使って行われます。サービスアカウントクレデンシャルにはアカウントのメールアドレスや、鍵情報、クライントIDなど認証に必要な情報が含まれています。これまでの認証と違ってアカウント認証もこの資格情報で行います。
取得方法
1. プロジェクトを選択
Google Cloud Consoleにログインして、APIを利用するプロジェクトを選択します。
APIとサービス > 認証情報
2. サービスアカウントキーの作成
「+認証情報を作成」から「サービスアカウント」を選択
サービスアカウント名を入力
続行を選んで、完了します。
3. 鍵ペアの作成
作成したサービスアカウントを選んで
鍵を追加で、「新しい鍵を追加」してJSONを選びます。 ついでに、上部のメールアドレスもコピーしておきましょう。
保存した.json
ファイルを認証に利用します。
Pythonでの利用例
例として、Pythonからサービスアカウントキーを用いてGoogle スプレッドシートのセルの値を取得してみます。まずClould Consoleの各プロジェクトでGoogle Sheet APIを有効にしておきましょう。
APIとサービス > ライブラリ
検索窓でsheet
を入力して、Google Sheets APIを選ぶ
次の準備として、Google スプレッドシートで新規にスプレッドシートを作成し、作成したシートをサービスアカウントと共有(編集権限を与える)しておく必要があります。
何か入力して、右上の「share」を押下
さきほどメモったサービスアカウントのメールアドレスを貼り付けて「OK」を押して、シート共有します。
さらにシートのURLから以下のsheet_id
をメモっておきましょう
https://docs.google.com/spreadsheets/d/<sheet_id>
実行するコードこちらです。これでシートの値が取得できます。
from googleapiclient.discovery import build from google.oauth2 import service_account CREDENTIAL_FILE = 'credential_json_path' credentials =\ service_account.Credentials.from_service_account_file(CREDENTIAL_FILE) SAMPLE_SPREADSHEET_ID = '1CL0pg_LDpASaHQqagDxBDKonmD64NRS6CQnpAbVDjhs' SAMPLE_RANGE_NAME = 'Sheet1!A1:B4' # Sheets APIビルド service = build('sheets', 'v4', credentials=credentials) # セルの値を取得 sheet = service.spreadsheets() result = sheet.values().get(spreadsheetId=SAMPLE_SPREADSHEET_ID, range=SAMPLE_RANGE_NAME).execute() values = result.get('values') >>> # 出力 [['1', 'あ'], ['2', 'い'], ['3', 'う']]
APIで権限まわりは混乱しがちですね。ちょっと長くなりましたが整理してみました。日々精進
YouTube API: 再生時間が条件に合う動画を選んで再生リストを作成(2/2)
前回はB-lifeのYouTubeチャンネルを題材に、チャンネル中にある動画情報一覧および再生時間を取得し、再生時間が10分以上15分以下の条件に収まる動画のみに絞り込みを行いました。本投稿では、これら動画を使って再生リストを作成します。
前回掲載した全ステップの中のでいうと本投稿はステップ3を説明します。
特定のチャンネル(B-life)の中にある動画ID一覧を取得する(前回済)動画の再生時間を取得し、再生時間で絞り込む(前回済)自分のYouTubeアカウント上に該当する動画の再生リストを作成する
1.準備
APIの認証情報について
前回search
APIを使う際はAPIキーさえあれば良かったですが、API経由でYouTube再生リストを作成するにはOAuth 2.0 クライアントクレデンシャルが必要です。これは再生リストの作成・編集の際、ユーザデータへのアクセスが必要なためです。
ユーザデータへのアクセスはOAuth2.0認証で行われますが、OAuth認証のためにOAuthクライアントクレデンシャルが必要になります。OAuth2.0認証はユーザーデータへ安全にアクセスできるよう、許可する権限範囲など細かに指定できる仕組みを提供します。
今回具体的に必要なものは、クライアントクレデンシャル情報が書き込まれた.json
ファイルです。Google API Consoleで作成してダウンロードしておきます。
APIキーやOAuthクライアントキーについての基本的な内容は以下を参考にしてください。 messefor.hatenablog.com
認証用ライブラリのインストール
認証部分はGoogleのドキュメント:quickstart.pyにあるコードの丸パクリですが、このサンプルコードがgoogle_auth_oauthlib
を使っているのでインストールしておきます。
pip google-auth-oauthlib
2. OAuth認証とAPIのビルド
OAuth認証の処理部分は以下のget_credentials
関数で行います。引数token_storage_pkl
で指定された場所に有効なトークンがなければ、ユーザにログイン(OAuth認証処理)をしてもらい、データへのアクセスを許可してもらいます。ユーザの許可により有効なトークンが得られれば、次回以降のアクセスのためにそれをtoken_storage_pkl
にpickleとして保存します。
def get_credentials(client_secret_file, scopes, token_storage_pkl='token.pickle'): '''google_auth_oauthlibを利用してOAuth2認証 下記URLのコードをほぼそのまま利用。Apache 2.0 https://developers.google.com/drive/api/v3/quickstart/python#step_1_turn_on_the_api_name ''' creds = None # token.pickleファイルにユーザのアクセス情報とトークンが保存される # ファイルは初回の認証フローで自動的に作成される if os.path.exists(token_storage_pkl): with open(token_storage_pkl, 'rb') as token: creds = pickle.load(token) # 有効なクレデンシャルがなければ、ユーザーにログインしてもらう if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: creds.refresh(Request()) else: flow = InstalledAppFlow.from_client_secrets_file( client_secret_file, scopes=scopes) creds = flow.run_local_server(port=0) # クレデンシャルを保存(次回以降の認証のため) with open(token_storage_pkl, 'wb') as token: pickle.dump(creds, token) return creds
実行部分のコードは以下です。YOUTUBE_READ_WRITE_SCOPE
がOAuth認証で使われるスコープになります。今回はYouTubeアカウントの管理権限(全読み書き)を範囲にしていますが、OAuth 2.0 Scopes for Google APIsを見て、適宜必要な最小限のスコープを絞ってください。
'''OAuth認証とAPIのビルド実行''' # 利用するAPIサービス YOUTUBE_API_SERVICE_NAME = 'youtube' YOUTUBE_API_VERSION = 'v3' # OAuthのスコープとクレデンシャルファイル YOUTUBE_READ_WRITE_SCOPE = 'https://www.googleapis.com/auth/youtube' CLIENT_SECRET_FILE = 'config/client_secret.json' # OAuth認証:クレデンシャルを作成 creds = get_credentials( client_secret_file=CLIENT_SECRET_FILE, scopes=YOUTUBE_READ_WRITE_SCOPE ) # API のビルドと初期化 youtube_auth = build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, credentials=creds) >>> # 出力されるメッセージ Please visit this URL to authorize this application: https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=hoge.apps.googleusercontent.com&redirect_uri=http%3A%2F%2Flocalhost%3A54887%2F&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyoutube&state=hoge&access_type=offline
このコードを実行すると、token_storage_pkl
で指定された場所に有効なトークンがない場合、ブラウザが立ち上がって以下のようなGoolge ユーザログイン画面が表示されます。(ブラウザが立ち上がらない場合は、出力されるメッセージに含まれるURLをクリックして、OAuth認証を行ってください)
さらに警告画面が表示されますが、実験なので無視して詳細 >(安全でないページ)に移動
と遷移すると
YouTubeアカウントの管理権限を許可するかどうか聞かれますので、許可
を選択します。
再度確認されるので、許可
を押します。
これでユーザ側のOAuth認証許可の処理が完了になります。API経由で再生リストを作成するための権限が与えられました。次は実際に再生リストを作成して、動画を追加します。
3. 再生リストの作成と動画の追加
新規再生リストの作成
まず新規再生リストを追加します。タイトルと説明、再生リストを公開にするか限定するかを指定しています。
'''新規再生リストの作成''' title = 'B-life Test 10分〜15分' description = '再生時間が10分〜15分の動画の再生リスト' privacy_status = 'public' # 'private' # 新規再生リストを追加 # https://developers.google.com/youtube/v3/docs/playlists/insert playlists_insert_response = youtube_auth.playlists().insert( part="snippet, status", body=dict( snippet=dict( title=title, description=description ), status=dict( privacyStatus=privacy_status ) ) ).execute()
これを実行して、YouTubeサイトに行って自分のライブラリを確認するとB-life Test 10分〜15分
という再生リストが作成されています。
まだ動画は一本も含まれていないので、クリックしても「この再生リストには動画がありません」と表示されます。
再生リストへの動画の追加
追加する動画は、前回は作成した動画IDリストとします。df_video_playlist
データフレームのid
カラムに動画IDが格納されています。また、追加先となる再生リストのIDは新規再生リストの作成時のレスポンスとして取得できていますので、それを利用します。
'''再生リストへの動画の追加 ''' # 動画ID videoids = df_video_playlist['id'].values # example videodis # 前回の続きとして実行しない場合は以下のコメントを外す # videoids = ['nZSe3ZZUSJw', 'EYqPH91q5nE', 'Zw_osQctQKs', 'kBLCmz8pODg', # 'IBt5l9Um_rY', 'WWxKW8ncPe0', 'CmWJJWKLeKg', 'e9HR7-3I3MA',] # プレイリストに動画を追加 # https://stackoverflow.com/questions/20650415/insert-video-into-a-playlist-with-youtube-api-v3/22190766 playlistid = playlists_insert_response['id'] # 作成した再生リストのIDを取得 # 動画IDをループ for videoid in videoids: resourceid = dict(kind='youtube#video', videoId=videoid) response = youtube_auth.playlistItems().insert( part='snippet', body=dict( snippet=dict( playlistId=playlistid, resourceId=resourceid ) ) ).execute()
先程の再生リストを見ると10分〜15分の動画が追加されているのが分かります。Mariko先生がバッチリ映えております。 コード全体はGithubにアップしています。
YouTube Data APIで再生リストを作成し動画を追加しました。動画コンテンツの整理は時間だけでなく、自然言語処理を使ったカテゴリ分類や音声や動画を用いたラベル付けなど、色々拡張ができそうです。日々精進
YouTube API: 再生時間が条件に合う動画を選んで再生リストを作成(1/2)
YouTubeで再生時間の条件で動画の再生リストを作りたくなることありませんか?今回YouTube Data APIを使って再生時間が10分以上、15分以下の動画のみを含む再生リスト作成するPythonコードを書いたので共有します。長くなりそうなので、2回に分けて投稿します。
背景:YouTubeのフィルタが微妙
運動不足解消のため、毎朝妻と一緒にB-lifeというYouTubeのヨガレッスンやっています。B-lifeのチャンネルには質の高いレッスン動画が多くアップロードされていて嬉しいのですが、毎朝やっているとその日やる動画を選ぶのも結構迷います。このとき「今日は時間がないので5分のレッスンが良い」「今日は15分」というような状況が頻繁に発生するので、再生時間の条件で再生リストが作れると便利です。
ただ残念ながらYouTubeの検索フィルタでは細かい再生時間の絞り込みはできなさそうです。
YouTube Data APIを使ったらできるだろうということでやってみました。
実現までのステップ
YouTube Data APIを使った今回の実現ステップをまとめると次の3つです。
特定のチャンネル(B-life)の中にある動画ID一覧を取得する
動画の再生時間を取得し、再生時間の条件に合う動画を選ぶ
自分のYouTubeアカウント上に該当する動画の再生リストを作成する
1と2に関しては、認証も必要なくAPIキーさえ用意すれば気軽に行えます。3にはOAuth認証が必要になります。本投稿では、第1回として上記1と2の手順を一つひとつ見ていきたいと思います。3は次の投稿で説明します。
APIキーやOAuthクライアントキーについては以下を参考にしてください。
0. 準備
YouTubeチャンネルのIDを確認
APIで動画やチャンネルを指定するためには、それらのIDが必要です。準備として対象とするYouTubeチャンネルのIDを確認します。チャンネルIDはURLから直接分かります。YouTubeチャンネルのURLはhttps://www.youtube.com/channel/<チャンネルID>
となっているので、ブラウザでアクセスして確認します。
ちなみにB-lifeのチャンネルIDはUCd0pUnH7i5CM-Y8xRe7cZVg
でした。
B-lifeのYouTubeチャンネルのホーム画面
赤枠がチャンネルIDです。
なおB-lifeは初心者向けの動画も多く、Mariko先生のレクチャが大変分かりやすいのでおすすめです
ライブラリをインストール
pipでPythonのGoogle API Client、をインストールしておきましょう。あとpandas
も使いますのでなければインストールしましょう。
pip install --upgrade google-api-python-client pip install pandas
1. チャンネルにある動画ID一覧を取得
さっそくYouTubeチャンネルの中にある動画ID一覧を取得していきます。
APIのビルドと初期化
まずAPIの初期化です。
Google APIを使うためには、APIキーが必要です。Google Cloud Consoleから作成して、下記のYOUTUBE_API_KEY = 'your-api-key-here'
を作成したAPIキーに書き換えてください
from googleapiclient.discovery import build # 利用するAPIサービス YOUTUBE_API_SERVICE_NAME = 'youtube' YOUTUBE_API_VERSION = 'v3' # APIキー YOUTUBE_API_KEY = 'your-api-key-here' # API のビルドと初期化 youtube = build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, developerKey=YOUTUBE_API_KEY)
動画情報一覧を取得
次にsearch()
APIを使って動画ID一覧を取得するのですが、それを関数get_video_list_in_channel()
で定義しています。 get_video_list_in_channel()
はチャンネルIDを入力とし、動画情報を出力します。仕様としては、指定したチャンネルIDの中で公開日時が新しいものから50件ずつ最大2回リクエストされます。なので最大50 * m_re_cnt
の動画情報が取得されます。
Google APIでは1日あたりのAPI使用量(クォータ)が限られているので、沢山リクエストすると一発で上限を超えてしまいます。今回実験ということで引数max_req_cnt=2
と小さめに指定しています。必要に応じて調整してください。
from datetime import datetime, timedelta import pandas as pd def get_video_list_in_channel(youtube, channel_id, max_req_cnt=2): '''特定のチャンネルの動画情報を取得し、必要な動画情報を返す 公開時刻が新しい順に50ずつリクエスト デフォルトでは最大2リクエストで終了 ''' n_requested = 50 earliest_publishedtime =\ datetime.now(tz=timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ') req_cnt = 0 result = [] while True: response = youtube.search().list(part='snippet', channelId=channel_id, order='date', type='video', publishedBefore=earliest_publishedtime, maxResults=n_requested).execute() req_cnt += 1 video_info = fetch_video_info(response) result.append(video_info) # 取得動画の最も遅い公開日時の1秒前以前を次の動画一覧の取得条件とする last_publishedtime = video_info['publishTime'].min() last_publishedtime_next =\ datetime.strptime(last_publishedtime, '%Y-%m-%dT%H:%M:%SZ') - timedelta(seconds=1) earliest_publishedtime =\ last_publishedtime_next.strftime('%Y-%m-%dT%H:%M:%SZ') if req_cnt > max_req_cnt: # リクエスト回数がmax_req_cntを超えたらループを抜ける print('Result count exceeded max count {}.'.format(max_req_cnt)) break if len(response['items']) < n_requested: # リクエストした動画数より少ない数が返った場合はループを抜ける print('Number of results are less than {}.'.format(n_requested)) break if len(result) > 1: df_video_list = pd.concat(result, axis=0).reset_index(drop=True) else: df_video_list = result[0] return df_video_list def fetch_video_info(response, as_df=True): '''APIのレスポンスから必要な動画情報を抜き出す''' info_list = [] for item in response['items']: info = {} info['title'] = item['snippet']['title'] info['kind'] = item['id']['kind'] info['videoId'] = item['id']['videoId'] info['description'] = item['snippet']['description'] info['publishTime'] = item['snippet']['publishTime'] info['channelTitle'] = item['snippet']['channelTitle'] info['thumbnails_url'] = item['snippet']['thumbnails']['default']['url'] info_list.append(info) if as_df: return pd.DataFrame(info_list) else: info_list
実行部分のコードは以下です。df_video_list
に取得した情報が格納されています。df_video_list
のvideoId
列が動画IDになります。動画ID一覧が取得できました。
# 動画一覧を取得したいチャンネルID channel_id = 'UCd0pUnH7i5CM-Y8xRe7cZVg' # 動画一覧を取得 df_video_list = get_video_list_in_channel(youtube, channel_id) # 出力 df_video_list.head()
2. 動画の再生時間を取得し、フィルタをかける
上で取得した動画情報一覧に動画の再生時間は含まれていませんので、別途videoId
をキーにしてAPI経由で取得する必要があります。再生時間を取得した後はpandas
で条件に合ったものを絞り込むだけです。
再生時間を取得
再生時間を取得するにはvideo
APIに動画IDを渡して詳細情報を取得します。ここでは、下表のような機能のラッパー関数をいくつか定義しています。
関数 | 機能概要 |
---|---|
get_contents_detail(), get_contents_detail_core() | 動画の詳細情報取得 |
get_duration(), get_basicinfo(), | レスポンスjsonのパース |
pt2sec() | 再生時間のフォーマット変換 |
1回のリクエストで50動画IDまでしか問い合わせできないみたいなので、get_contents_detail()
では動画ID一覧を50件ずつに分割してから処理しています。
import re import numpy as np def get_contents_detail_core(youtube, videoids): '''動画の詳細情報を取得''' part = ['snippet', 'contentDetails'] response = youtube.videos().list(part=part, id=videoids).execute() results = [] for item in response['items']: info = get_basicinfo(item) info['duration'] = get_duration(item) results.append(info) return pd.DataFrame(results) def get_contents_detail(youtube, videoids): '''必要に応じて50件ずつにIDを分割し、詳細情報を取得''' n_req_pre_once = 50 # IDの数が多い場合は50件ずつ動画IDのリストを作成 if len(videoids) > n_req_pre_once: videoids_list = np.array_split(videoids, len(videoids) // n_req_pre_once + 1) else: videoids_list = [videoids,] # 50件ずつ動画IDのリストを渡し、動画の詳細情報を取得 details_list = [] for vids in videoids_list: df_video_details_part =\ get_contents_detail_core(youtube, vids.tolist()) details_list.append(df_video_details_part) df_video_details =\ pd.concat(details_list, axis=0).reset_index(drop=True) return df_video_details def get_duration(item): '''動画時間を抜き出す(ISO表記を秒に変換)''' content_details = item['contentDetails'] pt_time = content_details['duration'] return pt2sec(pt_time) def get_basicinfo(item): '''動画の基本情報の抜き出し''' basicinfo = dict(id=item['id']) # snippets keys = ('title', 'description', 'channelTitle') snippets = {k: item['snippet'][k] for k in keys} basicinfo.update(snippets) return basicinfo def pt2sec(pt_time): '''ISO表記の動画時間を秒に変換 ''' pttn_time = re.compile(r'PT(\d+H)?(\d+M)?(\d+S)?') keys = ['hours', 'minutes', 'seconds'] m = pttn_time.search(pt_time) if m: kwargs = {k: 0 if v is None else int(v[:-1]) for k, v in zip(keys, m.groups())} return timedelta(**kwargs).total_seconds() else: msg = '{} is not valid ISO time format.'.format(pt_time) raise ValueError(msg)
再生時間取得の実行部分です。df_video_details
が結果で、duration
カラムに再生時間が入っています。再生時間の単位は秒です。
# 動画ID videoids = df_video_list['videoId'].values # 動画の詳細情報を取得 df_video_details = get_contents_detail(youtube, videoids) df_video_details.head()
再生時間でフィルタをかける
ここではpandas.between()
を使って10分以上15分以下の動画に絞り込みこみました。
# 再生時間が10分以上、15分以下の動画に絞り込む lower_duration = 10 * 60 # 10分以上 upper_duration = 15 * 60 # 15分以下 is_matched = df_video_details['duration'].between(lower_duration, upper_duration) df_video_playlist = df_video_details.loc[is_matched, :] # 出力 df_video_playlist.head()
これで10分以上、15分以下の動画IDのリストができました。めでたし、めでした。次回はこれら動画を含む再生リストをAPI経由で生成したいと思います。 コードはGithubに挙げています。
久々にGoogle APIを触りました。サービスも年々増えているみたいで、やりさえすれば色々できそうです。やりさえすれば。。。日々精進
ファイルをバイト単位で比較:cmpコマンド
2つのzipファイルを比較したい場合など、バイナリレベルでファイルを比較したいことがあります。こういう場合はシェルのcmp
コマンドで比較すると良いです。
FILE1
とFILE2
を比較する場合の構文は以下になります。
cmp FILE1 FILE2
完全に一致していれば何も出力されず、不一致の場合は
file1.tar.gz file2.tar.gz differ: char 5, line 1
のように比較した各ファイルパスとどの部分が異なるのかを標準出力に出力してくれます。(ここではfile1.tar.gz
とfile2.tar.gz
を比較しています)
また差異の有無によって終了ステータスコードが異なり、差があった場合はexit status = 1
、差がなかった場合は exit status = 0
となります。差の有無によって分岐処理などしたい場合はexit status
で判断すれば良いかなと思います。
例:2つのディレクトリにある全ファイルをバイト単位で比較する
使用例として、2つのディレクトリにある同名のファイル同士を比較し、差があるときだけメッセージを出すbashスクリプトを書いてみます。差の有無の判断はexit status
で行っています。
#! /bin/bash # 2つのディレクトリにある同じ名前のファイル同士を比較し、差があるときだけメッセージを出す dir1=src1/ # 1つ目のディレクトリ dir2=src2/ # 2つ目のディレクトリ fpaths=${dir1}* # 第1ディレクトリの全ファイルパスを取得 # ファイルパスをループ i=0 for f1path in $fpaths; do fnm=`basename $f1path` # echo $fnm f2path=${dir2}${fnm} cmt=`cmp $f1path $f2path` # 2つのファイルを比較 if [ $? -eq 1 ]; then echo $cmt # 差があった場合は、メッセージを表示 i=$(( $i + 1 )) # 差があったファイル数をカウントしておく fi done # 差が1つもなかった場合はその旨を表示 if [ $i -eq 0 ]; then echo 'All file pairs are the same.' fi
検証環境:
macOS Catalina
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin19) Copyright (C) 2007 Free Software Foundation, Inc.
コードはこちらです。
shellとかRとか、少し触らないとすぐに忘れてしまいます。栓が抜けた湯船のように、蛇口から入ってくる水より多くの水が抜けていきます。そのうちスッカラカンになりそうな。。。日々精進
Softbank光が遅いのでWiFiルータを買ったら爆速になった話
Softbank光に加入して数ヶ月が経ちました。レンタル品(光BBユニットEWMTA2.3)のWiFi/ルータ機能を使っていたのですが、どうも通信が安定せずにストレスフルな状態でした。そこでWiFiルータを新しく調達したところ大幅に快適になりました。私の環境でどのように改善したかまとめます。
今回分かったこと(結論)
環境に依ると思いますが、光BBユニット使っている人は市販のWiFiルータ買うと通信ストレスが大幅に改善される気がします。
光BBユニットのWiFiルータは電波の飛びが弱く不安定
- 特にルータから遠い部屋では通信が遅い。5GHz帯が弱い印象
- 速度計測だけでは測れない不安定さがある
- 市販のWiFiルータを調達することで、大幅に通信速度が改善し、快適になる
- バッファロー AirStation WSR-1800AX4/NWHは比較的安価で個人的にはおすすめでした
改善前の状況
5月にSoftbank光を導入し「これで爆速になる」とワクワクしていたのだが、実際に使い始めるとどうも様子がおかしい。
我が家ではルータ(AP:アクセスポイント)はリビングに置いていて、私の作業部屋からAPまで扉を2つ挟んで10mくらいの距離がある。作業部屋でリモート会議をしていると、かなり頻繁に音声が途切れる。平均すると20秒に1-2秒くらい聞こえないんじゃないかというひどい状況。新型コロナの影響で在宅勤務が増えている中、仕事にも支障が出てきてたので対策をすることにした。
参考:なぜ光BBユニットを使っているか
携帯がY!mobileなのでSoftbank光 + おうち割 光セット が適用されると携帯の通信費も安くなる(端末を2台使っているので毎月1000円以上割引)。このおうち割の適用条件に「光BBユニットレンタル」が含まれているため、レンタルして使っていた。
つまり光BBユニットをレンタルすることでおうち割(1000円割引)を手に入れているのだ。特に光BBユニットを使いたいわけではない。そんなもんだろう。おうち割についてはこのサイトが分かりやすい
改善前の速度
作業部屋での速度計測サイトで10回ずつ計測すると、帯域・上り下りの平均はだいたい30Mbpsくらい。ただ5GHz帯が上り下りともに弱いのがグラフでも見て取れる。
※エラーバーは±1標準誤差
さらに計測すると、光BBユニットWiFiルータが置いてあるリビングでは140Mbps、有線では295Mbpsと十分な速度がでていることも分かった。さすがに有線だとリモート会議をしていても全く途切れない。音質も良いとのこと。 以上からAPから離れた場所でのWiFiの速度低下が問題だろうと考え、そこを改善しようと考えた。
改善策と手順
WiFiの改善アプローチだが、以下2つを思いた。
結構迷ったが以下の記事に背中を押され、まずはWiFiルータを購入することにした。 設定手順もこの記事に全て書いてある。ありがたく参考にした。
ソフトバンク光に無線LANルーターを追加したら、爆速安定して人生が多少変わった
購入したルータ
バッファロー AirStation WSR-1800AX4-WHを購入。
値段は8,000円くらいまでで検討した。他の選定条件は以下
項目 | 条件 | 理由 |
---|---|---|
価格 | 8000円くらいまで | 家庭の事情 |
色 | ホワイト | 家庭の事情 |
発売時期 | 比較的新しい機種 | 電化製品は新しい方が良いと思っているから |
機能 | ビームフォーミング | 改善前の電波が弱く改善したいから |
WiFi 6対応 | 今後使うかもしれないから | |
IPv6 | 今後使うかもしれないから | |
中継機能 | 今後使うかもしれないから |
ルータはほとんどが黒のものが多く、色と値段だけでかなり絞り込まれる。また中継機能がついているルータならば、もし今回のルータ交換で速度が改善しなくても作業部屋に中継機として設置できると考えた。
どの程度改善したか
5GHz帯がめちゃくちゃ改善した。速度で見ても3倍程度出ている。2.4Hz帯は数値ではさほど変わりがないが、2.4GHzも使っていて安定感が大幅にアップした印象を受ける。どちらも通信がほぼ途切れなくなった。リモート会議でも音声が途切れることはほぼなくなり、ストレスから開放された。Webページ開くときのレスポンスも結構早くなった印象を受ける。
結局我が家の環境では、5GHz帯が遠くまで飛ぶことが重要だったと考えている。これがルータを買うことで改善されたとみた。
5GHz帯、有線の速度比較@作業部屋
アクセスポイント | 5GHz帯 平均速度 |
リモート会議や動画通話 | |
---|---|---|---|
WiFi(改善前) | 光BBユニットEWMTA2.3 | 27.2Mbps | 頻繁に途切れる |
WiFi(改善後) | WSR-1800AX4/NWH | 84.3Mbps | 問題ない(たまに途切れることがある ) |
有線 | - | 295.5Mbps | 途切れない。音が良い(空気感が伝わる) |
表を見ても分かるとおり、なんだかんだで有線は圧倒的に通信速度が早い。有線だと空気感が伝わるほど高音質とのことで、会話相手にも評判だ。なお私はMacBookProを使ってリモート会議など参加しているのですが、マイクの性能はそこそこ良いみたい。通信が良いと、マイクやスピーカも買おうかなという気持ちになってくる。。。
まとめ
- 光BBユニットのWiFiルータは電波の飛びが弱く不安定
- 特にルータから遠い部屋では通信が遅い。5GHz帯が弱い印象
- また速度計測だけでは測れない不安定さがある
- Softbank光では市販のWiFiルータを調達することで、大幅に速度が改善し快適になる
- 5GHzが遠くても比較的強い
- BUFFALO AirStation WSR-1800AX4/NWHは安価で個人的にはおすすめでした
- けっきょく有線最強
在宅勤務では通信環境が精神的な負担をかなり左右します。改善してから随分ストレスフリーになりました。同じような症状の方は市販ルータの購入を検討してみてもいいと思います。
技術以外での日常の奮闘もどこかに出力しておきたいなと思って書きました。日常分からない事だらけですね。日々精進
Stanでガンマ回帰(動かす編)
回帰問題で目的変数が正の連続値をとる場合、ガンマ回帰は選択肢の一つになります。個人的に適用シーンは多いと思うのですが比較的情報が少ない気がします。本投稿ではトイデータとStanでサクッとモデルを動かしてみたいと思います。なおガンマ回帰はGLM(一般化線形モデル)の枠組みで推定できるためRのglm()
でも簡単にfitできます。シンプルにモデリングしたいだけであれば、あえてStanでやる意味はないかもしれません
設定
今回の設定として目的変数の期待値が、説明変数のべき乗の線形和 で表現できるとします。
推定するパラメタはです。あえてを入れたのは、Stanなら冪乗の項の対数をとるなど工夫せずに直接推定できそうだと思ったからです。またGLMでいうリンク関数はidentity
(恒等リンク関数)です。そのためとならないように説明変数の範囲を注意しないといけません。
さて期待値でガンマ分布に従うを考えたいのですが、ガンマ分布はshape
パラメタ:とrate
パラメタ:によって次のように決まります。(は正の実数)
われわれに馴染み深い期待値と分散は、で次のように表現できます。正規分布と違ってとは互いに強く依存しています。
ガンマ分布ではとを決める必要があるため、よりとを とで表します。
今回の設定ではを説明変数の関数で表します。またはGLMの枠組みでは一定と仮定するのが一般的なようです。今回も同様の仮定を置きます。 以上が今回のデータ生成過程の設定になります。モデリングではこれらの関係性と観測データをStanに直接指示すれば推定できそうです。まずはトイデータを生成しましょう。
トイデータ生成
Rを使って学習データを生成します。としています。
set.seed(1234) N <- 150 # 推定したいパラメタ a <- 0.3 b0 <- 1.0 b1 <- 1.5 phi <- 0.1 # 一様分布からxを生成 x <- runif(n=N, 0.1, 20) # 期待値と線形予測子 mu <- b0 + b1 * x ^ a # パラメタの変換 shape <- mu ^ 2 / phi rate <- mu / phi # ガンマ分布に従う乱数生成 y <- rgamma(n = length(x), shape = shape, rate = rate)
生成したデータをプロットします。との散布図です。
library(tidyverse) # x vs. y の散布図プロット g <-list(x = x, y = y, mu = mu) %>% data.frame() %>% ggplot() g <- g + geom_point(aes(x=x, y=y)) g <- g + geom_line(aes(x=x, y=mu, color='mu')) g <- g + ggtitle('Toy data $mu = 0.2 + 1.5 * x ^ 0.3')) g
Stanファイル
先程のデータ生成過程をそのままStanファイルに記載するだけです。これをexample01.stan
として保存します。
data { int N; // 観測数 vector[N] x; // 説明変数x vector[N] y; // 目的変数y } parameters { // 推定したいパラメタ real<lower=0> a; real<lower=0> b0; real<lower=0> b1; real<lower=0> phi; // variance of gamma } transformed parameters { vector<lower=0>[N] alpha; // ガンマ分布のshapeパラメタ vector<lower=0>[N] beta; // ガンマ分布のratioパラメタ vector<lower=0>[N] mu; // 期待値 for (n in 1:N) { mu[n] = b0 + b1 * pow(x[n], a); } for (n in 1:N) { alpha[n] = pow(mu[n], 2.) / phi; } beta = mu / phi; } model { y ~ gamma(alpha, beta); // likelihood } generated quantities { vector<lower=0>[N] y_new; for (n in 1:N) { y_new[n] = gamma_rng(alpha[n], beta[n]); } }
MCMC実行
rstan
を使って事後分布からのランダムサンプリングを実行します。
library(rstan) options(mc.cores = parallel::detectCores()) rstan_options(auto_write = TRUE) data <- list(N = N, x = x, y = y) fit <- stan(file = 'example01.stan', data = data, seed = 1234, iter = 2000)
divergent transitions
の警告が出ますが、警告の遷移数が少ないのでここでは無視します。
Warning message: “There were 2 divergent transitions after warmup. Increasing adapt_delta above 0.8 may help. See http://mc-stan.org/misc/warnings.html#divergent-transitions-after-warmup” Warning message: “Examine the pairs() plot to diagnose sampling problems
警告の詳細は以下を参考にしてください。 messefor.hatenablog.com
収束も問題なさそうです。
パラメタ推定結果
どのパラメタも真の値 を分布の25-75%20-80%の間に含んでいるので、大きく外していないようです。
mean | se_mean | sd | 2.5% | 25% | 50% | 75% | 97.5% | n_eff | Rhat | |
---|---|---|---|---|---|---|---|---|---|---|
a | 0.2643425 | 0.0018063278 | 0.04469031 | 0.20230515 | 0.22890738 | 0.2563850 | 0.2925427 | 0.3675151 | 612.1158 | 1.0053146 |
b0 | 0.6953202 | 0.0173214550 | 0.43094516 | 0.02972518 | 0.33524315 | 0.6733556 | 1.0266480 | 1.5374601 | 618.9781 | 1.0035314 |
b1 | 1.8591395 | 0.0164183020 | 0.40759706 | 1.06712580 | 1.54369900 | 1.8705854 | 2.1966878 | 2.5121268 | 616.3191 | 1.0038491 |
phi | 0.1051311 | 0.0003396671 | 0.01246250 | 0.08323707 | 0.09635969 | 0.1042309 | 0.1122532 | 0.1334601 | 1346.1805 | 0.9992741 |
推定したパラメタからのサンプリング
推定したパラメタからのサンプリングデータを使って、学習データと乖離していないか確認します。
# 推定パラメタからのサンプリングを抽出 is.pred <- str_detect(rownames(result.summay), 'y_new.') data <- data.frame(result.summay[is.pred,]) colnames(data) <- c('mean', 'se_mean', 'sd', 'p2.5', 'p25', 'p50', 'p75', 'p97.5', 'n_eff', 'Rhat') data$x <- x data$y <- y data$mu <- mu # 図示 g <- ggplot(data=data) g <- g + geom_line(aes(x=x, y=mean, color='post_mean')) + geom_line(aes(x=x, y=mu, color='mu')) + geom_point(aes(x=x, y=y)) g <- g + geom_ribbon(aes(x=x, ymin=p25,ymax=p75), fill="blue", alpha=0.2) + geom_ribbon(aes(x=x, ymin=p2.5,ymax=p97.5), fill="blue", alpha=0.2) g <- g + ggtitle('True and Predicted') + labs(y='y') g
水色の線が事後分布の期待値です。また濃い青が50%予測区間、薄い青が95%予測区間です。 大きな乖離はなさそうですし、推定できているとしましょう。
コード全体はここに置いてあります。Rのコード、Stanのコード
まとめ
Stanを使ってガンマ回帰を行いました。正規分布を仮定した線形回帰での推定は、目的変数の分布の対称性が仮定されていることや負の値が出てきてしまうため、今回のようなケースではガンマ回帰の方がより自然な選択肢になります。GLMでも推定は行えますが、不確実性やモデルを柔軟に構築したい場合はStanを使うのも手かもしれません。
統計モデリングでは確率分布の知識が要求されますね。日々精進
参考文献
stan: Divergent transitions after warmupの警告が出る場合
stanでサンプリングを行っていると次のような警告がでる場合があります。
1: There were 15 divergent transitions after warmup. 2: Examine the pairs() plot to diagnose sampling problems
おっかないので、ドキュメントを読んで整理してみました。部分的にしか理解してない状態ですので、間違いがありましたら教えてもらえると嬉しいです。
なんの警告なのか
MCMCでは推定するパラメタを表す擬似粒子を考えて、事後分布が作り出す(位置エネルギー)場を粒子がどういうふうに動くか、その軌跡をシミュレートします。この軌跡がある一定の基準から遠くに大きく外れて行くことをdivergence
(発散?逸脱?)と呼んでいます。divergence
が大きくなると、このシミュレーションは信頼できないため、devegent trasitions
の警告がでます。
どいういうときに発生するか
シミュレーションは、1ステップごと逐次的に探索をしながら行われるのですが、事後分布がぐにゃぐにゃにカーブしているケースでは、ステップ幅が事後分布のカーブに対して十分に小さな値でないと、分布の形状をキャプチャできず、本来たどるべき軌跡から逸脱してしまいます。一方でステップ幅が小さすぎると、探索が少しずつしか進まず非効率になったり、途中で(Uターンする前に)停止してしまう可能性があります。最急降下法と同じ感じですね。 場所によってカーブの大きさが異なるような事後分布では、ある場所では非効率な探索ステップになり、ある場所では逸脱が大きくなるようなステップになってしまいます。これが警告の主な原因とのこと。
どうすればいいか
シンプルな対処としてadapt_delta
パラメタを大きくする と書いてあります。adapt_delta
はデフォルトで0.8に設定されていますので、stan()
のcontrol
引数でlist(adapt_delta = 0.99)
のように0.8より大きい値を指定します。この値を大きくするとステップ幅を強制的に小さくできます。
# `adapt_delta`を0.8より大きくして、ステップ幅を小さくする fit <- stan(..., control = list(adapt_delta = 0.99))
結果的にサンプリングに時間がかかるようになる可能性がありますが、ウォームアップ後にdivergence
が発生して結果の信頼性が落ちるよりはマシだろうという考えです。
もう少し詳細な対処法
警告文に出ているようにpairs()
を使って結果を図示して診断することで、もう少し詳しく診断できます。
# 結果のペアプロット pairs(fit)
見にくいかもしれませんが、グラフ中の小さな赤い点がdevegent trasitions
ですが、この赤い点が1. 対角の下半分(下三角)のグラフに出ているか、2.上半分(上三角)に出ているか、で性質や対処法が若干変わります。上の例では下三角にdevegent trasitions
が出ています。
1.上三角にでている場合は、単純にadapt_delta
を大きくすれば良いですが、2.下三角に出ている場合adapt_delta
を大きくするだけで改善するケースは限られています。devegent trasitions
の数が小さい場合は改善する可能性があるので、とりあえずにadapt_delta
を大きくしましょう。それでも改善しない場合は、事後分布の形がシンプルになるように、モデルの表現を変更する必要があります。
特に階層モデルを使う場合にこのケースが発生しやすいとのこと。
対処法をまとめると、
devegent trasitions (赤い点)が出る位置 |
対処 |
---|---|
1.上三角部分にあるグラフに出ている | adapt_delta を大きくする |
2.下三角部分にあるグラフに出ている | - adapt_delta を大きくする(改善するケースは限定的)- 事後分布の形が単純になるようにモデルを変更する |
事後分布の形がシンプルになるように、モデルの表現を変更する というのはかなり難しそうに思えますが、別機会に詳細をまとめてみたいと思います。
物理出身の方は、MCMCやHMCをすんなり理解できるみたいですが、物理分かってない私みたいな人間には目が回りそうです。日々精進