2021年5月29日土曜日

フリゲ紹介: 忍屋

忍屋 は不思議な力を持った14歳の少女が、なんでも屋の「忍屋」で働きながら、母親に会うため奮闘するお話です。 和風ステルスアクションゲームで、クリアまでのプレイ時間はちょうど 5 時間でした。



最近は凄い作品がガンガン出てきていますが、今回紹介する 忍屋 も制作 12 年という大作です。 何が凄いって、ドットがすごい…。 プロ方面でも活躍されている方のようで、ありとあらゆるドットがとにかく細かい。 キャラごとに多種多様なドット絵が用意されている上、ステージの雰囲気がすごい。これは作るの大変だろうなあ (下)。



操作感のほうは割とクラッシクなステルスアクションで、 アクションはバトルをするためにあるというよりは、ハック&スラッシュするためにある印象です。 忍者らしく敵に見つからないように物陰に隠れて、首を絞めて落とす! だいたいこれでいけます (下)。



ハック&スラッシュが非常に面白い作品なのですが難点が一つあって、普通のキーボードでは中盤から厳しいです。 一般的に普通のキーボードは同時に3つ以上のキーを押せない問題があります。 忍屋 は慣性ジャンプができないため、ダッシュ状態を維持したジャンプができないんですねえ。 ゲーミングキーボードやコントローラを買う手もあるのですが、どうしてもすぐにプレイしたくて無理やり何とかする方法を考えました。

結果、なんとかなりました。

まず AutoHotkey というアプリをインストールしてください。 その後、以下のコードを keytojoy.ahk などの名前で保存し、実行してみてください。 このコードはキーボードのキー操作をジョイスティックのキー操作としてエミューレートして動かします。 普通のキーボードでも忍屋が楽しめます! ジョイスティックのエミュレートはやったことがなかったので、これでイケるんだなあと目から鱗でした。
vk26::SendPlay,{Up}
vk28::SendPlay,{Down}
vk25::SendPlay,{Left}
vk27::SendPlay,{Right}
q::SendPlay,q
w::SendPlay,w
e::SendPlay,e
r::SendPlay,r
a::SendPlay,a
s::SendPlay,s
d::SendPlay,d
f::SendPlay,f
z::SendPlay,z
x::SendPlay,x
c::SendPlay,c
v::SendPlay,v


ちなみに主人公の椿は手裏剣を投げたりできる訳ではなく、肉体重視の完全な武闘派です。 ボス戦は結構難しくて、ひたすらガードを固めるマンになっていたのですが、それで正しかったのかはよくわかりません。 忍者っぽい勝ち方をできたら恰好良かったですが、アクションが上手い人でないと難しいかも?

作者様の紹介動画はこちら。



ステータスアップのアイテムが結構色々なところに隠されていたり、ミニアクションがあったり、やり込み要素もあります。

2021年5月19日水曜日

英単語学習アプリ Vocabee を作った

英単語学習アプリ Vocabee を作りました。 英単語ってやたらみんな気になるみたい。 色々使ってみたところ、まあまあ良いのはあるんだけど、どれもあまりしっくりこない。 少し怪しめのところをうまく扱う仕組みが欲しいなあとか、もっと頻度をきっちり意識したのが欲しいなあとか、色々な課題は感じます。 作ってみました。



基本的な仕組みの改善

英語が得意な人たちは、単語についてだいたい同じことを言ってる気がしますし、実際その通りだと思います。 (1)高速に、(2)紐付けて覚え、(3)効率的にくり返す。 あとはレベルごとのケースバイケースですね。

高速に学ぶ

1は UI の話です。 英語は即座に答えられるかどうかなので、覚えるときもそれを意識させることは割と重要です。 自己採点の YES/NO が最速に確認できていいと私は思っていますが、学校などのテストに使えない問題はあります。 4択問題は覚えるのには使いにくいけど、テストに使える。 両方用意が良さそうですし、さらに色々あっても良いかも知れない。

英単語は正確に理解するのは無理で、イメージが大切だと思っています。 例えば WordNet で適当に検索すると、簡単な単語でもすさまじい量の語義があり、とても覚えられるものではありません。 瞬間的に意味が思い付くかのほうが重要、意訳のイメージが重要、だからこそ YES/NO が基本的には良いと思ってます。 そして瞬間的に出てくるべき意味は利用頻度が高く文に依存しない単語です。 このへんの考え方をしていないアプリが多い気がするのですが、割と重要なとこに思います。

あともっと単純な理由として、私は4択問題で覚えるのが嫌でした。 解いたときは瞬間的に選んでるだけで覚えるステップがなく、思考をブロックされる。 解き終わった後に並べて覚える作業こそが大切なんじゃないかと思ってます。 どのアプリもそのへんが課題に思えて、頭に入ってくる感じがしなかったので、色々と改良しました。 最後に解答を表示し、選択肢を間違った時にも極力覚えさせるステップを入れました。

紐づけて覚える

2は脳構造と技術の話です。 紐付けて覚えることは大切です。 しかし動画などの紐付けは難しく、ユーザ体験を良くするのは難しい印象です。 技術の限界もよく考えないといけないと思ってます。 他の方法はどうなっているでしょうか。

例文は難しい

例文はすぐに思いつく方法です。 しかし実際色々なアプリを使ってみたのですが、例文を見てもよくわからず時間ばかり取られることが多く、動画と同じ問題があるように感じました。 多くの人が知りたいのは語義のわかる短文だと思うのですが、作るのはすごく大変です。 語義をきちんとわかるようにしようとすると文字が多くなるので読むのが面倒になる。 そして例文を載せる時、UI が難しくて、どの使い方に対する例文なの?みたいな話が出てきて、個人的にはうーん。

逆に文を短くし汎用性を極限まで高めると、それは文法の学習になります。そしてただ短くすればいいかというとそうでもないです。 わかりやすい例として、文法は初学者ほど make from, make by みたいなのを分離して覚えがちな問題があります。 このような単純な文法のほとんどは単語の意味をしっかり理解していれば、覚える必要がありません。 というか下手な覚え方をすると大学受験の問題が解けなくなります。 つまり言いたかったことは、例文を載せるのは思ってるより難しいということです。

画像と音声ならいける

単語のイメージを脳に叩き込むのに現実的な解法は、文字以外だと音声、画像だと思います。 この2つに意味がある理由は明確です。 音声についてはスペルがわかりにくくても発音構造がわかってしまえば母国語の知識でわかるものがあるからです。例えば sausage とかね。 サウサゲと読んでいたら覚えられませんが、きちんとした発音理解があればすんなり理解できます。 発音記号はなくて良いと個人的には思っていて、発音そのものを覚えてしまえばいい。 画像は単語に対するイメージを持つのに便利。どちらも瞬間的に理解できるのが良く、効率的なくり返しの妨げになりません。 理想は文法要素を画像で瞬間的に理解できること。 例文でもきちんとできてないことなので限度はあるけど、例文で覚えるよりは遥かに効率が良いと思う。

詰め込みすぎない

現在の結論としては、単語をイメージとして覚えるのと、正確に覚えること、用例をきちんと覚えることは、それぞれ独立しているので、あまり詰め込まないほうが良いんじゃないかなあと思ってます。 文法を単語学習に組み込むのはアリだと思ってるのですが、頻度がわからないからなあ。 難しいことを考えるくらいならアプリは分けたほうが良いと思うし、 Vocabee は単語をイメージとしてすばやく覚えることに注力していきます。 そして技術的にも UI 的にも「ユーザが必要と思ったときにボタン一つで調べられる」が現実解じゃないかなあ。

効率的にくり返す (無駄のない学習)

3は普通過ぎる話ですが、意外と効率を意識したアプリが少ない気がします。 効率を重視しているからアプリを使うのに、なんか不思議な感じ。 プレイしていて、なんで私はこの単語テストやってんだろうとは思わないようにしたい。 そもそもテストがいるのか? 反復練習がいるのか? …など考えると、自己採点とテストは分けるべきだと思ってます。

あと最初の学習が苦痛なものが結構多かったです。 やはり「これ意味ないよな?」と思わせないことは大事な気がして、 そのためには自由な位置から始めて、自由に戻ったり先に進めるのが良いと思いました。

mGSL という一つの単語リストですべての単語帳を作ってるのも大切なところです。 なんちゃら英単語みたいなのを何度も購入するのではなく、科学的に効率的に学習しようということです。 財布にも易しいし、学習履歴をきちんと活用できます。

応用的な仕組みの改善

このように細かく見れば改善点は多いのですが、既に山ほどある英単語アプリを、平凡に作り直すのはあまり面白くありません。 しかし作りたかった理由が 1つだけあって、それは「どのアプリも学習履歴を有効活用できない」からです。 学校のテストも自宅の勉強もバラバラでは非効率です。そういうのはそろそろやめようと。 そこで Google Spreadsheet に書き出して、学校でも個人でもデータを共有できるようにしました。 情報流出しないし、学校関係者も使いやすいアプリになってると思います。

実装面では Google Spreadsheet を利用し、完全な静的サイトとして構築しました。 実装的には Dropbox / Box / OneDrive のほうが楽そうだけど、学習状況をブラウザで見れるのが良いかなと。 どちらも一昔前だとできなかった実装方法で、この実装方法を選べるようになって最近は気楽にアプリが作れますね。 ただ公開ステップを勘違いして 3週間くらい迷走し、Google さんにご迷惑をかけて申し訳なかった。 API 使うと毎回何かしら公開直前にハマってる気がする。そのへんが API の課題と言えば課題なのかも。

2021年5月18日火曜日

NGSL を超える英単語リスト mGSL を作った

NGSL を超える英単語リスト mGSL を作りました。 英単語リストってかなり色々あって、日本では古めの NGSL が有名です。 世界だと何が有名なのかいまいちわからないところがあるけど、単語数などを見てると NGSL ではなさそうな雰囲気はあります。

mGSL

NGSL / NAWL / TSL / BSL の課題

NGSL の一番の問題は単語数の少なさ。 2800語しか対応していないために、NAWL / TSL / BSL を併用するケースが多いように見えます。 ただデータセットが小さいために統計値に問題があるので、頻度が落ちてきた時が気になります。 データ量に着目し、頻度情報だけを見るなら、 Peter Norvig's compilation of the 1/3 million most frequent English words というのがあって、 Google n-gram 以上の膨大なデータで生成されていて、現時点で最強です。

新定番を目指した mGSL

前々から 1/3 million などを使って、ノイズ除去しながら自動生成したらどうなるのだろうと思っていました。 NGSL も考慮はしてるみたいなんですが、バラバラではなく大きな一つのリストが欲しいんですよね。 遊んでみたら結構うまくできたので、mGSL という名前で公開してみました。

それでは生成方法について。 (1)語彙を原形に戻す正規化、(2)固有名詞の除去、(3)翻訳の補完 が問題となります。

1. 正規化

NGSL などの頻度リストは正規化をどれくらいしているのか謎なところがあります。 少なくとも total と totally の 2つがリストに含まれるような状態になっています。 動詞の正規化も正直よくわかりません。 正規化はどこまでやるべきかは何とも言えませんが、頻度を測定するだけでなく、 勉強に使用したいのであれば、同一品詞内の正規化はしたほうが良いように思います。

正規化の方法は色々あります。 同一品詞内の正規化だけでなく、useful→use までやってくれるような強力な正規化として、Lemmatization と Stemming が知られているので、検討はしました。 ただ Stemming は consider→consid にしてしまったり。他にもノイズだらけでとても使えません。 Lemmatization は 2019年に精度が 95.6% まできましたが、実際にやってみると肝心なところで精度が低いです。 95.6% という数字自体が精度として低すぎますし、実行してみた印象としても精度は上がりにくいと予想しました。 例えば gonna のような口語をどのように Lemmatization するかは難しくて、gon としてしまいます。 同じようなケースはたくさんあると思うので、技術的な限界を感じます。

そこで Lemmatization について軽く勉強してみると、どうやら文字の統計的な変化可能性まで考慮したものを指すらしいです。 別にそこまではいりません。わかっているものだけを変換してくれれば良い。 そこで SoTA の LemmInflect はどんなデータセットを使っているのかと調べてみると、 AGID を使っていることがわかり、 AGID では変化のレベルを数段階で用意してくれていました。 なんだ素晴らしいものがあるじゃないかということで確実に変化する部分の辞書だけ利用させて頂きました。 不確実な部分には誤用なども含まれていると考えられるため、その排除は英和辞書に任せれば、完璧な正規化ができそうです。 精度はいくつかのアプリで確認しているのですが、駄目なところがあっても手動で直せるくらいには完璧です。 他のリストだとここまでうまくいきません。 正規化をクリアできて一気に精度的な心配が消えました。

2. 固有名詞の削除

ノイズ除去や正規化が終わったら固有名詞を削除します。これが一番難しい。 平凡な辞書を作るなら固有名詞の削除は不要なのですが、英単語学習においては固有名詞は言語依存要素があまりにも多いので、削除が必要と思っています。 大文字小文字で排除できるものはそのまま排除すれば良いのですが、たまに辞書でも混じってしまっているし、n-gram では小文字で扱われるものもあったりする。 大文字小文字を無視して固有名詞を削除しようとすると、急に難しくなります。 そして固有名詞はどこまで削除が必要なのか、という問題もあります。 これは最初から最後まで非常に悩んだ問題です。

そもそも日本の教科書でも出てくる John みたいな名前って、実は普通名詞の意味があったりするので、固有名詞を排除するのって意外と難しい。 WordNet で考えると、カウントの大きな意味をメインに使うのが現実的と思うのですが、トイレという意味が名前と同程度に使われるらしい。 John は普通名詞の意味だけ覚えるべきなのか、両方覚えるべきなのか、そもそも両方覚えないべきなのか。すごく難しい問題です。 私の結論としては、John のように頻度の高い固有名詞以外の意味を持つものは、意味の取り違えが起きるので、覚えたほうが良いと思ってます。 しかし固有名詞しか意味を持たないもの (例えば David) は、意味の取り違えが起きないため、覚えなくても良いと思っています。

ちなみに技術面から見ると 固有名詞は 97.8% で判定できることになってます。 しかし実際やってみると全然駄目なので、やっちゃ駄目。文構造を利用しないと難しいってことですね。 色々やってみた結果、NGSL / NAWL / TSL / BSL のような固有名詞より重要度の高い単語を登録した後に、不要なリストを削除するのが良さそうと思いました。 不要なリストは色々ありますが、企業名や製品名などは必須語を登録した後でも単語を削除してしまう可能性があるので、削除しないほうが良さそう。 今のところ、性的用語などの禁止語、冒涜語、地名、人名、国名、言語名、短縮名、元素記号など、てんこ盛りでストップワード処理をしています。

3. 翻訳の補完

禁止語、冒涜語などの事故が少ないリストを削除した後に、まずは辞書を用いて和訳、そして和訳できなかったものを英訳します。 英訳もできなかった単語は重要度が低いので削除します。 辞書はライセンス的に利用しやすい ANCDIC, 日本語 WordNet、EJDict、WordNet、Websters を利用しています。 WordNet は 3.0 ですが、3.1 を使ってもあまりノイズが減らせなさそうなのは軽く確認して、現状に至ってます。 3.1 は使いにくいからそれほど前向きではない。

色々遊んでいてわかったのは、都市名や人名の除去は不安定なので、大文字小文字による除去が重要ということです。 大文字小文字は他の場合でも重要で、(1) march と March のようにわずかに意味が違う言葉があり、 (2) 大文字小文字を考慮した辞書かどうかでノイズが大幅に減ります。 ちなみに 1/3 million は大文字小文字を考慮しておらず、また WordNet も考慮していません。 n-gram のほうは文章の先頭かどうかでかなり統計が揺らぐのでまあいいです。 ただ WordNet はあまり積極的に使えません。 なるべく大文字小文字を意識した辞書を優先して翻訳を行い、最終手段として使うようにします。

追記: 最初のリリースでは WordNet を使ってましたが、ほとんどのノイズの原因が WordNet になってしまい、またあまりにもノイズが多かったので、今は使ってません。

辞書の訳もそのままでは完璧ではなく、訳が多くなりすぎたり、複雑過ぎたり、学校で習う表記とズレが発生しやすい問題があります。 特にズレが発生しやすい助詞/助動詞などは、学校の教科書などを参考にしながらすべて標準的な訳に変えました。 また気になる訳はチマチマと手動更新しています。 ひとまず最初の 8000語まで一通り処理した後、重要度の高い 5000語は何周か調整しているので、だいぶまともになっているんじゃないかと思います。 巷の辞書は機械的にパースが難しいので、かなり大変でしたが、何日か頑張りました。 頻度の低いところで完璧を目指すのは難しいのでいったんリリース。 機械的にパースできる辞書としても有用かと思います。

まとめ

以上、今のところベストと思われる手法を検証しながら、mGSL を作りました。 実際に作ってみると、既存のリストは低頻度領域がやや効率悪そうな感じですね。 レベルが上がるにつれて mGSL のほうが効果が高くなるのではないかと思います。 mGSL は不満があればすぐに直せるようになっており、技術の進化に追随しやすいのが利点です。 変なところや改善点があれば修正します。

作り終わってから 色々な研究がある のを知ったのですが、あまり知見は活用できず。 残りの問題は和訳と低頻度ノイズなんだよなー。そればかりはどうしても手作業が必要になります。 和訳は単語レベルなら embedding な機械翻訳が割と簡単に作れそうではあるけど、 embedding は頻度が下がると全然駄目な印象があるので、やはり手作業しかないんじゃないかと思っています。

2021年5月8日土曜日

漢字タイピングと英単語タイピングを作った

漢字タイピング英単語タイピング を作りました。 タイピングは慣れてくると入力が早いので、高速にくり返すことができ、暗記に向いています。 以前から漢字や英単語の学習に向いていると思っていました。

なので作ってみました。 発音も学べるようにしてあるので、五感もしっかり鍛えます。 またどちらも小学生から大人まで幅広く問題をカバーしています。 実際、大人が遊んでも面白い。 タイピングゲームとしても楽しめるし、単純に勉強としても歯ごたえがあります。



いざ作ってみると、どちらもめちゃくちゃ良いアプリな気がする。 特に英単語は思っていた以上に良かった。 何が良いかというと、すぐに浮かぶレベルまで覚えてないと、HARD モードで苦労するからです。 かなり正確に理解チェックできるのがわかり、4択問題よりずっと良いと思いました。 4択問題だと選択肢から「どうせこれやろ」と選べてしまうので、まったく実用度が異なります。 覚えながらタイピング入力していると、ビシビシと頭に入ってくる感じがして、とても良い感じ。

どちらも最初は EASY モードで覚えて、自信が出てきたら HARD にしてみてください。 思ってるよりずっと難しいことがわかると思います。

2021年5月2日日曜日

Barcode Memo を作った

私はバーコードを読み取りたいことはほとんどないんですが、 逆にいざ読み取りたいとなった時は一気にたくさん読み取りたいことがあります。 古本の確認とかですね。



良いアプリもなさそうだったので、一度に大量のバーコードを読み取ってリストを保存できるアプリ、Barcode Memo を作りました。 なんちゃって QRコードも読み取れるのですが、連続で読み取りたいニーズはないかも知れません。 ISBN や JAN コードを読み取った場合は、きちんと読み取れているかの確認が割と大事なので、商品情報を表示するようにしてみました。 そのため、バーコードリーダーとしてはかなり優れもの。 ただこういった処理もどこまでやるかは微妙で、海外の商品どうするのかなど、無理難題はいくらでもあります。 日本国内の商品に限って言えば悪くないので、ひとまず良しとしておきます。

すごい簡単なアプリなのでサクッと作れるかと思ったら、世界のバーコードの情勢の調査や、ライブラリの選定、PC の Web カメラの精度の低さに手間取って、意外と時間が掛かってしまった。 バーコードをきちんと読み取れるライブラリって実は案外少ないみたいです。 OpenCV でもできるようになったらしいのですが、サクッと作れる気がしなかったので採用せず。 いつか試してみても良いかも。

2021年5月1日土曜日

picture/source/srcset な WebP を力技で polyfill

iOS 14 で WebP がサポートされたので、webp-hero で IE のために polyfill した上で、いくつかのサイトを完全に WebP 化してみました。 しかし webp-hero は picture/source/srcset に対応していないので、iOS 14 以下で WebP が表示されないという問題が起きてしまいました。 IE はともかく古めの iPad / iPhone を使っている人はそれなりに居るのでこれはマズイ。



遊んでみたところ、webp-hero の picture/source/srcset の polyfill 問題は、以下のように解決できるとわかりました。 荒業ですが、picture/source/srcset を削除します。 私は picture/source/srcset を通信量削減のために使うことが多いので、古い OS の表示が遅くなることになります。 とはいえ指定の仕方を統一しておけば影響も小さいので、別に良いかなと。 この適当 polyfill を入れておけば、IE を含めた古めの iOS サポートも安心です。
<picture>
  <source srcset="/img/test.webp" media="(min-width: 768px)">
  <img src="/img/test-small.webp" alt="test"
    class="img-fluid" loading="lazy" width="300" height="225">
</picture>
<img src="/img/test.webp" alt="test" width="600" height="450"
  srcset="/img/test-small.webp 300w,/img/test.webp 600w"
  sizes="(max-width: 768px) 300px,600px"
  class="img-fluid" loading="lazy">

<script>
  // picture/source 要素で指定している場合はこちら
  function removePicture() {
    const imgs = document.querySelectorAll('picture > img');
    for (let i=0; i<imgs.length; i++) {
      const picture = imgs[i].parentNode;
      picture.parentNode.insertBefore(imgs[i], picture);
      picture.parentNode.removeChild(picture);
    }
  }

  // srcset 属性で指定している場合はこちら
  function removeSrcset() {
    const imgs = document.querySelectorAll('img[srcset]');
    for (let i=0; i<imgs.length; i++) {
      imgs[i].removeAttribute('srcset');
    }
  }

  var webpMachine = new webpHero.WebpMachine();
  webpMachine.webpSupport.then(function(support) {
    if (!support) {
      removeSrcset();
      removePicture();
      webpMachine.polyfillDocument();
    }
  });
</script>
これにて PNG/JPEG とはおさらばです。 小さな画像だとたまに PNG/JPG のほうが圧縮率が高いこともあるのですが、面倒だから WebP で良いかなという気もしてます。