[Sourcetree/GitHub] 草が生えないのはメールアドレスの設定が原因だった

Sourcetreeを使ってGitによるバージョン管理を試し始めた。
GitHub上に草が生えないという事態に遭遇したので、原因と対処法を書いておく。

f:id:soratokimitonoaidani:20181223165023j:plain
Photo by Stanislav Kondratiev on Unsplash

問題の内容:コミット・プッシュは通常通りにできるのに、草が生えない。

Sourcetreeを使っている。Sourcetreeを通じてコミット・プッシュをすることはできるのに、GitHub上に草が生えない。
「草が生えない」と最初からスラングを使ってしまっているが、正確に言うと「Contributionを示すグラフが緑色にならない」のことである。
GitHub上で新規リポジトリを作成したことは、Contributionに正常に反映されていた。SourcetreeからGitHubに対する動作だけがContributionにならない。
となると、Sourcetree側の設定に何か問題があると推測される。

原因はメールアドレスの設定がされていなかったから

(Sourcetreeなしで)Git単体の場合だと、以下のページに書いてあるとおりだ。
GitHubに芝が生えない件について - Qiita
GitHub上に登録されているメールアドレスと、コミット時のメールアドレスが一致していないと、Contributionにカウントされない(=草が生えない)。

参考のため、GitHub公式のページも示しておこう。
Why are my contributions not showing up on my profile? - User Documentation
Viewing contributions on your profile - User Documentation

「何で草が生えないんですか」は英語で正確に言うと「Why are my contributions not showing up on my profile?」なんですね。なるほど。

Sourcetree / Git でメールアドレスを確認・変更するには

「全てのリポジトリでデフォルトで使うメールアドレス」と「個々のリポジトリのメールアドレス」の2つがあるが、
前者についてだけ話す。
デフォルトのメールアドレスを指定しておけば、リポジトリで個別に指定しない限りはそのアドレスが適用されるためである。

Sourcetreeの ツール→オプション を押す。
f:id:soratokimitonoaidani:20181223164404j:plain ここに書いてあるメールアドレスが、GitHubで設定したアドレスと一致していればOK。
違っているならば、新しいメールアドレスを入力して「OK」を押せば変更できる。

なお、コミットに使ったメールアドレスは他人から見えてしまうので注意。
GitHubのメールアドレスが漏れる?コミット時のメールアドレスにnoreplyを設定 - メンチカツには醤油でしょ!!
このアドレスはすぐに見えるというものでも無い。しかし、どこかのサービスが個人の連絡先を取得するためにGitHubのコミットログからメールアドレスを引っこ抜いてくることがあるので、要注意だ。

メールアドレスが知られたくない場合には、ユーザ名を使ったダミーアドレスにすることが可能である。
(上の画像でもそうしている)
About commit email addresses - User Documentation

Sourcetree / Git のメールアドレスは連動しているのか

している。
Sourcetreeのほうでアドレスを変更してから、ターミナルの上でメールアドレスを確認すると、同じように変更されていた。
上の画像で「SourcetreeにGitとMercurialのグローバル設定ファイルの変更を許可する」という項目がある。デフォルトではチェックが入っていた。
ここにチェックがあると、Sourcetreeで変更したメールアドレスがGit上でも変更されるんだと思う。

以上。それでは。

C++ randomライブラリをクラス内で使う方法

この記事はC++ Advent Calendar 2018 の12日目の記事です。
そして今日の日付は2018年12月16日です。
……ごめんなさい。期日に遅れました。

11日目→@kizulさん なぜC++のclassとstructは同じ機能なのか - Qiita
13日目→@tyanmahouさん C++標準属性まとめ - Qiita

ライブラリをクラスの中で使うにはどうすれば良いんだろう、という話。

ライブラリの単純な使い方は、2016年のアドベントカレンダーで @_EnumHackさんが書いている。
C++の乱数ライブラリが使いにくい話 - Qiita

(というか今気づいたけど、以下で俺が書いているような難しさ面倒臭さを一気に解決してくれる便利なクラスを 今年の2日目の@Gacchoさんが書いていますね……
お手軽 乱数実装【C++11】 - Qiita)

環境

コンパイラVisual Studio 2017 Version 15.9.3
OS: Windows 7 64bit

ライブラリの単純な使用法

ライブラリはC++11で追加された。色々な分布に従う乱数を生成できるのが特徴だ。しかしその代わりに、使い方が少々面倒だ。
単純な使い方の例を以下に示す。

#include "pch.h"
#include <iostream>
// https://cpprefjp.github.io/reference/random.html のリンク先の各ライブラリからコピー、改変
#include <random>

int main()
{
    std::random_device seed_gen;
    std::default_random_engine engine(seed_gen());

    // 0以上9以下の値を等確率で発生させる
    std::uniform_int_distribution<> dist1(0, 9);

    for (std::size_t n = 0; n < 50; ++n) {
        // 一様整数分布で乱数を生成する
        int result = dist1(engine);

        std::cout << result << ",";
    }
    std::cout << "\n\n";

    // 平均0.0、標準偏差1.0で分布させる
    std::normal_distribution<> dist2(0.0, 1.0);

    for (std::size_t n = 0; n < 50; ++n) {
        // 正規分布で乱数を生成する
        double result = dist2(engine);

        std::cout << result << ",";
    }
    std::cout << "\n\n";

    // 確率0.7でtrueを生成し、確率0.3(1.0 - 0.7)でfalseを生成する
    std::bernoulli_distribution dist3(0.7);

    for (std::size_t n = 0; n < 50; ++n) {
        // 乱数を生成する
        bool result = dist3(engine);

        std::cout << result << ",";
    }
    std::cout << "\n\n";

}

基本的に、ライブラリを使うときは以下の3段階のステップを踏めば良い。

  1. random_deviceクラスのインスタンスを作る
  2. default_random_engineクラスのインスタンスを作って、その引数(=乱数シード)として1番から生成した乱数を入れる
  3. 自分の目的に合った乱数クラス(例えばuniform_int_distributionクラス)のインスタンスを作って、その引数に2番から生成した乱数を入れる

……面倒だね。

問題設定の例

……ところが、乱数を生成して使用する箇所は、実際にはmain関数の中ではなく各クラスの中が多いだろう。

単純な例を考えて作ってみた。

  • 2人ですごろくをする
  • マスは全部で30マス (ちょうどに止まらなくても通過すれば上がり)
  • 2, 12, 22 のマスに止まったら「サイコロを振り、4以上の目が出たら3マス進む」
  • 7, 17, 27 のマスに止まったら「サイコロを振り、4以上の目が出たら3マス戻る」

大したゲームでもないけど、コンソールの出力は例えば以下のようになる。

Aさんは6が出たので、6に進みました
Bさんは4が出たので、4に進みました
Aさんは4が出たので、10に進みました
Bさんは4が出たので、8に進みました
Aさんは6が出たので、16に進みました
Bさんは3が出たので、11に進みました
Aさんは5が出たので、21に進みました
Bさんは1が出たので、12に進みました
6が出たので、Bさんは盤面の効果により、15に進みました
Aさんは1が出たので、22に進みました
1が出たので、動きません
Bさんは6が出たので、21に進みました
Aさんは2が出たので、24に進みました
Bさんは6が出たので、27に進みました
1が出たので、動きません
Aさんは3が出たので、27に進みました
4が出たので、Aさんは盤面の効果により、24に戻りました
Bさんは1が出たので、28に進みました
Aさんは3が出たので、27に進みました
3が出たので、動きません
Bさんは5が出たので、33に進みました
Bさんの勝ちです

rand()を用いた実装

設計に自信がないけど、Player / Board / Dice クラスを作って以下のようにした。

/*player.cpp*/

int Player::turn()
{
    int num = dice->cast();
    position += num;
    std::cout << name << "さんは" << num << "が出たので、" << position <<"に進みました\n";

    if (position >= board->goal_position) {
        std::cout << name << "さんの勝ちです\n";
        has_ended = true;
    }

    if (board->HasEffect(position)) {
        board->ApplyEffect(this);
    }
    // 改めてゴール判定はしない:3マス進んでゴールに行くことは無いことを暗黙裏に仮定
    // 改めて効果判定はしない:効果マスで進んだり戻ったりした先が効果マスでないことを暗黙裏に仮定

    return 0;
}

まず単純にrand()関数を使う方式だと、Diceクラスの中身は以下のようになる。

#include <stdlib.h>
#include <time.h>

void Dice::init() {
    srand((unsigned)time(NULL));
}

int Dice::cast() {
    return rand() % 6 + 1;
}

全貌はここには書かないので以下を参照。

github.com

この例に限らず一般の場合にも、サイコロ(または類似の乱数)のクラスを作って、呼ばれたらサイコロを振った値を返すようにしたいじゃん。多分。

しかし、rand関数を使ったやり方だと、以下の問題点がある。

  • 厳密にはそれぞれの目が等確率ではない(rand関数は32768通りの値を返すがこれは6の倍数ではない)
  • 乱数の品質が良くない
  • 自分の目的に合った乱数に計算し直す必要がある(1~6の整数ならまだしも、正規分布ポアソン分布に従う乱数をrand()から計算するのは一苦労だ)

というわけで、randomライブラリを使って書くにはどうすれば良いのだろう。

一様初期化を用いた実装

/* header.h */

#pragma once
#include <iostream>
#include <string>
#include <random>

class Player;

class Dice {
public:
    int cast();
    Dice() { ; }

    std::random_device seed_gen;
    std::default_random_engine engine{ seed_gen() };
    std::uniform_int_distribution<> dist1{1,6};
};

/* dice.cpp */
#include "header.h"
int Dice::cast() {
    return dist1(engine);
}

std::uniform_int_distribution<> dist1{1,6} という書き方は、

の合わせ技である。
dist1(1,6)と普通のカッコを使うとエラーになる。dist1という名前のメンバ関数を定義したと解釈されてしまうからだ。

上記の2項目はC++11の機能である。
コンパイラの実装状況 - cpprefjp C++日本語リファレンスによれば、S2013以降で対応となっている。
(俺はある場所ではVS2012を使っているのでこの方法が使えない。つらい。)
他の方法はあるんだろうか? と考えたのが、下のメンバイニシャライザの方法である。

メンバイニシャライザ

乱数ライブラリのインスタンスをクラスのメンバ変数にしたい。
このメンバ変数に引数を与えて初期化をしたい、というのが、今やりたいことである。
したがって、メンバイニシャライザ(メンバ初期化子) を使えば目的は達成できる。

/* header.h */
#pragma once
#include <iostream>
#include <string>
#include <random>

class Dice {
public:
    int cast();
    Dice();

    std::random_device seed_gen;
    std::default_random_engine engine;
    std::uniform_int_distribution<> dist1;
};

/* dice.cpp */
Dice::Dice():
    engine(seed_gen()),
    dist1(1,6)
{
}

int Dice::cast() {
    return dist1(engine);
}

Dice::Dice()の中でメンバイニシャライザを使って初期化すれば良い。
(メンバイニシャライザを検索してもいまいち情報が出てこなくて自信がないけど、C++11や14の機能じゃないよね?)

エラーになる例

メンバイニシャライザを使わずにコンストラクタの中で初期化することはできない。
コンストラクタ内部に書くと、()演算子の呼び出しであって、初期化にはならないからだ。

Dice::Dice()
{
    engine(seed_gen()); //エラー
    dist1(1, 6);  //エラー
}
E0304    オーバーロードされた関数 "std::uniform_int_distribution<_Ty>::operator() [代入_Ty=int]" のインスタンスが引数リストと一致しません
E0304   関数 "std::mersenne_twister<_Ty, _Wx, _Nx, _Mx, _Rx, _Px, _Ux, _Sx, _Bx, _Tx, _Cx, _Lx>::operator() [代入_Ty=unsigned int, _Wx=32, _Nx=624, _Mx=397, _Rx=31, _Px=2567483615U, _Ux=11, _Sx=7, _Bx=2636928640U, _Tx=15, _Cx=4022730752U, _Lx=18]" のインスタンスが引数リストと一致しません

という、めっちゃ長いエラーメッセージに襲われることになる。

以上。それでは。

継続を目指して取り組んだ、2018年のブログ活動を振り返る

write-blog-every-week Advent Calendar 2018 の7日目の記事です。
https://adventar.org/calendars/2925 6日目の記事は、id:jalemy さんの「テキスト校正くん」を導入して読みやすい文章を書きやすくなった - むにえる牧場 でした。

write-blog-every-weekとは、毎週月曜日から日曜日までの間に、ブログに1記事上げることを目標としている会です。できた経緯はここを参照。
勢いで週一ブログ書くslackグループを作った - もがき系プログラマの日常

最近のLTでも今年のブログについてちょっと触れました。
(speakerdeckのURLを追記予定……) 2018年12月24日 speakerdeckのURLを追記しました。

1年の締めくくりの良い機会なので、この記事では今年のブログ活動を振り返る。(というわけで基本的には自分語りです。) 今年の始めから時系列順に書いていこう。

@kakakakakkuさんの記事を見てブログ更新を頑張る→挫折

旧ブログの、今年の目標の記事より引用する。

・ブログ週1で書く
間違いなくこの辺の影響ですね。
「ブログを書く技術」を発表した - kakakakakku blog
アウトプット駆動学習を習慣化する - kakakakakku blog
残念ながら俺にはブログメンターなんて素晴らしいものが無いので、どれくらい続くかすこぶる怪しいですが、この記事で3週間目です。
2018年の目標 子供の落書き帳 Remix

今年の始めの時点で、@kakakakakkuさんの影響を受けて、週一で更新しようと頑張っていることがわかる。
2018/03/17 まで(かなり甘めにカウントして)9週間続いていた。しかしその後は更新頻度が落ち、1ヶ月以上間が空いてしまった。

なぜブログが書けなくなるのだろう。
ブログを書き始めたのは2007年と、ブロガー歴だけは長いので理由はよくわかっている*1。 書いていても反応が全く無いと、どうしても張り合いがなくて続かなくなってしまうからだ。 雑踏の中で孤独に一人演説をしているみたいな気分になってしまう。

ブログメンターを受ける

そうこうしているうちに、@kakakakakkuさんの「ブログメンターをするのでメンティー募集するよ」という投稿を見かけて、申し込みをする。

6月6日に@kakakakakkuさんに申し込みをしている。 ただ、そのあとしばらくやり取りがあったので、実際に週一更新を始めたのは6月の中旬からである。 (余談だが、機械学習の勉強会で前で発表したのが6月15日だった。この時期は俺のモチベーションが高かったらしい。「コンフォートゾーンを脱出するんだ!」と思ってた。) 6月の後半と7月にメンタリングを受けた。 (メンティを卒業したときにエントリーを書く人が多いが、俺の場合は機会を逸して書いてない。)

教えてもらったことの1つで「GitHubのtrendingを見て、良さそうなライブラリがあったら、その紹介/試してみた を書く」 たまに暇な時に検索してますけど、見てるだけでも最近の流行りが分かってきて良い。 まだ「記事にする」とこまでは行けてないけど。

7月末に卒業し、これで週1で技術ブログの更新がはかどり、俺は圧倒的成長を遂げて身長も伸び、モテモテになり、年収も3倍に…… であればハッピーなのだが。そう上手くは行かなかった。

8月は持ち堪えたものの、9月中旬から更新が滞り始める。

やっっっっぱり、無いのだ反応が。
自分のブログの名前でTwitter検索しては、「なんか1つでも反応があれば俺は嬉しいのに……何もない……」と思っていた。

FC2からはてなに移行

最初のブログはYahooブログ、その次のがFC2ブログだった。

世の潮流は圧倒的にはてなブログだよなぁ、どんなもんか、まずは試してみるか……とはてなブログで少し書いてみた。 8月12日にこのブログの最初の記事を書いている。

それでそのままFC2ブログには戻らなかったので、はてなが良かったということだろう。良いところを挙げると以下の通りだ。

良いと思うけどなかなか良いと言い切れないところは以下の通りだ。

  • 他のはてなブログから俺のブログに対してリンクがつくと通知される(便利は便利なのだが、もともとブログ全体にあったトラックバックを、はてなの中に閉じた形で採用したので、はてな以外のブログの人は恩恵をうけることができず、微妙な気分ではある)
  • 「読者になる」ボタンの存在(便利は便利なのだが、各種ブログに使えるRSSリーダーを、はてなの中に閉じた形で……以下略)

write-blog-every-week Slackグループに入る

9月下旬。勢いで誕生したwrite-blog-every-week Slackグループに、勢いで入った。 kojirooooocks.hatenablog.com

@kakakakakkuさんのメンターだった人が多いが、もちろん誰でも歓迎である。

ちょうどこの辺のツイートをしてて、ブログへのモチベーションが下がってきていた時期である。

「人がやってるなら俺も」で10週続いています

(↑実は1回更新落とした) write-blog-every-weekグループの良いところを挙げてみよう。

  • 人がやってるなら俺も頑張ろうと思う(心理学でいうピア効果?)
  • 書いたことに対して反応がある!!!*2
  • botが各人のブログの更新を管理し、1週間のうちに書いていないと煽って……リマインドしてくる。メンバーがbotをいい感じに作ってくれました。俺にはできない。技術の力ってすげー。

機械学習pythonがメイン……しかし無関係な記事がバズった 複雑……

一応、メインは機械学習pythonについて書くブログのはずである、しかし、この前何の気なしに書いた全然関係ない記事が 人気を集めてしまい、「嬉しい……けどちょっと微妙な気分だ……」となった。 linus-mk.hatenablog.com (なんか役に立つことを書いたわけでもなく「分からん。困った。皆どうしてるの?」って記事なのに、なんでブクマが増えたんやろ……)

反省点

ここまでつらつらと今年の軌跡をたどってきた。 ブログを書く上での反省点を挙げる。

更新が日曜夜になることが多い

その結果、「あーっ、間に合わねぇ、仕方ないからこれで投稿しよう」となって、記事のクオリティが落ちることが少なからずある。

ネタ探しが定着しない

ネタが出てきたらEvernote内のネタ帳に記録する……はずなのだが、この間みたらネタ帳が1ヶ月以上更新されていなかった。
まだネタの書き留めが習慣化できてないなー。しばらくうまく回ったと思ったら忘れる…… 「毎日朝に」みたいな定期的なものじゃなくて、「ネタを思いついたときに」という不定期だから習慣づけが難しい。

ブログに時間を割きすぎてコード書く時間が減る

つらい。

ブログの記事(=1週間)より長い単位での学習計画が無い

詳細はさっきの記事を参照: エンジニアの自己学習の中長期計画をどう決めたらいいんだろう? - 子供の落書き帳 Renaissance

ブログを書き続けるのは難しい、だから

ブログのメンターを受けた人は週1件(以上)の更新をしていたわけだが、その後どうなったのだろうか。
はてなブックマーク - kakakakakkuに関するky_yk_dのブックマーク
にブログメンティ卒業したエントリーが6件あった。そのうち3人はブログの更新が止まり、残りの3人は今も更新し続けている。ついでにいうと、後者の3人はwrite-blog-every-week グループのメンバーである。

更新をしなくなった人を批判するつもりは別に全然ない。だって俺自身もメンティ卒業後週1度更新ができてなかった時期があるし。 それにメンティを卒業したあとのあとの更新はノルマではないし。
自分から志願してメンターをお願いしに行くほどやる気のある人たちでさえ、その後更新を続けるのは難しい。というのが言いたいことだ。 グループメンバーのtadaken3さんの記事経由で知ったけど、1年間続くブログは30%(3ヶ月に一回書けばとりあえず継続とみなすという激アマ条件)というデータもある。
誰かの後押しがなくなったときに、自分一人で走り続けるのは難しいのだ。
そして、write-blog-every-weekは頑張るブロガーたちが応援し合ってブログの更新を積み重ねていく、素敵なグループである。


8日目の担当は、技術同人誌を精力的に書きながらブログの更新も熱心に続けているid:konosumiさんです。よろしく!

*1:このうち大半の時期は技術ブログではなく適当な雑記ブログであった

*2:Slack上では絵文字で気軽にリアクションできるため、反応がほしいときには絶好のツールである。

pythonの音楽解析ライブラリlibrosaを試してみた

librosaというのはpythonのライブラリの1つであり、音楽を解析するのに使う。

python 音楽 解析」で検索してみると、結構な割合でlibrosaを使っている。

Pythonを使った音楽解析をやってみる - のんびりしているエンジニアの日記

深層学習を使って楽曲のアーティスト分類をやってみた! - Platinum Data Blog by BrainPad
この記事はブレインパッドの公式ブログのもので、音楽からアーティストの分類をしている。なかなか面白いなと思う。ここでもスペクトログラムを描くのにlibrosaを使っている。

どうやらlibrosaは、pythonを使って音楽の分析をしようと思ったらメジャーに使われているライブラリらしい。
しかし、pythonの良いライブラリを紹介しているawesome pythonには入っていない。(何でやねん。)
awesome-python: A curated list of awesome Python frameworks, libraries, software and resources

ちょっと使い始めて、公式サンプルを動かしてみたところまでのメモです。

公式ドキュメント

https://github.com/librosa/librosa
https://librosa.github.io/librosa/
同じようで少し違うドキュメントが2ヶ所に存在している。

上のgithub.comのほうに「Introduction notebook」のリンクがあった。リンク先のjupyter notebookを落として、動かしてみる。
なお、下のgithub.ioのほうがより詳細で、ライブラリ内の各関数の使い方が書いてある。

サンプルを動かしたらエラーが出た

librosaのインストールは簡単で、pip install librosaでインストールできる。
ダウンロードしたnotebookを起動して、実行……
あれ、エラーが出てきた。

---------------------------------------------------------------------------
NoBackendError                            Traceback (most recent call last)
<ipython-input-5-9f5c39d613b9> in <module>
      5 # audio_path = '/path/to/your/favorite/song.mp3'
      6 
----> 7 y, sr = librosa.load(audio_path)

c:\program files\python37\lib\site-packages\librosa\core\audio.py in load(path, sr, mono, offset, duration, dtype, res_type)
    110 
    111     y = []
--> 112     with audioread.audio_open(os.path.realpath(path)) as input_file:
    113         sr_native = input_file.samplerate
    114         n_channels = input_file.channels

c:\program files\python37\lib\site-packages\audioread\__init__.py in audio_open(path)
    114 
    115     # All backends failed!
--> 116     raise NoBackendError()

NoBackendError: 

librosaのForum上でも同じ報告が上がっているし、
githubのissueも立っている
これらを読むと、「対象のファイルの読み込みに失敗している(読み込める形式でない)。ffmpegをインストールすれば解決する」ということが分かる。

audio_path
 -> 'c:\\program files\\python37\\lib\\site-packages\\librosa\\util\\example_data\\Kevin_MacLeod_-_Vibe_Ace.ogg'

audio_path = librosa.util.example_audio_file() で指定されるファイルは、拡張子oggである。デフォルトで(ffmpeg不使用で)どの形式の音楽ファイルが読み込めるのかよくわからない。oggがデフォルトでは読み込めず、ffmpegを使う必要があるということだろう。

http://librosa.github.io/librosa/tutorial.html の方には

The first step of the program:
filename = librosa.util.example_audio_file()
gets the path to the audio example file included with librosa. After this step, filename will be a string variable containing the path to the example audio file. The example is encoded in OGG Vorbis format, so you will need the appropriate codec installed for audioread. と書いてある。「appropriate codec installed」ってのが「ffmpegをインストールしておけ」ってことなんだろうか?

https://www.ffmpeg.org/
からffmpegを落とすが、Windows版ではソースからビルドすることができないので実行ファイル形式でダウンロードする。 https://www.ffmpeg.org/download.html
から上の「Download」を選ばずに下のWindowsロゴマークから進み、ビルド済みの実行ファイルをダウンロードする。exeファイルのあるディレクトリに対してパスを通せば完了だ。

librosaチュートリアルでは何をやってるの

  • メル・スペクトログラムの描画
  • Harmonic-percussive source separation (HPSS) 音程のある音と打楽器の音を分離する
  • クロマグラム(Chromagram) 12音のうちどれが鳴っているか
  • MFCC (メル周波数ケプストラム係数)

など。

Machine Learning Casual Talks #7 イベントレポート #MLCT

2018年11月20日に開催されたこのイベントに参加してきました。 Machine Learning Casual Talks #7 - connpass 以下、敬称略です。

発表

@_stakaya(高柳慎一) 「The load to Machine Learning Engineer from Data Scientist」

  • LINE DEVELOPER DAY_2018に参加しましょう(この発表の翌日だった)
  • LINEのDataLab所属

    • DataLabとは……LINEの多くのサービスに対して一気通貫でデータ管理をする、横串の位置付けにある組織。その中のエンジニアの人は、LINE全社で使える機械学習基盤を作ったりしている
  • クオンツからここまで来るのにどういう経緯を歩んできたのか?

  • 社会人歴は13年程度

  • 景気のウェーブを感じろ! と常日頃から言っている。

    • 2006年に新卒で就職した (2000年のバブル崩壊から復調の兆しが見えた頃。新卒就活もまぁまぁ何とかなった)
    • 2008年10月に転職……と同時にリーマンショックを喰らう
      • 年功序列のおかげで居続けると賃金が上がる
      • けどキャリア採用の募集は皆無
  • 不景気だったときに新卒で入った人たちは、厳しい競争を勝ち抜いて入社した人。優秀な人という補正をかけてよい

  • 景気を意識して行動しろ。

    • なぜなら俺は6年ほど(不景気で転職できなかった時期に)エクセルのアドインとかコンサルタントをやってたからだ
  • 2011年の東日本大震災

    • システム障害を起こした銀行が、手作業で業務をしていた報道を見ていた上司曰く「最後は人だぜ!?」……いやそうじゃねぇだろ
  • 意思決定に必要なのはデータに基づいた事実ではなく、相手の弱みだったりする(「獅子のごとく」って本を読むとそのへんが分かるよ)

  • リクルート系に転職して思ったこと:

    • エンジニアやってるよりも、媒体のUXを改善したほうが金になるんじゃね?
      • KDDに行ってきて(宣伝:LINEでは会議に社費で行かせてくれるよ!)、Airbnbの人たちがABテストのメトリクスをどう測ればいいかを論じていたのを聞いてそう思った
      • 機械学習の精度を何%上げましょう、よりも収益に直接結びつく
  • 本橋智光(前処理大全の著者)が同僚だったが転職した。ので、俺も移るか、と思ってLINEに来た

  • 景気が良いから、私には「機械学習エンジニア」というラベルが貼られて、お金をたくさんもらえているという状態です。

@xecus(大田黒) 株式会社 ABEJA 「(仮)ABEJA InsightにおけるML活用サービスのデリバリー」

  • 2015年新卒入社。趣味はいろいろ工作すること。

    • 脈拍計で異常検知したり、FPGAやったり
  • 会社紹介

    • 2012年に創業
    • NVIDIAから、アジアで唯一出資してもらった
  • 2つのサービスを運営している

    1. ABEJA Platform AIプラットフォーム
    2. ABEJA Insight 特定業種向けのアプリケーション群
  • ABEJA Insightの中の1つであるABEJA Insight for retail

    • 店舗経営をする人向けのプラットフォーム
    • 映像を解析し、性別年齢を推定する
    • 最近はリピーター推定機能に取り組んでいる
    • 顔の特徴量の抽出は法的にグレーゾーン……
    • 経済産業省のワーキンググループに参加して、規則を整備しつつ開発した
  • データのデリバリーは考えればできるのだが、運用観点でのバックエンド設計ができずに辛かった

  • データ転送関連

    • 映像をクラウドに垂れ流しているので、「P2Pやファイル共有とかやってるんじゃないか」と、ISPや情シスからブロックされたりした
    • PASMO決済ができなくなったことがあった
  • 設置環境が多様になって、エンジニアの対応工数が増えてきた

    • 天井がない店舗で、「数十メートル離れたところからカメラ設置して年齢性別を取れるか?」と言われたり
  • バイス品質の見える化が大事 システムを導入して安心していたけど、実は導入してからが本番

  • 環境が変わることでデータ採取に失敗する例

    • 特定時間帯だけデータが取れないんだけど→逆光だった
    • オクルージョン(遮蔽物により認識したい物体の一部が隠れてしまう事象)
      • 店舗内の配置が変わるとカメラに映る人の顔が隠れたりする
      • 七夕イベントは要注意!笹の葉によるオクルージョンが発生する

LT

@techeten : Jupyter だけで機械学習を実サービス展開できる基盤 ~ サイエンティストとエンジニアの共生へ ~

リクルートグループのサービス横断の利益創出チームに所属
プランナー・サイエンティスト・エンジニアという組織
いろいろなサービスの運用から企画が上がってくるので、当たりを見つけて、見つけたら他に応用していきたい

本番環境から呼べるABテストのサーバーが必要
サイエンティストがエンジニアをしている余力がない

構築した基盤の詳細については、リクルートライフスタイルの技術ブログを参照 engineer.recruit-lifestyle.co.jp

自由に使えるjupyter環境
papermill を使って、notebookをそのままバッチ実行できる

kubeFlow, MLflowなどがあるが使わないのか?
工数はあまりとらず、今のところ2人月くらいで開発した。要件を満たす物があれば置き換えも可能 →構築した基盤環境は疎結合なので、いいツールがあったらパーツを置き換えられるようにしている

@yukiyan_w「機械学習基盤を一人で構築するということ」

TimeTree ユーザ数はグローバルで1000万 予定に関するターゲティング広告を出したい。そのためにユーザの予定を分類する

機械学習が未経験の状態から、だいたい3ヶ月でリリースした 学習時に単語の前処理に時間がかかる→spark(databricks)

やったことないのない技術は、PoCの延長ぽくなってしまう
→タスクの洗い出し、見積もりに重点を置いた。プロダクトオーナーとの握りの部分

技術的負債が貯まるのを避けるために、ドキュメントかいて口頭で共有した。

KiichiUeta 「JapanTaxiにおける機械学習活用事例」

ドライバ向け 流し需要予測システム

BIチーム データ基盤整理、レポート作成化・可視化
AIチーム 機械学習の高度なロジックを考える

エリア別の需要予測 未来の需要を予測して可視化する。500メートル四方のメッシュ。
過去実績に加えて、天気、電車運行、イベント情報、人口動態を入力とする
電車が止まるとタクシー需要が増大するため。
人口動態は携帯のGPSデータの集積

ピンポイントで需要スポットの予測 タクシーの乗車実績を入力とし、クラスタリング DBscanを使う

ディスカッション

(回答者は発表者の高柳さん・大田黒さん。どちらの回答であるかは下記に書いていません。このパートはスライドが無いので、うまくメモできなかったところは私の判断でカットしています。)

今後年収を上げるために機械学習エンジニアが身につけるべきスキルは何だと思いますか?

業界が物を言うので、高収入な業界に行くと良さそう。
素直にエンジニアやってれば良いんじゃないかと思う。
金になりそうなプロジェクトに自分から首を突っ込め

AWSGCPのインフラ周り
業界独自のドメイン知識を十分に持っていると強い

GoogleのAutoMLのような、機械学習エンジニアは今後いらなくなるんじゃないかという風潮についてはどう考えてますか?

最終的にはエンジニアによって終わるんじゃないかなー
継続的な運用が肝心だと思うので、autoMLでやってるのを継続的に精度改善とかしていく形になるのではないか 大企業の大規模データだと、AutoMLを使うと一瞬で大量の課金が発生してしまうので、使用は現実的ではない

効果的なエンジニア採用方法
勉強会とかリファラルとか、色々ですね。wantedlyで採用をやってはいるけど、なかなか難しい。
面白いデータを扱っている感を出すとよい
伝手はめっちゃ重要。
応募者のコードを見よう

技術ブログを書かないと「あの会社、何やってるか分からん」と言われて応募されない傾向にある

機械学習システムはかなり利用企業の事情に反映されやすいと思いますが、kubeflowなどで汎化は可能だと思いますか?

繰り返しになるが、運用フェーズが肝だなと思っている
顧客企業が少ないうちはいいけど、増えてくると運用に耐えられないことが起きる

パブリッククラウドほとんど使ってない、自社で管理している
LINEの技術ブログのどこかに書いてあるけど、OpenStackを使ったプライベートクラウドがほとんど

(このあたりの記事が近いか?)
LINE Engineer Insights vol.4「OpenStackベースのPrivate cloud "Verda" の野望」 - LINE ENGINEERING
OpenStackベースの自社クラウド "Verda" - LINE DEVELOPER DAY 2017|開発ソフトウェア|IT製品の事例・解説記事

関連リンク

Togetterまとめ
Machine Learning Casual Talks #7 まとめ #MLCT - Togetter

私はこの勉強会にブログ枠で参加していましたが、同じブログ枠にいた他のお二人が先に記事を書いていたので紹介します。

@__john_smith__さん
Machine Learning Casual Talks #7に参加してきました #MLCT | DevelopersIO

@tai_hatakeさん
Machine Learning Casual Talks #7@メルカリを聞いてきました! | 稼げるエンジニアになる

それでは。

エンジニアの自己学習の中長期計画をどう決めたらいいんだろう?

そういや、自分は自己学習についてロクに計画を立ててないなー、とふと気になったので、ブログ記事にしてみる。
今の自分の状況は次の通り。

  • 週に1回はブログを書くことに決めている
  • そのために、題材をネタ帳から選んで、適宜検証作業をしてブログを書く
  • それ以上の単位の学習計画はなし

振り返ってみると、ブログのおかげで「今週はこれに取り組む」はあるんだけれど、それ以上のまとまりが無い。
「今月は/この四半期はこれに取り組む」を特に決めていない。
その結果、なんか、一週間ごとの取り組みがつながっていなくてバラバラになっているような感覚・印象がある。

(以下があったときはそれに沿って勉強を進めていた。この手の勉強会があると適切なペースで勉強を進めることができて、しかも結果的に一定のまとまった範囲を習得できる。ありがたい。

現時点で、以下のような困りごとが生じている。

  • 良さそうだと思って買った技術書を読まず、本棚にしまいっぱなしにしてしまう
  • GitとAWSがいつまでも分からないマンになり続ける*1
    • Udemyで、AWSの良い講座があったので買ったのは良いけれど、まだやっていない(少しは観ました。少しは。)

機械学習・データ分析・pythonを基本的に優先して学習しているので、それ以外の分野は後回しにすることが多い。
すると、「上記の分野以外で、エンジニアならば習得しておくべき基本的な事項」が抜けてしまう。AWSとか。
でも今どき、「AWSは全然分かりません」じゃあ通用しないですよね、きっと。

先に書いた「困りごと」をもう少し抽象的に言うと、中長期的な計画を立てずにやってるから、「気づいたら、自分のスキルセットが思ってたのと違う。こんなはずじゃなかったのに……」となりがちなのだ。

計画を立てて学習していったほうがいいなー。
ただし、計画を立てるってことは、予定と実際の管理をして、振り返りをするあたりまでセットでやらないといけない。
週次で自己レビューやるの、少しやってたけど、習慣にならなくてやめちゃったんだよな……

エンジニアの人ってみんな、自己学習に対して定期的に

をしてるもんなのかな?

プライベートな勉強の中で、いつどの本を読んで、いつ何のサービスを作って、いつどの分野の勉強をして……って、どうやって決めてるんだろうか、みんな。
誰か教えてほしい。

*1:AWSおよびGitは少しは触ったけれど、忘れてしまった部分も多いし、自分のスキルに書けるほど習得していない

numpyの[::-1].sort()で配列を降順にソートできる理由を考える

きっかけ

東京大学 松尾研究室の「第3期 Data Science Online Course」が、先週から始まった。
online course3 | 東京大学グローバル消費インテリジェンス寄付講座
「グローバル消費インテリジェンス寄付講座」、Global Consumer Intelligenceを略してGCI講座と書かれることもある。……名称が結構色々ある。
13週間にわたってデータサイエンスの講座をオンラインで受講する。また、受講生はWeb上の環境を利用することができ、毎週課題を提出する。

なお講座のテキストはこちらで一般公開されている。
GCIデータサイエンティスト育成講座・演習コンテンツ公開 | 東京大学松尾研究室 - Matsuo Lab

先週は初回であり、基本的な内容だったのであまり難しくはなかったのだが、1箇所だけ引っかかった。それが今回の話である。

問題のコード:numpyの[::-1].sort()で配列を降順ソート

以下のようにするとnumpy配列が降順にソートできる、とテキストには書いてあった。

import numpy as np  
  
sample_array = np.array([1,4,2,5,3])  
  
sample_array[::-1].sort()  
print("ソート後:", sample_array)  
  
#→ ソート後: [5 4 3 2 1]  

……あれ?

  • sort関数は標準では配列を昇順にソートする
  • [::-1]は配列を逆順にする
    ということも講座テキストには書いてある。

俺の予想は

  • sample_array[::-1]は配列を逆順にするから、[1,4,2,5,3]を[3,5,2,4,1]にする
  • その後、sort関数で配列を昇順にソートするから、表示されるのは[1 2 3 4 5]である
    だった。しかし実際には、予想と違って降順にソートされている。

なぜなんだろう?

以下、

  • 1次元のnumpy配列(ndarray)のみを対象にする。2次元以上については触れない。
  • メモリの効率性については触れない。
  • 計算時間の効率性については触れない。

numpy.sortと numpy.ndarray.sortがある

少し紛らわしいが、ndarrayをソートするにはnumpy.sortと numpy.ndarray.sortがある。
numpy.sortはndarrayを引数に取って、その配列をソートした配列を返す。
https://docs.scipy.org/doc/numpy-1.15.1/reference/generated/numpy.sort.html
一方、numpy.ndarray.sortは呼び出し元のインスタンスであるndarrayをソートする。返り値はNoneである。
https://docs.scipy.org/doc/numpy-1.15.1/reference/generated/numpy.ndarray.sort.html

type(sample_array)  
#→ numpy.ndarray  

というわけで、sample_array.sort()は、numpy.ndarray型のオブジェクトであるsample_arrayのメソッドを呼び出している。今回使っているのはnumpy.ndarray.sortだわ。
以下、ndarray.sortと書く。

なお、numpy.sortを用いて配列を降順ソートする方法は後述。

スライスの中身を変えて試す→「その部分列をソートし、他の部分は不変」になる

どういうわけで降順ソートになるのか、納得できなかったので、スライスの中身を変えて色々試してみた。

sample_array = np.array([9,7,6,5,1,2,3])  
  
sample_array[0:3:1].sort()  
print("ソート後:", sample_array)  
#→ソート後: [6 7 9 5 1 2 3]  

sample_array[0:3:1] は配列の最初の3要素を指す。
結果を見ると、最初の3要素だけが昇順にソートされていて、他の値は変わらない。

sample_array = np.array([1,100,4,400,3,300,2,200])  
  
sample_array[::2].sort()  
print("ソート後:", sample_array)  
#→ソート後: [  1 100   2 400   3 300   4 200]  

sample_array[::2] は配列の最初から1つずつ飛ばしていった要素(配列を1番めから数えたときの奇数番目)を指す。
結果を見ると、該当する要素だけが昇順にソートされていて、他の値は変わらない。

sample_array = np.array([1,100,4,400,3,300,2,200])  
  
print("ソート前の部分列:", sample_array[::-2])  
sample_array[::2].sort()  
print("ソート後:", sample_array)  
#→ソート前の部分列: [200 300 400 100]  
#→ソート後: [  1 400   4 300   3 200   2 100]  

sample_array[::-2] は配列の最後から最初に向かって1つずつ飛ばしていった要素を指す。

結果を見ると、該当する要素だけが降順にソートされていて、他の値は変わらない。
この結果から考えると、
sort関数に[200 300 400 100]を渡して[100 200 300 400]を得て、それを元の場所に入れ直した。
つまり、もともと200があった場所に100を入れて……ということをやっているみたいだ……?
sort関数がやってることって、配列[200 300 400 100]から[100 200 300 400]への写像作用素と思っておけば良いのかな?

その部分列を取り出してソートするには?

部分列を取り出して昇順にソートしたものを得たいときにはどうするか。
まず、いったん他の変数に代入するという方法がある。

sample_array = np.array([9,7,6,5,1,2,3])  
  
a = sample_array[0:3:1]  
a.sort()  
print("ソート後:", a)  
  
#→ソート後: [6 7 9]  

または、ndarray.sortではなくnp.sortを使い、引数に部分列を指定する。

sample_array = np.array([9,7,6,5,1,2,3])  
  
b = np.sort(sample_array[0:3:1])  
print("ソート後:", b)  
  
#→ソート後: [6 7 9]  

最初の例に戻る

最初の例も、sample_array[::-2]と同様に考えれば一応納得できる。下記に再掲。

sample_array = np.array([1,4,2,5,3])  
  
sample_array[::-1].sort()  
print("ソート後:", sample_array)  
  
#→ ソート後: [5 4 3 2 1]  

sort関数に最初の配列の逆である[3 5 2 4 1]を渡して[1 2 3 4 5]を得て、それを元の場所に入れ直した。
結果的に、配列は降順ソートされた[5 4 3 2 1]になった。
(sort関数に何かオプションを指定したわけではないので、sort関数のほうは「降順にソートした」とは思っていない)

色々と実際に試してみて挙動を確認してみたけど、この辺が限界っぽい。
試してみるだけだと、なんでこういう風になっているのか、が分からない。
「sample_array[::-1].sort()は配列を逆順にしたあとにソートしていると思うが、なぜそうならないのか?」と聞かれたら答えられないしなー。
ndarray.sort関数には(C++言語でいうところの)ポインタとか参照が渡っているのか……?
(ここで唐突にC++が出てきたのは、俺が普段業務で触っているのがC++だからである)
公式ドキュメントを「reference」とかで探してみてもうまくヒットしない。

おまけ 配列を降順にソートする他の方法

個人的にはこのやり方で降順ソートをするのは直感的でないと思ったので、
別の書き方のほうが読んで分かりやすいのなら、そっちを使おうと思った。

sample_array = np.array([1,4,2,5,3])  
sample_array.sort()  
print(sample_array[::-1])  
#→[5 4 3 2 1]  
  
sample_array = np.array([1,4,2,5,3])  
print(np.sort(sample_array)[::-1])  
#→[5 4 3 2 1]  

ただし、降順ソートした結果を実際に使おうとすると変数に代入しないといけないから配列のコピーが発生する。

……と、記事を最後まで書いて気づいたけど、ほとんど同じ質問がStack Overflowにあったわ。
python - Efficiently sorting a numpy array in descending order? - Stack Overflow

それでは。