Hack Dayで三冠したチームがもう一度四冠するまでにやったこと ——Yahoo! JAPAN Hack Day 2021 参加記
2021.03.20-21開催の「Yahoo! JAPAN Hack Day 2021」にて、僕の属するチーム「ポピポポピピプピリロピロリププププピーポ」が「最優秀賞」「ClearML賞」「LINE Blockchain Developers賞」「Happy Hacking賞」の4つの賞をいただいた。
本番のプレゼンは以下より。
山下まみさんのチーム名を読むシーンもさることながら、
終わった後のタイラさんのコメントが完璧すぎてすごい
今回我々が作ったプロダクトを無理矢理言葉で説明すると、
いろんな情報(写真、手書きメッセージ、お年玉)が限界までに圧縮され暗号化されて印刷された年賀状。受け取ったらカメラでスキャンし、Webサービス上で復元される。データ自体は紙面にあるが、取引データや所有権などのデータはブロックチェーンで管理されており、内容を見ることができるのはまさにその年賀状の所有者だけとなっている。
という感じだろうか。さっぱりピーマンである。
僕が担当した箇所は以下である。
・原案&全体のストーリー作り
・データ圧縮
・プレゼンスライド作成
・UI/UXデザイン
・バグってめげそうになっている人に「いや、それは原理的におかしいから次はこれを試そう」って言う係(後述)
例によってわけわからんぐらい長くなってしまったが、僕の目線で、今回我々のチームがどう"足掻いた"のかをできるだけ漏れなく記したつもりである。頑張って書いたので最後まで読んでいただきたい。最後まで。大事なことなので2回言いました。
前回のダイジェスト
我々ポピポポピピプピリロピロリププププピーポは、前回のHack Dayにも参加しており、初出場ながら「O2O賞」「Happy Hacking賞」「Buzz賞」の3つをいただいた。その時の全貌はこちら。
「寿司がフィールド上を動き回ってて、注文したら直線で来たら面白いと思うんやんか」
「は?」
前回は「寿司が走る」というのがすごくインパクトがあり、なおかつ僕自身が(なぜか)わけわからん位に情熱があったので、そのまま突っ走って行ったら爆発的な成功を収めてしまったのだ。
前回大会の最後に、
運営の方「ぜひ来年も出てよ〜」
「いやーでも来年はメンバーの修論とブチ当たるので厳しいかもしれませんね〜」
などという会話が行われたのを覚えているが、内心では「もう一回やってもこれを超えられる気がせんし、蛇足になるのでは?」みたいなことを思っていた。
神は出場しろと言った
そうこうしているうちに年は明け、コロナ禍に突入し、例年なら募集が始まる時期になっても、何ならハッカソン当日であろう日になっても、特に公式から連絡なく、ああ、今年はそういうことなんだな、いずれにしても出られなかったから仕方ないかな、と思っていた。
そんな矢先、
LINEのグループ名は長いので省略しています
絶妙に出られる....
出ることになった。
卍卍卍メンバー卍卍卍
前回メンバーの5人は今回も参加することになったのだが、そもそも前回のメンバー選びの際の話をしたい。少し長くなるがご辛抱。
HackDayのチーム枠は6人なので、実はもう一人誘えたのだ。
「おぐりん誘う?」
「いや、あいつを入れたら僕たちはあいつを頼ってしまうからやめておこう」
このチームはエンジニアリングに何も関係ないアカペラサークルの同期で結成したのだが、サークルには一際輝いてコンピューターサイエンスの全ての分野に精通している同期がいた。
そいつはIOI(国際情報オリンピック)の銀メダリストで、僕の中学時代からの同期でもあり、自分のプログラミング人生においては幾度となく(米沢牛を対価に)頼ってきた人物である。
そういうわけで、僕はこいつをメンバーに入れずに(広義)優勝するということに並々ならぬ拘りがあった。これは独立宣言であった。コンプレックスと言われればそうなのかもしれない。
もはや僕の完全な私情であったが、他のメンバーも概ね賛同してくれたように記憶している(してなかったらごめん)。結果、一長一短なメンバーがお互いに支え合って何とかプロダクトを作る形となった。
これに加えて、「Buzz賞(Twitterで一番RT/Favされた)」も獲りました
さて、蓋を開けてみたらそれで三つも賞を取っていた。俺たちはこいつの手など借りる必要はなかったのだ。わっはっは。
というわけで今回、晴れてこいつをちゃんと「誘う」ことができるようになった。誘った。
快諾
チームは、しお(僕)、かも、ねりこ、ぱぬ、しうにおぐりんが加わり6人となった。
そんなおぐりん a.k.a. かつっぱ、競プロYouTuberとして頑張っているのでぜひチャンネル登録してあげてください。
ちなみにチームの役割分担は、大まかに
フロントエンド: ねりこ
バックエンド: かも
画像処理・機械学習: しう
統括・デザイン: しお
コンテンツアイデア・プレゼン・事務・進行: ぱぬ
庶務(庶務とは言ってない): おぐりん
という役割分担になった。ハードウェア専門のぱぬは、今回かなり苦しい戦いだったが、結果として他の人が自分の作業に集中できるようにうまく振る舞ってくれたと思う。当日は2時間おきに進捗報告イベントを発生させていた。素晴らしい。
HackDay ONLINEの戦い方 〜Dodge Beltの向こうへ〜
今回のHack DayはCovid-19の影響でオンラインでの開催であった。故に、「逆にゴリゴリなハードウェアを開発したら面白いのでは」みたいなことは考えていた(結果的に、そこが年賀状という「紙」へのこだわりに繋がった)。
また、前回と違う最も重要な点は、9つの技術があらかじめ提供されていることであった。
まとめると、
ML、秘密計算、顔認識、量子イジング型コンピュータ、ブロックチェーン、OCR、分散型DB(ブロックチェーン)、ベクトル探索、自然言語理解
といったところだろうか。
今までと状況が大きく違うだけに、同じやり方では通用しない。
逆に言えば、他のチームもこの特殊な状況にあるわけで、その中でブチ抜くためにどう戦うべきかというのをめちゃくちゃ考えた。
あー今、Completely agreeをいただきましたけどもね
要するに、Tech全振りもウケ全振りも逃げだと思っていて(図の"Dodge Belt")、最初の奇抜でキャッチーなアイデアに対して、必然的にそれぞれの技術を用いるという戦略である。
この考え方は、後に何らかの決断を迫られた時の、最終的な判断基準となった。
案出し
案出しに限らず、定例会の議事録などはNotionを用いて管理することにした。前回はHackMDやGoogle Docs/Spreadsheetなどを用いたが、Notionはページ内に新たにページを作成したり、表などの挿入も簡単に行え、共同編集にも強いため、情報が集約してとても重宝した。
今回も案はものすごい数になり、実際、他のチームの作品のうち5つくらいは我々も考えていた(びっくり)。ちなみに、この段階で、「技術ぜんぶ無理矢理使うやつ」というのも考えはした(先程の「判断基準」によりここでは流れた)。
最終的に残ったのは4つで、「語感が似ている単語探すやつ」と、「トイレDX」と、「ソーシャルディスタンスにリアル会話できるやつ」と、「年賀状DX」であった。
それぞれの案に提供技術がどう使えるかを無理やり考える会
案が4つになってからは、slackで4つのチャンネルを作り、それに決まったとしてそれぞれ構想を固めていった。
しかし、これをずっと続けていくのも問題があった。
「もしこれが採用されたら、こういうことしたい」や、「こういうポイントをアピールできるね」という議論は一見客観的で良さそうであるが、裏を返せば"無責任な"議論なのである。このままいくと、誰にもモチベーションがないプロダクトになる可能性があった。
そこで、「最終的には、みんながプロダクトに愛を持たなきゃいけない」「自分が"推せる"ポイントを次回の会議までに見つけて」と言ったら年賀状に収束した。
「みんなが、自分が作りたいものを作っていたら、結果として一つの作品になっていた」みたいなのが理想なのであって、「ウケるためにはこういうことしなきゃいけないからやる」みたいなのは二流なのである。ABテストなんてクソ喰らえだと思っている(暴論)。
ちなみに僕は「年賀状」という言葉のわかりやすさが推しポイントだった。「ヤバい名前のチームが年賀状を作っていた」という記憶は、他のチームにも埋もれないと思ったので。
計算通り。
技術検証
9個の技術のうちどれが使えそうかを判断するための技術検証期間があった。とりあえず使ってみなければわからないので、
ClearML(ML-Ops): MLするならとりあえず使えそうなので申請
DataArmor(暗号計算): MySQL使うならとりあえず使えそうなので申請
JCV(顔認識): エッジでしか動かない(サーバーで動かない)ので今回は使わなかった
Jij-Cloud(量子コンピュータ): 使えたらかっこいいから何に使うかわからんけど申請
LINE Blockchain(ブロックチェーン): お年玉とか送れたらいいから申請
CLOVA OCR(OCR): 僕が個人的に興味があったので申請
Scalar DL(分散DB): DBって言ってるしとりあえず申請(DataArmorと何が違うの?)
Vald(ベクトル探索): レコメンドとかでとりあえず使えそうなので申請
NLU(自然言語理解): Siriみたいに声で操作とかできるのかなと見てみたら、Speech to Textは含まれていなかったので使わなかった
という感じで、後で欲しくなってもいけないのでとりあえず7個申請した。この時はまさか全部使うとは思ってもいなかった。
Artificial Insanity
今回の僕の中のテーマは「人工の狂気」であった。前回のようによくわからないが形になっている案ではなく、この段階では「年賀状×DX」というふわふわした案に留まっている。
ふんわり時期のプロダクトコンセプト
80ほどあるチームの中で埋もれないためには、何としてもぶっ飛んだ要素を入れなければならなかった。大会が近づき、他のメンバーはある程度「こんなもんかな」という現実的な案に落ち着きそうになっている中、僕は「まだ弱い」と何度もひっくり返した。
僕が最後までこだわった部分は以下の三点に集約される。
1. ストーリーに一貫性がある(他のさまざまな要素は、このストーリーから自然に派生したと感じられる)
2. プレゼンスライドに落とし込んだとき、各スライドがパワーワードになっている
3. ユーザー目線で、無駄のない体験になっている
特に1.に関して言うと、最初に一つ大きな障壁を納得させれば(前回でいう「寿司、直線距離で走ってきて欲しいですよね」)、あとはそこから納得できるようなプロダクトにしたかった。
最終的には、この段階ではプロダクトは固まらなかったものの、技術検証と案だしの両輪でなんとかなるだろうという(僕の)確信が得られたので、「なんか年賀状を魔改造したがっている」くらいで走り始めることにした。
Scalar DL、DataArmor DB、そしてLINE Blockchain
まずScalar DLが何なのかよくわかっていないところからスタートした。「分散DB」としか認識しておらず「ブロックチェーン」「スマートコントラクト」が何なのかもよくわかっていなかった僕たちは、製品を触っていく中で「これは結果的にDBなのであって、実態はデータの変更イベントが堅牢に管理されているもの」なのだということを知った。Javaをみんなほとんど触ったことない状態からよく頑張ったと思う。特にかも、頑張った。
ちなみに本番中、かもはずっとJavaのエラーに苦しんでいた。かわいそう。そしてかもはこれが終わると即フロントエンドの手伝いに行った。偉い。
つまるところ、DataArmorが「流出したら困る情報」を保管するべきなのに対し、Scalar DLは「改竄されたら困る情報」を載せるものなのだとわかった。リアル住所や氏名等の個人情報を前者、年賀状の授受などのイベントを後者で管理することとなった。
これだけだと後者のインセンティブが少し弱いが、今回は「お年玉(独自コイン)」も年賀状に埋め込むつもりで、差出人は年賀状作成時に支払うのに対し受取人は1/1に受け取るため、その情報を正確かつ明確に保持しておく必要があった。
構想途中のDB設計
DataArmor、カラム単位で平文か暗号文か選べて良い
Scalar DLが実はブロックチェーンだったことに気づいた僕たちは、じゃあLINEブロックチェーンは何なんだということになった。
LINE Blockchainは3種類の使い道があり、雑に言うと①リアルマネーと同値なやつ、②アプリ内コイン(魔法石みたいなやつ)、③アイテム(運営からもらえたりするやつ) である。お年玉は①か②で良いとして、③を何らか使えないかと考えた。
③は更に二つに分けられ、a. 「オンライン割引クーポン」みたいにこの世にn個しかないことが保証される(交換できる) やつと、b. 「映画の指定席オンラインチケット」のようにこの世に1つしかないことが保証される(交換できない) やつである。後者がいわゆる非代替性トークン、NFT(Non-Fungible Token)というやつだ。
(2021.04.01 追記: ②と③-a は同じような気がしてきました(FTという意味で))
これを無理矢理年賀状に発行してみたらどうなるか。
オンラインチケットを何回印刷しようとも一つのチケットであるのと同じで、「紙の年賀状は年賀状の情報が記録されている(ストレージとして用いられている)だけで、真の——概念上の——年賀状はサーバー上で、厳密に存在と所有者を管理されている」という事になる。
プラトン『国家』より「洞窟の比喩」。画像はこちらから引用。
年賀状のイデアはサーバー上に、実体は物理上に存在する。
普通はこういう時、「データはサーバーに置いて、そこを見にいくリンクなりを紙に印刷する」はずなのだが、完全にそれがひっくり返ったプロダクトになった。何やら面白くなりそうな予感がしてきた。ちなみにこのわけわからない発想から、LINE Blockchain Developers賞をいただくことになった。世の中はイノベーションに飢えている。
もう一つの年賀状、「超圧縮」
実は安出しの段階で、僕は年賀状DXと同時に「年賀状超圧縮」というのを考えていた。QRコードは白黒だが、その色数を増やせばむちゃくちゃ情報量を増やせるのではないかというアイデアである。
案出し時のメモより
「写真1枚には写真1枚程度の情報しか乗らない」
と気づくのはもう少し後の話。
話をする中でこの案は年賀状DX案とmargeされ、見た目が以下のようになることが確定した。
実際のプロダクトより
64色 = 6bitなので、4pxで3Byteの情報量。
当初はRGB各16段階 = 4bit * 3で3000ピクセルほどを想定していたので、4*3 * 3000 / 8 = 4.5KB であった。しかしすぐに、「RGB各16段階」を印刷したものを写真で撮って区別など不可能であることに気づく。ここから、「コンテンツ圧縮班 vs. 積載情報量(色数&ピクセル数)最大化班」という戦いが繰り広げられる。
超解像と機械学習
元々は完成した実寸大の年賀状画像を載せる予定だったので、画像をどう圧縮するかという話になる。画像から画像への埋め込みなので、ちょっとやそっとの圧縮では意味がない。そんなことと、残った技術を何やら使えないかを悩んだ結果、降ってきたのが超解像であった。
量子コンピュータを変な使い方できないか考えまくっていた僕。
棚ぼたでMLが使えることになった。
年賀状にありそうな画像を大量に集めて、圧縮したものと同時に学習させれば、綺麗に教師あり学習ができる。
年賀状といえば「人のおめでたい写真」と「風景写真」だろうということで、人の写真はメンバーの手持ちのフォルダから、風景はPublic Domainの機械学習用データセットから、それぞれ約200枚ずつ、目で厳選してデータセットを作成した。ぱぬお疲れ様である。ちなみに、大会前日がメンバーのうち4人の卒業式だったので、おめでたい画像はふんだんに用意することができた。
超圧縮レナさん。元画像は全て512px四方に整形してある。
まず超圧縮側は、128px(7bit)四方に縮小、k-meansで8色(3bit)まで減色、更にindexed-color方式にすることで4KB程度まで抑えることができた。RGB256段階の頃と比べると、原理的には1/33554432の情報量である。このサイズでも、8色あれば意外と元画像がどんなのか(人間が見て)わかるので、超解像可能であろうと判断した。(6人ぐらいの自撮りでも、誰が誰なのかわかるくらいだった)
画像サイズの分布。高々6KB。
これはpngとしての出力画像なので、実質の情報量はもっと減る。
他方、超解像側はCNN(畳み込みニューラルネットワーク)を用い、低解像度画像→高解像度の深層モデルを学習していった(SRCNNなるものがあるらしい)。自前で用意したおめでたい画像を圧縮してデータセットを作ったので、シンプルな教師あり学習を行えたのが嬉しかった。
普通の単一画像超解像においては、ピクセルとその周辺領域から高周波成分を復元するわけであるが、今回は8色まで減色しているせいで近傍だけ見ていても情報量が足りない。なので全体を見るようにモデルを試行錯誤した。特に、グラデーションなのか輪郭なのかの識別はなかなか難しい問題であった。最終的にGAN(敵対的生成ネットワーク)なども試したようだが、ここの部分(減色縮小画像超解像)はまだまだ研究の余地がありそうだ。
全体を見て初めてそこが何だったかわかる
結局、この超解像の部分でML-OpsであるClearMLを使わせていただき、その結果ClearML賞をいただくこととなった。実際、モノクロ画像に彩色するとか、フルカラーtoフルカラーの超解像とかは手垢がつくほど実用化されているが、色数を限界まで減らした低解像画像でのフルカラーへの超解像は聞いたことがないし、惑星探査機からの映像のような通信が限られた環境における圧縮技術としては結構面白いテーマだと思う。
色空間、キャリブレーション、量子コンピュータとの邂逅
さてもう一つ、積載情報量最大化の話である。
QRコードは2色、それを4色(RGBと黒)にしたカラーコードなどは存在するが、実際何色までならidentifyできるのかという話になった。その結果、RGB各色4段階くらいまでなら見分けられそう(なんとなく)ということで、64色くらいならいけそうだと(なんとなく)決まった。あと一日あるのなら、この「64」に対する最適化をしていたと思う。
ただし一筋縄ではいかない点があった。データ上でRGB64段階といっても、実際に印刷する際にはプリンタの都合上CMYKに変換され印刷される。RGB空間とCMY空間は完全に一致しないので、いわゆる「色域外」の色が生まれる。
RGB空間(黒三角)とCMY空間(白六角形)。
明るい緑やどぎつい青は印刷できないのがわかる。
加えて、CMYがそれぞれ100%に近いものは黒色になるが、一般的には黒はCMYKの黒を用いて表される。いわゆるリッチブラックに近い部分は、理想的な形で印刷されないだろうし、色同士の区別がつきづらいだろう。
極め付けは、印刷したものをスキャナーではなくカメラで撮影したい点である。環境光によって写真の色や暗さなんかは容易に変化する。赤っぽい部屋で撮ったら、色が全て一個隣の色として認識されたらたまったもんではない。だから、それら環境要因の補正も行わなければならない。
よって、「RGB空間→それを印刷して写真で撮って環境要因を取り除いたRGB空間」という変換が起こるわけである。64色というのは、この後者の空間における「もっとも見分けやすい64色(になるような、前者の空間における色)」なのである。
年賀状中央のQRコード。
読み取るとWebツールのページに飛ぶ他、真ん中の「あけおめ」は年賀状であることを示し、更にそれらは#f00, #0f0, #00f, #fffを表している。
こういう無駄の無さが個人的な美学。
キャリブレーション(補正)の流れは以下である。
①上記「あけおめ」の「め」と、年賀状全体を囲む枠線から、真っ白と真っ黒を認識し、それぞれ#ffffff, #000000になるように空間を引き伸ばす。(ホワイトバランス補正)
②上記「あけおめ」の「あけお」から、R,G,Bベクトルが印刷によってどう歪んだかを認識し、線形変換により補正する。
さて、あとは補正後の色空間から64色を選ぶだけである。
RGB各17段階=4913色をとりあえず印刷し、カメラで撮り、上記の補正をかけ、読み取り前後の色の対応表を作った。
何かに躓くたびに「これって量子コンピューター使えんじゃね?」と
言い続けてきた甲斐があった
この4913の点に対し、「この二つは"見分けづらい"」場合に辺を張ることにすると、「どの2点を取っても見分けられる」ように最も多くの点を選びたい気持ちになる。これは最大独立集合問題で、これは一般にはNP困難(普通のコンピューターでは解くのにものすごい時間がかかる)な問題であるが、QUBOで表せるため今回提供された量子イジング型コンピューター(Jij-Cloud)で解くことができる。あとは、ギリギリ64点が選べる範囲で、「似ている」判定をキツくしていけば良い。
最大独立集合問題。
辺が「似ている」とすると、赤色の5点はどれも「似ていない」。
かくして、コンテンツの情報量削減にClearMLを、積載情報量最大化にJij Cloudを組み込むことができた。前者はしう、後者はおぐりんの尽力により達成された。流石。
"あたたかみ"をどう形にするか
画像とお年玉は載せるとして、このネオネンガならではのコンテンツをもう一つ載せたいと思った。
最近急速に、年賀状を出さない人が増えている。今年は高校同期から1枚もらっただけで、それも結局返信していない(伏線)。正直自分も、Twitterの#年末なのでいいねした人に一言みたいなタグで足りてしまっている部分がある。そこで、年賀状を年賀状たらしめているものは何かというのを分析した。その結果、「リアルなコミュニケーション」「手書きメッセージの温かみ」などが見つかった。
わかる。わかるけど、それだけのために印刷するのがめんどくさいって思っちゃうんだよね。
そんなこんなで、載せるコンテンツ案をぱぬにぶん投げていたら、「リアルタイム筆跡」という案を挙げてくれた。
これをみんなにシェアしたところ、ねりこが一瞬で
これを見つけてきた。HTMLのCanvas上で筆跡を記録・再生できるツールである。具体的には、10msごとにどの座標にペンがあるかを記録するというものである(ペンが動いていない時は、止まっている時間が記録される)。
最終的にはこのコードをアレンジし(結局参考にする程度で書き直した)、「写真が乗った年賀状に、上からコメントを手書きできる」ツールが完成した。
誤り訂正はしないが、汚れても動いて欲しい
かくして、「画像データ=ピクセルの配列」と「手書きメッセージ=座標の配列」が紙面に乗ることとなった。
しかしながら、これを単純に圧縮して乗せればいいかというとそういう話でもない。実際ここまで一通り完成して読み取ってみた結果、普通に復元できなかった。原因は、超圧縮の際に画像のバイナリをzlibで圧縮していたからで、少しでも破損すると圧縮ファイルとしての形を保てなくなるからであった。何千あるピクセルを全て正しい色で認識できるわけもなく、このままでは一箇所の破損で全体が見えなくなってしまう。
例えばQRコードは、重要な情報は二箇所に書かれていたり、
誤り訂正符号が入っていたりして多少の汚れでも元データを
完璧に復元できるようになっている。
そこで、それぞれのデータを「形を保ったまま」バイナリ単位で手動で圧縮することで、ある箇所が破損しても、(例えば画像なら周辺数ピクセルの色がおかしくなるだけで)全体は生きているという状態にした。
def encodeImage(im, shape):
img = cv2.resize(im, shape)
Z = img.reshape((-1,3))
Z = np.float32(Z)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
K = 8
ret,label,center=cv2.kmeans(Z,K,None,criteria,10,cv2.KMEANS_RANDOM_CENTERS)
ar = label.flatten().tolist()
s = []
while len(ar):
b = 0
for i in range(8): #ピクセル数が8の倍数じゃないと死ぬ
b = b * 8 + int(ar.pop(0))
s += b.to_bytes(3, byteorder='big')
center = np.uint8(center)
s = center.tobytes() + bytearray(s)
c = zlib.compress(s) # ここのzlib.compressを外した
return c
画像を超圧縮するコード。8ピクセルを3バイトにして、sにpushしている。
(8色に減色しているので、1ピクセルは3bitで表すことができる)
「した」と言ったが、元々僕はちょっとでも圧縮したかったので、元々zlibを嚙ます前に手動で上記圧縮を実装していた。だからzlib.compressを外してもサイズは1.3倍程度にしかならなかった。ヒヤリハット(1回目)。
ノイズに強い暗号化とは
年賀状トークンを管理して所有権を持っている人だけが見られるとしたら、暗号化はセットで必要となる。しかしながら、圧縮と同じで局所的なデータの破損があっても全体が成立しているような暗号化でなければならない。
天才か
これなら、1ピクセル破損してもそこのデータが壊れるだけで済む。
あとは、このシードkをどう作るかだが、こちらでいい感じにランダム生成して年賀状IDと紐付けてDataArmorにしまっておけばいい。
あれ?このセクションだけ順調すぎませんか?(伏線)
ストーリーとしてのプレゼンと、レコメンドは逃げなのかということ
さて、ここまでで各要素(パワーワードスライド)は形になってきたが、全てを一つのストーリーの形に収めるという仕事がまだ残っている。
当日深夜のメモ。色はその時点での進捗を表している。
プロダクトというのは、我々が作ったものではなく、カスタマーにとってどう見えるかで決まる。使う人にとってのプロダクトがプロダクトなのだ。だから、プレゼンはユーザー体験に沿って行うことにした。
一方、いずれ使えるんじゃないかと思いつつ、使い方を悩んでいたのがVald(密ベクトル探索)である。一般的にはネットワークを利用してレコメンドを行うのが定石だが、単純に「この人に書いている人はこんな人に書いています」だと、(まあ筋は通っているものの)取ってつけた感があるし、何より実装コストと見合わないと思った。
そんな時、まだ昇華できていない部分があることに気づいた。「年賀状をほしがる」機能である。
ボツストーリー案。
「年賀状をあげるのはもう古い。これからの年賀状はもらいにいく時代」
当初はむしろこの方向性を押し出そうとしていたが、議論が進むにつれほしがる機能はあってもなくてもいいようになっていた。プレゼン原稿においても軽く触れられる程度であり、90秒に収めるために削ろうかとも思っていた。
しかし、この「ほしがる」機能によって、「もっと年賀状が欲しい」という感情が満たされるのであれば、レコメンド機能は「もっと年賀状を書きたい」という感情に対応するのではないか、ということに気づき、例の「必然性」を満たすと判断したので、急遽Valdを組み込むことにした。二日目早朝のことである。ちなみに、諸々に手間取ってしまい最終的にはネットワークの構築はできたものの、リコメンドのところまではギリギリ行きつかなかった。無念。
案自体は↑のように以前から出ていたが、
ストーリーに落とし込めたという話でした
ブランドコンセプト、UXデザイン、UIデザイン
最初の案の段階から、僕は「そのプロダクトは一言で言うと何なのか」ということにこだわってきた。それはマーケティングにおけるキャッチコピーであり、ブランドコンセプトである。無理矢理にでも一言に落とし込むことは、外部に対して自分たちのプロダクトを誤解なく伝えるだけでなく、内部においても、全員の向く方向を揃えると言う意味で非常に重要なのである。
「年賀状ってまだやれると思いませんか」「年賀状をもっとあたたかくしましょう」など色々考えた結果、最終的に捻り出されたものが「サイバーお正月」であった。もう誰もサイバーお正月が何なのかはわからないがそんなことはさておき、これが決まった瞬間に、僕の中でのビジュアルの方向性が確定したのだ。タイトルの「ネオネンガ」もすぐ思いついた。
真ん中が普通のお正月だとすると、「お正月がサイバーを希求した」イメージが左で、「サイバーがお正月を希求した」イメージが右
メインの「年賀」はみんな大好き"TA-candy"でサイバー×めでたみを出し、カタカナの「ネオネンガ」は"どんぐり かな"で手書き×デジタル感を表現した。いかがだろうか。
初めはお正月にサイバー成分を足そうとしていたが、あまりに相性が悪かったので、思い切ってサイバーにお正月を足すことにした。お正月は「和柄」で表すことができそうだったので、結果的にすごく相性が良くなった。
プロトタイプ。完成形はこの記事のヘッダーを見て
このメインビジュアルとコンセプトは展開が非常にやりやすく、
当初ねりこが作ってくれた"骸骨"——CSSがほとんどない、コンポーネントを置いただけの状態を僕はこう呼んでいる——は、
二時間ほどでこうなった。背景に和柄がふよふよ飛んでいる。もちろん色だのボタンだのはまだまだ詰められるが、最低限サイバーお正月みはかなり感じられるのではないだろうか。ちなみにUIデザインはAdobe XDでねりこと共同編集していたので、僕の進捗がリアルタイムで共有された。
フロントエンド
フロントエンド、というかwebページが作られる頃には僕はもうキャパりにキャパっていたので、フロントエンドが何をしていたかは全く把握できなかった。
とりあえずデプロイにはVercel、フレームワークはNext.jsを使ったらしい。vercel、ローンチ簡単すぎて惚れる。
年賀状作成はPC(コメント書くから)、デコードはスマホ(カメラスキャンするから)がメインになることから一応レスポンシブになるはずなのだが、今みなさんが見ているnoteのUIもそうであるように、max-widthを設定することで最小限のコストで納められたと思う。
プレゼンの方がやばすぎて、素材たちを一通りぶん投げて放置していたら、ねりことかもが爆速で形にしてくれた。最強。というか、かもはフロントエンドもバリバリ書けるのか。すごい。
実際のwebページ
これだからハッカソンはやめられない 〜帰ってきたOCR〜
いよいよ全ての機能があらかた実装され、結合のフェーズに入った。
ここで、気づく。
「年賀状IDってどこに書いてある?」
「書いてないけどなんで?」
「復号する鍵がDataArmorにしまってあるから」
「じゃあ画像とかメッセージとかと一緒に入れとくね」
「いや、それを復号するために年賀状IDが要るんだってば」
「あ」
固まる。時計を見る。時間は二日目のam10:20。終了まであと1時間半。
これはやばいかもしれない。年賀状とは別で、平文で年賀状IDを送らなければならない。QRコードを増やすか?いや、全体の配置はもうしっかり固めてコードが書かれている。今からモザイクの一部だけ暗号化しないようにするか?原理的にはそれが一番いいが、今からエンコード&デコードのコードを直すのか...?
そんな時、
ぱぬ「審査員に最終的に使った技術を送らなきゃいけないんだけど、OCR以外全部って返事しちゃっていい?」
ん?OCR?
年賀状IDぐらい直接書けばいいのでは?
ベタ書き
かくして、終了1.5h前に急遽OCR導入が確定した。
DX的な文脈で個人的に書類の電子化などに興味があり、今回使わないかもしれないと思いつつちゃんと技術検証をしていたOCRが我々を救うこととなった。ヒヤリハット(2回目)。というか奇跡。
かくして、我々のプロダクト「ネオネンガ」は完成した。
自分を褒める
個別のタスクはさておき、自分が今回やった「バグってめげそうになっている人に『いや、それは原理的におかしいから次はこれを試そう』って言う係」はかなりいろんなことを解決したと思っている。
先述した「最も間違えにくい64色を選ぶ」話に関して、おぐりんは初め古典的なアニーリング(焼きなまし法)を行って、そこそこのスコア(マンハッタン距離で49)を出していた。その後それを量子アニーリング(Jij-Cloud)で実装し直すと、スコアが4とか7とかになった。「これ普通にやった方が(量子コンピュータ使わない方が)性能出る気がする」とおぐりんは言ったが、冷静にランダムにとってきても4とか出るはずなので、いやそんなはずはない、どっかバグってるはずだと言い続けていたらやはりバグっていて、ちゃんと動かしたら一発で51が出た。その後パラメータをチューニングして、最終的には100近いスコアを出すことができた。
これは「ディスプレイをカメラで撮った時に見分けやすい64色」
また、モザイクをスキャンしてデータを読み取るとき、あまりにエラー率が高いので64色では厳しいだろうとしうが言った。確かにエラー率がすごいので元データを見てみることにすると、確かに近い色と間違っているものも多いが、まれに全く違う色と見間違えている場合があった。さらにその色を見比べてみると、元々緑っぽい色が赤っぽい色だと勘違いされていた。OpenCVはBGR、PillowはRGBの順で画像を表すので、絶対そこのミスだろうと喚き、あれこれ調べまくった結果、最初の64色を選ぶ話の時から入れ替わっていたことに気づいた。
できないとわかっていることをやり続けても仕方ないので、可能性があるのかどうかはきちんと見極めなければならないが、可能性があることがわかっているならば、どれだけ詰まっていようとも諦めなければいつかは解決するのである。
まとめ 〜二度咲くのは難しいんだぞ〜
ここまで読んでくださった方ならわかると思うが、今回自分の中で一番悩んだ点は、「一発屋は、二発目にどう足掻けるのか」であった。
正直、去年のプレッシャーが半端なかったのは事実で、周りからは期待されているだろうし、前回三冠のチームが今回ヘボかったらシンプルに悲しまれると思っていた。
戦略は二つあり得て、一つは前回の上位互換を作ることだが、前回全力を出し切ってしまったおかげで超えられる気がしなかった。しかし今回は大会自体がオンラインかつ最先端技術の提供という違う形だったので、結果としてもう一つの戦略である「前回とは全く違うベクトルに飛ばす」という形になった。今回のポピポポピピプピリロピロリププププピーポは、ある意味お行儀よく「年賀状」というまあありそうな題材を元に、一方で提供された技術たちをハイレベルに使い倒すという、エンジニアとしての技量の高さも見せつけることができたように思う。
Happy Hacking賞(視聴者投票賞)の発表の時は
展示の準備で忙しくて気づいたら受賞していた
いや、ほんと、この立場に立ったらわかると思うんですけど、一度バズった人がもう一度バズを起こすのって、めちゃくちゃ難しいんですからね。同じことしても二番煎じになるだけだし。
まあ、今回はこれだけ頑張って何とか前回を超える戦績となりました。四冠本当にありがとうございます。今度こそ次回はないかもしれない。流石に。
後日談
ハッカソンが終わってから表彰式までの間に、こんなニュースが入ってきた。(ハッカソンが3/20-21、前者が3/23、後者が3/25、表彰式が3/27、今日が3/28)
NFTによってツイートに資産としての価値が生まれたり、NFT & Crypto Artという分野が日本でも話題になったり。ブロックチェーン?何それ美味しいの?状態からスタートして年賀状にトークンを発行するに至った我々だが、意外と正しい道を歩んでいたのかもしれないと思った。
最後に
今回は前回にも増していろいろなものをHackさせていただいたが故に、各所に大変お世話になっての受賞となったと思う。
・最先端技術を(出場料を払えば)無料で使わせてくださったClearML/DataArmor/Scalar DL/LINE Blockchain/Jij-Cloud/Vald/CLOVA OCRの皆さま
・二年連続で大暴れする場を提供していただいたHackDay運営の皆さま
・チーム名を噛まずに読んで場をブチあげてくださったり、カオスなプレゼンをめちゃくちゃわかりやすく解説してくださった司会のお二人
・応援してくださった周りの皆さま
・3時間寝させてと言われたので1時間で叩き起こさせてもらったチームメンバーのみんな
本当にありがとうございました。
最後に、僕から皆さんへ年賀状をしたためましたので、ぜひデコードしてみてください。
この画像をダウンロードして、以下のデコードページにぶち込んでみてください。(追記: 超解像するので数十秒かかります)(追記: スマホからだとダウンロードの時に色が変わって壊れることがあるので、PCからのアクセスを推奨いたします)
デコードページ: https://hackday2021-ppp-testweb.vercel.app/decrypt-nengajo
(ちなみに真ん中のQRはトップページへのリンクで、LINEログインを要求されます。本来はLINEログインしないと見られない設計だったので。ちなみに、Bitmax Walletの都合上、皆さんはログインしても年賀状は作れません。残念!)
_人人人人人人_
> バレてた <
 ̄Y^Y^Y^Y^Y^Y^ ̄