加具留矢流余

かぐるやるよ

numpyのchoice関数を読む

javascriptは標準で重み付きのchoice関数が無いのでライブラリを探すか自分で作る必要がある。折角なのでpythonの勉強がてらnumpyのchoice関数を読んだ。

github.com

説明のため該当箇所のソースコードを引用しつつ説明を加えていく。

概観

ざっくり2つの引数(replace, p)によって挙動が変わっている。replaceは復元抽出か非復元抽出かを表していてTrueなら復元抽出になる。 pは各要素の選択確率を表していて指定されなかった場合には等確率で選択される。

replaceがTrueかFalseか×pが指定されたかされてないかの4パターンで異なるロジックが用意されていた。

復元抽出の場合

if replace:
    if p is not None:
        cdf = p.cumsum()
        cdf /= cdf[-1]
        uniform_samples = self.random(shape)
        idx = cdf.searchsorted(uniform_samples, side='right')
        # searchsorted returns a scalar
        idx = np.array(idx, copy=False, dtype=np.int64)
    else:
        idx = self.integers(0, pop_size, size=shape, dtype=np.int64)

まずは復元抽出の場合pは各要素の抽出確率を表す1次元配列。pが指定された場合=各要素の抽出確率が指定された場合には逆関数法的な発想で抽出を行っている。cdfが累積確率、uniform_samplesが一様分布から生成された乱数を表している。2分探索(searchsorted)を使って生成したuniform_samplesに対応する要素のインデックスをcdfから探しているだけ。

連続の場合なら逆関数法そのものだけど離散的な場合にも逆関数法と呼ぶのだろうか?少し調べるとmultinominal resampling(多項リサンプリング)という単語がヒットしたが一般的かどうかわからない。

pが指定されなかった場合には何も考えずにランダムな整数の配列を生成して、それをインデックスとして使うだけ。

非復元抽出の場合

if p is not None:
    if np.count_nonzero(p > 0) < size:
        raise ValueError("Fewer non-zero entries in p than size")
    n_uniq = 0
    p = p.copy()
    found = np.zeros(shape, dtype=np.int64)
    flat_found = found.ravel()
    while n_uniq < size:
        x = self.random((size - n_uniq,))
        if n_uniq > 0:
            p[flat_found[0:n_uniq]] = 0
        cdf = np.cumsum(p)
        cdf /= cdf[-1]
        new = cdf.searchsorted(x, side='right')
        _, unique_indices = np.unique(new, return_index=True)
        unique_indices.sort()
        new = new.take(unique_indices)
        flat_found[n_uniq:n_uniq + new.size] = new
        n_uniq += new.size
    idx = found

次に非復元抽出の場合。多次元のインプットに対応しているせいでかなりわかりにくい。詳しくは分からないがベースのアイディアは復元抽出と同じみたい。確率の累積和を計算⇒一様分布から生成された乱数を2分探索とベースは同じ。違うのは重複した結果が含まれていた場合。newのユニークな要素を抽出して結果に足し込む+次回以降サンプルされないように当該要素がサンプルされる確率を0にする、という処理を結果が指定された要素数になるまで繰り返している様子。

非復元抽出×pが指定されなかった場合の処理は読み解けなかった。コメント欄にFloyd's Algorithmというメモがあったが検索かけてもワーシャルフロイド法しか見つからない。関係があるのだろうか。

その他

issueを調べると「もっと効率いい手法あるだろ!」というツッコミが入っている。それに対して「何回も言ってるけど効率の良い手法は殆ど入力が一次元の場合にしか対応してないだろ!numpyは多次元の入力にも対応する必要があるんじゃ!」とレスが付いてて、なるほどとなった。効率が良い手法を知りたいのであれば別のソースを読むべきだったかもしれない。 https://github.com/numpy/numpy/issues/11584

GAEのフレキシブル環境でpuppeteerを使う

prill のプリント作成機能のコア部分にはpuppeteerを使っている。GAEでpuppeteerを動かしたくて試行錯誤した結果、フレキシブル環境でカスタムコンテナを使うことで動作に成功したのでそのときのメモ。後から調べたところによると適切に設定すればスタンダード環境でもpuppeteer動かせそう。スタンダード環境のほうがお手軽だし今から試す人はそっちを使ったほうが良いかもしれない。

↓ 参考記事 github.com

TL; DR;

  • GAEのフレキシブル環境ではカスタムコンテナを動かせる
  • Chromeを実行可能なカスタムコンテナを作れば好きにpuppeteerを動かせる
  • フレキシブル環境個人で使うには高いからスタンダードのほうが良いかも

カスタムコンテナを動かすための設定

下記のチュートリアルに従いapp.yamlを編集する。カスタムランタイムをフレキシブル環境下で動かすよ!という設定をしているだけ。

runtime: custom
env: flex

cloud.google.com

Chrome入りコンテナを作る

puppeteerの公式チュートリアルでは親切にDockerで動かすためのDockerFileを公開してくれている。これをGAE用に微修正してあげればOK.

github.com

# puppeteerのチュートリアルで公開されているDockerfileをGAE用に修正
# (修正)ベースイメージをgae用のものに変更
# FROM node:12-slim
FROM gcr.io/google-appengine/nodejs

# Install latest chrome dev package and fonts to support major charsets (Chinese, Japanese, Arabic, Hebrew, Thai and a few others)
# Note: this installs the necessary libs to make the bundled version of Chromium that Puppeteer
# installs, work.
RUN apt-get update \
    && apt-get install -y wget gnupg \
    && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
    && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
    && apt-get update \
    && apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 \
      --no-install-recommends \
    && rm -rf /var/lib/apt/lists/*

# If running Docker >= 1.13.0 use docker run's --init arg to reap zombie processes, otherwise
# uncomment the following lines to have `dumb-init` as PID 1
# ADD https://github.com/Yelp/dumb-init/releases/download/v1.2.2/dumb-init_1.2.2_x86_64 /usr/local/bin/dumb-init
# RUN chmod +x /usr/local/bin/dumb-init
# ENTRYPOINT ["dumb-init", "--"]

# Uncomment to skip the chromium download when installing puppeteer. If you do,
# you'll need to launch puppeteer with:
#     browser.launch({executablePath: 'google-chrome-stable'})
# ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true

# Install puppeteer so it's available in the container.
RUN npm i puppeteer \
    # Add user so we don't need --no-sandbox.
    # same layer as npm install to keep re-chowned files from using up several hundred MBs more space
    && groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruser \
    && mkdir -p /home/pptruser/Downloads \
    && chown -R pptruser:pptruser /home/pptruser \
    && chown -R pptruser:pptruser /node_modules

# (追加)GAEではポート8080を公開する必要がある
EXPOSE 8080

# (追加)必要なソースコードとかのコピー
RUN mkdir /workdir
ADD . /workdir
WORKDIR /workdir
RUN npm install --only=production

# Run everything after as non-privileged user.
USER pptruser

# (修正)自前のアプリを動かすよう変更
# CMD ["google-chrome-stable"]
CMD ["npm", "start"]

デプロイする

一行で済んでめちゃ楽。

gcloud app deploy

やってることは本当に少し設定を変えてDockerFile作っただけ。必要なパッケージを好きにインストールできるのはフレキシブル環境の強みかも。

小学校低学年向け学習用プリント作成サービスprillをリリースしました

リリースして3週間くらい経っていますがせっかくリリースしたので記念に記事を書きました。全て応用情報技術者試験のせいでリリースに割くリソースが無かったせいです。(いい訳)

prill.jp

まだそっけないトップページですがサービスとして最低限のものが凝縮されています。

f:id:theflyingcat28:20210419213631g:plain
サービスのイメージ概要

生成したいプリントの条件をセレクトボックスに入れるとそれに応じた問題をデータベースから探してプリントの形に整えてくれます。このサービス作った理由は↓に書いたのでこっちのブログではサービスを構成する技術的なことを書いてみようと思います。

prill.jp

TL; DR

  • puppeteerは軽くてサーバー用途でも使いやすいのでHTML+CSSから画像を生成するサービス作りたならめちゃくちゃ便利だよ!
  • GAEのフレキシブル環境はDockerコンテナを好きに動かせてデプロイも簡単なのでめちゃくちゃ便利だよ!(ただし多少高い)
  • サービス使ってね!

プリント作成機能

メインの機能です。気合いれて作りました。

プリントをフロントエンドで生成 or バックエンドで生成の2択でサービス考案当初は少し悩みましたが、フロントで動かすとなるとブラウザなどのユーザーの環境によって左右されて画一的なプリントを提供できないのでは?ということで割とサックリとバックエンドで作ることに決めました。

なんとなくのサービス構成を考えて次は要件です。要件がふわっとしてる?個人開発なんてこんなんでいいんですよ。

  1. 隙間を埋めるようにちょうどいい感じでレイアウトしたい
  2. 子供が解いてて楽しいデザインにしたい
  3. あとからデザインの修正が簡単にできるようにしたい
  4. 複数の異なる問題形式を同じように扱いたい

1は適切な配置を探索する感じで実現できると思うので一回あたりのレンダリングにかかる時間は短いほうがいいなぁ、デザインの修正が楽な方法となるとCSSとかでいい感じに描画できないかなぁ、4は設計しだいだなぁ、などと考えていて行き着いたのがヘッドレスブラウザを使った画像生成でした。

久々にヘッドレスブラウザについて調べてみると、ヘッドレスブラウザといったらPhantom JS!という時代が終わったことを知りました。じゃあSeleniumでヘッドレスChrome使ってみようかと調べると複数の接続を同時に処理するのは難しそう・・・と絶望していたところでpuppeteerという存在を知りました。

github.com

Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the DevTools Protocol. Puppeteer runs headless by default, but can be configured to run full (non-headless) Chrome or Chromium.

puppeteerはrendertron(サーバーサイドレンダリングのライブラリ)にも採用されているjavascriptライブラリなので、1つのChromeインスタンスで複数のリクエストをぱぱーっと捌くことが出来ます。もうこれ使うしかないじゃんと思って採用しました。

画像を生成する方法さえ決まってしまえばあとはゴリゴリコード書いていくだけでした。プリントを生成するざっくりとした流れは

  1. 問題を使いたい順に並び替える
  2. 空きスペースが最小になるように各大門の問題数を深さ優先探索
  3. レイアウトした問題の情報を収集してQRコードに変換
  4. スクリーンショットを撮って画像に変換

puppeteerのレンダリングにかかる時間によっては2の処理時間が膨大なことになるかもしれないと考えていましたが適当な打ち切り条件を入れれば現実的な時間で動くようになりました。

本当は簡単な問題から難しい問題に変化するようにうまく調整したりしたかったですがリリース優先ということで最低限の機能に絞りました。今後の課題ということでそのうち片付けます。

サーバー

annt作ったanntはフロントエンドがメインのサービスなのでVue使ってひたすらにJSを作り込んでいく作業がメインでした。そのためサーバーサイドはFirebaseでファイル返して少しだけ認証周りを書くだけでも十分なサービスでしたが、一方今回作ったprillはサーバーサイドでゴリゴリプリントを作る必要があるのでサーバーレスではなくてもう少し自由度が高いほうがいいなーと思っていました。プリントの作成をpuppeteerでやりたいからheadless chrome好きに使えて管理は楽なやつという基準で探していくとGoogle App Engineがお手頃そうでGAEに決めました。

GAEにはスタンダード環境とフレキシブル環境があって、後者のフレキシブル環境は好きにDockerコンテナを動かせます。かつ一度環境整えちゃえばデプロイ周りも簡単にできるのでめちゃくちゃ便利です。フレキシブル環境でpuppeteer動かす方法はいつか記事にしようと思います。

www.serversus.work

ただフレキシブル環境は高いです。東京リージョンで最安の構成で動かしていますが月6000円くらいは飛んできます。社会人だから耐えられましたが学生なら死んでました。 

DBは少しお高いですがCloud SQLを使っています。採用したのに深い理由は無く同じGoogleで管理に手間かけたくないと思ったからです。アクセス少ないから最低価格の構成でいいや!と割り切って使ってます。アクセスが欲しい(涙

最後に

見てくれよ俺の自慢のサービス!という記事でした。サービス作ったあとに記事書いてる時間が一番楽しいかもしれません。辛いのは維持。充実したコンテンツを提供できるよう頑張ります。

Javascriptでキーボードショートカットを実装する

anntにキーボードショートカットを実装したいと思って調べてみても、検索に引っかかるのはライブラリの使い方ばっかりであまり具体的な仕組みが見つからない。あまりブラックボックスなライブラリを使いたくないという気持ちと、この程度簡単に実装できるだろというイキリで更に調査を進めるとshortcut.jsという非常にシンプルなライブラリが見つかった。

www.openjs.com コンパクトにまとまっているので、内容を読み解いた上で使いやすいようにクラスとして実装し直した。 注意点としてshortcut.jsはブラウザのバグやIEにも対応しているが、シンプルさを優先して今回の実装では省いている。

前提知識

キーボードに限らずブラウザのイベントは要素にaddEventListenerを登録することで監視することができる。キーボードのイベントとしてkeydown、keyupが定義されていて、keydownはその名の通りキーが押し込まれたときに発生するイベント、keyupはキーを離したときに発生するイベントとなっている。今回はkeydownイベントを監視することで、予め登録されたショートカットが押されたかを判断している。

keydownやkeyupのリスナーを登録しておくと、登録したコールバック関数にKeyboard Eventというオブジェクトが渡される。keyプロパティcodeプロパティでどのキーが押されたかを確認できる。keyもcodeプロパティも押されたキーを取得するプロパティだが、両者には明確に違いがあるので使用する際にはキーボード: keydown と keyupを確認するのが良い。

Ctrlキー、Altキー、Cmdキーが押されているかどうかはctrlKey、altKey、metaKeyプロパティで取得できる。keyプロパティで押されたボタンを、ctrlKeyなどでコントロールキーを始めとしたキーが押されているかどうかを判断できるので、これでキーボードショートカットを実装できる。

プロパティ名 概要
key 押されたキーの文字を表す
code キーボードのレイアウトに依存しない押されたキーの位置を表すコードを表す
ctrlKey Ctrlキーが押されているか
altKey Altキーが押されているか
metaKey Cmdキーが押されているか

参考資料
ブラウザイベントの紹介
KeyboardEvent - Web APIs | MDN

プログラムの概要

ShortcutController

ショートカットを管理するメインのクラス。registerShortcutでキーバインドと関数を登録した状態で、handleメソッドをaddEventListenerで登録すればショートカットが使えるようになる。registerShortcutメソッドは使いやすいようにCtrl+Sのように'+'区切りでキーバインドを登録できるようにしてある。handleメソッドでは登録されているShortcutObject(次項参照)のigniteメソッドを一つづつ呼び出していく。

ShortcutObject

一つのショートカットを管理するためのホルダークラス。igniteメソッドは入力されたキーの情報を引数に取って、ショートカットの条件に合致した場合にのみ登録された関数を実行する。

プログラムの実装


const ALLOWED_KEY = ['backspace', 'esc', 'escape', 'tab', 'space', 'return']
/**
 * 使用可能なコードか判定を行う.
 * 1文字の英字かALLOWED_KEYに含まれる特殊文字のみが登録可能です.
 * https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
 * 入力は小文字である必要があります
 * @param {String} code 
 */
function allowedCode(code) {
  if (code.length === 1 && code.match(/[a-z]/i)) {
    // 長さが1
    return true;
  }
  return ALLOWED_KEY.includes(code);
}

/**
 * 1つのショートカットを表現するオブジェクト
 */
class ShortcutObject {

  /**
   * コンストラクタ
   * @param {*} char: ショートカットとして使用する英字1文字
   * @param {*} ctrl: コントロールボタンと同時に押す必要があるか
   * @param {*} meta: (Mac用): コマンドボタンと同時に押す必要があるか。
   * @param {*} propagate: Eventを伝播させるか。
   * @param {*} func: ショートカットが押されたときに実行する関数
   */
  constructor(char, ctrl, meta, propagate, func) {
    this.char = char;
    this.ctrl = ctrl;
    this.meta = meta;
    this.propagate = propagate;
    this.func = func;
  }

  /**
   * 条件に合致したか判定して、合致した場合のみ登録された関数を実行
   * @param {String} char 
   * @param {Boolean} ctrl 
   * @param {Boolean} meta 
   */
  ignite(event, char, ctrl, meta) {
    if (char === this.char && ctrl === this.ctrl && meta === this.meta) {
      this.func();
      if (!this.propagate) {
        event.preventDefault();
        if (event.stopPropagation()) {
          event.stopPropagation();
        }
      }
    }
  }

}
class ShortcutController {
  /**
   * コンストラクタ
   */
  constructor() {
    this.shortcutList = [];
  }

  /**
   * ショートカットを登録する 
   * @param {String} keybind: キーバインドを表す文字列, 例) Ctrl+S
   * @param {Function} callback: ショートカットが押されたときに呼び出す関数
   * @param {Boolean} propagate : デフォルトのショートカットを呼び出すか。
   */
  registerShortcut(keybind, callback, propagate=false) {
    // + で区切ってすべて小文字のリストに
    // 例)Ctrl+A => [ctrl, a]
    const keys = keybind.split('+').map((s) => s.toLowerCase());

    // キーバインドをパース
    let needCtrl = false;
    let needMeta = false;
    let char = null;
    keys.forEach((k) => {
      if (k === 'ctrl') {
        needCtrl = true;
      } else if (k === 'meta') {
        needMeta = true; 
      } else if(allowedCode(k)){
        char = k;
      } else {
        // 使用できないキーが含まれている
        throw new Error('Irrelvalent Shortcut');
      }
    });
    if (char === null) {
      throw new Error('Irrelvalent Shortcut');
    }

    // ショートカットをリストに追加する
    const shortcut = new ShortcutObject(char, needCtrl, needMeta, propagate, callback);
    this.shortcutList.push(shortcut);
  }

  /**
   * addEventListenerようのハンドラー
   * 
   * @param {Event} event 
   */
  handle(event) {
    const ctrl = event.ctrlKey;
    const meta = event.metaKey;
    const char = event.key.toLowerCase();

    this.shortcutList.forEach((shortcut) => {
      shortcut.ignite(event, char, ctrl, meta);
    });
  }
}

export default {
  ShortcutController,
}

割と短く実装出来た。流し読みして見切り発射の実装なので何か漏れがあるかも。上でも書いたがshortcut.jsではブラウザ周りのバグなどにも対応してそうなので、特段の主義主張が無いのであればこちらを使うのがおすすめです。

mikebird28.hatenablog.jp

mikebird28.hatenablog.jp

個人開発サービスの記事周りをWordpressに切り出した。

タイトルの日本語が怪しい気がします。コツコツ開発しているanntの話です。

anntはFirebaseで動いていて基本的には静的なページで動いていたので、チュートリアルなどの記事を書くときも直接htmlタグ付けしていくという苦行をしていました。最近さすがに当時の自分は何を考えてこんなの作ったんだ?と思い始めたので記事や頻繁に編集する部分をWordpressに切り出しました。

利用する環境

とにかく楽に環境を整えたかったのでWordpressGCP Market Placeで立てようと決めていました。GCP Market PlaceでWordpressを立てるのは本当に楽でクリックしていくだけで新しいインスタンスWordpressが動いている状態になります。Google様々です。

cloud.google.com

f:id:theflyingcat28:20201209002043p:plain
本当に運用開始ボタンひとつで起動できます。

全体の構成として当初はannt.aiに来た/post以下のリクエストだけをうまくWordpressに回せないか検討していました。色々方法を模索しましたがFirebaseはあくまで静的ファイルのホスティングやサーバレスでシンプルなAPIを実現するためのサービスです。来たリクエストの一部を別のサーバーに流すようなロードバランサ的な機能は簡単には実現できなさそうでした。

Firebaseの前に別のロードバランサ挟んでリクエスト割り振ればいいのでは?と思いもしましたが更に課金するのは嫌だなという気持ちになりました。嘘です。本当は面倒くさかっただけです。なので更に別の方法を模索しました。

最終的な構成

結論としてサブドメイン取ってサービス本体とは完全に切り離して運用することにしました。本体のサービスとは完全に分かれているので管理が楽です。何かに負けている気がしますが気にしません。大事なのはスピードです。

GCP Market Placeで作成されたインスタンスにはIPアドレスが振り分けられますがデフォルトだとエフェメラルという設定になっていて、インスタンスを起動停止するたびに別のIPに変わってしまう可能性があります。最終的にいい感じにインスタンスに対してサブドメインを設定するには、

  • 静的IPアドレスに切り替える
  • ネームサーバーに設定を追加
  • TLSの設定

こんな感じの作業をやっていく必要があります。

静的IPアドレスの設定

調べるとたくさん記事が出てきます。 Google Cloud Platformの外部IPアドレスのURLから静的アドレスに変更できます。 条件によっては課金されるので注意してください。

GCPで作成したWordpressの静的IPアドレスの設定方法 - Qiita

ネームサーバーの設定変更

利用しているレジストラ(お名前.comやGoogleドメイン等)によってやり方は違うと思います。 やることとしては新しいサブドメインと作成した静的アドレスを対応付けるAレコードを追加するだけで大丈夫なはずです。

TLSSSL)の設定

今はTLSが正しい表現になるのでしょうか。初めてTLSの設定を行いましたがCertbotというツールを使えばLet's encryptの証明書を簡単に手に入れられます。

GCP Market PlaceでWordpressを立ち上げるとおそらくDebianApacheで動いていると思います(今後変わる可能性もあるので注意してください)。 自分はaptでcertbotをインストールしましたが(うろ覚え)CertbotのトップページにOS+サーバーを指定すれば導入方法を親切に教えてくれるので、それに従うのが一番確実かと思います。Wordpress+GCP Market Place + TLSググると何個か日本語の資料も引っかかりますが古いのかCertbotが正しく動作しなかったので素直に公式に従うのがいいと思います。 certbot.eff.org

そんなこんなで最後には無事Wordpress環境を整えることが出来ました。本当に楽にセットアップできるので、雑にブログ or それに類する記事執筆システム作るにはおすすめです。

↓ 作ったもの。

https://post.annt.ai/

Pascal VOCのフォーマットに関する調査

anntのpythonライブラリにpascalVOCの読み込み&書き込み機能を実装を検討中。 実装にあたって具体的な仕様を調べてみたもののあまり厳密に決まってなさそう。 調べてなんとなくわかったこととかメモなどを記載していく。

調べるにあたって参考にした資料

フォーマットの例

Pascal VOC 2007から抜粋したxmlファイルの例を記載する。

<annotation>
    <folder>VOC2007</folder>
    <filename>voc2007.jpg</filename>
    <source>
        <database>The VOC2007 Database</database>
        <annotation>PASCAL VOC2007</annotation>
        <image>flickr</image>
        <flickrid>325991873</flickrid>
    </source>
    <owner>
        <flickrid>archintent louisville</flickrid>
        <name>?</name>
    </owner>
    <size>
        <width>500</width>
        <height>375</height>
        <depth>3</depth>
    </size>
    <segmented>0</segmented>
    <object>
        <name>chair</name>
        <pose>Rear</pose>
        <truncated>0</truncated>
        <difficult>0</difficult>
        <bndbox>
            <xmin>263</xmin>
            <ymin>211</ymin>
            <xmax>324</xmax>
            <ymax>339</ymax>
        </bndbox>
    </object>
    <object>
        <name>chair</name>
        <pose>Unspecified</pose>
        <truncated>0</truncated>
        <difficult>0</difficult>
        <bndbox>
            <xmin>165</xmin>
            <ymin>264</ymin>
            <xmax>253</xmax>
            <ymax>372</ymax>
        </bndbox>
    </object>
    <object>
        <name>chair</name>
        <pose>Unspecified</pose>
        <truncated>1</truncated>
        <difficult>1</difficult>
        <bndbox>
            <xmin>5</xmin>
            <ymin>244</ymin>
            <xmax>67</xmax>
            <ymax>374</ymax>
        </bndbox>
    </object>
    <object>
        <name>chair</name>
        <pose>Unspecified</pose>
        <truncated>0</truncated>
        <difficult>0</difficult>
        <bndbox>
            <xmin>241</xmin>
            <ymin>194</ymin>
            <xmax>295</xmax>
            <ymax>299</ymax>
        </bndbox>
    </object>
    <object>
        <name>chair</name>
        <pose>Unspecified</pose>
        <truncated>1</truncated>
        <difficult>1</difficult>
        <bndbox>
            <xmin>277</xmin>
            <ymin>186</ymin>
            <xmax>312</xmax>
            <ymax>220</ymax>
        </bndbox>
    </object>
</annotation>

フォーマットの説明

1つのファイルに対して1つのアノテーション情報を記載したxmlファイルが用意されている。 xmlファイルは'annotation'タグで始まっていて、直下にメタデータに関するタグや、具体的なアノテーション情報が記載されているobjectタグなどが配置されている。

タグ名 説明
folder 画像が格納されているフォルダ名
filename アノテーションした対象の画像ファイル名
source (不明)情報源に関する情報?
owner (不明)画像の所有者に関する情報?
size タグ名通り画像サイズに関する情報(幅、高さ、チャネル数)
segmented (不明)0のことが多い気がする。
object 肝心のアノテーション情報。詳細は次の段落で説明

objectタグについて

objectタグには肝心な画像内部に写っている物体の情報が記載されています。 各タグの説明は上述のガイドラインに従って記載しています。

タグ名 説明
name 写っている物体のクラス名
pose おそらくアクション分類タスク用の人物の行動を表すタグ
truncated 物体が15~20%以上画像からはみ出しているか
difficult 認識するのが難しいオブジェクト。e.g. 理解に文脈が必要な画像等
occluded 2008以降追加 物体の5%以上が何かに隠れているか
bndbox バウンディングボックスの位置情報
part (不明)ポーズに関する情報?
actions 写っている人物の行動に関する情報。行動推測タスク用
point (不明)

殆ど不明となってしまった。明確なフォーマットが見つからずライブラリ作るのが大変そう。正直使う上で必要なタグは限られているので不要な月は実装しなくて良い気がする。 適宜、修正・追記していく予定。

AtCoder色変記事(緑)。あるいは趣味としての競プロの勧め。

何番煎じか分かりませんがこの度晴れてAtCoderで緑帯に突入することが出来ました。 もう巷には緑になるために必要な修行方法が溢れているので、「緑になるために何をしたか」ではなくて、「趣味としての競プロ楽しいよ!」という内容にフォーカスを当てて記事を書こうかなと思います。

簡単に自己紹介

院卒社会人2年目です。理系だったものの学科も勤め先もITやプログラミングとは程遠くプログラミングは完全趣味でやっています。大学時代はコツコツ独学でAIなどの勉強をしてました。その延長でkaggleなどにも参加していましたが、あまりまとまった時間が取れずメダルを取ること無く引退してしまいました。

競プロに出会ったきっかけは覚えていませんが、初めて参加したきっかけは腕試し目的でした。独学ですがそれなりにプログラムを数書いてきた自負があったので緑くらい数回参加すればなれるでしょ、と思っていましたが初参加(確か企業コン)で現実を思い知らされました。以降悔しさから積極的に参加するようになって今では虜です。

競プロって何?

自分が説明するよりWkipediaを見ていただいたほうが正確で早いです。

ja.wikipedia.org

あえて自分の言葉で説明すると、「数問の問題が出されるので、コンテスト時間内に問題を解くプログラムを正確かつ素早く解く競技」になるんでしょうか。AtCoder社が開催するABCというコンテストを例にすると、100分という制限時間の6問(A~F)の問題が出題されるので、自らの知識・技術・経験を総動員してできる限り多くの問題を素早く解くことを競います。1問目(A問題)は非常に簡単ですが、後半になると深いアルゴリズムの知識と考察力が必要とされるので全問解けるのは多くの場合参加者のごく一部です。

コンテストサイトによって細部は異なるかもしれませんが、大手のコンテストサイトには実力を示すレーティングの仕組みがあります。レーティング帯に対応する色が定義されていて、この色によってざっくりとその人の実力が分かるようになっています。AtCoderだとレーティング0~400が灰、400~800が茶、800~1200が緑と400ごとに区切りが設定されていて、初心者は多くの場合灰〜茶帯を彷徨うことになります。

chokudai.hatenablog.com

競プロの魅力

で、それって面白いの?という話ですが、めちゃくちゃ楽しいです。楽しすぎて最近はABCには殆ど毎回欠かさず参加しています。これまで何で競プロが楽しいと思うのか?について言語したことがなかったので何とか言葉にしてみました。

達成感

個人的にはこれが一番のモチベーションです。おそらくですが最初数回のコンテスト参加で適正なレーティングに配置されます。アルゴリズムを少し齧ったことがある、程度だと多くの場合良くて茶色になるのではないかと思います。一度自分の適正レーティングに到達したあとに次の色を上げるためにはかなりの努力が必要です。

コンテストに参加しても思うように解けずレートが下がって(よく冷えるとか言われてます)ぐぬぬ...となります。Twitterで他の人が色変してると嫉妬で血の涙が出そうになります。それでも負けじとコンテストに参加し続けて次の色に到達したときはめちゃくちゃ嬉しいです。アドレナリンだかエンドルフィンだか分からないですけど脳内で謎の汁がいっぱい出ます。緑になったときは無意味にシャドーボクシングしてました。

f:id:theflyingcat28:20201109221837p:plain
入緑嬉しい

努力が身になっているのが実感できる

競プロはコンテスト自体は2時間程度です。それ以外の時間は過去問を説いて勉強したり、解けなかった問題を振り返ったりという修行の時間になります。コンテスト自体は頻繁に開催されているので、努力したら努力した分見える形で結果として帰ってきます。最初自分は数学系の問題だったり幅優先探索系が苦手過ぎてコンテストの度に絶望する日々を送っていたのですが、精選100問やったり類題解きまくったりしてると「はいはいこのパターンね」みたいな感じになって俺TUEEEEE(弱い)が出来ます。

f:id:theflyingcat28:20201109222354p:plain
有志によって運営されるAtCoder Problemsで修行の成果が見れます

短期間で努力の結果がフィードバックされるのはモチベーションの維持としては良い仕組みだなーと思って修行してます。kaggleの数ヶ月に渡るマラソン的なコンテストと違って競プロは短時間なので忙しくて修行できなくても、落ち着いた段階で気楽に再開できるのも良い点だなと思ってます(この辺は個々人の好みによるところが大きいかもしれないです)。

感情を揺さぶられる

上2つの項目で書いたことの言い換えになってしまっているところはありますが、コンテストに参加すると感情が揺さぶられます。今までD問題までしか解けなかったときにE問題まで解けたときは嬉しくて情緒不安定になりました。逆にC問題が解けずレートが下がったときに回答を見たらめちゃくちゃ簡単な問題だったときは辛くて情緒不安定になりました。こんな感じでコンテストに参加していると喜怒哀楽大体の感情が楽しめます。コンテストに参加することによって平坦な生活に刺激が発生します。

コンテスト終わったあとにTwitter眺めているのも楽しいです。コンテスト後は喜んでる人、落ちこんでる人、解説してくれる人、いろんな人が居てお祭りみたいな感じです。普段Twitter見ないだけかも知れませんが。

社会人にこそおすすめの趣味

なんか参加している人学生が多そうだし社会人になってから始めるのもな...と感じているかも知れません。というか自分がそうでした。大して時間も取れないし学生に遅れを取りそうと思っていました。でも実際に初めて見ると社会人の方もたくさんいらっしゃいますし、時間的な面もあまりハンデになっていないなと思っています。

AtCoderのコンテストであれば大体土日の夜開催

初心者向けのABCを始めとしてAtCoderはだいたい土日の夜に開催されます。飲み会とかをセットしない限り独身であれば高確率で参加できます。突如コンテストが生えることもありますが、基本的に数日前には開催がFIXしてるのでそれなりに予定も組みやすいです。

隙間時間でも修行できる

修行するしないは個人の自由なので、仕事終わりに一日一問みたいな感じに練習してみてもいいですし、土日にまとめて修行みたいな感じでも好きなようにできます。自分はやる気がある日にぐぉーって自分の適正より難しい問題を数問説く感じでやっています。

というような感じで割と柔軟にできます。そういう意味ではまとまった時間が取りにくい社会人にこそおすすめの趣味なのでは?と思っています。

実利的な話をするとAtCoder Jobsなどで転職の助けになったりすることもあるみたいです。あとアルゴリズムの知識が仕事の役に立つかもしれないです。競プロは仕事の役に立たない!という意見も見かけますが、職場に寄るところが大きい気がします。「役にたたない...」と思ってやるよりは「役に立つ!」と思ってやったほうが確実にモチベーションは繋がるので役に立つと思っておくのがいいかと思います。

始めてみたい!

始めましょう。まずは日本語で参加できるAtCoderのコンテストがおすすめです。初回参加して絶望したら先人の色変記事を読みましょう。やるべきことが見えてきます。おすすめの記事を貼っておきます。

ntk-ta01.hatenablog.com qiita.com qiita.com

終わりに

もう色変記事じゃない気がしてきました。それにAtCoderの回し者みたいになってしまいました。この記事を見てAtCoder見て始めてくれる人がいたら嬉しいです。こんな楽しみ方もあるよ!という意見があれば是非記事にしてください!