ikeid
@ikeid

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

One Hot Encodingでモデルと予想データの列数が合わない

解決したいこと

one hot encodingした場合、予測用データに出てこない列はどうするのでしょうか?

例えばカテゴリー変数を仮に出身都道府県とした場合、これをone hot encodingしたもので学習したモデルは都道府県情報について47つの列を持っているが、実際の予測に使うデータをone hot encodingしたデータには47つも都道府県の列がないのでモデルの持つ列と入力データの列数が合わないことになりませんか?

自分はtrainデータを作るときと同じように予測用のデータにもone hot encodingを施すものだと思っていたのですが、違う正しいやり方があるのでしょうか?

1

2Answer

自分はtrainデータを作るときと同じように予測用のデータにもone hot encodingを施すものだと思っていたのですが

ここでおっしゃっている同じようにとは,同じエンコーディングのルールを用いてという意味なのでしょうか.それとも同じコードの記述でということなのでしょうか.前者であれば正しい認識で,後者であれば間違っているコード場合にデータの列数が合わなくなりますので間違った認識だと思います.

47都道府県の出力のあるTrainデータと,47都道府県未満しかないValidationデータを保持してて,それぞれTensorFlow Kerasのto_categoricalを利用して同じコードの記述でそれぞれ別々にOne Hot Encodingすると,たしかにデータの列数は合いませんね.

列数が合わない
y_train = to_categorical(y_train) # 47都道府県をエンコーディングできるルールで変換
y_valid = to_categorical(y_valid) # それよりも少ない都道府県をエンコーディングしてしまうルールで変換

また同様に,Scikit-LearnのOneHotEncoderを用いた場合でも

列数が合わない
from sklearn.preprocessing import OneHotEncoder
y_train = OneHotEncoder().fit_transform(y_train)
y_valid = OneHotEncoder().fit_transform(y_valid)

このようにできてしまいます.どちらも動作はto_categoricalと一緒です

TensorFlow Kerasでは普通,to_categoricalを用いてTrainデータもValidationデータも一緒にまとめて同じエンコーディングのルールを採用させてOne Hot Encodingします.

y = to_categorical(np.concat(y_train, y_valid)) # どちらも47都道府県をエンコーディングできるルールで変換(concatでいいのかはわからないけど分かれているなら適切な結合が必要)
y_train, y_valid = # trainとvalidに分ける

Scikit-LearnではOneHotEncoderを用いてTrainデータをOneHotEncoder.fit_transform()してエンコーディングのルールを作成と同時に変換した後,そのルールを用いてValidationデータをOneHotEncoder.transform()するなど,別々にOne Hotにすることも可能です.

OneHotEncoderを使う例
ohe = OneHotEncoder() # エンコーダの作成
y_train = ohe.fit_transform(y_train) # エンコーディングの変換ルールの作成と変換
y_valid = ohe.transform(y_valid)     # 先のルールを採用した変換

また,最初に47都道府県のデータでOneHotEncoder.fit()だけしたあとに,それぞれOneHotEncoder.transform()という同じコードの記述でTrainもValidationも同じエンコーディングのルールを採用した変換ができます.

同じコードで変換できる例
ohe = OneHotEncoder().fit(y_train) # エンコーダの作成と変換ルールの作成
y_train = ohe.transform(y_train)   # 変換
y_valid = ohe.transform(y_valid)   # 変換

終わりに

同じようにの意味として,同じエンコーディングのルールを採用するようにという意味であれば正しい認識で間違っていないです.そしてそれを実装されてください.同じコードという意味であれば別々のルールを採用させてしまうことがあるため,場合によっては間違った認識かと思って上のような回答になりました.

また,列の数が合わないとのことですが,具体的なOne Hot Encodingのコードも示していただけると幸いです.

1Like

自分で実装する場合の具体例を示しておきます.

class OneHotEncoder:
	def __init__(self):
		self.id = 0        # どのindexを1にするかの指標及びパラメータの種類数
		self.rule = dict() # 変換ルール
	
	def fit(self, x):
		for _x in sorted(x): # sortはしなくても良い(けどした方が良さげ)
			if not _x in self.rule.keys(): # まだ_xについて変換例がないのであれば
				self.rule[_x] = self.id # _xについて配列の要素id番目を1とするようなOneHotEncodingの変換ルールを作成する
				self.id += 1 # 次の_xのためにidを1増やしておく
		return self
	
	def transform(self, x):
		ret = list()
		for _x in x:
			r = [0] * self.id # 種類数分の長さを持つ配列rを作成する
			r[self.rule[_x]] = 1 # 配列rのrule[_x]番目を1にする
			ret.append(r)
		return ret
	
	def fit_transform(self, x):
		return self.fit(x).transform(x)

def to_categorical(x):
	return OneHotEncoder().fit_transform(x)

if __name__ == "__main__":
	print("----- correct column integer -----")
	y_train = [1, 2, 3, 4, 5]
	y_valid = [3, 2, 2, 1]
	ohe = OneHotEncoder()
	print(ohe.fit(y_train).transform(y_train))
	print(ohe.transform(y_valid))
	print(ohe.rule)

	print("\n-----   wrong column integer -----")
	print(OneHotEncoder().fit_transform(y_train))
	print(OneHotEncoder().fit_transform(y_valid))

	print("\n----- correct column strings -----")
	y_train = ["Bronze", "Silver", "Gold", "Platinum", "Diamond", "Predetor"]
	y_valid = ["Gold", "Silver", "Predetor"]
	ohe = OneHotEncoder()
	print(ohe.fit_transform(y_train))
	print(ohe.transform(y_valid))
	print(ohe.rule)

	print("\n-----   wrong column strings -----")
	print(to_categorical(y_train))
	print(to_categorical(y_valid))
出力
----- correct column integer -----
[[1, 0, 0, 0, 0], [0, 1, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, 1, 0], [0, 0, 0, 0, 1]]
[[0, 0, 1, 0, 0], [0, 1, 0, 0, 0], [0, 1, 0, 0, 0], [1, 0, 0, 0, 0]]
{1: 0, 2: 1, 3: 2, 4: 3, 5: 4}

-----   wrong column integer -----
[[1, 0, 0, 0, 0], [0, 1, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, 1, 0], [0, 0, 0, 0, 1]]
[[0, 0, 1], [0, 1, 0], [0, 1, 0], [1, 0, 0]]

----- correct column strings -----
[[1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1], [0, 0, 1, 0, 0, 0], [0, 0, 0, 1, 0, 0], [0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0]]
[[0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 1, 0]]
{'Bronze': 0, 'Diamond': 1, 'Gold': 2, 'Platinum': 3, 'Predetor': 4, 'Silver': 5}

-----   wrong column strings -----
[[1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1], [0, 0, 1, 0, 0, 0], [0, 0, 0, 1, 0, 0], [0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0]]
[[1, 0, 0], [0, 0, 1], [0, 1, 0]]

Scikit LearnのOneHotEncoderの入力は2次元配列を必要としますが,今回は簡単のため1次元配列を受け取って変換できるものを作成してみました.

1Like

Comments

  1. @ikeid

    Questioner

    同じルールでやろうと思っていながらも実装では同じコードを繰り返して2回fitさせていました…
    おかげさまで解決しました。ありがとうございます。

Your answer might help someone💌