蛇ノ目の記

技術のことも。そうでないことも。

PyCon SG 2019に行ってきた話 (主に観光

10/10~12にかけて開催されたPyCon SG 2019に行ってきた。

pycon.sg

10/10, 11がカンファレンス, 10/12がチュートリアルという構成。詳しくは後述するけどカンファレンスのみ参加。

例によって真面目な話はPython学習チャンネルの記事を見てください。

blog.pyq.jp

3行でカンファレンスの感想。

TL;DR

本編

今回の日程は10/9~10/13の4泊5日。 id:nikkie-ftnextと共に行動した。なんとかsoftという人は不運にも参加できず。

1日目

今回の宿。カーテンを開くとシンガポールの街並みが、なんてことはなく吹き抜けのロビーが見える。つまり陽の光が差さない。たぶん日本だと建築法とかそんなんで怒られそうな作り。

宿はChinaTown駅周辺。少し歩くと中華街がある。

中華街……?お前らどう見ても中南米っぽいけど。

地下鉄でOrchardに移動。中華街から一変して高層ビル群。

去年とは違って、今年は1日目から晴天。いわゆるシンガポールの画。

宿泊して無くても展望台には上ることができる。お値段なんと23SGD(1800円程度)。高いわ。でもスカイツリーのてっぺん行くよりは安いのかな。知らんけど。

今年もマリーナベイサンズのカジノへ。100SGD持って2SGDルーレットに挑戦。

途中でちょっと増えたけど、結局ゼロに。引き際覚えろ。というかポーカーでいうところの50BBスタートなのでつらみがある。100BBはあった方がいい。

nikkie氏とカジノのつらみを味わったあとはMaxwell Food Centre海南鶏飯。なお、今回の旅では合計3回食べている。天天海南鶏飯が一番美味い。

シンガポールでも天気の子が上映されているっぽい。天気の子めっちゃ好きな推しに思わずInstagramのDMで送ってしまうなどした。仕方ないね。

日本からのPyCon SG参加勢とシンガポール在住勢で中華屋さん(津津餐室)で夕飯。鶏肉美味しいです。

二次会はThe Armouryクラフトビールシンガポールは酒が高いけど、結構クラフトビールの文化があるっぽい。

China Townに某名探偵が。版権ガン無視イラストかな、と邪推したけど「紺青の拳」コラボイラストらしい。

PyCon SG 2019会場のRepublic Polytechnic。

めっちゃ広いし近代的でおしゃれ。日本の建築家によるデザインらしい。

真面目な話はPython学習チャンネルに丸投げしているので、カンファレンスの話は一切しない。

2日目もビール。ここは去年も行った川沿いのシャレオツクラフトビールスポット。

時間が少し飛んで4日目。nikkie氏がチュートリアルに向かったので一人で朝飯へ。Ya Kun Kaya Toastでシンガポール名物のカヤトースト。カヤというのはココナッツミルクと卵、砂糖で作られたジャム。甘い。半熟卵に浸して食べるスタイル。美味しいのでシンガポール行ったらぜひ。

歴史からも経験からも学べない愚者なのでセントーサ島のカジノへ。スロットだけやったけど全然ダメだった。あとスロットあんまり面白くない。

チュートリアルが終わったnikkie氏と合流して観光へ。アラブ街は去年も来たけど、今年はモスクの中を見に行ったりした。狭い街にいろいろな文化が混在しているシンガポールの雰囲気やっぱ好き。

夜はtakanoryさんたちと共にPyCon SGスタッフディナーへ。シンガポールだけど台湾料理の鼎泰豊。美味しかった。ありがとうPyCon SGスタッフ。

なんかすごい量を注文したっぽくて炒飯3皿、豆苗炒めとかいろいろ出てきた。Keynote speakerのRich Jones氏とスタッフと日本のアニメの話をした。Ghost In the ShellとかCowboy Bebop超クールだよね、みたいな話をした気がする。

そして蒸籠タワーが築かれた。

他の参加者は二次会行ったけど、シンガポールの夜景を見ておきたくて徒歩でマーライオン公園へ。映える。

そこからLittle Creatures Brewingに移動してまたクラフトビール

最終日。チャンギ空港に併設されたショッピングモールの中には滝があったりした。

というわけでシンガポールの話でした。

アイドルで理解するYoutube Data API - 動画情報取得編

アイドルで理解するhogehogeシリーズ第2弾です。

PyLadies Tokyo 5周年パーティのLT大会で@meliさんが発表した、Youtube Data APITwitterとLambdaを使って「Pythonで推しの役に立ちたい!」というLTに刺激を受けて、Youtube Data API面白そうじゃんと思うも使ったことなかったので使い方を書いてみる。

TL;DR

3行でおk

下準備

  1. Google API Consoleでプロジェクトを作成する。

  2. API Consoleの認証情報からAPI Keyを作成する

  3. ダッシュボードのライブラリからYouTube Data API v3を選択して、APIを有効にする

これでYoutube Data APIを使う準備が整った。

今回はOAuthを必要としないAPIを使っていく。

APIを叩くのを簡単に済ませたいのでrequestsを使う。仮想環境を作ってrequestsをインストールしておく。

$ python3 -m venv venv
$ . venv/bin/activate
(venv)$ pip install requests

API Keyはconfig.pyに書いておいて、複数のスクリプトで使いやすくしておく。

# config.py
API_KEY = YOUR API KEY

余談

requestsのドキュメントでレスポンスをJSON形式で取得するメソッドを検索したら、

このサイトは安全に接続できません

2.python-requests.org から無効な応答が送信されました。

ERR_SSL_PROTOCOL_ERROR

とか出てきた。Kenneth氏〜

動画の情報を取得してみる

動画情報のAPIVideos エンドポイントにまとめられている。

developers.google.com

その中でも情報の取得にはVideos:listを使う。

developers.google.com

GETメソッドを使う。

GET https://www.googleapis.com/youtube/v3/videos

Videos:listでは例えば以下のような情報が取れる。

  • snippet: 動画の基本的な情報(タイトルやアップロード日時、説明など)

  • ContentDetail: 動画の長さやアスペクト比、字幕の有無、各国でのレーティング状況など

  • statistics: 動画の統計情報(再生回数, 高評価・低評価の数など)

その他の情報はリファレンスを参照のこと。

一部の情報(fileDetail, processingDetail, suggestions)は動画の所有者しか取得できないので注意。

基本的な情報を取得してみる

パラメータのうち、今回は必須パラメータであるpartと動画IDのidを使う。

partはどの情報を取得するかを指定するパラメータで、snippetstatisticsなどをカンマ区切りリストで指定する。

まずはpartにはsnippetidにはsora tob sakana 3rdシングル「flash」のティザーMVのIDを指定する。

flashは11/13(水)に発売される新曲。先行配信は10/26(土)から始まったのでみんなチェックしような。

CDの方はアーティスト盤のジャケ写がめっちゃかっこいい。ポスター欲しくなる出来。

import requests

from config import API_KEY

URL = 'https://www.googleapis.com/youtube/v3/videos'

part = ['snippet']
ids = ['JTpFWzKGhFU']

payload = {
    'part': ','.join(part),
    'id': ','.join(ids),
    'key': API_KEY,
}

r = requests.get(URL, params=payload)
res = r.json()
snippet = res['items'][0]['snippet']
print(snippet['title'])
print(snippet['publishedAt'])
print(snippet['channelTitle'])
print(snippet['description'])

レスポンスはJSON形式で返ってくるため、requests.models.Response.json()で辞書形式にしている。

JSONの詳細についてはリファレンスのリソース表現を見てもらうとして、itemsフィールドに各動画の情報が格納されている。ここでは動画IDを一つだけ指定しているのでres['items'][0]を参照している。複数の動画IDを指定した場合は指定した順に格納される。

各動画の情報はさらに辞書形式になっている。ここにpartで指定した種類の情報が入っている。ここではsnippetのみなので、res['items'][0]['snippet']を参照している。

sora tob sakana/flash(Teaser)
2019-10-18T15:00:07.000Z
sora tob sakana Official YouTube Channel
【新曲「flash」CD発売に先駆けての先行配信決定】
2019年10月26日(土)より順次配信開始します。
各配信リンク先は追ってお知らせします。

==

2019年11月13日発売3rdシングル「flash」MUSIC VIDEOのティザーVです。

flash
(TVアニメ「ハイスコアガールⅡ」オープニングテーマ)
Lylic/music/arrange:照井順政

ーーMUSIC VIDEO STAFFーー
Director 斉藤友和(epoch)

Producer 須貝日東史(koe Inc.)

Cameraman 柳沼貴幸
Camera Assistant  横山穂高

Lighting Designer 二階堂 譲
Lighting Assistant 高谷英之 堀尾秀樹

Hair&Make(sora tob sakana Member) 横山 藍 
Costume Design/Styling 浅野実希

Choreographer MARIKA

Production Staff 平田樹生 木﨑 竣



=====================================================
sora tob sakana/flash
発売日:2019年11月13日(水)
<アーティスト盤>CD+DVD 1000749884 2,091円+税
=CD=
1.flash
(TVアニメ「ハイスコアガールⅡ」オープニングテーマ)
作詞・作曲・編曲:照井順政
2.パレードがはじまる
作詞・作曲・編曲:照井順政
3.踊り子たち
作詞・作曲・編曲:照井順政

=特典DVD=
「flash」 Music Video

=ジャケット仕様=
16Pブックレット
  ・ライナーノーツ
  ・イラストレーター「ayaka nakamura」「Megumi Fujita」
   による描きおろしのイラスト

<通常盤>CD 1000749885 1,182円+税
=CD=
1.flash
(TVアニメ「ハイスコアガールⅡ」オープニングテーマ)
作詞・作曲・編曲:照井順政

2.flash-TV Size-
3.flash-YMCK Remix- 

=ジャケット仕様= 
アニメ「ハイスコアガールⅡ」描き下ろしイラスト差し替え
ジャケット
8Pブックレット
=====================================================
【公式HP】http://soratobsakana.tokyo
【公式Blog】http://ameblo.jp/soratobsakana/
【公式Twitter】@soratobsakana
【公式Instagram】
  神﨑風花:@fuka_sakana
  寺口夏花:@natsuka_sakana
  山崎 愛:@mana_sakana
【レギュラー番組】
<ラジオ>文化放送「sora tob sakanaの飛ばなきゃ損損」(毎週木曜26時30分~27時)

通常盤にはRemixが入ってる。どっちも買おうな。

descriptionは普段、YouTubeで折り畳まれている部分。今回のようにとても長い場合がある。

動画の統計情報を取得してみる

partstatisticsを指定するだけ。とても簡単。

import pprint

import requests

from config import API_KEY

URL = 'https://www.googleapis.com/youtube/v3/videos'

part = ['statistics']
ids = ['JTpFWzKGhFU']

payload = {
    'part': ','.join(part),
    'id': ','.join(ids),
    'key': API_KEY,
}

r = requests.get(URL, params=payload)
print(type(r))
res = r.json()
stat = res['items'][0]['statistics']
pprint.pprint(stat)

statisticsはフィールドが少ないのでpprintで整形してすべて表示してみた。

{'commentCount': '68',
 'dislikeCount': '4',
 'favoriteCount': '0',
 'likeCount': '625',
 'viewCount': '13296'}

投稿から1週間程度で1万再生以上。いいぞ。

複数の動画を扱う

最後にsora tob sakanaの3つの動画を対象に、複数動画の情報を取得してみる。

import requests

from config import API_KEY

URL = 'https://www.googleapis.com/youtube/v3/videos'

part = ['snippet', 'statistics']
ids = ['JTpFWzKGhFU', 'K_Qx7EE4ucY', 'MyikIWSAEE4']  # flash(Teaser), ささやかな祝祭, knock!knock!

payload = {
    'part': ','.join(part),
    'id': ','.join(ids),
    'key': API_KEY,
}

r = requests.get(URL, params=payload)

res = r.json()

for item, _ in zip(res['items'], ids):
    print('-' * 50)
    print(item['snippet']['title'])
    print(item['snippet']['publishedAt'][:10])
    print(item['statistics'])

ISO 8601(YYYY-MM-DDThh:mm:ss.sZ)形式のpublishedAtを10文字目までスライスしてYYYY-MM-DDにしている。

sora tob sakana/flash(Teaser)
2019-10-18
{'viewCount': '13300', 'likeCount': '625', 'dislikeCount': '4', 'favoriteCount': '0', 'commentCount': '68'}
--------------------------------------------------
sora tob sakana/ささやかな祝祭(Full)
2019-07-06
{'viewCount': '191270', 'likeCount': '3129', 'dislikeCount': '88', 'favoriteCount': '0', 'commentCount': '269'}
--------------------------------------------------
sora tob sakana/knock!knock! (Full)
2019-02-06
{'viewCount': '299740', 'likeCount': '3969', 'dislikeCount': '98', 'favoriteCount': '0', 'commentCount': '434'}

動画情報の格納順はIDを渡した順と一致していることがわかる。

Videosエンドポイントを使って動画の情報を取得する方法がざっくりわかったところで今回はここまで。

次回は特定のチャンネルから動画IDを取ってくる方法を書きたい思っている。

PyCon TW 2019に行ってきた話(だけど主に観光の話

【2019/11/16追記】

PyQ公式ブログPython学習チャンネルにて真面目な方のPyCon TW 2019参加レポートを書きました。

blog.pyq.jp


PyCon JP 2019の疲れも取れぬまま、台北に飛んでPyCon TW 2019に参加してきた。という話をPyCon SG 2019の会場で書いている。

LCCはアラサーの体には堪える。なんなら4時半発なので羽田空港で夜を明かしてダメージ倍増。

tw.pycon.org

真面目な話はいずれ公開されると思うので、ここでは9/18の台北観光メインで書く。

実質、訪台中のツイートまとめ。

人生2度目の台湾。

上陸直後の台湾メシ。素麺みたいな細さのやつ。

nikkie氏と合流するため、地下鉄を乗り継いで桃園空港から松山空港へ。台北は地下鉄が発達してるので移動が割と楽。

桃園からのMRTの終点、台北車站にギリギリ隣接といえなくも無い距離で隣接している松山新店線の北門駅に移動して、南京復興駅へ。

南京復興駅から文湖線で松山機場へ。

nikkie氏と合流したついでにPyCon JP 2019座長の吉田さんと会えたので3人行動に切り替えて、台北市内にUberで移動した。

魯肉飯食いたいという気持ちで探したよさげな店のある龍山寺へ。美味いけど見た目が映えない。

ローカル感あるいい感じに雑感のある店だった。

四方阿九魯肉飯 (万華) 最新のレストランの口コ(2019年) - トリップアドバイザー

その後は龍山寺へ。

龍山寺でひいたおみくじ。後述するメイドカフェメイドさんにどんな内容か聞いてみたらだいぶいい感じらしい。上上って書いてあるし、納得感ある。

ここからは吉田さんと別れてnikkie氏と行動。台北オタク文化に触れるため、秋葉原と原宿を足して2で割った雰囲気の西門町へ。

ご当地キャラの垂れ幕があった。かわいい。林默娘っていうらしい。なんて読むのかは知らない。

台北アニメイトへ。翻訳されたラノベとか漫画がメインだけど、台湾独自のオタクコンテンツもあった。「食用系少女」って字面が危険な感じだけど、台湾グルメの擬人化なのでご安心。

こっちは鉄道むすめの台湾バージョンみたいな雰囲気。

@doi-tさんに教えてもらったマンゴーかき氷の店へ。一人で余裕で食べれる量やろ、と高をくくっていたら地獄を見た。ひたすら冷たい。美味いけど冷たい。

隣のテーブルに来た日本人の学生3人組は分けて食べるとのことだった。賢いと思ったら、彼らも一度は地獄を見ていたらしい。

暖かいものを飲んで冷えた体を温めようとメイドカフェへ。最初から行く予定だったけども。

台湾でのメイドカフェ探しは 台湾女僕珈琲(メイドカフェ)まとめ|もえ氏(萌大小姐)|note が役に立つ。

ここのメイドさんはカタコトながら日本語が話せる娘だったのでとても助かった。というか可愛くてカタコト日本語はポイント高い。チェキも撮った。

この日の夜は日本からのPyCon TW参加勢と合流して、青葉という台湾料理の店へ。

この翌日からPyCon TW 2019が始まるわけだけど、そちらの話は近日公開予定。

Uberはいいぞ

というかタクシー怖い。

PyCon TWの会場、中央研究院は南港展覧場駅からバスないしタクシーで行く距離にある。

カンファレンス初日、駅の前に停まってたタクシーを使おうとして運転手の爺さんにGoogle Mapで目的地を見せるも上手く伝わらず。どうやら字が小さいと言っているようだったので、テンパりながらも拡大しようとし続けててたら数十メートル走ったところで爺さんがキレて降ろされてしまった。タクシーはコミュニケーションが必要になるのでUberが絶対おすすめ。

Uberなら英語話せる若い人もいるしね。最終日、クラフトビールの店から台北車站まで乗ったUberの運転手の兄ちゃんはとてもいい人だった。パイナップルケーキのおすすめの店とか、東京に行ったことあるけど日本感なかったので京都がよかったかも、とかいろいろ話した。

PyCon JP 2019とビープラウド入社3年目突入

ども

から始まる山崎愛構文は「アイドルで理解するTF-IDF」ではないのでやらない。

PyCon JP 2019

9/16(月)から9/17(火)にかけてPyCon JP 2019が開催された。

pycon.jp

スタッフ3年目の今年は2018年に引き続き、コンテンツチームに所属してポスターセッション(コミュニティ)の担当をやっていた。

コミュニティは去年の5つから5つも増えて日本各地+海外から11のコミュニティに出展していただくことができた。ポスターセッションのコアタイムにはどのコミュニティの前にも人だかりができていて、コミュニティメンバーとの交流やコミュニティ間の情報交換が行われていた。年に一度、日本国内外からPythonistaが集まるイベントだからこそできる貴重な機会を作ることができて本当に良かった。

コミュニティブースはPythonコミュニティが活動を紹介する企画で、南はPyCon Kyushu、北は札幌Pythonまで日本各地から6つのコミュニティが参加した。

PyCon JP 2018とBP入社一周年 - 蛇ノ目の記

あと、気づいたらオープニング・クロージングの案内を任されていた。2年前のように全体的な司会ではないものの、200~300人はいそうな会場で喋るのはとにかく緊張する。今度、風花ちゃんに会ったら緊張しないコツとか教えてもらおう。英語の発音をいろいろな人から褒められたのは嬉しかった(話すのが早いという課題もあるけど)。なお、英語喋れてる風なだけで聞き取れないので会話は成立しない模様。

査読付きLT・飛び入りLTの司会も担当した。次の発表者までの繋ぎをやるのがとにかく難しくて、繋ぐことを諦めてしまったのが反省点。2日目のLTでは接続が上手くいかないことが何度かあったりした。ああいったトラブルの対応も難しい。

LTといえば、2日目飛び込みLT、 @mizzsuggar0425による「PyCon JP 2018で勇気をもらってPythonエンジニアになった話」が最高にいい話だった。PyCon JPで聞いたLTに刺激を受けてPythonエンジニアになりました、だけでなく今はまだ自信が無い人もきっとやれる、というメッセージがとにかくエモい。来年、@mizzsugar0425の後に続く人がLTするなんてことがあったらきっと感動にあまりにむせび泣く。

speakerdeck.com

そして特に面白かったトーク@_sin_tanakaによる「Pythonでライブをしよう!FoxDotを使った新時代のPython活用法 」

ライブコーディング(コードを書く方ではなくコードを書くことでライブ演奏する)を行えるPythonライブラリFoxDotの概要とDEMO。音楽わからない自分でも曲作れそうと思えたし、simple-spotifySpotifyの楽曲データAPIを叩いてコードやスケール、BPMを取得してくれば好きな曲と同じコードとスケール、BPMを素材にして演奏したりできるなと考えた。

speakerdeck.com

FoxDotを使ってLightningTalkならぬLightningLiveもできそう。今度stapyでやってみようかな。

ところで、来年も引き続きスタッフをやるかどうか実は悩んでいる。飽き性なので数ヶ月に渡る準備の間にモチベーションが下がってしまうことが多々あって、他のスタッフに迷惑を掛けてしまっているのではないかと思う。本格的にPyCon JP 2020が動き出すまでに、モチベーションをある程度維持できるように工夫するか当日スタッフだけやるかを決めておこう。文化祭をやっているみたいで楽しいのでスタッフをやらない選択肢は今のところなさそう。

ビープラウド入社3年目突入

ビープラウドに入社したのがPyCon JP 2017の直後だったのでPyCon JPの季節になると自然とビープラウド歴も更新される。

最近はタスクをバラしてスケジュールを立てることの苦手さやコードレビュー依頼の雑さに直面していて、特に何かできるようになったという感じは実はあまりない。

とはいえ、(自分にとって)新しいことを経験して、多少は知識も増えている。例えば直近ではUbuntu14.04から18.04, Python2.7から3.7, Django1.11から2.2という3つの移行をやっていることもあってUbuntu(というよりsystemdやjournald)の知識を新たに得たり、Djangoソースコードを読むようになったりしている。 移行案件が終わったら、イチから開発する案件をやってみたいなとか思いつつ、休みが明けたら移行済みテスト環境の画面テストで出たバグとの戦いに精を出すことになる。

このあたりでPyCon JP 2019のざっくりまとめとビープラウド3年目突入の話は終わり。

さて、PyCon TW 2019に遊びに行くために羽田空港にいて、早朝便まで寝れる場所を確保しないといけない。

アイドルで理解するTF-IDF TF-IDF編

ども

そろそろPyCon JP 2019が始まりますね

始まると一気にブログを書かなくなるので今のうちに書きます

PyCon JPのお祭り感が好きなんですよね

そのあとはPyCon TWに遊びに行きます

人生二度目の台湾です

TL; DR

  • TF は文書頻度。

    • 文書中の単語の重要度を表す指標
  • IDFは逆文書頻度

    • 各単語がどれだけの文書に出現するかを表す指標
  • TF-IDFはTFとIDFの積。それぞれの文書の中で重要な単語ほど大きい値になる

TFの計算式

文書d_iにおける単語t_jtfは以下のように表される。

tf_i=\frac{n_i,j}{\sum_k n_{k,j}}

n_{i,j}は文書d_iにおける単語t_jの出現回数。

{\sum_k n_{k,j}}は文書d_iにおけるすべての単語の出現回数の和。

tf-idf - Wikipedia

式で表しても(自分が)今ひとつわからないので実際の文書で考えてみる。

神﨑風花はかわいい

寺口夏花はかわいい

山崎愛はかわいい

みんなかわいい

という文書があって、単語は

  • 神﨑風花

  • 寺口夏花

  • 山崎愛

  • みんな

  • かわいい

と分けるとする。

文書中の単語はベクトルで表す。ここで単語は6つなのでベクトルの成分は6つになる。

神﨑風花はかわいい

の各単語のTFを求めてみる。

単語のベクトルはこのようになる。

神﨑風花 寺口夏花 山崎愛 みんな かわいい
1 0 0 0 1 1

「神﨑風花」という単語のtfは以下のように求められる。

{\sum_k n_{k,j}}は文書d_iにおけるすべての単語の出現回数の和なので、3になる。

tf_i=\frac{n_i,j}{\sum_k n_{k,j}}=\frac{1}{3}=0.3

同様に他の単語についてもtfを求めると、

神﨑風花はかわいい

の各単語のTFは以下のようになる。

神﨑風花 寺口夏花 山崎愛 みんな かわいい
0.3 0 0 0 0.3 0.3

IDF

文書d_iにおける単語t_jidf_iは以下のように表される。

|D|は文書の総数。

|\lbrace d:d \ni t_i \rbrace |は単語t_iを含む文書数。

tdf_i=\log\frac{|D|}{|\lbrace d:d \ni t_i \rbrace |}

tf-idf - Wikipedia

実際には単語tが出現しない文書があるとゼロ除算が発生するので、分子と分母にそれぞれ1を加える。

やっぱり式で表しても(自分が)今ひとつわからないので実際の文書で考えてみる。

神﨑風花はかわいい

寺口夏花はかわいい

山崎愛はかわいい

みんなかわいい

があって、単語のベクトルはそれぞれ以下のようになる。

神﨑風花 寺口夏花 山崎愛 みんな かわいい
1 0 0 0 1 1
0 1 0 0 1 1
0 0 1 0 1 1
0 0 0 1 0 1

単語それぞれのIDFを求めてみる。

  • 神﨑風花

idf_{神﨑風花}=\log_2\frac{4}{1}=\log_2 4=2.0

  • 寺口夏花

idf_{寺口夏花}=\log_2\frac{4}{1}=\log_2 4=2.0

  • 山崎愛

idf_{山崎愛}=\log_2\frac{4}{1}=\log_2 4=2.0

  • みんな

idf_{みんな}=\log_2\frac{4}{1}=\log_2 4=2.0

idf_{は}=\log_2\frac{4}{3}=0.4

※ 少数第二位で四捨五入してる

  • かわいい

idf_{かわいい}=\log_2\frac{4}{4}=0

神﨑風花 寺口夏花 山崎愛 みんな かわいい
2.0 2.0 2.0 2.0 0.4 0

TF-IDF

TF-IDFはtf_i,jidf_iの積。

「神﨑風花はかわいい」の場合

tf_{i,j}

神﨑風花 寺口夏花 山崎愛 みんな かわいい
0.3 0 0 0 0.3 0.3

idf_i

神﨑風花 寺口夏花 山崎愛 みんな かわいい
2.0 2.0 2.0 2.0 0.4 0

ifidf_{i,j}

神﨑風花 寺口夏花 山崎愛 みんな かわいい
0.6 0 0 0 0.12 0

となる。最も値が大きいのは「神﨑風花」なので、文書「神﨑風花はかわいい」の中で重要度が最も高いのは「神﨑風花」という単語ということになる。

Pythonでやってみる

scikit-learnを使えば簡単にできると「Pythonによるあたらしいデータ分析の教科書」という本に書いてあるけど、今回はNumPyを使って自力で実装してみる。

なお、TF-IDFのL2正規化はNumPyの力を借りた。

import typing

import numpy as np

def get_all_docs(in_csv: str) -> typing.List:
    all_docs = []
    with open(in_csv, encoding='utf-8') as fin:
        for line in fin:
            words = [word.strip() for word in line.split(',')[3:]]
            all_docs.append(words)
    return all_docs


def create_words_list(all_words: typing.List) -> typing.List:
    flatten_words = []
    for words in all_words:
        for word in words:
            flatten_words.append(word)
    words_list = []
    for word in flatten_words:
        if word not in words_list:
            words_list.append(word)
    return words_list


def get_word2int(words_list: typing.List) -> typing.Dict[str, int]:
    word2int = {}
    for idx, word in enumerate(words_list):
        word2int[word] = idx
    return word2int


def calc_tf(all_docs: typing.List, word2int: typing.Dict[str, int]) -> np.ndarray:
    tf_array = np.zeros((len(all_docs), len(word2int)))
    for doc_idx, doc in enumerate(all_docs):
        temp_array = np.zeros((1, len(word2int)))
        for word_idx, word in enumerate(word2int.keys()):
            if word in doc:
                temp_array[0, word_idx] += 1
        tf_array[doc_idx:] = temp_array / np.sum(temp_array)
    return tf_array


def calc_idf(all_docs: typing.List, word2int: typing.Dict[str, int]) -> np.ndarray:
    temp_array = np.zeros((1, len(word2int)))
    all_docs = all_docs
    for idx, word in enumerate(word2int.keys()):
        word_count = 0
        for doc in all_docs:
            if word in doc:
                word_count += 1
        temp_array[0, idx] = word_count
    idf_array = np.log2((len(all_docs) + 1 / temp_array + 1))
    return idf_array


def l2_normalize(tf_idf: np.ndarray) -> np.ndarray:
    l2_norm = np.linalg.norm(tf_idf, ord=2)
    return tf_idf / l2_norm

def tf_idf(in_csv: str):
    all_docs = get_all_docs(in_csv)   # in_csvは形態素解析して取り出した名詞のCSVファイル
    words_list = create_words_list(all_docs)
    word2int = get_word2int(words_list)
    tf_array = calc_tf(all_docs, word2int)
    idf_array = calc_idf(all_docs, word2int)
    tf_idf = tf_array * idf_array
    normalized_tf_idf = l2_normalize(tf_idf)

tf_idf_with_idol/tf_idf.py at master · NaoY-2501/tf_idf_with_idol · GitHubから抜粋。

次回は山崎愛ちゃんのブログに対して計算したTF-IDFと実際の記事を見比べて、特徴語が取り出せているかを検証したりする予定。

アイドルで理解するTF-IDF 形態素解析編

ども

昨日はstapy #49でしたね

LT聴いたくれた方ありがとうございます。

アイドルで理解するTF-IDF 形態素解析編です

TF-IDFまであと一歩です

別に記事を増やしたくて細かく分けてるわけじゃないです

なにごとも一歩ずつ進めていくのがいいと思うんです

知らんけど

TL;DR

  • NEologdを使うときはTaggerに辞書のパスを指定する

  • MeCab.Nodeオブジェクトはiterableじゃない

  • 形態素の詳細な情報はMeCab.Tagger.parseToNode.featureで取得できる

    • 次の形態素MeCab.Tagger.parseToNode.nextで取得する

形態素解析する

mecab-python3を使う。

pypi.org

今回のコードはこれだけ。

import csv

import MeCab


def analysis(in_csv: str, out_csv: str):
    tagger = MeCab.Tagger('-d /usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd')

    with open(out_csv, 'w', encoding='utf-8') as fout:
        with open(in_csv, encoding='utf-8') as fin:
            reader = csv.DictReader(fin)
            for row in reader:
                lemmas = []
                node = tagger.parseToNode(row['body'])
                while node:
                    result = node.feature.split(",")
                    pos = result[0]
                    lemma = result[-3]
                    if pos == '名詞':
                        lemmas.append(lemma)
                    node = node.next
                row = f"{row['date']},{row['url']},{row['title']},{','.join(lemmas)}\n"
                fout.write(row)

tf_idf_with_idol/morphological_analysis.py at master · NaoY-2501/tf_idf_with_idol · GitHub

MeCab: Yet Another Part-of-Speech and Morphological Analyzerスクリプト言語のバインディングにおおよその使い方が書いてある。

MeCab::Tagger というクラスのインスタンスを生成し, parse (もしくは parseToString) というメソッドを呼ぶことで, 解析結果が文字列として取得できます.

これはPerlの例で書いているけど、Pythonであれば「MeCab.Taggerというクラスのインスタンスを作成し〜」となる。 また、Taggerクラスの引数にオプションを指定できる。使用する辞書を指定するときは-dオプションの後に辞書のパスを書く。今回は前回、DockerイメージにインストールしたNEologdの辞書のパスを指定する。

表層系や形態素など詳しい情報を取りたい場合はMeCab.Tagger.parseToNode()メソッドを使う。 parseToNode()メソッドは文章を渡されると、'MeCab.Node'オブジェクトを返す。

MeCab::Node は, 双方向リストとして表現されており, next, prev というメン バ変数があります.

スクリプト言語のバインディング

Nodeオブジェクトはiterableではないので、for文で回そうとすると怒られる。

Traceback (most recent call last):
File "tf_idf_with_idol.py", line 13, in <module>
for n in node:
TypeError: 'MeCab.Node' object is not iterable

スクリプト言語のバインディング にあるようにwhile文を使って繰り返し処理しよう。

node<Swig Object of type 'MeCab::Node *' at 0x7fe6a4fae960>というようなオブジェクトになっている。 詳細な情報を取得するときはfeature属性を使う。 こんな感じの文字列が取得できる。

 名詞,一般,*,*,*,*,迷路,メイロ,メイロ

カンマ区切りの文字列なので、 split()メソッドを使ってリストにすると使いやすくなる。品詞は見てわかるように先頭にある。

なんとなくMeCabの使い方がわかったところで「広告の街」の歌詞の一部を形態素解析して終わりにする。

www.youtube.com

 BOS/EOS,*,*,*,*,*,*,*,*
 名詞,一般,*,*,*,*,迷路,メイロ,メイロ
 助詞,連体化,*,*,*,*,の,ノ,ノ
 名詞,非自立,助動詞語幹,*,*,*,様,ヨウ,ヨー
 助動詞,*,*,*,特殊・ダ,体言接続,だ,ナ,ナ
 名詞,一般,*,*,*,*,恋,コイ,コイ
 助詞,格助詞,一般,*,*,*,に,ニ,ニ
 動詞,自立,*,*,一段,連用形,落ちる,オチ,オチ
 助詞,接続助詞,*,*,*,*,て,テ,テ
 動詞,非自立,*,*,五段・カ行促音便,基本形,いく,イク,イク
 名詞,一般,*,*,*,*,心,ココロ,ココロ
 助詞,並立助詞,*,*,*,*,と,ト,ト
 名詞,一般,*,*,*,*,裏腹,ウラハラ,ウラハラ
 名詞,一般,*,*,*,*,ゲーム,ゲーム,ゲーム
 助詞,連体化,*,*,*,*,の,ノ,ノ
 名詞,非自立,助動詞語幹,*,*,*,様,ヨウ,ヨー
 助詞,副詞化,*,*,*,*,に,ニ,ニ
 名詞,サ変接続,*,*,*,*,レベルアップ,レベルアップ,レベルアップ
 形容詞,自立,*,*,形容詞・アウオ段,連用テ接続,うまい,ウマク,ウマク
 助詞,係助詞,*,*,*,*,は,ハ,ワ
 動詞,自立,*,*,五段・カ行促音便,未然形,いく,イカ,イカ
 助動詞,*,*,*,特殊・ナイ,基本形,ない,ナイ,ナイ
 名詞,サ変接続,*,*,*,*,検索,ケンサク,ケンサク
 名詞,副詞可能,*,*,*,*,結果,ケッカ,ケッカ
 動詞,自立,*,*,一段,連用形,並べる,ナラベ,ナラベ
 助詞,接続助詞,*,*,*,*,て,テ,テ
 助詞,係助詞,*,*,*,*,も,モ,モ
 動詞,自立,*,*,一段,未然形,探せる,サガセ,サガセ
 助動詞,*,*,*,特殊・ナイ,基本形,ない,ナイ,ナイ
 名詞,一般,*,*,*,*,気持ち,キモチ,キモチ
 助詞,係助詞,*,*,*,*,は,ハ,ワ
 名詞,一般,*,*,*,*,迷路,メイロ,メイロ
 助詞,連体化,*,*,*,*,の,ノ,ノ
 名詞,一般,*,*,*,*,奥,オク,オク
 助詞,格助詞,一般,*,*,*,に,ニ,ニ
 動詞,自立,*,*,一段,連用形,消える,キエ,キエ
 助詞,接続助詞,*,*,*,*,て,テ,テ
 動詞,非自立,*,*,五段・カ行促音便,基本形,いく,イク,イク
 名詞,代名詞,一般,*,*,*,あなた,アナタ,アナタ
 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
 動詞,自立,*,*,五段・サ行,基本形,探す,サガス,サガス
 BOS/EOS,*,*,*,*,*,*,*,*

照井さんたちによる演奏動画もめちゃめちゃかっこいいので観てください。

www.youtube.com

次回はいよいよTF-IDF編の予定です。PyCon JPが始まる前に書かないと絶対フェードアウトするのでがんばるぞい。

アイドルで理解するTF-IDF 環境構築編

ども

台風やばかったですね

雨と風が強すぎてぜんぜん寝れませんでした

うちの近くに去年の台風で傾いた建物があるんですがそれはなんか無事でした

どうでもいいですね

はい

アイドルで理解するTF-IDF第2回です

まだTF-IDFまでたどり着きません

TL;DR

  • Dockerを使って形態素解析&TF-IDF計算の環境作った

  • Ubuntu 18.04 + Python3.7 + MeCab + NEologd

    • NEologdの辞書のPATHは必ずチェックしよう
  • docker-composeで形態素解析とTF-IDFの計算を実行するようにした

Dockerイメージを作る

Python3についてはPython3.7を使いたかったのでソースからビルドした。

MeCabとNEologdのインストールについてはMeCab用のDockerfile - sanshonokiの日記 を参考にDockerfileを書いた。

イメージの役割は形態素解析とTF-IDFの計算のみで、IPAフォントのインストールは不要と判断して削除した。Maplotlibで日本語入ったグラフを出すとかだったら入れないとダメ。

NEologdをMeCabのデフォルト辞書に設定する箇所について、参考にしたブログでは以下のようになっていた。

RUN sed -i 's/dicdir = \/var\/lib\/mecab\/dic\/debian/dicdir = \/usr\/lib\/mecab\/dic\/mecab-ipadic-neologd/' /etc/mecabrc

/etc/mecabrcdicdirをNEologdの辞書のパスに置き換えているわけだけど、この置き換え先のパスが今回の環境では異なっていて、/usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd となっていた。DockerイメージにNEologdをインストールしたら、一度そこでコンテナを実行して、NEologdの辞書のパスを確認するのがよさそう。

$ docker run -it イメージ名 /bin/bash
$ echo `mecab-config --dicdir`"/mecab-ipadic-neologd"

辞書のパス確認のコマンドはNEologdのREADMEに書かれている。

github.com

今になって思ったけど、sedを使って書き換えるではなく手元にdicdirを書き換えたmecabrcを用意してDockerイメージをビルドするときに置き換えるのもいいかもしれない。

$ docker run -it イメージ名 /bin/bash
# NEologdの辞書のパスを確認
$ echo `mecab-config --dicdir`"/mecab-ipadic-neologd"
# mecabrcを表示
$ cat /etc/mecabrc

ここで表示したmecabrcをコピーして手元で書き換える、とか。

とりあえず現状最新のDockerfile。

# ref. http://sanshonoki.hatenablog.com/entry/2018/10/09/231345
FROM ubuntu:18.04
RUN apt update \
    && apt install -y \
    build-essential \
    zlib1g-dev \
    libncurses5-dev \
    libgdbm-dev \
    libnss3-dev \
    libssl-dev \
    libreadline-dev \
    libffi-dev \
    wget \
    git \
    mecab \
    curl \
    libmecab-dev \
    mecab-ipadic-utf8\
    language-pack-ja \
    xz-utils \
    file \
    openssl \
    gawk \
    sudo \
    unzip \
    && apt clean \
    && update-locale LANG=ja_JP.UTF-8

# Set locale
ENV LANG ja_JP.UTF-8
ENV LANGUAGE ja_JP.UTF-8
ENV LC_ALL ja_JP.UTF-8

# Install Python3.7
# ref. https://linuxize.com/post/how-to-install-python-3-7-on-ubuntu-18-04/
WORKDIR /usr/local/src
RUN wget https://www.python.org/ftp/python/3.7.3/Python-3.7.3.tar.xz \
  && tar -xf Python-3.7.3.tar.xz \
  && cd Python-3.7.3 \
  && ./configure --enable-optimization \
  && make \
  && make altinstall

# Install mecab-ipadic-NEologd
WORKDIR /opt
RUN git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git
WORKDIR /opt/mecab-ipadic-neologd
RUN ./bin/install-mecab-ipadic-neologd -n -y
RUN cd /opt
RUN rm -rf mecab-ipadic-neologd

# Set mecab-ipadic-NEologd as default
RUN sed -i 's/dicdir = \/var\/lib\/mecab\/dic\/debian/dicdir = \/usr\/lib\/x86_64-linux-gnu\/mecab\/dic\/mecab-ipadic-neologd/' /etc/mecabrc

# Make directory and Copy code
WORKDIR /home
RUN mkdir code
RUN mkdir output
WORKDIR /home/code
COPY code .

CMD python3.7 -V

mecab-pythonという名前をつけてDockerイメージをビルド。

docker build -t mecab-python .

docker-compose.ymlを作る

本当はDockerイメージをビルドする段階でパッケージをインストールしておきたかった。$ python3.7 -m pip install -r requirements_docker.txtでPython3.7のpipであることを明示的にしてインストールしたにもかかわらず、docker-composeでコンテナを開始するとそのパッケージが見つからなくなっていた(今回、原因まで調べられていない)。そのためコンテナ開始のタイミングでインストールしている。

version: '2'
services:
    mecab-python:
        build: .
        volumes:
            - ./code:/home/code
            - ./output:/home/output
        command: >
          /bin/bash -c
          "python3.7 -m pip install -U pip
          && python3.7 -m pip install -r requirements_docker.txt
          && python3.7 tf_idf_with_idol.py"

これで環境構築ができたので、次回は形態素解析とTF-IDFです。#stapyでのLTが終わってから書きます。

めっちゃどうでもいいし俺がやってもアレなだけなんだけど、このシリーズの冒頭は山崎愛ちゃん構文に寄せてます。