経験は何よりも饒舌

10年後に真価を発揮するかもしれないブログ 

JSのオブジェクトの分割代入で既定値が割り当てられるのはundefinedの場合のみ

「JSのオブジェクトの分割代入で既定値が割り当てられるのはundefinedの場合のみ」ということは、MDNのAssigning to new variable names and providing default valuesAssigned a default value in case the unpacked value is undefined.と書いてあることや、13 ECMAScript Language: Expressionsの13.15.5.3 Runtime Semantics: PropertyDestructuringAssignmentEvaluation4. If Initializeropt is present and v is undefined, thenとあることから分かる。

ここからNode.jsのfs.readを使ってみていく。

node/lib/fs.js#L605~ に以下のコードがある。

function read(fd, buffer, offsetOrOptions, length, position, callback) {
...
      } else if (arguments.length === 3) {
      // This is fs.read(fd, bufferOrParams, callback)
        if (!isArrayBufferView(buffer)) {
        // This is fs.read(fd, params, callback)
          params = buffer;
          ({ buffer = Buffer.alloc(16384) } = params ?? kEmptyObject);
        }
        ...
...
   ({
      offset = 0,
      length = buffer.byteLength - offset,
      position = null,
    } = params ?? kEmptyObject);
...
   validateBuffer(buffer);
...

例えばfs.read(fd, {offset: 1}, callback)というように呼び出すと、
params = {offset: 1}
({ buffer = Buffer.alloc(16384) } = {offset: 1}
buffer = Buffer.alloc(16384)
というように処理される。

もしfs.read(fd, {buffer: null}, callback)というように呼び出すと、
params = {buffer: null}
({ buffer = Buffer.alloc(16384) } = {buffer: null}
buffer = null
というように処理される。
この場合、validateBufferが呼びされる前にlength = buffer.byteLength - offsetbuffer = nullが参照されてしまう。

これを解決したのがこのPR。
github.com

ちなみにdeno_stdは現時点でNodeのv18.8.0との互換を保っているため、以下のように意図的にnullを参照するようにしている。
github.com

// @ts-ignore: Intentionally create TypeError for passing test-fs-read.js#L87
length = opt.buffer.byteLength;

RFC8259 の「Implementations MUST NOT add a byte order mark to the beginning of a networked-transmitted JSON text」について

プログラマのための文字コード技術入門』の p.216 にある、
JSONでは、データ先頭にBOMをつけないことが求められています(RFC8259)」
についてちょっとだけ詳しく調べる。

RFC8259の該当箇所は8.1. Character Encodingで、
「Implementations MUST NOT add a byte order mark (U+FEFF) to the beginning of a networked-transmitted JSON text」
とある。
続いて
「In the interests of interoperability, implementations that parse JSON texts MAY ignore the presence of a byte order mark rather than treating it as an error」
とあるように、相互運用性の観点から、BOMがあればエラーとして扱うのではなく、無視してもいいらしい。

BOM(Byte Order Mark)とは、ビッグエンディアンかリトルエンディアンのどちらのバイト順を採用しているかを示すために、データの先頭に付ける印のこと。
U+FEFFの符号位置を用い、ビッグエンディアンではFE FF、リトルエンディアンではFF FEという2バイトの列になる。
符号位置は、文字コード表の中の位置のこと。
ビッグエンディアンは、上位8ビットが先頭にくるバイト順、リトルエンディアンは、下位8ビットが先頭にくるバイト順のこと。
U+FEFFU+は、Unicodeの符号位置を表すのに付ける接頭辞。

16ビットのデータを8ビット単位のバイト列にするときには、ビッグエンディアンかリトルエンディアンのどちらを採用するかの問題がある。
Unicodeの符号化方式の中の1つであるUTF-16は、16ビット単位であるためこの問題が発生し、解消するためにBOMを付けることがある。

JSONでBOMを付けないとなると、バイト順の区別はどうするのだろうと思ったところ、同じ RFC8259 8.1. Character Encoding にこう書いてあった。
JSON text exchanged between systems that are not part of a closed ecosystem MUST be encoded using UTF-8
UTF-16を扱わないのにRFCなぜBOMに言及しているのだろうと思ったところ、『プログラマのための文字コード技術入門』の p.154 にこう書いてあった。
「バイト順が問題になることのないUTF-8にはBOMは本来関係がありません。ところが、BOMをUTF-8で表現した3バイトの値(EF BB BF)が、UTF-8のデータ列の先頭についていることがあります。バイト順の印という元々の意味を離れて、UTF-8のデータであることの印として利用価値があるという考えもあります。」

ネットワーク転送されるJSONにはその利用価値は必要なく、相互運用性に支障をきたすから、「Implementations MUST NOT add a byte order mark to the beginning of a networked-transmitted JSON text」とされている。



BOMに関する実際の問題の例として、node-fetchのHandling a BOM with .json() #541というissueにある、レスポンスをJSONとして取得する際にエラーが起こるという問題がある。
node-fetchの`.json()`には内部的に`JSON.parse`が用いられており、引数のtextにBOMが含まれていれば、そこでエラーが生じてしまう。JSONの構文はRFC8259に準拠しているからだ。
ユーザー側の解決例としてはここにあるように、textとして取得してから、BOMに当たる0xFEFFを除去し、JSONにする例がある。
node-fetch側のリリースされている解決法は、fix: handle bom in text and json #1482にあるように、内部でbuffer.toString()ではなくTextDecoder().decode()を使うようにしている。
buffer.toString()では、BOMに当たる0xFEFFがあったとしてもそのまま文字列化してしまい、それをJSON.parseするためエラーが生じる。
TextDecoder()のパラメータであるutfLabelのデフォルトは"utf-8"
そして重要なのがデフォルトがfalseTextDecoder.ignoreBOM
命名がややこしいが、TextDecoder.prototype.ignoreBOM not working as expectedの回答に
「If on the other hand you want it to be removed from the output, then leave it as false, the parser will treat it specially, and remove it from the output」
とあるように、TextDecoder().decode(buffer)の結果にはBOMが含まれなくなり、問題なくJSON.parse(text)ができる。


このように目に見えないBOMと戦ってJSONが生成される例がある。

野良コミッターがOSSの仕様を決める

OSS活動を始めて今まで157PRがCloseされた。翻訳やtypoの修正やテスト、ドキュメントの整備も含まれるけど、野良コミッターでも仕様を決める、もしくはそれに近いことができたのでいくつかまとめておく。

新しい仕様を追加する

このスライドに詳しく書いた

github.com


配列操作のreducecurrentIndexを使えるようにした

github.com

既存の仕様を変更する

いわゆるbreaking changeに当たるが、注意しないとバグを埋め込むことになる
wafuwafu13.hatenadiary.com

github.com

開発のプロセスを改善する

lintが通らなかったらマージできないようにした
github.com

ややこしいけどこれも開発のプロセス改善
wafuwafu13.hatenadiary.com

開発の方針を決める

denoland/deno_std/node/_tools/testに生成されるNodeのバージョンを上げた
github.com

denoland/deno_std/pathdenoland/deno_std/node/pathの関係を整理して進めていった
github.com


こんな感じでorgに所属してない、コミッター認定されてない野良コミッターでも頑張れば割と大きい部分の仕様に関わることができる。
GitHubでの活動はGitHubの登場はコミッタを特権階級から追い落とす革命だった - Hello, world! - s21gのタイトル通り、何の資格も持っていない文系大学生にとっては飛び道具だった。

日本の方のSocket migration for SO_REUSEPORTという発表を知って、こういうことができるようになりたいと思った。
こういうこととはどういうことかを考えると、世界の仕様を決めるということで、今まで自分が何をできてきたのかをまとめたくなって書いた。
逆に何が足りないかを再認識できてきたので、それを踏まえて活動を進めていく。

機械学習の知識・2022春

  • 数IA,IIBはセンター試験8~9割とってた記憶
  • 数IIIは以下のように独学した

wafuwafu13.hatenadiary.com

  • 以下の本は何周もして理解を進めてきた

wafuwafu13.hatenadiary.com

qiita.com
qiita.com

  • OSS活動の指標の一つとしてInterested in AI Frameworksを掲げ、まずはGiNZAにコミットした

github.com
wafuwafu13.hatenadiary.com

scrapbox.io

  • 統計検定1級に落ちたことがある

wafuwafu13.hatenadiary.com

  • コンペに参加する予定

www.nishika.com

  • 英語: 論文読んだりはまだできていない スピーキングはDMM英会話で修行中

wafuwafu13.hatenadiary.com

  • 以下の本は何度か理解しようと試みてきたが進捗はイマイチ

  • その他読んで面白かった本

人工知能のための哲学塾

人工知能のための哲学塾

Amazon

  • 積読: 数学力とKaggleでメダルを取る力をつけたい 卒論の一環でSNSのテキストアナリティクスをする予定

GiNZAにコミットしながらNLP Libraryの勉強

環境構築

GiNZA - Japanese NLP Library | Universal Dependenciesに基づくオープンソース日本語NLPライブラリを眺めてとりあえず$ pip install -U ginza ja_ginzaを試してみると以下のエラー。

  raise VersionConflict(dist, req).with_context(dependent_req)
       pkg_resources.VersionConflict: (setuptools 49.2.1 (/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/site-packages), Requirement.parse('setuptools>=58.0'))
       [end of output]
   
   note: This error originates from a subprocess, and is likely not a problem with pip.
 error: subprocess-exited-with-error
 
 × Getting requirements to build wheel did not run successfully.
 │ exit code: 1
 ╰─> See above for output.

setuptoolsのバージョンの問題っぽかったから$ pip install setuptools --upgradeしてから$ pip install -U ginza ja_ginzaやり直しても同じエラー。

 $ pip install setuptools --upgrade
 Defaulting to user installation because normal site-packages is not writeable
 Requirement already satisfied: setuptools in /Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/site-packages (49.2.1)
 Collecting setuptools
   Using cached setuptools-62.1.0-py3-none-any.whl (1.1 MB)
 Installing collected packages: setuptools
 Successfully installed setuptools-62.1.0


仕方ないから https://pypi.org/project/setuptools/#filesからダウンロードしてインストールしてみる。良さそう。

 $ sudo python3 setup.py install
 Installed /Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/site-packages/setuptools-62.1.0-py3.8.egg
 Processing dependencies for setuptools==62.1.0
 Finished processing dependencies for setuptools==62.1.0
 
 /Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/site-packages 

元から入っていたsetuptoolsは削除する。

/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/site-packages
$ sudo rm -r setuptools

これで$ pip install -U ginza ja_ginzaは成功する。

コミット

https://github.com/megagonlabs/ginza#development-environmentに沿って環境構築をして、コードを眺めて気になったところに修正コミット。
github.com
github.com

https://github.com/megagonlabs/ginza#run-testsに沿ってテストを走らせてカバレッジを上げるコミット。
github.com

split_mode深堀

既存のコマンドラインのテストによるとsplit_modeA, B, Cを選択でき、それぞれ以下のように形態素解析がされる。

("A", "機能性食品", ["機能", "性", "食品"]),
("B", "機能性食品", ["機能性", "食品"]),
("C", "機能性食品", ["機能性食品"]),

さらにコードを追っていくと、split_modeをセットしている箇所が2箇所ある。


1箇所目では、output_formatmecabが指定されている場合、SudachiPySplitModeの値が設定される。

https://github.com/megagonlabs/ginza/blob/d60d3d4bf4af0c5879357a5f95b36f72ea8eb317/ginza/analyzer.py#L67-L68
https://github.com/megagonlabs/ginza/blob/d60d3d4bf4af0c5879357a5f95b36f72ea8eb317/ginza/analyzer.py#L23-L28

def try_sudachi_import(split_mode: str):
    """SudachiPy is required for Japanese support, so check for it.
    It it's not available blow up and explain how to fix it.
    split_mode should be one of these values: "A", "B", "C", None->"A"."""
    try:
        from sudachipy import dictionary, tokenizer

        split_mode = {
            None: tokenizer.Tokenizer.SplitMode.A,
            "A": tokenizer.Tokenizer.SplitMode.A,
            "B": tokenizer.Tokenizer.SplitMode.B,
            "C": tokenizer.Tokenizer.SplitMode.C,
        }[split_mode]
        tok = dictionary.Dictionary().create(mode=split_mode)
        return tok
...
def set_nlp(self) -> None:
        if self.output_format in ["2", "mecab"]:
            nlp = try_sudachi_import(self.split_mode)
...

SudachiPyの実装をさらに追うと、modeとして渡され、以下のように分岐される。

https://github.com/WorksApplications/SudachiPy/blob/6fb25bd20e206fc7e7e452b2dafdb2576dcf3a69/sudachipy/tokenizer.pyx#L127
https://github.com/WorksApplications/SudachiPy/blob/6fb25bd20e206fc7e7e452b2dafdb2576dcf3a69/sudachipy/tokenizer.pyx#L172-L180

def tokenize(self, text: str, mode=None, logger=None) -> MorphemeList:
        """ tokenize a text.

        In default tokenize text with SplitMode.C

        Args:
            text: input text
            mode: split mode
       ...
       """
...
def _split_path(self, path: List[LatticeNode], mode: SplitMode) -> List[LatticeNode]:
        if mode == self.SplitMode.C:
            return path
        new_path = []
        for node in path:
            if mode is self.SplitMode.A:
                wids = node.get_word_info().a_unit_split
            else:
                wids = node.get_word_info().b_unit_split

LatticeNode#get_word_infoWordInfoを返す...と続いていくが詳細には追えなかった。
WordInfoのコンストラクタにa_unit_splitb_unit_splitがあることまで確認できた。

https://github.com/WorksApplications/SudachiPy/blob/6fb25bd20e206fc7e7e452b2dafdb2576dcf3a69/sudachipy/latticenode.pyx#L79-L84

...
cdef class LatticeNode:
...
    def get_word_info(self) -> WordInfo:
        if not self._is_defined:
            return UNK
        if self.extra_word_info:
            return self.extra_word_info
        return self.lexicon.get_word_info(self.word_id)


2箇所目では、spaCyで何かしらのモデルがロードされた後、set_split_mode(nlp, self.split_mode)が呼び出される。

https://github.com/megagonlabs/ginza/blob/d60d3d4bf4af0c5879357a5f95b36f72ea8eb317/ginza/analyzer.py#L69-L86

...
def set_nlp(self) -> None:
...
        else:
            # Work-around for pickle error. Need to share model data.
            if self.model_name_or_path:
                nlp = spacy.load(self.model_name_or_path)
            else:
                try:
                    nlp = spacy.load("ja_ginza_electra")
                except IOError as e:
                    try:
                        nlp = spacy.load("ja_ginza")
                    except IOError as e:
                        raise OSError("E050", 'You need to install "ja-ginza" or "ja-ginza-electra" by executing `pip install ja-ginza` or `pip install ja-ginza-electra`.')

            if self.disable_sentencizer:
                nlp.add_pipe("disable_sentencizer", before="parser")

            if self.split_mode:
                set_split_mode(nlp, self.split_mode)
...

https://spacy.io/api/language#factoryによると@Language.factoryLanguage.add_pipeができて...みたいな説明があった。

https://github.com/megagonlabs/ginza/blob/d60d3d4bf4af0c5879357a5f95b36f72ea8eb317/ginza/__init__.py#L48-L54
https://github.com/megagonlabs/ginza/blob/d60d3d4bf4af0c5879357a5f95b36f72ea8eb317/ginza/__init__.py#L111-L114

@Language.factory(
    "compound_splitter",
    requires=[],
    assigns=[],
    retokenizes=True,
    default_config={"split_mode": None},
)
...
def set_split_mode(nlp: Language, mode: str):
    if nlp.has_pipe("compound_splitter"):
        splitter = nlp.get_pipe("compound_splitter")
        splitter.split_mode = mode

compound_splitterginza/compound_splitter.py at develop · megagonlabs/ginza · GitHubがあるからおそらくカスタム関数をパイプラインに追加してそうだけど、まだPipelinesがよくわかってないから勉強してから出直す。
spacy.io
www.youtube.com



とりあえずNLP Libraryの実装の雰囲気が掴めたから終わり。

TOEIC 675 から 825 に上げた


1年3ヶ月前は市販模試を3回分やって受けて675(305/370)だった。
今回は真面目に対策をして受けてみることにした。勉強期間は半日(3時間くらい)を1ヶ月半くらい。

リスニング対策は「極めろ!リスニング解答力TOIEC L&R TEST」をした。
692ページあるから忍耐力が必要だった。

リーディングのPart5対策は「TOIEC L&Rテスト文法でる1000問」をした。
1000問解く必要があったのかはわからない。

Part6,7 は英文を読むことに慣れる必要がありそうだったから「英文解釈教室」を読んだ。
文法のことが大体わかる良書。

長文を読むことに慣れるために「英文 詳説世界史 WORLD HISTORY for High School」を読んだ。
日本語の教科書があったから英語->日本語(流し読み)をした。世界史の復習にもなるから一石二鳥。

あとはSRE本の英語版が公開されているからこれも日本語と合わせて読んだ。

sre.google

仕上げに市販の模試「新メガ模試1200問」を解いた。
スコアは1問5店換算で750(390/360) -> 745(425/320) -> 810(425/385) -> 790(400/390) -> 845(420/425) -> 800(415/385) だったから本番で思ったより取れてた。




リーディングは時間が余って自信があったけどそこまで伸びていなかった。
リスニングは単に問題の形式とか傾向に慣れて伸びた部分が多いと思う。
ライティングとスピーキングの学習動機としてTOEFLを検討してるけど3万円に見合うかどうか...

円安が過ぎる