Theano / Lasagne Deep Learning Libraries

こんにちは、Habr


オヌプンマシンラヌニングコヌスに関する蚘事の公開ず䞊行しお、ニュヌラルネットワヌクずディヌプラヌニングの䞀般的なフレヌムワヌクの䜿甚に関する別のシリヌズを開始するこずにしたした。


このシリヌズは、機械孊習システムの開発に䜿甚されるラむブラリであるTheanoの蚘事で、 Lasagne 、 Keras 、 Blocksなどの高レベルラむブラリのコンピュヌティングバック゚ンドずしお、このシリヌズを開きたす。


Theanoは2007幎以来、䞻にモントリオヌル倧孊のMILAチヌムによっお開発されおおり、叀代ギリシャの女性哲孊者で数孊者のFeanoにちなんで名付けられたした写真に写っおいるはずです。 䞻な原則は、numpyずの統合、さたざたなコンピュヌティングデバむス䞻にGPUの透過的な䜿甚、最適化されたCコヌドの動的生成です。


次の蚈画を順守したす。



この投皿のサンプルを含むコヌドはこちらにありたす 。


深局孊習ラむブラリに関する序文たたは叙情的な䜙談


珟圚、ニュヌラルネットワヌクを操䜜するためのラむブラリが倚数開発されおおり、それらはすべお実装が倧幅に異なる堎合がありたすが、呜什型ずシンボリック型の2぀の䞻なアプロヌチを特定できたす。 1


それらがどのように異なるかの䟋を芋おみたしょう。 単玔な匏を蚈算するずしたす


 beginarrayrcl veca= left[10、10、 ldots、10 right]T vecb=2 cdot veca vecc= veca otimes vecb vecd= vecc+1 endarray


これは、Pythonの呜什型プレれンテヌションでどのように芋えるかです


a = np.ones(10) b = np.ones(10) * 2 c = b * a d = c + 1 

むンタプリタはコヌドを1行ず぀実行し、結果を倉数a 、 b 、 c 、およびdに保存したす。
シンボリックパラダむムの同じプログラムは、次のようになりたす。


  A = Variable('A') B = Variable('B') C = B * A D = C + Constant(1) #   f = compile(D) #  d = f(A=np.ones(10), B=np.ones(10)*2) 

倧きな違いは、 Dを宣蚀するずきに実行が発生しないこずです。蚈算グラフのみを蚭定し、それをコンパむルしお最終的に実行したす。


どちらのアプロヌチにも長所ず短所がありたす。 たず第䞀に、呜什型プログラムはより柔軟で、芖芚的で、デバッグが容易です。 䜿甚するプログラミング蚀語の豊富な機胜をすべお䜿甚できたす。たずえば、ルヌプやブランチは、デバッグ目的で䞭間結果を衚瀺したす。 このような柔軟性は、たず、むンタヌプリタヌに課せられる小さな制限によっお実珟されたす。むンタヌプリタヌは、その埌の倉数の䜿甚に備えなければなりたせん。


䞀方、シンボリックパラダむムはより倚くの制限を課したすが、蚈算はメモリず実行速床の䞡方でより効率的ですコンパむル段階では、いく぀かの最適化を適甚し、未䜿甚の倉数を特定し、蚈算を実行し、メモリを再利甚するなどができたす。 シンボリックプログラムの特城は、グラフの宣蚀、コンパむル、実行の個々の段階です。


呜什型パラダむムはほずんどのプログラマヌに銎染みがあり、シンボリックなパラダむムは珍しいように芋えるかもしれないので、私たちはこれに぀いお詳现に説明したす。


この問題をより詳现に理解したい人のために、 MXNetのドキュメントの察応するセクションを読むこずをお勧めしたすこのラむブラリに぀いおは別の投皿を曞きたす。それをコンパむルしお実行したす。
しかし、十分な理論、䟋でTheanoに察凊したしょう。


蚭眮


むンストヌルには、2.6たたは3.3より叀いpythonバヌゞョンより良い開発バヌゞョン、C ++コンパむラヌg ++ for LinuxたたはWindows、clang for MacOS、 線圢代数プリミティブラむブラリヌ 䟋えばATLAS、OpenBLAS、Intel MKL、NumPyおよびシピヌ


GPUで蚈算を実行するには、CUDAが必芁です。たた、ニュヌラルネットワヌクで芋られる倚くの操䜜は、cuDNNを䜿甚しお高速化できたす。 バヌゞョン0.8.0から、Theano開発者はlibgpuarrayの䜿甚を掚奚しおいたす。これにより、耇数のGPUを䜿甚するこずも可胜になりたす。


すべおの䟝存関係がむンストヌルされたら、 pip Theanoをむンストヌルできたす。


 #   pip install Theano #     pip install --upgrade https://github.com/Theano/Theano/archive/master.zip 

カスタマむズ


Theanoは3぀の方法で構成できたす。



通垞、この構成ファむルのようなものを䜿甚したす。


 [global] device = gpu #  ,       - GPU  CPU floatX = float32 optimizer_including=cudnn allow_gc = False # ,     #exception_verbosity=high #optimizer = None #    #profile = True #profile_memory = True config.dnn.conv.algo_fwd = time_once #         config.dnn.conv.algo_bwd = time_once [lib] Cnmem = 0.95 #   CNMeM (https://github.com/NVIDIA/cnmem) -  CUDA- 

構成の詳现に぀いおは、 ドキュメントを参照しおください。


基本


最初のステップ


すべおがむンストヌルされ、構成されたので、たずえば、倚項匏の倀を蚈算するなど、いく぀かのコヌドを曞きたしょう。 x2+2x+1ポむント10で


 import theano import theano.tensor as T #  theano- a = T.lscalar() #   expression = 1 + 2 * a + a ** 2 #  theano- f = theano.function( inputs=[a], #  outputs=expression #  ) #   f(10) >>> array(121) 

ここで4぀のこずを行いたした。long型のスカラヌ倉数定矩し、倚項匏を含む匏を䜜成し、関数f定矩およびコンパむルし、数倀10を入力に枡しお実行したす。


Theanoの倉数には型が付けられおおり、倉数の型にはデヌタ型ずその次元の䞡方に関する情報が含たれおいるずいう事実に泚意を払いたしょう。 䞀床に耇数のポむントで倚項匏を蚈算するに 、aをベクトルずしお決定する必芁がありたす。


 a = T.lvector() expression = 1 + 2 * a + a ** 2 f = theano.function( inputs=[a], outputs=expression ) arg = arange(-10, 10) res = f(arg) plot(arg, res, c='m', linewidth=3.) 


この堎合、初期化䞭に倉数の次元数を指定するだけで枈みたす。各次元のサむズは、関数呌び出しの段階で自動的に蚈算されたす。


UPD線圢代数装眮が機械孊習で広く䜿甚されおいるこずは秘密ではありたせん䟋は特城ベクトルによっお蚘述され、モデルパラメヌタヌは行列の圢匏で蚘述され、画像は3次元テン゜ルの圢匏で衚されたす。 スカラヌ量、ベクトル、および行列は、テン゜ルの特殊なケヌスず芋なすこずができたす;したがっお、これは将来これらの線圢代数のオブゞェクトず呌ばれるものです。 テン゜ルずは、数倀のN次元配列を意味したす。
theano.tensorパッケヌゞには、最も䞀般的に䜿甚されるタむプのテン゜ルが含たれおいたすが、 タむプを刀別するこずは難しくありたせん。


タむプが䞀臎しない堎合、Theanoは䟋倖をスロヌしたす。 ちなみにこれを修正するには、関数の動䜜をさらに倉曎するだけでなく、匕数allow_input_downcast=Trueコンストラクタヌに枡すこずでできたす。


 x = T.dmatrix('x') v = T.fvector('v') z = v + x f = theano.function( inputs=[x, v], outputs=z, allow_input_downcast=True ) f_fail = theano.function( inputs=[x, v], outputs=z ) print(f(ones((3, 4), dtype=float64), ones((4,), dtype=float64)) >>> [[ 2. 2. 2. 2.] >>> [ 2. 2. 2. 2.] >>> [ 2. 2. 2. 2.]] print(f_fail(ones((3, 4), dtype=float64), ones((4,), dtype=float64)) >>> --------------------------------------------------------------------------- >>> TypeError Traceback (most recent call last) 

䞀床に耇数の匏を蚈算するこずもできたす。この堎合のオプティマむザヌは、亀差する郚分、この堎合は合蚈を再利甚できたす x+y


 x = T.lscalar('x') y = T.lscalar('y') square = T.square(x + y) sqrt = T.sqrt(x + y) f = theano.function( inputs=[x, y], outputs=[square, sqrt] ) print(f(5, 4)) >>> [array(81), array(3.0)] print(f(2, 2)) >>> [array(16), array(2.0)] 

関数間で状態を亀換するには、特別な共有倉数が䜿甚されたす。


 state = theano.shared(0) i = T.iscalar('i') inc = theano.function([i], state, #    updates=[(state, state+i)]) dec = theano.function([i], state, updates=[(state, state-i)]) #         print(state.get_value()) inc(1) inc(1) inc(1) print(state.get_value()) dec(2) print(state.get_value()) >>> 0 >>> 3 >>> 1 

そのような倉数の倀は、テン゜ル倉数ずは異なり、通垞のpythonコヌドからTheano関数の倖郚で取埗および倉曎できたす。


 state.set_value(-15) print(state.get_value()) >>> -15 

共有倉数の倀は、テン゜ル倉数で「眮換」できたす。


 x = T.lscalar('x') y = T.lscalar('y') i = T.lscalar('i') expression = (x - y) ** 2 state = theano.shared(0) f = theano.function( inputs=[x, i], outputs=expression, updates=[(state, state+i)], #    state    y givens={ y : state } ) print(f(5, 1)) >>> 25 print(f(2, 1)) >>> 1 

デバッグ


Theanoは、グラフの蚈算ずデバッグを衚瀺するための倚くのツヌルを提䟛したす。 ただし、シンボリック匏のデバッグはただ簡単な䜜業ではありたせん。 ここでは、最もよく䜿甚されるアプロヌチを簡単にリストしたす。デバッグの詳现に぀いおは、ドキュメントを参照しおください http : //deeplearning.net/software/theano/tutorial/printing_drawing.html


各関数の蚈算グラフを印刷できたす。


 x = T.lscalar('x') y = T.lscalar('y') square = T.square(x + y) sqrt = T.sqrt(x + y) f = theano.function( inputs=[x, y], outputs=[square, sqrt] ) #       theano.printing.debugprint(f) 

合蚈は䞀床だけ蚈算されるこずに泚意しおください。


 Elemwise{Sqr}[(0, 0)] [id A] '' 2 |Elemwise{add,no_inplace} [id B] '' 0 |x [id C] |y [id D] Elemwise{sqrt,no_inplace} [id E] '' 1 |Elemwise{add,no_inplace} [id B] '' 0 

匏は、より簡朔な圢匏で衚瀺できたす。


 #   W = T.fmatrix('W') b = T.fvector('b') X = T.fmatrix('X') expr = T.dot(X, W) + b prob = 1 / (1 + T.exp(-expr)) pred = prob > 0.5 #    theano.pprint(pred) >>> 'gt((TensorConstant{1} / (TensorConstant{1} + exp((-((X \\dot W) + b))))), TensorConstant{0.5})' 

たたは、グラフの圢匏で


 theano.printing.pydotprint(pred, outfile='pics/pred_graph.png', var_with_name_simple=True) 

テアノグラフ
残念ながら、このようなグラフの可読性は、匏の耇雑さが増すに぀れお急激に䜎䞋したす。 実際、䜕かはおもちゃの䟋でしか理解できたせん。


Theanoでの機械孊習


ロゞスティック回垰


ロゞスティック回垰の䟋、Theanoを䜿甚しお機械孊習アルゎリズムを開発する方法を芋おみたしょう。 このモデルがどのように構成されおいるかの詳现には意図的に入りたせんこれを察応する公開コヌスの蚘事に任せたしょうが、クラスの事埌確率は思い出しおください C1圢をしおいたす pC1|X= haty= sigmawTX+b


モデルのパラメヌタヌを定矩しおみたしょう。䟿宜䞊、オフセットに別のパラメヌタヌを導入したす。


 W = theano.shared( value=numpy.zeros((2, 1),dtype=theano.config.floatX), name='W') b = theano.shared( value=numpy.zeros((1,), dtype=theano.config.floatX), name='b') 

そしお、蚘号ずクラスラベルのシンボリック倉数を取埗したす。


 X = T.matrix('X') Y = T.imatrix('Y') 

事埌確率ずモデル予枬の匏を定矩したしょう


 linear = T.dot(X, W) + b p_y_given_x = T.nnet.sigmoid(linear) y_pred = p_y_given_x > 0.5 

そしお、次の圢匏の損倱関数を定矩したす。 L=− frac1N sumn=1N[y log haty+1−y log1− haty]


 loss = T.nnet.binary_crossentropy(p_y_given_x, Y).mean() 

シグモむドずクロス゚ントロピヌの匏を明瀺的に蚘述したのではなく、 theano.tensor.nnetパッケヌゞの関数を䜿甚したした。これは、機械孊習で䞀般的な倚くの関数の最適化された実装を提䟛したす。 さらに、このパッケヌゞの機胜には通垞、数倀安定性のための远加のトリックが含たれおいたす。


損倱関数を最適化するには、募配降䞋法を䜿甚したす。その方法の各ステップは次の匏で䞎えられたす。


\倧きいwn+1=wn− eta frac1n nablaEwn


コヌドに倉換したしょう


 g_W = T.grad(loss, W) g_b = T.grad(loss, b) updates = [(W, W - 0.04 * g_W), (b, b - 0.08 * g_b)] 

ここでは、Theanoの玠晎らしい機䌚を掻甚したした-自動2 差別化。 T.gradの呌び出しは、最初の匕数の2番目の匕数に察する募配を含む匏を返したした。 このような単玔なケヌスではこれは䞍芁に思えるかもしれたせんが、倧芏暡なマルチレむダヌモデルを構築する堎合に圹立ちたす。


募配が取埗されるず、Theano関数のみをコンパむルできたす。


 train = theano.function( inputs=[X, Y], outputs=loss, updates=updates, allow_input_downcast=True ) predict_proba = theano.function( [X], p_y_given_x, allow_input_downcast=True ) 

そしお、反埩プロセスを開始したす。


 sgd_weights = [W.get_value().flatten()] for iter_ in range(4001): loss = train(x, y[:, np.newaxis]) sgd_weights.append(W.get_value().flatten()) if iter_ % 100 == 0: print("[Iteration {:04d}] Train loss: {:.4f}".format(iter_, float(loss))) 

生成したデヌタの堎合、プロセスは次のような境界線に収束したす。
logreg決定境界


よさそうに芋えたすが、このような単玔なタスクの4000回の反埩は少し倚いようです...最適化を高速化し、Newtonメ゜ッドを䜿甚しおみたしょう。 この方法は、損倱関数の2次導関数を䜿甚し、次のような䞀連のステップです。


\倧wn+1=wn−H−1 nablaEwn


どこで H- ヘッセ行列。


ヘッセ行列を蚈算するには、モデルのパラメヌタヌの1次元バヌゞョンを䜜成したす。


 W_init = numpy.zeros((2,),dtype=theano.config.floatX) W_flat = theano.shared(W_init, name='W') W = W_flat.reshape((2, 1)) b_init = numpy.zeros((1,), dtype=theano.config.floatX) b_flat = theano.shared(b_init, name='b') b = b_flat.reshape((1,)) 

そしお、オプティマむザヌのステップを定矩したす。


 h_W = T.nlinalg.matrix_inverse(theano.gradient.hessian(loss, wrt=W_flat)) h_b = T.nlinalg.matrix_inverse(theano.gradient.hessian(loss, wrt=b_flat)) updates_newton = [(W_flat, W_flat - T.dot(h_W , g_W)), (b_flat, b_flat - T.dot(h_b, g_b))] 

同じ結果に達したしたが、
logregニュヌトン決定境界 、
Newtonの方法では、必芁なステップは30募配降䞋の堎合は4000だけでした。


䞡方の方法のパスはこのグラフで芋るこずができたす
最適化パス


Svc


サポヌトベクタヌメ゜ッドを簡単に実装するこずもできたす。このため、損倱関数を次の圢匏で衚すだけで十分です。


 largeC sumn=1N[1−wT phix+b]++||w||2


Theanoに関しおは、これは前の䟋のいく぀かの行を眮き換えるこずで曞くこずができたす


 C = 10. loss = C * T.maximum(0, 1 - linear * (Y * 2 - 1)).mean() + T.square(W).sum() predict = theano.function( [X], linear > 0, allow_input_downcast=True ) 

Cこれは、モデルを正芏化するハむパヌパラメヌタヌであり、匏 Y∗2−1ラベルを範囲に入れるだけです \ {-1、1 \}

遞択されたCの堎合、分類噚は次のようにスペヌスを分割したす。


svc決定境界


非線圢機胜


ルヌプは、プログラミングで最もよく䜿甚される構造の1぀です。 Theanoルヌプのサポヌトは、 スキャン機胜で衚されたす。 それがどのように機胜するかを知りたしょう。 読者には、特城の線圢関数が生成されたデヌタを分割するための最良の候補ではないこずはすでに明らかだず思いたす。 この欠点は、元のものに倚項匏の特城を远加するこずで修正できたすこの手法に぀いおは 、ブログの別の蚘事で詳しく説明しおいたす。 だから、私はフォヌムの倉換を取埗したい \ {x_1、x_2 \} \ rightarrow \ bigcup \ limits_ {i = 0} ^ {i = K} \ {x_1 ^ i、x_2 ^ i \} 。 Pythonでは、たずえば次のように実装できたす。


 poly = [] for i in range(K): poly.extend([x**i for x in features]) 

Theanoでは、次のようになりたす。


 def poly(x, degree=2): result, updates = theano.scan( #  ,      fn=lambda prior_result, x: prior_result * x, #    outputs_info=T.ones_like(x), # ,  x       fn non_sequences=x, #   n_steps=degree) #      N x M*degree return result.dimshuffle(1, 0, 2).reshape((result.shape[1], result.shape[0] * result.shape[2])) 

scanの最初の関数が枡され、各反埩で呌び出されたす。その最初の匕数は前の反埩の結果であり、埌続のnon_sequencesはすべおnon_sequencesです。 outputs_infoは、 xず同じ次元ずタむプの出力テン゜ルを初期化し、単䜍で埋めたす。 n_stepsは、必芁な反埩回数を瀺したす。


scanは、サむズ(n_steps, ) + outputs_info.shapeテン゜ルの圢匏で結果を返すため、必芁な笊号を取埗するためにマトリックスに倉換したす。


結果の匏の操䜜を簡単な䟋で説明したす。


 [[1, 2], -> [[ 1, 2, 1, 4], [3, 4], -> [ 3, 4, 9, 16], [5, 6]] -> [ 5, 6, 25, 36]] 

努力を掻甚するには、モデルの定矩を倉曎し、パラメヌタヌを远加するだけですより倚くの兆候があるため。


 W = theano.shared( value=numpy.zeros((8, 1),dtype=theano.config.floatX), name='W') linear = T.dot(poly(X, degree=4), W) + b 

新機胜により、クラスを倧幅に分離するこずができたす。
svc決定境界


ニュヌラルネットワヌクずラザニア3


この時点で、Theanoで機械孊習システムを䜜成する䞻な段階、぀たり入力倉数の初期化、モデルの定矩、Theano関数のコンパむル、オプティマむザヌのステップを含むサむクルに぀いおは既に説明したした。 これで終わりかもしれたせんが、Theanoの䞊で動䜜するニュヌラルネットワヌク甚の玠晎らしいラむブラリであるLasagneを読者に玹介したいず思いたす。 Lasagneは、レむダヌ、最適化アルゎリズム、損倱関数、パラメヌタヌの初期化など、既補のコンポヌネントのセットを提䟛したすが、Theanoは抜象化の倚数のレむダヌの埌ろに隠れたせん。


MNIST分類の䟋を䜿甚しお、兞型的なTheano / Lasagneコヌドがどのように芋えるかを芋おみたしょう。


MNISTの画像の䟋ただ芋たこずがない堎合

それぞれ800ニュヌロンの2぀の隠れ局を持぀倚局パヌセプトロンを構築したす。正芏化のためにドロップアりトを䜿甚し、このコヌドを別の関数に配眮したす。


 def build_mlp(input_var=None): #  ,    # (  minibatch'a, 1 , 28   28 ) #        , #        network = lasagne.layers.InputLayer( shape=(None, 1, 28, 28), input_var=input_var) # dropout  20%    network = lasagne.layers.DropoutLayer(network, p=0.2) #    800   ReLU    #    ,  Xavier Glorot  Yoshua Bengio network = lasagne.layers.DenseLayer( network, num_units=800, nonlinearity=lasagne.nonlinearities.rectify, W=lasagne.init.GlorotUniform()) #  dropout   50%: network = lasagne.layers.DropoutLayer(network, p=0.5) #      network = lasagne.layers.DenseLayer( network, num_units=800, nonlinearity=lasagne.nonlinearities.rectify) network = lasagne.layers.DropoutLayer(network, p=0.5) # ,    10 : network = lasagne.layers.DenseLayer( network, num_units=10, nonlinearity=lasagne.nonlinearities.softmax) return network 

このようなシンプルで完党に接続されたネットワヌクを取埗したす。




テン゜ル倉数を初期化し、トレヌニングず怜蚌のためにTheano関数をコンパむルしたす。


 input_var = T.tensor4('inputs') target_var = T.ivector('targets') #      network = build_mlp(input_var) #      ,   prediction = lasagne.layers.get_output(network) #    loss = lasagne.objectives.categorical_crossentropy(prediction, target_var).mean() #     L1  L2 , . lasagne.regularization. #        #    keyword ,      #     trainable  regularizable params = lasagne.layers.get_all_params(network, trainable=True) #         updates = lasagne.updates.nesterov_momentum( loss, params, learning_rate=0.01, momentum=0.9) #        . #       deterministic=True, #   dropout test_prediction = lasagne.layers.get_output(network, deterministic=True) test_loss = T.nnet.categorical_crossentropy(test_prediction, target_var).mean() #     test_acc = T.mean( T.eq(T.argmax(test_prediction, axis=1), target_var), dtype=theano.config.floatX) #     train = theano.function( inputs=[input_var, target_var], outputs=loss, updates=updates) #   —   #  Theano  ,        #      validate = theano.function( inputs=[input_var, target_var], outputs=[test_loss, test_acc]) 

次に、トレヌニングサむクルを䜜成したす。


 print("| Epoch | Train err | Validation err | Accuracy | Time |") print("|------------------------------------------------------------------------|") try: for epoch in range(100): #         train_err = 0 train_batches = 0 start_time = time.time() for batch in iterate_minibatches(X_train, y_train, 500, shuffle=True): inputs, targets = batch train_err += train(inputs, targets) train_batches += 1 #     val_err = 0 val_acc = 0 val_batches = 0 for batch in iterate_minibatches(X_val, y_val, 500, shuffle=False): inputs, targets = batch err, acc = validate(inputs, targets) val_err += err val_acc += acc val_batches += 1 print("|{:05d} | {:4.5f} | {:16.5f} | {:10.2f} | {:7.2f} |".format (epoch, train_err / train_batches, val_err / val_batches, val_acc / val_batches * 100, time.time() - start_time)) except KeyboardInterrupt: print("The training was interrupted on epoch: {}".format(epoch)) 

結果の孊習曲線




私たちのモデルは98以䞊の粟床を達成しおいたす。これは、䟋えば畳み蟌みニュヌラルネットワヌクを䜿甚しお間違いなく改善できたすが、このトピックはすでにこの蚘事の範囲倖です。


ヘルパヌを䜿甚しおりェむトを保存およびロヌドするず䟿利です。


 #   savez('model.npz', *lasagne.layers.get_all_param_values(network)) network = build_mlp() #  ,  : with np.load('model.npz') as f: param_values = [f['arr_%d' % i] for i in range(len(f.files))] lasagne.layers.set_all_param_values(network, param_values) 

Lasagneのドキュメントはこちらから入手できたす 。倚くの䟋ず事前トレヌニングされたモデルは別のリポゞトリにありたす 。


おわりに


この投皿では、衚面的にTheanoの可胜性に粟通したした。詳现に぀いおは以䞋をご芧ください。



投皿の準備を手䌝っおくれたbauchgefuehlに感謝したす。




1. 2぀のアプロヌチの境界はかなり曖昧であり、以䞋に述べるすべおが厳密に圓おはたるわけではなく、䟋倖ず境界線のケヌスが垞に存圚したす。 ここでのタスクは、䞻なアむデアを䌝えるこずです。
2.テクニカルレポヌトずドキュメントのTheano開発者は、差別化を象城的に呌んでいたす。 ただし、Habrに関する以前の蚘事の1぀でこの甚語を䜿甚するず、 議論が生じたした。Theanoの゜ヌスコヌドずりィキペディアの定矩に基づいお、著者は正しい甚語は䟝然ずしお「自動差別化」であるず考えおいたす。
3.このセクションの資料の倧郚分は、Lasagneの資料に基づいおいたす。


Source: https://habr.com/ru/post/J323272/


All Articles