kakakakakku blog

Weekly Tech Blog: Keep on Learning!

Feature Engineering : Kaggle Courses で「特徴量エンジニアリング」を学ぶ

Kaggle が公開している「Kaggle Courses」「Feature Engineering」コースを受講した.機械学習モデルを構築するときに重要になる「特徴量エンジニアリング」を多岐にわたる観点から学べる.「特徴量エンジニアリング」の目標は「データセットを目の前の問題に対してより適切な形にすること」であるとコースでは紹介されていて,より具体的には以下となる.

  • モデルの予測パフォーマンスを向上させるため
  • 計算またはデータのニーズを削減するため
  • 結果の解釈可能性を向上させるため

www.kaggle.com

アジェンダ ✏️

「Feature Engineering」コースは「計6種類」のレッスン(ドキュメント)と「計5種類」のレッスン(演習)から構成されている.「相互情報量」「K-means」「主成分分析 (PCA)」「Target Encoding」など,多岐にわたる.

  1. What Is Feature Engineering
  2. Mutual Information
  3. Creating Features
  4. Clustering With K-Means
  5. Principal Component Analysis
  6. Target Encoding

また「Feature Engineering」コースの前提になる「Intermediate Machine Learning」コースは既に受講していて,以下にまとめてある.

kakakakakku.hatenablog.com

相互情報量 ✏️

まず,特徴量とラベルの関係を知るために「相互情報量 (Mutual Information)」を使う.よく Pandas の corr() 関数を使って「相関係数」を算出するけど,あくまで「相関係数」は2種類のデータが「線形的な」関係性のときに使う.「相互情報量」は線形的な関係性でなくても使える.簡単に表現すると「相互情報量」とは「A が B の不確実性を低減する尺度」と言える.

kakakakakku.hatenablog.com

そして scikit-learnfeature_selection モジュールには「相互情報量」を計算する関数として mutual_info_regression() : 回帰mutual_info_classif() : 分類 がある.

今回は Kaggle の住宅価格を予測する「住宅データセット」を使う.

www.kaggle.com

今回は住宅価格を予測する回帰問題であるため mutual_info_regression() を使って SalePrice(住宅価格) との「相互情報量」を計算する.以下にソートした結果を表示する.結果を読み解くと OverallQual(全体的なクオリティ)Neighborhood(地域内の場所)GrLivArea(リビングの広さ)YearBuilt(建設日) など,まぁ一般的に考えて重要そうな特徴量が上位になっている.なお「相互情報量」が低くても重要な特徴量はあるため,最終的には「ビジネス理解」が重要とも書いてあった.

OverallQual     0.581262
Neighborhood    0.569813
GrLivArea       0.496909
YearBuilt       0.437939
GarageArea      0.415014
TotalBsmtSF     0.390280
GarageCars      0.381467
FirstFlrSF      0.368825
BsmtQual        0.364779
KitchenQual     0.326194
ExterQual       0.322390
YearRemodAdd    0.315402
MSSubClass      0.287131
GarageFinish    0.265440
FullBath        0.251693
Foundation      0.236115
LotFrontage     0.233334
GarageType      0.226117
FireplaceQu     0.221955
SecondFlrSF     0.200658
Name: MI Scores, dtype: float64

特徴量抽出 ✏️

次に新しく特徴量を抽出する「計5種類」の手法を学ぶ,それぞれを以下に簡単にまとめる.

1. Create Mathematical Transforms(数学的変換)

例えば,GrLivArea(リビングの広さ)LotArea(ロットサイズ)から,新しく LivLotRatio(ロット比率) を計算する.

X_1["LivLotRatio"] = df.GrLivArea / df.LotArea

2. Interaction with a Categorical(カテゴリ変数)

例えば,BldgType(住居タイプ) を One-Hot エンコーディングをする.

X_2 = pd.get_dummies(df.BldgType, prefix="Bldg")

kakakakakku.hatenablog.com

3. Count Feature(カウント)

例えば,OpenPorchSF(オープンポーチエリア) など「Porch(建物の外壁から突き出している部分)」の総和を計算する.

X_3["PorchTypes"] = df[[
    "WoodDeckSF",
    "OpenPorchSF",
    "EnclosedPorch",
    "Threeseasonporch",
    "ScreenPorch",
]].gt(0.0).sum(axis=1)

4. Break Down a Categorical Feature(カテゴリ変数を区切る)

例えば,MSSubClass(販売に関係する住居タイプ) には One_and_Half_Story_Finished_All_AgesOne_Story_1946_and_Newer_All_Styles という「カテゴリ変数」が含まれているため _ で文字列分割をした1個目(今回で言えば One)を抽出する.バリエーションを減らすために使う.

X_4["MSClass"] = df.MSSubClass.str.split("_", n=1, expand=True)[0]

5. Use a Grouped Transform(グループ化)

例えば,Neighborhood(地域内の場所) でグループ化をした GrLivArea(リビングの広さ) の中央値を特徴量にする.カテゴリ変数の値によって差が出る場合に効果のある特徴量になりそう.

X_5["MedNhbdArea"] = df.groupby("Neighborhood")["GrLivArea"].transform("median")

K-means ✏️

次は「教師なし学習」の代表的なアルゴリズムである「K-means」を使ってクラスタリングを行う.今回は「5種類の特徴量」から「10種類のクラスタ」を探索する.クラスタリングの結果 0 ~ 9 の値を Cluster という新しい特徴量として追加している.以下はサンプルコードを抜粋して載せている.

X = df.copy()
y = X.pop("SalePrice")

features = [
    "LotArea",
    "TotalBsmtSF",
    "FirstFlrSF",
    "SecondFlrSF",
    "GrLivArea",
]

X_scaled = X.loc[:, features]
X_scaled = (X_scaled - X_scaled.mean(axis=0)) / X_scaled.std(axis=0)

kmeans = KMeans(n_clusters=10, n_init=10, random_state=0)
X["Cluster"] = kmeans.fit_predict(X_scaled)

また Seaborn で可視化をして,特徴量ごとにどのようにクラスタリングされているのかを確認することもできる.

f:id:kakku22:20211202141634p:plain

主成分分析 (PCA) ✏️

「主成分分析 (PCA)」「教師なし学習」の代表的なアルゴリズムで以下の目的で使うことができる.

  • Dimensionality reduction(次元削減)
  • Anomaly detection(異常検出)
  • Noise reduction(ノイズリダクション)
  • Decorrelation(非相関)

今回は以下の「4種類の特徴量」「主成分分析 (PCA)」を実行する.必ずしも次元削減をする必要はなく,データの特性(分散など)を計算することにも使える.今回は scikit-learnPCApca.fit_transform(X) を使う.

  • GarageArea(ガレージサイズ)
  • YearRemodAdd(リフォーム日)
  • TotalBsmtSF(地下室サイズ)
  • GrLivArea(リビングの広さ)

scikit-learn.org

そして,PC1 ~ PC4 という「4種類の特徴量」を抽出することができる.そして,以下のように「loading(主成分負荷量)」を表示すると,例えば,PC1「広さ」に着目した値になっているように考えることができる.また PC3GarageArea(ガレージサイズ)YearRemodAdd(リフォーム日)0 に近いため「リビングと地下室の関係性」 に着目した値になっているように考えることができる.

                   PC1       PC2       PC3       PC4
GarageArea    0.541229  0.102375 -0.038470  0.833733
YearRemodAdd  0.427077 -0.886612 -0.049062 -0.170639
TotalBsmtSF   0.510076  0.360778 -0.666836 -0.406192
GrLivArea     0.514294  0.270700  0.742592 -0.332837

f:id:kakku22:20211202152718p:plain

まとめ ✏️

「Kaggle Courses」「Feature Engineering」コースを受講した.「特徴量エンジニアリング」として使うテクニックなどを多岐にわたって学べてとても良かった.そして,改めて「ビジネス理解」が重要だなと感じることができた.また「主成分分析 (PCA)」の活用に関しては個人的にまだ理解が浅いため引き続き学んでいく!

f:id:kakku22:20211202142251p:plain

関連記事

scikit-learn の Pipeline を使って前処理やアルゴリズムをまとめて宣言する

scikit-learnPipeline を使うと,データセットの前処理や機械学習アルゴリズムなどを「1つのオブジェクトに」まとめることができる.

scikit-learn.org

前回の記事で紹介した「Kaggle Courses」「Intermediate Machine Learning」コースでも使われていたこともあり,もう少しドキュメントを読みながら試していく.

kakakakakku.hatenablog.com

Pipeline に入門する

「Kaggle Courses」のコースを参考にサンプルコードを書くと,以下のようになる(重要なコードのみを抜粋している).Pipeline を使わずにコードを書くと SimpleImputerOneHotEncoderRandomForestRegressor など,それぞれで fit()fit_transform() を実行する必要があるため,コード量を減らしつつ,可読性が高く実装できる.イイネ👏

from sklearn.ensemble import RandomForestRegressor
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder

X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.8, test_size=0.2, random_state=0)

pipeline = Pipeline(
    steps=[
        ('imputer', SimpleImputer(strategy='most_frequent')),
        ('encoder', OneHotEncoder(handle_unknown='ignore')),
        ('model', RandomForestRegressor(n_estimators=50, random_state=0))
    ]
)

pipeline.fit(X_train, y_train)

pipeline.predict(X_test)

また Pipelineverbose=True パラメータを追加すると fit() を実行しながら以下のように進捗を表示することもできる.

pipeline = Pipeline(
    steps=[
        ('imputer', SimpleImputer(strategy='most_frequent')),
        ('encoder', OneHotEncoder(handle_unknown='ignore')),
        ('model', RandomForestRegressor(n_estimators=50, random_state=0))
    ],
    verbose=True
)

# [Pipeline] ........... (step 1 of 3) Processing imputer, total=   0.1s
# [Pipeline] ........... (step 2 of 3) Processing encoder, total=   0.1s
# [Pipeline] ............. (step 3 of 3) Processing model, total=  10.1s

そして,以下のドキュメントを参考に set_config() を使って「ダイアグラム表示」を有効化すると,以下のように Pipeline 構成を図として表示できる.実際には枠をクリックすることができて,パラメータを表示したり,非表示にしたりできる.

scikit-learn.org

from sklearn import set_config
set_config(display='diagram')
pipeline

f:id:kakku22:20211123175045p:plain

Pipeline で前処理をカテゴライズする

さらに Pipeline の中で前処理をカテゴライズすることもできる.例えば,以下のように numeric_preprocessor(量的変数)categorical_preprocessor(カテゴリ変数) としてまとめている(重要なコードのみを抜粋している).最終的には make_pipeline() を使って RandomForestRegressor とまとめている.今回のサンプルコード以上に前処理などが多いときに効果が出そう.

scikit-learn.org

from sklearn.compose import ColumnTransformer
from sklearn.ensemble import RandomForestRegressor
from sklearn.impute import SimpleImputer
from sklearn.pipeline import make_pipeline
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler

numeric_preprocessor = Pipeline(
    steps=[
        ('imputer', SimpleImputer(strategy='most_frequent')),
        ('scaler', StandardScaler()),
    ]
)

categorical_preprocessor = Pipeline(
    steps=[
        ('onehot', OneHotEncoder(handle_unknown='ignore')),
    ]
)

pipeline = make_pipeline(
    ColumnTransformer(
        [
            ('numerical', numeric_preprocessor, numerical_cols),
            ('categorical', categorical_preprocessor, categorical_cols)
        ]
    ),
    RandomForestRegressor(n_estimators=50, random_state=0)
)

同じく set_config() を使って「ダイアグラム表示」を有効化すると,より整理された Pipeline 構成を表示することができた.

from sklearn import set_config
set_config(display='diagram')
pipeline

f:id:kakku22:20211124095951p:plain

まとめ

scikit-learnPipeline を使うと,データセットの前処理や機械学習アルゴリズムなどを「1つのオブジェクトに」まとめることができる.今回はシンプルな Pipeline 構成とカテゴライズをした構成を紹介した.ドキュメントを読むと,他にも GridSearchCV と組み合わせてハイパーパラメータを探索することもできて,便利だった.

scikit-learn.org

Intermediate Machine Learning : Kaggle Courses で学びながら「住宅価格予測」コンペに参加する

Kaggle が公開している「Kaggle Courses」「Intermediate Machine Learning」コースを受講した.Kaggle のコンペティション「Housing Prices Competition for Kaggle Learn Users(住宅価格予測)」をテーマに試行錯誤をして,実際にモデルを登録することもできる.アルゴリズムとしては「ランダムフォレスト」「XGBoost」を使う.アジェンダを見るとわかる通り,データセットの前処理からモデルの構築評価まで幅広く体験できる.

www.kaggle.com

なお,前提コースである「Pandas」「Intro to Machine Learning」は既に受講していて,以下にまとめてある.

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com

アジェンダ 🏠

「Intermediate Machine Learning」コースには「計7種類」のレッスン(ドキュメントと演習)がある.

  1. Introduction
  2. Missing Values
  3. Categorical Variables
  4. Pipelines
  5. Cross-Validation
  6. XGBoost
  7. Data Leakage

住宅価格予測 🏠

1460 件のデータセットに 80 個の特徴量を含んでいる.その中から今回は 7 個の特徴量を使う.このあたりは前提コース「Intro to Machine Learning」と同じ.そして,特徴量の中に予測する SalePrice(住宅価格) も含まれていることから「教師あり学習(回帰)」と言える.

X_full.shape
# (1460, 80)

X.shape
# (1460, 7)

X.columns
# Index(['LotArea', 'YearBuilt', '1stFlrSF', '2ndFlrSF', 'FullBath', 'BedroomAbvGr', 'TotRmsAbvGrd'], dtype='object')

まず 1. Introduction2. Missing Values3. Categorical Variables4. Pipelines では,scikit-learnRandomForestRegressor を使って「ランダムフォレスト」でモデルを構築する.以下のように適当にパラメータを設定して比較したり(今回は model_3 の平均絶対誤差 MAE が1番低かった👌),欠損値を考慮したり,カテゴリ変数をエンコーディングしたり,一歩一歩モデル精度を向上させていく.また scikit-learnPipeline を使って,コード実装を改善する.

model_1 = RandomForestRegressor(n_estimators=50, random_state=0)
model_2 = RandomForestRegressor(n_estimators=100, random_state=0)
model_3 = RandomForestRegressor(n_estimators=100, criterion='mae', random_state=0)
model_4 = RandomForestRegressor(n_estimators=200, min_samples_split=20, random_state=0)
model_5 = RandomForestRegressor(n_estimators=100, max_depth=7, random_state=0)

実際に評価をしながら進めるため,今回は欠損値を平均値で埋めるよりも特徴量を削除した方がパフォーマンスが良いなど,予想とは違う気付きもあって良かった.

  • Missing Values(欠損値)
    • [1] A Simple Option: Drop Columns with Missing Values(単純に特徴量を削除する)
    • [2] A Better Option: Imputation(より良く平均値などで埋める)
    • [3] An Extension To Imputation(値を埋めつつ欠損値の有無を表現する新しい特徴量を追加する)
  • Categorical Variables(カテゴリ変数)
    • [1] Drop Categorical Variables(カテゴリ変数を削除する)
    • [2] Ordinal Encoding(順序エンコーディング)
    • [3] One-Hot Encoding(ワンホットエンコーディング)

実際に Kaggle コンペティションにモデルを登録して,Leaderboard で順位が上がっていくことも確認する🏆

f:id:kakku22:20211123120838p:plain

交差検証 🏠

5. Cross-Validation では,scikit-learncross_val_score() を使って「交差検証」を行う.分割数 cv35 に設定しながら最終的な平均値を確認する.

scikit-learn.org

そして「ランダムフォレスト」に設定するパラメータ n_estimators の最善値を探す.今回は n_estimators = 50, 100, 150, 200, 250, 300, 350, 400 で比較して,結果的として 200 のパフォーマンスが1番良かった.

f:id:kakku22:20211123122155p:plain

XGBoost 🏠

ここまでは「ランダムフォレスト(アンサンブル学習)」を使ってきたけど,次は「XGBoost(勾配ブースティング)」を適用する.以下のようにパラメータを変えて比較をするけど,重要なのはアルゴリズムを変えることも考えながらコンペティションに参加するという点になる.

my_model_1 = XGBRegressor(random_state=0)
# Mean Absolute Error: 17662.736729452055

my_model_2 = XGBRegressor(n_estimators=1000, learning_rate=0.05)
# Mean Absolute Error: 16688.691513270547

my_model_3 = XGBRegressor(n_estimators=1)
# Mean Absolute Error: 127895.0828807256

xgboost.readthedocs.io

Data Leakage 🏠

7. Data Leakage では,モデルに対する考察として「Data Leakage(データ漏洩)」を学ぶ.「Data Leakage」とは,トレーニングデータセットにターゲットに関する「含まれていはいけないデータが含まれていること」と言える.よって,モデルの検証時にはパフォーマンスが高くなり,本番環境にデプロイするとパフォーマンスが低くなってしまう.「Data Leakage」として,具体的には以下の2種類が載っていた.

  • Target leakage(ターゲット漏洩)
    • 例 : 肺炎予測をするときに「抗生物質を服用しているか」という特徴量を使ってしまう
      • 本来は「肺炎である」と診断されてから処方されるため,将来の予測には使えない
  • Train-Test Contamination(トレインテスト汚染)
    • 例 : データセットを前処理する場合に欠損値を平均値などで埋める場合に検証データの精度が高くなってしまう
      • 検証データを除外して前処理をする
      • scikit-learntrain_test_split() から Train-Test という名前になっている

まとめ 🏠

「Kaggle Courses」「Intermediate Machine Learning」コースを受講した.Kaggle のコンペティション「Housing Prices Competition for Kaggle Learn Users(住宅価格予測)」に参加することもできて,一般的な機械学習ワークフローを体験することができた.とても良かった👏

次は「Feature Engineering」コースと「Machine Learning Explainability」コースに進もうと思う.

f:id:kakku22:20211123123758p:plain

imbalanced-learn の SMOTE モジュールを使って簡単にオーバーサンプリングを実現する

分類などの機械学習モデルを構築するときにデータセットに偏り(不均衡データ)があると適切に学習できない可能性がある.データセットを強制的に増やす操作を「オーバーサンプリング」と言って,SMOTE (Synthetic Minority Over-sampling Technique)ADASYN (Adaptive Synthetic) など,具体的な「オーバーサンプリング手法」がよく知られている.ちなみに SMOTEk-NN (k-Nearest Neighbor) : k近傍法 を参考に近接データを増やす.

imbalanced-learn とは

今回紹介する imbalanced-learn「不均衡データ」を扱うライブラリで「オーバーサンプリング」「アンダーサンプリング」などを簡単に実装できる.そして scikit-learn と互換がある.また GitHub だと scikit-learn-contrib プロジェクトで管理されている.

github.com

SMOTE モジュールを試す

今回は imbalanced-learn に入門するために SMOTE モジュールを試す.Over-sampling のドキュメントに載っているサンプルコードを参考にしつつ,もっと簡単に書き直してみた.

まず,scikit-learnmake_classification() 関数を使って,2000 データセットを 0.05 : 0.95 の割合で不均衡データとして生成した.特徴量はシンプルに2個にした.そして imbalanced-learnSMOTE モジュールで fit_resample() 関数を使うと簡単に「オーバーサンプリング」をすることができる.

from imblearn import FunctionSampler
from imblearn.over_sampling import SMOTE
from sklearn.datasets import make_classification
import matplotlib.pyplot as plt

def plot_resampling(X, y, sampler, ax):
    X_res, y_res = sampler.fit_resample(X, y)
    ax.scatter(X_res[:, 0], X_res[:, 1], c=y_res, alpha=0.8, edgecolor="k")
    title = f"Resampling with {sampler.__class__.__name__}"
    ax.set_title(title)

X, y = make_classification(
    n_samples=2000,
    n_features=2,
    n_informative=2,
    n_redundant=0,
    n_classes=2,
    weights=[0.05, 0.95],
    class_sep=1.0,
    random_state=1
)

fig, axs = plt.subplots(nrows=2, ncols=1, figsize=(20, 20))

samplers = [
    FunctionSampler(),
    SMOTE()
]

for ax, sampler in zip(axs.ravel(), samplers):
    plot_resampling(X, y, sampler, ax)

そして matplotlibscatter() 関数で散布図を描画した.Resampling with FunctionSampler(通常)Resampling with SMOTE(オーバーサンプリング) を比較すると,特徴量(紫色)に大きく差を確認できる.便利だ!

f:id:kakku22:20211101135838p:plain

関連記事

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com

iris データセットを libsvm フォーマットにする : scikit-learn の dump_svmlight_file()

scikit-learndatasets モジュールの中に libsvm フォーマットのデータセットを扱う関数がある.libsvm フォーマットは以下のフォーマットでデータセットを表現し,1番左にラベル(教師データ)を持つ.例えば Amazon SageMaker の組み込みアルゴリズム XGBoost でもサポートされている.

<label> <index1>:<value1> <index2>:<value2> ...

以下のサイトには libsvm フォーマットで多種多様なデータセットが公開されている.

www.csie.ntu.edu.tw

libsvm フォーマット例

例えば iris データセット(アヤメ)に以下のサンプルデータセットがあったとする.Label は3種類ある.

  • 0 = setosa(ヒオウギアヤメ)
  • 1 = versicolor(ハナショウブ)
  • 2 = virginica(カキツバタ)
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) Label
5.1 3.5 1.4 0.2 0
4.9 3.0 1.4 0.2 0
4.7 3.2 1.3 0.2 0

サンプルデータセットを libsvm フォーマットにすると以下となる.

0 0:5.1 1:3.5 2:1.4 3:0.2
0 0:4.9 1:3 2:1.4 3:0.2
0 0:4.7 1:3.2 2:1.3 3:0.2

scikit-learn dump_svmlight_file() 関数

scikit-learn を使って簡単に iris データセット(アヤメ)をセットアップできるため「特徴量」「ラベル」を取得して,同じく scikit-learndump_svmlight_file() 関数をさっそく試す.最終的に iris-libsvm ファイルに書き出す.コードはシンプルに書ける!

from sklearn.datasets import dump_svmlight_file
from sklearn.datasets import load_iris

iris = load_iris()

# 特徴量
x = iris.data

# ラベル
y = iris.target

dump_svmlight_file(x, y, 'iris-libsvm')

scikit-learn.org

実行後に head -n 50 iris-libsvm コマンドを実行して書き出した iris-libsvm ファイルを確認すると,期待通りに libsvm フォーマットになっていた.

0 0:5.1 1:3.5 2:1.4 3:0.2
0 0:4.9 1:3 2:1.4 3:0.2
0 0:4.7 1:3.2 2:1.3 3:0.2
0 0:4.6 1:3.1 2:1.5 3:0.2
0 0:5 1:3.6 2:1.4 3:0.2
0 0:5.4 1:3.9 2:1.7 3:0.4
0 0:4.6 1:3.4 2:1.4 3:0.3
0 0:5 1:3.4 2:1.5 3:0.2
0 0:4.4 1:2.9 2:1.4 3:0.2
0 0:4.9 1:3.1 2:1.5 3:0.1
0 0:5.4 1:3.7 2:1.5 3:0.2
0 0:4.8 1:3.4 2:1.6 3:0.2
0 0:4.8 1:3 2:1.4 3:0.1
0 0:4.3 1:3 2:1.1 3:0.1
0 0:5.8 1:4 2:1.2 3:0.2
0 0:5.7 1:4.4 2:1.5 3:0.4
0 0:5.4 1:3.9 2:1.3 3:0.4
0 0:5.1 1:3.5 2:1.4 3:0.3
0 0:5.7 1:3.8 2:1.7 3:0.3
0 0:5.1 1:3.8 2:1.5 3:0.3
0 0:5.4 1:3.4 2:1.7 3:0.2
0 0:5.1 1:3.7 2:1.5 3:0.4
0 0:4.6 1:3.6 2:1 3:0.2
0 0:5.1 1:3.3 2:1.7 3:0.5
0 0:4.8 1:3.4 2:1.9 3:0.2
0 0:5 1:3 2:1.6 3:0.2
0 0:5 1:3.4 2:1.6 3:0.4
0 0:5.2 1:3.5 2:1.5 3:0.2
0 0:5.2 1:3.4 2:1.4 3:0.2
0 0:4.7 1:3.2 2:1.6 3:0.2
0 0:4.8 1:3.1 2:1.6 3:0.2
0 0:5.4 1:3.4 2:1.5 3:0.4
0 0:5.2 1:4.1 2:1.5 3:0.1
0 0:5.5 1:4.2 2:1.4 3:0.2
0 0:4.9 1:3.1 2:1.5 3:0.2
0 0:5 1:3.2 2:1.2 3:0.2
0 0:5.5 1:3.5 2:1.3 3:0.2
0 0:4.9 1:3.6 2:1.4 3:0.1
0 0:4.4 1:3 2:1.3 3:0.2
0 0:5.1 1:3.4 2:1.5 3:0.2
0 0:5 1:3.5 2:1.3 3:0.3
0 0:4.5 1:2.3 2:1.3 3:0.3
0 0:4.4 1:3.2 2:1.3 3:0.2
0 0:5 1:3.5 2:1.6 3:0.6
0 0:5.1 1:3.8 2:1.9 3:0.4
0 0:4.8 1:3 2:1.4 3:0.3
0 0:5.1 1:3.8 2:1.6 3:0.2
0 0:4.6 1:3.2 2:1.4 3:0.2
0 0:5.3 1:3.7 2:1.5 3:0.2
0 0:5 1:3.3 2:1.4 3:0.2

関連記事

kakakakakku.hatenablog.com