Terrarium

いわゆる掃き溜めの ありふれた有象無象

実験でQiita記事のタグ情報を可視化した話

この記事はeeic (東京大学工学部電気電子・電子情報工学科) Advent Calendar 2017 その2の12日目の記事として書かれたものです。

qiita.com

枠が空いていたので元々アドベントカレンダーの記事にしようとしていたものをこちらに書くことにしました.

はじめに

EEIC2016でB4をやっているtellusiumです. 私は今年の秋に大学の実験の方でTAをやっていました. 情報可視化実験というものです.

2017infovislab:start [Koji Yatani's Course Webpage]

去年私自身もその実験を受講しており,せっかく作ったのと後輩の参考になればと思いこの記事を書くことにしました.

どんな作品が製作されたかは,まとめ動画をみていただければ分かるかもしれません.ちなみに2016年度の方は私が製作しました(BGMが激しいのはこの動画にインセンティブを受けたからという裏話

www.youtube.com

www.youtube.com

製作物について

なんとなく私はQiitaの記事を使って何か可視化できたら面白そうということで,@natsuki-1994を誘ってチームを組みました. (この実験は基本的には2人のチームで行われます)

紆余曲折を経て完成したのが,QIitaのタグ同士の関係を可視化したものです.

これは私のGithub(下のリンク)で現在公開しています.

https://ta21cos.github.io/qiqiqi/src/zero.html

使っている記事情報が去年までのため,新しい情報は含まれていませんのでご注意ください. (後述のWikipedia情報と人気Contributor情報は動的に取ってきているので実は最新です)

説明

プログラマのための技術共有サービス「Qiita」の各記事には投稿者が タグを付与することができますが,「盛り上がっている技術分野の可視化」「技術同士の関連性の可視化」 ができるのではないかと思い、このシステムを作りました.

可視化にはForce Layoutを使っています.タグ同士の関係を表すにはこれが最もふさわしいと考えました.

ノード1つ1つがタグを表していいて,大きさはタグのついている記事数を表しています. タグは1万種類ほどあるため,ある程度の記事数があるタグのみを表示しています. また,各ノードは分野(これは自分たちの方で勝手に分類した)ごとに色分けしています.

リンクは特に結びつきの強い分野のノード同士を繋ぐようにしました(そうでないととても見辛かったので…) しかしこのままでは離れ小島なタグが大量発生したので,最も関連性の高いノード一つとは必ず繋がるようにしています. また年を変えることができ,ノードの大きさは左下に表示されている年までの記事の積算数に対応しています.

タグ上にマウスオーバーすると,WikipediaAPIを用いてタグ名で検索した時に出てくる情報の最初の一文を表示しています. なので物によっては全く関係のない文章が表示されることがあります… (AWSとか見ると面白いかも)

各ノードをクリックすると,各タグの人気ContributorTOP3とそれぞれのContributorの人気記事一つが表示されるモーダルウィンドウが表示されます. またContributorや記事はQiitaへのリンクになっており,ページに飛ぶこともできます.

使っている言語など

この実験では可視化ライブラリとしてD3.jsを使います. なのでこのシステムもページ自体はD3.jsを使って作成しました. データ処理などは後述しますがPythonを用いています.

データ収集・データ処理

この時は(今もですが)あまりDBに詳しくなかったため,記事の重複を避けるため面倒な手順を踏んでいます. QiitaのAPIを用いて,

  • tagの一覧を取って来る(get_tags.py
  • 各tagを引数にして記事を10000件ずつ取得(get_article_by_tags.py
  • 各記事について,そのユーザーを取得(get_users_by_article.py
  • 各ユーザーについて,記事を全て取得(get_article_from_users.py

した結果の最後のデータを記事データとして用いています. もっといい方法があるはず(primary keyを使うとか)

この後でタグ同士の共起関係を計算したものをjsonとしてあらかじめ用意し,これを用いて可視化を行なっています. 共起関係の計算は年ごとに行い,年ごとにjsonファイルを作成しています. (calc_cooccurance.pyあたりに書いてあると思います)

想定される利用方法

この実験では可視化して面白いだけでなく,ターゲットはどんなでどんな風に使われるのか,また可視化することでどんな知見が得られるかまでしっかりと考えることが求められています.

このシステムは,ある技術分野に関して興味を持っており,詳しくその分野を知ってみたいと思ったビギナーをターゲットとして製作しました. 特定の分野の人気Contributorを表示することで,その人の記事を辿ることで効率よく良質な記事にたどり着け,最短でその分野をキャッチアップできます. なぜなら,特定のタグの人気記事よりも,人気なContributorが投稿した記事の方が良質な記事が多いと考えたからです. それゆえにモーダルウィンドウに表示されるのも人気記事ではなく人気Contributorの記事となっています.

2012年,Qiitaの登場した年を見てみると,記事数も少なく,注目されるタグ数も少ないことがわかります. (と書きましたがAPIで取りきれていない可能性も否定できないのであくまでも可能性ということで)

2013年をみるとタグの種類が増加し,Qiitaがプログラマ界隈で徐々に使われてきている様子がわかります. またスマートフォン関連で見てみるとiOSタグしかなく,Androidがまだ注目されていないこともわかります.

2014年にはタグ数・記事数共に大幅に増えていることがわかります. Androidも急激に増加しiOSと変わらない大きさに,またPythonも大きく増加しており機械学習などで一気に注目を浴びたことが伺えます. またAppleが「Swift」を発表した年でもあり,Swiftタグが登場していることもわかります.

2016年はあの「AlphaGo」が世間を騒がせた年ですが,ここにきて「AlphaGo」の基礎技術である「強化学習」タグやGoogle機械学習フレームワークである「Tensorflow」タグが出現しています. またIoTタグやRaspberryPiタグも登場しており,IoTというワードが注目され始めたこともわかります.

まとめ

これまで書いたことは実験で提出したレポートを参考に書かれています.

ここまで読んだ方はお分かりかもしれませんが,この実験は多くのことを学生に求めていることがわかります. ここでは最終成果物について書きましたが,これ以外にも先生から情報可視化についての講義や実際に例となるシステムを作るHands-on Tutorialもあり,内容は盛りだくさんです. ちなみにこの実験は10日実験(週3日)なので,3週間から1ヶ月程度でここまでのことをやります.

確かにとても大変ですが,非常に密度の高い実験で,私はとても楽しく感じました(2日くらい徹夜した気がします)

EEICには他にもCPUを自分で作るとか,OSSを自分で改良してみるとか,色々な楽しい(つらい)実験がたくさんあります. この記事を見て,一人でも多くの方がEEICに興味を持っていただけると嬉しいです. (最後なんてまとめればわからないのでとりあえずEEICを宣伝していくスタイル)

シェルスクリプトを使ってGitHubの特定のOrganizationの持つリポジトリを全てCloneしたい

表題の通りです.あるOrganizationのリポジトリを全部バックアップする必要があったため,それをシェルスクリプトでやってみようという話です.

TL;DR

curl -u [USERNAME] -s https://api.github.com/orgs/[ORGANIZATION]/repos | grep "ssh_url" | sed -e 's/\".*\".*\"\(.*\)\".*/\1/' | xargs -L1 git clone

調査

シェルスクリプトはあまり触れたことがなかったので,やり方を調べる必要がありました.以下は自分向けのメモとして冗長に上記コマンドにたどり着くまでの経緯を書きます.

1. Organizationの全リポジトリの取得方法

やりたいことを英単語でつらつら書いて検索すると似たような事例が出てきました.

Clone all repos from a GitHub organization · GitHub

どうやらcurlを使って特定のAPIにアクセスすれば取得できそうです.このページではRubyを使って書いていますが今回はシェルスクリプトのみでやりたいので,前半のみ参考にします.

2. リポジトリのURLの取得

curlAPIにアクセスするとOrganizationの持つリポジトリの情報が色々取得できますが,ここからCloneのためのURLを抽出する必要があります.ざっと中身を見ていくとssh_urlを使えばCloneできそうです.というわけでgrep "ssh_url"を使って特定の行のみ取り出せました.

次はこの行からURLの部分のみを抽出したいのですが,そのためにはsedを使えば良さげです.マッチさせたい部分を括弧でくくるとできるらしいので,2つ目のダブルクォーテーションに囲まれた文字列を取り出します.すなわち"[文字列]"[文字列]"([取り出したい文字列])"[文字列]ということです.すごく適当ですが…マッチした部分はその順番を用いて\1で取り出せるので,sed -e s/[正規表現]/\1/と書くことによってURLのみを取り出すことができました.

(ちなみに括弧もエスケープする必要があること,\1の後にもバックスラッシュが必要であるというところで一瞬つまずきました…)

stackoverflow.com

stackoverflow.com

3. clone

これまでで取得したURLについて,それぞれgit cloneを走らせます.そのためにはxargsを使えそうですので,使い方を調べるとxargs git cloneでできると思ったのですが,too many argumentsと怒られました.入力に使う行数を指定する必要があったようです(当たり前だ…)xargs -L1 git cloneで実行できました.

まとめ

記事執筆も含めこれまでおおよそ40分くらい.書き終わった後でほぼ同じStackOverflowのページを見つけました…

stackoverflow.com

PythonでSwift-likeなstructを使いたいので検討(してみたがダメだった)

Pythonはいろいろなことを簡単に書けるのでよく使っています. ところが『実践Swift』を読んでみたところ,その型のある世界に憧れるようになってしまいました… (大学で型についていろいろ勉強したのもある)

www.amazon.co.jp

今回はSwift-like(と言い切ってしまっていいのか不安が残りますが)な構造体を使いたい場合にどんな実装をすれば良いのかについての検討です.

Swiftでは変数(や関数)をまとめた型としてenumstructclassprotocolなどがあります. 『実践Swift』では,structは変数のまとまりを値型として扱いたい場合に用いると書かれています.

一番簡単な例が座標情報などですね. Pythonでクラスを用いて実装すると次のようになります.

class Point():
    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(10, 20)
print(p.x) # 10
print(p.y) # 20

一見これで良さそうに見えるのですが,インスタンス同士の代入を行なった場合クラスでは参照がコピーされます. すなわち片方の座標の値を変更するともう片方も変更されてしまいます. これは座標というただの値を表現する手法として適切ではありません.

以下がその例です.

# (上の続き)
q = p

q.x = 30

print(q.x) # 30
print(p.x) # 30

Swiftのstructを用いると,この座標を参照ではなく値そのものがコピーされるため,以上のような問題は発生しません. (そもそもC言語でも同じです) ちなみに上のコードでq = pの部分をq = copy.deepcopy(p)とするとうまくいきます(import copyが必要)が,できればq = pでコピーできてほしいものです.

すなわちPythonで値型で構造的に記述したいというのが今回の目的です.

ということをTwitterに呟いたら,enum.Enumを使うという啓示をいただきました.

早速調べると…おお…これは使えそうだ(早とちり)ということで書いてみました. 実装には次のサイトを参考にさせていただきました.

py3.hateblo.jp

import enum

class EnumTest(int, enum.Enum):
    # 継承元最初のintはvalueメソッドにおける加算にて型を決める必要があるため書く
    _x: int = 10
    _y: int = 15

    # getter を試した
    @property
    def x(self):
        return self._x
        # return self._x.value()
        # とすればうまくいくが全てのpropertyでこれをやるのは非効率
        # いい方法がないものか…?

    @classmethod
    def value(self) -> int:
        return self._x + self._y

# getterを利用(うまくいかない)
print(EnumTest.x)
# 要素を参照(名前が出てくる)
print(EnumTest._x)
# 要素の値を知る方法(ちょっとめんどい)
print(EnumTest._x.value())
# メソッドを利用(これはうまくいく)
print(EnumTest.value())

# 上から
# <property object at 0x1050a6b38>
# EnumTest._x
# 25
# 25

やりたかったようにEnumTest.xで簡単に値が取得できればよかったのですがちょっと追加の実装がいるのであまり綺麗じゃないですね.

上の実装では致命的な欠陥がいくつかあります.

  • 値の変更ができない
  • Constructorがない(座標データごとにclassを宣言する必要がある)

これではSwift-likeなstructとは離れ過ぎていますね. さらなる検討を重ねるか諦めるか…

ちなみにnamedtupleを使うとpoint.xという形で値が取得できるようですが,こちらも値を変更できないという問題があるそうです.

うーん…そもそも値型の構造体を使うよりもっとPython-likeな手法があるのかもしれないなぁ…

というわけで検討してみたがダメだったという記録です.

(追記) namedtupleが値の変更を行えないと書きましたが関数型っぽく書くなら値の変更は必要ないですね… さらにnamedtupleにメソッドを追加した例もあるのでこちらを使った方が便利そうです. まあいずれにせよスッキリとは書けないみたいだ…

Python で namedtuple を使ってバリューオブジェクトを作る | CUBE SUGAR STORAGE

超基本的なコンパイラを作ってみる(1. 字句解析編)

コンパイラ

大学で言語処理系論というコンパイラを扱う講義を受講したので,その実践を兼ねて自分でコンパイラを作ろうと決意しました. いきなり"なんとか言語"のコンパイラを作るのは難しいので,本当に単純なものから始めたいと思います.

作る題材は何かと言うと,『よくある例 (The all-time favorite) 、卓上計算機です。』(引用元:http://ocaml.jp/archive/ocaml-manual-3.06-ja/manual026.html

新規性のカケラもない企画ですが,自分で作るところに意味があると思っています. もちろんコピペではなく「参考にして」オリジナルの機能も導入しながら作っていきます.

コンパイラの構成

コンパイラを作るためにはまず構成を知らなくてはいけないので,調べました. 参考文献はこちらです. いわゆるタイガーブックというやつですね.

https://www.amazon.co.jp/%E6%9C%80%E6%96%B0%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9%E6%A7%8B%E6%88%90%E6%8A%80%E6%B3%95-Andrew-W-Appel/dp/4798114685

さて,コンパイラのフェーズには(上の本よりも)大雑把に分けて次のものがあります.

  1. 字句解析
    • ソースファイルを個々のトークンへと分解する
  2. 構文解析
  3. 意味解析
    • 変数や式の型を検査して各句の意味を決定する
  4. 中間コード生成
  5. 最適化
    • 高速化を図る
  6. 機械語コード生成
    • 最適化した中間コードを元に機械語コードを生成する

字句解析

まずは字句解析器から作ってみたいと思います. 字句解析の入出力は次のイメージです.

  • 入力:x = 3.1 + y * 20
  • 出力:ID("x") EQ Float(3.1) PLUS ID("y") TIMES INT(20)

入力された文字列の各単語について,対応するトークンを割り当てます.

字句解析器の仕組み

字句解析器をどのように作るかというと, “正規表現” を使います. 正規表現についての説明は割愛します.

正規表現のプログラムでの実装は,正規表現オートマトンに変換することで行います.

しかしここでは簡単のため"字句解析器生成ツール"というものを使いたいと思います.

Lex

Lexは字句解析器生成ツールです. 正規表現トークンの情報を書いたファイルを入力すると,字句解析器を生成してくれます.

今回はLexのOcaml版,Ocamllexを使います.(Ocamlをインストールすれば一緒についてきた気がします…)

Ocamllex

Ocamllexの使い方については,下のリンクに書いてあります(がかなり分かりづらかったです) Lexer and parser generators (ocamllex, ocamlyacc)

以下,上のサイトのまとめになっています.

Ocamllexの設定ファイル(拡張子は.mll)のフォーマットは次のようになっています.

%{
  header:あらかじめ設定しておきたい事項や読み込んでおきたいモジュールを書いておく
%}
  declarations:変数を宣言したりする.正規表現を変数にしておくときとか
%%
  rules:正規表現を用いてトークンの定義を記述する
%%
  trailer: 最後に実行したい処理や後に使いたいOCamlのコードを書いておける

以上を書いたファイルをlexer.mllとして保存します. ここで,今回記述する文法の仕様を決めたいと思います.拡張する可能性もあるのでかなりざっくりですが…

  • 四則演算はそのまま記述できるように
  • 後に変数が使えるように,1文字目が英字,2文字目以降が英数字もしくはアンダースコアなものをIDとして認識

例えば今回は次のように記述しました. なおこのファイルは上のサイトだけでなく以下の講義サイトも参考にしています(講義資料は受講者しか見れませんが…)

http://www-kb.is.s.u-tokyo.ac.jp/~koba/class/compiler/

{
(* トークンの型を宣言 *)
type token = PLUS | MINUS | TIMES | DIV | LPAREN | RPAREN | EOL | EOF | ID of string | INT of int
}

(* よく使う正規表現に名前をつける *)
let space = [' ''\t''\r']
let digit = ['0'-'9']
let lower = ['a'-'z']
let upper = ['A'-'Z']

(* トークンのルール *)
rule tokenize = parse
| space+ { tokenize lexbuf } (* そのままバッファを返す *)
| (lower|upper)(lower|digit|upper|"_")+ { ID(Lexing.lexeme lexbuf) } (* 上で述べたIDの定義に従って正規表現を書いた *)
| digit+ { INT(int_of_string(Lexing.lexeme lexbuf)) } (* 数字 今はint型のみ *)
| "+" { PLUS }
| "-" { MINUS }
| "*" { TIMES }
| "/" { DIV }
| "(" { LPAREN }
| ")" { RPAREN }
| "\n" { EOL }
| eof { EOF }
| _
    { Format.eprintf "unknown token %s" (* エラー *)
    (Lexing.lexeme lexbuf);
    failwith "lex error" }

{
(* trailer *)
(* 字句解析を実行するコード *)
let rec readloop lexbuf =
    let t = tokenize lexbuf in
        if t=EOF then []
            else t::(readloop lexbuf)

(* main ファイル名 で実行 *)
let main filename =
    let lexbuf = Lexing.from_channel(open_in filename) in
        readloop lexbuf
}

以上を準備したら,次の手順で字句解析を実行できます.

  1. 入力ファイルを用意
    • 例えばx1 - (2 + 3) / 5 *6と書いたファイルをin.mlとして用意
  2. ocamllex lexer.mllを実行する.エラーが出たら直す
  3. lexer.mlが生成されているので,シェル上でocamlと叩いてインタラクティブ環境を起動し,#use "lexer.ml"と入力してエンター.ここでエラーになること重ある(特にtrailer内でのエラーが多い)
  4. 同じインタラクティブ環境で,trailerに書いたコードを読み出す.この場合ではmain "in.ml"と入力する
  5. エラーがなければ,次のように出力されるはず
token list =
[ID "x1"; MINUS; LPAREN; INT 2; PLUS; INT 3; RPAREN; DIV; INT 5; TIMES;
 INT 6; EOL]

きちんとスペースなども考慮して字句解析が行われていることがわかります.

まとめ

Ocamllexを使うことで,オートマトンなどの記述の必要なく字句解析を実行することができました. 機会があればオートマトンからフルスクラッチで書いてみたいと思います.そこまで手間でないかもしれないので…

字句解析はただの文字列が解釈されるだけであまり面白くないので,次はこれを構文木の形に直す構文解析を行ってみたいと思います.