教師あり・なし・強化学習|機械学習の問題設定と評価分割

このページはプロモーション(広告)を含みます。

機械学習の問題設定と評価分割(初級・読む25分+演習30分)

この授業のゴール

仕事でどう使うか

機械学習のプロジェクトは、モデルを選ぶ前に「これはどの種類の問題か」を決めるところから始まります。種類を取り違えると、集めるデータ・評価のしかた・必要な期間がすべてずれます。たとえば「優良顧客を見つけたい」という依頼は、過去に優良と判定した記録があれば教師あり、なければ教師なし、クーポンを配りながら反応で配り方を変えるなら強化学習寄りの問題です。

路線 この授業の判断が使われる成果物・意思決定
DS(データサイエンティスト) 分析計画書の「問題の種類」「必要なラベル」欄。ラベルがなければ、まず記録の仕組みを作る提案をする
ML(機械学習エンジニア) 学習パイプラインの設計。教師ありなら正解列と評価分割、教師なしなら解釈・安定性の確認、強化学習なら安全な試行の範囲を決める
DX(DX推進・AI企画) ベンダー提案の妥当性確認。「AIで自動分類」がどの種類かを聞き、ラベル作成の工数と費用が見積もりに入っているか確かめる
RS(研究者) 研究課題の定式化。手法の比較は同じ種類の問題どうしでしか公平にならない

この授業では、架空の動画配信サービス(月額制)を例に、3つの種類を同じデータ上で動かして比べます。

前提

次の言葉と操作を使います。不安があれば先に戻ってください。

この授業のPythonは画面内で実行できます。内蔵データ churn.csv(架空の解約データ。1行=顧客1人、3000人)を使います。

身近な状況:同じ会議で出た3つの依頼

動画配信サービスの定例会議で、データチームに3つの依頼が来ました。

依頼 依頼者 手元にある記録
① 来月解約しそうな会員を、毎月1日に一覧にしてほしい 会員サポート 過去3年分の会員ごとの利用状況と「解約したか(0/1)」
② 会員をいくつかのタイプに分けて、タイプ別の企画を考えたい マーケティング 利用時間・問い合わせ回数・在籍月数。タイプの正解はない
③ アプリ起動時に出す3種類のクーポンのうち、どれを出すと一番使われるか、配りながら決めたい 販促 まだ記録はない。出したクーポンが使われたかどうかは翌日わかる

3つとも「AIでやりたい」と言われましたが、必要なデータも、成功の測り方もまったく違います。

この違いが、教師あり学習・教師なし学習・強化学習の違いです。

直感的な説明:答え合わせができるか、行動で結果が変わるか

3つの種類は、学校の勉強にたとえると区別しやすくなります。

判断は、次の2つの質問でほぼ決まります。

  1. 予測したい量の「正解」は、過去のデータに列として記録されているか?(はい → 教師あり候補)
  2. システムの行動が、次に集まるデータを変えるか? 報酬を見ながら行動の選び方を改善したいか?(はい → 強化学習候補)

両方「いいえ」で、構造を見つけたい・要約したいなら教師なしです。

たとえ話の限界:実務では「正解」が完全に記録されていることは少なく、一部の行だけに正解がある(半教師あり)、人がラベルを付ける(アノテーション)などの中間もあります。たとえは入口として使い、次の節の定義で判断してください。

正確な定義:入力・出力・評価の違い

記号を先に決めます。$x$ は1件(ここでは会員1人)の特徴量のまとまり(在籍月数・月額料金などの数値の並び)、$y$ はその会員の目的変数(予測したい量。解約なら1、継続なら0)です。$n$ は件数です。

教師あり学習:$n$ 組の $(x_i, y_i)$($i=1,\dots,n$)から、新しい $x$ に対して $y$ を予測する関数 $\hat{y}=f(x)$ を学ぶ。$\hat{y}$(ワイハット)は予測値を表します。

下位分類 目的変数 $y$ の型 例 主な評価指標
回帰 数値(円、個、時間) 来月の視聴時間、日別の販売数 平均絶対誤差など
分類 カテゴリ(解約/継続、5種類の問い合わせ) 来月解約するか 正解率・適合率・再現率など

教師なし学習:$y$ のない $x_1,\dots,x_n$ だけから構造を見つける。代表は、似た行をまとめるクラスタリング(例:k-means)、列を少数の軸に要約する次元削減(例:主成分分析)、ほかと違う行を見つける異常検知です。正解がないので、評価は「業務の人が解釈できるか」「乱数や期間を変えても似た結果になるか(安定性)」で行います。

強化学習:エージェント(判断するプログラム)が、状態(今の状況)を見て行動を選び、環境から報酬(数値の良し悪し)を受け取る。これを繰り返し、長期的な報酬の合計が大きくなる行動の選び方(方策)を学ぶ。状態がなく行動と報酬だけの単純な形を多腕バンディットと呼び、クーポンや広告の出し分けでよく使われます。

教師あり 教師なし 強化学習
学習に使うもの $x$ と $y$ の組 $x$ だけ 状態・行動・報酬の記録
出力 予測値 $\hat{y}$ クラスタ番号・要約軸・異常度 行動の選び方(方策)
答え合わせ 取っておいた正解と比べられる 唯一の正解はない 試した行動の報酬しか見えない
必要なもの 正解の記録(ラベル) 解釈する人と業務知識 試行してよい環境と安全策

依頼①は分類($y$ が0/1)、②は教師なし(クラスタリング)、③は多腕バンディット(強化学習の一種)です。

段階付き例題:同じデータで3種類を動かす

ステップ1:教師あり(依頼①)。訓練データ(split=='train' の1800人)の特徴量 $x$ と正解 $y$(churned)を fit に渡し、検証データの会員の解約確率を出します。LogisticRegression は分類のモデルで、predict_proba(X) は各行について「0である確率, 1である確率」の2列の配列(numpyの ndarray)を返します。[:, 1] で「解約する確率」の列だけを取り出しています。

import pandas as pd
from sklearn.linear_model import LogisticRegression

df = pd.read_csv('/data/churn.csv')
cols = ['tenure_months', 'monthly_fee', 'support_calls', 'usage_hours']
train = df[df['split'] == 'train']
valid = df[df['split'] == 'valid']

model = LogisticRegression(max_iter=1000)
model.fit(train[cols], train['churned'])          # X と y の両方を渡す=教師あり
prob = model.predict_proba(valid[cols])[:, 1]     # 解約する確率(0〜1)

out = valid[['customer_id', 'tenure_months', 'support_calls', 'churned']].head(4).copy()
out['解約確率'] = prob[:4].round(3)
print(out.to_string(index=False))

出力(Pyodide上での実行結果):

 customer_id  tenure_months  support_calls  churned  解約確率
           2             67              1        0 0.058
           8             28              1        0 0.346
          10              1              0        1 0.246
          21              4              3        1 0.725

意味:churned が実際の結果です。在籍4か月・問い合わせ3回の顧客21のような会員ほど確率が高く出ています。ただし顧客8は0.346でも実際は継続、顧客10は0.246でも解約しました。確率は「この条件の人が100人いたら何人くらい解約するか」の見込みで、1人の運命を言い当てるものではありません。

ステップ2:教師なし(依頼②)。今度は churned を渡さず、在籍月数・問い合わせ回数・利用時間だけで3つのグループに分けます。KMeans(n_clusters=3) は「3つの中心点を置き、各会員を一番近い中心に割り当てる」を繰り返す手法です。単位が違う列(月・回・時間)をそのまま距離に使うと数字の大きい列が支配するので、先に標準化します。

import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans

df = pd.read_csv('/data/churn.csv')
cols = ['tenure_months', 'support_calls', 'usage_hours']
Z = StandardScaler().fit_transform(df[cols])      # 単位をそろえる(月・回・時間)
km = KMeans(n_clusters=3, n_init=10, random_state=0)
df['cluster'] = km.fit_predict(Z)                 # y は渡さない=教師なし

summary = df.groupby('cluster')[cols].mean().round(1)
summary['人数'] = df['cluster'].value_counts().sort_index()
print(summary)
# 学習には使っていない churned を、解釈のためだけに後から見る
print(df.groupby('cluster')['churned'].mean().round(3))

出力:

         tenure_months  support_calls  usage_hours    人数
cluster
0                 53.9            1.1         12.1  1318
1                 17.5            1.5         12.2  1157
2                 35.3            1.1         34.4   525
cluster
0    0.102
1    0.351
2    0.135

意味:クラスタ0は「長く在籍する普通の利用者」、1は「在籍が短い人」、2は「よく見るヘビー利用者」と読めます。学習には使っていない解約率を後から見ると、クラスタ1が35.1%と高めです。ただしこれは「グループの特徴を説明する材料」であって、クラスタリングが解約を予測したわけではありません。

ステップ3:強化学習(依頼③)。3種類のクーポンの本当の利用率(2%、5%、3%)をシステムは知りません。10%の確率でランダムに選び(探索)、残りは今までの成績が一番良いものを選ぶ(活用)方法(ε-greedy)を3000回試します。

import numpy as np

rng = np.random.default_rng(0)
true_rate = np.array([0.02, 0.05, 0.03])   # クーポンA,B,Cの本当の利用率(システムは知らない)
n_rounds, eps = 3000, 0.1

shown = np.zeros(3)      # 各クーポンを出した回数
reward = np.zeros(3)     # 各クーポンで得た報酬(利用された回数)
for t in range(n_rounds):
    if t < 30 or rng.random() < eps:                       # 探索:ランダムに選ぶ
        a = rng.integers(3)
    else:                                                  # 活用:今いちばん良さそうなもの
        a = int(np.argmax(reward / np.maximum(shown, 1)))
    r = rng.random() < true_rate[a]                        # 選んだクーポンの結果だけが分かる
    shown[a] += 1
    reward[a] += r

print('表示回数   :', shown.astype(int))
print('推定利用率 :', (reward / shown).round(3))
print('合計利用数 :', int(reward.sum()), '/ 全部ランダムなら期待値', round(n_rounds * true_rate.mean()))

出力:

表示回数   : [ 125 2697  178]
推定利用率 : [0.016 0.045 0.045]
合計利用数 : 132 / 全部ランダムなら期待値 100

意味:配りながら、利用率が最も高いB(真の値5%)に表示が集中し、ランダムに配るより多く使われました(132回と期待値100回)。一方、Cは178回しか出していないので推定値0.045は本当の0.03からずれています。あまり選ばなかった行動ほど、推定が不確かなまま残るのが強化学習の特徴です。

操作して理解する:値を変えて予想する

上の3つのコードを書き換えて、結果を予想してから実行してください。

  1. ステップ2で n_clusters=3 を 4 に変えると、どうなるでしょうか。→ グループが1つ増え、ヘビー利用者や短期在籍者がさらに分かれます。「正しいクラスタ数」はデータだけでは決まりません。企画担当が4種類の施策を運用できるかどうか、という業務の条件で決めます。
  2. ステップ3で eps = 0.1 を 0.0(探索しない)にすると? → 最初の30回の偶然でたまたま良く見えたクーポンに固定されることがあります。0.5 にすると、悪いクーポンを出し続ける回数が増えて合計利用数が減ります。探索と活用はトレードオフです。
  3. ステップ1の cols から 'support_calls' を外すと、確率はどう変わるでしょうか。→ 問い合わせ回数が多い顧客21の確率が下がるはずです。どの列を入れてよいかは、次の授業 目的変数と利用時点 の「利用時点」で厳密に判断します。

次の表で、身の回りの依頼を自分で分類してみましょう。

依頼 正解は記録されるか 行動がデータを変えるか 種類
問い合わせ文を5カテゴリに自動で振り分ける(過去に担当者が付けたカテゴリあり) はい(カテゴリ) いいえ 教師あり・分類
センサー値からいつもと違う機械を見つける(故障記録はほぼない) いいえ いいえ 教師なし・異常検知
倉庫ロボットの経路を、到着時間を報酬に試行で改善する 報酬のみ はい 強化学習

発展:境界のケースと、種類を決めたあとに必要なこと

実務の問題はきれいに3つに分かれないことがあります。

種類を決めたら、教師ありなら「目的変数と利用時点」(目的変数と利用時点)、「比べる基準」(ベースライン)、「評価データの分け方」(訓練・検証・テスト)が次の仕事です。

よくある失敗

失敗1:クラスタ番号を順位やラベルとして扱う

「クラスタ0が優良顧客、2が要注意」と番号に意味があるように報告するのは誤りです。k-means の番号は、初期値の乱数で入れ替わるただの名札です。

import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
df = pd.read_csv('/data/churn.csv')
Z = StandardScaler().fit_transform(df[['tenure_months', 'support_calls', 'usage_hours']])
for seed in [0, 1, 2]:
    lab = KMeans(n_clusters=3, n_init=10, random_state=seed).fit_predict(Z)
    tmp = df.assign(cluster=lab).groupby('cluster')['usage_hours'].mean().round(1)
    print('random_state =', seed, '→ 各クラスタの平均利用時間', tmp.to_dict())

出力:

random_state = 0 → 各クラスタの平均利用時間 {0: 12.1, 1: 12.2, 2: 34.4}
random_state = 1 → 各クラスタの平均利用時間 {0: 12.2, 1: 12.1, 2: 34.4}
random_state = 2 → 各クラスタの平均利用時間 {0: 12.3, 1: 34.4, 2: 12.0}

ヘビー利用者のグループ(平均34.4時間)は、乱数の種によって番号2にも1にもなります。直し方:番号ではなく、各クラスタの平均値などの中身で名前を付ける(「ヘビー利用」「短期在籍」)。報告には乱数の種と、種を変えても同じ中身のグループが出るかの確認結果を添えます。

失敗2:クラスタを正解にして教師ありで当て、精度を誇る

教師なしで作ったクラスタ番号を目的変数にし、同じ列から決定木で予測すると、高い正解率が出ます。

import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score
df = pd.read_csv('/data/churn.csv')
cols = ['tenure_months', 'support_calls', 'usage_hours']
Z = StandardScaler().fit_transform(df[cols])
cluster = KMeans(n_clusters=3, n_init=10, random_state=0).fit_predict(Z)
# 誤り:クラスタ番号を「正解」にして、同じ列から予測する
acc = cross_val_score(DecisionTreeClassifier(max_depth=4, random_state=0), df[cols], cluster, cv=5).mean()
print('クラスタ番号の予測正解率', round(acc, 3))

出力:クラスタ番号の予測正解率 0.953

95.3%は「クラスタを作った規則を、同じ列から再現できた」というだけで、解約や売上を予測できた証拠ではありません。答えを自分で作って自分で当てる循環です。直し方:教師ありの目的変数は、業務で実際に起きた出来事(解約した、購入した)を後から記録したものにします。クラスタは特徴量の1つとして使うことはできますが、その場合も評価は本物の目的変数で行います。

失敗3:配信ログから、出していないクーポンの効果を推定する

過去の配信ログが次のようだったとします(架空)。

顧客タイプ 出したクーポン 出した回数 使われた回数
新規 A 500 20
新規 B 0 0
既存 B 400 24

「新規にはAの利用率4%(20÷500)、Bは0件なので0%。だから新規にはAが良い」と結論するのは誤りです。新規にBを一度も出していないので、Bの利用率は0%ではなく不明です(0件で割る計算はできません)。0と欠損を混同しています。

直し方:①行動を選ぶ仕組みが偏っていないかをログで確認する。②まったく試していない組み合わせは「不明」と報告する。③少量でもランダムに出し分ける期間(探索)を設け、比較できるデータを作る。これは強化学習・A/Bテストに共通する原則です。

まとめ

用語

教師あり学習(supervised learning)

特徴量と目的変数(正解)の組から、新しいデータの目的変数を予測する関数を学ぶ方法。

目的変数(target variable)

予測したい量。正解ラベルとも呼ぶ。記号 y で表す。

回帰(regression)

目的変数が数値の教師あり学習。

分類(classification)

目的変数がカテゴリの教師あり学習。

教師なし学習(unsupervised learning)

目的変数のないデータから、クラスタ・要約軸・異常などの構造を見つける方法。

クラスタリング(clustering)

似た行どうしをグループにまとめる教師なし学習。k-meansが代表例。

強化学習(reinforcement learning)

エージェントが状態を見て行動を選び、報酬を手がかりに長期の報酬合計が大きくなる方策を学ぶ方法。

多腕バンディット(multi-armed bandit)

状態を持たず、複数の行動から報酬が大きいものを探索と活用のバランスを取りながら選ぶ問題。

よくある質問

なぜ最初に問題の種類を決める必要があるのですか?

種類によって、必要なデータ(正解の記録があるか)、評価のしかた、かかる期間が変わるからです。教師ありだと思って始めたのに正解が記録されていなければ、まずラベルを集める仕組みづくりから始める必要があり、計画が数か月ずれることもあります。

分類と回帰の違いは、数字かどうかですか?

目的変数の意味で決めます。解約を1/0と数字で書いても、意味は「解約/継続」というカテゴリなので分類です。満足度1〜5のように順序のあるカテゴリは、どちらで扱うかを目的と評価方法から選びます(順序付き分類という方法もあります)。

強化学習とA/Bテストはどう違いますか?

A/Bテストは、あらかじめ決めた割合で出し分けて、最後に比較する方法です。多腕バンディットは、途中の結果を見て良さそうな方を多く出すように配分を変えていきます。損失を減らせる一方、比較の統計的な解釈は難しくなります。施策の効果を正確に測りたいならA/Bテスト、配りながら成果を最大化したいならバンディット、と使い分けます。

正解が一部の行にしかない場合はどうすればよいですか?

まず、正解がある行だけで教師ありモデルを作り、単純な基準と比べます。正解がある行が偏っていないか(例:クレームになった案件だけにカテゴリが付いている)も確認します。偏っていると、正解のない行に当てはめたときに性能が落ちます。必要なら人がラベルを付ける作業を計画します。

仕事で「AIで顧客を分類したい」と言われたら、何を聞けばよいですか?

「分類の正解は誰がどこに記録していますか」「分けた結果を何に使いますか(施策・優先順位・自動判定)」「分け方が間違っていたら何が困りますか」の3つです。正解の記録があれば教師あり、なく、タイプを発見したいなら教師なし、と判断でき、評価の方法と必要な工数も見積もれます。

クラスタリングの結果を、新しい顧客にも使えますか?

使えます。k-meansなら predict で新しい顧客を最も近い中心に割り当てられます。ただし、顧客の構成が時間とともに変わると中心も古くなるので、定期的に作り直して、前回のグループと中身が対応するかを確認します。

確認問題

問1 「過去の会員データ(利用状況と、翌月に解約したかの0/1)から、今月の会員が翌月解約するかを当てたい」。最も適切な分類はどれか。

  1. ① 教師なし学習(クラスタリング)
  2. ② 強化学習(多腕バンディット)
  3. ③ 教師あり学習(分類)
  4. ④ 教師あり学習(回帰)
解答と解説

正解:③

正解(解約したか)が過去に記録されており、目的変数が0/1のカテゴリなので教師あり学習の分類です。

問2 k-meansで顧客を3つに分けたところ、「クラスタ2」の平均購入額が最も高かった。この結果の扱いとして正しいものはどれか。

  1. ① 番号2は最上位を意味するので、次回も番号2を優良顧客として扱う
  2. ② 番号は乱数で入れ替わる名札なので、平均値などの中身で「高額購入」と名付け、種を変えても同じ中身が出るか確認する
  3. ③ 番号が大きいほど購入額が高くなるように、k-meansは自動で並べている
  4. ④ クラスタ番号を目的変数にして決定木で当てれば、分け方の正しさを証明できる
解答と解説

正解:②

クラスタ番号には順序も意味もありません。中身で名前を付け、安定性を確認します。

問3 強化学習(多腕バンディット)で「探索」をまったく行わない(eps=0)と、起こりやすい問題はどれか。

  1. ① 最初の偶然でよく見えた行動に固定され、本当に良い行動を見つけられないことがある
  2. ② すべての行動を同じ回数ずつ選ぶので、報酬が最小になる
  3. ③ 報酬が観測できなくなる
  4. ④ 教師なし学習に自動で切り替わる
解答と解説

正解:①

探索しないと、初期の少ない試行で良く見えた行動ばかり選び、他の行動の推定が改善されません。

問4 過去のクーポン配信ログで、新規顧客にはクーポンBを一度も出していなかった。新規顧客へのBの利用率について正しい説明はどれか。

  1. ① 使われた回数が0なので利用率は0%
  2. ② 既存顧客でのBの利用率と同じと考えてよい
  3. ③ データがないので不明。比べるには新規顧客にBを出す試行が必要
  4. ④ Aの利用率から引き算で求められる
解答と解説

正解:③

出していない行動の結果は観測されていません。0回÷0回は0%ではなく計算できない(不明)です。

問5 教師なし学習の結果を評価する方法として、最も適切な組み合わせはどれか。

  1. ① テストデータの正解率だけで評価する
  2. ② 業務担当者が解釈・活用できるか、乱数や期間を変えても似た結果になるかを確認する
  3. ③ クラスタ数が多いほど良いと判断する
  4. ④ 評価はできないので、結果をそのまま採用する
解答と解説

正解:②

唯一の正解がないため、解釈可能性と安定性、業務での使いやすさで評価します。

問6 「来週の日別の来店者数(人)を、過去2年の来店記録から予測する」問題の分類として正しいものはどれか。

  1. ① 教師なし学習。日付の並びに構造があるから
  2. ② 強化学習。時間が進むから
  3. ③ 教師あり学習の分類。曜日がカテゴリだから
  4. ④ 教師あり学習の回帰。過去の実績(人数)という数値の正解があるから
解答と解説

正解:④

目的変数(来店者数)が数値で、過去の実績が正解として記録されているので回帰です。時系列であることは分割の方法に影響します。

次に学ぶこと

次は 目的変数と利用時点 で、教師あり学習の目的変数を「誰の・いつの・何を」まで正確に定義し、予測する時点で使える特徴量だけを選ぶ方法を学びます。

fit / predict の基本に不安があれば 「fit・transform・predict」(有料版) へ戻ってください。この授業の確認問題だけでモジュールの習得とはしません。機械学習の問題設定と評価分割 の総合テストと自力課題で判定します。

キャリア学習:データサイエンティスト・機械学習エンジニア・DX推進・研究者を目指す人の学習アプリ。164モジュール・984授業を、ブラウザで動くSQLとPythonの演習で。基礎と各分野の最初のモジュールは無料。全部は2,980円の買い切り。

アプリを無料で開く有料版と無料版の違い