コーヒーおかわり

データサイエンティストを目指す大学院生の日記。

日商簿記2級合格。可視化とふりかえり

日商簿記2級に合格した

  • 2019/3/27 全国の合格率が発表されたので情報を修正しました。

    簿記2級ギリギリ合格した(70点/100点中)
    簿記2級ギリギリ合格

内定先の会社から入社までに簿記2級を取得するように言われ、日商簿記2級(第151回)を受験した。第151回は、ひどい回だった。過去問で全くみたことのないような問題(しかも、処理量が多い問題)が設問3で出題され、Twitterのトレンドに簿記2級がでるぐらい騒ぎになった。 下記のように簿記の先生が怒ってる動画がでてるw

www.youtube.com

東京・横浜の商工会議所のデータでは合格率は13.3%となっているが、全国の合格率は12.7%しかなかった。
3/15に結果がでて、合格したことがわかったときは嬉しかった。
簿記2級の合格率の平均は20%-30%といろいろなWebサイトに書いてあったりするが、 平均が全く役に立たないほど合格率の変動が激しい。
標準偏差を計算したり、合格率の変動を可視化したら、簿記2級の異常さがわかるかと思い、Jupyter Notebookを公開した。
Jupyter Notebookの公開にはGoogle Colabを使用した。
ランタイム -> 全てのセルを実行
で可視化プログラムが動く。
可視化にはplotlyを使用しており、カーソルを表の上におくとその年度の合格人数などの詳細な値が出るようになっている。

https://colab.research.google.com/github/kotaru23/analyze-nisshoboki-class-2/blob/master/analyze-boki-class-2.ipynb

実行できない(したくない)人のために、下記に画像を貼っておく。

f:id:GeoTaru:20190327145431p:plain
151回までの合格率の推移

勉強法

150回の試験も受験していたのだが、その時は過去問と実際に出た問題の難易度が違いすぎて落ちた。過去問を中心に勉強していたのがよくなかった。今回は過去問ではなく、TACの問題集とTACの直前講座の予想問題を解きまくったのがよかったと思う。TACの直前講座は動画で解答解説をしてくれるものだったが、解答解説動画は勉強するのに効率がよくなかったから無視した。ひたすら予想問題を解いて採点し、解答解説書を読み、わからないところは教科書・問題集に戻って理解を深めた。修士論文の発表を終えてから、簿記の勉強を再開したため、簿記の勉強をまともにできたのは2週間しかなかったが、なんとかなった。
本気で簿記を勉強した時間は第150回、第151回対策合わせて150-200時間くらいか。

受験中の戦術

まず、どんな問題が出ているか最初に確認した。
3の内容が苦手な上になんかやばそうな気配がしたので、 4 -> 5 -> 1 -> 2 -> 3の順で解くことにした。4(25分)、5(15分)、1(25分)、2(15分)くらいの時間配分で解いて、3には残りの40分を費やした。ひょっとすると見直しをした方が点数が高かったかもしれないが、試験中に「3を切り捨て他の問題を見直す」という判断はできなかった。

感想

なんとか合格点ピッタリ(70点/100点中)で合格したが、なぜかもやもやする。落ちてたら「ふざけるな、ふざけるな、ばかやろう!なにが資格試験じゃボケー」と叫んでいたところだが、合格したので「ふざけるな、バーロー」ぐらいにしておく。
簿記2級は有名な資格試験だから、いろいろな人が受けているだろう。だから、合格率が低いなら難易度が高いと考えてもおかしくはないと思う。難易度が回によって大きく変わる、運ゲーな資格試験はもう受けたくない。課金しても当たるかどうかわからないなんて、ソシャゲのガチャじゃあるまいし。

駄文失礼しました。

2018の振り返り

不運(ハードラック)と踊(ダンス)っちまった2018

2018年の漢字は「災」だと言うニュースをこの前聞いた.
私にとっても2018年は「災」の多い1年だった...

news.mynavi.jp

1月 実家に帰省

諸事情あり,今まで居候していた祖父母の家から実家に引っ越した.
都会の生活に慣れてしまっていたため,田舎での生活は不便で仕方なかった. 家から駅までは30分くらいあるし,大学まで往復5時間かかるのはもう発狂しそうだ

1月 愛犬の病気

去年の1月,愛犬が死ぬかもしれないと言う状況になった.
いつも食欲旺盛な愛犬が,餌をほとんど食べてくれなくてだいぶ焦った. 胆のうに石ができたとのことで,その石を取り除く手術をしなければならないが,その手術は非常に難しいものとなると医者は言っていた.
本犬の努力と点滴のおかげで手術は避けることができたのは幸いであったが,不安はまだなくなったわけではない...

3月 自転車事故

まったく最悪だった.
通学途中で単独で自転車事故をおこしてしまった.幸いだったのは,受け身をとったことで骨折などの重症にならなかったことと,自分以外の誰も怪我をさせなかったことだ.ただ,顔面を地面で擦ってしまい,血だらけになり,顔を何十針も縫うことになり,しばらく体力が戻らなかった...

就活のスケジュールの変更しなければならなくなったのもいたかった. 就活はなんとか第一希望群の1つから内定をいただけたことは幸いであった.が...

11月 簿記

内定をくれた会社は入社の条件として,入社までに簿記2級に合格することを条件としていた.効率よく勉強を進めるために,過去問を中心に勉強したが,穴があった.その穴が試験に出てしまい,大きく失点した.さらに日商簿記は難易度の難易度の変化が激しく,回によって全く難易度が異なる.簡単な会は40%以上が合格し,難しい回は合格率が10%程度になる.私が受けた回は合格率が15%にとどかない,難易度の高い回だった.
やれやれ入社が少なくとも3ヶ月遅れることが確定してしまった.

さて

2018年はついてない年だったが.2019年は良い年になるだろうか

目が点! すっごく小さいDockerイメージ! tweetコマンドをDockerを使って動くようにした

世の中のDockerイメージは大きすぎて、ダウンロードに時間がかかりすぎる!(某CM風)

Dockerイメージを作っていると悩まされるのがDockerイメージ(以下イメージ)のサイズです。イメージサイズが大きいとダウンロードやアップロードに時間がかかり、不便です。

そこで、テザリングでパソコンをインターネットに繋げているときにダウンロードしても、通信量・通信時間が気にならないような大きさのイメージを作りました。

https://hub.docker.com/r/kotaru/tweet/

その大きさはなんと、3MB!
"目が"点ですよ。メガだけに!

https://hub.docker.com/r/kotaru/tweet/tags/
3MBのイメージ!

はぁぁぁぁい!ずっと言いたくて言えなかったダジャレを言ったところで、このイメージを作った過程を説明します。なにをしたかをとっとと知りたい人はまとめを見ましょう。

tweet command written in Go

github.com

Go言語を覚えたてのころに作ったtweetコマンドですが、最近更新しました。Dockerを使って、Go言語の開発環境がなくても簡単に使えるようにしました。
なお、動かすためにはTwitterAPIキーが必要で、環境変数にセットする必要があります。詳しくは上記のリンクで。

tweetコマンドを作った背景

このコマンドを作った時は、まだ研究室にSlackが導入されていませんでした。サーバで長期実行プログラムが終わったときに、通知するいい方法がなかったので、このコマンドを作りました。
機能はシンプルで、「標準入力から受け取った文字列をそのままツイートする」だけです。案外便利なので今でも普通につぶやく時に自分は使ってます。後輩に変人だと言われてしまったが...w

(注意) 重要な情報はツイートしないようにしてくださいね

Docker multi stage build with scratch

さてさて、本題といきましょうか。
Docker 17.05からmulti stage buildという機能が追加されています。
詳細はリンク先を参照してください。

docs.docker.com

簡単に説明すると、アプリケーションをビルドするためのコンテナとアプリケーションを実行するコンテナを分離することができるという話です。
分割することで、ビルド時のみに必要なファイルをわざわざ消すシェルスクリプトを書かなくてすみ、綺麗で保守性の高いDockerfileをかけるようになります。 下記は私の作ったtweetコマンドをDocker上で動かすためのDockerfileです。

FROM golang:1.11.2-alpine3.8 as builder

RUN apk --update --no-cache add git ca-certificates
WORKDIR /go/src/github.com/kotaru23/tweet
COPY . .
RUN go get .
RUN CGO_ENABLED=0 GOOS=linux go build -o tweet


FROM scratch

COPY --from=builder /go/src/github.com/kotaru23/tweet/tweet /go/bin/
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
CMD ["/go/bin/tweet"]

注目するべきは、3点あります。

  1. FROMが2回書かれている。そのうち1つには "as builder"と書かれている
  2. COPYの後に"--from=builder"と書かれている
  3. FROM scratch

説明

FROMが2回書かれていますが、これは決して間違いではありません。
1つ目のFROMから次のFROMまでの間では、tweetコマンドをソースコードからコンパイルしています。また、この後の工程のために"builder"という名前をコンテナにつけました。

FROM golang:1.11.2-alpine3.8 as builder

RUN apk --update --no-cache add git ca-certificates
WORKDIR /go/src/github.com/kotaru23/tweet
COPY . .
RUN go get .
RUN CGO_ENABLED=0 GOOS=linux go build -o tweet

2つ目のFROM以降は、アプリケーション実行用のイメージです。
1つ目のイメージを継承しているのではなく、全く別のイメージです。

FROM scratch

COPY --from=builder /go/src/github.com/kotaru23/tweet/tweet /go/bin/
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
CMD ["/go/bin/tweet"]
COPY --from=builder "builderイメージのパス" "コピー先のディレクトリ"

とすることで、先ほど作成したbuilderイメージからファイルを持ってきています! Go言語で書かれたアプリケーションは、外部ライブラリ(C言語等)を使用していなければ、バイナリ1つをコピー&ペーストすれば実行できます。(バイナリは各OS, CPUのアーキテクチャごとにコンパイルする必要があります)
Go言語のライブラリ、ランタイムなどは全てバイナリの中に含まれているようです。 このtweetコマンドはGo言語で書かれていてバイナリとCA証明書があれば動くようになっているので、先のbuilderコンテナからバイナリとCA証明書を実行用コンテナに持ってきています。

FROM scratch

scratchとは、ほとんど何もないオフィシャルイメージです。/bin/shすらないようです。詳しくは下記リンク

https://hub.docker.com/_/scratch/

このイメージはファイルを他の場所からコピーしてきて、バイナリを実行するぐらいの用途にしか使えないのではないかと思います。JavaなどのVMを使用する言語で書かれたアプリケーションでは使えないかもしれません。しかし、外部依存のない純粋なGo言語で書かれたアプリケーションであれば、バイナリを持ってくるだけで実行できます。今回の例では通信をするためにCA証明書もビルド用のコンテナから持ってきていますが、それだけです。

まとめ

3MBのイメージを作るために必要な3つのこと

  • Go言語で書かれたアプリケーション(C言語の依存なし)
  • Docker multi stage build
  • scratchイメージ

あとがき

あ、"3"って数字使いすぎですかね。コンサルタントの真似ですw
それとBuildKitの話ができなかったのが残念です。

最後に言わせてほしい。

$ echo "純子ちゃん、やーらしか!" | \
docker run --rm -i \
    -e TWITTER_CONSUMER_KEY=$TWITTER_CONSUMER_KEY \
    -e TWITTER_CONSUMER_SECRET=$TWITTER_CONSUMER_SECRET \
    -e TWITTER_ACCESS_TOKEN_KEY=$TWITTER_ACCESS_TOKEN_KEY \
    -e TWITTER_ACCESS_TOKEN_SECRET=$TWITTER_ACCESS_TOKEN_SECRET \
    -e TWITTER_SCREEN_NAME=$TWITTER_SCREEN_NAME \
    kotaru/tweet

近況報告というか簿記に対するぼやき

この文章は頭の中のもやもやをそのままアウトプットしていて理路整然とはしていないかもしれない。あしからず

近況報告というか簿記に対する愚痴

前回の投稿からだいぶ時間がたってしまった。

というのも、ここのところ猛烈に忙しい。
まとめると下のような感じ

  1. 内定先の企業から簿記の2級を入社前にとるように言われたので、簿記の2級の勉強をして、受験した
  2. 大規模なデータを扱うためのハイパフォーマンスなプログラムを作成した
  3. ディープラーニングをするためのモデルを構築している

特にやばいのは1の簿記の勉強だ。 考えるとき以外は持たないシャーペンと消しゴムを握りしめ、いつもの生活でやらないような暗記をし、プログラム書けば良さそうなタスクを、しょぼい電卓で計算しなければならないのは控えめに言っても苦痛でしかない。Jupyter Notebook使いたいw

会計の仕組みを知っておくという点で簿記が重要であるという考えはわかる。ただ、この資格試験おかしくないか? 近年出題範囲の変更(拡大)があって覚えるべきことが増大しているうえに、回によって合格率が大きく異なるようだ。合格率の分散が大きすぎる。 いろいろな立場のひとが受ける試験なので、受験者の質が回によって大きく変わるというのは考えにくく、合格率の変動が難易度の変動とほぼ同期していると考えても問題ないだろう。 難易度を一定に保たないのって、なんでだろう。資格の価値が受験する回によって揺らいでしまうのはおかしくないか?

まあ、そんな簿記必須の企業を就職先として選んだのは自分なので自業自得だし、やるしかないのだが。入社後は良さそう

2, 3に関してもすごく忙しいが、すごく楽しい。簿記の勉強(特に商業簿記)とは異なり論理的な思考が重要で、脳内メモリ消費量が少ない。

ふう。 さらに忙しくなるのはこれからだ。 簿記の勉強をしながら、ビッグデータ処理・機械学習を使う研究をしつつ、修論を書かなければならない。地獄の始まりだ!

余談

日商簿記を開催している日本商工会議所だが、「日商プログラミング検定」を創設するらしい。サンプル問題を見てみてみたが、なんだかなあって感じ。Scratchの操作方法覚えてもなあ... てか、VBAって... そもそもサンプル問題のファイルがENTRYレベルとBASICレベルはPDFなのになぜSTANDARDは画像ファイル(jpeg)なのか。半角英数字と全角英数字がごちゃ混ぜになっているのも、キモチワルイ。生理的に無理

https://www.kentei.ne.jp/pg-effect

駄文失礼しました。

www.boki-navi.com

www.yutorism.jp

www.kentei.ne.jp

LaTeXのtexファイルをPDFに変換するツールをDockerでつくった

前置き

機械学習のネタを投稿したいが,研究で使っているデータは公開できないし,未発表の研究を投稿するわけにもいかないので,Dockerネタになってしまう.

公開データを使って実験して投稿しようかなあ...

LaTeXの環境構築が辛かった...

本題
研究の中間発表に向けて,レジュメを作り始めたのだが,macOSの環境にTeXの環境を作るのに失敗し,うまくいかなかったのでDockerで作った.

Quick Start

$ docker run --rm \
             -v "texファイルがあるディレクトリを絶対パスで記述":/home/geotaru/workdir \
             -e target=/home/geotaru/workdir/"マウントしたディレクトリの下にある,コンパイルしたいtexファイルのパス" \
             geotaru/latex-compiler

Example

カレントディレクトリにあるresume.texからPDFファイルを生成する

$ docker run --rm \
             -v `pwd`:/home/geotaru/workdir \
             -e target=/home/geotaru/workdir/resume.tex \
             geotaru/latex-compiler

Dockerfile

Dockerfileは下記の通り.

他の人のレポジトリを参考にDockerfileを書き直し,コマンドラインツールを作った.参考にしたURL・レポジトリは最下部に書きました.

alpine Linuxを使って軽量化したが色々追加したせいか,非圧縮で1GBを超えてしまった.

FROM alpine:3.8

MAINTAINER geotaru <geotaru.dev@gmail.com>

ENV PATH /usr/local/texlive/2018/bin/x86_64-linuxmusl:$PATH

ENV USER geotaru
ENV HOME /home/${USER}

RUN apk --no-cache --update add bash
ENV SHELL /bin/bash
RUN adduser -S ${USER}

WORKDIR /root
RUN apk --no-cache --update add wget perl xz tar fontconfig-dev && \
    wget http://mirror.ctan.org/systems/texlive/tlnet/install-tl-unx.tar.gz  && \
    mkdir install-tl-unx && \
    tar xf install-tl-unx.tar.gz --strip-components 1 -C install-tl-unx

COPY texlive.profile /root/install-tl-unx/
RUN /root/install-tl-unx/install-tl \
    --repository=http://mirror.ctan.org/systems/texlive/tlnet/ \
    --profile=/root/install-tl-unx/texlive.profile && \
    tlmgr install \
        collection-basic collection-latex \
        collection-latexrecommended collection-latexextra \
        collection-fontsrecommended collection-langjapanese latexmk && \
    ( tlmgr install xetex || exit 0 ) && \
    rm -rf /root/install-tl-unx && \
    apk --no-cache del wget xz tar fontconfig-dev && \
    rm -rf /root/install-tl-unx

COPY ./compile.sh /opt/compile
RUN chmod 777 /opt/compile

USER ${USER}
WORKDIR ${HOME}/workdir
ENV target ${HOME}/target.tex

CMD /opt/compile ${target}

compile.shの中身

#!/bin/bash
platex $1
dvipdfmx $(echo $1 | sed "s/.tex$/.dvi/g")

上記のDockerfileの最新版は下記URLにおきました. git cloneしてお好きなように改変して自分の端末でbuildするなり,フォークしていじくりまわすといいと思います.ご自由にどうぞ

github.com

感想

自分のMacの環境がごちゃごちゃになってしまい,環境構築に失敗してtexファイルをpdfにうまく変換できなかったが,Dockerを使うことで解決した.Dockerはイイゾー!

参考

3846masa.hatenablog.jp

github.com

滅びの呪文を唱えてみた

滅びの呪文唱えてみた

Linuxを破壊するコマンドで、"sudo rm -rf --no-preserve-root /"というものがある。やったことなかったので、家のどうでもいいLinuxでこのコマンドをたたいてみた。雰囲気を出すために、わざわざホスト名を変えたりユーザを作ってやったw

バルス!!

天空の城ラピュタ」にでてくる呪文をエイリアスとして登録し、コマンドを叩いた。

[Pazu@laputa ~]$ alias バルス='sudo rm -rf --no-preserve-root /'
[Pazu@laputa ~]$ バルス

あなたはシステム管理者から通常の講習を受けたはずです。
これは通常、以下の3点に要約されます:

    #1) 他人のプライバシーを尊重すること。
    #2) タイプする前に考えること。
    #3) 大いなる力には大いなる責任が伴うこと。

[sudo] Pazu のパスワード:

「大いなる力には大いなる責任が伴うこと」
sudoコマンドを初めて叩く時に出るメッセージだが、なんかかっこいいw

大量のエラー

なにやら色々でてきたw sudoでも消去できないのかな?

rm: `/sys/kernel/debug/tracing/events/libata/ata_eh_link_autopsy_qc/enable' を削除できません: 許可されていない操作です
rm: `/sys/kernel/debug/tracing/events/libata/enable' を削除できません: 許可されていない操作です
rm: `/sys/kernel/debug/tracing/events/libata/filter' を削除できません: 許可されていない操作です
rm: `/sys/kernel/debug/tracing/events/drm/drm_vblank_event/format' を削除できません: 許可されていない操作です
rm: `/sys/kernel/debug/tracing/events/drm/drm_vblank_event/filter' を削除できません: 許可されていない操作です
rm: `/sys/kernel/debug/tracing/events/drm/drm_vblank_event/id' を削除できません: 許可されていない操作です
rm: `/sys/kernel/debug/tracing/events/drm/drm_vblank_event/enable' を削除できません: 許可されていない操作です
rm: `/sys/kernel/debug/tracing/events/drm/drm_vblank_event_queued/format' を削除できません: 許可されていない操作です
rm: `/sys/kernel/debug/tracing/events/drm/drm_vblank_event_queued/filter' を削除できません: 許可されていない操作です
rm: `/sys/kernel/debug/tracing/events/drm/drm_vblank_event_queued/id' を削除できません: 許可されていない操作です
rm: `/sys/kernel/debug/tracing/events/drm/drm_vblank_event_queued/enable' を削除できません: 許可されていない操作です
rm: `/sys/kernel/debug/tracing/events/drm/drm_vblank_event_delivered/format' を削除できません: 許可されていない操作です
rm: `/sys/kernel/debug/tracing/events/drm/drm_vblank_event_delivered/filter' を削除できません: 許可されていない操作です
rm: `/sys/kernel/debug/tracing/events/drm/drm_vblank_event_delivered/id' を削除できません: 許可されていない操作です
rm: `/sys/kernel/debug/tracing/events/drm/drm_vblank_event_delivered/enable' を削除できません: 許可されていない操作です

中略

rm: `/sys/kernel/debug/tracing/events/filelock/break_lease_noblock/format' を削除できません: 許可されていない操作です
rm: `/sys/kernel/debug/tracing/events/filelock/break_lease_noblock/filter' を削除できません: 許可されていない操作です
rm: `/sys/kernel/debug/tracing/events/filelock/break_lease_noblock/id' を削除できません: 許可されていない操作です
rm: `/sys/kernel/debug/tracing/events/filelock/break_lease_noblock/enable' を削除できません: 許可されていない操作です
rm: `/sys/kernel/debug/tracing/events/filelock/break_lease_block/format' を削除できません: 許可され�

ホームディレクトリ消失

bash 「あっはっはっは、どこへ行こうというのかね!?」
cdコマンドがまだ生きているってことかな。

[Muska@laputa ~]$ cd
-bash: cd: /home/Muska: No such file or directory

コマンド消失

コマンドが消去されたようだ。

[Muska@laputa ~]$ ls
-bash: ls: command not found
[Muska@laputa ~]$ whoami
-bash: whoami:command not found

感想

「大いなる力」を感じることができた!

続Proxyの影響下のUbutnu 18.04でJupyter Notebook with Keras on TensorFlow on Docker

下記サイトに公開しました。

Dockerfileはベースイメージをnvidia/cudaを使用したものに変更しました。 あと、もう少し丁寧に書きました。

qiita.com

GPUサーバを立てた

研究室にDeep Learning用のGPUサーバを立てた

先週半ばにパーツが届き、先週金曜日に後輩の助けを借りつつGPUサーバを組み立てた。今まで、パソコンを組み立てたことがなく、GPUをSLI構成なんて見たことがなかったため、すごく新鮮でいい経験になった。配線に苦戦したので、パーツの選びかたが大事だなと思った。

OSはUbuntuを採用した。その理由は数あるLinuxディストリビューションの中で、NVIDIAのドライバーをインストールするのがもっとも簡単だと思ったからだ。

以前、NVIDIA Dockerを使ってCUDAドライバをなんとかする方法を投稿したが、その手法は今も使っている。今回のサーバは透過プロキシを利用したため、Docker Hubは必ずしも必要ない。やっぱり必要だった...

ただ、なぜかCNNのモデルが動かない。なんでだろう...

Go言語で並列プログラミング!! ~ Go注文は並列処理ですか? ~

街の国際バリスタデータサイエンティストになりたい!

f:id:GeoTaru:20180713142945j:plain
研究室で珈琲を淹れている

Go言語とは

Go言語はGoogleのエンジニアが作った言語で、DockerやKubernetes, Vuls(脆弱性スキャナ)などに用いられている言語である。Go言語の持つ特徴の中でもっともカッコイイ特徴は並列プログラミングを楽に実装できるということだ。今日は久しぶりにGo言語を使って並列プログラミングをしよう。

題材

「コーヒーを挽いて淹れる」

といっても、コーヒーを挽いて淹れる過程を並列プログラミングでシミュレーションするだけなので、実際に作るわけではない。 手順を簡単に説明すると下記のとおり。

  • ポットでお湯を沸かす
  • コーヒー豆を挽く
  • コーヒーを淹れる

どこが並列になるかというと、「お湯を沸かしている」ところと、「コーヒー豆を挽く」ところである。お湯が沸くのをただ待つのは時間の無駄なので,お湯が沸くのを待っている間にコーヒー豆を挽く。お湯を沸騰させて、コーヒー豆をミルで砕いたら、ドリッパーの上にコーヒーフィルタをいれて、粉になったコーヒーをコーヒーフィルタに入れる。そしてお湯を注ぐ。

作成したコード

コードは下記の通り。

package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println("美味しいコーヒーを淹れるぞい!!")
    bw := make(chan float64) // ポットのお湯が沸いたことを通知するチャネル
    water := 500.0
    fmt.Printf("ポットに、%.1fmlの水を入れる\n", water)
    fmt.Println("ポットのスイッチをOnにする")
    go BoilWater(water, bw)
    gcb := make(chan float64) // コーヒー豆を挽いたことを通知するチャネル
    beans := 12.0
    fmt.Printf("%.1fgのコーヒー豆をコーヒーミルの中に入れて、豆を挽く\n", beans)
    go GrindCoffeeBeans(beans, gcb)
    GetReady(bw, gcb)
    // コーヒ=を淹れる
    MakeCoffee(water)
    fmt.Println("コーヒーができました")
    return
}

func BoilWater(w float64, c chan float64) {
    // お湯を沸かします
    done := time.Tick(time.Duration(w/2) * time.Second)
    for {
        select {
        case <-done:
            fmt.Println("お湯が沸いたぜよ")
            // お湯が沸いたので通知
            c <- w
            return
        default:
            fmt.Println("グツグツ!")
            time.Sleep(time.Duration(10) * time.Second)
        }
    }
    return
}

func GrindCoffeeBeans(beans float64, c chan float64) {
    estimateTime := beans * 3.0
    // コーヒーミルで豆を挽きます
    done := time.Tick(time.Duration(estimateTime) * time.Second)
    for {
        select {
        case <-done:
            // コーヒー豆を挽き終えたので通知
            c <- beans
            return
        default:
            fmt.Println("ゴリゴリ! バリバリ!")  //  コーヒーを挽く音
            time.Sleep(time.Second)
        }
    }
}

func GetReady(bwc chan float64, gcb chan float64) {
    hotWater := 0.0
    coffeeGrounds := 0.0
    for {
        select {
        case hw := <-bwc:
            hotWater += hw
            fmt.Printf("熱湯: %.1fml 用意しました\n", hotWater)
        case g := <-gcb:
            coffeeGrounds += g
            fmt.Printf("コーヒーの粉: %.1fg 用意しました\n", coffeeGrounds)
        default:
            if hotWater > 0.0 && coffeeGrounds > 0.0 {
                fmt.Println("コーヒーフィルターの端を折ってドリッパーの上に載せる")
                fmt.Println("ドリップする準備が完了")
                return
            }
        }
    }
}

func MakeCoffee(hotWater float64) {
    fmt.Println("コーヒーをドリップします")
    // 泡が出る最大量
    bubble := 1000.0
    initBubble := bubble
    check := time.Tick(20 * time.Second)
    coffee := 0.0
    for {
        select {
        case <-check:
            // 注ぐお湯の量
            pour := 100.0
            fmt.Println("もういいかい?")
            if bubble > initBubble*0.6 && hotWater > 0 {
                // お湯がまだあり、泡がまだ一定以上ある
                fmt.Println("まーだだよ")
                if hotWater >= pour {
                    hotWater -= pour
                    coffee += pour
                } else {
                    coffee += hotWater
                    hotWater = 0
                }
                // 泡が減る
                bubble -= 100.0
            } else {
                fmt.Println("もういいよ")
                time.After(20 * time.Second)
                fmt.Printf("コーヒー: %fmlできました。\n", coffee)
                return
            }
        }
    }
    return
}

実行結果

実行結果は下記の通り。

$ go run main.go
美味しいコーヒーを淹れるぞい!!
ポットに、500.0mlの水を入れる
ポットのスイッチをOnにする
12.0gのコーヒー豆をコーヒーミルの中に入れて、豆を挽く
ゴリゴリ! バリバリ!
グツグツ!
ゴリゴリ! バリバリ!
ゴリゴリ! バリバリ!
ゴリゴリ! バリバリ!
ゴリゴリ! バリバリ!
ゴリゴリ! バリバリ!
ゴリゴリ! バリバリ!
ゴリゴリ! バリバリ!
ゴリゴリ! バリバリ!
ゴリゴリ! バリバリ!
グツグツ!
ゴリゴリ! バリバリ!
ゴリゴリ! バリバリ!
~ 中略 ~
ゴリゴリ! バリバリ!
ゴリゴリ! バリバリ!
ゴリゴリ! バリバリ!
ゴリゴリ! バリバリ!
ゴリゴリ! バリバリ!
ゴリゴリ! バリバリ!
コーヒーの粉: 12.0g 用意しました
グツグツ!
グツグツ!
グツグツ!
~ 中略 ~
グツグツ!
グツグツ!
グツグツ!
グツグツ!
お湯が沸いたぜよ
熱湯: 500.0ml 用意しました
コーヒーフィルターの端を折ってドリッパーの上に載せる
ドリップする準備が完了
コーヒーをドリップします
もういいかい?
まーだだよ
もういいかい?
まーだだよ
もういいかい?
まーだだよ
もういいかい?
まーだだよ
もういいかい?
もういいよ
コーヒー: 400.000000mlできました。
コーヒーができました

解説

GrindCoffeeBeans関数でコーヒーミルでコーヒーを挽く音「ゴリゴリ!バリバリ!」を出すのと同時に、BoilWater関数でボイルでお湯を沸かす音「グツグツ!」が出ていることを確認できただろうか。BoilWater関数とGrindCoffeeBeans関数が同時に並列で動いているためこのような結果になる。

全体の流れを把握するためにmain関数に注目してほしい。

func main() {
    fmt.Println("美味しいコーヒーを淹れるぞい!!")
    bw := make(chan float64) // ポットのお湯が沸いたことを通知するチャネル
    water := 500.0
    fmt.Printf("ポットに、%.1fmlの水を入れる\n", water)
    fmt.Println("ポットのスイッチをOnにする")
    go BoilWater(water, bw)
    gcb := make(chan float64) // コーヒー豆を挽いたことを通知するチャネル
    beans := 12.0
    fmt.Printf("%.1fgのコーヒー豆をコーヒーミルの中に入れて、豆を挽く\n", beans)
    go GrindCoffeeBeans(beans, gcb)
    GetReady(bw, gcb)
    // コーヒ=を淹れる
    MakeCoffee(water)
    fmt.Println("コーヒーができました")
    return
}

"go"がBoilWater関数とGrindCoffeeBeans関数の前に書いてあることが確認できただろうか。7行目ではBoilWater関数を実行するスレッドを作成してすぐに次の8行目を実行する。つまり、BoilWater関数の終了を待つことなく次の行を実行する。 しかし、このままだとBoilWater関数の実行が終わらないうちにreturnまで実行されてプログラムが終了する。それを防ぐためにGetReady関数の中の無限ループでBoilWater関数の終了を待ち受ける。GrindCoffeeBeans関数も同様である。

func GetReady(bw chan float64, gcb chan float64) {
    hotWater := 0.0
    coffeeGrounds := 0.0
    for {
        select {
        case hw := <-bw:
            hotWater += hw
            fmt.Printf("熱湯: %.1fml 用意しました\n", hotWater)
        case g := <-gcb:
            coffeeGrounds += g
            fmt.Printf("コーヒーの粉: %.1fg 用意しました\n", coffeeGrounds)
        default:
            if hotWater > 0.0 && coffeeGrounds > 0.0 {
                fmt.Println("コーヒーフィルターの端を折ってドリッパーの上に載せる")
                fmt.Println("ドリップする準備が完了")
                return
            }
        }
    }
}

GetReady関数の無限ループについて

チャネルからの通知を待ち受ける部分についてここで説明する

    for {
        select {
        case hw := <-bwc:
            hotWater += hw
            fmt.Printf("熱湯: %.1fml 用意しました\n", hotWater)
        case g := <-gcb:
            coffeeGrounds += g
            fmt.Printf("コーヒーの粉: %.1fg 用意しました\n", coffeeGrounds)
        default:
            if hotWater > 0.0 && coffeeGrounds > 0.0 {
                fmt.Println("コーヒーフィルターの端を折ってドリッパーの上に載せる")
                fmt.Println("ドリップする準備が完了")
                return
            }
        }
    }

GetReady関数内の無限ループのなかのselect文では、caseの後に書かれたチャネルbwcやgcbから値が受信されなければ、defalut文の中の

            if hotWater > 0.0 && coffeeGrounds > 0.0 {
                fmt.Println("コーヒーフィルターの端を折ってドリッパーの上に載せる")
                fmt.Println("ドリップする準備が完了")
                return
            }

を実行し続けている。

チャネルから値を受け取った時

お湯が沸いたことを通知するためにチャネル(channel)を用いており、BoilWater関数内で

            // お湯が沸いたので通知
            c <- w

が実行されると、チャネルc(実態はチャネルbw)にwの値が入力される。
すると、GetReady関数の中のcaseが実行され、チャネルbwc(実態はチャネルbw)から変数hwに入力される。

        case hw := <-bwc:
            hotWater += hw
            fmt.Printf("熱湯: %.1fml 用意しました\n", hotWater)

チャネルの挙動や並列処理を理解できただろうか。

感想

就活前にGo言語で通信を含むプログラムを書いて以来、久しぶりにGo言語を扱った。 Go言語で並列処理を実装するのは他の言語と比べてやっぱり楽だと思う。
コーヒーを作りたくなってきたので今日はここまで。

Proxyの影響下のUbuntu 18.04でJupyter Notebook with Keras on TensorFlow on Docker

就活が終わってまた研究を再開した。

いままで、研究用のサーバにCentOS7を使っていたが、研究でDeep Learningを使うことにしたので、NVIDIAのドライバが簡単にインストールできるUbuntu 18.04にOSを変えた。

Dockerfileを訂正したものを下記のリンクにのせました

20180804追記

qiita.com

NVIDIA driverのインストール

下記のコマンドで簡単にNVIDIAGPUを扱うことのできるドライバをインストールできる。

$ sudo ubuntu-drivers autoinstall

ここまでは簡単だった。

しかし、2018/07時点ではまだNVIDIAのCUDAがUbuntu 18.04に対応しておらず、 強引な方法しかなさそうだった。

qiita.com

代替手段: NVIDIA Docker

簡単で、安心して利用できる方法がどこかにないか... どっかにないか、どっか、Docker... ということで、NVIDIA Dockerを利用することにしたw NVIDIA DockerはDockerのラッパーで、DockerコンテナからGPUを扱うことのできる優れものである。詳しくは下記のリンクを参照してください。

NVIDIA Container Runtime for Docker github.com

DockerコンテナのProxyの問題を回避する策: Docker Hub

これでうまくいくように思えたが、私の環境はProxy下であり、Docker自体は使える(docker pullはできる)が、Dockerコンテナの中から外部に通信ができず、docker build ができなかった。色々試したがどれもうまくいかなかった。

しょうがないのでDocker Hubにdocker imageを作成し、そのレポジトリからダウンロードすることにした。

GitHubにレポジトリを作成し、下記のようなDockerfileをそこに置いた。

FROM tensorflow/tensorflow:latest-gpu-py3
MAINTAINER geotaru

RUN apt-get update -y && apt-get upgrade -y && \
apt-get install -y git \
build-essential \
wget \
curl \
graphviz

RUN pip install keras \
numpy \
scikit-learn \
pandas \
scipy \
seaborn \
matplotlib \
plotly \
jupyter \ 
tqdm \
cython \
jupyter_contrib_nbextensions \
pydot \
graphviz \
pydot3 \
pydot-ng \
folium \
RISE


RUN jupyter contrib nbextension install --user && \
mkdir -p $(jupyter --data-dir)/nbextensions && \
cd $(jupyter --data-dir)/nbextensions && \
git clone https://github.com/lambdalisue/jupyter-vim-binding vim_binding &&  \
jupyter nbextension enable vim_binding/vim_binding && \
jupyter-nbextension install rise --py --sys-prefix && \
jupyter-nbextension enable rise --py --sys-prefix

WORKDIR /notebooks

Docker HubのAutomated buildという機能を利用して、GitHubのレポジトリとDocker Hubを連携させた。これにより、GitHubのレポジトリにpushするたびにGitHubのレポジトリのDockerfileの内容のDocker imageがDocker Hub上でbuildされる。

$ docker pull "Docker Hubのレポジトリ名"

でKeras on TensorFlow on Dockerな環境を構築することができた。

下記のレポジトリに作成したものを置きました。

https://hub.docker.com/r/geotaru/keras-notebook/

$ docker pull geotaru/keras-notebook

でダウンロードできます。

docker run --runtime=nvidia -e NVIDIA_VISIBLE_DEVICES=0 --rm -p 8888:8888 -v `pwd`:/notebooks -v `pwd`/data:/notebooks/data -it geotaru/keras-notebook

のようなコード(ご自分の環境に合わせて修正してください)を作成して、 http://localhost:8888/"ターミナルに出力されたtoken"にブラウザでアクセスすれば Keras on TensorFlow on GPUなJupyter notebookを使えます。

感想

現時点20180706でUbuntu 18.04でKeras + GPUの環境構築するのはまだめんどくさい。
ProxyやCUDAの問題が発生したせいで大げさになってしまった気がするが、これはこれで、環境を隔離し持ち運べるという点では良い解決策かなと思う。
Dockerのオーバーヘッドがどれだけあるかわからず、性能がどれだけ低下するか、私、気になります!

参照

How to install the NVIDIA drivers on Ubuntu 18.04 Bionic Beaver Linux linuxconfig.org

Ubuntu 18.04へのCUDAインストール方法 qiita.com

NVIDIA Container Runtime for Docker github.com

Docker Hub の自動構築 Docker Hub の自動構築 — Docker-docs-ja 17.06.Beta ドキュメント

バード電子の木製パームレスト買ってしまった

つい買ってしまった

パームレストの画像
HHKBのパームレスト

裏面画像 BIRD ELECTRONの文字が中央に
裏面 BIRD ELECTRONの文字が中央に

つい手が滑ってというか、深夜のテンションでHHKBのための木製パームレストを買ってしまった。狭い机の上でもなんとかおけるくらいの大きさですごくよい。 底面に貼るグリップは2つのサイズから選べるが自分は高いほうを取り付けた。HHKBとの段差がなくなってなかなかよい。

アルバイト先に持って行って疲れ具合を試そうかな。

メモリが足りない場合に考えるべきこと

まえがき

私は研究で大きなデータセットを用いてプログラミングすることがよくある。
データのサイズが大きいとRAMの容量が32GBのサーバだとメモリが足りなくなる。そんな時に考えるべきことをまとめておく。

メモリが足りない場合の対処手順

  1. メモリを買う。スケールアウトできるように最初からプログラムを作成しているなら、マシンを買い足す。
  2. 予算などの都合で1が実行できない場合、プログラムを見直す。

1についてだが、最も簡単で小さいリスクでメモリ不足を解決できる。しかも早い。2についてだが、すでに動いているプログラムをいじるため、リスクがある。しかし、場合によっては少ないコストでメモリの削減が実現できるから、これも考えるべきである。

プログラムの見直し

精度の変更

精度を変更する。具体的には64bitの表現を32bit表現に変更する。Pythonのnumpyであれば、

import numpy as np

array = np.arrray([1, 2, 3])
array32 = array.astype(np.float32)

のように変更できる。 これができるのは機械学習のように数値の厳密な精度を要求されない場合だ。しかも、これはリスクが小さく実現でき,かつ効果的だ。具体的には64bitから32bitに変更した場合は、単純に計算して消費するメモリが半分で済む。

メモリを無駄に確保していないか

例えば巨大な2次元配列を行ごとに処理する必要があるとする。このとき2次元配列を全てメモリに格納する必要はない。一行ずつ処理すればメモリ消費量は小さくすることができる。この方法はメモリ消費量を抑えることができるが、配列すべてをメモリに入れる場合より処理時間が増える場合があるため、よく考えてからプログラムを変更するべきである。

あとがき

メモリが足りないなら、メモリを増設するべきだ。
メモリが足りなくてもハードウェアでなんとかできるように最初からスケーラブルなフレームワーク(Spark)などを使ってプログラムを組むべきだ。プログラムの変更は時間を消費するし、プログラムが動かなくなるリスクもある。研究をしていてそう感じた。