思考の本棚

機械学習のことや読んだ本の感想を整理するところ

2019年の振り返り

2019年も残りわずかとなったので、今年の学びと2020年の目標を記録として残しておこうと思います。

 

純粋な興味から機械学習を勉強し始めたのが昨年の11月で、そこから非常に多くのことを学んできました。1年の振り返りなので時系列で整理していきたいと思います。

 

1月

Couseraの機械学習コースを受講しました。

 

ja.coursera.org

スタンフォード大学のAndrew Ng氏が講師を務めるオンライン講義で機械学習の入門として評判が高い+無料ということで受講しました。講義は英語でしたが、有志の方のおかげで日本語字幕もあったため、言語による障害はほとんどありませんでした。

Andrew氏の講義は非常に分かりやすく、動画学習の良さというのをここで初めて感じました。ただ使用するプログラミング言語Octaveという言語で進行していたため、実装をそれほど重要視せずあくまで理論の方を理解するというスタンスで受講しました。

今考えると理論だけでなく、別の教材やコンペ等で実装を試しつつ、理解を深めた方が良かったのかなとも思いました。

 

2月

日本ディープラーニング協会が主催するG検定を受験しました。

www.jdla.org

機械学習分野に関して全くの無知であったため、分野に関する概要を浅くはあるものの幅広く知るという意味でいい動機づけになると思い、受験しました。

公式の問題集とStudy-Alというサイトにある模試を基本的には行なっていました。

その他にもQiitaのまとめ記事やAI白書等を読み、検定に臨んだ結果無事合格することができました。資格自体が役に立っているとは言い難いですが、画像認識、自然言語処理強化学習、生成モデル、ベイズ学習など、非常に幅広い機械学習分野にどういう技術があるのか、その大枠を知ることができたのは良かったと思っています。

 

3月

機械学習の世界的コンペKaggleに初めて参加しました。

1-2月でどちらかというと知識のインプットばかりを行なっているように感じていたので思い切って参加してみました。当時開催されていたコンペの中で最も規模の大きかった『Santander Customer Transaction Prediction』というコンペに参加しました。

www.kaggle.com

このコンペは銀行のユーザーが商品あるいはローン等に対して契約をするか否かを、200個もの匿名の特徴量から判定するというものでした。初めてのKaggleであったので公開されているKernel(現Notebook)を辿ることしかできませんでしたが、EDA探索的データ分析)の重要性をこのコンペから学びました。

 

4月

就職活動が終了した解放感から新しいことを学びたいと思い、以下の書籍を購入し勉強しました。

ベイズ推論による機械学習入門

機械学習のエッセンス

 

機械学習のエッセンスは数学的な機械学習の基礎部分を丁寧に解説されていてPython実装もある良書でした。個人的にはCouseraの機械学習コースよりもこちらの本の方が基礎を学ぶ目的であれば取り組みやすいかなと思いました。

ベイズ本は途中まで読んでいたのですが、挫折してしまい半分までしか読めていないので2020年に改めて読み返してみようと思っています。

 

5月

Kaggleのコンペ『Freesound Audio Tagging 2019』に参加しました。

www.kaggle.com

内容は与えられた音声データのラベルを予測するというものでした。このコンペでも正直Kernelを参考に少し変更を加える程度しかできなかったのですが、そのKerneがPyTorchで書かれており、そこで初めてPyTorchに触れました。もともとはKerasしか使ったことがなかったのですが、KaggleではPyTorchを利用する人が多い印象を受けたのでここらへんからPyTorchを使うようになりました。

 

6月

研究で機械学習を使うことになりました。

私の所属する研究室は鉄鋼材料の研究をメインに行なっているところで機械学習とは無縁のところでした。しかし縁あって機械学習を用いた研究に参加できることになり、そこから4ヶ月ほど研究でも機械学習を使いました。当時プログラミングによる機械学習の実装技術はそれほどなかったのですが、研究で用いたのを機にステップアップできたように感じています。やはり勉強のための勉強ではなく、目的がありそれを達成するために学んでいくことが一番学習効率が良いことを肌で感じました。

 

7-8月

Kaggleコンペ 『Generative Dog Images』に参加

www.kaggle.com

今年参加した機械学習コンペの中で個人的に最も楽しかったコンペです。

GAN(敵対的生成ネットワーク)を使用して与えられた犬のデータセットをもとに犬の画像を生成し、その生成画像の精巧さと多様性の観点から競い合うというものです。ただしKaggle内のKernel環境(Google Colabのようなもの)でのみ学習可能という条件付きであったため制限時間のある中でより良い画像を生成するというのがこのコンペの肝でした。このコンペが自分の中で最もコミットしたもので、DiscussionやKernelは毎日追い、GANの論文も1日1本ペースで読んでいました。結果は残念ながら203/927位とメダル獲得はできませんでしたが、KaggleそしてGANという技術の面白さを体感することができました。反省としては、当時比較的新しいGANのモデルであったBigGANやSAGANが上位入賞者に使われていたのですが、私は検討すらしていませんでした。Kaggleではある技術を取り入れるだけでメダル入賞も十分ありうるので、日本語の記事だけでなく新しい文献の調査も今後コンペに参加する際は取り組んでいこうと思いました。

 

9月

ベイズに挫折した私ですが再び挑戦してみようと思い、当時話題となっていた以下の新著を購入し勉強しまたもや挫折しました。

ガウス過程と機械学習

この本も2020年度に持ち越したいと思います。

 

10月

SIGNATEで学生限定の家賃予測コンペに参加

kutohonn.hatenablog.com


学生限定コンペがあるという情報を聞き、初めてSIGNATEに参加しました。内容は東京23区内に存在する家の家賃を予測するというものでした。初めて真剣に取り組んだテーブルデータのコンペで学びが非常に多かったのと同時に、コンペ終了後の上位者の解法を見て、同年代の人と現在の自分との差を痛感したコンペでもありました。

 

12月

初めてAdventCalender投稿しました。

qiita.com

KaggleのGANコンペに参加し興味を持ったのと、発信する機会を強制的に作りたいという理由からDeep learning 論文紹介AdventCalenderにてGANの論文まとめを投稿することに決めました。内容は当時ちょうど開催されていたICCV2019でSinGANというGANが話題となっていたのでその論文についてまとめることにしました。投稿後自分が思っていた以上に反応があって、アウトプットすることのモチベーションになりました。私はもともと情報系の専門ではないので、GitHubやQiita、Arxivなどのように情報を共有する環境・雰囲気が整っており、それに対して反応がくるという文化がすごく羨ましく感じました。

2020年の展望

現在考えている私の来年度の目標はこんな感じです。

[勉強面]

[Kaggle]

  • Kaggle expertになる
  • Kaggleにチームを組んで参加する

[実績作り]

  • 機械学習を使った成果物を作る
  • 勉強会等の外部のイベントに参加してみる
  • ブログ等での発信の頻度を増やす

 

まず勉強面に関してです。強化学習は以前から興味があったのですが、取り組めていなかったので見識を広げるという意味でも勉強していこうと思っています。また統計の知識が圧倒的に不足していると感じているので統計学を基礎から学びつつ、今年挫折してしまったベイズガウス過程への理解を深めれればと思っています。

Kaggleに関しては実績としてまだ何も残せていないので、次の称号であるexpertをまずは目標として取り組みたいと思います。また複数人でのコンペ参加を体験したことがないので来年度はチームを組んでの参加にも挑戦します。ただKaggle等のコンペというのはあくまで与えられた課題に対しての取り組みであることに留意する必要があるとも考えています。

そこでコンペ以外での実績づくりも行なっていきたいと考えています。最近以下のような記事を拝見しました。この取り組みは非常に面白いなと思います。

hampen2929.hatenablog.com

このように自分で一から問題を設定して、〇〇×AlというようにAlを別の分野に応用する形で面白い成果物を作りたいなと考えています。この方のように自分の興味のある分野に応用するのが一番熱中して取り組めると思うので、今思いつくあたりだと、私はもともと野球をやっていたので投手の癖をAlで見抜いたり、投球フォームからボールの軌道を予測するというようなことができたら非常に面白いのかなと思っています。そういう取り組みの過程をブログやイベントで発信することで、実績を積み重ねていきたいと思っています。

 

以上が今年の振り返りと来年の展望です。

1年後の今頃、自分がどのような振り返りをしているのかとても楽しみです。

PyTorchでDCGANを実装する

はじめに

この記事ではPyTorchを使ってDCGANの解説および実装を行います。 今回はMNISTのデータセットを利用して、手書き数字の0~2の画像生成を行います。 DCGANの解説には元論文とDCGANの解説が非常にわかりやすい以下のスライドを使用します。 DCGANの実装には書籍『PyTorchで作る発展ディープラーニング』を使用しています。なお本記事でのコードの紹介はモデルの定義と訓練の課程のみとさせていただきます。詳細はGitHubをご確認ください。

f:id:kutohonn:20191206201536p:plain

         

www.slideshare.net

本記事で紹介するコードの全体 github.com

参考にした本の著者のGitHub github.com

DCGANの概要

f:id:kutohonn:20191211012518p:plain

DCGANは2016年に出たGANの派生系です。オリジナルのGANが多層パーセプトロンでモデルを作成していたのに対してDCGANでは画像認識タスクで有効とされているDeepなCNN構造をGANに適応することでGANの表現力をあげたGANの歴史の中でも非常に重要な存在です。 ただしGANの構造自体はそれほど変わりません。生成モデルであるGeneratorは多次元ノイズを入力とし、画像を生成し出力します。識別モデルであるDiscriminatorは、Generatorによって生成された画像と本物の画像を入力とし、各画像の識別結果を出力します。GeneratorはDiscriminatorを騙すために本物そっくりの画像を生成するように、DiscriminatorはGeneratorが生成した画像と本物画像をしっかり識別するように、2つのモデルが互いに敵対しながら学習するのがGANの基本的な構造です。

話をDCGANに戻します。GANにCNNを適応しようという取り組みはDCGAN以前にも行われていましたが、GANの学習における不安定性がCNNの適応を困難なものにしていたようです。学習の安定化のためにDCGANでは以下の事項に変更が加えられています。

  • GANにPooling層のないCNNを導入
  • Batch Normalizationを導入
  • 隠れ層では全結合層を使用しない
  • Generator/Discriminatorの活性化関数にはReLU/LeakyReLUをそれぞれ使用(ただしGeneratorの出力層はTanh)

以上がDCGANの概要となります。

Generator と Disicriminatorの解説と実装

それではGeneratorとDiscriminatorの解説をしていきます。

Generator(生成モデル)

f:id:kutohonn:20191211020513p:plain こちらの図がGeneratorの構造を示したものです。
Generatorは100次元のノイズzを入力し4層のCNNによって64*64の画像G(z)を生成します。 CNNの1つの層は①転置畳み込み層 + ②Batch Norm + ③ReLUの3つで構成されています。 転置畳み込み層はCNNで一般的に用いられる畳み込み層の逆っぽい操作をします。 こちらのサイトが畳み込み層と転置畳み込み層をアニメーションで示しており非常にわかりやすいです。

f:id:kutohonn:20191211140608g:plain
(上記サイトより引用)
このアニメーションではpadding=1, stride=1, kernel=(4*4)の条件で転置畳み込みを行なっていますが、今回のGeneratorで用いる転置畳み込み層はstride=2です。 以下、Generatorの実装になります。

class Generator(nn.Module):
    # nzは入力ノイズの次元数
    def __init__(self, nz, image_size):
        super(Generator, self).__init__()

        self.layer1 = nn.Sequential(
            nn.ConvTranspose2d(nz, image_size*8, 
                               kernel_size=4, stride=1),
            nn.BatchNorm2d(image_size*8),
            nn.ReLU(inplace=True))
        
        self.layer2 = nn.Sequential(
            nn.ConvTranspose2d(image_size*8, image_size*4, 
                               kernel_size=4, stride=2, padding=1),
            nn.BatchNorm2d(image_size * 4),
            nn.ReLU(inplace=True))

        self.layer3 = nn.Sequential(
            nn.ConvTranspose2d(image_size*4, image_size*2, 
                               kernel_size=4, stride=2, padding=1),
            nn.BatchNorm2d(image_size * 2),
            nn.ReLU(inplace=True))

        self.layer4 = nn.Sequential(
            nn.ConvTranspose2d(image_size*2, image_size, 
                               kernel_size=4, stride=2, padding=1),
            nn.BatchNorm2d(image_size),
            nn.ReLU(inplace=True))

        # MNISTなので出力のチャンネルは1
        self.last = nn.Sequential(
            nn.ConvTranspose2d(image_size, 1, kernel_size=4, 
                               stride=2, padding=1),
            nn.Tanh())

    # zは乱数で生成するノイズ
    def forward(self, z):
        out = self.layer1(z)
        out = self.layer2(out)
        out = self.layer3(out)
        out = self.layer4(out)
        out = self.last(out)

        return out

zを入力としlayer1から出力層まで順に流していく実装になります。 4つのlayerを通ったあと、活性化関数Tanhを使用して-1から1の出力結果になるようにします。

Discriminator(識別モデル)

f:id:kutohonn:20191211020223p:plain こちらの図は元論文には掲載されていなかったのですがこちらのサイトより引用したDiscriminatorの構造を示した図です。 Discriminatorは一般的に画像分類タスクに使用されるCNNと同様のものと考えてもらって構いません。 異なる点としては各layerの活性化関数ReLUの代わりにleakyReLUを使用していることです。
以下、 Discriminatorの実装です。

class Discriminator(nn.Module):
    
    def __init__(self, nz, image_size):
        super(Discriminator, self).__init__()
        # MNISTなので入力チャンネルは1
        self.layer1 = nn.Sequential(
            nn.Conv2d(1, image_size, kernel_size=4, stride=2, padding=1),
            nn.LeakyReLU(0.1, inplace=True))

        self.layer2 = nn.Sequential(
            nn.Conv2d(image_size, image_size*2, 
                      kernel_size=4, stride=2, padding=1),
            nn.LeakyReLU(0.1, inplace=True))

        self.layer3 = nn.Sequential(
            nn.Conv2d(image_size*2, image_size*4, 
                      kernel_size=4, stride=2, padding=1),
            nn.LeakyReLU(0.1, inplace=True))

        self.layer4 = nn.Sequential(
            nn.Conv2d(image_size*4, image_size*8, 
                      kernel_size=4, stride=2, padding=1),
            nn.LeakyReLU(0.1, inplace=True))

        self.last = nn.Conv2d(image_size*8, 1, kernel_size=4, stride=1)
    
    # 入力xは本物画像or生成画像
    def forward(self, x):
        out = self.layer1(x)
        out = self.layer2(out)
        out = self.layer3(out)
        out = self.layer4(out)
        out = self.last(out)

        return out

損失関数の解説と実装

ここではDCGANの損失関数について解説していきます。なお実装では訓練の過程やデータの受け渡しも行っておりますが、解説は損失関数のみに絞らさせていただきます。 はじめにDiscriminatorの損失関数について考えていきます。 画像xを入力としたときy=D(x)で表せます。また正しいラベルlはGが生成した画像をl=0、訓練データセット画像をl=1と定義します。 そうした場合Discriminatorの出力は

y^ l(1-y)^ {1-l}
で表せます。実際のDiscriminatorの出力はバッチサイズM個分の同時確率となるので、

\displaystyle{\prod_{i=1}^M y_i^ {l_i}(1-y_i)^ {1-l_i}}

これの対数をとって

\displaystyle{\sum_{i=1}^M l_i\log y_i + (1-l_i)\log(1-y_i)}

この式を最大化するように最適化することがDiscriminatorの学習です。 ちなみにこの式を具体的に考えたとき、Discriminatorの学習とは l=1のラベルを持つ本物画像x_iを入力したとき、\log(D(x_i))を最大化することであり、 l=0のラベルを持つ偽物画像G(z_i)を入力したとき、\log(1-D(G(z_i)))を最大化することです。 これは本物画像を与えたときは1(本物ラベルと同じ)を出力し、偽物画像を入力したときは0(偽物ラベルと同じ)を出力するように学習することを示しており、直感的にもわかりやすくDiscriminatorの学習を示しています。

次にGeneratorの損失関数について考えていきます。 GeneratorはDiscriminatorをだましたいのでDiscriminatorが最大化しようとしている式を最小化すればよいです。 つまりDiscriminatorが最大化しようとしていた以下の式、

\displaystyle{\sum_{i=1}^M l_i\log y_i + (1-l_i)\log(1-y_i)}

この式を最小化することがGeneratorの学習となります。

Generatorの損失関数を考える際は入力はGeneratorが生成した画像のみで良いのでl=0y=D(G(z_i))の時を考えます。 このとき

\displaystyle{\sum_{i=1}^M \log(1-D(G(z_i))}

となります。ただしこの式はGeneratorの学習が進みずらいことが分かっています。そこで要はD(G(z_i))が1と判定してくれればよいだろうと考えてDCGANではGの損失関数を

\displaystyle{-\sum_{i=1}^M \log(D(G(z_i))}

としています。

両者の損失関数は2値の交差エントロピーを示しているため、PyTorchの実装ではnn.BCEWithLoss()を使用することで簡単に記述できます。 以下損失関数を含めた訓練課程の実装です。 なおここではDatasetやDataloaderの定義の実装コードは掲載しておらず、あくまで訓練の流れを理解するために訓練課程のコードを載せています。

def train_model(G, D, dataloader, num_epochs, nz, mini_batch_size, device):
    # 最適化手法の設定
    G_lr, D_lr = 0.0001, 0.0004
    beta1, beta2 = 0.0, 0.9
    optimizerG = torch.optim.Adam(G.parameters(), G_lr, [beta1, beta2]) 
    optimizerD = torch.optim.Adam(D.parameters(), D_lr, [beta1, beta2])

    # 誤差関数の定義
    criterion = nn.BCEWithLogitsLoss(reduction="mean")

    # ネットワークをGPUへ
    G.to(device)
    D.to(device)

    # 訓練モードへ切り替え
    G.train()
    D.train()

    num_train_imgs = len(dataloader.dataset)
    batch_size = dataloader.batch_size

    iteration = 1

    # 各epochでの損失を記録
    G_losses = []
    D_losses = []

    print("Start training!")
    for epoch in tqdm(range(num_epochs)):

        for data in dataloader:

            # --------------------
            # 1. Update D network
            # --------------------
            # ミニバッチが1だとBatchNormでエラーが出るので回避
            if data.size()[0] == 1:
                continue

            # GPU使えるならGPUにデータを送る
            data = data.to(device)
            
            # ラベルの作成
            mini_batch_size = data.size()[0]
            # smoothing label を使って学習の安定化を図る
            real_label = torch.full((mini_batch_size,), 0.8).to(device)  
            fake_label = torch.full((mini_batch_size,), 0).to(device)

            # 真の画像を判定
            D_real_output = D(data)
            
            # 偽画像を生成して判定
            z = torch.randn(mini_batch_size, nz).to(device)
            z = z.view(z.size(0), z.size(1), 1, 1)
            fake_imgs = G(z)
            D_fake_output = D(fake_imgs)

            # 誤差を計算
            lossD_real = criterion(D_real_output.view(-1), real_label)
            lossD_fake = criterion(D_fake_output.view(-1), fake_label)
            lossD = lossD_real + lossD_fake

            # 誤差逆伝播
            optimizerG.zero_grad()
            optimizerD.zero_grad()
            lossD.backward()
            optimizerD.step()

            # --------------------
            # 2. Update G network
            # --------------------
            # 偽画像を生成して判定
            z = torch.randn(mini_batch_size, nz).to(device)
            z = z.view(z.size(0), z.size(1), 1, 1)
            fake_imgs = G(z)
            D_fake_output = D(fake_imgs)

            # 誤差を計算
            lossG = criterion(D_fake_output.view(-1), real_label)

            # 誤差逆伝播
            optimizerG.zero_grad()
            optimizerD.zero_grad()
            lossG.backward()
            optimizerG.step()

            # -----------
            # 3. 記録
            # -----------
            D_losses.append(lossD.item())
            G_losses.append(lossG.item())
            iteration += 1
        
        # 画像生成
        if epoch % 20 == 0: 
            # 画像表示用の自作関数
            generate_img(G, dataloader, epoch, batch_size=16, nz=nz, device=device)
        
    return G, D, G_losses, D_losses

流れとしてはDataloaderからデータをバッチサイズごとに読み込みDiscriminatorの学習、Generatorの学習を行い、これを200epoch繰り返します。 画像は64px四方の0~2のMNISTの画像を各クラス200枚ずつ用意しています。最適化手法はAdamです。
訓練過程の実装で分かりにくいのはGeneratorの学習部分の損失関数の定義のところです。 Generatorの損失関数を先程のように少し変形しているためコードに起こす際はこのように記述します。
lossG = criterion(D_fake_output.view(-1), real_label)

画像生成結果

それでは学習をしてみた結果を示します。 f:id:kutohonn:20191211215307g:plain 20epochごとに画像生成したものがこちらです。 画像は学習が進むにつれて綺麗に数字を表現しています。しかし数字の2を訓練データに含んでいたにも関わらず0と1しか生成されていません。 これがモード崩壊と呼ばれる現象で、簡単に生成できる0と1のみを学習してしまった過学習のような状態です。

こちらは学習の損失関数の様子をプロットしたものです。 f:id:kutohonn:20191211215401j:plain 学習が上手くいっていると2つの損失関数が競合しながら減少していきます。このバランスが崩れるとモード崩壊となって全く同じ画像しか生成しなくなったり、綺麗な画像が生成されなかったりします。今回の結果を見ると途中からGeneratorの損失が増加しているのがみて取れます。このような場合は、パラメータを調整したりGANの構造に変更を加えることでDiscriminatorを弱くする必要があります。少し昔ではありますがDCGANなどの基本的な構造のGANで学習を安定化させる方法はこちらの論文が参考になります。 arxiv.org

おわりに

本記事ではDCGANの構造の解説とPyTorchを用いて簡単に実装の説明を行いました。 実際に自分で画像生成をしてみるとGANの不安定性が明らかになったように感じます。オリジナルのGANよりも安定性が向上したDCGANにおいても, 学習の安定性への工夫は重要そうです。

【SIGNATE】 (学生限定)マイナビコンペの振り返り

今回学生限定のコンペが開催されるということで、初めてSIGNATEに参加しました。

結果から言うと300人中90位と思うような結果は得られませんでしたが、初めてテーブルコンペにまともに取り組んでみて、学びが非常に多かったため今回ここにまとめておこうと思います。

コンペの概要

f:id:kutohonn:20191108003745p:plain

今回のコンペの目的は東京23区内の家賃予測でした。約3万件の訓練データとテストデータが与えられており、説明変数には、住所、築年数、土地面積などを含む15個の特徴量が与えられていました。

KaggleのHouce pricesやSIGNATEの土地の販売価格の予測など比較的似た内容のコンペに関する情報が公開されていたので、これらを参考に進めていきました。

www.kaggle.com

signate.jp

序盤で行ったこと

データの前処理

序盤は主にデータの前処理ばかりを行っていたような気がします。与えられているデータはほとんどが日本語で書かれた未処理のデータであったため、正規表現とpandasのモジュールを使用してデータ整形を行いました。ここでの作業でpandasの処理にだいぶ慣れることができました。個人的にはデータを可視化する作業がとても好きなので、序盤にEDAをしている時はワクワクしていたように思います。

学習モデルはとりあえずGBDT*1を使っとこうという理由でlightGBMを使用しました。

この段階ではベンチマークを僅かに超えたくらいでした。ただ既存のデータを前処理してパラメータを適当に選んだ状態で学習させた程度であるにも関わらず、それなりの精度がでるGBDTには正直驚きました。のちに知ることとなるのですがGBDTモデルでは欠損値を補完する必要がないことを知らなかったため、当初は全ての欠損値を律儀に補完していました。またデータの特徴量化に関しては当時はone-hot encodingしか知らなかったため、カテゴリ変数は全てone-hot-encodingで処理していました。

中盤で行ったこと

緯度経度のデータ取得

今回のコンペは外部データの利用も可であったので、与えられている住所から緯度と経度をgeocodingというサイトを利用して取得しました。

www.geocoding.jp

しかしこの作業がかなり大変で、サーバーに負担をかけないために10秒間隔でスクレイピングを行う必要がありました。テストデータも含めると約6万件だったので全てのデータを取得するのに1週間ほどかかる計算でした。しかも試してみるとうまくデータを取得できなかったり、予期せぬエラーが発生して計算が途中で止まり取得したデータが消える場面もありました。

そこでデータを1000件ずつに分けて1000件データを取得できたらcsvにデータを保存するを繰り返すという方法を取りました。試行錯誤を繰り返しながらなんとか無事に全データの取得が終わったのがコンペ終了の1週間前でした。

Kaggle本を読んでとりあえず色々試す

2019年10月9日(コンペ開催期間)に『Kaggleで勝つデータ分析の技術』が発売されました。twitter等で話題になっていた本で、u++さんの書評を見てこれは今読むべき本だと思い、購入しました。

upura.hatenablog.com

読んでみると、テーブルデータに対してのありとあらゆる手法が網羅されている印象で非常に買ってよかった1冊でした。またコードも掲載されていたためこの本を見ながらすぐに様々な手法を試すことができました。

今回私がこの本から学んだことをいかに整理しておきます。

  • target encoding, label encoding等のカテゴリ変数をencodingする手法
  • 評価指標RMSEは外れ値に影響を受けやすい
  • 特徴量の作成手順
  • GBDTは欠損値補完や数値の変換をしなくてもいい
  • k分割交差検証モデルの予測値の平均を予測値とする方法
  • スタッキング

特にtarget encodingとスタッキングはリークの可能性があるためout-of-foldで行う点は非常に重要であると感じました。

 

特徴量の作成

データが3万件と非常に多いのに対して特徴量が15個と少ないこと、加えてGBDTはある程度意味のない特徴量の追加に対してもロバストであることから特徴量をとりあえず大量に増やす方針に変えました。具体的に作成した特徴量は

  • 家賃÷面積=地価とみなし、各カテゴリ変数の水準ごとに算出した地価の平均を各物件の地価から引き算、割り算
  • 面積×階数=建物の体積(建設費用と相関がありそうと仮定)
  • 居住階÷最上階=建物内での相対的な位置
  • 面積÷間取り(2LDKなど)から得た部屋数=1部屋あたりの面積

これらを組み合わせながら特徴量を200個ほどに増やしていったのが中盤でのハイライトです。

終盤で行ったこと

クラスタリング

取得した緯度経度のデータを用いてより細かく土地区分を行おうと思いクラスタリングを行いました。

f:id:kutohonn:20191108102827p:plain
f:id:kutohonn:20191108102900p:plain

上記の図はクラスタ数が100のもので、その他に50,  500, 1000も特徴量に加え他の特徴量と組み合わせて特徴量を計290個にまで増加しました。

スタッキング

上記のKaggle本で掲載されていたスタッキングをコンペ終了前日から取り組みました。

lightGBMで予測した値を特徴量として、線形モデル・lightGBMで再度学習を行いました。しかしここではスタッキング後の精度の方が悪くなってしまい、2層目にlightGBMを用いたモデルに関しては精度が倍以上に悪くなったため、おそらくリークしていたのだろうと思われます。

ここをごちゃごちゃ触っているうちに制限時間がきて90位でコンペを終えました。

 

コンペを終えての所感

正直最初は「自分上位食い込めるのでは」という謎の期待を抱いていましたが、あえなく散ってしまいました。ただ学びは非常に多く参加したことには意義があったと思います。あわよくば賞金が欲しかった。笑

またこれが初めてのブログ投稿でしたが、ただ「コンペに参加して90位でした」だけだと周りからも特に評価されないし、自分としてもやっただけで終わってしまう気がしたので、今後はこういった取り組みの過程も逐一残していこうと思いました。

[GitHub]  https://github.com/kuto5046/signate

 

*1:Gradient Boosting Decceision Tree