かえるのプログラミングブログ

プログラミングでつまずいたところとその解決策などを書いていきます。

Google Colaboratory で fastText の pretrained model のSetup をする。

こんばんは、かえるるる(@kaeru_nantoka)です。今回は、fastText の公式ページだと説明が不親切だなーと感じたので 「pretrained の fasttext を GoogleColaboratory で利用する」というタイトルで手順を備忘録として残しておきます。

ソースは例のごとく GitHub (https://github.com/osuossu8/Utils/blob/master/fastText_pretrained_Setup.ipynb) においていますので必要に応じてご参照ください。


目次

1. 動機

2. ソースコードと説明

3. 感想


1 : 動機

公式ページ (https://fasttext.cc/docs/en/english-vectors.html)のわかりやすいところに連携されている pretrained model は .vec の形式で与えられていました。

gensim のラッパーとして提供されている FastText.load_fasttext_format や KeyedVectors.load_word2vec_format やら試したものの未知語に出会うとエラーに。。未知語でもよしなにしてくれるのが fasttextの利点じゃなかったっけ・・??

どうやら、 .bin 形式で与えられているものがいいらしいということで FastText.load_fasttext_format を試してみるもエラー。。

もう自力でビルドするしかないとソース引っ張ってきてやってみた次第です。


2 : ソースコードと説明

# Colab だと tmp ディレクトリに落としてきて、ドライブで解凍すれば容量食わないからいい感じです
!wget  https://dl.fbaipublicfiles.com/fasttext/vectors-english/wiki-news-300d-1M-subword.bin.zip

!unzip wiki-news-300d-1M-subword.bin.zip -d drive/My\ Drive

!git clone https://github.com/facebookresearch/fastText.git

!cd fastText;sudo pip install .

import fastText

# なかなか時間かかる
model = fastText.FastText.load_model('drive/My Drive/wiki-news-300d-1M-subword.bin')
print(model)

# インスタンスの説明(どんなメソッドがあるかなど)が見られます。
# とても便利
print(help(fastText.FastText._FastText))


import numpy as np
import pandas as pd

# class でラップして使っています
class fastTextVectorizer:
    def __init__(self):
        self.model = model
 
    def get(self,corpus):
        vec = []
        # 空のリストが渡ってきたときにゼロベクトルで埋めるようにしてます
        if len(corpus) != 0:
            for p in corpus:
                vec.append(model.get_word_vector(p))
        else:
            vec.append(np.zeros(300))

        return np.array(vec)

Vectorizer = fastTextVectorizer()

sentance = "Kaerururu likes kaggle very much"

corpus = sentance.split(' ')

print(corpus)
# [out]
# ['Kaerururu', 'likes', 'kaggle', 'very', 'much']

vec = Vectorizer.get(corpus)

print(vec)

"""
[out]
array([[ 0.00479189,  0.00076622, -0.00025739, ..., -0.00490327,
        -0.00920221, -0.014365  ],
       [ 0.01596881,  0.04688494,  0.00066695, ...,  0.01253003,
         0.0161329 , -0.00909959],
       [ 0.01079815,  0.0054005 , -0.01178535, ...,  0.00313774,
         0.00204078, -0.00597636],
       [ 0.00384124, -0.06426769,  0.02252812, ..., -0.00964052,
        -0.01634751, -0.00016563],
       [ 0.00118656, -0.03619536,  0.0089293 , ..., -0.01233855,
         0.02183427, -0.01065825]], dtype=float32)
"""

'kaerururu' といった単語もきちんとベクトル化してくれました。


3 : 感想

bin が 7GB くらいあるのでなかなか容量持っていかれますが、Colab だとデフォルトで 15GB 割り当てあるし、色んな単語を vector 化できて楽しいので割にはあるんじゃないかなーと思います。また BERT や ELMo が盛り上がってきて fastText 使いたいのに古い情報しかないぞ・・・!!!と困った私みたいな方に届いたら嬉しいなーと思います。

あと GitHub が充実していくのって楽しいです。

以上です、ありがとうございました。

PyTorch NN を用いて Titanic コンペに挑戦する。

こんばんは、かえるるる(@kaeru_nantoka)です。今回は、テーブルデータに Neural Network (以下 NN) を応用してみる Tutorial として PyTorch で Titanic 問題に取り組んでみたので、そのソースコードと説明をご紹介できればと思います。

今回のソースコードは、kaggle kernel に公開設定で置いております( https://www.kaggle.com/kaerunantoka/titanic-pytorch-nn-tutorial?scriptVersionId=12211278 ) ので、質問やアドバイスなど大歓迎です。


目次

1. 動機

2. 参考 kernel 集

3. ソースコードの説明

4. 感想など

5. Todo


1 : 動機

https://twitter.com/nyanp/status/1111242580765769728

こちらの Nomi さんのスライドにも言及されているように、昨今の kaggle で上位入賞を果たしている方(チーム)はほとんど木系のモデルのみではなく、NN でもモデリングをしてアンサンブルすることでスコアを伸ばしています。

そんな重要な知見であるテーブルデータにおける NN ですが、ネットで調べてみてもほとんど出てこず、自分で勉強していく中で何か参考になる簡単な問題に対して適用されたコードがあればいいなと思いましたので、復習としてまとめたものを output しようと思った次第です。


2: 参考kernel集

今回の私のコードを書くにあたって参考にしたカーネルです。ネットで調べてもほとんど出てこず〜と上では書きましたが、PyTorch の docs で勉強し、うまい書き方ないかなと、過去コンペのコードを漁っていた傍、参考になりそうなものはありました。こちらにまとめておきます。

i : Quora コンペより

( https://www.kaggle.com/hengzheng/pytorch-starter )

model.fit() の引数で ループ数を回す keras と異なり、PyTorch では、 dataloader というデータと正解ラベルを n個づつ返す iterater のループを、さらに epoch数のループで回すような見慣れない書き方をします。

それプラス Kfold のループを回すような書き方を探していた時に見つけたカーネルです。 Quora コンペも Titanic コンペと同じ 2値分類タスクなので、評価指標の f1-score の部分を acc に書き換える実装をして他の部分はほとんどそのまま使っています。

i i : TwoSigma コンペより

( https://www.kaggle.com/christofhenkel/market-data-nn-baseline )

NN 自体は keras での実装ですが、NN に特徴量を投入する前の前処理部分を参考にしました。カテゴリカルデータ(文字などの数値で与えられていない特徴量や一応数値では与えられているが数値としての意味を持たないもの(男性、女性を 0, 1 として表して与えられているもの))の encoding や numerical データ(数値がそのままの意味を持っているデータ。金額や人数など)を正則化するなど最低限の前処理がなされております。


3: ソースコードの説明

[2] PyTorch の利点である 「seed を簡単に固定できる」 を実現してくれる部分です。

    def seed_everything(seed=1234):
        random.seed(seed)
        os.environ['PYTHONHASHSEED'] = str(seed)
        tf.set_random_seed(seed)
        np.random.seed(seed)
        torch.manual_seed(seed)
        torch.cuda.manual_seed(seed)
        torch.backends.cudnn.deterministic = True
    kaeru_seed = 1337
    seed_everything(seed=kaeru_seed)

[5] categorical columns と numerical columns を指定します。 cat_cols は今回は数値でないものを適当に指定しています。

num_cols はテーブルによっては非常に多くなることもあるので、list(set()) を使用すると簡単に取得できます。

cat_cols = ['Cabin','Embarked','Name','Sex','Ticket',]

num_cols = list(set(train.columns) - set(cat_cols) - set(["Survived"]))

[6] [7] categorical columns と numerical columns それぞれの前処理の部分です。

def encode(encoder, x):
    len_encoder = len(encoder)
    try:
        id = encoder[x]
    except KeyError:
        id = len_encoder
    return id

encoders = [{} for cat in cat_cols]


for i, cat in enumerate(cat_cols):
    print('encoding %s ...' % cat, end=' ')
    encoders[i] = {l: id for id, l in enumerate(train.loc[:, cat].astype(str).unique())}
    train[cat] = train[cat].astype(str).apply(lambda x: encode(encoders[i], x))
    print('Done')

embed_sizes = [len(encoder) for encoder in encoders]

# =====

from sklearn.preprocessing import StandardScaler
 
train[num_cols] = train[num_cols].fillna(0)
print('scaling numerical columns')

scaler = StandardScaler()
train[num_cols] = scaler.fit_transform(train[num_cols])

[8] [9] 今回は私の練習として、自作の層を定義してそれを利用するような NN にしてます。

nn.Sequential() は keras の sequential model と同じように使えます。

自作レイヤーを使わずに net を定義し直すと、

net = nn.Sequential( nn.Linear(12, 32) nn.ReLU(), nn.Dropout(0.5), nn.Linear(32, 1) )

というように書けます。

class CustomLinear(nn.Module):
    def __init__(self, in_features,
                 out_features,
                 bias=True, p=0.5):
        super().__init__()
        self.linear = nn.Linear(in_features,
                               out_features,
                               bias)
        self.relu = nn.ReLU()
        self.drop = nn.Dropout(p)
        
    def forward(self, x):
        x = self.linear(x)
        x = self.relu(x)
        x = self.drop(x)
        return x

net = nn.Sequential(CustomLinear(12, 32),
                    nn.Linear(32, 1))

入力は 12次元 (targetを除いたもの), 出力は 1次元です。 のちに、BCEWithLogitsLoss を最小化するように計算して sigmoid 関数で 0,1 の2値分類を解く形になっています。 (誤りがあったら教えてくださいm( )m)

# Kfold のループ部分
for i, (train_idx, valid_idx) in enumerate(splits):

    # X_train, y_train, X_val, y_val をテンソル化(PyTorch で扱える形に変換)し、 .cuda() (GPUで計算するために特徴量を GPU に渡す処理)をする。
    x_train_fold = torch.tensor(X_train[train_idx], dtype=torch.float32).cuda()
    y_train_fold = torch.tensor(y_train[train_idx, np.newaxis], dtype=torch.float32).cuda()
    x_val_fold = torch.tensor(X_train[valid_idx], dtype=torch.float32).cuda()
    y_val_fold = torch.tensor(y_train[valid_idx, np.newaxis], dtype=torch.float32).cuda()
    
    # model を呼び出して、
    model = net
    # model も GPU に渡す。
    model.cuda()
    
    # loss 関数を呼び出す。BCELoss() よりも好まれるらしい。。
    loss_fn = torch.nn.BCEWithLogitsLoss(reduction="sum")
    optimizer = torch.optim.Adam(model.parameters())
    
    # dataloader で扱える形( = Dataset )にする
    train = torch.utils.data.TensorDataset(x_train_fold, y_train_fold)
    valid = torch.utils.data.TensorDataset(x_val_fold, y_val_fold)
    
    # x_train_fold batch_size個, y_train_fold batch_size個ずつを各ループで返す iterater の定義
    train_loader = torch.utils.data.DataLoader(train, batch_size=batch_size, shuffle=True)
    # x_valid_fold batch_size個, y_valid_fold batch_size個ずつを各ループで返す iterater の定義
    valid_loader = torch.utils.data.DataLoader(valid, batch_size=batch_size, shuffle=False)
    
    print(f'Fold {i + 1}')
    
    # epoch 分のループを回す
    for epoch in range(train_epochs):
        start_time = time.time()
        
        # model を train mode にする
        model.train()
        avg_loss = 0.

        # x_train_fold と y_train_fold を batch_size 個ずつ渡すループ
        for x_batch, y_batch in tqdm(train_loader, disable=True):
            # predict
            y_pred = model(x_batch)
            # loss の計算
            loss = loss_fn(y_pred, y_batch)
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            avg_loss += loss.item() / len(train_loader)
        
        model.eval()
        valid_preds_fold = np.zeros((x_val_fold.size(0)))
        test_preds_fold = np.zeros(len(X_test))
        avg_val_loss = 0.
        for i, (x_batch, y_batch) in enumerate(valid_loader):
            y_pred = model(x_batch).detach()
            avg_val_loss += loss_fn(y_pred, y_batch).item() / len(valid_loader)
            valid_preds_fold[i * batch_size:(i+1) * batch_size] = sigmoid(y_pred.cpu().numpy())[:, 0]
        
        elapsed_time = time.time() - start_time 
        print('Epoch {}/{} \t loss={:.4f} \t val_loss={:.4f} \t time={:.2f}s'.format(
            epoch + 1, train_epochs, avg_loss, avg_val_loss, elapsed_time))

    # X_test_fold を batch_size ずつ渡すループ    
    for i, (x_batch,) in enumerate(test_loader):
        y_pred = model(x_batch).detach()

        # batch_size のリストのリストになっているのを単一階層のリストに変換して、cpuに値を渡し、テンソルから numpy.array()に変換したものを sigmoid 関数に渡す
        test_preds_fold[i * batch_size:(i+1) * batch_size] = sigmoid(y_pred.cpu().numpy())[:, 0]

    train_preds[valid_idx] = valid_preds_fold

    # 予測値の kfold数で割った値を加える
    test_preds += test_preds_fold / len(splits)

[15] 最後に予測値に対して閾値を決めて 予測値 を 0, 1 の形に変換する。

from sklearn.metrics import accuracy_score

def threshold_search(y_true, y_proba):
    best_threshold = 0
    best_score = 0
    for threshold in tqdm([i * 0.01 for i in range(100)]):
        score = accuracy_score(y_true=y_true, y_pred=y_proba > threshold)
        if score > best_score:
            best_threshold = threshold
            best_score = score
    search_result = {'threshold': best_threshold, 'accuracy_score': best_score}
    return search_result

この submission は、LB 0.66985 でした。

遠い昔、簡単に前処理と特徴量エンジニアリングして決定木にかけた時の最高スコアが、0.71770 だったので初めてにしては悪すぎずといったところでしょうか :)


4 : 感想

今回の目標である、テーブルデータに対して PyTorch の NN を適用することはできました。今後はこのコードを使いまわして、過去コンペの NN 縛り LateSubmission チャレンジをやってみようかなと思ってます。


5 : Todo

・出力1, BCELogitsLoss --> 出力2, CrossEntropyLoss でやってみる

・LGBM とアンサンブルしてどれくらいのスコアの上がり幅になるか見る。


以上です。ありがとうございました

日本語版 ELMo のチュートリアル (2019/02/23)

かえるるる(@kaeru_nantoka)です。

今回は、「日本語 ELMo の Tutorial 」と題して、私が来たる 4月より join するストックマーク社が公開した(一応掲載許可はもらいました) pre-trained model ELMo の日本語 ver を Google Colaboratory で扱う手順をご紹介します。

日本語版 ELMo についての詳細は、ストックマーク社のホームページをご覧ください。 ( https://stockmark.ai/2019/01/25/783/

私の GitHubリポジトリ( https://github.com/osuossu8/Utils/blob/master/ELMo_ja_Try.ipynb )

に公開しております、ソースコードのセル番号と Agenda に沿って、ご説明いたしますので、合わせてご覧ください。

それでは、やっていきましょう。


Agenda

  1. MeCab の設定をする
  2. ELMoForManyLangs を clone する
  3. 日本語版 ELMo を wget する
  4. 必要なその他パッケージを入れる
  5. Config file の設定をする
  6. 実験してみる

1 ) MeCab の設定をする。

英文と異なり、日本語文は単語毎の切れ目が(プログラム目線で)わかりにくいため、「分かち書き」といって文章を単語毎に区切る前処理が必要になります。

Google Colab 上でのセットアップは下記記事の通りにやったところ 2019/02/23 現在できました。 ( https://qiita.com/pytry3g/items/897ae738b8fbd3ae7893

セル上の [1]-[3] のコマンドを実行するだけです。


2. ELMoForManyLangs を clone する

3. 日本語版 ELMo を wget する

2, 3 では 元のELMo と日本語版ELMo 本体をDL してます。git clone やら wget やらして最後に unzip で 解凍します。


4. 必要なその他パッケージを入れる

[9] [10] で overrides というパッケージを一旦アンインストールしてインストールし直しています。 overrides はデフォルトで入っているようなのですが、デフォルトのままだと後のセルでエラーを吐いちゃうので入れ直しています。

Google Colab 上だとこれで良いのですが、GCP でやる場合は、テキストで記述している h5py, torch もインストールする必要があります。


5. Config file の設定をする

設定ファイルをいじります。

デフォルトだとディレクトリごと必要な json ファイルがないので、

ja/configs/cnn_0_100_512_4096.json

となるようにディレクトリと json ファイルを用意します。

そして、Colab 上だと vi, vim が使えなかったので(使っている方いらしたら教えてください。。)、マジックコマンドの %%writefile を使いました。

%%writefile ファイル名 とセルの最初にかくと、そのセルの記述内容がファイルに書き込まれるようです。

ここまで準備が完了すると、[15]セルのように日本語を埋め込みベクトルに変換することができます。


6. 実験してみる

ELMo の強みは、文意を汲み取って同じ単語でも意味が異なればきちんと別なベクトルで表現することができる点です。

英語版の実験ですと、以下の記事で言及されているものがわかりやすいです。 ( https://qiita.com/tktktks10/items/a189f88e6e963c441c8f )

ということで、似たような文意だが単語の役割がことなる3つの以下の文章

  1. "かえるるるはその本を読んだ"
  2. "かえるるるは流行りの小説を読んだ"
  3. "かえるるるが買ったマンガ

の文ベクトルの cos類似度を見てみる実験をして見ました。以下その解説です。

ぱっと見、1 と 2 は 「SがOをVした」という内容で 2 と 3 は「SがVしたO」という内容なので文意が若干異なります。違いがきちんと数字に現れるでしょうか。。

(もっと適切な例文があるよという方はアドバイスくださると嬉しいです。マサカリお待ちしてます。)

・[18] 文章を分かち書きします。(文章(str) -> 単語リスト)

・[19] 単語リストを ELMo に投入します。 (単語リスト -> ベクトルのリスト)

[20] にあるように、vector_list[0] に投入した単語の数だけのベクトルができています。 str2_vec の場合は、["かえる", "るる", "が", "買っ", "た", "マンガ"] の6語投入したので、str2_vec[0][0] ~ str2_vec[0][5] までの6つベクトルができています。shape を見てみると全単語 1024次元で表現されています。

・[0(21)] 分かち書きして得られた単語をインプットに単語毎のベクトルを得ることができたのでこれらを足し合わせて文ベクトルとします。

sum をとったり mean をとったりするらしいです。今回は、sum を用いますが、このような単純な線形和でもちゃんと work するらしいです。

[22] 単語ベクトルの線形結合ベクトルをインプットに cos 類似度を求めます。

かえるるるはその本を読んだ かえるるるは流行りの小説を読んだ similarity = 0.9651474 かえるるるはその本を読んだ かえるるるが買ったマンガ similarity = 0.9467888

となりました。

はじめの予測通り「SがOをVした」という内容が同じものは1.0に近く(文意が似ている)、「SがVしたO」というように同じような単語が並んでいながらも文脈が異なるように使用されているものはより1.0から離れた結果になりました。


終わりに

Google Colab という取っ掛かりやすい環境で日本語で学習した ELMo の環境構築及び簡単な実験まで紹介いたしました。

これを機会にNLPに興味を持ってくださる方が増えたら嬉しいです。

ありがとうございました!

営業マンが1年でSEになって機械学習エンジニアに転職する話

こんばんは、かえるるる(@kaeru_nantoka)です。

先日、10ヶ月勤めたSES企業に辞意を伝えました。 そして4月からは、ストックマーク株式会社(https://stockmark.ai/ )にて、NLPを応用した機械学習エンジンを開発する機械学習エンジニアとして参画することになりました。

ちょうどいい人生の節目なので、流行っている転(退)職エントリを描いてみようと思います。


概要

・営業職だけど趣味で始めたプログラミングにハマったよ

・未経験だけど第二新卒的なアレでプログラマーになるぞ

・ひょんなことから kaggle にハマったぞ

・なんか上京することになったよ

・なんで私がエクセル職人に!?

・なんとかソロ銅メダル取れたぞ

・kaggle 強くなりたいからもう一度転職するぞ!


筆者のスペック

・経済学部卒

・プログラミング歴1年ちょい(2017年12月~)

・kaggle(機械学習)歴半年ちょい (2018年8月~)


目次

I ) 証券時代(職種: 営業)

I I ) 転職活動(1)

I I I ) SES企業時代(職種: C#プログラマー)

I V ) 金融系SIer時代(職種: エクセル職人)

V ) 転職活動(2)

V I ) おまけ


I )

新卒では大手証券会社に入社し、営業職をしておりました。

金融商品の営業は難しい仕事であるという認識はあったものの、色々あって(この内容でもう一本書けそう)私の社会人経験はこの仕事でスタートしました。日経新聞を毎日読み、風が吹けば桶屋が儲かると言わんばかりに記事の一つ一つを無理やり取り扱い金融商品の値動きで締めくくるような営業トークを朝練習して、金融商品のパンフレットを持って個人宅や各種法人に突撃する日々を過ごしていました。

金融の勉強は面白かったし、この商品はイイぞと思える商品もあったのですが、突然来た若造が売りに来た値下がりするかもしれない金融商品を買うでしょうか?それ以前に、「金融商品買ってください」で締めくくられる話を聞く気になるでしょうか?

そもそも、金融商品の良さを知ってる人は自分でやってます。

結局、練りに練った営業トークは聞き入れられることもなく数字は上がりませんでした。

中々数字が上がらないので上司に相談したりしました。「運もあるからね。仕方ないよ。これだけ頑張ってたらどこかで報われるよ。」と励ましてくれましたが、「結局最後は運なのか」と思いました。

別部署へ異動したいという考えもありましたが、資格が必要で、その資格の要件に「実務経験3年以上」というものがあり、この環境を出るためにこの環境に3年も居続けなければならず、さらに資格を取っても配属面談やら人事考課やら、大企業ならではのプロセスがあり不確実です。そして、自分の人生なのに自分の職種を会社が決め、さらに数年経ったらローテーションで職種も変わり、転勤で住む場所も変わりうるという所謂一般的な大企業の人事制度が自分に合わないなと思うようになりました。

そこで、「自分の努力がダイレクトに返ってくる。自分の人生を自分である程度はハンドリングできる実力をつけられる。そんな環境で勝負したい。」と思い、当時流行り始めていたプログラミングを始めることにしました。もともと「ブラッディマンデイ」や「王様達のヴァイキング」といった漫画が好きで、プログラミングできたらカッコイイし、やってみたいけどやり方分からんの状態だったので 「PythonGUIRPGゲームを作ろう」みたいな本からはじめました。


I I )

見る見るプログラミングにハマりました。 自分の書いたコードがそれなりの形になって画面上に表示されることの喜び、理解には時間がかかるものの時間をかけたら何とか動かせたことに「自分の努力がダイレクトに返ってくる体験」を覚えました。もともと営業仕事を終え自宅に帰ってからの時間でやっていて楽しく感じていたので、「これだけコードを書くのが楽しければ、仕事でつまづいても業後の時間を全て割いてでもキャッチアップしたい」「社会人1年目で第2新卒枠として年収を下げてでもチャレンジする最後のチャンス」だと思い、マイナビ転職、リクナビネクスト、greenなどに登録し、未経験OKかつコードを書かせてくれそうなところに片っ端からエントリーしては面接しました。営業職からの異業種転職ということで面接では結構苦労しましたが、最終的に地元のSES企業5社から内定をもらい、金融関係にシステムの受託開発をしているという現職の内定をお受けすることにしました。

ここでの決め手は、金銭面と業務面です。金銭面では、プログラミング未経験でありながら金融機関での経験を買ってもらい当時の給与を少し下がるくらいの水準で給与提示してくれました。(他の4社は実務未経験なので額面月17万からスタートみたいなのが多くコレは生活できんぞ…と思ってました。)業務面では金融関係のドメインを使えるということで「1年でやめてしまうはずの金融の知識を引き続き使えるならちょっとお得だな」と思ったのとコードかけるよと言ってもらったからです。他の数社では、コードを書けるのは案件次第だったり、案件のない隙間の期間で別の非コーディングの案件に入ってもらうという話でした。

利用言語はJava ということでしたが、自分のスキルとして残り、それで勝負できるという働き方ができるならそれでもいいかと納得して4月からその会社に勤めることとなりました。


I I I )

入社してから2ヶ月間は、研修と称し、Java + HTML + CSS + 生JS + MySQL で顧客管理システムを作ったり、使わなくなったPCに ubuntu を入れて自作サーバーを作るなどしてLinux の扱いなどを学ぶ傍ら、金融系の受託案件のコーディングを手伝うなどさせてもらってました。それから9月まで、別な金融機関向けのスマホアプリの案件ということで C# を用いた開発のコーディングを手伝わせてもらってました。残業で次の日の朝に帰るようなこともあり受託開発の厳しさを知るもののコーディングしている満足感から辞めたいとまでは感じませんでした。どちらかというとデスマーチさえも楽しめていたと思います。

一方プライベートでは、このころ Twitter アカウント(かえるるる)が誕生しました。 ツイッターの世界では、自作のWEBアプリを公開し、たくさんのいいねやRTをもらっている先人たちがおり、すごいなと思って見てました。

「わいもバズるアプリ作ってアルファアカウントになるんや!!」と意気込み、Django でその場で聴ける簡単な楽曲投稿サービスを半月ほどで作成しリリースしました。6月くらいだったと思います。これがめちゃくちゃバズ...らず、ユーザー志向のサービスを作るのは難しいなあなどと考えていました。

そんな中、Qiita で顔判別AI を作ろうみたいな記事を見つけ、この技術は面白い、これを応用したらすごいアプリが作れそうだと思いました。 使われていたのは keras でした。

画像は自分で用意しましたが、実装の部分は写経して何とか動くものを作りました。これだとオリジナリティがないので、元ネタはWebアプリとしてリリースしていたものを、LINE-BOT API + Heroku + flask に書き換え、顔判別LINE-BOT 「塩顔しょうゆ顔ソース顔判別ちゃん」通称もこみちLINE-BOTを無事リリースしました。これはなかなかバズって友達登録数39人、200いいね近くいきました。

( https://twitter.com/kaeru_nantoka/status/1041705619705610240 )

ちなみに画像は Google Custom Search API なるものを使って全部で 400枚ほど集めましたが、被りが多く素材集めに大変苦労しました。

実は、この「AI使ったアプリ作り楽しいけど画像集め大変やん...」がきっかけで、無料でビッグデータを使用して機械学習に取り組めるサービスを探し、Kaggle にたどり着いたのです。

Kaggle は titanic, homecredit, サンタンデール、Two sigma, 塩コンペなどのカーネルを見てやり方に慣れていきました。

ド初心者の私でも(今でも駆け出しですが)、XGB というものに入れられるように データを云々して、出てきたデータをさらに云々して、 to_csv すればええんやくらいの理解で写経を中心に取り組んでいたのですが、それでもめちゃくちゃ楽しかったです。よって、業務ではC#をかいて、帰ったらkaggle をやるという今のスタイルに近い形が8-9月で出来上がりました。


I V )

そんなある日社長から言い渡されます。「SEとして東京に行くんやで」 正直えっと思いましたが、今度は Java ということではあるが引き続きコードを書けるということだったし、このころには何人かの kaggler の方をフォローしており東京に行くのも悪くないなと快諾しました。

10月に東京に来てからの業務は、9割方 Excel を使用したものでした。 プロジェクトは引き続き、金融機関向けのアプリの作成業務で、オフショアのコーディングをしてくださる方のために、仕様書を起こしたり、画面のイメージやレイアウトをお伝えするために画面キャプチャをエクセル上で切った貼ったするものばかりでした。 正直騙されたわ。。。と思ったもののこの頃はまだ定時で帰ることができていたので、「とにかく定時で帰れるように頑張って仕事を調整して、一秒でも長く kaggle する時間を確保できるようにしよう」の思いで日々過ごしていました。

一方プライベートでは、 Twitterで繋がった kaggler の方とお話しする機会を設けてもらったり、 PLAsTiCCコンペに夜な夜な取り組んだり、kaggle meetup tokyo に一回でも submit したことのある枠で参加させていただいたり東京に住んでいるメリットをフルパワーで享受していました。

仕事の話に戻ると、12月頃から残業が多くなって来ました。 というのもアプリの画面開発に際して、(自称)アジャイルな開発体制をとっていたのですが、作ってはここを変えてくれ、作ってはここを変えてくれの繰り返しで一向に進展しませんでした。そのように手戻りは多いものの、「営業で使うから」や「役員にプレゼンするから」今日中に仕上げてくれみたいな支持を受けることがままあり、「そういったことがあるなら事前に教えておいてほしいな」などと思いながら残業に甘んじていました。

個別の仕事自体はそれほどしんどくないし、何なら kaggle やっている方が頭をめちゃくちゃ使うくらいの認識だったので残業そのものは何ともありませんでしたが、エクセル仕事、特に VBAマクロのようなコードを書く仕事ではなく何も身に付かない仕事で残業を強いられるのが辛くて堪りませんでした。 また、kaggle をする時間が圧迫されるのも我慢なりませんでした。

このプロジェクトにコーディングをする仕事がないかというとそうでもないですが、新卒社員を中心にコーディングの仕事が割り振られていたようでした。SES社員には特別なスキルのいらない仕事を振っておいて、自社の社員にはきちんとコーディングを教えるというのはとても理にかなっていることだと思います。

こういった事情から、この会社にいてもずっとテイよく一労働力として搾取され続けるんだろうなと思うようになり、転職したいなと思うようになりました。 しかし、社会人二年目にして早くも二社も退職してしまうのは少し気が引けたのと、「どうせ転職するなら kaggler のいるような職場がいいなあ。でもこれといった実績もないなあ。どうしようかなあ。」というような気持ちでした。

この辺りから、kaggle でソロメダルを獲得してそれを実績として捉えてくれるところに転職しようという目標ができました。


V )

転職の動機

上記のような不満も多少なりともありましたが、直接的な動機は kaggle で強くなりたい。という気持ちでした。

わたしの知る範囲内の先輩 kaggler は理系で大学院を出た方、或いは大学に在学中で余暇は関連の技術論文を読み漁っては実装しているなどの数理的な背景、技術のために惜しみなく時間を使っている(時間を使える環境にある)といった印象を受けました。

私も「時間がない」という理由は甘えでしかないと考えており、18:30定時に会社を出られた日は20時から、残業で20時くらいに会社を出ることになったらタクシーを利用するなどして時間と体力を温存しつつ、22時くらいから26~27時くらいまで kaggle に取り組んでいました。

上京してそうした暮らしを2ヶ月ほど続けて、やっとの想いで PLAsTiCCコンペにてソロ銅メダルを獲得(ボーダーから数えて3番目くらい)しました。

銀メダル圏も見えていましたが、最後の最後まで Public LB では届きませんでした。その上 Private LB でのシェイクダウンによりこの成績です。

「ソロメダルを獲得する」という当初の目標は達成できましたが、同時に「こんなに時間を投入してもギリギリ銅メダルなのか」と厳しさを実感しました。

経済学部卒で在学中はプログラミングもまともに勉強したことがなく、論文といえば日本語の歴史系のものを卒業のために何本か読んだことのあるくらいでした。

そんな普通の文系卒の私が、「日中エクセルに画面キャプチャを何時間も貼って過ごして平日業後と土日にちょちょいとキャッチアップする生活を続けて kaggle で勝てるのか?」と考え、日中の時間も機械学習モデリングEDAができる環境に飛び込みたいと考えるようになりました。

転職活動

今回は以下のツールを使用しました。

Twitter

Wantedly

企業フォームからの直接の応募

ヒヨコの方のように、Twitter 転職でかっこよく決めたかったのですが、今回は Wantedly で転職予定の会社と出会いました。

有名どころのWeb企業の機械学習エンジニア職には直接企業の応募フォームからエントリーしましたが、こちらで応募した会社はことごとく書類落ちしました。

両ツールを通してたくさんの方と面談させていたくことができたのでとても勉強になりました。

転職活動の軸

kaggler と働きたいな~職場の人とチーム組んでコンペに参加したいな~などと思ってましたが、最終的には

「業務で得た知見を kaggle で活かす、kaggle で得た知見を業務で活かす」

のサイクルを確立した働き方がしたい

という想いに賛同頂ける会社

の一本に落ち着きました。

現場社員の方との面談や技術面談、CTOの方との面談で上記の話は必ずしました。

この話をして「うちならできるよ」や「うちでやってる人いるよ」などの返答が返ってくる場合は、既に kaggler が活躍されているなどで kaggler に対する理解があるかつ kaggler が満足して働ける環境がある会社ということなので、「ぜひ今すぐに私も混ぜてください~」という気持ちになってました。

持論ですが、kaggler が満足して働けている会社はいい会社です。

職務経歴書

職務経歴書を書き始めました。

そこには、

1年で新卒で入った証券営業職を辞め、転職先のSES企業でコーディング半年(しかもC#)、最後の数ヶ月に至ってはエクセル職人(それっぽく書くと、オフショアのプログラマの方々への作業依頼書の作成含むのディレクション業務 になりました。)

の悲惨な業務経験の2年目社会人がいました。

「こんなんで機械学習エンジニアになりたいとかどうかしてるぞ…?てか社会人2年目で2社目を辞めようとしてるのか私は…?これサラリーマン向いてないんじゃ…」 ととても悩みました。

kaggleソロ銅メダル、これを評価してくれる会社があればいいなとダメ元で進むことにしました。

最終的には、独学短期間でkaggleソロ銅メダルを評価していただいたことが内定獲得に大いに貢献してくれました。

内定を受諾した決め手

内定は今回お世話になる1社のみですが、数社選考の途中で辞退させていただきました。いずれの会社も面接で私がやりたいことに対してとても関心を示してくださり、魅力的で自分が三人くらいに分身できたら全部の会社の選考を最後まで受けたかったです。

決め手は、自社サービスを運営している点とNLP技術に特化している点でした。 先にも述べた通り、私はこの世界で生きていくことを考えたときにあまりにも背景が弱いです。よってこれからこの分野は自信を持って得意と言えるぞ、というものが欲しかったのです。NLP は前処理も多岐にわたり学習済みのモデルも日進月歩で進歩しています。また、使用技術がNNなのも魅力的でした。昨今の kaggle ではテーブルデータについてもNNを使用してモデルを作り、アンサンブルすることで上位に食い込んでいるチームが多いです。よって、業務でNNに取り組める環境は私の kaggle で強くなりたいという目標の実現を加速させるものだと思いました。 自社サービスである点については、単純に前職が、受託開発とSESだったので、一回自分の会社のプロダクトを成長させるために注力してみたい気持ちがあったからです。

転職活動を終えて思うこと

今回の転職活動において、kaggle ソロ銅メダルはとても大きな仕事をしてくれました。

コンペ途中に、最終的に銀メダルを獲得するチームとチームマージさせていただけるかもしれない機運があったのですが、「ソロ」の実績が欲しかったこともあり、お断りしてしまいました。kaggle master になるには、金一枚と銀二枚以上のメダルが必要ということもあり、勿体無いことしたなあと思ったこともありますが、今ではあの決断は間違ってなかったなあとも思います。

なにはともあれ、4月から昼も夜も機械学習に塗れた生活を送れるということで楽しみで仕方ありません。

これからげきつよkaggler になれるよう精進していく所存ですので、どうぞよろしくお願いします。


V I )

SIer勤務kaggler として辛かったこと

日中kaggleに全く関係のないことで時間が削れていくつらみもありましたが、最もつらかったのは「話題がない」ことでした。

上京し kaggle meetup や コンペの反省会を通じて知り合った方々との勉強会に参加させていただく機会がチラホラ増えてきたのですが、その勉強会後の飲み会での話題にとても困りました。

勉強会に参加されてる方同士の会話では「(さまざまな業界で分析業務に従事されてる方々が)実務においてどのように技術を活用しているか」や「コンペで使われてたあの手法について」などが主で、私を含めてそう言った会話をしに(聞きに)来ているという認識でした。

したがって私の「エクセルに画面キャプチャを貼っている愚痴」のような話題でみなさんの貴重な時間を奪うのは誰も幸せにならないと思い、聴講マシーンになってました。

実はここで見聞きしたことは、面接や面談で大いに活きました。というのも「実際に分析業務に携わっている方の関心」「kaggle と実務でのデータやモデルの扱いの違い」について業務未経験ながら背景知識やその業務に従事することのイメージを持つことができ、質問したい内容が自然出てきたり、自分がその御社で働くことになったとしたらどういうような働き方をしそうかのようなところのすり合わせがスムーズにできたように実感しています。


以上です、ありがとうございました。

【追記】 一部日本語の使い方に誤りがある点をご指摘いただきましたので訂正致しました。 ご指摘くださった皆様、どうもありがとうございました。

VOC2007 dataset で遊ぶ① (2019/01/26)

かえるるる(@kaeru_nantoka)です。

今回はかねてより segmentation task に取り組みたいなあ と思っていた私が、手頃なデータ落ちてないかな〜とネットサーフィンをしていたところ tarファイルで 480MB という頑張ったらローカルで云々できそうなデータセットを見つけたので、云々した物語の第一弾です。

それでは、VOC2007データセットで遊ぶ① と称して

VOC2007データセットのダウンロード ~ train_dfの作成 ~ リサイズ ~ img画像と mask画像の可視化

までを残していこうと思います。

ソースコードは ↓ です。

https://github.com/osuossu8/Play_With_VOC2007/blob/master/get_idx_name_and_mk_train_df.ipynb

ちなみに環境は、Google Colab です。

(...いいぱそこんほしい。。。)


参考 URLs

data の準備 :

https://qiita.com/tktktks10/items/0f551aea27d2f62ef708

train_dfの作成:

https://www.kaggle.com/shaojiaxin/u-net-with-simple-resnet-blocks-v2-new-loss



まず、データをここ(http://host.robots.ox.ac.uk/pascal/VOC/voc2012/#devkit)から落としてきます。

次に、tar ファイルを解凍します。

!tar -xvf "/content/drive/My Drive/VOCtrainval_06-Nov-2007.tar"

/content/ 配下に解凍されました。

[in]
import os
os.listdir("/content/VOCdevkit/VOC2007/")

[out]
['SegmentationObject',
 'Annotations',
 'JPEGImages',
 'ImageSets',
 'SegmentationClass']

中身は ↑ のようになっています。 今回使うのは、JPEGImages(元画像), SegmentationObject(マスク画像) の二つです。

これらの配下に、 'hogehoge.jpg' や 'piyopiyo.png' といった形で画像が入っています。

最終的には、画像を pixel の numpy.array に変換して保持します。

Kaggle の塩コンペの kernel を参考に実装していきました。

img_path = "/content/VOCdevkit/VOC2007/JPEGImages"
mask_path = "/content/VOCdevkit/VOC2007/SegmentationObject"
import os

def get_idx_name(path):
  idx = os.listdir(path)
  idx_name = []
  for i in range(len(idx)):
    idx_name.append(os.path.splitext(idx[i])[0])
  return idx_name

まず、画像ファイル名の拡張子以外の部分を抜き出します。↑

[in]
img_idx = get_idx_name("/content/VOCdevkit/VOC2007/JPEGImages")
mask_idx = get_idx_name("/content/VOCdevkit/VOC2007/SegmentationObject")
print(len(img_idx), img_idx[:3])
print(len(mask_idx), mask_idx[:3])

[out]
5011 ['006473', '008023', '009659']
422 ['000661', '005859', '009950']

↑ を見ると、segmentation と対になっている元画像が422枚しかないようです。 segmentation 画像のある元画像を抜き出してみます。

[in]
img_has_mask_idx = set(img_idx) & set(mask_idx)
img_has_mask_idx = list(img_has_mask_idx)

print(len(img_has_mask_idx), sorted(img_has_mask_idx)[:3])
print(len(mask_idx), sorted(mask_idx)[:3])

[out]
422 ['000032', '000033', '000039']
422 ['000032', '000033', '000039']

これで使いたい画像のインデックスを集めることができました。 次にこれをもとに画像を取得して pixelの numpy.arrayにしたものを df[img], df[mask] というカラムに格納します。

まず使うライブラリを宣言して

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
plt.style.use('seaborn-white')
import seaborn as sns
sns.set_style("white")

%matplotlib inline

from keras.preprocessing.image import array_to_img, img_to_array, load_img
train_df = pd.DataFrame()
train_df["images"] = [np.array(lod_img(img_path + "/{}.jpg".format(idx))) / 255 for idx in sorted(img_has_mask_idx)[:100]]
train_df["masks"] = [np.array(load_img(mask_path + "/{}.png".format(idx), color_mode="grayscale")) / 255 for idx in sorted(mask_idx)[:100]]

こんな感じになります。

[in]
train_df.images[0].shape, train_df.masks[0].shape

[out]
((281, 500, 3), (281, 500))

このままだと、縦横の長さが異なるので一旦縦に合わせてリサイズします。 コードは、塩コンペのカーネルにあったものを流用しています。

from skimage.transform import resize

img_size_ori = train_df.images[0].shape[0]
img_size_target = 101

def upsample(img):# not used
    if img_size_ori == img_size_target:
        return img
    return resize(img, (img_size_target, img_size_target), mode='constant', preserve_range=True)
    
def downsample(img):# not used
    if img_size_ori == img_size_target:
        return img
    return resize(img, (img_size_ori, img_size_ori), mode='constant', preserve_range=True)
[in]
img_resize = downsample(train_df.images[0])
msk_resize = downsample(train_df.masks[0])

img_resize.shape, msk_resize.shape

[out]
((281, 281, 3), (281, 281))

揃いました。

あとは、plt.imshow()したら終わりです。お疲れ様でした。

plt.subplot(1, 2, 1)
plt.imshow(img_resize)

plt.subplot(1, 2, 2)
plt.imshow(msk_resize)

f:id:kaeru_nantoka_py:20190125032916p:plain


結び

野生のデータを取得して、過去コンペのソースを頼りに欲しい形にデータを整形し、データセットを作ることができました。 train, mask 420枚づつと少々物足りないですが、augmentation などで水増しすることなどを含めて遊んでいこうと思います。

ありがとうございました。

[おまけ] 筆者が、 plt.imshow()と間違えて、plt.plot() してしまった時の写真。

f:id:kaeru_nantoka_py:20190125032815p:plain

おぞましすぎる。。

julia 言語に入門してみた。 (2019/1/11)

かえるるる(@kaeru_nantoka)です。

今回は julia 言語を Jupyter notebook 上で扱えるよう環境構築をしたので備忘として残しておきます。

環境 **

PC : MacBook Air
OS : macOS High Sierra ver 10.13.6

how to **

$ brew cask install julia
$ julia

bash 上で上記コマンドを叩きます。
私の場合は、install した時点で環境変数に登録されていましたので、
すぐに $ Julia でインタラクティブ・セッションに入れました。

julia > ]
(v1.0) pkg > add Conda#master
(v1.0) pkg > add IJulia

julia > のモードに入ったら、] を叩きます。
するとさらにモードが変わるので、残りのコマンドを叩いて完了です。

$ jupyter notebook


f:id:kaeru_nantoka_py:20190111014522p:plain

比べてみる **

julia の特徴はとにかく計算が高速らしいです。

中学校で習うレベルの簡単な計算問題を for 文で回してみました。
まずは、 python

import time
from tqdm import tqdm

start = time.time()

def f(x):
    y = 2 * x + 1
    return 3 * y

for i in tqdm(range(50001)):
    print (f(i))

end = time.time()

print("{} sec".format(end - start))

f:id:kaeru_nantoka_py:20190111023436p:plain

約6.5 sec ... 微妙...100万ループとか気合いの入ったコード書けばよかったですね笑(チキってしまいました。)

それでは、次に julia

@time for i in 1:50000;f(x) = 3(2x+1);println(f(i)); end

f:id:kaeru_nantoka_py:20190111022303p:plain

約1.4 sec 4~5倍も差がついてますね。早いです。

そしてちゃっかり、julia の別な特徴である
「数式をそのままかける(ものもある)」

を盛り込みました。

f:id:kaeru_nantoka_py:20190111024159p:plain

数式チックにかけると言っても、数字がカッコの後ろに来てはいけないなどの簡単なルールがあります。

感想 **

環境構築が簡単 & kaggler だと馴染みの深い人も多かろう Jupyter notebook で実験できるのはとっつきやすいなーという印象でした。

計算の速さを活かして kaggle の前処理なんかに使えていけたらなと思っております。

ちなみに、Jupyter notebook の ju は julia の頭文字らしいです。
https://en.wikipedia.org/wiki/Project_Jupyter

それでは、ありがとうございました。

Confusion Matrix の復習をしてみた。 2018/12/18

かえるるる(@kaeru_nantoka)です。

今回は、私が今朝まで参加していた通称 PLAsTiccコンペ
PLAsTiCC Astronomical Classification | Kaggle)で大変お世話になった、
Confusion Matrix (sklearn.metrics.confusion_matrix — scikit-learn 0.20.1 documentation)

について復習したのでその備忘として書いていこうと思っております。

ソースコードは、私の github に残しているのでよろしければご覧になってください。

https://github.com/osuossu8/myEDAs/blob/master/plasticc/confusion_matrix.ipynb

*****

( i ) Confusion Matrix とはなんぞや

このような図を描画してくれる便利メソッドです。

f:id:kaeru_nantoka_py:20181218225533p:plain
PLAsTiCC コンペでの私の best model の Confusion Matrix

簡単に言うと分類問題で、test label と prediction label とで、何を何に分類しているのかが格子状になっていてひと目でわかる描画メソッドです。

PLAsTiCC コンペは たくさんある星のデータを15種類に分類するのが課題でした。

コンペ中は、
New Confusion matrix | Kaggle

というような discussion スレッドが立ち、上位陣のモデルでは何を正しく分類できており、何を正しく分類するのが難しいのかを知ることができ、大変参考になりました。

( ii ) 動機

今回の執筆の動機は、シンプルに言うとまた使えるように整理しておきたかったからです。
実はこの PLAsTiCC コンペで散々お世話になったこのメソッド、 コンペ期間中はコピペで、中身を理解せずに使用しておりました。
コピペのままじゃあ次につながらぬ。と言うことでおさらいがてらまとめてみようと思った次第です。

( iii ) 本題

はい、今回は定番中の定番、アヤメのデータセットを使って復習してみました。(一度やってみたかった)

アヤメのデータセットは、花弁や茎などの長さといった特徴から 3種類に分類する基本的なデータです。
分類に使用したモデルは XGBoost です。

import numpy as np
import pandas as pd
import warnings
warnings.filterwarnings('ignore')

import matplotlib.pyplot as plt
import seaborn as sns 

import xgboost as xgb

from sklearn import datasets
from sklearn.model_selection import train_test_split

使用するライブラリを import して

# Iris データセットを読み込む
iris = datasets.load_iris()
X, y = iris.data, iris.target

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=1111)

params = {
        # 多値分類問題
        'objective': 'multiclass',
        # クラス数は 3
        'num_class': 3,
}

# 上記のパラメータでモデルを学習する
clf = xgb.XGBClassifier(**params)
    
clf.fit(X_train, y_train)
    
# テストデータを予測する
y_pred = clf.predict(X_test)

# 精度 (Accuracy) を計算する
from sklearn.metrics import accuracy_score
print(accuracy_score(y_pred,y_test))

accuracy は 0.97 とかでした。

アヤメのデータセットは ndarray の形で与えられているので、DataFrame に変換しておきます。

y_test_df = pd.DataFrame(y_test)
print(y_test_df[0].unique())

y_pred_df = pd.DataFrame(y_pred)
print(y_pred_df[0].unique())


で主役の Confusion Matrix です。

def plot_confusion_matrix(cm, classes,
                          normalize=False,
                          title='Confusion matrix',
                          cmap=plt.cm.Blues):
    if normalize:
        cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]
        print("Normalized confusion matrix")
    else:
        print('Confusion matrix, without normalization')

    print(cm)

    plt.imshow(cm, interpolation='nearest', cmap=cmap)
    plt.title(title)
    plt.colorbar()
    tick_marks = np.arange(len(classes))
    plt.xticks(tick_marks, classes, rotation=45)
    plt.yticks(tick_marks, classes)

    fmt = '.2f' if normalize else 'd'
    thresh = cm.max() / 2.
    for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
        plt.text(j, i, format(cm[i, j], fmt),
                 horizontalalignment="center",
                 color="white" if cm[i, j] > thresh else "black")

    plt.ylabel('True label')
    plt.xlabel('Predicted label')
    plt.tight_layout()


# http://scikit-learn.org/stable/modules/generated/sklearn.metrics.confusion_matrix.html

from sklearn.metrics import confusion_matrix

# Compute confusion matrix
cnf_matrix = confusion_matrix(y_test_df, y_pred_df) # 引数に先ほどの df を渡す...(a)
np.set_printoptions(precision=2)

class_names = list(y_pred_df[0].unique()) # 正解ラベルを定義 ...(b)

# Plot non-normalized confusion matrix
plt.figure(figsize=(5,5))
foo = plot_confusion_matrix(cnf_matrix, classes=class_names,normalize=True,
                      title='Confusion matrix') ...(a) (b) を渡す

これで以下のような画像が得られます。

f:id:kaeru_nantoka_py:20181218231632p:plain


1.00 となっている label 0 と label 1 は 100% 正しく分類できており、0.94 となっているところは、label 2
を正しく分類できた確率が 94% , 残りの 0.06 は、正解ラベル 2 のものを間違えて ラベル 1 と予測しちゃったのが 6% あるよ。という見方をします。


( iv ) まとめ

結局ソースコード自体は kernels のものを丸々 パクった 参考にした形になりましたが、
うまく基本データセットで応用できたのできちんと身についたかなと思います。

ありがとうございました。