各語の説明がある章へのリンク:
🍰 まずはやさしく
データの共通点を見つける技術です。
未知のデータを分けるために使います。
都道府県をグループに分ける例です。
仕組みと評価の方法について読みます。
パターン認識(Pattern Recognition):観測を特徴(数値の並び)で表し、 それがどのクラス(類型)に属するかを決める規則をデータから作る技術
🍰 まずはやさしく
機械学習の基本となる考え方です。
分析のどの段階でも役立ちます。
実際のデータを使って学習します。
全体像から実装までの手順を読みます。
あなたは パターン認識(Pattern Recognition) の用語ページを読んでいる。 文字・顔・音声の認識のような応用の土台にある、 「特徴からクラスを決める」考え方そのものを扱う。 機械学習の基礎では分類・クラスタリングの総称として登場する。
題材は SSDSE-B-2026 の 2023 年度 47 都道府県(112 列)。 気温・降水日数・人口などを特徴にして、 「日本海側か太平洋側か」を当てる識別(教師あり)と、 ラベルを与えずに県の類型を探すクラスタリング(教師なし)の両方を、 手計算と Python で確かめる。
🍰 まずはやさしく
データの地図を描き直すようなものです。
似ているもの同士をまとめるために使います。
都市型か地方型かを分ける例です。
直感的なたとえ話について読みます。
郵便番号の手書き数字を読む機械を想像する。 機械は「7」という字の意味を知らない。 知っているのは、 画像をいくつかの数値(縦線の長さ、 横線の位置、 黒い画素の割合など)に直したときの特徴の並びと、 過去に「これは 7」と教わった例がその特徴空間のどのあたりに集まっていたか、 だけである。 新しい字が来たら、 特徴を測り、 どの数字の集まりに一番近いか(またはどの数字である確率が一番高いか)で答える。 これがパターン認識の骨格で、 「何を特徴として測るか」と「特徴空間のどこに境界を引くか」の 2 つに分かれる。
都道府県でも同じことができる。 「日本海側の県」を 1 年の降水日数という 1 本の軸に並べると、 日本海側 10 府県は平均 149.2 日、 残る 37 都道県は平均 99.8 日と、 軸の上で別々の山を作る。 その 2 つの山のあいだに境界を 1 本引けば、 地図を見なくても降水日数だけで「日本海側らしさ」を判定できる。 ただし山の裾は重なっていて(山形県 129 日・京都府 104 日・島根県 134 日)、 境界の近くの県はどちらとも言い切れない。 どこに線を引けば誤りが一番少なくなるかを決めるのが、 次の 📐 のベイズ決定則である。
🍰 まずはやさしく
データから規則性を見つける技術です。
正しく分類して予測するために使います。
スマホの顔認証などの技術に近いものです。
数学的な定義と使う条件について読みます。
観測 $x$(特徴ベクトル)を、 $K$ 個のクラス $C_1,\dots,C_K$ のどれかに割り当てる規則 $g(x)$ をデータから作ることをパターン認識という(英語名 Pattern Recognition)。 誤って割り当てる確率を最も小さくする規則は、 事後確率が最大のクラスを選ぶベイズ決定則である。
$$\hat{k}(x) = \arg\max_{k} P(C_k \mid x) = \arg\max_{k} \frac{p(x \mid C_k)\,P(C_k)}{p(x)} = \arg\max_{k}\; p(x \mid C_k)\,P(C_k)$$
分母の $p(x)$ はどのクラスでも同じなので、 比べるときは分子(尤度 × 事前確率)だけでよい。 実際の手法は、 この理想の規則のどこを近似するかで分かれる。
| 手法 | 判定のしかた | ベイズ決定則との関係 |
|---|---|---|
| ガウス分布による識別(ナイーブベイズ・判別分析) | クラスごとに $p(x\mid C_k)$ を正規分布で近似し、 事前確率を掛けて比べる | 尤度を分布の形で仮定して直接計算する(生成モデル) |
| ロジスティック回帰 | $P(C_1\mid x) = 1/(1+e^{-(w^\top x + b)})$ を直接当てはめる | 事後確率を関数で直接学ぶ(識別モデル) |
| k 近傍法 | 近い $k$ 個の訓練点の多数決 | 近傍に占める各クラスの割合を $P(C_k\mid x)$ の推定値として使う |
| テンプレートマッチング | 鋳型 $t_k$ との内積 $\langle x, t_k\rangle$ が最大のクラス | 鋳型のまわりに同じ広がりの分布を仮定したときの近似 |
ラベルが無いときは、 クラスそのものをデータから見つけるクラスタリングになる。 k-means は「各点を一番近い中心のクラスに割り当て、 中心を割り当てた点の平均に動かす」を繰り返し、 クラス内の二乗距離の和 $\sum_k \sum_{x_i \in C_k} \|x_i-\mu_k\|^2$ を小さくする。
| 年 | 出来事 | パターン認識との関係 |
|---|---|---|
| 1936 | Fisher: 線形判別分析 | 2 つの群を最もよく分ける直線(射影)を求める。 統計的な識別の出発点 |
| 1951 | Fix & Hodges: 最近傍の規則 | 分布の形を仮定せず「近い例のクラス」で判定する k 近傍法の原型 |
| 1958 | Rosenblatt: パーセプトロン | 重みを誤りから更新して線形の境界を学習する |
| 1967 | Cover & Hart: 最近傍法の誤り率 | 最近傍法の誤り率はベイズ誤り率の 2 倍以下と示した |
| 1967 | MacQueen: k-means | ラベル無しでクラスタの中心を求める教師なしの代表 |
| 1973 | Duda & Hart『Pattern Classification and Scene Analysis』 | ベイズ決定理論を軸に分野を体系化した教科書 |
| 1980 | 福島邦彦: ネオコグニトロン | 位置のずれに強い階層型の特徴抽出。 後の CNN の先駆け |
| 1995 | Cortes & Vapnik: サポートベクターマシン | マージン最大の境界。 2000 年代の標準的な識別器 |
| 1998 | LeCun ら: LeNet-5 | 手書き数字(MNIST)を畳み込みネットで認識。 特徴抽出も学習する |
| 2012 | Krizhevsky ら: AlexNet | 大規模画像認識で深層学習が従来法を大きく上回った |
ベイズ決定則の記号を、 「降水日数で日本海側かどうかを判定する」例に当てはめて読む(値は 🧮 で計算する 2023 年度の実測)。
| 記号 | 読み方・意味 | 日本海側の判定での中身 |
|---|---|---|
| $x$ | 観測した特徴 | その県の年間降水日数(山形県なら 129 日) |
| $C_k$ | クラス | $C_0$ = 太平洋側など 37 都道県、 $C_1$ = 日本海側 10 府県 |
| $P(C_k)$ | 事前確率:特徴を見る前の各クラスの割合 | $P(C_0)=37/47=0.787$、 $P(C_1)=10/47=0.213$ |
| $p(x\mid C_k)$ | 尤度:そのクラスだとしたら $x$ がどれくらい出やすいか | 各クラスの降水日数を正規分布で近似した密度(平均 99.8 日と 149.2 日) |
| $P(C_k\mid x)$ | 事後確率:$x$ を見た後の各クラスの確からしさ | 山形県で日本海側 0.527 |
| $\arg\max_k$ | 値が最大になる $k$ を選ぶ | 事後確率の大きい方のクラスを答える |
式を言葉にすると「特徴を見る前の見込み(事前確率)を、 特徴がそのクラスらしい度合い(尤度)で更新し、 一番確からしいクラスを答える」となる。 大事なのは、 尤度だけでなく事前確率も判定に効くことである。 山形県の 129 日は日本海側の分布のほうが約 4.1 倍出やすいが、 日本海側は 47 県中 10 県しかないので、 事前確率を掛けると差はほとんど縮まり、 事後確率は 0.527 と五分五分に近くなる。 クラスの数が偏っているとき、 少ないクラスは判定されにくくなる。
data/raw/SSDSE-B-2026.csv を 年度 2023 の 47 行に絞り、 関係する指標を取り出して手計算ベースの確認をする。
| 列 | 意味 | min | median | max | std |
|---|---|---|---|---|---|
| A1101 | 総人口 | 537,000 (鳥取) | 1,549,000 | 14,086,000 (東京) | 2.80e6 |
| A4103 | 合計特殊出生率 | 0.99 (東京) | 1.30 | 1.60 (沖縄) | 0.13 |
| A1303 | 65歳以上人口 | 179,000 (鳥取) | 524,000 | 3,205,000 (東京) | 6.94e5 |
| B4101 | 年平均気温 | 11.0 (北海道) | 17.4 | 23.8 (沖縄) | 2.0 |
| A9101 | 婚姻件数 | 1,810 (鳥取) | 5,599 | 71,774 (東京) | 1.31e4 |
| ペア | Pearson r | 解釈 |
|---|---|---|
| A1101 vs A9101 | 0.989 | 総人口と婚姻件数はほぼ完全相関 → 多重共線性に注意 |
| A1101 vs A4103 | -0.564 | 人口と出生率は中程度の負相関(大都市ほど出生率低い) |
| B4101 vs A4103 | +0.502 | 気温と出生率は中等度の正相関(南日本ほど高い) |
| A1303/A1101 vs A4103 | +0.201 | 高齢化率と出生率は弱い正相関(過疎・高齢地域ほど出生率がやや高い傾向) |
合成データで 3 パターンと観測の類似度 (内積) を計算する。
1 2 3 4 5 6 | import numpy as np templates = [np.array([1,0,1,0]), np.array([0,1,0,1]), np.array([1,1,0,0])] x = np.array([1, 0, 1, 1]) scores = [x @ t for t in templates] print(f"スコア: {scores}") print(f"認識: P{np.argmax(scores)+1}") |
💬 手計算 (Step 2) P1 と Python 出力が完全一致。
📐 の $\hat{k}(x)=\arg\max_k p(x\mid C_k)P(C_k)$ を、 2023 年度の降水日数で計算する。 クラスは日本海側 10 府県(青森・秋田・山形・新潟・富山・石川・福井・京都・鳥取・島根)とそれ以外の 37 都道県、 判定する県は山形県(129 日)。 各クラスの降水日数は正規分布 $\mathcal{N}(\mu_k,\sigma_k^2)$ で近似する。
| Step | 計算 | 太平洋側など $C_0$ | 日本海側 $C_1$ |
|---|---|---|---|
| 1. 事前確率 | $P(C_k)$ = クラスの県数 / 47 | 37/47 = 0.7872 | 10/47 = 0.2128 |
| 2. 平均・標準偏差 | $\mu_k$ = 合計 / 県数、 $\sigma_k$ は n で割る | 3,691/37 = 99.76 日、 σ = 13.60 | 1,492/10 = 149.2 日、 σ = 20.03 |
| 3. 標準化 | $z = (129-\mu_k)/\sigma_k$ | (129 − 99.76)/13.60 = 2.151 | (129 − 149.2)/20.03 = −1.008 |
| 4. 尤度 | $p(x\mid C_k)=e^{-z^2/2}/(\sigma_k\sqrt{2\pi})$ | 0.0990 / 34.08 = 0.00290 | 0.6015 / 50.22 = 0.01198 |
| 5. 尤度 × 事前確率 | $p(x\mid C_k)P(C_k)$ | 0.00290 × 0.7872 = 0.00229 | 0.01198 × 0.2128 = 0.00255 |
| 6. 事後確率 | 5 の値 / 5 の合計 | 0.00229 / 0.00484 = 0.473 | 0.00255 / 0.00484 = 0.527 |
事後確率は日本海側 0.527 がわずかに上回るので、 山形県は日本海側と判定される。 尤度だけなら日本海側が 0.01198 / 0.00290 ≈ 4.1 倍出やすいが、 日本海側は 47 県中 10 県しかないため、 事前確率を掛けると差がほぼ消える。 仮に事前確率を 0.5 ずつにすると、 事後確率は 0.01198 / (0.00290 + 0.01198) = 0.805 まで上がる。 少ないクラスは、 同じ特徴でも判定されにくい。
🎯 このコードでやること:降水日数だけを特徴にして、 クラスごとの事前確率・平均・標準偏差を計算し、 山形県(129 日)の事後確率を手計算と同じ手順で求める。 最後に scikit-learn の GaussianNB で同じ値になるかを確かめる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | import pandas as pd import numpy as np from scipy.stats import norm from sklearn.naive_bayes import GaussianNB df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1) d = df[df['年度'] == 2023].reset_index(drop=True) japan_sea = ['青森県','秋田県','山形県','新潟県','富山県','石川県','福井県','京都府','鳥取県','島根県'] d['日本海側'] = d['都道府県'].isin(japan_sea).astype(int) x = d['降水日数(年間)'].values.astype(float) y = d['日本海側'].values prior = np.array([(y == 0).mean(), (y == 1).mean()]) mu = np.array([x[y == 0].mean(), x[y == 1].mean()]) sd = np.array([x[y == 0].std(), x[y == 1].std()]) # 最尤推定(n で割る) print('事前確率 P(太平洋側), P(日本海側) =', prior.round(4)) print('平均 μ =', mu.round(2), ' 標準偏差 σ =', sd.round(2)) x0 = d.loc[d['都道府県'] == '山形県', '降水日数(年間)'].item() lik = norm.pdf(x0, mu, sd) post = lik * prior / (lik * prior).sum() print(f'山形県の降水日数 x = {x0:.0f} 日') print('尤度 p(x|C) =', lik.round(5), ' 事後確率 P(C|x) =', post.round(3)) nb = GaussianNB().fit(x.reshape(-1, 1), y) print('GaussianNB の事後確率 =', nb.predict_proba([[x0]]).round(3)[0]) pred = nb.predict(x.reshape(-1, 1)) print('47 県の訓練正解率 =', round((pred == y).mean(), 3), ' 日本海側と判定された県 =', d.loc[pred == 1, '都道府県'].tolist()) |
💬 事前確率 0.7872/0.2128、 平均 99.76 日/149.2 日、 標準偏差 13.60/20.03、 尤度 0.00290/0.01198、 事後確率 0.473/0.527 と、 Step 1〜5 の手計算がすべて一致し、 GaussianNB も同じ 0.527 を返す。 この規則で 47 県を判定すると 46 県が当たり(訓練正解率 0.979)、 外れるのは降水日数 104 日で太平洋側の山に入る京都府だけである。 ただしこれは学習に使った県をそのまま当てた値なので、 汎化の見積もりには ⚠️ の 1 県ずつ外す評価を使う。
内積の例では鋳型を人が決めたが、 実データでは各クラスの訓練例の平均を鋳型にするのが素朴な方法である(最近傍重心法)。 特徴の単位が違うので、 先に z 得点に直してから距離を測る。
🎯 このコードでやること:上の内積の例を実データに移し、 各クラスの特徴の平均を「鋳型(テンプレート)」にして、 一番近い鋳型のクラスを答える最近傍重心法を動かす。 山形県の 2 つの鋳型までの距離を出したあと、 1 県ずつ外して鋳型を作り直す LOOCV で 47 県を判定する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import pandas as pd import numpy as np from sklearn.neighbors import NearestCentroid from sklearn.model_selection import LeaveOneOut, cross_val_predict df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1) d = df[df['年度'] == 2023].reset_index(drop=True) japan_sea = ['青森県','秋田県','山形県','新潟県','富山県','石川県','福井県','京都府','鳥取県','島根県'] y = d['都道府県'].isin(japan_sea).astype(int).values cols = ['降水日数(年間)', '年平均気温'] Z = (d[cols] - d[cols].mean()) / d[cols].std(ddof=0) # 標準化(z 得点) t0 = Z[y == 0].mean().values; t1 = Z[y == 1].mean().values # 各クラスの鋳型 = 平均 print('鋳型 t0(太平洋側など)=', t0.round(3), ' 鋳型 t1(日本海側)=', t1.round(3)) x = Z[d['都道府県'] == '山形県'].values[0] d0, d1 = np.linalg.norm(x - t0), np.linalg.norm(x - t1) print('山形県 z =', x.round(3), f' 距離 t0 {d0:.3f} / t1 {d1:.3f} →', '日本海側' if d1 < d0 else '太平洋側など') pred = cross_val_predict(NearestCentroid(), Z, y, cv=LeaveOneOut()) # 1 県ずつ外して鋳型を作り直す print(f'LOOCV 正解率 {(pred == y).mean():.3f} 再現率 {((pred == 1) & (y == 1)).sum()}/10' f' 誤検出 {d.loc[(pred == 1) & (y == 0), "都道府県"].tolist()} 見逃し {d.loc[(pred == 0) & (y == 1), "都道府県"].tolist()}') |
💬 鋳型は太平洋側などが (−0.416, 0.176)、 日本海側が (1.538, −0.652) で、 日本海側は「降水日数が多く、 やや寒い」側にある。 山形県 (0.74, −1.531) から鋳型までの距離は √((0.74+0.416)² + (−1.531−0.176)²) = √4.250 = 2.062 と √((0.74−1.538)² + (−1.531+0.652)²) = √1.410 = 1.187 で、 日本海側の鋳型のほうが近い。 LOOCV では正解率 0.915・再現率 9/10 で、 見逃しは京都府だけだが、 寒い北海道・岩手県と降水日数の多い滋賀県を日本海側と誤る。 気温を特徴に加えたことで山形県は拾えるようになった一方、 「寒い県」も日本海側の鋳型に寄ってしまう。 鋳型は特徴の選び方をそのまま映す。 (標準化の平均と標準偏差は 47 県全体で計算しているので、 LOOCV の値はわずかに楽観的になりうる。)
ここでは 2023 年度 47 県を題材に、 教師なし(k-means・Ward 法・DBSCAN)で県の類型を探す方法と、 教師あり(ランダムフォレスト・ロジスティック回帰)でラベルを当てる方法を順に動かす。
🎯 このコードでやること:SSDSE-B-2026 の 47 都道府県(2023 年度)を総人口・年平均気温・降水日数・65 歳以上人口の 4 特徴で標準化し、 KMeans で 4 グループに分けて Silhouette で分かれ具合を評価する。
📥 入力データ(SSDSE-B-2026.csv 抜粋、 47 都道府県 × 113 列):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import pandas as pd import numpy as np from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler from sklearn.metrics import silhouette_score df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df_2023 = df[df['SSDSE-B-2026']==2023].copy() # 4 つの特徴で 47 都道府県のパターンを抽出 feats = ['A1101','B4101','B4106','A1303'] X = df_2023[feats].values Xs = StandardScaler().fit_transform(X) km = KMeans(n_clusters=4, random_state=42, n_init=10).fit(Xs) df_2023['cluster'] = km.labels_ print(f"Silhouette = {silhouette_score(Xs, km.labels_):.3f}") print("\nクラスタごとの代表県:") for k in range(4): members = df_2023[df_2023['cluster']==k]['Prefecture'].tolist()[:4] print(f" cluster {k}: {members}") |
📤 実行結果:
💬 結果の読み方:Silhouette=0.46 は『中程度のまとまり』。 クラスタ 0 = 太平洋側の中規模県、 1 = 首都圏大都市、 2 = 寒冷内陸・北日本、 3 = 日本海側多雪地帯 と、 気候と人口構造が織り成すパターンが自動抽出された。 これがパターン認識の第一歩。 ※ Silhouette 値は random_state=42 で固定しているが、 scikit-learn のバージョン等の実行環境により小数第 2 位以下は変動しうる。
🎯 このコードでやること:実装 1 のクラスタリングとは逆に、 ラベル(合計特殊出生率が 1.30 以上か)を与えて、 同じ 4 特徴から当てる教師ありの識別にする。
📥 入力データ:実装 1 と同じ SSDSE-B-2026(年度 2023)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | # パターン認識を「教師あり分類」に展開 import pandas as pd from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report from sklearn.model_selection import cross_val_score df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df_2023 = df[df['SSDSE-B-2026']==2023].copy() # 出生率 1.30 以上を High、未満を Low とラベル df_2023['label'] = (df_2023['A4103']>=1.30).astype(int) X = df_2023[['A1101','B4101','B4106','A1303']] y = df_2023['label'] clf = RandomForestClassifier(n_estimators=200, random_state=42) scores = cross_val_score(clf, X, y, cv=5, scoring='accuracy') print(f"5-fold CV Accuracy: {scores.mean():.3f} +/- {scores.std():.3f}") print(f"各 fold: {scores.round(3).tolist()}") |
📤 実行結果:
💬 結果の読み方:気候・人口の 4 特徴で出生率 1.30 以上か未満かを 5 分割交差検証の平均 76.4% 当てられた。 1.30 以上は 24 県・未満は 23 県とほぼ半々なので、 当て推量の 51% より 25 ポイントほど上になる。 ただし fold ごとの正解率は 0.667〜0.9 と揺れ、 各 fold のテストは 9〜10 県しかないので、 1 県の当たり外れで 10 ポイント前後動く。 ※ CV Accuracy は random_state=42 固定だが、 fold 分割・ライブラリのバージョン等の実行環境に依存して数値は前後する。
🎯 このコードでやること:実装 1, 2 からさらに踏み込み、 Ward 法の階層クラスタリングで 47 都道府県を 3・5・7 グループに切り分け、 解像度を上げるとどの県がまとまりから外れるかを見る。
📥 入力データ:SSDSE-B-2026(年度 2023、 47 都道府県)。特徴は総人口・合計特殊出生率・年平均気温・65 歳以上人口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | # 階層的クラスタリングでパターンの「木構造」を見る import pandas as pd from scipy.cluster.hierarchy import linkage, fcluster from sklearn.preprocessing import StandardScaler df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df_2023 = df[df['SSDSE-B-2026']==2023].copy().reset_index(drop=True) feats = ['A1101','A4103','B4101','A1303'] X = StandardScaler().fit_transform(df_2023[feats].values) # Ward 法で階層クラスタ Z = linkage(X, method='ward') # 3, 5, 7 クラスタで分割し、 各クラスタの代表県を出す for k in [3, 5, 7]: labels = fcluster(Z, t=k, criterion='maxclust') df_2023[f'c{k}'] = labels print(f"\n--- {k} クラスタ ---") for ci in range(1, k+1): members = df_2023[df_2023[f'c{k}']==ci]['Prefecture'].tolist() print(f" cluster {ci} ({len(members)}件): {members[:3]}{'...' if len(members)>3 else ''}") |
📤 実行結果:
💬 結果の読み方:階層クラスタリングで k=3 から k=7 まで分割するとパターンが解像度上がる。 k=3 で『首都圏・北日本・その他』、 k=5 で『東京』『沖縄』が単独クラスタとして分離、 k=7 で『北海道』『九州西部』も独立。 パターン認識は『何段階の解像度で見るか』が設計判断。
🎯 このコードでやること:パターン認識を一歩進めて、 密度ベースの DBSCAN(eps=0.8, min_samples=3)で 47 都道府県を「まとまりに属する県」と「どこにも属さない孤立した県(ノイズ)」に分ける。
📥 入力データ:SSDSE-B-2026.csv(年度 2023, 47 都道府県)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | # DBSCAN で『コア・境界・ノイズ』のパターン分類 import pandas as pd from sklearn.cluster import DBSCAN from sklearn.preprocessing import StandardScaler df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df_2023 = df[df['SSDSE-B-2026']==2023].copy().reset_index(drop=True) feats = ['A1101','B4101','A1303'] X = StandardScaler().fit_transform(df_2023[feats]) db = DBSCAN(eps=0.8, min_samples=3).fit(X) df_2023['cluster'] = db.labels_ print(f"クラスタ数 (ノイズ除く): {len(set(db.labels_)) - (1 if -1 in db.labels_ else 0)}") print(f"ノイズ点 (-1): {(db.labels_==-1).sum()} 件") print("\nノイズ判定された県 (孤立パターン):") print(df_2023[df_2023['cluster']==-1]['Prefecture'].tolist()) |
📤 実行結果:
💬 結果の読み方:DBSCAN は『密度に基づくクラスタ』なので、 大都市圏や北海道・沖縄のような独自パターンは『ノイズ』として分離される。 これは KMeans と違って、 パターン認識で『どこにも属さない異常県』を自動識別できる利点。 ※ DBSCAN 自体は決定的だが、 ノイズ判定件数は eps・min_samples と標準化・ライブラリのバージョン等の実行環境に依存する。
🎯 このコードでやること:パターン認識を「識別問題」として扱い、 年平均気温・降水日数・降水量・65 歳以上人口の 4 特徴からロジスティック回帰で日本海側 10 府県とそれ以外を見分ける。
📥 入力データ:SSDSE-B-2026.csv(年度 2023)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | # 識別問題: 47 県を Logistic Regression で『太平洋側 vs 日本海側』に分類 import pandas as pd import numpy as np from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report from sklearn.preprocessing import StandardScaler df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df_2023 = df[df['SSDSE-B-2026']==2023].copy().reset_index(drop=True) # 日本海側県のリスト (簡略) japan_sea = ['青森県','秋田県','山形県','新潟県','富山県','石川県','福井県','京都府','鳥取県','島根県'] df_2023['side'] = df_2023['Prefecture'].apply(lambda p: 1 if p in japan_sea else 0) feats = ['B4101','B4106','B4109','A1303'] X = StandardScaler().fit_transform(df_2023[feats].fillna(df_2023[feats].median())) y = df_2023['side'].values clf = LogisticRegression(max_iter=1000).fit(X, y) pred = clf.predict(X) print(f"訓練データ正解率: {(pred==y).mean():.3f}") print(f"\n係数:") for f, c in zip(feats, clf.coef_[0]): print(f" {f}: {c:+.3f}") print(f"\n切片: {clf.intercept_[0]:.3f}") |
📤 実行結果:
💬 結果の読み方:訓練データでの正解率は 0.957 で、 47 県中 45 県を当て、 外したのは日本海側の山形県と京都府の 2 つだけ。 ただし日本海側は 10 県しかなく、 全県を「太平洋側」と答えるだけでも 37/47 = 78.7% になるうえ、 学習に使った県をそのまま当てた値なので汎化性能ではない。 標準化後の係数では B4106 降水日数(+1.937)が突出しており、 日本海側を分けているのは気温や降水量より「雨や雪の日の多さ」だと読める。
パターン認識の結果は「クラス」だけでなく「そのクラスである確率」として出せる。 確率をクラスに直す閾値は、 誤りの重さに合わせて選び直してよい。
🎯 このコードでやること:ベイズ決定則は「事後確率が 0.5 を超えたら日本海側」と判定する。 1 県ずつ外して求めた事後確率 $P(\text{日本海側}\mid x)$ に対して、 判定の閾値を 0.5・0.3・0.2・0.1 と下げたときに、 正解率・再現率・誤検出の数がどう変わるかを見る。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import pandas as pd import numpy as np from sklearn.naive_bayes import GaussianNB from sklearn.model_selection import LeaveOneOut, cross_val_predict df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1) d = df[df['年度'] == 2023].reset_index(drop=True) japan_sea = ['青森県','秋田県','山形県','新潟県','富山県','石川県','福井県','京都府','鳥取県','島根県'] y = d['都道府県'].isin(japan_sea).astype(int).values X = d[['降水日数(年間)']].values.astype(float) # 1 県ずつ外して、 その県の P(日本海側 | 降水日数) を求める prob = cross_val_predict(GaussianNB(), X, y, cv=LeaveOneOut(), method='predict_proba')[:, 1] for thr in [0.5, 0.3, 0.2, 0.1]: pred = (prob >= thr).astype(int) tp = ((pred == 1) & (y == 1)).sum(); fp = ((pred == 1) & (y == 0)).sum() print(f'閾値 {thr:.1f}: 正解率 {(pred == y).mean():.3f} 再現率 {tp}/10 誤検出 {fp} 県' f' 新たに日本海側と判定: {d.loc[(pred == 1) & (prob < 0.5), "都道府県"].tolist()}') |
💬 閾値 0.5 では正解率 0.957・再現率 8/10・誤検出 0 県。 閾値を 0.3 に下げると見逃していた山形県を拾って再現率 9/10 になるが、 北海道・岩手県・滋賀県・沖縄県の 4 県を新たに日本海側と誤判定し、 正解率は 0.894 に下がる。 0.1 まで下げると誤検出は 6 県に増える。 京都府(104 日)はどの閾値でも拾えない。 見逃しと誤検出のどちらが重いかで閾値を選ぶのが、 コストを入れたベイズ決定則の考え方である。 沖縄県(124 日)が誤検出に入るのは、 日本海側の分布を正規分布で近似したために裾が太平洋側の山まで伸びているからで、 分布の仮定が判定に効くことも分かる。
🎯 このコードでやること:2 クラスではなく 7 クラスの認識として、 気候の 5 特徴から「どの地方の県か」を k 近傍法(k=3)で当て、 1 県ずつ外した予測を混同行列(正解 × 予測のクロス集計)にまとめる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import pandas as pd import numpy as np from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler from sklearn.neighbors import KNeighborsClassifier from sklearn.model_selection import LeaveOneOut, cross_val_predict df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1) d = df[df['年度'] == 2023].reset_index(drop=True) code = d['地域コード'].str[1:3].astype(int) # R01000 → 1 bounds = [(7, '北海道・東北'), (14, '関東'), (23, '中部'), (30, '近畿'), (35, '中国'), (39, '四国'), (47, '九州・沖縄')] d['地方'] = [next(n for b, n in bounds if c <= b) for c in code] cols = ['年平均気温', '最高気温(日最高気温の月平均の最高値)', '最低気温(日最低気温の月平均の最低値)', '降水日数(年間)', '降水量(年間)'] model = make_pipeline(StandardScaler(), KNeighborsClassifier(n_neighbors=3)) pred = cross_val_predict(model, d[cols], d['地方'], cv=LeaveOneOut()) print(f'7 地方を気候 5 特徴で当てる LOOCV 正解率 = {(pred == d["地方"]).mean():.3f}' f'(一番多い中部 9 県と答え続けると {(d["地方"] == "中部").mean():.3f})') order = [n for _, n in bounds] tab = pd.crosstab(pd.Categorical(d['地方'], order), pd.Categorical(pred, order), rownames=['正解'], colnames=['予測']) print(tab.to_string()) |
💬 正解率は 0.340 で、 一番多い中部と答え続ける 0.191 よりは上だが、 3 県に 2 県は外れる。 混同行列を見ると、 九州・沖縄は 8 県中 6 県、 北海道・東北は 7 県中 4 県が当たる一方、 関東は 1 県も当たらず 7 県中 4 県が中国と判定される。 関東と瀬戸内の県は気温も降水も似ているので、 気候の特徴空間では同じ山に入る。 7 地方は行政上の区分で、 気候で分かれるクラスではない。 正解率 1 つでは見えない「どのクラスとどのクラスを取り違えるか」が、 混同行列だと読める。
🎯 このコードでやること:🐍 の第 5 弾と同じ「日本海側かどうか」の識別を、 特徴を降水日数 1 つにした場合と 4 つにした場合で、 訓練データでの正解率と、 1 県ずつ外して当てる LOOCV の正解率で比べる。 全県を太平洋側と答える多数派の正解率も並べる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import pandas as pd import numpy as np from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression from sklearn.model_selection import LeaveOneOut, cross_val_predict df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1) d = df[df['年度'] == 2023].reset_index(drop=True) japan_sea = ['青森県','秋田県','山形県','新潟県','富山県','石川県','福井県','京都府','鳥取県','島根県'] y = d['都道府県'].isin(japan_sea).astype(int).values print(f'多数派(全県を太平洋側)と答えるだけの正解率 = {(y == 0).mean():.3f}') for cols in [['降水日数(年間)'], ['年平均気温', '降水日数(年間)', '降水量(年間)', '65歳以上人口']]: model = make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000)) train_acc = model.fit(d[cols], y).score(d[cols], y) pred = cross_val_predict(model, d[cols], y, cv=LeaveOneOut()) # 1 県ずつ外して予測 miss = d.loc[pred != y, '都道府県'].tolist() hit = ((pred == 1) & (y == 1)).sum() print(f'特徴 {len(cols)} 個: 訓練正解率 {train_acc:.3f} / LOOCV 正解率 {(pred == y).mean():.3f}' f' / 日本海側 10 県の再現率 {hit}/10 / 外した県 {miss}') |
💬 全県を「太平洋側」と答えるだけで正解率は 0.787 ある。 特徴 4 つのロジスティック回帰は訓練正解率 0.957 だが、 1 県ずつ外して当てると 0.894 に下がり、 北海道・岩手県まで日本海側と誤判定する。 降水日数 1 つだけのモデルは訓練でも LOOCV でも 0.936 で、 特徴を 3 つ足したほうが未知の県ではかえって悪い。 どちらも日本海側 10 府県のうち当たるのは 7 府県で、 山形県・京都府・島根県は取りこぼす。 47 件では、 特徴を足すと訓練データへの当てはまりだけが良くなりやすい。
中央のパターン認識は、 生データを数値にする特徴抽出と、 その特徴からクラスを決める分類器・識別関数の 2 段でできている。 どの分類器も、 誤りを最小にする理想の規則である Bayes 決定理論の近似として位置づけられ、 結果は混同行列・F1 などで評価する。 画像・音声・テキストのように特徴を人手で作りにくい対象では、 CNN・Transformer が特徴抽出まで学習し、 画像・音声・テキストの認識という応用分野につながる。
パターン認識は入力データから規則性を抽出するタスクで、 機械学習・統計分類・特徴抽出と一体で運用される。
SSDSE-B-2026 を用いた演習では、 「パターン認識」 を中核に据えて上記の上流・並列・下流の手法を実データで連結する経験を積むと、 単独の手法暗記より実務的応用力が身につく。
「パターン認識」を実際に使うとき、 何をどう選ぶかを順に判断する。 上から順に答えていくと、 使うべき手法と評価の仕方が決まる。
パターン認識は「入力の種類」と「ラベルの有無」でほぼ手法が決まる。 迷うときは、 その 2 つを先に書き出す。
2 次元の特徴空間に複数クラスのデータ点(都道府県の類型を模した架空点)を配置した。 空白をクリック/タップするとクエリ点(★)が置かれ、 kNN(k 近傍法)がどのクラスに分類するかを正確に計算して表示する。 背景の淡い色分けが決定境界。 k を変えると境界が滑らかになったり複雑になったりし、 クラスタの重なりを上げると認識が難しくなる様子を体感できる。 点はドラッグで動かせる。
パターン認識の本質は「特徴空間の近さ=カテゴリの近さ」という賭けである。 上のデモで見たように、 47 都道府県を「人口密度」「高齢化率」など数本の軸で表せば、 似た県は近くに集まり、 新しい県は周囲の多数派に倣って類型付けできる。 人間が「都会っぽい/田舎っぽい」と直感で判断するのを、 距離という 1 本のものさしに翻訳したものが kNN だと言える。
関連ページ:k 近傍法・分類タスク・特徴量エンジニアリング・過学習・深層学習アーキテクチャ。 (次元の呪い・決定境界は本ページ内で解説)
🎯 このコードでやること:単位も散らばりも違う降水日数(日)と降水量(mm)の 2 特徴で、 k 近傍法の k を 1・3・5・9・15 と変え、 そのまま距離を測った場合と、 標準化してから測った場合の LOOCV 正解率と再現率を比べる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import pandas as pd import numpy as np from sklearn.neighbors import KNeighborsClassifier from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler from sklearn.model_selection import LeaveOneOut, cross_val_predict df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1) d = df[df['年度'] == 2023].reset_index(drop=True) japan_sea = ['青森県','秋田県','山形県','新潟県','富山県','石川県','福井県','京都府','鳥取県','島根県'] y = d['都道府県'].isin(japan_sea).astype(int).values X = d[['降水日数(年間)', '降水量(年間)']] # 単位: 日 と mm print(X.describe().loc[['mean', 'std']].round(1).to_string()) for k in [1, 3, 5, 9, 15]: raw = KNeighborsClassifier(n_neighbors=k) std = make_pipeline(StandardScaler(), KNeighborsClassifier(n_neighbors=k)) p_raw = cross_val_predict(raw, X, y, cv=LeaveOneOut()) p_std = cross_val_predict(std, X, y, cv=LeaveOneOut()) print(f'k={k:2d} そのまま: 正解率 {(p_raw == y).mean():.3f} 再現率 {((p_raw == 1) & (y == 1)).sum()}/10' f' 標準化: 正解率 {(p_std == y).mean():.3f} 再現率 {((p_std == 1) & (y == 1)).sum()}/10') |
💬 そのまま距離を測ると、 k によらず正解率は 0.702〜0.787 で、 全県を太平洋側と答える 0.787 を超えられず、 日本海側 10 府県はほとんど当たらない(再現率 0〜2/10)。 降水量の標準偏差 538.4 は降水日数の 25.6 の約 21 倍あり、 距離がほぼ降水量だけで決まるからで、 日本海側を分ける手がかりである降水日数が効いていない。 標準化すると k=3 で正解率 0.957・再現率 8/10 まで上がる。 k を 15 まで大きくすると再現率は 3/10 に落ち、 10 県しかない少数クラスが近傍の多数決で負けるようになる。 単位を揃えることと k を選ぶことは、 どちらも「何を近いとみなすか」を決める作業である。
※ 本セクションはページ既存の解説(kNN デモ・落とし穴・数式)と重複しない角度からの追記です。 SSDSE-B-2026(cp932・2023 年 47 都道府県)の実測値と、 明記した架空の合成例のみを用います。
パターン認識の骨格は2 段構えである。 まず生データを特徴(feature)に変換し(特徴抽出)、 次にその特徴空間で規則性やクラスを識別する(分類・クラスタリング)。 前段の質が後段の上限を決める点は、 人間が「顔のどこを見て人を見分けるか」を無意識に学ぶのと同じ構図で、 パターン認識は人間の認知(グループ化して意味づける働き)を計算で模倣したものだと言える。
この識別には 2 つのモードがある。 ラベル(正解カテゴリ)を与えて「この特徴なら都市型」と教える教師ありと、 ラベルを一切与えず「似た県は勝手に集まる」に任せる教師なしである。 本ページ前半の kNN デモは教師あり側だが、 教師なし側も同じ「特徴空間の近さ=カテゴリの近さ」という賭けの上に立つ。
2023 年 47 都道府県を log10(A1101 総人口)・B4101 年平均気温・A1303/A1101 高齢化率 の 3 特徴で標準化し、 ラベルを与えず k-means でクラスタリングした実測結果:
| クラスタ | 県数 | 平均気温 | 平均高齢化率 | 代表県(実測) |
|---|---|---|---|---|
| 0(北日本型) | 8 | 13.5℃ | 34.6% | 北海道・青森・岩手・秋田 |
| 1(関東周縁型) | 12 | 16.8℃ | 30.2% | 宮城・茨城・栃木・群馬 |
| 2(地方型) | 18 | 17.4℃ | 33.6% | 富山・福井・山梨・奈良 |
| 3(都市型) | 9 | 18.5℃ | 26.6% | 埼玉・千葉・東京・神奈川 |
正解ラベルを一つも渡していないのに、 「寒冷・高高齢の北日本」と「温暖・低高齢の首都圏」が自動的に分かれた。 これが教師なしパターン認識の面白さである。 ただしクラスタ数 k は恣意的で、 シルエットスコア(実測)は k=2 で 0.414、 k=3 で 0.299、 k=4 で 0.368、 k=5 で 0.346。 純粋に指標だけなら「2 分割(都市 vs 非都市)」が最も分離が良く、 4 類型は解釈のしやすさとのトレードオフだと分かる。 → クラスタリング・k-means・教師なし学習。
パターン認識の手法は、 大きく「照合 → 特徴設計 → 表現学習」という歴史の流れで捉えると整理しやすい。
これらを貫く理論的支柱がベイズ決定理論である。 事後確率 $$P(y\mid x)\propto P(x\mid y)\,P(y)$$ を最大にするクラスを選ぶMAP 決定則が、 誤り率を最小化する最適な認識であることが示せる。 誤分類のコストが非対称なら(例:少数クラスの見逃しが重い)、 単純な最大確率ではなく期待損失を最小化する閾値へずらすのが正しい。 → ベイズの定理・損失関数・機械学習。
補足: 上のクラスタ表・シルエット・相関ペア数・臨界相関・80.9% は SSDSE-B-2026 の実測。 内積テンプレートの例は本ページ既出の架空の合成データ。
🎯 このコードでやること:ラベルを渡さない k-means(k=2)で 47 県を 2 つに分け、 その分かれ方が「日本海側かどうか」とどれだけ一致するかを、 クロス集計と調整ランド指数(ARI、 1 で完全一致・0 で偶然並み)で見る。 特徴を気候の 2 つにした場合と、 人口の 2 つにした場合を比べる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import pandas as pd import numpy as np from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler from sklearn.metrics import adjusted_rand_score df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1) d = df[df['年度'] == 2023].reset_index(drop=True) japan_sea = ['青森県','秋田県','山形県','新潟県','富山県','石川県','福井県','京都府','鳥取県','島根県'] y = d['都道府県'].isin(japan_sea).astype(int) for cols in [['降水日数(年間)', '年平均気温'], ['総人口', '65歳以上人口']]: Xs = StandardScaler().fit_transform(d[cols]) lab = KMeans(n_clusters=2, n_init=10, random_state=0).fit_predict(Xs) # ラベルは使わない tab = pd.crosstab(pd.Series(lab, name='クラスタ'), y.rename('日本海側')) print('特徴:', cols, ' ARI =', round(adjusted_rand_score(y, lab), 3)) print(tab.to_string()) small = pd.Series(lab).value_counts().idxmin() print('少ない方のクラスタ:', d.loc[lab == small, '都道府県'].tolist(), '\n') |
💬 気候の 2 特徴では ARI 0.658 で、 片方のクラスタ 12 県に日本海側 10 府県のうち 9 府県が入った(外れたのは京都府。 代わりに北海道・岩手県・滋賀県が入る)。 人口の 2 特徴では ARI −0.113 で、 分かれたのは東京都・神奈川県など人口の多い 9 都道府県とそれ以外で、 日本海側とは無関係になる。 教師なしのパターン認識が見つけるのは「特徴の空間で固まっているもの」であって、 人が期待する類型とは限らない。 どの類型が浮かぶかは、 アルゴリズムより先に特徴の選び方で決まる。