ニューラルネットワークのハッカーガイド。 第2章:機械学習。 サポートベクターネットワーク(SVM)トレーニング

内容:

第1章:実際の価値の図
パート1:

  :        №1:   
      
      





パート2:

   №2:  
      
      





パート3:

   №3:  
      
      





パート4:

         
      
      





パート5:

    «»   " "
      
      





パート6:

      
      
      







第2章:機械学習
パート7:

   
      
      





パート8:

         (SVM)
      
      





パート9:

   SVM   
      
      





パート10:

    :  
      
      









具体的な例として、SVMを見てみましょう。 SVMは非常に一般的な線形分類器です。 その関数形式は、前のセクションで説明したものとまったく同じ形式です-f(x、y)= ax + by + c 。 この段階で、SVMの説明を見たなら、おそらくSVMの損失関数を決定し、自由変数、大きなフィールドの幾何学的概念、カーネル、双対性などの説明に飛び込むことを期待するでしょう。しかし、ここでは別のアプローチを使用したいと思います。



損失関数を定義する代わりに、サポートベクターメソッドの力の特性(ちなみに、この用語を作り出したばかり)に基づいて説明したいと思います。 後で見るように、力の特性と損失の関数は同じ問題を解決するための同じ方法です。 一般的に、次のようになります。



強度の特性 」:

正のデータエントリポイントをSVM回路に渡し、出力値が1未満の場合、+ 1の力で回路をプルする必要があります。 これは良い例ですので、もっと高い値が必要です。



一方、負のデータエントリポイントをSVMに渡し、出力値が-1より大きい場合、回路はこのデータエントリポイントに危険なほど高い値を与えます。回路を-1の力で引き下げる必要があります。



プルアップに加えて、パラメータa、bに少しテンションを加える必要があります(cに注意を払ってください!)。これにより、ゼロの方向に引っ張られます。 a、bが物理的なスプリングに結び付けられ、ゼロに結び付けられていることが想像できます。 物理的なバネの場合と同様に、これにより張力が各要素a、bの値に比例します(物理学におけるフックの法則、誰もが知っていますか?)。 たとえば、aの値が大きすぎると、| a |で強い緊張が生じます。 ゼロに戻ります。 この緊張は正則化と呼ばれるものであり、パラメーターaまたはbのいずれも不均衡に大きくならないようにします。 これは、両方のパラメーターa、bに入力特性x、yが乗算されるため(望ましくない可能性があります(方程式の形式はa * x + b * y + cであることを思い出してください))。 、分類器はこれらのパラメータに関して敏感すぎます。 特性は実際には不正確であることが多いため、これはあまり良いプロパティではありません。したがって、分類器が横に振れた場合、比較的スムーズに分類器を変更する必要があります。



小さくても具体的な例を簡単に見てみましょう。 a = 1、b = -2、c = -1などの任意のパラメーターを設定することから始めたとします。 次に、ポイント[1.2、0.7]をフィードすると、SVMは値1 * 1.2 +(-2)* 0.7-1 = -1.2を計算します。 このポイントは、トレーニングデータで+1としてマークされているため、値を1より大きくする必要があります。回路の上部の勾配は正になり、+ 1、エラーをa、b、cに伝播します。 さらに、ゼロの方向に、力を-1(小さくするため)の正則化張力と、力を大きくするために+2の正則化張力があります。



代わりに、データエントリポイント[-0.3、0.5]をSVMにフィードするとします。 1 *(-0.3)+(-2)* 0.5-1 = -2.3を計算します。 このポイントのラベルの値は-1であり、-2.3が-1未満であるため、強度特性に従ってSVMは満足すべきであることがわかります:計算された値は非常に負になり、この例の負のラベルに対応します。 変更の必要がないため、回路の終わりに張力はありません(つまり、ゼロになります)。 ただし、aには-1の力で、bには+2の力で正則化張力があります。



一般に、テキストが多すぎます。 SVMコードを記述して、第1章で説明したスキーマメカニズムを活用しましょう。



 //   5  (x,y,a,b,c),     //            var Circuit = function() { //     this.mulg0 = new multiplyGate(); this.mulg1 = new multiplyGate(); this.addg0 = new addGate(); this.addg1 = new addGate(); }; Circuit.prototype = { forward: function(x,y,a,b,c) { this.ax = this.mulg0.forward(a, x); // a*x this.by = this.mulg1.forward(b, y); // b*y this.axpby = this.addg0.forward(this.ax, this.by); // a*x + b*y this.axpbypc = this.addg1.forward(this.axpby, c); // a*x + b*y + c return this.axpbypc; }, backward: function(gradient_top) { //    this.axpbypc.grad = gradient_top; this.addg1.backward(); //    axpby  c this.addg0.backward(); //    ax  by this.mulg1.backward(); //    b  y this.mulg0.backward(); //    a  x } }
      
      







これは、a * x + b * y + cを計算するだけで、勾配も計算できる回路です。 第1章で作成したロジックコードを使用します。それでは、実際の回路に依存しないSVMを作成しましょう。 彼女にとって、彼女から出てくる価値だけが重要であり、彼女は回路を引き上げます。



 // SVM  var SVM = function() { //     this.a = new Unit(1.0, 0.0); this.b = new Unit(-2.0, 0.0); this.c = new Unit(-1.0, 0.0); this.circuit = new Circuit(); }; SVM.prototype = { forward: function(x, y) { // ,  x  y   this.unit_out = this.circuit.forward(x, y, this.a, this.b, this.c); return this.unit_out; }, backward: function(label) { //   +1  -1 //    a,b,c this.a.grad = 0.0; this.b.grad = 0.0; this.c.grad = 0.0; //     ,     var pull = 0.0; if(label === 1 && this.unit_out.value < 1) { pull = 1; //    :   } if(label === -1 && this.unit_out.value > -1) { pull = -1; //       ,   } this.circuit.backward(pull); //    x,y,a,b,c //     :       this.a.grad += -this.a.value; this.b.grad += -this.b.value; }, learnFrom: function(x, y, label) { this.forward(x, y); //   ( .value   ) this.backward(label); //   ( .grad   ) this.parameterUpdate(); //     }, parameterUpdate: function() { var step_size = 0.01; this.a.value += step_size * this.a.grad; this.b.value += step_size * this.b.grad; this.c.value += step_size * this.c.grad; } };
      
      







次に、確率的勾配の降下でSVMを使用しましょう。

 var data = []; var labels = []; data.push([1.2, 0.7]); labels.push(1); data.push([-0.3, -0.5]); labels.push(-1); data.push([3.0, 0.1]); labels.push(1); data.push([-0.1, -1.0]); labels.push(-1); data.push([-1.0, 1.1]); labels.push(-1); data.push([2.1, -3]); labels.push(1); var svm = new SVM(); // ,     var evalTrainingAccuracy = function() { var num_correct = 0; for(var i = 0; i < data.length; i++) { var x = new Unit(data[i][0], 0.0); var y = new Unit(data[i][1], 0.0); var true_label = labels[i]; // ,       var predicted_label = svm.forward(x, y).value > 0 ? 1 : -1; if(predicted_label === true_label) { num_correct++; } } return num_correct / data.length; }; //   for(var iter = 0; iter < 400; iter++) { //      var i = Math.floor(Math.random() * data.length); var x = new Unit(data[i][0], 0.0); var y = new Unit(data[i][1], 0.0); var label = labels[i]; svm.learnFrom(x, y, label); if(iter % 25 == 0) { //  10 ... console.log('training accuracy at iter ' + iter + ': ' + evalTrainingAccuracy()); } }
      
      







このコードは次の結果を出力します。



 training accuracy at iteration 0: 0.3333333333333333 training accuracy at iteration 25: 0.3333333333333333 training accuracy at iteration 50: 0.5 training accuracy at iteration 75: 0.5 training accuracy at iteration 100: 0.3333333333333333 training accuracy at iteration 125: 0.5 training accuracy at iteration 150: 0.5 training accuracy at iteration 175: 0.5 training accuracy at iteration 200: 0.5 training accuracy at iteration 225: 0.6666666666666666 training accuracy at iteration 250: 0.6666666666666666 training accuracy at iteration 275: 0.8333333333333334 training accuracy at iteration 300: 1 training accuracy at iteration 325: 1 training accuracy at iteration 350: 1 training accuracy at iteration 375: 1
      
      







最初、分類器のトレーニング精度は33%でしたが、トレーニングの終わりまでに、例はパラメーターa、b、cとして正しく分類され、適用される張力に応じて値が調整されます。 SVMをトレーニングしました! しかし、実際にはこのコードを使用しないでください:)実際に何が起こるかを理解するとき、すべてがより効率的に行われる方法を確認します。



必要な繰り返しの数。 この例のデータ、この例の初期化、および使用するステップサイズの設定では、SVMのトレーニングに300回の繰り返しが必要でした。 実際には、問題の複雑さや大きさ、初期化方法、データの正規化、使用するステップサイズなどに応じて、はるかに大きくまたは小さくなります。 これはモデルの例ですが、後でこれらの分類器の実際のトレーニングに最適な方法をすべて実際に使用します。 たとえば、ステップサイズの設定は非常に重要かつ複雑であることがわかります。 ステップサイズが小さいと、モデルの学習が遅くなります。 ステップサイズを大きくすると、より速く学習できますが、大きすぎると分類器がランダムにジャンプし、最終的な結果が得られません。 最終的には、データの最も有利な位置をとるために、より詳細な調整のために遅延データ検証を使用します。



評価してほしい点の1つは、この例で使用した線形予測関数だけでなく、スキームを任意の式にできることです。 たとえば、ニューラルネットワーク全体にすることができます。



ところで、私は意図的にコードをモジュール方式で構造化しましたが、はるかに単純なコードでSVMを実証することができました。 これが、これらのクラスと計算のすべてが実際に行うことです:



 var a = 1, b = -2, c = -1; //   for(var iter = 0; iter < 400; iter++) { //      var i = Math.floor(Math.random() * data.length); var x = data[i][0]; var y = data[i][1]; var label = labels[i]; //   var score = a*x + b*y + c; var pull = 0.0; if(label === 1 && score < 1) pull = 1; if(label === -1 && score > -1) pull = -1; //      var step_size = 0.01; a += step_size * (x * pull - a); // -a     b += step_size * (y * pull - b); // -b     c += step_size * (1 * pull); }
      
      







このコードはまったく同じ結果を生成します。 おそらく現時点では、コードを見て、これらの方程式がどのようになっているかを理解することができます。



これについていくつかメモをしましょう。テンションが常に1.0または-1であることに気付いたかもしれません。 他のオプション、たとえば、エラーがどれほど深刻かに比例した緊張を想像できます。 これはSVMのバリエーションにつながります。一部の人々は、これを正方形の区分的線形SVM損失関数と呼びます。理由は少し後でわかります。 データセットのさまざまな特性に応じて、機能が向上する場合と低下する場合があります。 たとえば、+ 100の値を持つ負のデータエントリポイントなど、データのパフォーマンスが非常に低い場合、分類器への影響は比較的重要ではありません。間違い。 実際には、分類器のこのプロパティはインジケーターに対する抵抗として認識されます。



簡単に繰り返しましょう。 N個のD次元ベクトルとそれぞれにラベル+ 1 / -1が与えられるバイナリ分類問題を導入しました。 これらの特性を、実際の値を持つ回路内の一連のパラメーターと組み合わせることができることがわかりました(例のように、Machine of support vectorの回路)。 その後、データを繰り返し回路に渡し、回路の出力値が指定されたラベルと一致するようにパラメーターを変更するたびに繰り返します。 変化は、特に重要であるが、回路を通じて勾配誤差を逆伝播する能力に依存します。 最終的に、最終設計は未知の例の値を予測するために使用できます!



All Articles