kakakakakku blog

Weekly Tech Blog: Keep on Learning!

Secret の自動マウントをオプトアウトするかどうか : automountServiceAccountToken フィールド

Kubernetes のセキュリティ対策として,Service AccountPod に設定できる automountServiceAccountToken フィールド(Secret の自動マウントをオプトアウトするかどうか)の動作確認をした.とは言え,Service AccountSecret の関係性なども整理する必要があるため,一歩一歩進めていく.今回は Kubernetes v1.20 を検証環境とした.

kubernetes.io

Pod と Service Account

まず,Service Account を指定せずに Pod を作ると default Service Account と紐付く.確認をするために nginx:1.21 イメージを指定した以下のマニフェストを検証用に適用する.

apiVersion: v1
kind: Pod
metadata:
  name: my-nginx
spec:
  containers:
    - name: my-nginx
      image: nginx:1.21

以下に kubectl get pods my-nginx -o yaml コマンドの結果を抜粋して載せる.serviceAccountName: default になっている通り,デフォルトでは Poddefault Service Account が紐付いている.

apiVersion: v1
kind: Pod
metadata:
  name: my-nginx
  namespace: default
spec:
  containers:
  - image: nginx:1.21
    name: my-nginx
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: default-token-5qnst
      readOnly: true
  serviceAccountName: default
  volumes:
  - name: default-token-5qnst
    secret:
      defaultMode: 420
      secretName: default-token-5qnst

Service Account と Secret

もう1点重要なのは,上記 YAML の volumesvolumeMounts で,default-token-5qnst SecretPod のボリュームとしてファイルシステムにマウントしている.この default-token-5qnst Secretdefault Service Account に紐付いている.以下に kubectl get serviceaccounts default -o yaml コマンドの結果を抜粋して載せる.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: default
  namespace: default
secrets:
- name: default-token-5qnst

そして kubectl get secrets default-token-5qnst -o yaml コマンドで default-token-5qnst Secret の値を確認すると,Base64 でエンコードされた token を確認できる(以下は xxxxxxxxxx としてマスキングをした).

apiVersion: v1
data:
  token: xxxxxxxxxx
kind: Secret
metadata:
  name: default-token-5qnst
  namespace: default
type: kubernetes.io/service-account-token

Kubernetes API にリクエストを送る

実際に kubectl exec コマンドを使って Pod に接続をすると,ファイルシステムにマウントされた /var/run/secrets/kubernetes.io/serviceaccount/tokenファイルを確認できる.token ファイルは Base64 でデコードされた値となる(以下は yyyyyyyyyy としてマスキングをした).この token を HTTP Header に乗せると Kubernetes API にリクエストを送れるようになる.今回 default Service Account には権限を付与していないため Forbidden になっているのは期待通りと言える.

$ kubectl exec -it my-nginx -- /bin/sh

# cat /var/run/secrets/kubernetes.io/serviceaccount/token
yyyyyyyyyy

# TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)

# curl -s -k https://kubernetes/api/v1/namespaces/default/ --header "Authorization: Bearer ${TOKEN}"
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {
  },
  "status": "Failure",
  "message": "namespaces \"default\" is forbidden: User \"system:serviceaccount:default:default\" cannot get resource \"namespaces\" in API group \"\" in the namespace \"default\"",
  "reason": "Forbidden",
  "details": {
    "name": "default",
    "kind": "namespaces"
  },
  "code": 403
}

Kubernetes API にリクエストを送る手順は以下のドキュメントを参考にした.

kubernetes.io

Pod : automountServiceAccountToken フィールド

一般的には Pod から Kubernetes API にリクエストを送る場面は少なく,不要なら automountServiceAccountToken フィールドを false にして Secret の自動マウントをオプトアウトする.例えば EKS Best Practices Guides にもベストプラクティスとして載っている.

aws.github.io

まず,PodautomountServiceAccountToken: false を指定すると,Pod レベルで Secret の自動マウントをオプトアウトできる.以下のようにマニフェストに1行追加して,Pod を作り直す.

apiVersion: v1
kind: Pod
metadata:
  name: my-nginx
spec:
  containers:
    - name: my-nginx
      image: nginx:1.21
  automountServiceAccountToken: false

すると volumesvolumeMounts の設定はなくなり,Secret の自動マウントはされなくなった.

$ kubectl get pod my-nginx -o yaml
apiVersion: v1
kind: Pod
metadata:
  name: my-nginx
  namespace: default
spec:
  automountServiceAccountToken: false
  containers:
  - image: nginx:1.21
    name: my-nginx
  serviceAccountName: default

念のため /var/run/secrets/kubernetes.io/serviceaccount/token ファイルを確認したところ,存在しなかった.

$ kubectl exec -it my-nginx -- /bin/sh

# cat /var/run/secrets/kubernetes.io/serviceaccount/token
cat: /var/run/secrets/kubernetes.io/serviceaccount/token: No such file or directory

Service Account : automountServiceAccountToken フィールド

Pod レベルではなく,Service Account レベルで automountServiceAccountToken フィールドを false にすることもできる.以下のマニフェストで新しく auto-mount-off Service Account を作る.

$ cat serviceaccount.yaml 
apiVersion: v1
kind: ServiceAccount
metadata:
  name: auto-mount-off
automountServiceAccountToken: false

次に serviceAccountName: auto-mount-off を指定した Pod を作る.今回は Pod には automountServiceAccountToken: false を指定していない.

$ cat pod.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: my-nginx
spec:
  serviceAccountName: auto-mount-off
  containers:
    - name: my-nginx
      image: nginx:1.21

以下に kubectl get pods my-nginx -o yaml コマンドの結果を抜粋して載せる.同じく volumesvolumeMounts の設定はなくなり,Secret の自動マウントはされなくなった.

apiVersion: v1
kind: Pod
metadata:
  name: my-nginx
  namespace: default
spec:
  containers:
  - image: nginx:1.21
    name: my-nginx
  serviceAccountName: auto-mount-off

なお,Service AccountSecret は紐付いているため,以下のように確認できる.

$ kubectl get serviceaccounts 
NAME             SECRETS   AGE
auto-mount-off   1         3m10s
default          1         50m

$ kubectl get secrets
NAME                         TYPE                                  DATA   AGE
auto-mount-off-token-7mhnv   kubernetes.io/service-account-token   3      3m10s
default-token-5qnst          kubernetes.io/service-account-token   3      50m

まとめ

Kubernetes のセキュリティ対策として,Service AccountPod に設定できる automountServiceAccountToken フィールド(Secret の自動マウントをオプトアウトするかどうか)の動作確認をした.CKS (Certified Kubernetes Security Specialist) の出題範囲にも関連するトピックが含まれているし,最近読んでいる「Docker/Kubernetes開発・運用のためのセキュリティ実践ガイド」にも automountServiceAccountToken フィールドの解説が載っていたので,気になっていた!引き続き試していくぞー!

Kubernetes と seccomp を組み合わせてシステムコールを制限する

前回の記事では seccomp (Secure computing mode) に入門するために「Docker と seccomp」を組み合わせて試した.docker run コマンドの --security-opt オプションを使って seccomp プロファイルを指定した.

kakakakakku.hatenablog.com

今回は「Kubernetes と seccomp」を組み合わせて試す.設定や手順などは以下のドキュメントに詳しく載っている.

kubernetes.io

検証環境

まず,kind を使って検証環境を構築する.以下の kind-config.yaml のように,ワーカーノードに seccomp プロファイルを置いたホスト側のディレクトリをマウントする(kubelet に読み込ませる).ドキュメントには ./profiles と書いてあるけど,今回は Docker for Mac を使っていて,マウントできるディレクトリに制限(設定次第)があるため,今回は Mac 側の /tmp/seccomp-profiles に置いた.

apiVersion: kind.x-k8s.io/v1alpha4
kind: Cluster
nodes:
- role: control-plane
- role: worker
  extraMounts:
  - hostPath: /tmp/seccomp-profiles
    containerPath: /var/lib/kubelet/seccomp/profiles

さっそく kind で Kubernetes クラスターを構築する.準備 OK 💡

$ kind create cluster --config=kind-config.yaml
Creating cluster "kind" ...
 ✓ Ensuring node image (kindest/node:v1.21.1) 🖼
 ✓ Preparing nodes 📦 📦
 ✓ Writing configuration 📜
 ✓ Starting control-plane 🕹️
 ✓ Installing CNI 🔌
 ✓ Installing StorageClass 💾
 ✓ Joining worker nodes 🚜

$ kubectl get nodes
NAME                 STATUS   ROLES                  AGE     VERSION
kind-control-plane   Ready    control-plane,master   2m20s   v1.21.1
kind-worker          Ready    <none>                 113s    v1.21.1

なお,seccompkubelet に設定する.以下のドキュメントを見ると,--seccomp-profile-root オプションに関する記載がある.Kubernetes 1.23 から /seccomp ディレクトリに変更すると書いてあり,覚えておこう!

kubernetes.io

検証 : mkdir システムコールを制限する

前回の記事と同じく Ubuntu で mkdir システムコールを制限して,mkdir コマンドの挙動を確認する.まず,何も指定せず,デフォルト設定のまま Pod で Ubuntu を起動する.

apiVersion: v1
kind: Pod
metadata:
  name: ubuntu
spec:
  containers:
    - name: ubuntu
      image: ubuntu:21.10
      command:
        - sleep
        - infinity

kubectl exec コマンドで Pod に接続をして,問題なく mkdir コマンドを実行できる.

$ kubectl apply -f ubuntu.yaml
pod/ubuntu created

$ kubectl exec -it ubuntu -- /bin/sh

# mkdir sample-dir

次に seccomp プロファイルを指定した Pod で Ubuntu を起動する.前回の記事と同じく Docker (Moby) のデフォルトプロファイルから mkdir システムコールのエントリーを削除した deny-mkdir.json を作る.

$ diff -u default.json deny-mkdir.json
@@ -200,7 +200,6 @@
                "membarrier",
                "memfd_create",
                "mincore",
-               "mkdir",
                "mkdirat",
                "mknod",
                "mknodat",

そして以下のように spec.securityContext.seccompProfileseccomp プロファイルを指定する.プロファイルはワーカーノードにマウントしているため,参照できるようになっている.なお,ドキュメントにも書いてあるけど,Kubernetes v1.19 より前はアノテーションとして annotations.seccomp.security.alpha.kubernetes.io/podseccomp プロファイルを指定していた.今後は spec.securityContext.seccompProfile を使う.

apiVersion: v1
kind: Pod
metadata:
  name: ubuntu-deny-mkdir
spec:
  securityContext:
    seccompProfile:
      type: Localhost
      localhostProfile: profiles/deny-mkdir.json
  containers:
    - name: ubuntu
      image: ubuntu:21.10
      command:
        - sleep
        - infinity

同じく kubectl exec コマンドで Pod に接続をすると,今度は mkdir コマンドを実行できなくなった.実際に Operation not permitted とエラーになった.

$ kubectl apply -f ubuntu-deny-mkdir.yaml
pod/ubuntu-deny-mkdir created

$ kubectl exec -it ubuntu-deny-mkdir -- /bin/sh

# mkdir sample-dir
mkdir: cannot create directory 'sample-dir': Operation not permitted

RuntimeDefault プロファイル

さっきは spec.securityContext.seccompProfile.typeLocalhost を指定したけど,他にも spec.securityContext.seccompProfile.typeRuntimeDefault を指定することができる.これはコンテナランタイム側のデフォルトプロファイルを設定することになる.Docker for Mac なら containerd あたり?

apiVersion: v1
kind: Pod
metadata:
  name: ubuntu-runtime-default
spec:
  securityContext:
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: ubuntu
      image: ubuntu:21.10
      command:
        - sleep
        - infinity

kubernetes.io

実際に動作確認をすると問題なく mkdir コマンドを実行することができた.最低限 RuntimeDefault を設定しておくと良さそう.

$ kubectl apply -f ubuntu-runtime-default.yaml
pod/ubuntu-runtime-default created

$ kubectl exec -it ubuntu-runtime-default -- /bin/sh

# mkdir sample-dir

seccomp アクション SCMP_ACT_LOG

ドキュメントを読んでいたら,システムコールを洗い出すための seccomp アクションとして SCMP_ACT_LOG の例が紹介されていた.実際にシステムコールを制限することはせず,syslog にログとして書き出せる.調査目的なら使えそう.

{
    "defaultAction": "SCMP_ACT_LOG"
}

お掃除

検証が終わったら Kubernetes クラスターを削除しておく.

$ kind delete cluster

まとめ

コンテナワークロードのセキュリティ対策として,前回の「Docker と seccomp」の組み合わせに続き「Kubernetes と seccomp」の組み合わせを試した.Podspec.securityContext には他にも多くの設定項目があるため,一歩一歩学んでいくぞ!

Docker と seccomp を組み合わせてシステムコールを制限する

seccomp (Secure computing mode) はプロセスに対してシステムコールを制限する Linux kernel の機能で,今回は「Docker と seccomp」を組み合わせて試す.ドキュメントは以下にある.

docs.docker.com

seccomp デフォルトプロファイル

まず,Docker はデフォルトで seccomp をサポートしている.Docker (Moby) の GitHub リポジトリにデフォルトプロファイル default.json がある.プロファイルは長く感じるけど,比較的簡単に読める.基本的に SCMP_ACT_ERRNO(拒否) となり,ホワイトリストとして列挙したシステムコールを SCMP_ACT_ALLOW(許可) としている.

基本的には制限しすぎず,ホスト側に影響を及ぼすシステムコールなどは禁止されている.ドキュメントにも「It is moderately protective while providing wide application compatibility(幅広いアプリケーション互換性を提供しながら適度に保護する)」と書いてある.

--security-opt オプション

docker run コマンドには --security-opt オプションがあり,--security-opt="seccomp=profile.json" のように seccomp プロファイルを指定できる.また unconfined を指定するとデフォルトプロファイルを無効化できる.

--security-opt="seccomp=unconfined" Turn off seccomp confinement for the container
--security-opt="seccomp=profile.json" White-listed syscalls seccomp Json file to be used as a seccomp filter

docs.docker.com

検証 : mkdir システムコールを制限する

単純な例として,Ubuntu で mkdir システムコールを制限して,mkdir コマンドの挙動を確認する.デフォルトプロファイルでは mkdir システムコールを許可しているため,デフォルトでは以下のように問題なく使える.

$ docker run --rm -d -it ubuntu:21.10
ad3a814faf29ce6407c44723f8a3330c46a425259507dcd4e7673cbaf0f9b2dd

$ docker exec -it ad3a814faf29 /bin/sh

# mkdir sample-dir

次に seccomp を試す.まず,GitHub から default.json を取得して,mkdir のエントリーを削除した deny-mkdir.json を作る.そして docker run コマンドに --security-opt seccomp=deny-mkdir.json オプションを指定すると,mkdir コマンドを実行できなくなった.実際に Operation not permitted とエラーになった.

$ wget https://raw.githubusercontent.com/moby/moby/master/profiles/seccomp/default.json

$ diff -u default.json deny-mkdir.json
@@ -200,7 +200,6 @@
                "membarrier",
                "memfd_create",
                "mincore",
-               "mkdir",
                "mkdirat",
                "mknod",
                "mknodat",

$ docker run --rm -d -it --security-opt seccomp=deny-mkdir.json ubuntu:21.10
4e24d4448c43169ef15a928080a6d82fc7732ee3b782234dfb9b00b0564689c8

$ docker exec -it 4e24d4448c43 /bin/sh

# mkdir sample-dir
mkdir: cannot create directory 'sample-dir': Operation not permitted

検証 : strace システムコールを制限する

関連する例として,今度は Ubuntu で ptrace システムコールを制限して,strace コマンドの挙動を確認する.デフォルトプロファイルでは,以下のように Linux Kernel 4.8ptrace を許可している.

{
        "names": [
                "process_vm_readv",
                "process_vm_writev",
                "ptrace"
        ],
        "action": "SCMP_ACT_ALLOW",
        "args": null,
        "comment": "",
        "includes": {
                "minKernel": "4.8"
        },
        "excludes": {}
},

よって,デフォルトでは以下のように strace コマンドを問題なく使える.

$ docker run --rm -d -it ubuntu:21.10
14c033b88ab4fc361561c37d271dc9d9a3cb98833d570c7c5e86e8cd94db2281

$ docker exec -it 14c033b88ab4 /bin/sh

# uname -r
5.10.25-linuxkit

# apt-get -y update
# apt-get -y install strace

# strace ls
execve("/usr/bin/ls", ["ls"], 0x7ffc3ae41460 /* 5 vars */) = 0
brk(NULL)                               = 0x561158e2e000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffd05d87490) = -1 EINVAL (Invalid argument)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=5826, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 5826, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fe3dd13d000
close(3)

(中略)

# strace -cw ls
bin  boot  dev  etc  home  lib  lib32  lib64  libx32  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 17.22    0.002433        2432         1           munmap
 16.71    0.002361          90        26           mmap
 12.49    0.001765         176        10           close
  8.47    0.001197         132         9           newfstatat
  6.58    0.000930         116         8           openat
  6.29    0.000889         127         7           read
  4.33    0.000612         101         6           pread64
  4.19    0.000592          84         7           mprotect
  3.72    0.000526         262         2           getdents64
  2.99    0.000423         423         1           execve
  2.10    0.000297         297         1           set_tid_address
  2.08    0.000294         146         2           ioctl
  1.99    0.000281         280         1           set_robust_list
  1.87    0.000264         132         2           rt_sigaction
  1.78    0.000252         126         2         2 statfs
  1.72    0.000243          81         3           brk
  1.36    0.000193         192         1           write
  1.32    0.000186          92         2         1 arch_prctl
  1.22    0.000172          86         2         2 access
  0.81    0.000114         113         1           prlimit64
  0.78    0.000110         109         1           rt_sigprocmask
------ ----------- ----------- --------- --------- ----------------
100.00    0.014131         148        95         5 total

次に ptrace のエントリーを削除した deny-ptrace.json を作る.そして docker run コマンドに --security-opt seccomp=deny-ptrace.json オプションを指定すると,strace コマンドを実行できなくなった.

$ diff -u default.json deny-ptrace.json
@@ -402,8 +402,7 @@
        {
            "names": [
                "process_vm_readv",
-               "process_vm_writev",
-               "ptrace"
+               "process_vm_writev"
            ],
            "action": "SCMP_ACT_ALLOW",
            "args": null,

$ docker run --rm -d -it --security-opt seccomp=deny-ptrace.json ubuntu:21.10
b78b00e8ce7b02b7635a19478fa089caeff1ebc85446ea4013e07696331ca9c6

$ docker exec -it b78b00e8ce7b /bin/sh

# uname -r
5.10.25-linuxkit

# apt-get -y update
# apt-get -y install strace

# strace ls
strace: test_ptrace_get_syscall_info: PTRACE_TRACEME: Operation not permitted
strace: ptrace(PTRACE_TRACEME, ...): Operation not permitted
strace: PTRACE_SETOPTIONS: Operation not permitted
strace: detach: waitpid(262): No child processes
strace: Process 262 detached

# strace -cw ls
strace: test_ptrace_get_syscall_info: PTRACE_TRACEME: Operation not permitted
strace: ptrace(PTRACE_TRACEME, ...): Operation not permitted
strace: PTRACE_SETOPTIONS: Operation not permitted
strace: detach: waitpid(266): No child processes
strace: Process 266 detached

seccomp と DockerSlim (docker-slim)

実際にプロファイルを作るときに必要最低限なシステムコールを洗い出すのは難しく,strace コマンドを使うこともできるけど,「DockerSlim (docker-slim)」を使うとお手軽にプロファイルを作ることができる.

github.com

「Docker/Kubernetes開発・運用のためのセキュリティ実践ガイド」を読んでいたら載っていた.「DockerSlim」は今度試す予定!

参考資料 : Introduction to Seccomp

seccomp を詳しく解説したスライドがあってとてもわかりやすかった💡後半は僕には難しかったけど...!

まとめ

コンテナワークロードのセキュリティ対策として,今回は「Docker と seccomp」を組み合わせて試した.プロファイルを編集してシステムコールを制限することができた.次は「Kubernetes と seccomp」を組み合わせて試すぞー!

GitHub Actions で Re:VIEW プロジェクトをビルドする

最近 Pandoc 以外の選択肢として Re:VIEW を試している.ビルドを自動化するために GitHub ActionsRe:VIEW プロジェクトをビルドできるように設定してみた.今回は vvakame/review Docker Image を使うことにした.検証日時点で最新となる Re:VIEW v5.1 もサポートされていて良かった💡その前に GitHub MarketplaceRe:VIEW 専用の Action も探してみたけど,あまり良さそうなものはなかった.

github.com

Re:VIEW プロジェクトを作る

サンプルとして review-init コマンドを使って Re:VIEW プロジェクトを作る.今回は sandbox-github-actions-review という名前にした.以下のように rake pdf コマンドで簡単に PDF 化できる.

$ review-init sandbox-github-actions-review --without-doc --without-config-comment
$ cd sandbox-github-actions-review
$ rake pdf

GitHub Actions

さっそく GitHub Actions ワークフロー用に YAML を書く.今回はmain ブランチに対するプルリクエスト」main ブランチに対するプッシュ(マージなど)」をトリガーするように on を設定した.そして jobsvvakame/review Docker Image を使うように設定した.なお actions/upload-artifact を使うとビルド結果などのアーティファクトを簡単にファイルをアップロードできる.便利!

name: Build

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  build:
    name: Re:VIEW Workflow
    runs-on: ubuntu-latest
    container: docker://vvakame/review:5.1
    steps:
      - uses: actions/checkout@v2
      - name: Build PDF
        run: rake pdf
      - uses: actions/upload-artifact@v2
        with:
          name: Artifacts
          path: book.pdf

YAML 構文はドキュメントに載っている.

https://docs.github.com/ja/actions/using-workflows/workflow-syntax-for-github-actionsdocs.github.com

ビルド結果

GitHub Actions でうまくビルドできた👌

関連記事

もし GitHub Actions 入門するなら以下の記事で紹介している「GitHub Learning Lab」を使うと便利!

kakakakakku.hatenablog.com

Re:VIEW「節単位」に分割したファイルをビルドする Tips は以下にまとめてある!

kakakakakku.hatenablog.com

Intro to Machine Learning : Kaggle Courses で「住宅価格予測」に入門した

Kaggle が公開している「Kaggle Courses」で機械学習に入門できる「Intro to Machine Learning」コースを受講した.Intro と書いてある通り,入門レベルではあるけど,scikit-learn を使って「決定木(回帰)」「ランダムフォレスト」「住宅価格予測」を体験できる.紹介も兼ねて,受講したメモを整理してまとめる!

www.kaggle.com

アジェンダ 🌴

「Intro to Machine Learning」コースには「計7種類」のレッスン(ドキュメントと演習)がある.なお,データを取り扱うときに Pandas の知識は最低限必要になるため,もし不安があれば,先に「Pandas」コースを受講しておくと良いと思う.詳細は前にブログで紹介した.

  1. How Models Work
  2. Basic Data Exploration
  3. Your First Machine Learning Model
  4. Model Validation
  5. Underfitting and Overfitting
  6. Random Forests
  7. Machine Learning Competitions

kakakakakku.hatenablog.com

1. How Models Work 🌴

最初は今回テーマとする「住宅価格予測」の概要と「決定木」の紹介が載っている.例えば「寝室を2つ以上持っているかどうか」という基準だと,一般的な傾向としては寝室が多い方が住宅価格は高いと予測できる.しかし,実際にはバスルームやロケーションなど,他にも様々な特徴を考慮する必要があり,それらを「決定木」として,木構造で Yes/No に分岐していく.

f:id:kakku22:20210616113341p:plain
How Models Work | Kaggle より引用

2. Basic Data Exploration 🌴

さっそく Pandas を使って「住宅データ」を確認していく.特に複雑な内容はなく,DataFramedescribe() 関数を使って統計量を確認している.データを観察することは重要!👀

f:id:kakku22:20210616113225p:plain

3. Your First Machine Learning Model 🌴

次に「決定木」を使ってモデルを構築する.まずは「住宅データ」の中から今回使う説明変数(特徴量)を決める.

  • 説明変数
    • LotArea
    • YearBuilt
    • 1stFlrSF
    • 2ndFlrSF
    • FullBath
    • BedroomAbvGr
    • TotRmsAbvGrd
  • 目的変数
    • SalePrice

実装としては scikit-learnDecisionTreeRegressor クラスを使う.今回は結果に統一性を持たせるために random_state パラメータも指定している.以下は重要なコードを抜粋して載せている.

import pandas as pd
from sklearn.tree import DecisionTreeRegressor

iowa_file_path = '../input/home-data-for-ml-course/train.csv'
home_data = pd.read_csv(iowa_file_path)

feature_names = [ 'LotArea', 'YearBuilt', '1stFlrSF', '2ndFlrSF', 'FullBath', 'BedroomAbvGr', 'TotRmsAbvGrd']
X = home_data[feature_names]
y = home_data.SalePrice

iowa_model = DecisionTreeRegressor(random_state=1)
iowa_model.fit(X, y)

predictions = iowa_model.predict(X)

実際に構築したモデルを使って予測をすると,以下のように結果を確認できる.

f:id:kakku22:20210616113237p:plain

4. Model Validation 🌴

予測結果を評価するために,次は「平均絶対誤差 : MAE (Mean Absolute Error)」を使う.MAE は名前の通り「正解と予測の差の平均値」を計算する.まずは scikit-learntrain_test_split() 関数を使って「学習用データと正解ラベル」「テスト用データと正解ラベル」に分割する.そして MAEmean_absolute_error() 関数を使えば良く,簡単に予測結果を評価できるようになった.とは言え 29652.931506849316 という平均絶対誤差は大きく,モデルを改善する必要がある.

from sklearn.metrics import mean_absolute_error
from sklearn.model_selection import train_test_split

train_X, val_X, train_y, val_y = train_test_split(X, y, random_state=1)

val_predictions = iowa_model.predict(val_X)
val_mae = mean_absolute_error(val_predictions, val_y)

print(val_mae)
# 29652.931506849316

5. Underfitting and Overfitting 🌴

そこで「Underfitting(過少適合)」「 Overfitting(過剰適合/過学習)」という概念を考慮しながら,「決定木」の最適な「リーフノード(葉)」を模索していく.以下のように 5 から 500 までの組み合わせを検証して,今回は 100 が最適だと判断できた.以下は重要なコードを抜粋して載せている.

def get_mae(max_leaf_nodes, train_X, val_X, train_y, val_y):
    model = DecisionTreeRegressor(max_leaf_nodes=max_leaf_nodes, random_state=0)
    model.fit(train_X, train_y)
    preds_val = model.predict(val_X)
    mae = mean_absolute_error(val_y, preds_val)
    return(mae)

candidate_max_leaf_nodes = [5, 25, 50, 100, 250, 500]

scores = {leaf_size: get_mae(leaf_size, train_X, val_X, train_y, val_y) for leaf_size in candidate_max_leaf_nodes}
print(scores)
# {5: 35044.51299744237, 25: 29016.41319191076, 50: 27405.930473214907, 100: 27282.50803885739, 250: 27893.822225701646, 500: 29454.18598068598}

best_tree_size = min(scores, key=scores.get)
print(best_tree_size)
# 100

6. Random Forests 🌴

モデルを改善するために「決定木」よりも優れている「ランダムフォレスト」を使ってモデルを作り直す.scikit-learnRandomForestRegressor クラスを使えば,簡単に「ランダムフォレスト」を実装できる.実際に予測結果を評価すると,平均絶対誤差は 21946.238703196348 となった.少し改善することができた.

from sklearn.ensemble import RandomForestRegressor

rf_model = RandomForestRegressor()
rf_model.fit(train_X, train_y)

rf_val_predictions = rf_model.predict(val_X)
rf_val_mae = mean_absolute_error(rf_val_predictions, val_y)

print("Validation MAE for Random Forest Model: {}".format(rf_val_mae))
# Validation MAE for Random Forest Model: 21946.238703196348

なお,コースには「ランダムフォレスト」の詳細解説までは載っていないため,関連資料を組み合わせるとより理解度が高まると思う.今回は「機械学習図鑑」を併読した.

7. Machine Learning Competitions 🌴

よりモデルを改善するために,最後は Kaggle Competitions「Housing Prices Competition for Kaggle Learn Users」に挑戦して!という内容だった.もう少し勉強をしたら Kaggle Competitions にも挑戦してみたい💪

www.kaggle.com

まとめ 🌴

「Kaggle Courses」で機械学習に入門できる「Intro to Machine Learning」コースを受講した.scikit-learn を使って「決定木(回帰)」「ランダムフォレスト」「住宅価格予測」を体験できる.Pandasscikit-learn の実装は「Python 3 エンジニア認定データ分析試験」を受験したときに繰り返し学んだため,特にハマるところはなくスラスラと読めた.

kakakakakku.hatenablog.com

そして今回も受講証明書を取得できた🏆

f:id:kakku22:20210616122153p:plain