株式会社エニグモのブログ - TECH PLAY

TECH PLAY

株式会社エニグモ

株式会社エニグモ の技術ブログ

252

エニグモ 、 BUYMA の中のひとを知ってもらおうと、エンジニアへのインタビューをしてみました。 第2回は、2016年3月入社の 山本さん です。 インタビュアー 小澤:2011年4月入社。部長。 伊藤:2018年5月入社。新卒2年目。 前職について 伊藤: 前職は何をされていたのですか? 山本: パチンコの開発をしていました。パチンコって真ん中に液晶あるじゃないですか。あれを制御する組み込みソフトの開発です。 デザイナーさんがAfterEffectsで動画をたくさん作るんですよ。それを組み合わせて表示します。 あとは音を鳴らしたりランプを光らせたり 役物 を動かしたり。 小澤: 最近パチンコやってないからわからない、、 言語や開発環境は? 山本: C言語 ですね。 フレームワーク はないです。 転職について 伊藤: なぜそこからWebの会社にきたんですか? 山本: 開発をしているので、開発について調べるじゃないですか。Webで調べるんで、Web開発の情報が多いですよね。 そうしているうちに興味を持って、勉強会ってのがあるんだとかわかってきて、2014 年の Github Kaigi というのに行ってみました。 そこでWeb開発たのしそうだなぁと思いました。 伊藤: Github を使ってたんですか? 山本: 前職ではGitはおろか Subversion も使ってなかったんですけど。Rebuild.fmの公開収録があったり面白そうだなとおもって行きました。 それでWeb開発をやってみようと思って、 Rails とReactをやってみました。 小澤: Rails はわかるんですが、なんでReactやったんですか。 山本: JavaScript もやらないとなと考えて調べると、ちょうどそのころは jQuery からReactへ、というような記事が多くて、やるなら流行っているものをやろうと思いました。 小澤: パチンコのUIをやっていて、もともとUIが好きでReactやったわけではないんですか? 山本: そうでもないですね(笑) エニグモ に決めた理由 伊藤: エニグモ の決めたのはなぜですか? 山本: 自分でやっていた Rails とReactが使えるところに行こうとおもって応募しました。 そしたら書類審査がはやくて、面接も早くて、内定が出たので決めました。 小澤: それだけですか? 山本: そうですね。 とにかく書類も面接の結果もどんどん返ってくるんですよ。こんな感じで仕事ができるのかっ、というスピード感が決め手でしたね。 入社後の印象は 伊藤: 入社してみての印象はどうでしたか? 山本: 自由だなぁと思いました。あれやれこれやれ言われないし。 もちろんタスクは振られるのですが、あとよろしくってかんじで。 小澤さんが自由なんですかね。 小澤: 私が自由ってどういうことですか (笑) 山本: 細かいこと言わないですよね。 小澤: そうですね。 山本: あとは、社員の人がキラキラしていてビビりました。 エンジニアだけじゃないし、前職に比べたら女性もいっぱいいますし。ファッションの会社ですし。 みんなおしゃれな店でランチしてそうで、コンビニ弁当なんか食べてたらちょっと恥ずかしい、みたいな。ちょっとした恐怖感はありました。エンジニアは関係ないですけど。 小澤: そういえば、ファッションの会社ってことでエンジニアに敬遠されることもあると思っているのですが、入社前に、おしゃれじゃないとだめかなとか不安はなかったですか? 山本: それは考えませんでしたね(笑) 一番の思い出 伊藤: 入社して一番の思い出はなんですか? 山本: 入社して一ヶ月ぐらいでお問い合わせのリニューアルに参加しました。 右も左もわからない状態で、レビューでもいろいろ指摘されたりもしましたが、すごく成長できました。 伊藤: レビューは厳しかったですか? 山本: 厳しいですけど、すごく丁寧で、親切に教えてくれたって感じですね。上から指摘されたりとか、怖いという感じではないです。 開発手法についても、 スクラム でやろうってなってて初めてでしたし、データベースも個人で使った見たていどで仕事ではやったことなかったですし、 Apache Solr( 検索エンジン )なんて知らなかったし。 いろいろはじめてでわからないことがわからない状態でした。ただ、一緒のチームの 齊藤さん と業務委託の方が二人共すごい技術力が高くて、二人の実装や設計スキルを見ながら勉強してました。 失敗 伊藤: 失敗したことはありますか? 山本: 定時後に大きめのリリースをして BUYMA が止まってしまいました。 小澤: そんな事あったっけ。どのぐらい止まりました? 山本: 完全に止まったわけではないですが、2時間ぐらい不安定でした。まあ見えにくかったので、止まってしまっていましたね。 それまでJSや CSS をブラウザでキャッシュするようにクエリパラメータをつけていたのですが、 SVN のリビジョンがついていたんですよ。それの仕組みがいやで変えたかったんです。 結構影響が大きいので、みんながリリースしない時間にやろうということになり、定時後にしてしまいました。 そしたら、原因の詳細は忘れましたが、ネットワークが不安定になってしまいました。 伊藤: 教訓はありますか? 山本: 定時後にリリースしないってことですね!(笑) 小澤 そうっすね(笑) 助けてくれる人も少ないですからね。調整して定時内にやりましょう。 変化 小澤: 入社してから変わったことはありますか? 山本: 変わったことですか? 小澤: 例えば、おしゃれになってますよね(笑) 山本: そうかも知れないですね(笑) みんなキラキラしてるし、おしゃれな人がたくさんいるんで、参考になる人がたくさんいるからですかね。入社前よりはだいぶ気を使うようになりました(笑) 変わったことかぁ。。。給料が上がりました!! 伊藤: そんなにですか(笑) 小澤: はじめが低かったからじゃないですか(笑) 山本: それもありますが、順調に上げてもらっていると思ってます。 給料を上げてくれるいい会社です(笑) 伊藤: 給料が上がって、おしゃれにもお金をかけられるようになったと(笑) どんな人と一緒に働きたいですか 伊藤: どんな人と一緒に働きたいですか? 山本: レガシーな部分もいっぱいあるんで、自分で勝手に改善したり、新しい技術を取り入れてくれる人ですね。 伊藤: 勝手に作って、リリースしていいんですか? 小澤: リリースはだめだよ。上場企業には統制というのがあってだな(笑) 山本: リリースはだめだけど直前までは勝手にやって大丈夫ですよ。自由なんで。 リリースするには、ちゃんと承認をもらってリリースしましょう。 会社のいいところ 小澤: 開発・仕事じゃないところで エニグモ のいいと思うところはありますか? 例えば、コーヒー屋さんとか。 山本: コーヒー飲まないんですけど、ビールがあるのはいいですね。 世界のビールがおいてあって、18:30以降はのんでいいんですよ。 あとはリゾートスペースが好きですね。とくにコーヒー屋さんの前のリゾートスペースが好きです。 ここだけモニターがなくてミーティングで使えないんですけど、集中したいときにここに来ます。 たれこみ 小澤: そういえば先日、伊藤さんから「山本さんはずっと Twitter を見てる」とタレコミがありましたが仕事してますか? 伊藤: Twitter 見てて、仕事は外注してる疑惑ですね(笑)あんなにできるはずがない(笑) 山本: もちろんずっと Twitter を見てるわけではないですよ! たまたま休憩するタイミングが同じなんじゃないですかね(笑) 伊藤さんは仕事しないで山本を見てるってことでしょ(笑) 弊社には Twitter を見る自由はありますよ。 今後 伊藤: 今後やりたいことはありますか? 小澤: ずっとフロントエンドチーム作りたいって言ってますよね。できてなくてすみません。 山本: フロントエンドの改善したいですね。一人だと辛いんでチームでやりたいです。 小澤: 伊藤くんが小さく手を上げてますけど。 山本: サーバーサイドがもう少し強くないとまだ一緒にやるのはつらいですね(笑) 伊藤: なぜか毎回、私のダメ出しみたいになりますね。 マネージメントは興味ないですか? 山本: ありますよ。 これから先ずっと技術を突き詰めていくタイプかっていうとそうでもないと思っていて、タスクを細かくして人に振っていくのも自分にむいているかなと最近感じていますね。 小澤: えっ、そうなの。いいこと聞いた。 山本: 技術もまだやっていきますが、マネージメントもありかなと思っています。 若手に向けたメッセージ 伊藤: ぼくら若手に向けたメッセージはありますか? 山本: Webの世界はすごく広大なので迷うことも多いと思うのですが、色々触って自分の好きな分野を見つけて、そこを伸ばしていってほしいです。 今後は特定の分野に特化した人が活躍するんじゃないかなぁとなんとなく思っています。 小澤: それではこの辺で、ありがとうございました。 山本・伊藤: ありがとうございました!
エニグモ 、 BUYMA の中のひとを知ってもらおうと、エンジニアへのインタビューをしてみました。 第1回は、2011年3月入社の齊藤さんです。 インタビュアー 小澤:2011年4月入社。部長。 伊藤:2018年5月入社。新卒2年目。 エニグモ に入社した理由・動機 伊藤: エニグモ に入社したのはなぜですか? 齊藤: 自社サービスがいいなと思って会社を探していました。 盛り上がっていたのでゲーム会社も面接に行ったのですが、すごい 機械的 で、 MySQL 運用したことありますか、とか、そういう質問ばかりで。 エニグモ は(前)部長とエンジニアの人と面接して、自分のできることに興味を持ってもらって、最終含めて3回面接しました。 EC に興味があったのと、どうしようかと思っているところで Stylist というサービスを公開しました、というメールがきたので、面白そうだなと思い入社を決めました。 残念ながら Stylist は今はクローズしていますけど。 小澤: エニグモ の前はイギリスですよね。 齊藤: もうやめて来ていました。 前の会社は受託で、ス クラッチ &ビルドみたいなことを何回もやるんで毎回工夫できて面白かったんですが、自社でやっているサービスも見ていたのでソッチのほうが面白そうだなと思っていました。 現在担当している業務は 伊藤: 最近はどんな業務をされていますか? 齊藤: 4年前ぐらいにユニット化されてからは、自分たちでこれやったら良さそう、変えたほうが良さそう、面白そうだなって思うア イデア を提案してやっています。 自分で発案したものもありますし、浮いていて面白そうだなってのを拾ったりしてやってます。 その前はトッ プレベ ル会議みたいなのがあって、大きなテーマのものは誰がやるかみたいなのが決まっていました。 小澤: この3年ぐらい、主にパーソナルショッパー向けの機能の開発をしていますよね。 齊藤: そうですね、それが僕の担当しているロールっぽくて、 小澤・伊藤: ぽくて (笑) 伊藤: ぽくてって認識なんですね。 齊藤: お問い合わせリニューアルぐらいからそうですね。 お問い合わせ機能が、データベースの設計から見た目までひどかったんですよ。でも、それまでは機能しているものをあまり見直さなかったんですよね。 でもあれは、なんで変えていいってなったんですかね? 小澤: 3年ぐらい言い続けたからじゃないですかね。 お問い合わせでなにかしたいという要望はあったんですが、お問い合わせは触っちゃだめだってエンジニアはみんな思ってたんですよね。 齊藤: 改修内容が大きすぎるから触れなかったんですかね。データベースから見た目まで全部やりたくなっちゃうから。。 結局そのときは全部やり直しましたね。 初めてメイン以外のデータベースの導入を決めて、データベースを設計して、既存データの移行を考えて。 UI はそのとき React が盛り上がっていたので、やったことはなかったけどやるでしょってなって、Redux の設計も勉強して導入しました。 伊藤: どのぐらいかかったんですか? 齊藤: 最初は3ヶ月ぐらいかと思ってましたが、半年ぐらいかかりました。 小澤: 当初計画の3ヶ月ってのは、もう長いからそれ以上考えられないっていう、漠然とした3ヶ月です。 齊藤: それまで3ヶ月もかかるのはなかったですからね。大体長くて1ヶ月みたいなものが多かったですね。 性能検証もあとから追加したり、見積もりが随時変わっていったっていうのも理由としてありますね。 それからこのとき 山本さん が入社して1ヶ月ぐらいでプロジェクトに参加して、1ヶ月ぐらいですでに何ヶ月も一緒にやっているような存在感で、インクリメンタルな実装を相互にレビュー・マージしていく作業フローもすぐにスムースにこなしていて驚きました。 そのプロジェクトのあとは、商品画像の差し替え、、、画像枚数からルールに従って URL を生成する処理になっていたので、画像が並べ替えられなかったんですよね。ショッパーや社内からも画像を変更したり並び替えたいという要望はずっとあったんですが、それも手を入れるのが大変だからってやってなかったんですよ。 ちょっとした改修では済まないものをそこからやるようになりました(笑)。 これも半年ぐらいかかりました。 管理画面( BUYMA 本体とは別の Java の Web アプリ)をいじらずに管理画面を改修するのもこの頃からやってます(笑)。 小澤: それもお問い合せ改修からですかね。 齊藤: そうですね、管理画面の変更( Java )がめんどくさかったんで。 画像の差し替えは半年メインラインと並走して開発したのでリリースでたくさんしくじりましたね。 今みたいに Git じゃなくて SVN だったという要因もあったり、商品画像はサイトのいろいろなところに出ているので色んな人が修正するんですよね。それでリリースマージが難しくなってしまって。。 お問い合わせ改修も半年かかったんですが、色んな所に表示される機能ではないので影響が小さかったんです。 余裕持ってやるつもりだったんですが、マージに労力を取られすぎて、マージ終わったらもうメンテじゃんていう感じで出してしまいました。 そのときはメンテ明けは普段は朝に帰るんですが、24時間ぐらい会社にいました。 小澤: その後にフィーチャーフラグとか、どんどん出していくように実装をしましたよね。 齊藤: そうですね。 その後は CSV での一括出品機能でした。機能としてはあったんですが、100品しか更新できない、下書きにしかならない、変更できない項目があるとか、使いにくいところがいっぱいあったんですよ。 1万件を上限にしたので、一つのバグでショッパーさんの全商品が壊れてしまうようなリスクがありました。 これはテストを入念にしたので時間がかかりましたね。 大変でしたけど、商品数の多いショッパーさんにすごく使われているので良かったです。 伊藤: 直近は何をされていますか? 齊藤: 直近はとある機能をやっているんですが、これまで BUYMA は モノリス なアプリなので改修自体が必要以上にめんどくさいなと感じていて、良い機会なのでマイクロサービスな設計で作ろうと思っています。 Kubernetes とか使ってインフラのメンバーにも協力頂いて進めたいです。 エニグモ に入社してからの一番の思い出 伊藤: 入社してからの一番の思い出ってなんですか?社員旅行でハワイに行ったりしてますよね? 齊藤: 一番か、難しいですね。。。。ハワイも思い出ですけど、 僕はメンテナンスが好きで、 小澤・伊藤: え、そうなんですか! 齊藤: メンテナンスでリリースするのって、サービスをコン トロール している実感が強いじゃないですか。 深夜にサービスを止めて、 ソースコード をリリースして、明け方徐々に使われだしていくその現場にいる感じが好きですね。 最近はリリースでサービスを止めるような機会は減ったのであまりないですけど。 伊藤: 一番の思い出はメンテナンス。 齊藤: 長期間開発してリリースしたメンテナンスは印象にとても残ってます。 頻繁にできる仕事ではないですけど、その分区切りにもなるし、達成感になりますね。 どういう人と働きたいですか 伊藤: これから、どういう人に来てほしいですか。どういう人と働きたいですか。 齊藤: アイディアがある人が良いですね。自分だったらこう変えるとか、これは良くないとか、自分の意見が言える人がいいですね。 あとは、人の変更見て、塩梅を汲み取って柔軟に実装できる人がいいですね。 例えば「そんなに作り込まなくてもいいです」っていう感覚が分かる人。 伊藤: 一気にハードル上がりますね。 齊藤: 思っている塩梅で、書けてないというか、「 工数 削るために ユニットテスト 書きませんでした」って来られると、そうじゃないよなと思いますし。 小澤: そうですね。 齊藤: チームで上手に動ける人がいいですね。不必要に足引っ張るようなレビューコメントで全体のリズムが悪くなることもあるので。 作業分担してても自分事になっている人がいいですね。 僕は年長者ですけど、経験者とばかり働きたいわけじゃないんですよ。 人のコードや動きを見て、良いと思うとか、これはこうじゃないですかってはっきり言える人がいいですね。 伊藤: 初心者とまではいかないですけど、あまり経験がなくてもいいと。 齊藤: そうですね。センスが良ければ関係ないと思います。 これまでにした一番大きなミス 伊藤: これまでにした一番大きなミスってなんですか? 齊藤: それは、画像差し替えプロジェクトのメンテナンス開けでバグがいっぱい出たときですね。 小澤: げんなりしたっていう。 齊藤: こんなに考慮漏れができるのかって思いましたね。 まあ、障害がでるとチーム感が出るというか、協力者が出てきてものすごいスピード感で解消していく過程はそれで楽しいですけどね。 小澤: すぐ直せっていうバグ修正はアドレナリンがでて楽しいですよね。 今後 小澤: 20年ぐらい プログラマー やっていて飽きないですか? 齊藤: 飽きないですね。 小澤: 次から次へあたらしい技術やアイディアが出てきてすごいなと思うんですよ。 齊藤: そうっすね。楽しい仕事です。 伊藤: ライバルはいるんですか? 齊藤: ライバル意識することはないですけど、、、、。 影響を受けるメンバーはいます。 小澤さんは調べるのはやいなと。バグがあったら「そこじゃない?」って速効性があってミニマムな修正を思いつくのが早いですし。 小澤: おぉぉ(笑) 齊藤: 浅香さんは作業の精度がずっと安定していてレスポンスも早いですよね。インフラ面から全体を見られていますし。 メンテナンスのバグ対応でもたくさん助けていただきました。 大川さんは無駄に作らないでツールがあるじゃんていって、新しいツールを使いこなすのも早いですし、考え方がスマートですね。真似できない。 木村さんも勉強熱心で、時代にそって変えて行くべきことを実行してますよね。最近マネージメントよりですけど。 皆さんとても良い刺激になります。 伊藤: みんな最近マネージメントよりな印象ですけど。 マネージメントには興味ないですか? 齊藤: 僕はミーティングがいっぱい入ってくると違うなって思ってしまいます。 リーダーシップはとりたいとは思いますけど、自分も動きたいですね。指示を出すようなロールではなくてメンバーでいたいです。 もっと上手な人が設計や案を出せばそれを実装したいってなると思います。 マネージメントするよりも、足を引っ張るようになるまでは開発を続けていきたいですね。 伊藤: オープンソース に貢献するのは興味ないんですか? 齊藤: そうですね、フォーカスしてやってみたことはないですね。 Rails とか開発しているのは楽しそうですけど、、ユーザーフェイシングな開発している方が面白いと思います。 あとは BUYMA ってレガシーなところがいっぱいあるから、新しいものに変えたいですね。 小澤: アイディアはいっぱい出てきますよね。 齊藤: 悔しいですけど「あれ、C社はできているのにうちはできていないなって」思うところがいろいろあって、そういう負債が消えていってほしいと思ってます(笑)。 若手になにかありますか 伊藤: 若手になにか思うことはありますか? 齊藤: 遠慮しないほうがいいと思います。なにか思うことがあったら提案しておいて、すぐにはできないことも多いですが提案すればだいたい自分がやることになるんで。 あとは障害とかあったら参加してみるとか。オンタイムに障害を克服することが開発者としてサービスに近づく一歩のようなところもあると思います。 うちの人って調べたことのログ張ったりするじゃないですか。それってここまでやったってのもあるし、他の人にもここ見ればいいって伝えたことになってて。 伊藤: 次同じことがあったらそれを見ればいいと。 齊藤: 思い出したらそれを真似してみれば良いんですよ。 昔はそうでした。人も少なかったんで誰もそんな親切には教えてくれなくて。見てればわかるっしょって感じでした。 小澤: 怖い会社ですね。 齊藤: はいったときからそうでした。 (※今も昔も聞けばやさしく答えてくれる優しい人ばかりです) 小澤: 齊藤さんのはじめのころのエピソードで面白かったのは、明日やろうと思ってたものが起きたらできていたって言う話。 伊藤: なんすかそれは。 齊藤: はじめのプロジェクトが二人でこんなサービスを作ってみてっていうのだったんですよ。 その人はやりましたとか言わないでしれっとプッシュしてありました。 伊藤: それは齊藤さんに割り当てられたタスクだったんですか? 齊藤: 今みたいに計画してやっていく感じじゃなくて、ざっくりしてたんですよ。 もうひとりの人は誰に頼らなくても一人でできちゃう人なんで、ついでにやっておきましたという感じです。 それで、やんなかったら全部作られちゃうなと思いました。 伊藤: そんなこともあったんですね。 齊藤: そうですね。それで折れてやめたいとは思いませんでしたが、頑張んなきゃなと思いました。 ちなみに 齊藤: 僕が一番目に指名されたのは(年長者なので)気を使ってですか? 伊藤: そうじゃないです。 小澤: そうじゃないですね(笑)。 誰に聞いてみたいって聞いたら、真っ先に齊藤さんが出てきました。
こんにちは。気が付けば入社から一年が経ち、 新卒の肩書きを失った @sean0628_i です。 4月4日(木)、4月5日(金)の日程で開発合宿を行ってきました。 場所は前回と同じく、 おんやど恵さん にお邪魔しました。 前回の開発合宿 が2017年だったようなので、2年ぶりの開催ですね。 テーマ 今回は『チーム力のアップ』をテーマに設定しました。 前回は個人個人好きなことを行うスタイルだったようですが、 今回はテーマが『チーム力のアップ』でしたので、基本的にはチーム開発で取り組むことにしました。 参加者の希望を聞き入れつつ、実行委員が1-3人のチームに分けました。 そして、チームごと取り組む内容を決定し、合宿に臨みました。 合宿一ヶ月ほど前にチームを発表し、準備期間も設けました。 スケジュール感: 3月初 - チーム発表 3月中 - 内容決定・下準備 4月4日、5日 - 合宿開催 合宿でやったこと 内容に関しては、業務に関する範囲であれば基本的になんでもOK です。 最終的にそれぞれ以下の内容に取り組んでいました。 内容: 1班:STYLE HAUS フロントの性能改善(画像リサイズ) 2班: BUYMA フロントの性能改善 3班:商品画像分類 4班:STYLE HAUS タグのサジェスト 5班:Jenkins のデプロイジョブのGitLab-CI 化 6班:GitLab Runner Autoscale on AWS 当日の様子 東京駅出発 皆さん遅れてくるかなぁと思い15分前集合にしましたが、まさかの全員5分前行動。 出発の20分前には全員集まりました、、、さすがです!!! 開発開始 湯河原駅 周辺で昼食をとって、開発開始です。 テーブルはチームごとです。 キーボード作成が趣味のメンバーは、自作キーボード持参で合宿に臨んでいました。 BUYMA パーカーを着て開発するメンバーも! 夕食 夜は宴会!!! テンションの上がったメンバーがコスプレをし、晩酌をしていると、、、 密告され、部長殿におこられてしまいました。。。 宴会の後は反省して、黙々と開発に励んでおりました。。。 記念撮影 宿の前でパシャり。 2日目はお昼まで開発を行い、 湯河原駅 周辺で昼食を食べ帰路につきました。 まとめ 当日は大きく成果を出せたチーム、あまり成果を出せなかったチームとありました。 ただ、事前準備の期間をおよそ1ヶ月用意したこともあり、どのチームも最低限の成果は出せていたように思います。 また、通常の業務を離れて普段あまり関わることのないメンバーと開発を行うことができて、 参加者それぞれ新たな気づきがあったことと思います。 『チーム力のアップ』という点で見ても、縦・横両方の意味で向上することができました。 私個人としても一日中シニアエンジニアの方に手取り足取り教えていただき、 Serverless、Docker などの知識を深めることができました。 またとない貴重な時間を過ごさせていただきました。 今後も積極的に社内勉強会、開発合宿を開催していきたいと思います。 最後まで読んでいただきありがとうございます。
はじめまして。 BUYMA の iOS アプリチームでデザインを担当しています。 この記事を読んでくださりありがとうございます。 2019年2月に、お問い合わせ機能をネイティブ化しました。 より使いやすくするために、Webviewからネイティブ化し、あわせてリデザインすることになりました。 Webとアプリを作業する上で、デザイナーとして苦労した部分を、新しいデザインと一緒に書いていきたいと思います。   まず、 BUYMA の iOS アプリってどんな感じ?   BUYMA はショッピングサービスなので必要なページがたくさん。 現状、アプリの半分くらいが、Webviewです。 ネイティブ化されたページとWebviewのページが混在しているため、ページによってデザインテイストが異なっていることを課題のひとつとして感じています。 ユーザーがアプリを起動してから目的を達成するまで、気持ちよく使えるよう統一されたデザインにしていきたいと考えています。   実際にデザインするときに考えていること2つ   1・デザインのトレンドを押さえた ユーザーインターフェース 古っぽいデザインは、扱っているコンテンツさえ古く感じさせてしまうのでもったいないです。特に BUYMA では、日本未入荷のレアアイテムを揃えていたり、最新のトレンドを発信しているサービスのため、サービス ブランディング の観点からも、とても大事になってくるポイントだと考えています。 そのためまずはとにかく他社リサーチに時間をかけています。国内外の人気アプリのデザインをチェックしてよく使われているUIを参考にしたり、海外のデザインギャラリーを漁ってアイディアの引き出しを増やしています。 よく参考にしている海外デザインサイト 2・ユーザーが目的を達成するためのデザイン トレンドだけ追いかけて「とりあえず流行りのデザインを取り入れたけど、実は使いにくかった」となっては本末転倒です。そのため、ユーザーが目的を達成しやすいデザインかどうか、という視点が重要になってきます。 そのページではユーザーに何を達成してほしいのかを明確にすること、そしてそれを達成するためには、ユーザーにどういうアクションをしてほしくて、きちんとアクションしやすいデザインになっているか、ということを考えながらデザインを行っています。   お問い合わせ画面の目的   BUYMA はCtoCサービスです。注文がはいるとパーソナルショッパーが買い付けに行き、商品を送ってくれるサービスです。 商品によって、配送日数が異なっていたり、買い付けに行ったけど在庫が無かったというケースが発生する場合があります。 もしユーザーが BUYMA を、在庫を持つ一般的な ECサイト だと思って使っていた場合、「想定よりも配送日数が長かった」「注文したのにキャンセルされた」といった体験はユーザーのペインポイントになりかねません。 このペインポイントは注文前に商品について事前問い合わせをすることで解消できます。 お問い合わせ画面では、このようなペインポイントを解消することを目的とし、2点を達成することが必要だと考えました。 BUYMA はCtoCサービスだと伝えられていること パーソナルショッパーへ在庫や商品詳細についてお問い合わせがしやすいデザインであること   現状のお問い合わせ画面の課題   それでは本当にCtoCサービスということが伝えられているのか、お問い合わせがしやすいデザインになっているのかということを念頭におき、現在のデザインから改善できるポイントがないかを考えていきます。 ▼現状のデザイン ▼現状の課題 CtoCサービスということが伝えられているのか → 1 パーソナルショッパーへの問い合わせフォームだということが伝わりづらいため、CtoC感がなくハードルが高い印象を与えかねない。 お問い合わせがしやすいデザインになっているのか → 2 商品ページからお問い合わせボタンをもっとタップしてもらいたいので、現状よりもボタンに気づきやすくしたい。 → 3 お問い合わせ一覧ページは、なんのアクションをしてほしいページなのか分かりづらい。 以上の課題に対してリデザインしていきます。   課題に対するアプローチ   ▼改修後のデザイン ▼課題に対する変更点 1 ・CtoC感がなくハードルが高い印象を与える。 メッセンジャー アプリに寄せたUIで問い合わせのハードルを下げ、気軽さを出しました。チャット形式のUIは、サイトへの問い合わせではなく、パーソナルショッパーとの人対人のコミュニケーションであることを意識づけられたと思います。 パーソナルショッパーのアイコンをナビゲーションバーの右位置に表示し、常に目に入るようにして、パーソナルショッパーの存在感を出しました。アイコンをタップすると、パーソナルショッパーページや出品アイテム一覧を見ることができます。 他人のお問い合わせ内容を見やすくしました。改修前はお問い合わせ内容がトグルに包まれて一文しか出ていなかったものを、全文出してひとめでわかるようにしました。他人のお問い合わせ内容が目に入ることで、 BUYMA はCtoCサービスであり、在庫確認などの問い合わせが必要であることを伝えられたのではないかと考えています。   2 ・商品ページのお問い合わせボタンをもっとタップさせたい。 商品ページのお問い合わせボタンの領域を広め、件数を表に出すことで、他の人の活発なお問い合わせのやりとりに気づきやすくなるようにしました。結果、問い合わせボタンのタップ数を130%増やすことができました。   3 ・お問い合わせ一覧ページは、なんのアクションをしてほしいページなのか分かりづらい。 新規問い合わせボタンをアイコン化して視覚的に認知しやすいようにしました。また、スクロールしてもボタンを画面右下のポジションでフィックスさせることで、ユーザーがどのタイミングでも問い合わせフォームへ進めるようにしました。 新規問い合わせボタンと同レベルで目立っていた「指名リク エス ト(※)」のボタンの優先順位を下げて奥へ場所を変更しました。指名リク エス トボタンの優先順位を下げたことで、クリック数は減りましたがリク エス ト数に影響は無かったため、ページで誘導したいアクションをよりシンプルにできました。 ※「指名リク エス ト」とは、自分が探している商品を世界中にいるパーソナルショッパーにリク エス トすることで自分の代わりに欲しい商品を探してもらうことができるサービスのこと。   Webデザインとアプリデザインの違い   Webページのデザインの場合は Photoshop でデザインし、コーディングまで担当しますが、 アプリデザインに関してはSketchで作成したデザインをZeplinにエクスポートして、エンジニアに実装してもらっています。 ちなみに、Webデザインの頭でアプリデザインをしてもエンジニアからの手戻りが発生することが多いです。 わたしはWebとアプリの違いを理解しないまま、アプリのデザインをはじめてしまったので修正対応に苦労しました。 たとえば… ・このボタンを押したときのボタンの挙動は?アニメーション入れる? ・ここはページの遷移はモーダルなのか?プッシュなのか? ・可変要素で無いパターンの場合、高さを詰めるか? ・アラートやアクションビューの要不要 ・タブバー隠すか出すか ・なんか実装してみたらここの動きがヘン Webページのデザインでは考慮しないようなポイントのため、アプリのデザインをするときはアプリの仕様をおさえておくことが必要です。 わたしは、モーダルとプッシュの役割の違いから勉強しなおしました。 わたしが今回特にお世話になった記事はこちらです。 ユーザインターフェイスのデザインのヒント - Apple Developer iOS ヒューマンインターフェースの原則 初心者がアプリのUXデザインで押さえておくべき9つの原則 [iOS版]アプリのUIデザイナーとして働き始める時に覚えておくと便利な用語 わたしと同じように、これから iOS のアプリデザインに挑戦するという人がいたら、ひとまず Apple のヒューマンインターフェース ガイドライン は必読です。 アプリの仕組みを理解することで、エンジニアとの共通言語が増え、コミュニケーションもより円滑になります。   BUYMA の iOS アプリチーム   とこんな感じで、 iOS エンジニアに助けてもらいながら、お問い合わせ画面を新しいデザインにすることができました。 Webデザインをするとき、HTMLや CSS のコーディングのことまで考えながらデザインしますが、 同じようにアプリデザインでもアプリエンジニアの実装のことも考慮してデザインできるデザイナーでありたいなと思います。 デザイナーだけでデザインをするというよりは、チームみんなで良いプロダクトになるよう日々ディスカッションしています。 BUYMA の iOS アプリチームのメンバー構成は、デザイナー・エンジニア・MDでアプリの改修やブラッシュアップをおこなっています。 わたしたちと一緒に iOS アプリをつくっていくメンバーを募集しています。 現在ディレクターが不在のため、特にディレクター職の応募をお待ちしています! www.enigmo.co.jp
はじめに この記事は Enigmo Advent Calendar 2018 の25日目です。 BUYMA でプロダクトマネージャー・ディレクターのようなことをしています。 機械学習 に関する案件を初めて進めてみようと思い、 プロダクトマネージャー・ディレクター目線 で 、やってきたことや学んだことをまとめます。 知識がなくてもプロジェクトや案件は進めれるとは思いますが、ある程度理解があることで、プロジェクトの幅も広がりますし、エンジニアとのコミュニケーションも円滑になりますし、 何より自分も楽しいです 。 また、もし 機械学習 に関して知見がない会社でプロジェクトを進めていく場合の参考になればと思います。 この記事の対象者 非エンジニア の プロダクトマネージャー・ディレクター  向けの記事です。 プロジェクトや案件で 機械学習 を利用しようと検討しているがどうしたらよいかわからない 自分で 機械学習 学びたい!せっかくなら、プロジェクトや案件にしたい!(自分はこのタイプです) という方向け。 対象ではない方 技術的な話とかかわらない方 既に 機械学習 を利用してプロジェクトを推進している方 機械学習 に関わるプログラミングを実施している方 エンジニアの方 全体の流れ 1.基本知識のインプット 2.実際にコーディングもやってみる 3.プロジェクト・案件にするまで 基本知識のインプット ゼロからスタートする場合は、何から初めてよいのか?と悩むことも多いと思います。 一番最初にやってみた まず参考にしたのが下記の記事 【保存版・初心者向け】独学でAIエンジニアになりたい人向けのオススメの勉強方法 網羅されていて、わかりやすいが、プロダクトマネージャー・ディレクターとしては、ここまではいらない。(実際にやってみて途中でいろいろ挫折。時間もかかる) その中で、やってきたことをプロダクトマネージャー・ディレクター向けに書きます。 下記に出てくる記事や本を読めば、最低限は大丈夫ではないかなと思います。 STEP1:まず 機械学習 を知る 例に漏れずここから。流し読みでもかなり面白い。 人工知能は人間を超えるか (角川EPUB選書) 読み終わると AIってなんかすごそうってなる なんかすごくAIがわかった気持ちになれる エンジニアが言っていることが何となくわかるようになる STEP2 : Python を知る 実際にコーディングもやることを考えると、ディレクターでも Python は必須です。 全く触れたことがなかったので、本当に簡単なところから。 ドットインストール 動画を通勤中や移動中に見る。理解しようとせず最初は、流し見でよいと思う。 3回ほど聞くと、なんとなくわかってくる。 終わると 動画なら、なんでも良いと思う Python がわかった気になれる Python を書いてみたくなる エンジニアの言っていることがわかるようになる Python3入門ノート 非常にわかりやすかった。 エンジニアではないので、日々コーディングしないと言語は習得が難しいので、なんとなく理解する程度で、読み飛ばす。 その中でも、読んでおくとよさそうのは、 Part3の「Numpyの配列」 ただ、読んでもほぼ忘れる。 あと、読み進める中で気になるところは、写経して実際にコードを書いみると良い。 一番重要なのは、 あとから困ったときに調べるために使う こと 。 読み終わると Python がかけて嬉しくなる エンジニアの仲間に入れてた気がする STEP3(+α):Courseraのmachine-learning 無料 でここまで使える学習ツールはすごい! 機械学習 のロジック部分で、実際にはCourseraを全くやっていなくてもコーディングはできる。知っておくとパラメーターチューニングの意味を理解できる面白い。 通勤中などの移動中に聞ける。 英語できなくても、日本語も用意されているのでほぼ問題ない。 こういう記事 からも力をもらいながら、完了。 Courseraのmachine-learning 学習を終えると なんか 機械学習 の理論がわかった気になれる ただ、本当にすぐに忘れる アンドリュー先生(講師の先生)が優しすぎて好きになる 実際にコーディングもやってみる PandasとMachineLearning KaggleのLearn がめちゃくちゃ良い。しかも 無料 。 英語だけれど、 Google翻訳 を使えばほぼ問題なく進められる。 機械学習 を実際に行う際のベースとなる部分なので、これはやっておくことをおすすめする。ただ、使わないと忘れるので注意。 本を読みながら深掘りしてみる pythonで始める機械学習  を読んで、気になるところは写経してみる。 実際のプロジェクトをする際は、 機械学習 のパラーメーターチューニングなどはディレクターには求められていないと思うので、1章から3章くらいまでやれば十分だと思う。 読み終わると 機械学習 のコードが書けて、嬉しくなる なんか実際のデータでやってみたくなる (補足)コーディングに利用するツール 自分で環境構築できる方はぜひそちらで。 環境構築ができない/しない方は、 Google の Colaboratory でやるのがおすすめ。 これも無料で簡単なので、僕もこれを使いました。めっちゃ楽です。 プロジェクト・案件にするまで はじめに プロマネ として、 機械学習 プロジェクトを始めるならこれは必読書です。 仕事ではじめる機械学習 本書では、 機械学習 やデータ分析の道具をどのようにビジネスに生かしていけば良いのか、また不確実性が高いと言われている 機械学習 プロジェクトの進め方について 整理しています。 本書はもともと、 機械学習 の初学者向けに書いた文章からはじまりました。入門者のために書きはじ めたのですが、実際には理論を軽めにしたソフトウェアエンジニア向けの実践的なカタログのような形 になっています。 アルゴリズム の話などは他の書籍でも数多く取り上げられているので、本書ではプロジェクトのはじ め方や、システム構成、学習のためのリソースの収集方法など、読者が「実際どうするの?」と気にな るであろう点を中心にしています とある通り、 プロジェクト始めたいけど、でどうするの? ということが書いてありますので、実施にプロジェクトを進める方にはすごく学びが大きいかと思います。 実際に、「その作業必要?」のようなリソース判断をすることや、全体像を理解することで手戻りを少なくする一助にもなるかと思います。 この要約 がわかりやすかったです。 読み終わると 機械学習 のプロジェクトは思っているよりも多くのことがあるのだと知る 実際にプロジェクトをやってみたくなる 実際にやったこと 本書の中で重要だと思ったのは、 機械学習 を使うことを目的化しないということでした。 機械学習 の本なのに、 別に 機械学習 を使う必要は必ずしもない  と書いてあり驚きました。 実際に案件化していくステップとしては、 1.ユーザーの課題を明確にする 2. 機械学習 を使わずに解決する方法を考える 3.どうしても必要な場合は 機械学習 を活用する です。 機械学習 の有無にかかわらず、 ユーザーの課題を明確にする ことからスタートして、 機械学習 を解決策の一つ として持っておく感覚かと思います。 STEP1:ユーザー課題を明らかにする プロジェクトを進めるにあたり、課題を明確化します。 実際に僕らが進めた場合は、経営陣やビジネスサイドの方と、 1.経営陣が認識している事業課題 2.現状現場が認識しているサービス課題 3.サービス以外にも、現場メンバーの作業に関する課題 など、様々なレイヤーでサービス課題/業務課題に関するディスカッションする機会を取りました。 課題を俯瞰して考えることで、課題理解も広がり、実際モチベーションも上がりました。 このステップを終えると やっぱり〇〇はすごい解決したいよね〜(サービスの大きな課題)を再認識できます 普段そんな作業をしてるのか。。。という現場のリアルを知れるます 他の部署の人と仲良くなります 実際にやる前提で話をするので、すごく楽しいです。 STEP2:課題の解決策を考えて、進める 初めて案件を進めるとなれば、どのように案件を進めるかが難しいかと思います。 はじめての 機械学習 関連のプロジェクトということもあり意識したのは、いかに ライトに着地させて一定の効果を出す かでした。 そのための課題設定と解決策を決めます。 本当は、これが理想。 ただ、簡単に見つからないですよね。。。 実際に進めていくなかで、最初の案件として一番しっくりきたのはこういう施策でした。 機械学習 を取り入れる理由が明確にあり 、且つ 工数 も比較的小さいもの。 ここを見つけてプロジェクト化していくことで、進みやすくなるかと思います。 実は、ここで言う開発 工数 とは、 機械学習 の基盤を作成する 工数 は除いて考えています。 機械学習 の運用基盤を作成するのは 工数 がもちろん高いので、 できるだけ使い回せるロジックにすること 一度作成すれば、それほどアプリケーション開発の 工数 が変わらないこと が重要なので、複数案件化できる状態にしておくことで基盤を作りやすくなるかと思います。 STEP3:巻き込む人を間違えない 前出の まとめ記事 にもありましたが、案件を進める上で上記のメンバーは巻き込む必要があります。 そこで間違えると話が進まないので、初めてプロジェクトを進める場合は気にしてみてください。 まとめ/終わりに ここまでお読みいただきありがとうございました。 はじめての記事でしたが、少しでも参考にしていただければ嬉しい限りです。 基本的なインプットから、コーディング、プロジェクト化など一通りやってみて非常に勉強なりました。 実際にコーディングもプロジェクトもやってみることが一番だと思いました。 案件を考えるときに「 機械学習 」という選択肢を持てるのは、 プロマネ としては強みにな今後なりうるのだろう思います。 ただ、選択肢の一つということを意識することは重要 機械学習 を使う!と言いながら、やはりいちばん大切なのは、「ユーザーの課題は何か?」という問いに尽きると改めて思いました。 実際に、知見がない中で初めてプロジェクトを進める場合は、関わるメンバーや案件は慎重に進めるとよいと思います。 時代の後押しはあるので、比較的会社も挑戦を応援してくれると思います。
概要 エニグモ サーバーサイドエンジニアの @gugu です。 この記事は Enigmo Advent Calendar 2018の24日目 です。 関数型言語 って結局何なの?と思ったので調べてみました。私が疑問に思ったことをベースに調べた内容を記載していこうと思います。 ※ 関数型言語 として主に Haskell をメインに調べているので、 関数型言語 すべての話ではない記述があるかもしれません。 対象者 オブジェクト指向 は知っているけど、 関数型言語 は知らないって方。 私と同じような 関数型言語 への疑問を持っている方。 参考書 すごいH本 (この略し方、、、センス良い!) 参考サイト https://qiita.com/stkdev/items/5c021d4e5d54d56b927c https://qiita.com/arowM/items/0305d4f439752f285438 https://qiita.com/hiruberuto/items/26a813ab2b188ca39019 https://qiita.com/wcsakurai/items/5c83a0dc8c8175861902 ありがとうございます! 関数型言語 の疑問 「関数型」っていうけど手続き型でも オブジェクト指向 でも関数を書くじゃん? 私がはじめに思った疑問です。みなさんもそう思いませんか? 下記のようです。 関数型言語 の関数というのは副作用のない純粋な関数のこと。(詳しくは後述) すべてが関数でできている。 Haskell だとif文も関数なのでelseの省略は不可。※言語によってそれぞれ例外あり。(そもそも何か値を返すのが「関数」、返さないのが「プロシージャ」と呼ぶ。 C言語 からプロシージャも含めて「関数」と呼ぶようになったとか。) オブジェクト指向 ではクラス内に関数を書く。(お作法的に。なぜこんな話をするのかは後述) 関数型言語 の中でもマルチ パラダイム 言語って両方使えて最強じゃね? 結論から言うとそうでもないらしいです。 その前に 関数型言語 の種類について説明。。。 純粋 関数型言語  → その名の通り関数型のみ。例: Haskell とか マルチ パラダイム 言語 → 関数型と オブジェクト指向 の両方の仕組みを備えている。例: Scala とか オブジェクト指向 が備わっているということは副作用が発生する可能性が増えるということ、つまり 関数型言語 の本来の目的とズレてしまっていることになります。(もちろん適材適所で オブジェクト指向 と関数型で使い分けられるメリットはあるかと。) 別に 関数型言語 でなくても オブジェクト指向 で副作用なく作れば良いのでは? まずは先に副作用がないとはどういうことか 雑な説明かもしれませんが、変数が途中で変わらず参照透明であることです。 束縛 関数型言語 で変数に「代入」することを「束縛」と呼びます。代入と違う点は一度値を入れると変更できません。 x = 1 x = 2 -- error 参照透過性 引数が同じなら返り値も必ず同じになる関数のことを「参照透過性」と言います。 で、 オブジェクト指向 でも参照透明に書けば良いのでは? その通りで、 オブジェクト指向 でも副作用を避けて書くことは大事です。ですが、そもそも言語としてのアプローチが異なります。 関数型言語 :副作用を排除する。というより普通に書けば排除される。 オブジェクト指向 : カプセル化 して副作用を内側へ隠蔽する。 関数型言語 は副作用が起きづらい?だから? 下記メリットがあります。 テストや保守を容易にする バグがおこりずらい 再利用可能な部品を作りやすい (注目)遅延評価 遅延評価 副作用がないので実現可能に。 使用するときに一度だけ計算されキャッシュされます。無駄な処理を省くことができます。 関数型言語 ってどうやって書くの? これが代表例なのかわかりませんが、私が一番なるほどと思ったのはループ文です。 関数型言語 は関数がメインなのでループをさせる際はfor文ではなく 再帰 を使用します。( Haskell だとそもそもforもwhileも無いみたいです。) Wikipedia(関数型言語) で、 オブジェクト指向 、 関数型言語 どちらで書くべき? すみません、わからないです。。。(汗) 個人的な意見だと、やはり堅牢でテストコードの書きやすい 関数型言語 がベターなような気がしますが、 オブジェクト指向 のほうが「オブジェクト」を自然と意識して作れるので設計しやすいような気もします。ただ書き慣れているだけかもしれないのでやはり 関数型言語 なのでしょうか。 関数型言語 の勉強に Scala は向いていないかも。。 関数型言語 の勉強で Scala を勉強していたのですが、結局は オブジェクト指向 脳を使って オブジェクト指向 で書いてしまうので、なにが 関数型言語 の特徴なのか理解しづらいです。 関数型言語 を学びたければ純粋型を学ぶのが良いかと思います。 まとめ 実際私が 関数型言語 への疑問を中心に書いたので、偏った知識かもしれません。しかし同じような疑問を持っている方に少しでも役にたてばと思って書いてみました。 調べた感じだと 関数型言語 は良いことづくしなような気もしますが、数学的でやはりとっつきにくいイメージも大きいかと思いました。結局は「 関数型言語 を知りたければ 関数型言語 で書くべし!」なんでしょうね。。 関数型言語 と仲良くなれるようにがんばります!
この記事は Enigmo Advent Calendar 2018の23日目 です こんにちは。 iOS チームでエンジニアをやっています。 Codable使ってますか? iOS チームでは、 Alamofire + Codable で ネットワークまわりの実装を行なっています。 最初はいいのかわからなかったのですが、今ではなくてはならないものになっています。 すごく便利すぎて、Codable無しじゃ開発できない!そんな生活を送っています。 Codableについて軽く説明からの、実際使ってみて、Codableの得意なところと苦手なところを書いていこうと思います。 TL;DR Codable良いから使ってみて! Codableになれると、 Enum をたくさん使うようになる Codableとは Swift4からFoundationに追加されたtypealiasです。 ( Codableは、 プロトコル ではありません ) Encodable と Decodable の二つ プロトコル に準拠します。 https://developer.apple.com/documentation/swift/codable Codable、何に使うの? JSON を扱う際の、 エンコード / デコード を Codableを使い、簡易に表現します どうやって使えばいいの? 例えば itunes . apple .com から取得できる JSON から今リリースしているアプリのVersion知りたい!という時に、使えます。 JSON : { " resultCount ": 1 , " results ": [ { .... " sellerUrl ": " https://www.buyma.com ", " trackName ": " BUYMA(バイマ) - 海外ファッション通販アプリ ", " currentVersionReleaseDate ": " 2018-12-12T06:02:14Z ", " version ": " 3.3.0 ", " minimumOsVersion ": " 10.0 ", ... } ] } Codable: struct AppInfoResponse : Codable { let results : [Results] struct Results : Codable { let version : String } } すごい簡単ですね。 Codableの良さ Foundation 純正 純正なので、Swiftのバージョンが上がった際に、オンタイムでアップデートされている サードパーティ のライブラリの場合、そのライブラリのアップデート対応が終わるまで Xcode /Swiftのバージョンを上げれない JSONDecoderのカスタムもいける API のレスポンス内のデータで、Date / DateTimeクラス が 2018-11-4 23:59 や 2018-06-24T23:59:59+09:00 のように揃っていないケースがあります JSONDecoder の dateDecodingStrategy を .custom にすることで、様々なケースのFormatに対応することができます decoder.dateDecodingStrategy = .custom { let container = try $0 .singleValueContainer() let string = try container.decode(String. self ) let formatter = DateFormatter() /// Date format: ISO_8601 formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" if let date = formatter.date(from : string ) { return date } formatter.dateFormat = "yyyy-MM-dd HH:mm" if let date = formatter.date(from : string ) { return date } return Date() } Codableの苦手なところ nil で返さず、空文字で返すと失敗する URLがあるけど、URLになっていない struct UserResponse : Codable { let name : String let imageUrl : URL ? enum CodingKeys : String , CodingKey { case name case imageUrl = "image_url" } } 成功: { " name ": " P ", " image_url ": " https://www.buyma.com/image " } </ pre > 失敗: < pre > { " name ": " P ", " image_url ": "" // 空文字 } 対応策 image_url を 空文字ではなく、 nil にしてもらう init(from decoder: Decoder) を実装する e.g.: init (from decoder : Decoder ) throws { let container = try decoder.container(keyedBy : CodingKeys.self ) name = try container.decode(String. self , forKey : .name) imageUrl = try? container.decode(URL. self , forKey : .imageUrl) } Arrayの中に、様々な Classが混ざっているケース "topics" の各Objectの "type" を見ないといけない: { " topics ": [ { " type ": " sale ", " title ": " 夏のセール開催中! ", " products ": [ ] } , { " type ": " news ", " title ": " 夏のセール開催中! ", " image_url ": " https://www.buyma.com/image ", " link ": " https://www.buyma.com/ " } , { " type ": " topic ", " title ": " 韓国ブランド集めました! ", " search_url ": " https://www.buyma.com/search " } ] } 対応策 type 見るCodableを使って、一度どの type になるのかを判定してから再度デコードする チームでCodableをどうやって使っているか 開発フロー API を cURL で叩いて json を取得 Codable準拠したstruct Responseを作成する cURL で取得した json ファイルを使用してstruct Responseにデコードされるかテストを実装する サンプルコード ネットワーククライアントのデコード処理: func decode (_ type : T.Type , from data : Data ) -& gt; T? { do { return try decoder.decode(type, from : data ) } catch { print( "---- API Parse Error ---" ) print(String(bytes : data , encoding : .utf8) ?? "" ) print( "Error Description: \(error) " ) return nil } } Codableができているかどうかのテスト: class ResponseTests : TestCase { func testDecodeResponse () { guard let path = Bundle( for : type (of : self )).path(forResource : "ResponseSample/sample" , ofType : "json" ), let fileHandle = FileHandle(forReadingAtPath : path ) else { fatalError() } guard let response = decoder.decode(SampleResponse. self , from : fileHandle.readDataToEndOfFile ()) else { fatalError() } XCTAssertNotNil(response) } } まとめ Codableにすることで、 CodingKey に準拠した CodingKeys を書かないといけない手間はありますが、それを書いてもメリットが大きいです 苦手はありますが、 API 設計に起因する部分が多いと思うので、チームで API を相談する際に、何が苦手なのかを伝えるといいと思います SwiftyJson, ObjectMapperと使っていましたが、ほぼCodableに移せました
この記事は Enigmo Advent Calendar 2018の22日目 です。 はじめに https://atarimae.biz/archives/7374 交番と犯罪件数が正の相関があるからといって、交番を減らして犯罪件数は減らないですよね。 さて、データ分析を行う上では、相関関係と因果関係を切り分けることが重要になることがあります。 例えば、KPIとある数値xが相関しているとします。 x → KPI という因果関係であれば、xの操作でKPI向上の施策を検討することができます。 逆に、KPI → x という因果関係であれば、xを操作してもKPIは変化しません。 y → x なのか、x → y なのか、xとyの相関関係の有無だけでは、因果関係は分かりません。 この記事では、 機械学習 ブロフェッショナルシリーズ、 統計的因果探索 を参考にしています。 なお、、z → x , z → y という未観測の共通原因(交絡因子とも呼ばれる)zが存在する場合についても、書籍では扱われていますが、今回の記事では未観測の共通原因については扱わないことにさせていただきます。 LiNGAM , の2変数の関係bを以下のように構造方程式とう呼ばれるモデルであると仮定します。 , の2変数で現れ、 , は外生変数がノイズ項です。 なお、LiNGAMでは非 ガウス分布 として扱います。 因果関係の推定の結果、 -> という因果関係がある場合は、 となり下記のようになります。 ここで、LiNGAMは以下の方法があります (1)独立成分によるによるアプローチ (2)回帰分析と独立性によるアプローチ 独立成分によるによる推定 既に Python で実装している方がいらっしゃるので、このアプローチに関してはそちらを参考にさせていただきます。 https://github.com/ragAgar/LiNGAM モジュールとともに、lingam.pyをimportします。 import pandas as pd import numpy as np from lingam import LiNGAM scikit-learn付属のボストン不動産デー タセット を使います。 価格をyにするのが自然ですが、敢えて 価格:x 部屋数:y とします。 boston = load_boston() df = pd.DataFrame(boston.data, columns=boston.feature_names) x = boston.target y = df.RM.values 推定を実行します。 data = pd.DataFrame(np.asarray([x, y]).T, columns=[ 'target' , 'rooms' ]) lg = LiNGAM() lg.fit(data) 推定結果 rooms ---| 9.102 |---> target 部屋数が上がると、価格が上がる。という正しそうな因果関係を推定することができました。 回帰分析と独立性によるアプローチ 本を Python で実装しました。ここでは、因果の向きのみの推定を行います。 なお、 エントロピー は近似であることが本では述べられており、近似式の導出については、参考文献を読む必要があります。 def calc_r (x, y): return ((x - np.mean(x * y) - np.mean(x)*np.mean(y)) / np.var(y) * y, (y - np.mean(x * y) - np.mean(x)*np.mean(y)) / np.var(x) * x) def normalize (x): return (x - np.mean(x)) / np.std(x) def entropy (u): Hv = ( 1 + np.log( 2 * np.pi)) / 2 k1 = 79.047 k2 = 7.4129 gamma = 0.37457 return Hv - k1*(np.mean(np.log(np.cosh(u))) - gamma)** 2 - k2 * (np.mean(u * np.exp(- 1 * u** 2 / 2 )))** 2 def lingam_reg (x, y, columns): xr, yr = calc_r(x, y) m = entropy(normalize(x)) + entropy(normalize(xr) / np.std(xr)) \ - entropy(normalize(y)) - entropy(normalize(yr) / np.std(yr)) if m >= 0 : print ( '{0} ---> {1}' .format(columns[ 0 ], columns[ 1 ])) else : print ( '{0} ---> {1}' .format(columns[ 1 ], columns[ 0 ])) 推定を実行します。 data = pd.DataFrame(np.asarray([x, y]).T, columns=[ 'target' , 'rooms' ]) lg = LiNGAM() lg.fit(data) 推定結果 rooms ---> target こちらも正しそうな推定結果を偉ました。 部屋数以外の変数でもやってみる INDUS: 小売業以外の商業が占める面積の割合 y = df.INDUS X = pd.DataFrame(np.asarray([x, y]).T, columns=[ 'target' , 'INDUS' ]) lg = LiNGAM() lg.fit(X) lingam_reg(x, y, columns=[ 'target' , 'INDUS' ]) 推定結果 INDUS ---|- 0.648 |---> target INDUS ---> target 小売業以外の商業、つまりオフィスの面積の割合が増えると、不動産価値は上がる。正しい気がする。 税率 y = df.TAX X = pd.DataFrame(np.asarray([x, y]).T, columns=[ 'target' , 'TAX' ]) lg = LiNGAM() lg.fit(X) lingam_reg(x, y, columns=[ 'target' , 'TAX' ]) 推定結果 target ---|- 8.586 |---> TAX target ---> TAX 不動産価値が高いと、税率が上がる。今までは逆の因果関係だが、この場合は正しそう。 最後に ごく簡単な例ですが、統計的因果探索であるLiNGAMを体験することができました。 前述の構造方程式の通り、変数間の関係が線形である等、仮定があることは注意しなければいけませんが、あくまで因果関係の向きを推定する上では、その仮定の上でも議論が進められると思います。 また、こちらの取り組みについても興味深いです。 https://qiita.com/MorinibuTakeshi/items/402cb905e70655724d35 未観測の共通要因については今回割愛してしまいましたが、LiNGAMは因果推論という枠組みの一つの取り組みであり、傾向スコアやグレンジャー因果性といった取り組みについても今後学習を進めていきたいと思います。
エニグモ の  @takurokamiyoshi です。 この記事は Enigmo Advent Calendar 2018 の21日目の記事です。 私は主にフロントエンド周りの実装や ディレクション 業務を行っています。 弊社ではファッション ECサイト である BUYMA を運営しており、 利用者とのコミュニケーションツールとしてメールやアプリ、そしてLINEも重要なツールだと考えています。 BUYMA LINEアカウントについて https://www.buyma.com/contents/line/ これからLINEアカウントの友だち数も増やして、LINEアカウント内でいろんな施策を実施したいと思っています。 LINE Messaging API とは? LINE社が提供するLINEカウントを通じて、ユーザとの双方向コミュニケーションを実現する API です。 メール施策で実施しているシナリオ配信のような配信に利用にしたり、ボットとして活用しているLINEアカウントも多いようです。 ※とりあえずLINE Messaging API 試してみたい人は無料で試せるLINE Developer Trialプランがあります。LINEアカウントを作成いただいて curl でLINE Messaging API をコールしてもらえればと思います。 Flex Messageとは? LINE Messaging API ではテキスト、画像、動画、カ ルーセル といった様々なメッセージタイプでメッセージを配信することができます。 Flex Messageは複数の要素を組み合わせてレイアウトを自由にカスタマイズできるメッセージです。 html、 css に近い感覚でメッセージレイアウトを考えることができます。 Simulator Flex Message Simulator LINE社が提供しているものになります。 Simulatorなしでゼロから作成するのはかなり困難で Flex Message Simulatorではサンプルがいくつか準備されているものでこれをベースに考えると良いと思います。 Flex Messageはレイアウトの自由度が高いのでいろいろ検討が必要だと思い今回の記事を作成しようと思いました。 レイアウト案 いくつかの案を記載していきます。 ※ curl の{XXX} の部分は各環境で置き換えてください。 ※ curl は長くなったので一部改行、空白なしにしています。 縦積みパターン とりあえず何かに使えそうだと思います。 curl -v -X POST https://api.line.me/v2/bot/message/push \ -H ' Content-Type:application/json ' \ -H ' Authorization: Bearer {channel access token} ' \ -d ' { "to": "{userId}", "messages":[ { "type": "flex", "altText": "this is a flex message", "contents": {"type":"bubble","body":{"type":"box","layout":"vertical","spacing":"none","margin":"none","contents":[{"type":"image","url":"{url}","aspectMode":"cover","aspectRatio":"5:2","size":"5xl","action":{"type":"uri","uri":"{uri}"}},{"type":"image","url":"{url}","aspectMode":"cover","aspectRatio":"5:2","margin":"sm","size":"5xl","action":{"type":"uri","uri":"{uri}"}},{"type":"image","url":"{url}","aspectMode":"cover","aspectRatio":"5:2","margin":"sm","size":"5xl","action":{"type":"uri","uri":"{uri}"}},{"type":"image","url":"{url}","aspectMode":"cover","aspectRatio":"5:2","margin":"sm","size":"5xl","action":{"type":"uri","uri":"{uri}"}},{"type":"image","url":"{url}","aspectMode":"cover","aspectRatio":"5:2","margin":"sm","size":"5xl","action":{"type":"uri","uri":"{uri}"}}]}} } ] } ' 商品一覧パターン スマホ サイトの商品一覧のような形のレイアウトです。 もっと長くしてもよいかなとも思いました。 curl -v -X POST https://api.line.me/v2/bot/message/push \ -H ' Content-Type:application/json ' \ -H ' Authorization: Bearer {channel access token} ' \ -d ' { "to": "{userId}", "messages":[ { "type": "flex", "altText": "this is a flex message", "contents": {"type":"bubble","hero":{"type":"image","url":"{url}","size":"full","aspectRatio":"10:3","aspectMode":"cover","action":{"type":"uri","uri":"{uri}"}},"body":{"type":"box","layout":"horizontal","spacing":"md","contents":[{"type":"box","layout":"vertical","flex":1,"contents":[{"type":"image","url":"{url}","aspectMode":"cover","aspectRatio":"1:1","size":"xxl","action":{"type":"uri","uri":"{uri}"}},{"type":"image","url":"{url}","aspectMode":"cover","aspectRatio":"1:1","margin":"md","size":"xxl","action":{"type":"uri","uri":"{uri}"}},{"type":"image","url":"{url}","aspectMode":"cover","aspectRatio":"1:1","margin":"md","size":"xxl","action":{"type":"uri","uri":"{uri}"}}]},{"type":"box","layout":"vertical","flex":1,"contents":[{"type":"image","url":"{url}","aspectMode":"cover","aspectRatio":"1:1","size":"xxl","gravity":"bottom","action":{"type":"uri","uri":"{uri}"}},{"type":"image","url":"{url}","aspectMode":"cover","aspectRatio":"1:1","margin":"md","size":"xxl","action":{"type":"uri","uri":"{uri}"}},{"type":"image","url":"{url}","aspectMode":"cover","aspectRatio":"1:1","margin":"md","size":"xxl","action":{"type":"uri","uri":"{uri}"}}]}]},"footer":{"type":"box","layout":"horizontal","contents":[{"type":"button","action":{"type":"uri","label":"More","uri":"{uri}"}}]}} } ] } ' 細いメッセージパターン 意外と需要がありそうです。 リッチメニューを出しているとメッセージが見える範囲が狭くなりますし、画像作成は必要ありません。 curl -v -X POST https://api.line.me/v2/bot/message/push \ -H ' Content-Type:application/json ' \ -H ' Authorization: Bearer {channel access token} ' \ -d ' { "to": "{userId}", "messages":[ { "type": "flex", "altText": "this is a flex message", "contents": {"type":"bubble","body":{"type":"box","layout":"horizontal","contents":[{"type":"button","style":"primary","action":{"type":"uri","label":"リンク1","uri":"{uri}"}},{"type":"button","style":"secondary","action":{"type":"uri","label":"リンク2","uri":"{uri}"}}]}} } ] } ' まとめ・感想 自由度が高いので細かいこだわりを反映できるメッセージですがやはり複雑なものを考えるのは難しいです。 Flex Messageをゼロから作成するのは結構大変なので、他の人が作成したものを共有していけるようにしていければ良いなと思いました。 ざっと触ってみてちょっとだけ慣れてきました。慣れてくれば、Simulatorなしでも作成できるようになるのだろうか。。 また以前は マルチキャスト /v2/bot/message/multicast で Flex Messageを配信できなかったですが使用できるようになったので大量配信にも使えそうです。 今回調べたことを実際の施策の中で活かしていきたいと思います。 参考 LINE Developers ドキュメント
この記事は Enigmo Advent Calendar 2018 の 20日 目の記事です。 概要 GCP (GKE)を利用してログからユーザ属性を 機械学習 により予測し、 その結果を API で返却するシステムを構築しました。 運用していく上でCI/CDツールは何にしようかなぁというところで、 今後社内でGitlabを使っていくという流れがあったのと GKEとの相性も良さそうということでGitlab CIを利用することにしました。 1 今回はGitlabと GCP (GKE)環境を連携する方法と、 Gitlab CIのキモとなる.gitlab-ci.ymlの内容についてまとめたいと思います。 システム構成 簡単にデプロイ対象のシステム構成を紹介します。 赤い枠で示したものが今回の作業対象になります。 環境条件 今回は下記の環境を使用しています。 Gitlabについては毎月バージョンが更新されるので、旧バージョンを利用されている方などは若干UI等が変わっているかもしれません。 Gitlab GitLab Community Edition 11.5.3 GKE クラスタ バージョン 1.10.9-gke.5 CI/CDのフロー CI/CDは下記のフローで行います。 テスト環境 developブランチへのcommit&push 以降は自動 dockerイメージのbuild dockerイメージをGCSへpush GKE(CronJon)へのデプロイ GKE(Deployment)へのデプロイ 本番環境 本番はすべて手動 developブランチからmasterブランチへのMR作成 マージ GKE(CronJon)へのデプロイ GKE(Deployment)へのデプロイ 設定手順 Gitlab CIを利用するにあたり必要な設定を行っていきます。 サービスアカウント作成 GCP (GKE)とGitlab CIとの連携のために GCP コンソールからサービスアカウントを作成します。 ここでは仮に「Gitlab CI」という名前のサービスアカウントを作成します。 役割(role)に下記の2つを付与してください。 Kubernetes Engine 開発者 ストレージ管理者 キーのタイプで JSON を選択し、キーを作成し、ローカルにキーファイルをdownloadします。 変数の設定 GKEへの認証情報をGitlabに登録します。 対象のプロジェクト( リポジトリ )のSettings -> CI/CD -> 変数にて、 キーに「SERVICE_ACCOUNT_KEY」を入力し、 値に先ほどdownloadしたキーファイルの中身をそのままコピペします。 下記の例では「SERVICE_ACCOUNT_KEY_PROD」というキーもありますが、 これは本番とテストでプロジェクトを分けているためです 1つのプロジェクト配下の場合は1つのキーで問題ないと思います。 Specific Runnersの登録 下記コマンドにてSpecific Runnerを登録します。 今回はdocker in docker方式で行います。 shell executorを利用する場合は こちら を参考にしてください。 $ sudo gitlab-runner register -n \ > --url [ URL ] \ > --registration-token [ TOKEN ] \ > --executor docker \ > --description " docker-runner " \ > --tag-list " gke " \ > --docker-image " docker:stable " \ > --docker-privileged ※指定するURLとTOKENについてはGitlabのSettings -> CI/CD -> Runnerの項目を参照してください。 今回はオンプレ版のGitlab環境を使用していますが、 gitlab.com でShared Runnerを利用する場合には こちらの手順は不要です。 .gitlab-ci.ymlの作成 今回はCronjobへのデプロイ定義を例に説明します。 Deploymentへのデプロイ定義も基本的に同じです。 共通設定 テスト/本番環境など、デプロイ対象の環境が複数ある場合にはどうしても定義が冗長的になります。 それを解消するために、下記では YAML のアンカー/ エイリアス とGitlabのextends(v11.3で追加)という機能を利用して、共通的な定義はまとめるようにしています。 .auth_gke : &auth_gke | gcloud auth activate-service-account --key-file=key.json gcloud config set project ${PRJ_ID} gcloud config set container/cluster ${CLUSTER_NAME} gcloud config set compute/zone ${CLUSTER_ZONE} gcloud container clusters get-credentials ${CLUSTER_NAME} --zone ${CLUSTER_ZONE} .setting_dev : environment : develop variables : CI_DEBUG_TRACE : 'true' PRJ_ID : 'XXXXX-dev' CLUSTER_NAME : 'dev-XXXX' CLUSTER_ZONE : 'asia-northeast1-a' GCS_KEY : 'XXXX.json' ENV_CONFIG : '/PATH/conf/settings_dev.json' .setting_prod : environment : production variables : PRJ_ID : 'XXXXX-prod' CLUSTER_NAME : 'XXXXX' CLUSTER_ZONE : 'asia-northeast1-a' GCS_KEY : 'XXXX.json' ENV_CONFIG : '/PATH/conf/settings_prod.json' services : - docker:dind stages : - build - deploy ジョブ定義① ここではdockerイメージのbuildと、GCRへのコンテナイメージのpushを行っています。 extendsで先程定義した内容を継承しています。 only:changesで該当のファイルに変更があった場合のみジョブが実行されるようになります docker build_base : extends : .setting_dev image : 'docker:stable' stage : build tags : - gke script : - docker info # Build the image - docker build --cache-from ${IMAGE_TAG} -t ${IMAGE_TAG}:${IMAGE_VER} . # Log in to Google Container Registry - echo "$SERVICE_ACCOUNT_KEY" > key.json # - docker login -u _json_key --password-stdin https://asia.gcr.io < key.json - docker login -u _json_key -p "$SERVICE_ACCOUNT_KEY" https://asia.gcr.io # Push the image - docker push ${IMAGE_TAG}:${IMAGE_VER} only : refs : - develop changes : - Dockerfile - bin/* - conf/* - requirements.txt ジョブ定義② ここではテスト/本番環境に対する2つのCronJobリソースへのデプロイを行います。 本番環境へはwhen: manualを定義して手動でデプロイするようにしています。 deploy_model_builder_dev : extends : .setting_dev image : 'claranet/gcloud-kubectl-docker:1.2.2' stage : deploy tags : - gke script : # Authenticate with GKE - echo "$SERVICE_ACCOUNT_KEY" > key.json - *auth_gke # set up CronJob - cat kubernetes/XXXX-job.yaml | envsubst | kubectl apply -f - only : refs : - develop changes : - .gitlab-ci.yml - kubernetes/XXXX-job.yaml deploy_bulk_judgement_dev : extends : .setting_dev image : 'claranet/gcloud-kubectl-docker:1.2.2' stage : deploy tags : - gke script : - echo "$SERVICE_ACCOUNT_KEY" > key.json - *auth_gke # set up CronJob - cat kubernetes/XXXX-job.yaml | envsubst | kubectl apply -f - only : refs : - develop changes : - .gitlab-ci.yml - kubernetes/XXXX-job.yaml deploy_model_builder_prod : extends : .setting_prod image : 'claranet/gcloud-kubectl-docker:1.2.2' stage : deploy tags : - gke script : # Authenticate with GKE - echo "$SERVICE_ACCOUNT_KEY_PROD" > key.json - *auth_gke # set up CronJob - cat kubernetes/XXXX-job.yaml | envsubst | kubectl apply -f - only : - master when : manual deploy_bulk_judgement_prod : extends : .setting_prod image : 'claranet/gcloud-kubectl-docker:1.2.2' stage : deploy tags : - gke script : # Authenticate with GKE - echo "$SERVICE_ACCOUNT_KEY_PROD" > key.json - *auth_gke # set up CronJob - cat kubernetes/XXXX-job.yaml | envsubst | kubectl apply -f - only : - master when : manual ロールバック せっかくなので ロールバック の手順についても記載しておきます。 Gitlabの左メニューから運用 -> 環境とたどっていくと、下記のような画面が表示されます。 上記で記載した.gitlab-ci.ymlにenvironmentという定義があると思いますが、 そこで定義した文字列が環境のところに表示されているのがわかると思います。 たとえばここでproductionをクリックします。 すると下記の画面に遷移します。 この一覧はproductionに対するコミットの一覧です。 たとえば直前のコミットに ロールバック したい場合は赤枠のところをクリックすればOKです。 もちろん、この画面から任意のコミットを選択して再デプロイすることも可能です。 environmentの定義があることで環境毎のコミット履歴が一覧で見れるかつ、 そこからのデプロイ、 ロールバック も簡単に行えます。 複数環境がある場合にはぜひ設定することをおすすめします。 まとめ 今回、Gitlab CIを初めて利用してみましたが、特に癖もなく使いやすかったです。 Auto DevOpsや、先日発表されたGitLab Serverlessなど、まだまだアツい機能があるので、時間を見つけて検証してみようと思います。 当初はGitlabのAuto Devopsの利用を考えていましたが、検討時点では利用しているGitlabのバージョンが古かったため、ひとまずGitlab CIを利用することにしました。  ↩
この記事は Enigmo Advent Calendar 2018 の19日目の記事です。 はじめに ネタ何にしようかなぁと思って、 カジュアルな感じでかつ単発で終わるようなもの 検索、 自然言語処理 関連で何か 年末的な何か ということを踏まえて、 Qiitaチームに日々挙げている自分の作業日報を可視化して2018年の振り返りをしてみることにしました。 私が エニグモ に入社したのが今年の2月なので、正確には1年ではないですが、 細かいことは気にせずいきたいと思います。 作業フロー 作業フローとしては、 Qiita API v2を利用して自分の作業日報の本文と作成日時を取得 取得した本文を 形態素解析 器にかけてキーワードのみを抽出 抽出したキーワードをword cloudを利用して可視化 可視化したものを見て思い出に浸る という感じで進めようと思います。 Qiita API で日報の本文を取得 アクセス トーク ンの発行 Qiitaチームからデータを取得するためアクセス トーク ンを発行します 今回は ユーザの管理画面 から発行しました。 API の詳細については 公式ページ に色々と記載があります アクセス トーク ンの利用 取得したアクセス トーク ンはAuthorizationリク エス トヘッダに指定して利用します。 今回は python で実装しましたが、 curl の場合は下記のような感じで取得できます。 $ curl https://qiita.com/api/v2/authenticated_user/items \ -H " Authorization: Bearer [アクセストークン] " 下記のメソッドを定義して日報データを取得します API を利用して20件ずつデータを取得 取得したデータはDataframeに突っ込み、不要なデータは取り除く 取得した作成日時をDatetimeIndexに設定して、月単位でデータが扱えるようにしておく def get_report (): BASE_URL = 'https://XXXX.qiita.com/api/v2/items?' PER_PAGE = 20 # tagで絞り込み QUERY = 'tag%3A%E6%97%A5%E5%A0%B1%2F%E4%BC%8A%E8%97%A4%E6%98%8E%E5%A4%A7' curr_page = 1 with open ( 'qiita_access_token.txt' ) as f: qiita_access_token = f.read().strip() header = { 'Authorization' : 'Bearer {}' .format(qiita_access_token)} # レスポンスヘッダを見てページング処理 df_list = [] total_cnt = 0 while ( True ): target_url = '{0}page={1}&per_page={2}&query={3}' .format(BASE_URL, curr_page, PER_PAGE, QUERY) req = urllib.request.Request(target_url, headers=header) res = urllib.request.urlopen(req) if curr_page == 1 : # ヘッダから全投稿数を取得 total_cnt = int (res.info()[ 'Total-Count' ]) # 最終ページの計算 if total_cnt % PER_PAGE == 0 : last_page = total_cnt // PER_PAGE else : last_page = total_cnt // PER_PAGE + 1 res_body = res.read() res_dict = json.loads(res_body.decode( 'utf-8' )) # dataframeに変換 df_list.append(pd.io.json.json_normalize(res_dict)) # 最終ページチェック if curr_page == last_page: break curr_page+= 1 # df_listを結合 report_df = pd.concat(df_list, ignore_index= True ) # 必要なデータのみ抽出 report_df = report_df[[ 'body' , 'created_at' ]] # created_atの文字列をdate型に変換 report_df[ 'created_at' ] = pd.to_datetime(report_df[ 'created_at' ]) # DatetimeIndexに変換 report_df.set_index( 'created_at' , inplace= True ) return report_df 形態素解析 器でtokenize&前処理 janome (辞書はneologd)を利用し、前処理として下記を実施します。 unicode 正規化 改行コード削除 大文字→小文字に統一 名詞のみ抽出(数字は対象外) def analyze (): # charfilter ## unicode正規化 ## 改行コード削除 charfilters = [UnicodeNormalizeCharFilter(), RegexReplaceCharFilter( u' \r\n ' , u'' )] # tokenizer tokenizer = Tokenizer(mmap= True ) # tokenfilter ## 大文字→小文字 ## 名詞のみ抽出(数は除去) tokenfilters = [POSKeepFilter( '名詞' ), POSStopFilter([ '名詞,数' ]), LowerCaseFilter()] # analyzer analyzer = Analyzer(char_filters=charfilters, tokenizer=tokenizer, token_filters=tokenfilters) return analyzer def get_word_list (report_list, analyzer): word_list = [] for report in report_list: word_list.append( " " .join( analyzed_line.base_form for analyzed_line in analyzer.analyze(report))) return word_list 可視化処理 word cloudを利用して可視化するためのメソッドを定義します def draw_wc (vecs_dic, fig_title): font_path = '/System/Library/Fonts/ヒラギノ角ゴシック W3.ttc' wordcloud = WordCloud(background_color= 'white' , font_path = font_path, min_font_size= 15 , max_font_size= 200 , width= 1000 , height= 1000 ) wordcloud.generate_from_frequencies(vecs_dic) plt.figure(figsize=[ 20 , 20 ]) plt.imshow(wordcloud,interpolation= 'bilinear' ) plt.axis( "off" ) plt.title(fig_title,fontsize= 25 ) plt.show() 指定した月の日報を可視化する では実際にやっていきます。 まず、私が入社した月の日報を見てみましょう。 1年間分の日報データを取得 df_reports = get_report() 可視化したい月を指定 target_month = '2018-02' 月の日報をまとめて 形態素解析 でtokenize my_analyzer = analyze() words = get_word_list(df_reports[target_month].body.tolist(), my_analyzer) TF-IDFで各単語を重み付け vectorizer = TfidfVectorizer(use_idf= True , token_pattern= u'(?u) \\ b \\ w+ \\ b' ) vecs = vectorizer.fit_transform(words) words_vectornumber = {} for k,v in sorted (vectorizer.vocabulary_.items(), key= lambda x:x[ 1 ]): words_vectornumber[v] = k vecs_array = vecs.toarray() all_reports = [] vecs_dic = {} for vec in vecs_array: words_report = [] vector_report = [] for i in vec.nonzero()[ 0 ]: vecs_dic[words_vectornumber[i]] = vec[i] 可視化 draw_wc(vecs_dic, target_month) 「入社」というキーワードでフレッシュ感がほのかに漂っています。 その他でみると「性能」、「分析」、「検証」、「測定」とかが大きくでてますね。 そういえば、この月は検索システムの性能改善をメインでやっていた気がします。 続いて6月の日報を見てみます。 「韓国」と大きく出てますが、これな何でしょうかね。全く覚えがありません(笑) 「chef」、「レシピ」、「zero」とかはこのあたりでchef zeroで何かの検証をしていたんでしょう。きっと。。 「パーソナライズド」、「fasttext」があるので、この時期あたりからパーソナライズサーチの検証をしてたんだと思います。 最後に11月 他人の日報なんて誰も興味ないと思うので、これで最後にします。 せっかくなのでクリスマスっぽくしてみました。 先月の内容なので、特に振り返ることもありませんが、 Gitlab CI周りの調査/検証や API の開発をメインで実施していた時期でした。 おわりに 今回初めてword cloudを使ってみましたが、 視覚的にデータを見るのって新たな気付きがあったりして楽しいですね。 来年はフォントとか画像をもうちょっとオサレな感じにして日報Tシャツ作ってヘビロテしようと思います。
こんにちは、 @pompom0c0 です。 この記事は Enigmo Advent Calendar 2018 の18日目の記事です。 17日目の記事は @natten の はじめてのキーボード設計における アンチパターン でした。 今日は BUYMA 内で使用している見出しデザインついて紹介して行こうと思います。 前置き 今回この記事を書こうと思った経緯 Sassの @mixin を実践でどんな風に使っているか知ってほしい! 使う見出しデザインがパターン化されているので、デフォルトであると便利だと思ったから 想定読者 LPの実装をするデザイナーの方 @mixin は自分から書いたことがない人。 今回扱わないこと FLOCSSの設計について @extend や @content での書き方紹介 mixinのおさらい 別で定義したスタイルを @include でなんども呼び出すことができます。 引数を指定して、関数的な使い方もできます。 @mixin hoge($color: #fff , $size: 1rem) { color : $color; font-size : $size; } .ttl__1 { @include hoge( #000 , 2rem ); } .ttl__2 { // 引数は初期値のを使用 @include hoge(); } .ttl__1 { color : #000 ; font-size : 2rem ; } .ttl__2 { color : #fff ; font-size : 1rem ; } 引数を使えば初期値で出力するだけでなく、「今回はもう少し違った雰囲気で見せたいな...」という際には値を変えれば デフォルトで CSS を設定していても、出力結果を冗長させることなく記述することができます。 あとはよくある見出しデザインをSassでまとめて記述しておきます。 よく使う見出しデザインSass 縦線 @mixin line--ver($color: #000 , $size: 30px) { &::after { content : '' ; width : 1px ; margin-top : 1em ; margin-bottom : . 5em ; display : inline-block ; // 線の色と長さを引数にする height : $size; border-right : solid 1px $color; } } 横線 @mixin line--under($color: #000 , $size: 1px) { position : relative ; &::before { content : '' ; position : absolute ; left : 50 %; bottom : -15px ; display : inline-block ; width : 60px ; -moz- transform : translateX( -50% ) ; -webkit- transform : translateX( -50% ) ; -ms- transform : translateX( -50% ) ; transform : translateX( -50% ) ; // 線の色と長さを引数にする height : $size; background-color : $color; } } 文字横に線 @mixin line--side($color: #000 , $size: 1px) { border-left : solid $size; border-right : solid $size; width : 45 %; margin : 0 auto 1em ; padding-bottom : 0 ; border-color : $color; } 文字下に 蛍光ペン でマーキング @mixin line--maker($color: #ffc63b , $size: .2rem) { background : linear-gradient( transparent 60% , $color 60% ) ; padding : 0 $size; } コンテンツの幅の分だけ色がついてしまうので、マーキングしたい箇所にのみ span タグでclass指定してください。 < div class = "framework__ttl" > < h2 >< span class = "ttl" > タイトル </ span ></ h2 > </ div > 鍵カッコ @mixin mark --quo($color: #000 , $size: 1px) { position : relative ; padding : 1rem ; &::before , &::after { content : '' ; width : 20px ; height : 30px ; position : absolute ; display : inline-block ; } &:: before { border-left : solid $size $color; border-top : solid $size $color; top : 0 ; left : 0 ; } &:: after { border-right : solid $size $color; border-bottom : solid $size $color; bottom : 0 ; right : 0 ; } } まとめ 久しぶりに使う見出しデザインを実装する直前になって 「あ、これどうやって書くんだっけ・・・」と ググる のは生産的じゃないな〜と思ってまとめてみました。 @mixin 使ってるけど引数までは使ってない!使いこなしてみたい!って方に参考になればと思います。 参考サイト Sass(SCSS)のmixin, extendなどまとめ Sassで@mixinを作る時に知っておきたい基礎知識 より素早くCSSコーディングするための、Sass(SCSS)のmixinスニペット集
こんにちは。Enigmo インフラグループの @natten です。 この記事は Enigmo Advent Calendar 2018 の17日目の記事です。 16日目の記事は @enigmo7 の デザインパターンとリファクタリング でした。 本日の記事は技術寄りの話ではなく趣味の世界、自作キーボードのお話です。 キーボードを設計するモチベーション 2015年登場のErgoDoxに端を発する(諸説あります)メ カニ カルキーボード自作のムーブメントは国内でも盛んになる一方で、今年はHelixやErog42, Mint60にCrkbdなどなどエポックメイキングなプロジェクトが多く登場しました。 自分もご多分に漏れずこの潮流に飲み込まれてしまい、キーボードを購入するために技術書典に足を運んだり、キーキャップ一式を購入してみては「こんなプラスチックの塊が2万円?!」と愕然とする日々です。 さて、しばらくキーボードを買ったり作ったり積んだりしていると、不思議なもので「自分でもキーボードを設計してみようかな?」という気持ちが芽生えてきます。 様々なキーボードのキー配置に触れているうちに好みのキー配列や指の可動域、多用するキーのポジションがわかってきて自分に最もフィットしたキーボードは自分にしか作れない、という結論になりました。 とにかくキーボードを設計する じゃあ実際にキーボードを設計してみましょう、ということで 教則本 を買います。 Crkbdを設計した @foostan さんによる、自作キーボード設計ノウハウ集です。 KiCADの使い方からケースの設計、PCBの発注と自作キーボード作成に必要な作業の一通りがわかりやすく記述された良書です。 こちらを斜め読みして、とにかく勢いとお試し感覚で設計してできあがった基板の実物がこちらになります。 https://pskbd.booth.pm/items/1044084 自作キーボードの世界に明るくない方はなんだこれ、となるでしょう。 じゃあ自作キーボード界の人ならどうかというと、同じくなんだこれ、という感想になります。 パーツを仮置きしてみると不思議がさらに加速していきます。 はみ出るように不自然に配置されたProMicroスペース、分離型でもないのに リバーシ ブル基板、キーと平行するように伸びるUSBケーブル。 なんでこんなことになってしまったのか、と冷静に振り返ってみると思い当たる節が次から次へと。 といったわけで、今回は こんなキーボードができたよ!すごいだろ! ではなく、 こんなキーボードができてしまった!こんなことしなければよかった! という点をご紹介します。 キーボード設計 アンチパターン コンセプトを山盛りにする 3列キーボードとか4列キーボードに追加できる、数字キー/ファンクションキー代わりの1列キーボードが欲しいな。数字キーだから0〜9で10キーかな キースイッチテスターってただのアクリルベース土台タイプが多いけど、打鍵感だけじゃなくて接点の違いとかも確認しようと思うと実際のキー入力が必要になるよね。 ホットスワップ タイプにすれば実用的なテスターとしても使えるんじゃない? 普段は使わないけど気まぐれに光らせたいときがあるなー。表面実装のLEDはハードルが高いから、とりあえずUnderGlowのLEDテープが取り付けられるようにパッドだけ用意しとこ モゲMicro怖いからProMicroソケット化したいし、もしものときに取り外せるようにトッププレートで覆わないようにしておいたほうがいいかな ダメなところ そんなにいっぱい並立できません 机上の空論なので、実際に基板を作るとコンセプト間で対立・矛盾が発生する 改善方法 各コンセプトの要素(実用,趣味,利便,実験,コストなど)のバランスを見直す。特に実験方向にステータスを振れば振るほど破綻する可能性が高くなる コンセプトはなるべく絞ると設計しやすい。反面、凡庸なものになってモチベーションが下がることもあるので、少しだけ背伸びするのも良い 何も考えずに リバーシ ブル基板にする 今後左右分離型のキーボードを作ることになるだろうし、多くの分離型キーボードと同じように リバーシ ブル基板にしよう! 気分や環境によって選べるのは利点だよね? ダメなところ MCU 周辺が無秩序なビアだらけになって汚い リバーシ ブルにしたばかりに配線がめちゃくちゃ面倒になる そもそも意味がない 改善方法 作ろうとしているキーボードの用途や量産体制、目的が リバーシ ブル基板に見合っているかよく考える 実例が多いからという雑な理由で リバーシ ブルにするより、片面できれいな配線方法を模索するのも一つの手です 基板以外の構成パーツについて深く考えない いろんなキーボードのソースデータからフットプリントのパーツも流用して基板の設計ができたぞ! 実際にフットプリントを置いて配線してみると、どうにもならない箇所があったから多少妥協して当初のイメージとは違う形になったけど、実物が見たいからとりあえず発注しよう。リセットスイッチとかスペーサーはあとで検討して調達すればいいや ダメなところ なんとなく流用したパーツの入手性やコスト、サイズ感を意識していない 平面上の寸法しか見ていないと、サンドイッチタイプのケースで物理的な干渉が発生することが多い 改善方法 実際にキーボードとして組み立てる場合のパーツ構成と具体的な寸法を書き出してみる 参考にしたキーボードが手元にあったら採寸して、パーツ配置の意図を読み取る 使用する可能性のあるパーツは検証用に一通り手元に揃えておく 二言目には未完成とか言い出す 最低限は形になったけど、ダメな点もいっぱいあるし Github で公開するのはちょっと気が引けるな…マサカリ飛んできたら心が折れちゃうかもしれないし、しばらく手元で温めようかな ダメな点 そういうこと言ってるやつは永久に完成させられない 改善方法 とにかくプロジェクト名を決めて Github に空 リポジトリ を作れ、 Twitter とか自作キーボードDiscordでガンガン進捗を上げてバンバン叩かれよう アンチパターン を振り返って よくよく振り返ってみると、どれもキーボード設計に限ったことではありませんね。 プロダクト開発やシステム設計でもありがちな話ばかりです。 コンセプトを山盛りにする →理想を高く掲げすぎず、実装する要素を精査する 何も考えずに リバーシ ブル基板にする →一般事例に惑わされず、自分の目的を見直す 基板以外の構成パーツについて深く考えない →細部にとらわれず、最終的なビジョンと全体像を考えて念入りに準備をする 二言目には未完成とか言い出す →初手から完璧を求めず、スモールスタートでもいいから人の目に晒して育てる 何かを利用・享受する側だけではなく、作り手の側に回り実際に手を動かしてみると、趣味の世界からでも得るものがあるという学びでした。 自分と同じようにはじめてキーボードを設計してみようかな、と考えている方の参考になれば幸いです。 Enigmoでは何かを作り出すことに熱中できる 仲間を募集しています 。 Adevent Calendarの記事ラインナップからもわかるように、バラエティに富んだメンバーがお待ちしていますので、 自作キーボードを一緒にワイワイやってくれる方の  ご応募お待ちしております。 明日の記事の担当は @pompom0c0 です。お楽しみに。
この記事は Enigmo Advent Calendar 2018の16日目 です デザインパターン と リファクタリング こんにちは。 iOS チームでエンジニアをやっています 今チームでは、プロジェクトの進行と並行して リファクタリング を行なっています 対象プロジェクトは、MVVMの デザインパターン を多用しています そのプロジェクトをどう リファクタリング していったかをツラツラと書いていこうかと思います TL;DR デザインパターン って、設計パターン。うまく対処するためにどう設計していけばいいかをまとめたものです デザインパターン を各画面で分けよう 簡単な実装なのに、MVVMを利用したら複雑になってしまった、であれば、設計を間違えている リファクタリング リファクタリング については、短期間で見ると、ビジネスとしてぶっちゃけ一円にもなりません。 しかし、 リファクタリング をする、しないでは、未来のプロジェクトの進行速度に影響していきます。 なぜ リファクタリング をしたのか メンテ、新規開発がしづらい 数行いじると全然違う画面でエラーがでてしまう どの画面がどのViewControllerなのかわからない Swiftらしいコードに MVVMなのに、UIViewControllerがふとっている 着手前 [アプリ設計] RxSwift / RxSwift Community の様々なライブラリを使って MVVM を 試してみた実装 iOS5 の時代のライブラリをそのまま使い続け、 iOS の新しい機能が実装されていない [構成] Managers サーバ API へアクセスする レスポンスをModelへパースする パースしたModelの一部を、 インスタンス 変数で管理 シングルトン ViewController 別ViewControllerに遷移 Userのアクションへのリアクション View SnapKit によるレイアウト実装 ViewModel Managerにデータを要求、受け取ったデータを管理 UIViewControllerにアクセスしてUIの更新等を行う Model データ 今 [アプリ設計] RxSwiftのみを使って他のCommunityライブラリを削除 実装は各画面でわけた  - 自由にかけるように変更 デザインパターン を各Componentで決めることにしました iOS5 から使っている OSS で、メンテがされていないものは削除 iOS / Swift の新しい機能を実装 https://developer.apple.com/design/human-interface-guidelines/ios/user-interaction/feedback/ https://developer.apple.com/documentation/uikit/uistackview https://developer.apple.com/documentation/swift/codable [構成] Components Model データ ViewController 別ViewControllerに遷移 Userのアクションへのリアクション Delegate / DataSourceの実装 Storyboard / Xib レイアウト実装 View iPhone のサイズによるFontサイズの調整など Network APIClient サーバに要求する Responseを生成する Response Codableによって API をモデル化 比較 Before After やったこと Componentsという ディレクト リを作ってその中に各画面毎のfileをいれるようにしました その画面に関係するfileが明確になり、関係ないものは使わないようにチームで心がけるようになりました 各Componentで デザインパターン を変更できるようにしたので、あった設計をできるようにしました 簡単な画面については、コード量が少ない設計に変更 APIClient / JSON デコーダ ー ともに複数あったので、新しいAPIClientを作成し、Codableを使うように変更 -> 古いものはまとめて削除。 Objective-C の時のような、 json["key"] as? Int のような実装をなくしました ソースコード に対するコメント / BTS なぜその実装になったのか等記載がない -> 現状を知り着手しやすいように、複雑な処理になる部分は、シーケンス図 / コメントで動作を記載 一行直すだけで、関係ないと思ったところでエラーが起こる -> Component間で扱うデータを減らし、非結合にしてComponent間の影響を減らしました Manager / Utility クラスは、シングルトンで実装 -> シングルトン実装のクラスを極力減らす。シングルトンにするとpropertyをつけたくなる人がいるので避けます UIはSnapKitのみで実装 -> Storyboard / Xib で実装、IBInspectable, IBDesignableを使い、 GUI で状況を把握しつつ実装  デザイナーが作ってくれているレイアウトに沿った物を作れる ( SnapKitだけの時、cornerRadiusなど漏れが発生していた ) まとめ よかったこと コードの削除をかなりできた 着手前と今の差分: 2467 files changed, 142326 insertions(+), 271177 deletions(-) すべての画面に、MVVMをあてようとして無理している部分が多々あり結果、読みにくいコードになっていたのを直せた レビューする際に、コードよりも、Storyboard / Xib で見た方がわかりやすかった Before: override func viewDidLoad () { super .viewDidLoad() ... . 40 行ほど } After: override func viewDidLoad () { super .viewDidLoad() imageView.image = UIImage(named : ... ) // 1行のみ } ドキュメント作成した事で効率改善が行えた デザイナーとの画像受け渡しは、 Xcode から直接やってもらえることになった ドキュメント自体もレビューされるので、その際に共有できた レビューする側も、どういう事をしたいロジックなのか理解できた リファクタリング を行う時、考慮すること チーム全員なので、非エンジニアにも理解してもらわないといけない リファクタしたいところを共有しておく 企画・ディレクター案件がある際、その画面対応の際にまとめて行う ディレクターには、案件を画面毎にまとめるように整理してもらう e.g. - ホームが重いので改善する作業中に、haptic feedbackの導入や、お気に入りのハートを他の画面と同期するように仕組みを入れました - 検索画面を改修する際に、保存した検索条件に起因する部分もまとめて、書き直し整理を行ったりしました
はじめに エニグモ サーバーサイドエンジニアの @gugu です。 この記事は Enigmo Advent Calendar 2018の15日目 です。 日頃は BUYMA の機能改修を行っていますが、弊社では月末の プレミアムフライデー は業務と関係ない開発を行って良い日となっています。 そこで、前から興味のあった 機械学習 で何か作ってみようと思いました。 Chainerを使って「まるばつゲーム」を学習させてみたので、簡単にやったことを書こうと思います。 github.com ※ちょくちょくリファクタするかもです。 本題に入る前に 私のスペック 日頃は Rails でサーバーサイドの開発 python はラズパイでLチカをしたぐらい はじめての 機械学習 仕様など みんな知っているまるばつゲームです。 学習させるコンピュータは常に先手 0〜8の計9マス。どこに打つかを学習させます。(もう既に打たれたマスにも打つことが可能。当然ルール違反なので負け) | | 0 | 1 | 2 | | -----+-----+----- | | 3 | 4 | 5 | | -----+-----+----- | | 6 | 7 | 8 | | 盤面のデータは誰も打ってないマスは0、○は1、☓は2の値が入ります。 | | | | ○ | | -----+-----+----- | | ☓ | ○ | ○ | | -----+-----+----- | | | ☓ | ☓ | | ↓ ↓ ↓ | | 0 | 0 | 1 | | -----+-----+----- | | 2 | 1 | 1 | | -----+-----+----- | | 0 | 2 | 2 | | 配列だと [0, 0, 1, 2, 1, 1, 0, 2, 2] コンピュータと戦わせて学習させます。(完全ランダムに打つ。打たれているマスには打たない。不毛な戦いになるので。) 盤上の状態を入力して、出力結果のうちの最大値のマスに打ちます | | | | ○ | | -----+-----+----- | | ☓ | ○ | ○ | | -----+-----+----- | | | ☓ | ☓ | | のとき入力値は [0, 0, 1, 2, 1, 1, 0, 2, 2] 出力結果がもし [0.1, 0.2, 0.4, 0.2, 0.1, 0.3, 0.9, 0.1, 0.5] だったら、数値が最大のマス6に打ちます。 勝敗結果から打ったマスが好手だったのか悪手だったのかを教えます Chainerの基礎知識 ニューラルネット を定義 from chainer import Link, Chain, ChainList import chainer.functions as F import chainer.links as L class MyChain (Chain): def __init__ (self): super (MyChain, self).__init__( # 9-20-9の3層(隠れ層の20はなんとなく。) l1 = L.Linear( 9 , 20 ), l2 = L.Linear( 20 , 9 ) ) def __call__ (self, x): # 伝播(sigmoidでも良いがleaky_reluの方が結果が良いような気がする。) h = F.leaky_relu(self.l1(x)) o = self.l2(h) return o 初期設定 # ニューラルネット model = MyChain() # 確率的勾配降下法(Stocastic Gradient Descent)を使用 opt = optimizers.SGD() opt.setup(model) 打つ場所を決める # 盤上の状態データをfloat32に形成する(chainerがfloat64には対応していないため) x = Variable(np.array([input_data], dtype=np.float32)) # 勾配を0に初期化(chainerのお決まりごと) model.zerograds() # 入力xを変換し出力yへ y = model(x) # 出力の最大値を打つ y.data.argmax() 学習させる # 教えるデータをfloat32に形成する t = Variable(np.array([teacher_data], dtype=np.float32)) # 出力yと教えるデータtとの差分を算出(平均二乗誤差) loss = F.mean_squared_error(y, t) # 逆伝播 loss.backward() # 最適化 opt.update() 上記の基礎知識を使ってまるばつゲームを学習させていきます。 学習方法 強化学習 はよく理解していないので、基礎知識のみで自己流に学習させます。 # result: 勝敗結果 # ドロー:0 # 勝ち:1 # 負け:2 # 既に打たれたマスに打った:3 def learn (self, result): for i, y in enumerate (models): # marks[i]: 打ったマス番号 # y.data[0]: 出力値 teacher = self.teacher(result, marks[i], y.data[ 0 ]) t = Variable(np.array([teacher], dtype=np.float32)) # 出力yと正解tとの差分を算出(平均二乗誤差) loss = F.mean_squared_error(y, t) # 逆伝播 loss.backward() # 最適化 opt.update() # 学んだらリセット del models[:] del marks[:] def teacher (self, result, mark, model): data = [] # draw, win if result == Settings.WIN: for i in range ( 9 ): if i == mark: # 打ったマスに値を与える data.append( 1 ) else : # 打ってない箇所は現状維持 data.append(model[i]) # lose or same place elif result == Settings.LOSE or result == Settings.SAME_PLACE: for i in range ( 9 ): if i == mark: data.append(- 1 ) else : data.append(model[i]) # draw else : for i in range ( 9 ): if i == mark: data.append( 0 ) else : data.append(model[i]) return data 学習方法説明 勝った場合 | | | | ○ | | -----+-----+----- | | ☓ | ○ | ○ | | -----+-----+----- | | ○ | ☓ | ☓ | | 最後の一手の出力結果が [0.1, 0.2, 0.4, 0.2, 0.1, 0.3, 0.9, 0.1, 0.5] で、マス6に打った場合、 出力結果: [0.1, 0.2, 0.4, 0.2, 0.1, 0.3, 0.9, 0.1, 0.5] 教えるデータ: [0.1, 0.2, 0.4, 0.2, 0.1, 0.3, 1 , 0.1, 0.5] とマス6の値を 1 にします。 同じように3手目が [0.3, 0.1, 0.9, 0.7, 0.5, 0.3, -0.3, -0.8, 0.5] で、マス2に打っていた場合 出力結果: [0.3, 0.1, 0.9, 0.7, 0.5, 0.3, -0.3, -0.8, 0.5] 教えるデータ: [0.3, 0.1, 1 , 0.7, 0.5, 0.3, -0.3, -0.8, 0.5] とマス2の値を 1 にします。 ※これを1手目まで繰り返します。 ※上記は勝った例なので 1 にデータを変換しましたが、負けた場合は -1 に、ドローは 0 にデータになります。 学習させてみた ※データは最後から100戦の勝率 100戦 win: 0.04 lose: 0.04 draw: 0.0 same_place: 0.92 1,000戦 win: 0.22 lose: 0.01 draw: 0.0 same_place: 0.77 10,000戦 win: 0.61 lose: 0.06 draw: 0.0 same_place: 0.33 100,000戦 win: 0.81 lose: 0.08 draw: 0.02 same_place: 0.09 強くなってる! VS 人間 実際に100,000戦したコンピュータと戦ってみました。 ※コンピュータ:○、 人間:☓ | | 0 | 1 | 2 | | -----+-----+----- | | 3 | ○ | 5 | | -----+-----+----- | | 6 | 7 | 8 | | ↓ ↓ ↓ | | 0 | 1 | x | | -----+-----+----- | | ○ | ○ | 5 | | -----+-----+----- | | 6 | 7 | 8 | | ↓ ↓ ↓ | | ○ | 1 | x | | -----+-----+----- | | ○ | ○ | x | | -----+-----+----- | | 6 | 7 | 8 | | ↓ ↓ ↓ | | ○ | 1 | x | | -----+-----+----- | | ○ | ○ | x | | -----+-----+----- | | 6 | 7 | x | | まともに戦えるが、弱い。。 学習方法を変えてみた どう変えたか? 「既に打たれたマスに打った」で負けた場合は、最後の一手のデータを調整するように変更します。 ソース # result: 勝敗結果 # ドロー:0 # 勝ち:1 # 負け:2 # 既に打たれたマスに打った:3 def learn (self, result): for i, y in enumerate (models): # 既に打たれたマスに打った and 最後の一手 if result == 3 and i == len (models) - 1 : # marks[i]: 打ったマス番号 # y.data[0]: 出力値 teacher = self.teacher(result, marks[i], y.data[ 0 ], True ) else : teacher = self.teacher(result, marks[i], y.data[ 0 ], False ) t = Variable(np.array([teacher], dtype=np.float32)) # 出力yと正解tとの差分を算出(平均二乗誤差) loss = F.mean_squared_error(y, t) # 逆伝播 loss.backward() # 最適化 opt.update() # 学んだらリセット del models[:] del marks[:] def teacher (self, result, mark, model, last_flg): data = [] # draw, win if result == Settings.WIN: for i in range ( 9 ): if i == mark: # 打ったマスに値を与える data.append( 1 ) else : # 打ってない箇所は現状維持 data.append(model[i]) # lose elif result == Settings.LOSE: for i in range ( 9 ): if i == mark: data.append(- 1 ) else : data.append(model[i]) # same plase elif result == Settings.SAME_PLACE: if last_flg == True : for i in range ( 0 , 9 ): if i == mark: # 最後に打ったマスだけを`-2`に調整する。 data.append(- 2 ) else : data.append(model[i]) else : for i in range ( 0 , 9 ): data.append(model[i]) # draw else : for i in range ( 9 ): if i == mark: data.append( 0 ) else : data.append(model[i]) return data 学習させてみた 10,000戦 win: 0.96 lose: 0.04 draw: 0.0 same_place: 0.0 コンピュータ相手だと9割以上勝てるようになって、打たれているマスには打たなくなりました。 VS 人間 (2) 100,000戦したコンピュータともう一度戦ってみました。 ※コンピュータ:○、 人間:☓ | | 0 | 1 | 2 | | -----+-----+----- | | 3 | ○ | 5 | | -----+-----+----- | | 6 | 7 | 8 | | | | 0 | ○ | 2 | | -----+-----+----- | | 3 | ○ | x | | -----+-----+----- | | 6 | 7 | 8 | | | | 0 | ○ | ○ | | -----+-----+----- | | 3 | ○ | x | | -----+-----+----- | | 6 | x | 8 | | | | x | ○ | ○ | | -----+-----+----- | | 3 | ○ | x | | -----+-----+----- | | ○ | x | 8 | | 強い!まだ最強とまでは言えませんが、2つリーチを作れることを覚えたようです。 まとめ 機械学習 はハードルがものすごく高いイメージでしたが、Chainerの基礎的な関数を駆使すれば初心者の私でも簡単な 機械学習 を作成できることがわかりました。 今後は 強化学習 ( DQN など)をちゃんと勉強して、負けないレベルに修正できたらと思います。
エニグモ データ活用推進室 @kmt47 です。 この記事は Enigmo Advent Calendar 2018の14日目 です。 概要 redashがv5にバージョンアップしました。(少し時間経ちますが) この記事では、ユーザ目線でredash v5の新機能を紹介します。 v3→v4へのバージョンアップと比べると、機能的な追加は少なめ&追加された機能の便利度も低めといった印象です。 また、v4で追加になった超便利機能「表示形式(format)」設定において、v5からパーセント表示の仕様が変更になっています。 それでは、上手に新機能を使って、redash作成を効率化していきましょう! クエリ編(表、グラフ) クエリを「お気に入り登録」できるようになった(便利度:★★) クエリを「お気に入り登録」できるようになりました。 「お気に入り登録」したクエリは、redashのトップページに「Favorite Queries」として表示されます。 ※ redashのトップページとは、画面の最上段のリ ダッシュ のマーク(💭)をクリックしたときの画面 また、クエリ一覧画面(Queries)では、右側の「☆Favorites」で絞り込むことができます。 小技として、最上段の「Queries」の右の下のボタンをクリックすると、「お気に入り登録」したクエリが選択できるようになっています。 「お気に入り登録」の方法は簡単で、クエリの画面でクエリ名の左の☆をクリックするだけです。 登録されたクエリは、★ が黄色になります。 もう一度 ★ をクリックすると、登録が解除されます。 良く利用するクエリは「お気に入り登録」しておくことで、トップページから簡単にアクセスできるようになります。 クエリに「タグ」を付けることができるようになった(便利度:★★) クエリに任意の「タグ」を付けることができるようになりました。 「タグ」を付けることで、クエリ一覧画面(Queries)の右側のリストから選択したタグで絞り込むことができます。 「タグ」を付ける方法は、クエリの画面の最上段にマウスをナビゲーションすると「+Add tag」のボタンが表示されます。 そのボタンを押すとポップアップ画面が表示されるので、既に登録済みのタグを選択、または新たなタグを入力して付与することができます。 1つのクエリにタグは複数付けることができます。 付けたタグを削除したい場合は、タグの右に表示される鉛筆マークをクリックして、削除したいタグの「×」を押してください。 パラメータの作成ボタンが追加になった(便利度:★★) これまでは、パラメータを作成するときは、クエリ入力画面に波括弧を二つ {{ }} 入力して、その間にパラメータ名を入れていたかと思います。 こんな感じです。→ {{ hoge }} v5では、パラメータ追加ボタンが追加されました。 クエリ入力画面の下の {{ }} ボタンがそれです。 ボタンをクリックすると「Add Parameter」というポップアップが表示されます。 「Keyword」は、クエリに埋め込むパラメータの名前、「Title」はパラメータの入力エリアの名前です。 「Type」でパラメータのデータ型を選ぶことができます。 なお、これまで通りクエリ入力画面で直接追加することもできます。 パーセント表示の仕様が変更になった(便利度:マイナス☆☆☆☆) この記事 「Redashがバージョンアップ(v4)して便利になったこと」 にも記載しましたが、v4で追加された最大の便利機能「数値の表示形式(フォーマット)をredashで設定できるようになった」のパーセント表示に関する仕様が変更になりました。 例えば、前年比を表示する場合、 「売上(今年) 120万円」÷「売上(去年) 100万円」の結果に対して、表示形式で「0.0%」を指定すれば、 120 ÷ 100 = 1.2 で 「120.0%」と表示されていました。 しかし、v5では、このままでは「1.2%」となってしまいます。 つまり、v4では自動的に100倍されましたが、v5でクエリで100倍する必要があります。 個人的な意見としては、v4の仕様の方が良い(正しい)と思います。 Excel でも1.2の値をパーセント表示にしたら120.0%になりますよね。それを敢えて100倍するなんて。 パーセント表示を使っていたクエリは、v5へのバージョンアップによって、全て修正が必要になりました。 ダッシュ ボード ダッシュ ボードを「お気に入り登録」できるようになった(便利度:★★) 使い方はクエリと同じなので、詳細は省略します。 ダッシュ ボードに「タグ」を付けることができるようになった(便利度:★★) 使い方はクエリと同じなので、詳細は省略します。 ダッシュ ボードの変更時にグチャってならなくなった。(便利度:★★★★) v5でもっとも良くなった点は、ここだと思います。 v4のユーザであれば「グチャって」いう意味が分かると思いますが、 v4では、 ダッシュ ボードのクエリのサイズ、特に高さを変更していると、クエリのサイズが崩れて(「グチャって」)、取り返しのつかないことになった経験がある方も多いと思います。 その為、一度変更したら、保存して、また変更したら保存して、といったように崩れても元に戻れる対策を講じながら作業を行う必要がありました。クエリのサイズを自由に変更できる機能はv4で追加された便利な機能なのですが、ちょっと不具合があったようです。 それがv5では修正されています。ちょっと変更するごとに保存して、「グチャって」なったら修正をキャンセルして、といった作業から解放されたのは非常に助かりました。 まとめ v5へのバージョンアップでは、便利機能の追加は少なめでした。 v4では、かなり便利な機能が多く追加されていたので、偶数バージョンのv6に期待します。
Org-modeを半年くらい使ってみた Enigmo Advent Calendar 2018の12日目の記事です。 こんにちは、エンジニアの @t4ku です。半年ほどorg-modeを使ってメモや、日々のタスク管理を行ってきたのでやってみた感想を共有しようと思います。 org-modeとは何か? org-modeとは emacs 上で動作する アウトライナー です。 アウトライナー は有名なところでいうと Mac アプリでは Omnioutliner や webアプリでも workflowy などがあります。 workflowlyについてはこちらの紹介記事がわかりやすいです(丸投げ) http://goryugo.com/20180412/dynalist-workflowy/ 課題やタスクのブレークダウンなど考えをまとめたりするのに org-modeではこのようなツリー構造をプレーンテキストで書いておけば 鞍上いい感じに表示してくれます。 markdown でも同じようなことができますが、ノードを移動したりインデントを変えたりするのが面倒なのでそういう用途で markdown を使う人はいないと思います。 また、スケジュール機能やTODOやタグやクロック機能もあるのでこれだけで 見積もりや振り返りがプレーンテキストで完結します。 org-modeのここがいい 自分が使っていて特によいと思った機能です。 テーブル表記の入力が楽 勝手にフィールドの幅を調整してくれたりなかなか便利だなと思いました。 体験すると、qiitaの markdown でtableを書くことが苦行というかほとんど罰ゲームに感じるようになってきました。 画像や数式が差し込める プレーンテキストでありながら画像も入れれるので、gitなどで履歴管理しつつ最低限わかりやすい ビジュアルをキープできるので、 プログラマ のメモとしてはいいバランスだと思います。 Latex 記法で書いたものは数式が表示されます。 ソースコード が実行できる org-babelという拡張があるので ソースコード ブロックで書いたものを評価して、結果を表示できます。 ※ob-ipythonというjupyterに繋ぐ拡張が必要ですが ob-ipython org-babel integration with Jupyter for evaluation of ( Python by default) code blocks スケジュール機能( アジェンダ ) ノードにスケジュールを設定しておくと、 アジェンダ コマンドを利用してその日にスケジュールされたタスク一覧( アジェンダ ビュー)を表示することができます。 ※実際のファイルが出せないのでテキトーなタスクなのでわかりにくくてすいません アジェンダ ビューはスケジュール日別に出したり、deadlineごとに出したり、タグごとに出したりいろいろカスタマイズできますが、自分の場合は、オペレーション系のタスクとプロジェクトごとのタスクごとに一覧化するようにして、一日毎の作業を管理する別のorgファイルにコピーします。 一日のタスクを直列に並べると、あんまり余計なことを考えずにただこなしていけるような気がします。 クロック機能が便利 各ノードにTODOステータスやスケジュールを設定するだけでなく、実際に作業をする時にクロックインすると 時間を記録してくれます。また、任意の期間でレポートを作成できます。 活用法 何も考えずに単体のorgファイルをそのまま使っててもいいのですが、自分の場合は下記のように Dropbox 配下の ディレクト リを分けてメモと予定/振り返りを管理しています。 orgファイル間は簡単にリンクを貼って辿れるので、 アジェンダ ファイル(Agenda/work.org)内のトピックから必要なファイルにリンクを張っておけば、だいたい事足ります。 どのようにファイルをオーガナイズするかやどういう単位で分割するかということについては深遠なテーマで、半年くらい使った素人ではまだキャッチアップできない(というか一生できる気がしない)のですが、下記の youtube シリーズはすごく勉強になりました。 org-mode tutorials 半年くらい使ってみた感想 何をどこに書くべきかが決まってきて キーバインド にも馴染んでくると、フローを壊さずに開発してるときもも打ち合わせしてるときも、ア イデア をためておけるので、何かのインタラプションがあっても、安心して忘れられる他、 ググる 回数やブラウザで遷移する回数が減ったきがします。 また、テキストなのですべてgitで管理できるので、週次ごとにプルリク エス トを作るようにすると diffを見れば振り返るのが一目瞭然です。 あと副産物ですが、普段プログラミングをする際は vim を使っているのですが、org-modeのためだけに emacs を使うようになり少し emacs の良さがわかってきました。そして両方の宗教を理解することで、世界平和に少し貢献できるような気がしてきました。 参考 org-mode tutorials How I use Emacs and Org-mode to implement GTD
Enigmo Advent Calendar 2018 の12日目の記事です。 注意: この記事のサンプルコードで使われている各ライブラリのバージョンは下記になります。 react 16 . 4 . 0 react-dnd 4 . 0 . 2 react-dnd-html5-backend 4 . 0 . 2 react-dnd-touch-backend 0 . 5 . 1 React DnD Reactで ドラッグアンドドロップ での並び替えを実装する際によく使われるのが React DnD というライブラリです。 このライブラリでは HTML5 の Drag and Drop API を利用して ドラッグアンドドロップ を実現していますが、この API 自体が スマートフォン などのタッチデ バイス には対応しておらず、 スマホ でそのまま ドラッグアンドドロップ を実装することができません。 TouchBackend React DnD を使う際、 ドラッグアンドドロップ したい コンポーネント を DragDropContext という HOC(Higer Order Component ) に渡します。 この DragDropContext の最初の引数に渡すのは通常、 HTML5Backend というバックエンドモジュールです。 import HTML5Backend from 'react-dnd-html5-backend' import { DragDropContext } from 'react-dnd' class YourApp { /* ... */ } export default DragDropContext(HTML5Backend)(YourApp) 前述した通りタッチデ バイス の場合はこの HTML5Backend は使えません。 しかしタッチデ バイス 対応した TouchBackend というものがあるのでそちらを使います。 import HTML5Backend from 'react-dnd-html5-backend' import TouchBackend from 'react-dnd-touch-backend' ; import { DragDropContext } from 'react-dnd' const isTouchDevice = () => { /* タッチデバイス判定 */ } class YourApp { /* ... */ } export default DragDropContext(isTouchDevice() ? TouchBackend : HTML5Backend)(YourApp) これだけでタッチデ バイス 対応ができました。 しかし、 HTML5Backend のようにいい感じにプレビューされません。 HTML5Backendではちゃんとプレビューされている TouchBackendではプレビューされていない! ※ Chrome のDevToolsで スマートフォン をエミュレートして録画しているためマウスカーソルが表示されています。 DragLayer React DnD には DragLayer という、ドラッグ時のプレビュー表示をカスタマイズできる API があります。 これを使うことでタッチデ バイス でもいい感じのプレビューを表示することができます。 利用側のサンプルコードは以下です。 import React from 'react' import DragLayer from 'react-dnd/lib/DragLayer' import TouchBackend from 'react-dnd-touch-backend' ; import { DragDropContext } from 'react-dnd' function collect(monitor) { const item = monitor.getItem() return { currentOffset: monitor.getSourceClientOffset(), previewProps: item && item.previewProps, isDragging: monitor.isDragging() && monitor.getItemType() === 'IMAGE' } } function getItemStyles(currentOffset) { if (!currentOffset) { return { display: 'none' } } const x = currentOffset.x const y = currentOffset.y const transform = `translate($ { x } px, $ { y } px) scale(1.05)` return { WebkitTransform: transform, transform: transform, } } class PreviewComponent extends React.Component { render() { const { isDragging, previewProps, currentOffset } = this .props if (!isDragging) { return null } return ( <div> { /*...*/ } </div> ) } } const DragPreview = DragLayer(collect)(PreviewComponent) class YourApp { render() { return ( <div> { /* ... */ } </div> ) } } export default DragDropContext(TouchBackend)(YourApp) かんたんに解説 DragLayer の引数 collect 関数では DragLayerMonitor のオブジェクトが渡されます。 monitor.getItem() で DragSource にアクセスすることができ、 任意で渡した props (今回の場合は previewProps という名前で渡していますが、どんな名前でも渡すことができます) にアクセスできます。 また、 monitor.isDragging で実際にドラッグされているか判定することができます。 同一画面の他の コンポーネント でも ドラッグアンドドロップ するために、 DragDropContext が複数ある場合は monitor.getItemType() でどのコンテキストなのかを判定するとよいでしょう。 プレビューがタッチした部分に追従するように monitor.getSourceClientOffset() を使ってオフセット座標を返しておきます。 collect 関数の返り値のオブジェクトはそのままプレビュー用の コンポーネント で props として受け取ることができます。 getItemStyles 関数では受け取った props.currentOffset を使って CSS を調整しています。 DragDropContext に渡した コンポーネント で DragLayer を描画することで、ドラッグ時にプレビューを表示することができます。 スマホ でもプレビューができた! ※ Chrome のDevToolsで スマートフォン をエミュレートして録画しているためマウスカーソルが表示されています。 最後に スマートフォン などのタッチデ バイス で HTML5 のような ドラッグアンドドロップ を実現する方法を解説しました。 実際に実装する際は、 TouchBackendのリポジトリ に完全に動作するサンプルがあるのでそちらも参考にしてみてください。 参考リンク http://react-dnd.github.io/react-dnd/about https://github.com/yahoo/react-dnd-touch-backend
はじめに この記事は Enigmo Advent Calendar 2018の11日目 です。 Enigmoでは、データウェアハウス(DWH)としてBigQueryを使っていて、サービスの アクセスログ やサイト内の行動ログ、データベースのデータをBigQueryへ集約させています。 データベースからBigQueryへのデータ同期には Apache Airflow を使っていて、今日はその仕組みについて紹介します。 Apache Airflowとは Airflowは、 python でワークフロー(DAG)を定義すると、そのとおりにタスク(オペレーター) をスケジューリングして起動してくれるツールです。 GCP でもGKE上でAirflowを動かすCloud Composerというサービスが提供されていてご存知の方も多いと思います。 データの処理の単位をオペレータで定義し、その処理の依存関係を反映したワークフローをDAGで定義してやればデータ処理のパイプラインを実現することが可能となります。 DBからBigQueryへのデータパイプライン データの流れ データの流れとしては、上の図の通り大きく2フェーズに分かれていて、まずはDB( SQL Server )から Google Cloud Storage(GCS)へデータをアップロードしています。その次にGCSからBigQueryへそのデータをロードしています。 それぞれのフェーズをAirflowのタスクの単位であるオペレーターで実現していて、さらに2つのオペレーターはそれぞれ同期するテーブルごと別のタスクとして存在し、それらをDAGという1つのワークフローの単位でまとめています。 SQL Server からGCSへ JdbcToGoogleCloudStorageOperator SQL Server からGCSへのデータの移動は JdbcToGoogleCloudStorageOperator というAirflowのオペレーターが担当します。 DBが MySQL の場合は MySqlToGoogleCloudStorageOperator というAirflowに組み込みのオペレーターがあるんですが、バイマのデータベースは SQL Server なので、 JDBC のクライアントで同様の働きをするオペレーターを自前で作ったものが JdbcToGoogleCloudStorageOperator です。Airflowの プラグイン として公開しています。 github.com このオペレータでの処理は、まずDBから SQL でデータを抽出し、一度JSONL形式のファイルとしてのオペレーターが動くサーバーのローカルに保存され、それがGCSへアップロードされるという流れです。BigQueryへロードするときに スキーマ 定義が必要なので、データファイルとは別に スキーマ 定義のファイルも JSON 形式でGCSへアップロードされます。 スケジューリングと更新差分抽出の仕組み DAGのスケジューリング間隔は1時間に設定しています。するとAirflowは時間を1時間ごとに期間を分けてDAGにその期間の開始時刻( execution_date )、終了時刻( next_execution_date )をテンプレートのパラメーターとして渡してくれます。それらを データ抽出 SQL のWHERE句のところでレコードの更新日時を記録するカラム(下の例では updated_at )を基準に期間指定すると、その期間に更新があったレコードだけが抽出され、BigQuery側へ送られる仕組みです。 SELECT * FROM table1 WHERE " {{execution_date.strftime('%Y-%m-%d %H:%M:%S') " <= updated_at AND updated_at < "{{next_execution_date).strftime(&# 039 ;%Y-%m-%d %H:%M:%S&# 039 ;)}}" もし間隔を変えてもDAGを編集することなく SQL がその期間に合わせて変わってくれるので便利です。 GCSからBigQueryへ GoogleCloudStorageToBigQueryOperator GCSからBigQueryへはその名の通りAirflow組み込みの GoogleCloudStorageToBigQueryOperator というオペレーターがやってくれます。 BigQuery側のデー タセット は同期元DBのデータベース単位、テーブルは同期元DBのテーブル単位に分けています。BigQuery側のテーブルはDB側のレコードの更新日ごとに日付分割しています。 BigQueryの更新は DML は使わずに、ファイルを読み込みジョブで更新されます。そうするとDB側のレコードが更新されるとBigQuery側には重複してレコードが溜まっていくのですが、それは後述の重複除外ビューで解決しています。 BigQuery側でレコードの重複を除外 BigQuery側のテーブルでは、次のような SQL でビューテーブルを作ることで、同期元のDBでレコードが何度も更新されても常に最新のレコードしか現れない仕組みになっています。 この例は、主キーが id で更新日時のカラムが  updated_at の場合の SQL です。同一 id に対して常に最新の updated_at をもつレコードしかこのビューには出てきません。 SELECT * FROM ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY id ORDER BY updated_at DESC ) etl_row_num FROM `db1.table1_*`) WHERE etl_row_num = 1 Airflowで便利だった機能 Airflowの機能でこの仕組みをつくるのに助けられた機能がいくつかあったので紹介します。書ききれてないですが、ほかにもたくさんあります。 Catchup DAGのスケジュールを過去の期間にさかのぼって実行してくれる機能なんですが、非常にありがたかったです。 過去のデータの移行でも差分同期の仕組みがそのまま使えましたし、一度に同期せずに、期間を区切って少しずつデータを持っていけたので、同期元のDBにも負荷をかけずにすみました。 Connection、Variable Connection は接続先となるDBや GCP への認証情報を一元管理してくれ、一度設定すればどのDAGからアクセスできて便利でした。次のPoolも同じなんですが、設定は GUI でも CLI でも設定できるので、ansibleなどのプロビジョニングツールでも設定できたのもありがたかったです。 Variable も単なるキーと値を設定できるだけなんですが、DAGを汚すことなくdevやproductionなどリリースステージごとに値を切り替えられて便利でした。 Pool タスクの同時実行数を制限する機能です。Poolはユーザーが定義でき、そのPoolにオペレーターを紐付けるとそのオペレーターはそのPoolのslot数を超えて同時実行されません。データ抽出のタスクが1つのDBに対して多数同時実行されてしまうとそのDBのコネクションも同時に消費され、枯渇しかねませんが、このPoolで上限数を設定できたので安心でした。 まとめ 最初は手っ取り早くcronと スクリプト で作ってしまおうと思ったのですが、すこしなれるまで時間はかかったもののAirflowで作って良かったです。開発が進むにつれ、特にプロダクション環境で動かすにあたっていろいろ考慮すべきことが出てくると思うのですが、作りながらほしいと思った機能が先回りされているかのように用意されていてとても助かりました。全て使いきれてないですが、ワークフロー運用のノウハウがたくさん詰まった良いプロダクトだと思いました。
この記事は Enigmo Advent Calendar 2018の10日目 です。 はじめに OptunaはPFN社が公開したハイパーパラメータ自動最適化 フレームワーク です。 https://research.preferred.jp/2018/12/optuna-release/ 目的関数さえ決めれば、直感的に最適化を走らせることが可能のようです。 今回、最適化自体の説明は割愛させていただきますが、 機械学習 の入門ということを考えるとハイパーパラメータの調整としては、gridsearchやRandomizedSearchCVで行う機会が多いと思います。 スキル、あるいはリソースでなんとかするということになるかと思いますが、特に、kaggleのような0.X%の精度が向上が重要になるような状況では、ハイパーパラメータのチューニングが大きなハードルの一つになります。 そこで、titanicでのsubmitはあるものの、Kaggleの経験がほぼゼロな筆者でも、Optunaで簡単にチューニングができるかどうかを試してみようと思います。 今回の対象コンペ 既にcloseしているコンペの中で、下記のPorto Seguro’s Safe Driver Predictionを選びました。 https://www.kaggle.com/c/porto-seguro-safe-driver-prediction 選定理由は以下の通りです。 データがそれほど大きくない 手元(自宅)のラップトップのRAMは8GBと大きくないので、XGboostではなくメモリ消費が抑えられるLightGBMでやってみたい 解法がシンプルかつ、LightGBMで上位のスコアを解法を公開している カーネル がすぐに見つかった 公開解法の再現 https://www.kaggle.com/xiaozhouwang/2nd-place-lightgbm-solution 上記をそのままコピペして一回submitします。 Python2対応のようなので、下記のようにPython3で動くように修正しました。 # part of 2nd place solution: lightgbm model with private score 0.29124 and public lb score 0.28555 import lightgbm as lgbm from scipy import sparse as ssp from sklearn.model_selection import StratifiedKFold import numpy as np import pandas as pd from sklearn.preprocessing import LabelEncoder from sklearn.preprocessing import OneHotEncoder def Gini (y_true, y_pred): # check and get number of samples assert y_true.shape == y_pred.shape n_samples = y_true.shape[ 0 ] # sort rows on prediction column # (from largest to smallest) arr = np.array([y_true, y_pred]).transpose() true_order = arr[arr[:, 0 ].argsort()][::- 1 , 0 ] pred_order = arr[arr[:, 1 ].argsort()][::- 1 , 0 ] # get Lorenz curves L_true = np.cumsum(true_order) * 1. / np.sum(true_order) L_pred = np.cumsum(pred_order) * 1. / np.sum(pred_order) L_ones = np.linspace( 1 / n_samples, 1 , n_samples) # get Gini coefficients (area between curves) G_true = np.sum(L_ones - L_true) G_pred = np.sum(L_ones - L_pred) # normalize to true Gini coefficient return G_pred * 1. / G_true cv_only = True save_cv = True full_train = False def evalerror (preds, dtrain): labels = dtrain.get_label() return 'gini' , Gini(labels, preds), True path = "input/" train = pd.read_csv(path+ 'train.csv' ) train_label = train[ 'target' ] train_id = train[ 'id' ] test = pd.read_csv(path+ 'test.csv' ) test_id = test[ 'id' ] NFOLDS = 5 kfold = StratifiedKFold(n_splits=NFOLDS, shuffle= True , random_state= 218 ) y = train[ 'target' ].values drop_feature = [ 'id' , 'target' ] X = train.drop(drop_feature,axis= 1 ) feature_names = X.columns.tolist() cat_features = [c for c in feature_names if ( 'cat' in c and 'count' not in c)] num_features = [c for c in feature_names if ( 'cat' not in c and 'calc' not in c)] train[ 'missing' ] = (train==- 1 ).sum(axis= 1 ).astype( float ) test[ 'missing' ] = (test==- 1 ).sum(axis= 1 ).astype( float ) num_features.append( 'missing' ) for c in cat_features: le = LabelEncoder() le.fit(train[c]) train[c] = le.transform(train[c]) test[c] = le.transform(test[c]) enc = OneHotEncoder(categories= 'auto' ) enc.fit(train[cat_features]) X_cat = enc.transform(train[cat_features]) X_t_cat = enc.transform(test[cat_features]) ind_features = [c for c in feature_names if 'ind' in c] count= 0 for c in ind_features: if count== 0 : train[ 'new_ind' ] = train[c].astype( str )+ '_' test[ 'new_ind' ] = test[c].astype( str )+ '_' count+= 1 else : train[ 'new_ind' ] += train[c].astype( str )+ '_' test[ 'new_ind' ] += test[c].astype( str )+ '_' cat_count_features = [] for c in cat_features+[ 'new_ind' ]: d = pd.concat([train[c],test[c]]).value_counts().to_dict() train[ '%s_count' %c] = train[c].apply( lambda x:d.get(x, 0 )) test[ '%s_count' %c] = test[c].apply( lambda x:d.get(x, 0 )) cat_count_features.append( '%s_count' %c) train_list = [train[num_features+cat_count_features].values,X_cat,] test_list = [test[num_features+cat_count_features].values,X_t_cat,] X = ssp.hstack(train_list).tocsr() X_test = ssp.hstack(test_list).tocsr() learning_rate = 0.1 num_leaves = 15 min_data_in_leaf = 2000 feature_fraction = 0.6 num_boost_round = 10000 params = { "objective" : "binary" , "boosting_type" : "gbdt" , "learning_rate" : learning_rate, "num_leaves" : num_leaves, "max_bin" : 256 , "feature_fraction" : feature_fraction, "verbosity" : 0 , "drop_rate" : 0.1 , "is_unbalance" : False , "max_drop" : 50 , "min_child_samples" : 10 , "min_child_weight" : 150 , "min_split_gain" : 0 , "subsample" : 0.9 } x_score = [] final_cv_train = np.zeros( len (train_label)) final_cv_pred = np.zeros( len (test_id)) for s in range ( 16 ): cv_train = np.zeros( len (train_label)) cv_pred = np.zeros( len (test_id)) params[ 'seed' ] = s if cv_only: kf = kfold.split(X, train_label) best_trees = [] fold_scores = [] for i, (train_fold, validate) in enumerate (kf): X_train, X_validate, label_train, label_validate = \ X[train_fold, :], X[validate, :], train_label[train_fold], train_label[validate] dtrain = lgbm.Dataset(X_train, label_train) dvalid = lgbm.Dataset(X_validate, label_validate, reference=dtrain) bst = lgbm.train(params, dtrain, num_boost_round, valid_sets=dvalid, feval=evalerror, verbose_eval= 100 , early_stopping_rounds= 100 , ) best_trees.append(bst.best_iteration) cv_pred += bst.predict(X_test, num_iteration=bst.best_iteration) cv_train[validate] += bst.predict(X_validate) score = Gini(label_validate, cv_train[validate]) print (score) fold_scores.append(score) cv_pred /= NFOLDS final_cv_train += cv_train final_cv_pred += cv_pred print ( "cv score:" ) print (Gini(train_label, cv_train)) print ( "current score:" , Gini(train_label, final_cv_train / (s + 1. )), s+ 1 ) print (fold_scores) print (best_trees, np.mean(best_trees)) x_score.append(Gini(train_label, cv_train)) print (x_score) pd.DataFrame({ 'id' : test_id, 'target' : final_cv_pred / 16. }).to_csv( 'model/lgbm3_pred_avg.csv' , index= False ) pd.DataFrame({ 'id' : train_id, 'target' : final_cv_train / 16. }).to_csv( 'model/lgbm3_cv_avg.csv' , index= False ) 公開解法でのsubmit Private Scoreで0.29097。5169チーム中46位のスコアとなり、シルバーメダル圏内に入りました。 コンペは終了しているので、もちろんスコアボードの本体は更新はされません。 なお、実際のコンペでは、 カーネル の著書から他のNeral Networkでの予測値の平均と記載があるので、2位のsubmitの再現というわけにならないようです。 しかし、このようなシンプルな方法でシルバーメダルのスコアを取れるのは、個人的にもKaggleに積極してみたいという励みになったと感じています。 ハイパーパラメータのチューニング さて、ハイパーパラメータのチューニングを フレームワーク の力を借りて、ハードルをぐっと下げようという、本題に移ります。 他のKaggleのコンペや、Stack over flowで雑に調査し、パラメータの範囲を決めました。 そうしてできた修正した ソースコード が、以下のようになります。 import lightgbm as lgbm import optuna from scipy import sparse as ssp from sklearn.model_selection import StratifiedKFold import numpy as np import pandas as pd from sklearn.preprocessing import LabelEncoder from sklearn.preprocessing import OneHotEncoder def Gini (y_true, y_pred): # check and get number of samples assert y_true.shape == y_pred.shape n_samples = y_true.shape[ 0 ] # sort rows on prediction column # (from largest to smallest) arr = np.array([y_true, y_pred]).transpose() true_order = arr[arr[:, 0 ].argsort()][::- 1 , 0 ] pred_order = arr[arr[:, 1 ].argsort()][::- 1 , 0 ] # get Lorenz curves L_true = np.cumsum(true_order) * 1. / np.sum(true_order) L_pred = np.cumsum(pred_order) * 1. / np.sum(pred_order) L_ones = np.linspace( 1 / n_samples, 1 , n_samples) # get Gini coefficients (area between curves) G_true = np.sum(L_ones - L_true) G_pred = np.sum(L_ones - L_pred) # normalize to true Gini coefficient return G_pred * 1. / G_true cv_only = True save_cv = True full_train = False def evalerror (preds, dtrain): labels = dtrain.get_label() return 'gini' , Gini(labels, preds), True path = "input/" train = pd.read_csv(path+ 'train.csv' ) #train = train.sample(frac=0.1, random_state=0).reset_index(drop=True) train_label = train[ 'target' ] train_id = train[ 'id' ] test = pd.read_csv(path+ 'test.csv' ) #test = test.sample(frac=0.1, random_state=0).reset_index(drop=True) test_id = test[ 'id' ] NFOLDS = 4 kfold = StratifiedKFold(n_splits=NFOLDS, shuffle= True , random_state= 218 ) y = train[ 'target' ].values drop_feature = [ 'id' , 'target' ] X = train.drop(drop_feature,axis= 1 ) feature_names = X.columns.tolist() cat_features = [c for c in feature_names if ( 'cat' in c and 'count' not in c)] num_features = [c for c in feature_names if ( 'cat' not in c and 'calc' not in c)] train[ 'missing' ] = (train==- 1 ).sum(axis= 1 ).astype( float ) test[ 'missing' ] = (test==- 1 ).sum(axis= 1 ).astype( float ) num_features.append( 'missing' ) train.shape for c in cat_features: le = LabelEncoder() le.fit(train[c]) train[c] = le.transform(train[c]) test[c] = le.transform(test[c]) # 事前にlabelEncoderを行っているから、この使い方でユニークな値で割り当てられる。引数categories = 'auto'で警告を消す enc = OneHotEncoder(categories= 'auto' ) enc.fit(train[cat_features]) X_cat = enc.transform(train[cat_features]) X_t_cat = enc.transform(test[cat_features]) ind_features = [c for c in feature_names if 'ind' in c] count= 0 for c in ind_features: if count == 0 : train[ 'new_ind' ] = train[c].astype( str )+ '_' test[ 'new_ind' ] = test[c].astype( str )+ '_' count += 1 else : train[ 'new_ind' ] += train[c].astype( str )+ '_' test[ 'new_ind' ] += test[c].astype( str )+ '_' cat_count_features = [] for c in cat_features+[ 'new_ind' ]: d = pd.concat([train[c],test[c]]).value_counts().to_dict() train[ '%s_count' %c] = train[c].apply( lambda x:d.get(x, 0 )) test[ '%s_count' %c] = test[c].apply( lambda x:d.get(x, 0 )) cat_count_features.append( '%s_count' %c) train_list = [train[num_features+cat_count_features].values, X_cat] test_list = [test[num_features+cat_count_features].values, X_t_cat] X = ssp.hstack(train_list).tocsr() X_test = ssp.hstack(test_list).tocsr() def objective (trial): drop_rate = trial.suggest_uniform( 'drop_rate' , 0 , 1.0 ) feature_fraction = trial.suggest_uniform( 'feature_fraction' , 0 , 1.0 ) learning_rate = trial.suggest_uniform( 'learning_rate' , 0 , 1.0 ) subsample = trial.suggest_uniform( 'subsample' , 0.8 , 1.0 ) num_leaves = trial.suggest_int( 'num_leaves' , 5 , 1000 ) verbosity = trial.suggest_int( 'verbosity' , - 1 , 1 ) num_boost_round = trial.suggest_int( 'num_boost_round' , 10 , 100000 ) min_data_in_leaf = trial.suggest_int( 'min_data_in_leaf' , 10 , 100000 ) min_child_samples = trial.suggest_int( 'min_child_samples' , 5 , 500 ) min_child_weight = trial.suggest_int( 'min_child_weight' , 5 , 500 ) params = { "objective" : "binary" , "boosting_type" : "gbdt" , "learning_rate" : learning_rate, "num_leaves" : num_leaves, "max_bin" : 256 , "feature_fraction" : feature_fraction, "verbosity" : verbosity, "drop_rate" : drop_rate, "is_unbalance" : False , "max_drop" : 50 , "min_child_samples" : min_child_samples, "min_child_weight" : min_child_weight, "min_split_gain" : 0 , "min_data_in_leaf" : min_data_in_leaf, "subsample" : subsample } x_score = [] final_cv_train = np.zeros( len (train_label)) final_cv_pred = np.zeros( len (test_id)) cv_train = np.zeros( len (train_label)) cv_pred = np.zeros( len (test_id)) params[ 'seed' ] = 0 kf = kfold.split(X, train_label) best_trees = [] fold_scores = [] for i, (train_fold, validate) in enumerate (kf): print ( 'kfold_index:' , i) X_train, X_validate, label_train, label_validate = \ X[train_fold, :], X[validate, :], train_label[train_fold], train_label[validate] dtrain = lgbm.Dataset(X_train, label_train) dvalid = lgbm.Dataset(X_validate, label_validate, reference=dtrain) bst = lgbm.train(params, dtrain, num_boost_round, valid_sets=dvalid, feval=evalerror, verbose_eval= 100 , early_stopping_rounds= 100 ) best_trees.append(bst.best_iteration) cv_pred += bst.predict(X_test, num_iteration=bst.best_iteration) cv_train[validate] += bst.predict(X_validate) score = Gini(label_validate, cv_train[validate]) print (score) fold_scores.append(score) cv_pred /= NFOLDS final_cv_train += cv_train final_cv_pred += cv_pred print ( "cv score:" ) print (Gini(train_label, cv_train)) print ( "current score:" , Gini(train_label, final_cv_train / (s + 1. )), s+ 1 ) print (fold_scores) print (best_trees, np.mean(best_trees)) x_score.append(Gini(train_label, cv_train)) print (x_score) pd.DataFrame({ 'id' : test_id, 'target' : final_cv_pred / 16. }).to_csv( 'model/lgbm3_pred_avg_2.csv' , index= False ) pd.DataFrame({ 'id' : train_id, 'target' : final_cv_train / 16. }).to_csv( 'model/lgbm3_cv_avg_2.csv' , index= False ) return ( 1 - x_score[ 0 ]) study = optuna.create_study() study.optimize(objective, n_trials= 150 ) パラメータの設定の範囲を抜粋すると以下のようになります。 drop_rate = trial.suggest_uniform( 'drop_rate' , 0 , 1.0 ) feature_fraction = trial.suggest_uniform( 'feature_fraction' , 0 , 1.0 ) learning_rate = trial.suggest_uniform( 'learning_rate' , 0 , 1.0 ) subsample = trial.suggest_uniform( 'subsample' , 0.8 , 1.0 ) num_leaves = trial.suggest_int( 'num_leaves' , 5 , 1000 ) verbosity = trial.suggest_int( 'verbosity' , - 1 , 1 ) num_boost_round = trial.suggest_int( 'num_boost_round' , 10 , 100000 ) min_data_in_leaf = trial.suggest_int( 'min_data_in_leaf' , 10 , 100000 ) min_child_samples = trial.suggest_int( 'min_child_samples' , 5 , 500 ) min_child_weight = trial.suggest_int( 'min_child_weight' , 5 , 500 ) なお、Optuna自体の使用方法は、下記の記事と公式リファレンスを参考させていただきした。 https://qiita.com/ryota717/items/28e2167ea69bee7e250d https://optuna.readthedocs.io/en/stable/index.html (18/12/11 19:41追記) コメントいただけた通り、'verbosity'は、警告レベルの表示を制御するパラメータであり、予測性能の最適化としては意味の無いパラメータでした。ですので、チューニングの対象にはすべきではありませんでした。 以下のように試行回数を定めていますが、 n_trials= 150 時間が足りなくなった関係で、その時点で計算されたパラメータで最適化を中断しております。 20時間ほど回し回しましたが、ハイパーパラメータによって検証の時間は1分から60分程度となり、 100回くらいの試行数だったようです。 そうしてできてパラメータが、以下のように、2位の解法と比較すると以下のようになります。 ハイパーパラメータ 今回のチューニング結果 2位の解法 drop _rate 0.3015600134599976 0.1 feature_fraction 0.46650703511665226 0.6 learning_rate 0.004772377676601769 0.1 subsample 0.8080720420805803 0.9 num_leaves 718 15 verbosity -1 0 num_boost_round 1942 10000 min_data_in_ leaf 212 150 min_child_samples 68 10 min_child_weight 151 150 2位コンペとの解法とは、雰囲気が異なるセットとなり、公開解法の再現ということにはならないようです。 K_fold=4 でやっていることも異なる要因になると思います。 算出できたハイパーパラメータでsubmit 最初のpython3の スクリプト からパラメータを入れ替え、予測値を算出しました。 K_fold =4, また、ランダムシートの数を16から4に減らしております。 結果 スコアは下がってます。 1176位相当。。ハイパーパラメータ次第でシルバーメダル圏内ということを考えると、微妙な結果です。 所感 結果としては残念ですが、grid searchだけに頼らない、ハイパーパラメータの最適化方法の導入のきっかけになりました。 また、非常に手軽に使えたというのもあり、今後もチューニングの場面でOptunaを活用してみたいと思います。 反省としては、探索するハイパーパラメータの設定が悪く、計算の効率化が著しく悪くなった恐れがあります。 validationの際に、fold数の全て計算するのではなく、スコアが下がらなそうなら、そのハイパーパラメータの計算をやめるとか、一定時間以上かかってしまったらまた、次に試行に移るとかできれば効率化できたように思えます。 フレームワーク は ブラックボックス でもある程度は動かすことができますが、やはり中身をある程度理解しないと遠回りしてしまうというのは、当然の結果と言えます。 もっと使いこなせるよう精進しなければと思いました。 公式リファレンスでも、OptunaでLightGBMをチューニングする例が出ており、そちらの例も参考にしながらリベンジしたいと思います。 github.com 最後にですが、この記事が何かの役に経てば幸いです。