.. _lr-fit: 学習メソッドの実装 ================== それでは,:ref:`lr-optimization` で紹介した :func:`minimize` を用いて,学習メソッド :meth:`fit` を実装します. :func:`minimize` とパラメータをやりとりするために,構造化配列を用いる方法についても紹介します. .. _lr-fit-fit: 学習メソッド ------------ あてはめをおこなう :meth:`fit` メソッドでは,まずデータ数と特徴数を設定しておきます. .. code-block:: python def fit(self, X, y): """ Fitting model """ # constants self.n_samples_ = X.shape[0] self.n_features_ = X.shape[1] そして,最適化関数 :func:`mimimze` で最適なパラメータを求めます. .. index:: minimize .. code-block:: python # optimize res = minimize(fun=self.loss, x0=np.zeros(self.n_features_ + 1, dtype=float), jac=self.grad_loss, args=(X, y), method='CG') :func:`minimize` を呼び出して,ロジスティック回帰モデルをあてはめて,その結果を:class:`OptimizeResult` のインスタンスとして受け取り, :obj:`res` に保持しています. 最適化手法には ``method`` で ``CG`` ,すなわち共役勾配降下法を指定しました. :func:`minimize` の引数 ``fun`` と ``jac`` には,それぞれロジスティック回帰の目的関数とその勾配ベクトル,すなわち :ref:`lr-lr` の式(2)と式(4)を計算するメソッドを与えています. これらのメソッドについては次の :ref:`lr-loss` で詳しく述べます. 最適解を探索する初期パラメータ ``x0`` には :func:`np.zeros` で生成した実数の0ベクトルを与えています. 目的関数のパラメータ配列の大きさは,この初期パラメータの大きさになります. ここでは,重みベクトル :math:`\mathbf{w}` の次元数,すなわち特徴数に,切片パラメータ (intercept) :math:`b` のための ``1`` を加えた数にしています. 目的関数と勾配ベクトルを計算するにはモデルのパラメータの他にも訓練データの情報が必要です. そこで,これらの情報を ``args`` に指定して,目的関数・勾配ベクトルを計算するメソッドに引き渡されるようにしています. 最適化が終わったら, :class:`OptimizeResult` のインスタンスである :obj:`res` の属性 :attr:`x` に格納されているパラメータを取り出します. .. code-block:: python # get result self.coef_ = res.x.view(self._param_dtype)['coef'][0, :].copy() self.intercept_ = res.x.view(self._param_dtype)['intercept'][0] このロジスティック回帰のクラスでは,重みベクトル :math:`\mathbf{w}` と切片 :math:`b` のパラメータを,それぞれ属性 :attr:`coef_` と :attr:`intercept_` に保持します. しかし, これらのパラメータはまとめて1次元配列 :obj:`res.x` に格納されています. そこで,このあとすぐ紹介する :meth:`view` と構造化配列を使って分離する必要があります. なお,ローカル変数である :obj:`res` は :meth:`fit` メソッドの終了時にその内容が失われるので, :meth:`copy` メソッドで配列の実体をコピーしていることに注意して下さい. .. _lr-fit-sarray: .. index:: ! structured array, ndarray; dtype 構造化配列 ---------- 1次元の配列にまとめて格納されている複数のパラメータを分離するために,ここでは構造化配列を利用します. そこで,まずこの構造化配列について紹介します. 構造化配列 (structured array) とは,通常のNumPy配列と次のような違いがあります. * 通常のNumPy配列では要素が全て同じ型でなければならないのに対し,構造化配列では列ごとに型を変更可能 * 文字列による名前で列を参照可能 * 列の要素として配列を指定可能 .. index:: dtype 構造化配列は今まで紹介した :class:`ndarray` とは, :attr:`dtype` 属性の値が異なります. 構造化配列では,列ごとにその要素が異なるので,各列ごとの型の定義をリストとして並べます. ``[(field_name, field_dtype, field_shape), ...]`` ``field_name`` は列を参照するときの名前で,辞書型のキーワードとして利用できる文字列を指定します. ``field_dtype`` はこの列の型で, :ref:`nbayes1-ndarray-access` で紹介したNumPyの型を表すクラス :class:`np.dtype` を指定します. ``field_shape`` は省略可能で,省略したり,単に ``1`` と指定すると通常の配列と同じ0次元配列,すなわちスカラーになります [#]_ . 2以上の整数を指定すると,指定した大きさの1次元配列が,整数のタプルを指定すると, このタプルが :attr:`shape` 属性の値であるような :class:`ndarray` がその列の要素になります. それでは,実際に構造化配列を生成してみます [#]_ . .. code-block:: ipython In [1]: a = np.array( ...: [('red', 0.2, (255, 0, 0)), ...: ('yellow', 0.5, (255, 255, 0)), ...: ('green', 0.8, (0, 255, 0))], ...: dtype=[('label', 'U10'), ('state', float), ('color', int, 3) ]) :func:`np.array` を用いて構造化配列を生成しています. 最初の引数は配列の内容で,各行の内容を記述したタプルのリストで表します. 配列の型を :attr:`dtype` 属性で指定しています. 最初の列は名前が ``label`` で,その型は長さ10の文字列です. 次の列 ``state`` はスカラーの実数,そして最後の列 ``color`` は大きさ3の1次元の整数型配列です. 次は,生成した構造化配列の内容を参照します. 型を指定した時の列の名前 ``field_name`` の文字列を使って,構造化配列 :obj:`a` の列は ``a[field_name]`` の記述で参照できます. それでは,上記の構造化配列 :obj:`a` の要素を参照してみます. .. code-block:: ipython In [2]: a['label'] Out[2]: array(['red', 'yellow', 'green'], dtype='`_ の項目を参照して下さい. .. _lr-fit-implementation: 構造化配列を用いた実装 ---------------------- それでは,この構造化配列を使って,ロジスティック回帰のパラメータを表してみます. :meth:`fit` メソッドで, 最適化を実行する前に,次のように実装しました. .. code-block:: python # dtype for model parameters to optimize self._param_dtype = np.dtype([ ('coef', float, self.n_features_), ('intercept', float) ]) 第1列目の ``coef`` は重みベクトル :math:`\mathbf{w}` を表すものです. 1次元で大きさが特徴数 :attr:`n_features_` に等しい実数ベクトルとして定義しています. 第2列目の ``intercept`` は切片 :math:`b` に相当し,スカラーの実数値としています. この構造化配列の型を :class:`dtype` クラスのインスタンスとしてロジスティック回帰クラスの属性 :attr:`_param_dtype` 保持しておきます. .. class:: np.dtype Create a data type object. :ivar obj: Object to be converted to a data type object. それでは, :func:`minimize` の結果を格納した :obj:`res.x` から,構造化配列を使ってパラメータを分離する次のコードをもう一度見てみましょう. .. index:: ndarray; view .. code-block:: python # get result self.coef_ = res.x.view(self._param_dtype)['coef'][0, :].copy() self.intercept_ = res.x.view(self._param_dtype)['intercept'][0] :meth:`view` は,配列自体は変更や複製をすることなく,異なる型の配列として参照するメソッドです. C言語などの共用体と同様の動作をします. :obj:`res.x` は大きさが ``n_features_ + 1`` の実数配列ですが,重みベクトルと切片のパラメータをまとめた :attr:`_param_dtype` 型の構造化配列として参照できます. :attr:`_param_dtype` 型では,列 ``coef`` は大きさが ``n_features_`` の1次元配列です. よって, ``res.x.view(self._param_dtype)['coef']`` によって :attr:`shape` が ``(1, n_features_)`` の配列を得ることができます. その後の ``[0, :]`` によって,この配列の1行目の内容を参照し,これを重みベクトルとして取り出しています. もう一方の列 ``intercept`` はスカラーの実数なので, ``res.x.view(self._param_dtype)['intercept']`` と記述することで,大きさが1の1次元実数配列を参照できます. この配列の最初の要素を参照し,これを切片として取り出しています. 以上で, :ref:`lr-lr` の式(3)を解いて,得られた重みベクトル :math:`\mathbf{w}` と切片 :math:`b` を,ロジスティック回帰の属性 :attr:`coef_` と :attr:`intercept_` とにそれぞれ格納することができました. 次の :ref:`lr-loss` では, :func:`minimize` に ``fun`` と ``jac`` の引数として引き渡す損失関数とその勾配を実装します.