混同行列、期待利益からのモデルの評価について
その2で終わるはずだったが続いた。
期待値としての評価をキノコの分類モデルで試して見た。コードはここ。
前回3つのモデルを作成し、性能の計測については損失関数や以下のような精度を使って評価した。
精度は分類器の性能を1つの値で表すことができ、また簡単に計算ができるためとても便利。
これらの評価の方法は単純すぎていくつか問題があるようだ。分類器を正しく評価するためにはクラスの混同という考え方と、分割表の一種である混同行列を理解する必要が大切らしい。
混同行列は n個のクラスを含む分類問題の場合、 (n,n) 行列になる。その行列の列はデータの実際のクラスを表し、行は分類器が予測したクラスを表す。
そのためテストデータに対して混同行列を用いると、分類器によって行われた決定を混同行列内の該当するセルに当てはめ、あるクラスの中に誤って別のクラスがどれくらい混ざっているかが一目でわかる。すごい!!!
2クラス分類での混同行列は以下のように定義される。
- 真陽性(実際のクラス、予測したクラスがどちらとも陽性のとき)
- 偽陽性(実際のクラスは陰性だが、予測したクラスは陽性のとき)
- 真陰性(実際のクラス、予測したクラスがどちらとも陰性のとき)
- 偽陰性(実際のクラスは陽性だが、予測したクラスは陰性のとき)
各セルからわかる通り、対角成分には正しく分類されたデータの数が入り、非対角成分には間違った予測をおこなったものが入る。
混同行列で誤った予測の数がわかるからなんぞや、となるが現実のビジネスだとモデルが誤った判断、偽陽性、偽陰性となった場合のコストを考慮しなくてはいけない。また偽陽性、偽陰性でのエラーは同じ重要度であるとは限らない。
真陽性・真陰性での利益、偽陽性・偽陰性でのコストが明確であれば、混同行列を組み合わせることで分類器を期待利益(期待値)という点で評価することができる。
さっそく前回のロジスティック回帰のモデルで期待利益を計算してみる。
まず混同行列を求める(テストデータ 2438件)。
この時モデルの損失としての精度は 0.68 ほど。陽性、陰性どちらとも五分五分な感じで分割されている。。
各セルは以下のように定義した。
次に期待利益を計算するため利益とコストを定義し、損益行列を作る。
近くの山で適当にキノコをたくさん拾って、そしてそのキノコを分類モデルにかけ、食用(陰性)と分類されたならそのキノコを焼いて客に売り、1000円の利益
を得るとする。また食用と分類されたが毒キノコを客に売ってしまった場合、代金を払い戻してさらに500円支払う(コスト)
ことにする。
毒キノコ売った時のコストがかなり軽い気がするが舌が痺れるくらいの症状しか起きない毒キノコとすれば軽くてもいいや。
上記を背景として以下のように損益行列を作った。(100円=1ドルみたいな感じに変換してる)
真陽性は毒キノコを分類するだけなので利益には影響しない 0 の値が、偽陽性も食用だったけど毒キノコと分類して売らないだけなので 0 の値が入る。
偽陽性と偽陰性の深刻度の違いが見て取れる。
期待利益はこのような数式で得ることができる。
確率にはこんな性質があるようで。
これを用いて変形すると、クラスの事前確率でくくり出すことができ、クラス配分が不均衡である影響を分離することができるらしい。かしこい。
ちなみにp(Y | p)などは混同行列を割合へ変換することで求めることができるので、こんな感じに表すこともできる。
事前確率などを混同行列から求め、期待利益を計算する。5000兆円いくかな。
一人当たり約200円期待できるとのこと。やっす。。。
次は利益曲線やROC曲線などモデルの性能の可視化を試す。
Kaggleのデータセットを使って特徴量を観察する その2
前回の続き
特徴量の選択を終えたのでキノコが食用か毒ありかの判別を行うモデルを構築していく
コードはここ github.com
モデリング
作成したモデルは
Tensorflowを使ったNNのロジスティック回帰モデル
決定木を用いたツリーのモデル
流行りのXGboostを用いたツリーのモデル
3種類を作った
決定木は前回の記事で紹介した本で知ったので、理解も兼ねてメモを残す
ざっくりいうとこんな形式の構造(木)になっている
△の木が逆さまで根が上にある。ブランチ(枝、図で言うと矢印)は常に四角で囲ってある属性
ノードから出ている。このノードから出ているブランチは属性の値を表す。ブランチを最後まで辿ると必ず葉(ラベル、目的変数)が割り当てられる。
ここでは矢印には特に何も書かなかったが、あるデータのある属性の値が x
以上なら左に、x
未満なら右に移動する、といったような設定ができる。また葉の値の割り当てはラベルでもいいし確率としての数値を割り当てることもできる。
基本的には情報価値の高い属性から上(根の場所から)に割り当てていくようだ。
特徴的な部分として決定境界の定め方がある。
こんな感じのデータ分布があるとする。
クラス x とクラス △ を分離するような境界を引けと言われたら
上の図のように引くのが多いはず。線形分類器のSVMや線形回帰、ロジスティック回帰とかもこんな風に引く。
決定木の場合はこのような直線では引かず、属性に対して垂直になるように境界線を引く
超平面と言うらしい。理解は難しいが n-1次元で境界が表せると言うことでいいのかな。
線形分類器が任意の直線を引けるのはf(x) = w1*x1 + w2*x2^2 + ...
で境界が定義できることからわかるように一度に全ての属性を使うから表現力の高い直線が引けるけど逆に決定木では一度に一つの属性しか使わないから'y = 10' のような垂直な直線しか引けないわけだ。
決定木の深さに関しては特に制限はないけど深すぎると過適合を起こすっぽい。深すぎるとある一つのデータに適した葉がたくさんできてしまうからかな。それだと訓練データを丸暗記しているのと同じやん。
以上が決定木のメモで、次は噂のXGBoost
完璧には理解できなかったが勾配ブースティング
とランダムフォレスト
を組み合わせたものだそうで。
ランダムフォレスト
は訓練データをn分割して一つ一つを利用し、n個の決定木を作る。そして予測の時にn個の決定木の出力から分類だったら多数決、回帰だったら平均を取った値を予測値とする。アンサンブル学習と考えが似ている。決定木を複数作るから森ってつけたセンスむせび泣くほど好き。
勾配ブースティング
は勾配とついているように勾配降下法などで損失関数の値が小さくなるように重みを更新していくっぽい。つまり木が進化していく。
ここでいう重みは決定木のブランチの値のことであっているかな。
XGBoost
はこの2つを組み合わせたと言うことは複数の木を作って、それぞれを勾配降下法で調節していく決定木と言うことか。中々やるじゃん・・・。
C++でブースティングの方を高速化しているので速いそうで。
評価
そんなこんなで各モデルを作ってテストデータの損失の値を見ていく。
見ればわかる通り二乗誤差での損失の値。二値分類だから本当はLog_lossを用いたかったがエラーが出てしまう。困った・・・。
一応クリッピングして無理やりLog_lossでの値を出すと
二乗誤差ではロジスティック回帰のが損失値は小さかったがLog_lossでは決定木の方が小さくなった。
どっちにしてもモデル構築に必要な特徴量を選べたと言う点では成功したからおけおけおっけ。
ロジスティック回帰は柔軟性に乏しいのでデータセットが少ない時は適していて、逆に決定木は柔軟性が高いので少ないデータだと過適合を起こしやすくデータセットが多い時のが適している特徴があるようだ。
最後に決定木のツリー構造を可視化する。
かなり複雑な木になっていた。これをコンピューターが自動で計算するんだからすごいな。。
Kaggleのデータセットを使って特徴量を観察する その1
最近、特徴量の観察、選択がとても大事だと実感した。
それもあって深層学習の勉強とは別に、データサイエンスの勉強もしようと思ってこの本を読んでいる。
まだ7章ほどだけどもためになることが結構書いてあっていい。自分の言語処理能力が低いのか翻訳が少し難しい気がする。
この本で得た現状の知識を残したいのとインターンでの経験を忘れないようにするため自分で特徴を選択してモデルを作るということをやって見た。
まずは何のデータセットを使うか。成り行きでKaggleのアカウントを作成したのでKaggleにあるダウンロードして良いデータセットを探してくる。
選んだのはキノコの分類のデータセット。食用か毒ありかの2クラス分類のようだ。
https://www.kaggle.com/uciml/mushroom-classification
本によるとデータマイニングは以下のようなサイクルで行われる。
折角なのでこのサイクルの流れに沿ってく
ビジネスの理解
解決すべき問題を理解する。今回このステップでは特に悩む必要もないかな。
あるキノコのデータからそれが食用か、毒ありかを判定する分類問題を解くくらいでいいだろう。
データの理解
データは問題を解決するための材料になる。でもこのステップでは問題に必要なデータ取得にかかるコスト、そこから得られる利益を見積もるなどを考えるステップっぽいので今回は利益として分類モデルを解決することで毒キノコに当たる人が減って(・∀・)イイ!! ということにしよう。
データの準備
分析ができるようにデータを表形式に変換する処理や欠損値を除外する処理などを行うステップ。ここが一番大事なステップ。
データの中から重要な情報価値のある変数や属性を見つけたり選択することがデータマイニングの基本らしい。情報価値のある属性とは予測値をより確実なものにすることができる属性のこと。相関に似ている。
というわけで分析していこう。コードはいつも通りここ
目的は2クラス分類に使えそうな特徴を発見、選ぶこと。
ノートブックを見ればわかる通り、キノコのCSVファイルを読み込むと以下のようなデータセットがあることがわかる。
cap-shapeとかが何を表すのかはこのサイトを参考にすれば理解できる。
https://edu.deepanalytics.jp/datasets/19
最初つぼとかつばとかわからなかった。そういえばつぼ焼きとかありましたね・・・
データセットは全8124件で、そのうち食用が4208件、毒ありが3916件。あまり偏りがなくてよかった。
数値データとして扱いたいので数値データに直しつつ、散布図で相関を見ていく。
cap-shape
,bruises
, odor
, gill-spacing
, stalk-surface-below-ring
, ring-type
, spore-print-color
, population
あたりが使えそうなことがわかったのでこれらにまずは絞ってみることにする。
絞ったのはいいが、選んだ特徴量がどれだけ情報価値を持っているかのわからない。
この問題に対しては情報価値の指標として、エントロピーを用いることができるようだ。
エントロピーといえば最近リリースしたアプリのマスコットキャラクターしか思い浮かばないが、ここでのエントロピーはある属性で絞ったデータの集まりにおいての乱雑さを測る尺度として使われる。乱雑さというのは絞ったデータの集まりにおいて、クラスAとクラスBがどれだけ別れているかの具合。
上の画像だとある属性で分けた結果、ほぼ綺麗にクラスが分けられているのでエントロピーが小さくなる。
数式ではこう定義される
logの底は2。p_i はデータセットに存在するクラスの目的変数(ラベル)が i という値である確率(相対的な割合)。
あるデータセットに10個のデータが存在して、全てラベルが同じなら、 p_i = 10 / 10 = 1 になりこの時、log(p_i)は最小の値 0 となる。
計算したエントロピーから情報価値(IG)は次のように計算する。
元のデータセットを親として、属性で分割したデータセットを子と表すとして、
IG(親、子) = エントロピー(親) - { p(c1)エントロピー(c1)+p(c2)エントロピー(c2)+.. }
ある子セット c_i のエントロピーに、その子セット中のインスタンスの数(割合)に応じた重み付けを行っている。
この重みによって分割したデータセットのデータが1個の場合、重みは小さくなるからIGにはあまり影響を与えなくなるんだな。
これらの式に従い、選んだ各特徴のIGを計算していくと次のようになった
IG値を見る限りcap-shape
、gill-spacing
は特徴として使わなくても良さそうなことがわかった。
またodor
などがかなり分類に対して貢献できそうなこともわかる。
次はcap-shape
、gill-spacing
を除いた選んだ特徴でモデルを構築して見る。
機械学習よりのインターンに行ってきた感想と特徴量について
タイトル通り機械学習よりのインターンに行ってきた。理由は3年生だからそろそろインターンの体験を積みたいというのが一番でかかったりする。
どこのインターンに行ったのかは言えません。東京湾に沈められますからね。
オフィスは綺麗でリラックマのひよこのぬいぐるみがふかふかしてたのが印象に残ってます。おやつが出て、ま○泉というお店の小さいバーガーが個人的にめっちゃくちゃ美味しくて感動した。東京はすごい。
インターン内容
データセットを渡されて何かを出力するモデルを構築して精度を競争しました。
何のデータセット、何を出力するモデルなのかは言えません。NDAもありますが、東京湾にコンクリートで詰められますからね。
モデルの構築、試行錯誤について
最初はデータの観察方法の講義があったので、それを参考にして各データが何を表すのか、散布図を表示して相関はあるのかなどを観察した。
散布図見ても理屈ではなく直感でこれとこれは相関ありそう!!!と判断したりしてた。後はモデルを構築するにおいてデータセットにおけるどの特徴量が一番出力に貢献しているかの確認もしたりした。
あまり識別には関係ないデータを特徴として渡しても次元が増えてモデルが無駄に複雑になるだけな可能性もありうる。いらないと思っていたデータが実は他のデータと組み合わせると精度が向上した、なんてケースもありえそうだし難しそうなとこだ思う。
そんなこんなで使う特徴を選択しつつモデルの構築に取り掛かった。最初はTensorflowを使ってロジスティック回帰のモデルを作った。
結果としては精度は悪かった。多層にしたり、単層にしたり、重み減衰、L2正則化などを追加したりしたり試行錯誤した。
精度が上がらなくてしょんぼりして帰り道を歩いていた時、S○nS○nのCMが店のモニターで流れていた。有名な白いTシャツの人たちがスタイリッシュな動きをするやつ。
実はそこの会社の勉強会の生放送を視聴していて、その時にXGBoost
、Stacking
という単語が出たのをCMを見たその時思い出した。
ニューラルネットのモデルで精度が出ないなら、線形SVMだったりランダムフォレストとやらを使えばいいじゃないかと閃いたので早速XGBoostを使ってモデルを構築した。
そしたらめっちゃ順位が上がった。XGBoostすげえ!!!!!
次に悩むのは過適合についてだった。高い順位に行くためには汎化能力の高さも大事だと思ったからである。
ブログ記事にもしたが自分はSPPnetの論文を読んだ。そこで
「XGBoost,SVM,ロジスティック回帰など複数のモデルからそれぞれ予測値を出して、その平均をとったものを出力として渡したらより精度が向上するかもしれないし、一つのモデルが過適合起こしても平均をとるからいい感じに過適合な予測値を避けることができるんじゃないか!?」
とSPPでのプーリング処理もどきをやってみようと思いついた。
そこでXGBoost
、Elasticnet
,MLP
,単層のロジスティック回帰
この4つのモデルを最終的に使用することにした。
この考えが後の思考を鈍らせるとはこの時の自分は思いもよらなかった・・・
最終順位
結構下でした^^
とても残念。しかし1位の人もXGBoostを使用しているらしかったので
「自分もXGBoost使ってるから実質優勝じゃん」
3秒で開き直りました。
実はXGBoost
単体と複数のモデルの組み合わせではXGBoost
単体のが精度が出ているのはわかっていました^^
しかし過適合を恐れて後者の方を提出してしまった。結果として平均を取った自分の作戦は他のモデルが足を引っ張ったという感じか。
特徴の整形や選択において、形態素解析を行なっている人もいたらしく、何というかモデルの構築って芸術センスがものを言うところあるよな・・・と思った。
振り返り
特徴の観察、抽出、選択の大切さが一番身にしみた。MNISTとか加工せずそのままモデルに渡せばいいデータセットしか使用していなかったため自分で特徴量を観察、抽出という行為をあまりしたことがなかったからである。
モデルのハイパーパラメータをいじるのではなく、もっと特徴量の観察をすればよかった。
あとは機械学習のインターンってメンターからのフィードバックを得るのが難しいと感じた。モデル構築においてこれが正解、というのがないからアドバイスを得難い。。自分からガシガシ質問に行けばよかったのかなという反省もあった。
機械学習と深層学習の違いって個人的には
データからパターンを人が見つけて、モデルを構築したりするのが機械学習
データからパターンを機械が見つけて、モデルを構築するのが深層学習
こんな感じだと判断している。だから機械学習のインターンでは良い特徴量、パターンを見つけることができるかが一番大切だと思った。
深層学習の勉強もいいが、特徴量エンジニアリング、データセットの可視化、観察あたりの知識もつけるべきということに気づけてとてもよかった。
外れ値検出、OneClassSVMについて
統計データにはよく外れ値なるものが含まれていることが多いそう。
外れ値とはデータの分布において他の観測値から大きく外れた値のことで、異常値とやらもあるけどそれとは異なるっぽい
外れ値検出、処理することは機械学習を行う上で学習の妨げをなくす大切な過程だそうで、行う必要があるので試してみた。
一つ目は単純にデータの集合から、第一四分位数、第三四分位数、四分位範囲(IQR)を求めて外れ値を検出する方法を試す。
大体のことはこのサイトを参考にした。
結果としてはこんな感じに
青が元のデータ集合、赤が外れ値の集合
こうしてみると外れ値じゃない範囲がとても狭い・・・
次に scikit-learn というライブラリにあるOneClassSVMを試す
SVMってRCNNの論文とか読んだ限り、クラス分類に使われているイメージなので最低でも2クラス以上で使用するんじゃないとかと思ったのですが調べる限りSVMの性質を使った外れ値のようだ。
実装についてはまずscikit-learnの公式にあるチュートリアルを理解することに。
Outlier detection on a real data set — scikit-learn 0.19.0 documentation
チュートリアルで使用するデータをまずは表示してみる
# Get data X1 = load_boston()['data'][:, [8, 10]] # two clusters X2 = load_boston()['data'][:, [5, 12]] # "banana"-shaped plt.xlim((-8, 28)) plt.ylim((3, 40)) plt.scatter(X1[:, 0], X1[:, 1], color='red') plt.xlim((3, 10)) plt.ylim((-5, 45)) plt.scatter(X2[:, 0], X2[:, 1], color='blue')
目で見てもわかるくらいの外れ値が確認できる
次にOCSVMを行うための下準備
# Define "classifiers" to be used classifiers = {"OCSVM": OneClassSVM(nu=0.261, gamma=0.05)} colors = ['m', 'g', 'b'] legend1 = {} legend2 = {} xx1, yy1 = np.meshgrid(np.linspace(-8, 28, 500), np.linspace(3, 40, 500)) xx2, yy2 = np.meshgrid(np.linspace(3, 10, 500), np.linspace(-5, 45, 500))
下2つのnp.meshgrid
は外れ値を除いたデータの集合を囲むための方眼紙のようなグリッド線を定義しているはず
赤い方のデータセット X1 からOCSVMを試す
for i, (clf_name, clf) in enumerate(classifiers.items()): plt.figure(1) # OCSVMにサンプル集合X1を与え、サンプル集合の境界を検出する clf.fit(X1) # xx1.ravel(), yy1.ravel() : 配列を1次元配列に変換する [[1,2,3],[1,2,3]] -> [1,2,3,4,5,6] # np.c_() : axis=1の配列の値で連結を行う # 各データとの距離が最大となる超平面を求める Z1 = clf.decision_function(np.c_[xx1.ravel(), yy1.ravel()]) Z1 = Z1.reshape(xx1.shape) plt.title("Outlier detection on a real data set (boston housing)") plt.scatter(X1[:, 0], X1[:, 1], color='red') bbox_args = dict(boxstyle="round", fc="0.8") arrow_args = dict(arrowstyle="->") plt.annotate("several confounded points", xy=(24, 19), xycoords="data", textcoords="data", xytext=(13, 10), bbox=bbox_args, arrowprops=arrow_args) plt.scatter(X1[:, 0], X1[:, 1], color='red') # 境界の輪郭を描写する legend1[clf_name] = plt.contour( xx1, yy1, Z1, levels=[0], linewidths=2, colors=colors[i])
OneClassSVM()
にあるfit()
にデータを渡して検出を行っている
plt.contour
にて描いた輪郭は以下のような結果になった
青のデータ X2 についてはこのような結果になった
どっちもデータが密集しているような場所を囲っているから大体学習は成功しているのかな
四分位数で使ったデータに対して同じようにOCSVMを試して見たところこんな感じになった
赤の外れ値をみる限りはいい感じかな囲みがちょっと狭いかも?
外れ値を気にするようなデータを扱ったことがないから実感わかないが、魑魅魍魎跋扈するこの地獄変、外れ値を含む実データなんて腐る程あるんだろうしそんなデータセットを用いてお仕事してる人がたくさんいるんだろう。統計についても勉強すべきだ・・・
SPPnetについてとTensorflowでの畳み込みフィルターの観察
前回の続き
SPPnetを利用した物体検出では入力画像から1回だけ畳み込みの操作を行うことで、大幅な計算時間の削減が可能ということで感動した。
他のサイトの情報や、論文の内容を読み直したりして以下のような手順でSPPnetによる物体検出器を作成しているのかなと考えた。
- 入力画像をCNNに渡して畳み込みの操作まで行い、特徴マップを得る。
- 入力画像からSelectiveSearch法などで、Region Proposal(物体の候補位置、領域)を得る。
- step1で得た特徴マップの対応するRegion Proposal の部分でSPP層の操作を行う。
- 各Region Proposalのプーリング結果を出力層に渡して分類結果を得る。
こんな感じかな。
いざTensorflowで実装するぞと意気込んだけど、step3をどう実装すればいいか詰まった。
入力画像を 224x224 サイズ、特徴マップのサイズを 14x14 とすると入力画像のRegionProposalの座標をどう特徴マップのサイズ内で変換すればいいかわからない。特徴マップのある始点と終点での切り抜きもできるかわかってないけれども。
単純に 224 -> 14 みたいに変換すればいいのかな?特徴マップはフィルター数も多いので悩む。設計するための知識が圧倒的に足りない。。
Fast-RCNNの論文を読み進めてまた悩むことに。
それとは別によく参考書などに畳み込みフィルターなどの小さいマップの画像がよく載っている。
観察してみたかったのでSPPnetでのネットワークで畳み込みフィルターの観察をしてみることにした。
コードはここ
github.com
大体はフィルター適用後の結果を出力してしまった。最後にフィルター自体を出力した。
フィルター自体は
モザイクばかりでエッジなどは見た限りわからない。こんなフィルターでも適用後は
入力画像の形が残っているので不思議だ。。
物体検出の実装を目指す-SPPnetについて
RCNNに続き、次はSPPnetの理解、実装を目指す。
今回も頑張って論文を読んだ。気のせいかRCNNの論文より読みやすかった。アジアの人が書いた論文だからかな。
まずRCNNの問題点として以下が挙げられる。
RCNNはSelectiveSearchなどで得た候補領域の1つ1つをクロッピング、ワーピングの処理を施してCNNの入力に渡すが、クロッピング、ワーピングの処理は望ましくない幾何学的な歪みをもたらす可能性があること。
SelectiveSearchで得る約2000件ほどの候補領域1つ1つをCNNにかけるのでとても時間がかかること。
2つめにおいては、認識に関係ないような候補領域までCNNにかけていたら時間が無駄になってしまう。
SPPnetはこれを改善し、なんと畳み込みの処理を画像全体から一度だけ行うことで計算時間の短縮ができると書いてあった。しゅごい。。。。
特に最大の特徴はSPP(Spatial Pyramid Pooling)と呼ばれるプーリングを実装することで、入力の画像サイズに関わらず固定長の出力を得ることができるので入力が固定ではなく可変サイズに変更できること。
RCNNの論文を読んだ自分からすれば胸が熱くなる内容しか書いてない。素晴らしい。。。。
実装としてはあるCNNの最後のConv層に、SPP層を追加するだけっぽい。
上の方法がRCNNでの手順で、下の方法がSPPnetでの手順。
SPP層で何を行うかというと以下のような処理をする。
畳み込み層からの特徴マップを それぞれ 16x16, 4x4, 1x1 のウィンドウでmaxプーリングを行う。(ウィンドウサイズの設定は条件がある。)
行った結果をreshapeして固定長の出力にして全結合層へ渡すといった形かな。
実装があっているか確信はないけど、SPP層らしきものを作り結果を試してみた。
学習率1e-5,ミニバッチ30枚でのAlexnetで試す。SPP層なしの方では
ミニバッチのせいもあるけど損失値が上がったり下がったり不安定。でもちゃんと学習している。
ありの方では
0~20stepからかなり小さい損失の値を出している。でも200stepくらいではなしの方と値はどっこいといった感じだろうか。ありの方が順調に学習している感じがする。実装が間違っていなければ
このSPP層による物体検出のプログラムを作りたいがいくつかまだ疑問が残っていて、
畳み込みの入力は画像一枚からだが、何を学習したCNNを選べばいいのか
SPP層の操作を行った出力か、それとも元の入力画像をSelectiveSearchにかけるのか。(SPP層の出力で強く発火した元の入力画像の候補領域を全結合層に渡すのか)
プログラムの構築も難しそうだが、まだ理解できていない部分が多い。道のりは長そう。
上記の画像はSPPnetの論文から引用。
論文のリンク