苦戦しながら統計検定2級に合格した

統計検定2級に合格したのだけど、ググると出てくるような「合格する方法!」のようにはいかず大変でした、という日記です。

きっかけ

統計学をちゃんと勉強したことがなく苦手意識がとても強かったのです。過去に簡単そうな書籍を読んだことはありましたが、なんとなく分かった気になるが何も分かっていない、という状態のままであり苦手意識はずっと残ったままでした。

統計検定を受けようと思ったきっかけは、ソフトウェアの品質についてもっと工学的にアプローチできるようにしたいなぁと書籍や論文をチラ見している中で統計学の話が出てくると読み進めることができず、「これは苦手意識を克服する必要があるぞ」と決心し勉強を始めました。

どれくらい勉強すれば良いのか見当がつかなかったので、資格試験に合格するというのが分かりやすいと思いマイルストーンとして置きました。

結果

4月5月に結構勉強しました。過去問を大体解けるようになってきたので6月に受験。しかし、時間が全然足りない・・・!そう、ちゃんと時間を測って通しで過去問をやっていなかったのです。こんなに時間が足りないとは・・・。まぁいけるんじゃないかなぁと思っていたのでショックを受けていったん勉強を止めてしまいました。

しかし、10月中旬になり、勉強を止めてしまったことにモヤモヤし始めてきたので、もう一度チャレンジすることにしました。ここでグッと理解度が深まったような気がしています。そして11月に合格!!

このように、一度挫折したのですが最終的には持ち直しました。はー、よかった。

勉強したこと

まず 数学ガールの秘密ノート/やさしい統計 を読みました。平均や分散、標準偏差、期待値という言葉の意味、それから記述統計と推測統計の違い、これらのことを理解しました。過去に別の本で読んで分かった気になってちゃんと分かっていなかったことの輪郭が徐々に見え始めてきました。本の最後の方で仮説検定の話題が出てきますが、よく分からないまま読み飛ばしました。

次に、改訂版 日本統計学会公式認定 統計検定3級対応 データの分析 を読みます。受験するのは2級ですが、まずは3級の公式テキストで勉強を始めました。練習問題を解きながらじっくりと。条件付き確率とベイズの定理のところが若干引っかかるものの練習問題はとりあえず解けるという感じ。区間推定と仮説検定もなんとなくこんな感じかなーというところで先へ進みます。

本じゃなくて 統計Web というサイトの「Step1. 基礎編」をやります。統計Webには本当にお世話になりました!過去問を解いていてなんだっけ?となったら読み直すというのを何度も何度もやりました。スマホでも見れるのが良い!

そして、日本統計学会公式認定 統計検定 2級 公式問題集[2018〜2021年]。2級の過去問集です。上記の3つを一通り読み通りしてから過去問をやりました。理解が浅いところは復習して解き直します。

ここまでで過去問は大体解ける!と思ったのですが、先述した通りいざ試験を受けると時間が全く足りず不合格。一度やる気を失いしばらく時間が空きます。

再度勉強しよう!となって読み始めたのがHead First Statistics ―頭とからだで覚える統計の基本です。オライリーなら良さそう、しかも Head First C が良かった思い出があるのでこれも良いはず、という気持ちでなんとなく読み始めました。この本は推定と仮説検定については入り口くらいまでしか書かれていないのですが、確率と確率分布、期待値、分散といった話題がとても丁寧に書いてあり、やる気を失う前に勉強したことを思い出しつつさらに理解を深めるのにとても良かった。ちなみに、この本で知った確率木のおかげで条件付き確率とベイズの定理はすごく分かるようになりました。

そこから再び過去問と統計Webを行ったり来たりするわけですが、参考書籍として統計学入門 (基礎統計学Ⅰ)も読み始めます。この書籍は通称赤本として色々なサイトでも紹介されてました。どうにも腹落ちしないなぁという部分はこの赤本をじっくり読むとなるほどねー!となりました。

おしまい

最初の不合格でやる気を失ったあと諦めなくてよかったです。合格するほどの知識は身についていなかったけど、なんかすごい、面白いぞ、という感情が芽生えるくらいには学習できていたのが良かったのかもしれません。苦手意識があることを補強できるかどうかは、チャレンジし始めてから頑張れている間に何となく面白そうだぞとポイントを見つけられるかどうかが大事なのかもしれないな〜。

WebブラウザでCASL2を試せるCASL2 Playgroundをつくってみた

この記事は個人開発 Advent Calendar 2021の7日目の記事です!

CASL2とは基本情報技術者試験のために策定されたアセンブラ言語です!そのCASL2をWebブラウザ上で書いて実行できるWebサービスをつくりましたという日記です。

github.com

なぜつくったのか?

自分が欲しいとかこういう人に使って欲しいという理由ではなく、自分の勉強のためにつくりました。勉強のための車輪の再発明

今年の2月の記事ですが、「計算機プログラムの構造と解釈」という本を読み始めました。

SICPを読み始めた - yo-kari の 日記

読み終えたんですけど最後の章「5 レジスタマシンによる計算」がうーむ。。という感じだったので似たようなものつくってみようと思ったのがきっかけです。

アセンブラ言語に全然馴染みがなくパッと思いついたのがCASL2だったので、Webブラウザ上で試せるようなツールをつくってみるか!とTypeScriptで実装しました。UIはJavaScriptでDOMをいじいじ...

「計算機プログラムの構造と解釈」のSchemaで実装されたアセンブラを元に、TypeScriptで実装していく形で実装を始めました。途中で家で積ん読になってた「言語実装パターン」という書籍を見つけこちらも途中から参考にします。最初から読んでいれば...という気持ちになるところもあり、すでに作り直したい部分がちらほら。

「計算機プログラムの構造と解釈」は、先日のブログ記事でも紹介させていただきましたが、hiroshi-manabeさんによる翻訳を読みました。

github.com

「言語実装パターン」は、O'Reillyの本です。

開発メモ

アセンブラってなんですか?

「計算機プログラムの構造と解釈」でのレジスタマシンとアセンブラの記述を自分なりのざっくり理解を書くと

  • レジスタと呼ばれる記憶素子の中身を操作する命令を順に実行するコンピュータがレジスタマシン
  • レジスタマシンを設計するためには、レジスタと演算を表すデータパス、それらの演算を正しく並べるコントローラが必要で、図で書いて表現できる
    • 「計算機プログラムの構造と解釈」では最初の例として最大公約数を求めるアルゴリズムを実現するレジスタマシンのデータパスとコントローラについて書かれてる
  • だけど複雑になってくるとすぐに図で記述できなくなる
  • そこで、レジスタマシン用言語で作って記述しよう。この言語(テキスト形式)を機械語(バイナリ形式)に変換するプログラムがアセンブラ

今回つくろうとしてるものに当てはめると、CASL2という言語をCOMMET2(基本情報技術者試験のために設計された仮想のコンピュータ=レジスタマシン)の機械語に変換し実行する、ということになる。

Image from Gyazo

「言語実装パターン」ではアセンブラは、「パターン26 バイトコードアセンブラ」の章で

人間によって読みやすいテキスト形式でできたアセンブリ言語プログラムをバイナリ形式のバイトコード命令へと変換します。

と同様のことが記述されてる。

さらに「言語実装パターン」では、アセンブラは4つの重要要素があるとも記述されている。

  1. 大域データ空間の大きさ
  2. コードメモリ
  3. mainプログラムのアドレス
  4. 定数プール

大域データ空間はメインメモリのことで、COMMET2のメインメモリは、1語16ビットで65536語の容量です。コードメモリというのは、メインメモリにアセンブラによって生成されたバイナリ形式の機械語そのものが格納される、ということ。mainプログラムのアドレスは、メインメモリのどのアドレスがプログラムの開始位置なのかを示す。定数プールは、文字列や浮動小数点、関数名などのオペランドをコードメモリ内に収まらないので別のアドレスに保管することを示していて、CASL2で言うとソースコードに記述されるラベルが該当します。

つまり、テキスト形式のソースコードから変換したバイナリ形式形式の機械語も定数も同じメインメモリ上に置かれる。

と、ここで気がつく。「計算機プログラムの構造と解釈」を読んでいてあまり腹落ちしなかったのは、この機械語とメインメモリとが具体的に出てこなかったからなのでは。メモリもリストとGCのところで抽象化してたからかもしれないなぁ。「計算機プログラムの構造と解釈」の 5 レジスタマシンによる計算 の書き出しを読み直すと以下のように書いてある。

典型的なレジスタマシンの命令は、レジスタの中身に基本的な演算を適用し、その結果をほかのレジスタに割り当てるというものです。レジスタマシンにより実行されるプロセスをこのように記述すると、従来式コンピュータの"機械語"に非常によく似ているように見えます。しかし、ここではある特定のコンピュータの機械語を扱うのではなく、いくつかのLisp手続きについて調べ、それぞれの手続を実行するための個別のレジスタマシンを設計していくことにします。そのため、課題に対する私たちのアプローチは、機械語プログラマというよりも、ハードウェア設計者としての視点によるものとなります。

「ある特定のコンピュータの機械語を扱うのではなく」のせいでかえって取っ付きづらくなっている気がする。ハードウェア設計者としての視点...ワカラン...

ラベルの取り扱い

CASL2のソースコードを見ていて、普段のプログラミング言語と明らかに違うなぁと思うのはラベル。ソースコードのとある行にラベルづけをしておく。if文で条件分岐するようなことをしたい場合は、条件を満たす場合はラベルAにジャンプ、条件を満たさない場合はラベルBにジャンプというように使う。他にも関数呼び出しのようなことをしたい場合もラベルを使うし、定数名のようにラベルを使うこともできる。

「計算機プログラムの構造と解釈」では以下のように記述されている。

アセンブラは、命令実行手続きを生成する前に、ラベルの参照先をすべて知っておく必要があります。

これは分かるな。例えば以下のようなコードがあったとする。 ADDAレジスタ GR1 とラベル TWO のアドレスにセットされている数値を加算する、というコード。ラベル TWO に数値 2 がセットされる。

SAMPLE01 START
         LAD     GR1,1
         ADDA    GR1,TWO
TWO      DC      2
         END

このソースコードを上から順に解析すると、3行目時点ではラベル TWO がまだ登場していないため、存在するラベルなのか?どの位置を指しているのか?ということが分からない。だから 命令実行手続きを生成する前に、ラベルの参照先をすべて知っておく 必要が出てくる。

まだ登場していないラベル(や関数名や変数や色々)を参照することを前方参照と言うらしい。

ラベルの前方参照とbackpatching

実装としては入力となるソースコードを行ごとでループして走査、 labelMap = Map<Label, Array<行>> というMapにラベル毎に集約しておく。集約する過程で各行の命令とオペランドからバイト数が分かるのでラベルのメモリアドレスも分かる。集約後、Mapの全エントリをループすることで各行をもう一度走査することで全てのラベルにセットされた値も把握しながら解析できる。

ただこれ、ソースコード全体を2回ループしてるし、同じようなやってるような感じもするけどどうなのか。

「言語実装パターン」の パターン26 バイトコードアセンブラ を読んでみる。アセンブラの変換のパターンとしては「パターン29 構文手動変換器」であると書かれている。ここで言う構文手動変換器とは、入力1行を読んだら変換先の1行を生成する、というもので前方参照を扱えないパターンと書かれている。

ラベルの前方参照ができないと困るじゃないか。

解決策として backpatching という方法が紹介されている。

Labelを表すオブジェクトに isForwardRef フラグというのを用意する。

type Label = {
  label: string
  memAddress: number
  isForwardRef: boolean
}

さらに、「後から前方参照を解決すべき被演算数のリスト」を forwardRefs = Map<string, Array<行>> で管理する。

const forwardRefs = Map<string, Array<>>

先程のCASL2コード例をもう一度。

SAMPLE01 START
         LAD     GR1,1
         ADDA    GR1,TWO
TWO      DC      2
         END

この例だと、アセンブラは入力となるCASL2ソースコード3行目で TWO というラベルを見つけたときに、以下のようにラベルMapとforwardRefsに追加する。

if (!labels.has("TWO")) {
  // 前方参照されているラベル(isForwardRef = true)としてラベルMapに追加
  labels.set("TWO", { label: "TWO", memAddress: 0, isForwardRef: true })
}

operandPos = ...
bytecodeList[operandPos] = ... // bytecodeListは最終的に出力する機械語であるバイト列

if (labels.get("TWO").isForwardRef) {
  // "TWO" を前方参照してるオペランドの位置を追加
  if (forwardRefs.has("TWO")) {
    forwardRefs.get("TWO").push(operandPos)
  } else {
    forwardRefs.set("TWO", [operandPos])
  }
}

4行目で TWO ラベルが定義されると、「後から前方参照を解決すべき被演算数のリスト」を走査して修正する。

// ラベル "TWO" のアドレス
currentMemAddress = ...

if (!labels.has("TWO")) {
  labels.get("TWO").memAddress = currentMemAddress
  labels.get("TWO").isForwardRef = false
}

...

forwardRefs.get("TWO").forEach(operandPos => {
  bytecodeList[operandPos] = currentMemAddress
})

こういうイメージか。たしかにソースコードの行すべてをもう一度走査することに比べたら「後から前方参照を解決すべき被演算数のリスト」を走査した方が効率良さそう。

構文手動変換器パターンのように、ソースコード全体を1回だけ走査することを1パスと呼ぶみたい。1パスは単純だけど、そのままだと前方参照ができない、入力と大きく並び順が異なる出力を生成できない、といったデメリットがある。1パスに対して、そうでないものをマルチパスと呼ぶらしい。

おわりに

学びたいポイントが次から次へと出てきて困るけど楽しいという感じでした!

自分の実装は、アセンブラとして機械語を生成する処理と、Commet2上で動きをシミュレートするための関数と、両方をごちゃっと実装してしまっている感じがあって分かりづらい。Commet2で動かす部分については「言語実装パターン」に書かれている パターン28 レジスタ方式バイトコードインタプリタ を参考しながら機械語を入力とするインタプリタとして作り直すと良さそう。

他にもbackpatchingで書き直したり、全然別の構文手動変換器ではない変換器をつくってみたりもしたいなぁ。

コーチングを受けてみた

元同僚と話していたら、いつのまにか(セミプロ?)コーチになっていたのでコーチングしてもらいました!という日記です。

急だったにも関わらずやってくれて @katsutomu に感謝です!

初めての経験だったのでどんな感じなんだろ?という気持ちで受けたのですが、結果的に気がついたことが多くありとても楽しく有意義な時間でした。コーチのスキルが良かったからなのか元々よくコミュニケーションをとっていた元同僚だったからなのか、自分自身の本音がぽろぽろ口から出てくる感じでとても良かったです。逆に言うと関係値が構築できていないコーチの場合はどういう風に進むのかなぁと気になりました。まずは関係を深めるところから徐々に進めていくのだろうか。

良かったところ

印象に残ってるのは、「コーチと私」と「私自身」の対話、というイメージですよと最初に説明してくれたところ。

このイメージを最初にできたことが本音で話す助けになっていたと思う。普段誰かとコミュニケーションを取る時には、相手と自分自身の間に「外向け用の自分」がプロキシサーバーのように機能していて、そのプロキシが相手に嫌な思いをさせてしまいそうな自分自身の本音を遮ったり逆に相手からの発言の意味を組み替えてしまって自分自身に伝えたりしているよなぁと。コーチングの時間は自分自身をコーチにさらけ出す時間だよと最初に説明してくれたことが良かったのかも。

もう1つ上記のイメージが良かったのは、自分自身と対話するのが自分だけではないところ。1人で内省するときなんかは「自分と自分自身」の対話になるけれど、その対話の中では色々なことを思いつくがスッと流してしまうこともある。自分では流してしまった考えをコーチが拾い上げてくれることで、自分の中に強く印象づけることができて良かった。新しいことを思いつくというよりは「昔も考えたことあったけどあんまり重要視してなかったなぁ、けどこれめっちゃ大事なことじゃん!」というような気づき。

ピンと来ていないところ

今回はお試しというか急だったので1回だけコーチングしてもらったけど、あんまり分かっていないのが本来はある程度の期間で複数回やった方が良いものになるんだろうなぁというところ。コーチングで色々得られるものはありつつも行動しないと意味ないし、行動の結果を改めてコーチと話すべきなのだろうと思う。コーチングの中でどういうアクションをしましょうか?という話があったけど、そのアクションを取ることがとても重いと感じる場合は次のコーチングの予定まで決めてしまったほうが良さそう。締め切り駆動的な。

おわりに

というわけでコーチングを受けていた時間はとても楽しかったです。自分自身との対話が好きだったり、自分の思考の仕方に興味があったり、本当の価値観を知りたい、みたいな人にとっては楽しいのではないでしょうか?こうやって書いてみると、解決したい何かにぶつかった時に自分のことをよく知ってることって大事なんだな〜。