ハイエンド PC が無くても Paperspace で手軽に MMD 動画を作成する

概要

MikuMikuDance (MMD) は樋口優さんが開発されたフリーソフトで、 3D モデルに動きを付けた動画を簡単に作成することができます。

MMD でクオリティの高い 3D モデルを扱ったり高解像度の動画を出力したりする場合には GPU を積んだ高性能な PC が必要になり、普通に購入すると少なくとも十数万円の出費になります。

そこで本記事では、 ハイエンド PC を所有していない人でも安価で手軽に MMD を始められる ように、 クラウドサービス (Paperspace) の GPU マシンを活用する方法を解説します。

クラウド (クラウドコンピューティング) とは

現在クラウドと呼ばれるサービスは非常に多岐に渡り、はっきりとした定義が存在するわけではありませんが、大まかに説明すると以下のような特徴が挙げられます:

  • インターネットの向こうにあるコンピューターをレンタルして使用できる
  • 高額な初期費用や面倒な初期設定が不要で、すぐに利用開始できる
  • 料金は使用した分だけ (1 秒あたりの従量課金制などで)かかる

特に今回扱うのは、 GPU を積んだ Windows 仮装マシンをクラウド上で起動して、リモート操作で自分の PC のように使用できる サービスです。 これによって動画出力など高負荷な処理が全てクラウド上で行われ、その結果の画面やファイルだけを受け取ることができます。 そのため、手元にあるのが低スペックなノート PC でも、また OS が MacLinux でも関係なく動作します。

f:id:P_N_D:20191112125630p:plain

クラウドサービスの選択肢

クラウド最大手のプロバイダとして以下の 3 社が有名です。

しかしこれらはいずれも、ソフトウェア開発者や研究者を主な対象として、非常に幅広い用途に合わせたサービスを多数提供しています。 そのため、知識の無い人がいきなり利用するにはあまりに複雑すぎ、サービスの選択や細かい設定などで途方に暮れる恐れがあります。

そのため本記事では、上記のどれでもなく Paperspace というクラウドサービスを紹介します。

Paperspace の使い方

概略

Paperspace には Core と Gradient という別々のサービスがあり、今回使用するのは Core の方です1

Core プランでは以下のような手順で Virtual Machine (VM) を起動して作業します。 (VM とはその名の通り、クラウド上で動作する仮想的な PC のことです。)

  1. Paperspace の Web コンソールにログイン
  2. VM を設定、作成
  3. VM にリモート接続
  4. MMD やその他必要なソフトを VM にインストール
  5. 作業が完了したら VM を停止、または削除

VM の設定

f:id:P_N_D:20191112190703p:plain

Paperspace の Web コンソール で "Create a Cloud VM" ボタンをクリックすると、 VM の設定を選択するページが表示されます。

f:id:P_N_D:20191112190929p:plain

VM を設置する地域は日本に近いほどネットワークの通信遅延が少なく快適になります。現状は US West 一択です。

f:id:P_N_D:20191112190954p:plain

OS は MMD が動作するように Windows を選択すれば (7 でも 10 でも) 問題ありません。

f:id:P_N_D:20191112191015p:plain

VM のタイプは、動画の出力スピードとクラウドの利用料金(後述)に直結する設定です。 パフォーマンスは使用するエフェクトや出力画質などに左右されるため、一概にどの VM タイプが良いとは言えません。 参考までに、私が最近作成した MMD 動画 (フル HD 60fps, UtVideo YUV420 BT.709, 再生時間 5 分 33 秒) を出力するのにかかる時間は

  • Advanced タイプ : 約 11 分 40 秒
  • Pro タイプ : 約 10 分
  • P4000 タイプ : 約 4 分 10 秒

f:id:P_N_D:20191112191152p:plain

ストレージ容量もクラウドの利用料金に影響する設定です。 MMD が出力する avi 動画は基本的にギガバイト級のサイズになるため、 容量が足りなくならないように設定しましょう2。 目安として、前述の動画のファイルサイズが約 12 GB になります。

VM へのリモート接続

Paperspace の VM にリモート接続する際には、特別なソフトのインストールや設定は必要ありません。

f:id:P_N_D:20191112191431p:plain

VM を作成して数分待ってから VM の一覧ページ を確認すると、 On/Ready という状態が表示された項目があるはずです。 これをクリックすると Web ブラウザ上にリモートデスクトップが表示されて VM を操作できるようになります。

MMD のインストール

通常の PC の場合と全く同じように、下記のソフトを VM にインストールします。

VM の 起動/停止/削除

VM の一覧ページ で歯車のアイコンをクリックすると、 その VM の管理ページが開きます。 ここで start / shutdown / deactivate ボタンを押すことで VM の 起動/停止/削除が可能です。

f:id:P_N_D:20191112191829p:plain

shutdown が一時的に電源を落とすようなイメージであるのに対して、 deactivate は VM のストレージに保存されているデータを全て削除します。

利用料金を節約するため、使い終わった VM はこまめに 停止/削除 しましょう。

Paperspace の料金体系

2 種類の料金がかかります。

  • Virtual Machine (VM) 起動時間に対する従量課金
  • ストレージ容量に対する月額課金

前者は VM が起動している間だけ課金されますが、 後者は VM が停止していてもデータを保持し続けるために料金がかかるので注意してください。 完全に課金を停止させるためには VM を削除する必要があります。

VM タイプやストレージ容量に対する具体的な料金は、公式のヘルプページから確認可能です。

まとめ

  • クラウドサービスを使うと、ハードウェアへの初期投資無しで手軽に MMD を試すことができます。
  • 特に Paperspace は安価で簡単に使えるのでおすすめです。

なお、下の招待リンクから Paperspace に登録すると $10 分のクレジットが貰えるので、もしよければご利用ください。


  1. Gradient は主に研究用に使用される計算サーバーで、 OS に Windows を選択できないため MMD を動作させることができません。

  2. 初期状態で Windows 10 のインストールに 20 GB 弱占有されるため、実際に使用できる容量はその分だけ減ることに注意してください。

にじさんじマイクラ鯖を企業として管理するのは本当に規約違反か?

概要

VTuber グループの「にじさんじ」には、ライバー専用の大規模な Minecraft サーバー(通称にじ鯖)があります。にじ鯖は会社側で管理しているわけではなく、ライバーのドーラさんが自費で管理し善意で無償提供しているものです。

これに関して「Minecraft の商業利用規約に違反するため会社側でにじ鯖を管理できない」という噂を見かけたため、真偽を確かめるため実際に規約を確認しました。結論として、この噂は誤りである可能性が極めて高いです。

注意事項

  • 本記事は筆者の個人的な解釈を述べたものです。 Mojang 公式の見解と一致する保証はありません。
  • 筆者は「にじさんじ」や Mojang と一切関係がありません。
  • 本記事の内容について「にじさんじ」の運営やライバーなどへ問い合わせるのはご遠慮ください。

背景

にじ鯖は最近、 10 月 2 日から約 3 週間のアップデートを挟んで新バージョンにアップデートされました。 その際の諸々の経緯について、管理者のドーラさんご本人が配信上で説明されています。

本記事で特に焦点を当てるのは、配信の一部で明言された下記の点です:

  • にじ鯖の管理、費用の負担などは全てドーラさんが個人的に行っている
  • 会社側で管理するのは、理由があって不可能

Minecraft に限らずサーバーの運用は専門的な知識を要します。非エンジニアでありながらライバー業と並行してにじ鯖の管理をするのは相当な負担となるはずです。 また、サーバーのレンタル代金も嵩みます1。 そのため、可能であれば会社側でエンジニアがにじ鯖を管理するのが理想的ですが、何らかの事情があってできないようです。

配信中のコメントを見ていると、「Minecraft利用規約に違反する」のがその理由だと述べている方々がいらっしゃいました。 しかしこれは恐らく誤りだと私は考えており、その理由を以降で説明します。

本題

Minecraft の規約はこのページで確認できます。 特に、 Minecraft を利用した収益化に関しては、 商業目的使用ガイドラインブランドおよび資産の使用に関するガイドラインに記載されています。

その中でも「規約に違反するため会社側で Minecraft サーバーを管理できない」という噂の元になっているのは、商業目的使用ガイドラインにある下記の一節だと思われます:

However YOU MAY:

  • Pay for advertisements of your business to be served on websites or servers related to Minecraft, so long as you are not hiring the server operator to design or host a Minecraft mod/map/server that builds an in-world representation of your brand, products, or services.
公式の日本語訳

ただし、お客様は、以下を行うことができます。

  • Minecraft に関連するウェブサイトまたはサーバーでお客様のビジネスの広告が利用されるために料金を支払うこと。ただし、お客様のブランド、製品またはサービスのワールド内での肖像を作成する Minecraft Mod/マップ/サーバーをデザインまたはホストするためにサーバーオペレータを雇用しない場合に限ります。

この日本語訳は非常に不自然で読み辛く、主語や係り受けも曖昧になっています。 そのため、一見すると「広告収益を得る際にはサーバー管理者を雇用してはいけない」かのような印象を受けるかもしれません。 しかし原文を注意深く読めば、この条項が禁止しているのは

  • 広告主が、自社製品を Minecraft のゲーム内に再現建築させる目的で、サーバー管理者に報酬を支払うこと

だとわかります。一方、いちから株式会社がにじ鯖のサーバー管理者を雇うのは

  • 配信者の所属する企業が、配信に使用するサーバーを管理させる目的で、サーバー管理者に報酬を支払うこと

に当たるので、報酬を支払う主体も目的も全く異なり、上記の条項とは関係ありません。

補足

前述の条項に関する説明を読んで、恐らく次のような疑問を持たれるのではないでしょうか:

  • この条項は何のためにあるのか?
  • 「自社製品を Minecraft のゲーム内に再現建築」とは具体的に何を指すのか?なぜ禁止されているのか?

その答えは、開発者が投稿したブログ記事の中にあります。 前述の条項は 2016 年に新しく追加されたものですが、その背景や大まかな内容が説明されています。

We want to empower our community to make money from their creativity, but we’re not happy when the selling of an unrelated product becomes the purpose of a Minecraft mod or server.

If you are an ad agency, corporation, non-profit, or politician, you can’t do these kinds of things or hire someone to do them:

  • Build a Minecraft mod or server that promotes unrelated products in playable form; e.g., if you are a restaurant chain, you can’t market your restaurant by releasing a mod that includes your restaurant built out of Minecraft blocks.
  • Build a Minecraft map or mod that markets a movie or TV show; e.g., if you’re a movie studio, you can’t make a map that uses Minecraft blocks to build out the fictional world of the movie or its characters, and you can’t make an official movie trailer out of gameplay footage from that map or mod.
  • It’s worth mentioning that fans of a particular restaurant, movie, or some other thing are still free to build things in Minecraft that represent or celebrate it so long as the goal or focus is not to promote or sell that stuff. The new rules only apply to companies or organisations that are using Minecraft to sell their products or promote their causes.
日本語訳

Minecraft ユーザーが創造性を発揮して収益を得ることは奨励していますが、サーバーや Mod の主目的が無関係な商品の販促になってしまうのは好ましくありません。
(中略)
あなたが 広告代理店 / 企業 / 非営利組織 / 政治家 に該当する場合、下記に挙げる行為をしてはいけません。また、三者を雇って行わせることも禁止します。

  • Minecraft と無関係な商品を宣伝するような、プレイアブルなサーバーや Mod を作成すること。例えば、レストランチェーンが Minecraft のブロックで自社レストランを建築してマーケティングに利用するのは禁止されています。
  • 映画やテレビ番組を宣伝するようなマップや Mod を作成すること。例えば、映画スタジオが Minecraft のブロックで映画の世界観やキャラクターを再現したり、それを用いて映画のトレイラーを作成したりするのは禁止されています。

注意点として、ファンが特定の商品などを Minecraft 上で再現建築するのは、宣伝が目的でない限り自由に行って構いません。今回新しく追加される条項は、企業が Minecraft を利用して自社のプロモーションをする場合にのみ適用されます。

この記事には書かれていませんが規約改定前に実際に起きた例として、携帯電話事業者の Verizon が Minecraft 内で実際に動作するスマートフォンを建築したことがあります。 これも現在の規約ならば違反行為にあたるものです。

結論

  • 「いちから」がにじ鯖の管理者を雇ったとしても、 Minecraft規約違反にはならないと考えられます。
  • 規約の不自然な日本語訳が原因で拡大解釈されている恐れがあります。
  • にじ鯖の管理者を雇えない理由は Minecraft の規約以外にも有り得るので、本記事を見て「にじさんじ」に問い合わせないでください

  1. 参考までに、 Conoha の VPS で Minecraft サーバーを立てる場合、プレイヤー数 11 人以上のプランでは月額 6,670 円かかります。アップデート後のにじ鯖のプレイヤー数は最大 35 人です。

Doneru についてのまとめ

概要

Doneru とは「Twitch・YouTubeのライブ配信者を支援する拡張プラットフォーム」で、 YouTube のスーパーチャットと類似した投げ銭機能などがあります。 この Doneru を、 VTuber 事務所「にじさんじ」に所属するライバーが使用したことがきっかけとなり、 Doneru が YouTube の規約に反するのではないかとネット上の一部で騒動が起きています。 本記事の目的は、この騒動に関する事実、デマ、私なりの意見をまとめて論点を整理することです。

最終的な私の意見の要点は:

  • Doneru は規約、倫理的には何も問題無いと考えられる
  • Doneru を使うことで YouTube から BAN されるとは考え難い
  • 現時点での Doneru は規約以外の面で問題が多く、信頼して実用できるレベルではない

起きた出来事

Doneru は 2019 年 7 月末にオープンβ版としてリリースされた新興のサービスです。 その後少し経って 9 月 12 日、「にじさんじ」所属の御伽原江良さんと椎名唯華さんが Doneru を導入した配信を行いました。 (このうち、御伽原江良さんの配信アーカイブは現在でも閲覧可能です。) 両名は「にじさんじ」の中でも特にフォロワーが多いためこの配信が注目を浴び、 Doneru や「にじさんじ」運営に対して様々なお気持ち表明をなさる方々が現れています。 その中でもよく見かける批判は概ね次の通りです:

  • YouTube の規約に反しているから、使用者が BAN されるのではないか
  • 投げ銭に添えられる音声読み上げや動画が悪用される
  • セキュリティ上問題がある

これを受けて、 Doneru の公式 Twitter アカウントから回答がなされました。

しかし、サービスの正当性の確認がリリース前にされていなかったことが再び批難を浴び、更なる騒動に発展しています。

Doneru 導入の背景

そもそも YouTube には、視聴者から配信者へ投げ銭できるスーパーチャット機能が既にありますが、あえて外部の投げ銭サービスを利用するメリットは何でしょうか? 収益化の面で主に以下の 2 点が挙げられます。

  1. スーパーチャットは手数料として 30% が YouTube 側に取られる。外部サービスならこれより安い手数料で済む。
  2. 収益源が YouTube だけに集中するのを回避し、リスク分散できる。

メリット 1 は既に有名な話なので、あえてここで詳細に取り上げることはしません。 一方でメリット 2 は比較的話題に上っていませんが、非常に重要です。

YouTube の収益化に対する締め付けは年々厳しさを増しており、規約違反と判定されて収益化を解除される例が頻発しています。VTuber 界での事例の一部を挙げると:

  • キズナアイさんのチャンネルが無警告で BAN されたがすぐ復活した1
  • 鈴鹿詩子さんのゲーム配信が「ヌードや性的なコンテンツ」と判定されてチャンネルが無警告で BAN された2
  • ベイレーンさんが「繰り返しの多いコンテンツ」と判定されて収益化解除された3
  • ふぇありすさんが「性的満足を意図したコンテンツ」と判定されて収益化解除されている4

これらの例は、中身をよく見てみればどんな人間でもおかしな判定だとわかるものです。 しかし YouTube では、コンテンツの最初の審査を bot が担当しているため誤審査がよく起こります。 異議申し立てをすれば人間による再審査が行われますが、申し立てができるのは一ヶ月後で、審査が覆る確率もそれほど高くないようです。 つまり、真っ当なチャンネルがある日突然に規約違反と判定されて、その後少なくとも一ヶ月間は収入が大幅減、最悪引退に追い込まれるリスクがあります。

YouTube 外部のサービスに収益源を分散しておけば、こうしたリスクをある程度軽減することが可能です。

ネット上の的外れな批判

今回の件について SNS などを見ていると、一部真っ当な批判もありますが、大半は全く見当違いな言いがかりやデマで溢れています。 そこで、明らかにおかしいと根拠を持って否定できる意見を以下で取り上げ、私なりの回答を述べます。

YouTube で配信しているのだからスーパーチャットを使え

配信者にそのような義務や義理はありません。理由は主に以下の 3 点です。

  • スーパーチャットは独立したオプション機能であり、使用するか否かはユーザー(配信者)の自由である。
  • YouTube利用規約収益化ポリシーには、外部サービスによる収益化を禁止するような条項は無い。
  • スーパーチャットの使用をもし強制すれば、それは巨大プラットフォームの力を濫用した囲い込みであり、市場競争を妨げる。

スーパーチャットを使わないのは、場所代を払わないタダ乗り行為である

「スーパーチャットを使わない」ことと「YouTube の利益に貢献しない」ことは全く異なります。 YouTube の主な収益源は、広告費と、ユーザーが生み出す行動履歴などの膨大な個人データです。 これはスーパーチャットの有無とは関係なく YouTube 上で大量の視聴者を集めれば必ず発生するものなので、そもそもタダ乗りは不可能です。

Doneru は中間マージンを取っているが Streamlabs はそうではないので同じ座組ではない

今回の件で YouTube 側にとって問題となり得るのは、外部サービスによってスーパーチャットの収益機会が失われるというただ一点だけであり、 その点において Doneru も Streamlabs も共通しています。中間マージンの有無やビジネスモデルの違いは問題に関係ありません。

Doneru は YouTube の承認済みクラウドファンディングサイトに記載されていないから使用できない

このページにあるのは、動画の再生終了直前に表示されるエンドカードにリンクとして設定できるサイトの一覧です。 ここに記載されていないサイトでも、動画内や概要欄でページを宣伝するのは問題ありません。 (もし禁止されていたら、いわゆる企業案件のアフィリエイトリンクなどは全て規約違反ということになります。)

Doneru を使うと YouTube アカウントが BAN される

上述したように Doneru の使用は YouTube の規約に反していないので、特に BAN される理由が考えられません。

Doneru ではなく YouTube 公式に承認されている Streamlabs を使え

Streamlabs の donation page は日本語表示に対応していません。

にじさんじ」運営は所属ライバーのことを考えず拝金主義に走っている

ライバーのことを考えるのであれば、ライバーの収益を増やす/安定化させるのは最重要事項です。

Doneru の問題点

Doneru が規約や倫理的には問題ない旨をここまで説明しましたが、それ以外の部分で後述する大きな欠陥を抱えています。 ただし、対処策が明らかな問題ばかりなので、今後の修正により完成されたサービスになる可能性はあります。

投げ銭時のメッセージや音声読み上げが荒らしに悪用される

通常のチャットログに埋もれて流れやすいスーパーチャットと違って、 Doneru の投げ銭に添えられるメッセージは専用の枠に表示されかなり目立ちます。 メッセージの音声読み上げがオンにされていれば尚更です。

これを悪用してセンシティブな内容を配信に投げつけることが可能で、実際に「にじさんじ」のライバーが Doneru を使った際にこうした荒らし行為が見られました。

サイトの作りが全体的に雑で、セキュリティを考慮していない可能性が高い

存在しない URL を指定すると、通常はそのことを示す 404 エラーページが表示されますが、 Doneru のサイトではエラーページの設定が正しくされておらず、本来ユーザーに対して表示すべきでないシステムログが露出しています。 例えば https://doneru.jp/example にアクセスすると、以下の内容のページが表示されます。

Not Found
404

NotFoundError: Not Found
    at /var/www/doneru-dev/app.js:95:8
    at Layer.handle [as handle_request] (/var/www/doneru-dev/node_modules/express/lib/router/layer.js:95:5)
    at trim_prefix (/var/www/doneru-dev/node_modules/express/lib/router/index.js:317:13)
    at /var/www/doneru-dev/node_modules/express/lib/router/index.js:284:7
   ⋮

三行目以降の部分はスタックトレースと呼ばれ、開発者がエラーの原因を特定するために用いるものです。 スタックトレースはユーザーが見ても理解できるものではなく、またシステム内部の情報を悪意ある攻撃者に与える恐れがあります。 そのため、製品段階のサイトではこのようなシステムログをユーザーに一切見せないのが常識とされています5

この他にも、フォームの入力内容を検証せずに送信するなど多数の問題が挙げられます。

個人的な感想

YouTube 一強の状態でクリエイター側が不利益を被っている中、それを解決できそうなサービスが現れるのは嬉しい限りです。 その際に何か問題点が想定できるのであれば、ただの推測で終わらせずに実験で検証すべきだと考えています。

「わざわざ金を払ってまで配信で嫌がらせをする生物が多くいる」というのは検証が必要な仮説だと私は思っていたのですが、 SNS 上の皆様には至極当然で確実に予測できる事実だったようです。

BERT 日本語モデルの実験

概要

f:id:P_N_D:20190528210128p:plain

BERT (arxiv, GitHub) を理解するための第一歩として、訓練済み日本語モデルを fine-tune して文章のトピック分類の実験をしました。

この記事に含まれない内容:

  • BERT の説明

この記事に含まれ内容:

  • 訓練済み BERT 日本語モデルのまとめ
  • 環境構築や実験にあたって私が遭遇した問題とその対処
  • BERT が分類に失敗したサンプルの定性的な分析

訓練済みの BERT 日本語モデル

現時点で私の知る限り、日本語に適用できる訓練済み BERT モデルは以下の4つがあります。特に今回の実験では、

という理由で BERT with SentencePiece for Japanese text を選択しました。

BERT-Base, Multilingual

BERT の GitHub レポジトリで公式に提供されている多言語モデル。使っているトークナイザーが日本語に適しておらず、過剰に小さな(ほぼ 1 文字単位の)トークンに分割されてしまうことが報告されています*1

BERT with SentencePiece for Japanese text

菊田遥平さんが提供しているモデル。前述のトークンの問題に対処するため SentencePiece を使用しています。

hottoSNS-BERT

ホットリンクの R&D 部が提供しているモデル。ここに挙げたモデルの中で唯一 Twitter データをコーパスに使っています。

BERT日本語Pretrainedモデル

京大の黒橋・河原研究室が提供しているモデル。当然ながら、トークナイザーには自製の JUMAN を採用しています。

TensorFlow + GPU 環境構築

私の研究室は GTX 1080 Ti を 2 枚積んだ GPU サーバーを所有しているのですが、極端に計算パワーを必要とする研究を私は今までしていなかったため、一度も使ったことがありません。今回は金をかけずにとりあえず BERT を試したかったこともあり、良い機会なのでこの GPU サーバーを使うことにしました。

しかし案の定と言うべきか、最初に環境を構築する段階で多数のエラーに見舞われかなり時間を費やしたので、その記録を残しておきます。

tensorflow-gpu インストール時のエラー対処

TensorFlow で GPU 計算をするためには tensorflow-gpu パッケージが必要です((https://www.tensorflow.org/install/gpu)。これをインストールした後に Pythonimport tensorflow を試みた時に以下のエラーが発生しました。

ImportError: libcublas.so.10.0: cannot open shared object file: No such file or directory

issue を見る限り、 TensorFlow と CUDA のバージョンが合致しない時にこのエラーが発生するようです。そこで、サーバーにインストールされていた CUDA のバージョンを確認すると

$ cat /usr/local/cuda/version.txt
CUDA Version 9.2.148

TensorFlow 最新安定版の 1.13.1 では CUDA 10 しかサポートしていない*4ので、どちらかのバージョンを変更する必要があります。管理者権限無しで手っ取り早く対処するために TensorFlow の 1.12.0 をインストールしようとしたところで再びエラーに遭遇しました。

ERROR: Could not find a version that satisfies the requirement tensorflow-gpu==1.12.0 (from versions: 1.13.0rc1, 1.13.0rc2, 1.13.1, 2.0.0a0)

もちろんバージョン 1.12.0 は存在していますが、 Python 3.7 と互換性が無い*5ためにインストールできなかったようです。これには Python 3.6 の仮想環境を作成してから TensorFlow をインストールすることで対処しました。

$ conda create -n bert python=3.6
$ conda activate bert
$ conda install -y tensorflow-gpu==1.12.0

最後に、 tensorflow-gpu が正常にインストールされて GPU が使用可能であることを確認します。

$ python -c "import tensorflow as tf; tf.test.is_gpu_available()"
2019-05-28 15:18:53.502786: I tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: SSE4.1 SSE4.2 AVX AVX2 AVX512F FMA
2019-05-28 15:18:54.302067: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1432] Found device 0 with properties:
name: GeForce GTX 1080 Ti major: 6 minor: 1 memoryClockRate(GHz): 1.582
pciBusID: 0000:17:00.0
totalMemory: 10.92GiB freeMemory: 10.77GiB
2019-05-28 15:18:54.377021: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1432] Found device 1 with properties:
name: GeForce GTX 1080 Ti major: 6 minor: 1 memoryClockRate(GHz): 1.582
pciBusID: 0000:65:00.0
totalMemory: 10.91GiB freeMemory: 10.77GiB

GPU の状態監視

nvidia-smi コマンドを使うと、 GPU の使用率などを確認することができます。 特に --format=csv オプションを指定すると結果が CSV 形式で出力されるので、スクリプトで読み込んで自動的に通知すると便利です。

私はこのレポジトリ に少し手を加えて Python 3 用に修正し、 GPU の状態を Slack の web hook に通知するようにしています。

メモリ解放

Jupyter Notebook 上で TensorFlow による GPU 計算が終わった後に状態を見ると、 GPU 使用率が 0% の一方で大量のメモリが占有されたままになっていました。ドキュメントを読む限り、これは特に異常なことではありません。メモリのフラグメンテーションを避けるために、 TensorFlow は プロセスが終了するまで GPU メモリを一切解放しない 仕様になっているようです。

Jupyter で GPU 計算が完了した後は忘れずにカーネルをシャットダウンしましょう。

実験

方法

finetune-to-livedoor-corpus.ipynb をほぼそのまま使用しました。 ただし、実験時に GPU メモリの空き容量が 7GB 程度しか無かったため、 run_classifier.py の引数 train_batch_size を 4 から 2 に下げて実行しました*6

データ

livedoor ニュースコーパス (NHN Japan株式会社がクリエイティブ・コモンズライセンス「表示 – 改変禁止」のもと提供)

livedoor ニュースの 9 つのカテゴリごとに収集した記事のテキストデータです。全部で 7376 個の記事があり、各カテゴリごとの内訳は表のようになっています。

カテゴリ 記事数 備考
トピックニュース 771 芸能ニュースなど
Sports Watch 901
IT ライフハック 871
家電チャンネル 865
MOVIE ENTER 871
独女通信 871
エスマックス 871 スマホ、モバイル、ガジェットなど
livedoor HOMME 512 男性向け、キャリア、デジタルなど
Peachy 843 女性向け、恋愛、ファッションなど

結果

f1-score などの数値は、 finetune-to-livedoor-corpus.ipynb の先頭に記載されている結果と大差無いため割愛します。

BERT が「不正解」の予測をした全 41 件のサンプル (csv ファイル) を見てみると、もう少しおもしろいことがわかります。例えば以下の記事を読んで、 livedoor ニュースの 9 つのカテゴリのどれに該当すると思いますか?

手のひらに載る本格派 LUMIX G2のGはグレートだ今やマイクロ一眼はデジタルカメラの人気商品となり、各社とも続々とこの市場に参入を開始している。マイクロ一眼は、レトロなスタイルに加え、ミラーレスによる小型化でバックに入れて持ち歩けるデジタル一眼として年齢を問わずに使えるところが受けている。とはいえ、今人気のマイクロ一眼には、コンパクトデジカメやレンズ一体型デジカメに比べて高画質というメリットはあるが、デメリットもある。パナソニック LUMIX DMC-G2は、マイクロ一眼のデメリットを解消したカメラなのだ。 (後略)

実際のカテゴリは「livedoor HOMME」で、一方 BERT が予測したカテゴリは「IT ライフハック」でした。また、その次に予測確率  1.6 \times 10^ {-6} で「家電チャンネル」が続きます。もうお気づきかと思いますが、これら 3 つのカテゴリは排反なものではなく重複があってもおかしくありません。上述のサンプルであれば「livedoor HOMME」よりむしろ「家電チャンネル」か「IT ライフハック」の方が尤もらしいカテゴリに感じます。

classification の問題は、カテゴリが排反か否かによって"multi-class" と "multi-label" に分けられます。しかし今回の実験設定には以下のようなミスマッチがあり、それが原因で「正解より予測の方が妥当」という状況が発生しています。

  • カテゴリは実際には multi-label であるべき
  • 正解データは multi-class
  • 今回用いた fine-tuning のコードは multi-class 用(softmax で確率を出力)

他の不正解サンプルについても「正解より予測の方が妥当」が大半を占めており、本当に予測ミスだと思われるものは 6 件 (csv ファイル)しかありません。 例として、以下の記事は実際には「livedoor HOMME」ですが、BERT はこれを「Peachy」と予測しました。

イケメン社長、野菜を売る——、若林輝男さんに聞く“農業ビジネスのカタチ”生産者と消費者が直接結びつく新たな市場として、農林水産省の補助事業として昨年9月に始まり、全国9都市で開催されるようになったマルシェジャポン。晴れ渡った3月の休日、大勢のお客さんで賑わう六本木の会場では、なんと“生姜(しょうが)のみ”を売る背が高くルックスのよい一人の男性を発見しました。土日は都内のマルシェジャポン会場で、平日は三輪自転車を走らせ都心の駅前を中心に、高知産の新鮮な生姜や青森産の大蒜(にんにく)を販売しているリーベル・アソシエイツ合同会社の代表=若林輝男さんです。 (後略)

結論

Google Colab 使った方が手っ取り早く実験できると思います。

にじさんじの Twitter フォロワー分析

概要

にじさんじに所属する全ライバーの Twitter フォロワー情報を取得し、

  • にじさんじ全体の(重複を除く)フォロワー数
  • 1人のファンがフォローするライバー数の分布
  • フォロワーの重複度合いを基に(t-SNE で)図示したライバーの位置関係

を分析しました。

背景

にじさんじは、執筆時点で合計 73 人のライバーを抱える最大規模の VTuber グループです。他のグループや事務所と比較して特徴的な点は:

  • 編集された動画よりも長時間のライブ配信がメイン
  • グループ内でのコラボが活発
  • 非常に速いペースで所属ライバーが増え続けている

特に最近はにじさんじ全体で YouTube チャンネルの登録者が大きく伸びているため、ファンの一人として嬉しく感じています。 一方で、「ライバーの増加により延べ登録者数が増えただけで、新規層の獲得はできていない」のではないかと疑問を抱いていました。 というのも、「頻繁にコラボ配信をするライバー同士は、視聴者層が重複しやすい」と予想できるため、「一人で多数のにじさんじライバーをフォローしている人が多い」だろうと考えられるからです。

そこで、上記のカギカッコで囲まれた推測の真偽を検証するために、にじさんじライバーの Twitter フォロワーを分析しました。

分析結果

にじさんじ全体のフォロワー数

にじさんじに所属するライバーの全フォロワー ID を取得しました。これを基ににじさんじ全体のフォロワー数を合計した結果:

  • ID の重複もカウントすると約 430 万人
  • ID の重複を除外すると約 47 万人

となりました。

1人のファンがフォローするライバー数の分布

各フォロワー ID ごとに出現回数をカウントすることで、「にじさんじのフォロワーは 1 人あたり何人のライバーをフォローしているか」を計測しました。 その結果をヒストグラムにしたのが下図です。横軸はフォロー中のライバー数、縦軸は該当するフォロワー数(対数スケール)を表しています。

f:id:P_N_D:20190525232408p:plain

また、同じ結果を以下の表にも示しました。

フォロー中のライバー数 該当フォロワー数
1 182385
2 60070
3 36276
4 23799
5 17270
... ...
69 1164
70 1082
71 1854
72 1603
73 5991

おもしろい特徴として、 フォロー 72 人から 73 人(全ライバー数)のところで該当数が大きく跳ねているのが見て取れます。 「数人取りこぼすくらいならいっそ全部フォローしたい」というコレクター精神が働いた結果でしょうか。

ライバーの位置関係の図示

2 つの Twitter アカウントのフォロワーを比較して、「フォロワーの重複の度合い」を数値のスコア(Dice 係数など)で表すことができます。 そのスコアを t-SNE と呼ばれるアルゴリズムに入力して、にじさんじライバーの位置関係を 2 次元のグラフ上で視覚化しました。このグラフでは

  • フォロワーの重複が多いライバーはグラフ上で近く
  • フォロワーの重複が少ないライバーはグラフ上で遠く

という関係を満たすように、各ライバーを表す点を自動的に配置しています。

f:id:P_N_D:20190526001415p:plain

上図を見ると、デビュー時期が近いライバーが近くに固まって配置されています。 よりわかりやすくするため、にじさんじ統合前のグループと、統合後に同時期にデビューしたライバーたちを囲んで下図に示しました。

f:id:P_N_D:20190526001437p:plain

更に、大きなクラスタの中に着目すると、 JK 組や舞元力一など頻繁にコラボ配信をするライバー同士が特に密集していることもわかります。

この結果から、「デビュー時期が近かったり頻繁にコラボ配信したりするライバー同士は、フォロワーが重複しやすい」という至極当然な推測が裏付けられました。

技術的な詳細

今回の分析には Python と Jupyter Notebook を使用しました。

Twitter API

にじさんじライバーのリストの取得

にじさんじ公式の Twitter アカウントが全ライバーのリストを作成しているので、今回はそこからライバーの情報を入手しました。

Twitter APIlists/members にリクエストすると、リストに含まれている全ユーザーの情報( ID 、スクリーンネーム、アイコン画像など)を取得できます。このとき、リストの owner_screen_nameslug の指定が必要です。リストの URL は

https://twitter.com/{owner_screen_name}/lists/{slug}

の形式になっているので、手動で確認することもできます。また、 owner_screen_name がわかれば /lists/list から slug を取得することも可能です。

フォロワーの取得

Twitter APIfollowers/ids に対して、ユーザー ID (e.g. 958737597336928256 )またはスクリーンネーム (e.g. @MitoTsukino ) を引数に入れてリクエストすると、全フォロワーのユーザー ID を取得できます。

1 回のリクエストでは最大 5000 件のフォロワーしか取得できず、また、リクエストは 15 分あたり 15 回までに限定されています。従って、記事執筆時点でのにじさんじフォロワー延べ 430 万人 を全て取得するためには 14 時間以上必要になります。

集合の類似度

任意のライバーのペアに対して、フォロワー ID の集合の類似度を Jaccard 係数、 Dice 係数、 Simpson 係数で計算しました。 notebook では 3 種類の類似度全てについて埋め込み結果を図示しましたが、この記事には Dice 係数を用いた結果だけを掲載しています。

t-SNE による 2 次元埋め込み

次元圧縮

ライバーに関する距離行列が前述の類似度によって与えられるので、次元圧縮で 2 次元ユークリッド空間に埋め込み可視化します。次元圧縮のアルゴリズムとして有名なものに

などがありますが、今回は特に t-SNE を使用しました。

パラメーター調整

t-SNE には perplexity というパラメータが存在します。原論文によれば:

The performance of SNE is fairly robust to changes in the perplexity, and typical values are between 5 and 50.

とのことですが、実際には perplexity の値によってかなり結果は変動します。少なくとも、 sklearn のデフォルト値をそのまま使うと、収束前に停止したり解釈困難な結果を出力したりすることが多々あります。

再び原論文から引用すると:

The perplexity can be interpreted as a smooth measure of the effective number of neighbors.

これは、 n 個の要素に関する一様分布の perplexity が  n になる事実から類推できます。 "the effective number of neighbors" はデータの性質やサンプル数によって大きく変動するはずなので、それに合わせて perplexity も設定する必要があると考えられます。特に今回はサンプル数が 73 と少ないので、 perplexity を 5 に設定して実験しました。

perplexity の影響については、この解説記事も参考になります。パラメーターを変えた結果をインタラクティブに確認できるので、ぜひ一度試してみてください。

結論

分析結果を踏まえると、最初に述べた推測はいずれも妥当なものだと言えそうです:

  • ライバーの増加により延べ登録者数は増えたが、新規層はあまり獲得できていない
  • 一人で多数のにじさんじライバーをフォローしている人が多い
  • 頻繁にコラボ配信をするライバー同士は、視聴者層が重複しやすい

1 人のファンが実際に追いきれるライバーの数には限度がある(長時間の配信が多いと尚更)ため、もしこのままの状況が今後も続くと、にじさんじ全体の収益はほぼ頭打ちになってしまうかもしれません。

万能な情報整理アプリ Notion のレビュー

Notion は、メモやタスク管理、その他情報を整理するための Wiki やデータベースとして使える万能なアプリです。Notion を一ヶ月ほど使用してみてわかった利点や欠点をまとめて記事にしました。

はてなブログにアニメ gif を貼ろうとして失敗したので、Notion 上で書いた原稿をそのまま公開します:
https://www.notion.so/Notion-f1b8c66221094286b3aea3e264203d63

PySnooper の中身を覗く

概要

こちらのブログ記事で、 PySnooper というデバッグ用の Python ライブラリが紹介されていました。
どんな仕組みで実装されているか気になり、またソースコードの行数も大したことがなかったので、実際に中身を覗いてわかったことをこの記事にまとめます。

PySnooper とは

関数定義にデコレーターをつけるだけで、実行中にソースコードの各行でローカル変数がどんな値を取っているか確認することができます。
例えば、以下のようなスクリプトを実行すると:

from pysnooper import snoop

@snoop()
def func(x):
    a = x + 1
    b = a * 4
    return a + 2

func(1)

標準出力に以下のようなトレース結果が表示されます:

Starting var:.. x = 1
23:06:03.976593 call         4 def func(x):
23:06:03.976593 line         5     a = x + 1
New var:....... a = 2
23:06:03.976593 line         6     b = a * 4
New var:....... b = 8
23:06:03.976593 line         7     return a + 2
23:06:03.977593 return       7     return a + 2
Return value:.. 4

ソースコード探訪

snoop 関数

PySnooper でユーザーが使用する API は基本的にデコレーター関数の snoope だけです。 というわけで、まず snoope が実装されているソースコードから見ていきましょう。

最初の数行は、トレース結果の出力先(stderr など)を指定するためのもので、本筋とはあまり関係が無いので無視します。

その直後の decorate 関数の定義を見ると、ほとんどの処理を Tracer クラスに委譲していることがわかります:

    def decorate(function):
        target_code_object = function.__code__
        tracer = Tracer(target_code_object=target_code_object, write=write,
                        truncate=truncate, variables=variables, depth=depth,
                        prefix=prefix, overwrite=overwrite)

        def inner(function_, *args, **kwargs):
            with tracer:
                return function(*args, **kwargs)
        return decorator.decorate(function, inner)

Tracer クラス

__enter__, __exit__ 関数

上述の decorate 関数の中では、 Tracer クラスのオブジェクトが with 構文で使われています。 つまり、デコレートする関数を呼び出す前後に Tracer クラスの __enter__, __exit__ 関数がそれぞれ呼び出されるということです。 そこで、これらの関数の定義を確認すると、 __enter__sys.settrace を使ってトレース関数を設定し、 __exit__ でそれを元に戻していることがわかります:

    def __enter__(self):
        self.original_trace_function = sys.gettrace()
        sys.settrace(self.trace)

    def __exit__(self, exc_type, exc_value, exc_traceback):
        sys.settrace(self.original_trace_function)

sys.settrace について

sys — System-specific parameters and functions — Python 3.7.3 documentation

sys.settrace(trace) を実行すると、システムのトレース関数として trace を設定します。その後以下のいずれかのイベントが発生したときに trace が呼び出されます:

  • call : 任意の関数を呼び出すとき
  • line : ソースコードの任意の1行を実行するとき
  • return : 任意の関数またはコードブロックが値を返すとき
  • exception : 例外が発生するとき
  • opcode : opcode を実行するとき

このとき、 trace は3つの引数 frame, event, arg と共に呼び出されます:

  • frame : 呼び出し時のスタックフレームの状態を表すオブジェクト。このドキュメントに記載されている frame 型の属性をもつ
  • event : 上述のいずれかのイベントを表す文字列
  • arg : イベントが return のときは関数の返り値、イベントが exception のときはタプル (exception, value, traceback)

trace 関数

トレースすべき関数か判定

snoope(depth=k) でデコレータされた関数の呼び出しと、そこから k 層分のスタックだけがトレースの対象になります。 すなわち、以下のいずれかの条件を満たす場合だけトレースが行われます:

  • 現在実行中の関数 frame.f_code とデコレータされた関数 self.target_code_object が一致する
  • 現在のスタック frame から呼び出し元 f_backk 回以下辿ることで、デコレータされた関数に到達できる

変更された変数を表示

以下の部分で、前回トレース時の変数の状態 old_local_reprs と現在の変数の状態 local_reprs を取得します:

        self.frame_to_old_local_reprs[frame] = old_local_reprs = \
                                               self.frame_to_local_reprs[frame]
        self.frame_to_local_reprs[frame] = local_reprs = \
                               get_local_reprs(frame, variables=self.variables)

frame.f_locals.items() によって、現在のスコープ中にあるローカル変数の名前と値を全て取得できます。 また、 self.variablessnoop(variables=(x, y)) のようにオプション指定されたスコープ外の変数を表します。

old_local_reprslocal_reprs が得られたら、これらを比較して変更または新規に追加された変数を特定し、結果を出力します。

実行中のソースコードの行を表示

frame からソースコードを取得する処理は get_source_from_frame 関数で定義されています。

キャッシュの参照や、 IPython の場合その他の例外処理を除いて、重要な部分だけに着目すると:

module_name = frame.f_globals.get('__name__')
loader = frame.f_globals.get('__loader__')
source = loader.get_source(module_name)

これが source = source.splitlines() で行ごとのリストに変換して返されます。その後 trace 関数の中で以下のようにして実行中の行だけを抽出します:

        line_no = frame.f_lineno
        source_line = get_source_from_frame(frame)[line_no - 1]

トレース関数の引数から取得できる情報を実験的に確認

PySnooper の挙動を大幅に簡略化して再現し、実際にソースコードやローカル変数を取得できるか確認しましょう。 以下の Python スクリプトを保存して実行します:

import sys
    
def func(x):
    a = x + 1
    b = a * 4
    return a + 2

def trace(frame, event, arg):
    if event == 'line':
        module = frame.f_globals.get('__name__')
        loader = frame.f_globals.get('__loader__')
        source = loader.get_source(module)
        lineno = frame.f_lineno
        line = source.splitlines()[lineno - 1]
        print(line)

    if event == 'return':
        print(frame.f_locals.items())

    return trace

sys.settrace(trace)
func(1)

すると標準出力に以下の結果が print され、確かにソースコードやローカル変数を取得できることがわかります:

    a = x + 1
    b = a * 4
    return a + 2
dict_items([('x', 1), ('a', 2), ('b', 8)])
dict_items([])

まとめ

  • snoop でデコレートされた関数の呼び出し前に sys.settrace を設定し、呼び出し後に元に戻す。
  • トレース関数の frame 引数から、現在のスタックフレームの状態を取得する。
    • デコレートされた関数から k 層以内のスタックか否か
    • 実行中の関数が定義されたソースコード
    • 実行中のソースコードの行
    • ローカル変数の名前と値
  • 実行中の行を表示
  • ローカル変数に変更があれば表示