• TensorFlowの勾配計算の闇

    2020-08-04 22:40
    研究でTensorFlowを使ってて勾配計算における闇を発見してそれに2日位悩まされたので記事にしておく
    TensorFlow 1.xの話です

    ・TensorFlowの勾配計算って?ああ!!

    通常TensorFlowを扱う時、以下の様に書いて勾配計算って全く気にせずに書くと思います。(というかTensorFlow1.x系列だと勾配自体がブラックボックスに近い技術になってる気がする)
    import tensorflow as tf

    optimizer = #optimizerの宣言
    train_op = optimizer.minimize(#誤差)
    RNNなどで勾配クリッピングする際は以下の様に書いて勾配を少し意識すると思います。
    import tensorflow as tf

    optimizer = # optimizerの宣言
    grad, vars = zip(*optimizer.compute_gradients(# 誤差))
    cliped_grad = # 勾配クリッピング処理
    train_op = optimizer.apply_gradients(zip(cliped_grad, vars))
    それ以外では勾配を意識することは無いと思います。ところがどっこい、optimizerのminimizeメソッド内部処理(https://github.com/tensorflow/tensorflow/blob/05ab6a2afa2959410d48aab2336cea9dc1e2c13e/tensorflow/python/training/optimizer.py#L355)を見てみるとcompute_gradientsメソッドを呼び出して勾配を計算し、その勾配をapply_gradientsメソッドで適用してることがわかると思います。minimizeメソッドを使用することでということは知らないうちに勾配を計算して、適用してることになります
    これから勾配計算の仕様と闇に触れます。

    ・勾配計算と適用の仕様

    1.勾配計算の仕様
    基本的にはcompute_gradientsメソッドに誤差を引数として実行することで勾配が計算されます。
    これの戻り値は勾配とその勾配を適用するための変数がペア(勾配と変数のタプル)になったリストになります。なのでZip関数を使用することで勾配と変数をそれぞれリストとして取り出すことができます。型は勾配:tf.Tensor、変数:tf.Variableとなります。
    2.勾配適用の仕様
    勾配をモデルに適用するするためにはapply_gradientsメソッドに勾配と変数のタプルのリストを引数として実行することで適用されます。
    なので一回勾配に対してクリッピングなどの処理を行なった場合は再度Zip関数を使用してペアのリストにする必要があります。
    次からその勾配計算に関する闇に触れます

    ・闇1.勾配クリッピング

    1つ目の闇は「勾配クリッピング」です。
    TensorFlowには値やノルム等で値をクリップする手段が多数用意されています。しかし、配(tf.Tensorのリスト)を扱える関数はtf.clip_by_global_norm以外存在しないです。その他の方法でクリッピングしたい場合はfor文で回さないといけないです・・・・(なんともめんどくさい)
    TensorFlow公式チュートリアルもtf.clip_by_global_normでクリッピングをしているので、それでやれってことなんでしょう・・・
    以下コードです
    import tensorflow as tf

    optimizer = # optimizerの宣言
    grad, vars = zip(*optimizer.compute_gradients(# 誤差))

    # グローバルノルムでのクリッピング
    cliped_gnorm_grad, norm = tf.clip_by_global_norm(grad, # クリップする値)

    # それ以外でのクリッピング
    cliped_other_grad = [# one_gradに対してtf.clip_by_valueなどの関数を適用する for one_grad in grad]

    ・闇2.勾配を計算グラフ外に取り出す

    2つ目の闇は「勾配を計算グラフ外に取り出す」です。
    なんでいちいち勾配を計算グラフ外に取り出すの?って思うかもしれないですがモデルが巨大(EfficentNet-B8とか)になってまともにバッチ数を稼げない時に、少ないバッチ数で勾配を数回計算してその勾配の平均値を取ったりすることで擬似的にバッチ数を稼ぐことができます。なのでそういった時にどうしても勾配の値を一回計算グラフ外に出したりします。
    普通にTensorFlowを使用している方なら「余裕じゃん、勾配自体をsess.runで呼び出せば出てくるでしょ?(以下のコード)」と思うのですが、そこが闇ポイントです。
    import tensorflow as tf

    optimizer = # optimizerの宣言
    grad, vars = zip(*optimizer.compute_gradients(# 誤差))

    init = tf.global_variables_initializer()
    with tf.Session() as sess:

    init.run()

    gradients = sess.run(grad, feed_dict={# データとプレースホルダ})
    上記のコードは正常に動作しません(コメントの部分を正しく書き直しても動作しません)、「嘘だ!!」と思う方は実際に書いてみて実行していただければ
    エラーの内容としましてはsess.runで実行するオペレーションがNoneになってるよってエラーです。
    「tf.Tensorを指定して実行しているのに何故?」「tf.TensorなのにNoneっておかしくね?」って首を傾げる人は多いと思います。このエラーが出る原因は、勾配自体が特殊なTensorになっているからです。勾配のリストの一部を表示した時、tf.Tensorと型が出ますがその後の名前がこのエラーの原因です。名前の後ろに"Control_dependency"とあると思いますこれはTensorFlowの中のTensorでも「実行する前に値が入ってないと行けないTensor」(https://www.tensorflow.org/versions/r1.15/api_docs/python/tf/control_dependencies)になります。なのでsess.runで実行することはできません。
    「じゃあどうするの?」ってなりますがsess.runにvarsつまり、勾配をセットする変数を指定すること(以下のコード)で取り出すことが可能になります。
    import tensorflow as tf

    optimizer = # optimizerの宣言
    grad, vars = zip(*optimizer.compute_gradients(# 誤差))

    init = tf.global_variables_initializer()
    with tf.Session() as sess:

    init.run()

    gradients = sess.run(vars, feed_dict={# データとプレースホルダ})
    勾配計算なのに変数を指定する・・・・闇が深い・・・

    ・闇3.勾配を計算グラフ外に取り出すオペレーションを文字列で指定する

    3つ目の闇は「勾配を計算グラフ外に取り出すオペレーションを文字列で指定する」です。
    グラフを外部から読み込んだ時、どうしても文字列でオペレーションを指定する必要があるので時々こうなります・・・
    上の2つ目の闇を読んだ方は「変数の名前の文字列のリストを指定すればいい」と考えてコードを打ち込んで実行すると思いますが、エラーもなく実行できますが、それが深い深い闇の入り口です・・・

    import tensorflow as tf

    optimizer = # optimizerの宣言

    grad, vars = zip(*optimizer.compute_gradients(# 誤差))

    gradient_op = [var.name for var in vars]
    init = tf.global_variables_initializer()

    with tf.Session() as sess:

    init.run()

    gradients = sess.run(gradient_op, feed_dict={# データとプレースホルダ})
    上記コードが実行できるのでこの勾配に対して、色々と操作してモデルに勾配適用しようと思ったら何故かエラーが出ます。何故かというときちんと勾配がグラフ外に取り出せてないからです。printとかで計算した勾配を見てみると1組の整数型のタプルを要素に持つndarray(データタイプはオブジェクト)のリスト(正しい勾配はfloat32のデータを持つndarrayのリスト)になっています。一見すると勾配計算間違えたのかな?って思いますがオペレーションの設定ミスです。
    原因は変数は変数であり実際の勾配の計算オペレーションとは違うところです。変数の名前はkernelという名前が最後につき、勾配はControl_dependencyという名前がついています。実際のオペレーションはControl_dependencyで行われ、それがkernelに代入されるという流れなので文字列で指定する場合は勾配の方のTensorの名前を設定する必要があります(以下コード)
    import tensorflow as tf

    optimizer = # optimizerの宣言

    grad, vars = zip(*optimizer.compute_gradients(# 誤差))

    gradient_op = [one_grad.name for one_grad in grad]
    init = tf.global_variables_initializer()

    with tf.Session() as sess:

    init.run()

    gradients = sess.run(gradient_op, feed_dict={# データとプレースホルダ})
    同じ勾配計算なのに、文字列だと勾配、テンサーだと変数を指定しないといけない・・・闇が深すぎる・・・・
  • 広告
  • TensorFlow2.x式の低レベルな書き方

    2020-01-24 02:26
    世界中で最も使われていて最も現場で使われているTensorFlow(以下TF)が去年、機能拡張を含んだ大型バージョンアップを行いました。その際にAPIの大掃除が行われTF1.x系列で使っていた低レベルな書き方が通用しなくなりました

    この記事は低レベルでゴリゴリ色んな所をカスタマイズして書きたいって人を支援できたら幸いです(自分自身もよくわかってない点もある)

    1.前話

    ・TF2.xで生き残っているTF1.xの書き方
    TF1.xとTF2.x両方に通用する書き方はKerasのFunctional APIとSequential APIです。
    それ以外の書き方は消えました。というかTensorFlow自体が「Kerasをベースに書いてね」という感じです。tf.layersとかは消されて使えません
    また、TF1.xからTF2.xに更新した際、まともに変更せずに動くのはKerasのみ(カスタムレイヤーとか使わずに)を使って書かれたコードだとおもいます(変換プログラムもあるけど使えるのかね)
    ※TF1.xから更新してTF2.xのKerasを動かす場合、多分Kerasのインポートを以下の様に変更する必要があります。

    from tensorfrow import keras

    ・どうしてそんなひどい事をしたの?
    答えは単純です
    「PyTorchとかの方がPythonic(Pythonぽくて)ぽくって、書きやすいから」です
    あとPyTorchの追い上げがすごいってのもある

    (引用:https://trends.google.co.jp/trends/explore?date=today%205-y&q=TensorFlow,PyTorch,Keras,Chainer)

    あとPyTorchとかKerasはオブジェクト指向ぽくかけて、TF1.xは関数型(tf.layersとかもろ関数型)ぽいからってのもあります。またはDefine on Run(TensorFlow)とDefine by Run(PyTorch)の違いもあったりします
    Define on RunとDefine by Runの解説はこの記事ではしませんが、気になる方は調べていただければ

    「Kerasでオブジェクト指向的なモデルの書き方は無いんじゃない?」って感のいい読者なら気づいたかもしれません。確かにTF1.xのKerasにはオブジェクト指向的な書き方は存在しません、TF2.xのKerasにはオブジェクト指向的なモデルの書き方ができます。本記事はその点にも触れます


    2.本編

    ・TensorFlow2.0の書き方
    TensorFlow2.0はKerasの作者によるColab Notebookによると以下の書き方があります。
    この記事では最も左下の書き方を解説します。


    ・Fully flexible with TensorFlow2.0(最も低レベルな書き方)
    TT2.xで最も低レベルな書き方は以下になります
     ・レイヤー :tf.keras.layers.layerを継承したクラスで書く
     ・レイヤーブロック :tf.keras.layers.layerかtf.keras.Modelを継承したクラスで書く
     ・モデル :tf.keras.Modelを継承したクラスで書く
     ・訓練イテレーション:クラスを宣言してその中で回す
    一番上以外はちょっとピンとこないと思います。一個ずつ解説します

    ・カスタムレイヤー :tf.keras.layers.layerを継承したクラスで書く
    TF1.xのカスタムレイヤーの書き方がそのまま使えます。
    基本的に __iniit(self)__で必要な変数を宣言してbuild(self,input_shape)で使うレイヤーと重み、バイアスを宣言します。以下例
    (引用と参考:https://www.tensorflow.org/tutorials/customization/custom_layers)
    class MyLayer(tf.keras.layers.layer):
       def __iniit__(self, output, **kwargs):
         super(MyLayer, self).__init__(**kwargs)
         self.output = output
       def build(self, input_shape):
         self.kernel = self
    .add_variable("kernel",shape=[   
                        int(input_shape[-1]),
                        self.num_outputs])
       def __call__(self, inputs):
         return tf.matmul(inputs, self.kernel)
    レイヤーブロック:tf.keras.layers.layerかtf.keras.Modelを継承したクラスで書く
    CNNとかで使われている層を重ねたブロック。上のカスタムレイヤーも入れて書くことも可能
    tf.keras.layers.layerで書く場合は重み、バイアスの宣言は行わないようにする
    tf.keras.Modelでも書ける(モデルの入れ子ができるため)けど入力の型が取れないから正直微妙である。以下例
    class MyLayerBlock_layer(tf.keras.layers.layer):
       def __iniit__(self, filter, kernel_size, channel_axes=-1 **kwargs):
         super(MyLayerBlock_layer, self).__init__(**kwargs)
         self.filter = filter
         self.kernel_size = kernel_size
         self.channel_axes = channel_axes 
       def build(self, input_shape):
         input_filter = input_shape[self.channel_axes].value
         self.conv2_a = tf.keras.layers.Conv2D(input_filter, (1, 1))
         self.bn2_a = tf.keras.layers.BatchNormalization()
         self.depthconv2 = tf.keras.layers.DepthwiseConv2D(input_filter,
                                 self.kernel_size,
                                 padding='same')
         self.bn2_depth = tf.keras.layers.BatchNormalization()
         self.conv2_b = tf.keras.layers.Conv2D(self.filter, (1, 1))
         self.bn2_b = tf.keras.layers.BatchNormalization()
       def __call__(self, inputs, training=True):
         x = self.conv2_a(input_tensor)
         x = self.bn2a(x, training=training)
         x = tf.nn.relu(x)
         x = self.depthconv2(x)
         x = self.bn2_depth(x, training=training)
         x = tf.nn.relu(x)
         x = self.conv2_b(x)
         x = self.bn2_b(x, training=training)
         x += inputs
         return tf.nn.relu(x)

    class MyLayerBlock_Model(tf.keras.Model):
       def __iniit__(self,
              input_filter,
              filter
    ,
              kernel_size,
              **kwargs):
         super(MyLayerBlock_Model, self).__init__(**kwargs)
         self.conv2_a = tf.keras.layers.Conv2D(input_filter, (1, 1))
         self.bn2_a = tf.keras.layers.BatchNormalization()
         self.depthconv2 = tf.keras.layers.DepthwiseConv2D(input_filter,
                                   kernel_size,
                                 padding='same')
         self.bn2_depth = tf.keras.layers.BatchNormalization()
         self.conv2_b = tf.keras.layers.Conv2D(filter, (1, 1))
         self.bn2_b = tf.keras.layers.BatchNormalization()
       def __call__(self, inputs, training=True):
         x = self.conv2_a(input_tensor)
         x = self.bn2a(x, training=training)
         x = tf.nn.relu(x)
         x = self.depthconv2(x)
         x = self.bn2_depth(x, training=training)
         x = tf.nn.relu(x)
         x = self.conv2_b(x)
         x = self.bn2_b(x, training=training)
         x += inputs
         return tf.nn.relu(x)
    モデル:tf.keras.Modelを継承したクラスで書く
    Kerasのベースモデルクラスを使ってモデルを作ります。これでPyTorchっぽく書けてPythonicなモデルになります(オブジェクト指向的)
    このモデルに対してcompileやpredictなどのメソッドを使うことができるが、作成したモデルを使ってtf.keras.models系の関数は使えません
    このモデルに対して複数の入力を与える場合、それぞれの1軸をバッチ次元にしたリストを渡して__call__の時に分解します
    class MyModel(tf.keras.Model):
       def __iniit__(self,output,**kwargs):
         super(MyModel, self).__init__(**kwargs)
         self.dense = tf.keras.layers.Dense(output)
       def __call__(self, inputs):
         x = self.dense(inputs)
         return tf.nn.relu(x)
    ・訓練イテレーション:クラスを宣言してその中で回す
    これが一番の肝です。以下のようにタスククラスを宣言しモデルの初期化などを行った後訓練メソッドを呼び出すようにします
    以下の場合だとtrainメソッドを呼び出せば訓練が開始されます。
    何故、こんな面倒くさい事をやってるかというと@tf.functionの自動グラフ構築を必要な所で使うことによって最適かつ高速に訓練を行う事ができます(GoogleのTransformerでも使われてるテクニック)
    Define on RunとDefine by Runを同時に操作できる強みがある
    class MyTask(object):
       def __iniit__(self,model,optimizer,loss_func,acc_func):
         self.model = model
         self.optimizer = optimizer
         self.loss_func = loss_func
         self.acc_func = acc_func

         self.model.compile() #必要ならコンパイルしとく
       def train(self, epoch):
         for iter in range(epoch):
           x, y = (データの取得用の関数とかクラス)
           loss=train_step( x, y )
         @tf.function
         def train_step(self, x, y)
           with tf.GradientTape() as tape:
              pred = self.model(x, training=True)
              loss = self.loss_func(y, pred)
           grd = tape.gradient(loss, self.model.trainable_weights)
           self.optimizer.apply_gradients(zip(grd,
            self.model.trainable_weights))
           self.acc_func.update_state(y, pred)
           return loss
       @tf.function
       def model_predict(self,x)
         return self.model.predict(x)

    3.おわりに

    これらのを利用することでTF1.x系でも、できた細かなカスタムや調整もできるようになったと思います
    最後にTensorFlow v2.x系はまだちょっと未完成なところ(ver2.0では勾配計算に失敗したり、今でのカスタムレイヤーで複数出力を計算時にバグがあったり)があるので研究等で使う場合は十分に気を付けてくださいませ
  • GregTech 5/5Uのエネルギーシステムについて(公式GTNH wiki「Electricity」日本語訳)

    2019-05-16 20:272

    この記事はGTNHの公式wikiの「Electricity」項目の日本語翻訳です(URL:https://gtnh.miraheze.org/wiki/Electricity)
    一部見やすいように改変していますのでご了承下さい

    2019/05/16:初版、1と2だけの翻訳
    2019/06/18:3を翻訳開始

    ⚡INDEX

    1. はじめに
    2. 電圧と電流
    3. ケーブルと損失
    4. 変圧器
    5. 機械の爆発とケーブルの焼失
    6. エネルギー変換
    7. エネルギー保存の管理
    8. 新規プレイヤー向けのガイド

    1.はじめに
    ⚠このページ及びGTNHの公式wikiの「Electricity」項目は未完成であり、情報を募集中です

    このページ及びGTNHの公式wikiの「Electricity」項目は、IC2Exのエネルギーシステムに不満をもったGregoriusT氏がGregTechにver5(Minecraft ver1.7.2向け)から実装したエネルギーシステムについての解説です
    よって全てのGregTech5のバージョンのエネルギーシステムの解説になります(非公式版も含む)
    IC2のエネルギーネットと互換性をなくした理由はケーブルの減衰が機能しないことと、そのネットワークにパケットが存在しない、そしてそれを整数型から倍精度浮動小数点型に変更したからです(より大きなエネルギーストレージは恐ろしいです)。言うまでもなく、タイルエンティティを常に登録及び解除しないでエネルギーの流れを制御することは非常に難しいことです
                                  ー GregoriusT


    2.電圧と電流
    GregTechは電圧(Voltage 単位:V)とアンペア(Ampere 単位:A)という用語を用いて新しい電力システムを実装しています。「1アンペア」はIC2のEUの1パケットと同じで、「電圧」はそのパケットのサイズと同じです。EU/tは受け取ったEUの合計値です。
    例えば、1つのマシンがある時に32Vのポケットを受け取り、そのあと24Vのポケットを受け取ったとしたら、受け取った合計のEU/tは32+24=56EU/tとなります。
    IC2のエネルギーシステムとは異なり、エネルギーによって作用する全てのGregTechのブロックは、それらが作用できる電圧とアンペアの両方に制限があります。
    機械によって入出力できるアンペアが異なります
    • GregTechの変圧器(Transformer)は昇圧時は、入力4A、出力1Aになり、降圧時は、入力1A、出力4Aになる
    • バッテリーバッファ(Battery Buffer)は1つのバッテリーにあたり入力2A、出力1Aとなる
    • バッテリー充電器(Battery Charger)は1つのバッテリーにあたり入力8A、出力2Aとなる
    • チェストバッファ(Chest Buffer)とスーパーバッファ(Super Buffer)は入力2Aとなる
    • エネルギーハッチ(Energy Hatch)は入力2Aとなる
    • 物質製造機(Mass Fabricators)は入力10Aとなる
    • マイクロウェーブエナジートランスミッター(Microwave Energy Transmitter)は入力3Aとなる
    • モンスターリペレンター(Monster Repellator)、ポンプ(Pump)、テレポーター(Teleporter)は入力2Aとなる
    • 他のすべてのEU受け取る機械はレシピに応じて少なくとも1Aを受け取ります:入力アンペア数はレシピで表示されるEU使用量の2倍を機械の入力電圧で割ったものに1を足したものです
      ・LVの遠心分離機が5EUのレシピを処理している場合、入力1Aとなる
      ・LVの化学炉が30EUのレシピを処理している場合、入力2Aとなる
      ・LVのアーク炉が96EUのレシピを処理している場合、入力は7Aとなる
    • 発電機は1Aを出力します
    機械名入力出力
     変圧器(Transformer 昇圧) 4A 1A
     変圧機(Transformer ※降圧) 1A 4A
     バッテリーバッファ(Battery Buffer) 2A(バッテリー1個あたり) 1A(バッテリー1個あたり)
     バッテリー充電器(Battery Charger) 8A(バッテリー1個あたり)
     2A(バッテリー1個あたり)
     チェストバッファ(Chest Buffer) 2A NONE
     スーパーバッファ(Super Buffer) 2A NONE
     エネルギーハッチ(Energy Hatch) 2A NONE
     物質製造機(Mass Fabricators)
     10A NONE
     マイクロウェーブエナジートランスミッター
     (Microwave Energy Transmitter)
     3A NONE
     モンスターリペレンター(Monster Repellator)
     2A NONE
     ポンプ(Pump)
     2A NONE
     テレポーター(Teleporter)
     2A NONE
     その他の全ての機械 ((レシピEU*2)/機械の電圧)+1A NONE
     発電機(全て) NONE 1A

    機械にエネルギーを入力する際に以下のことに注意することがある:
    • 機械の電圧より高い電圧を入力すると爆発します。機械は必要になるまでエネルギーを受け取らないので、実際に動作するまで爆発することはありません!
    • 機械に過剰入力されたアンペアは機械の電圧の限界を下回っている限り効果がありません。機械は電圧を必要としない限り電流を流さずアンペアの端数も流しません、これによって電力に関して機械は機械自身で調整します
    機械とレシピにはそれぞれ電圧のティアがあります。マルチブロックの機械はそのエネルギーハッチによって決定します。 機械のティアとレシピのティアは相互に作用するため、注意を払う必要があります
    • レシピのティアがマシンのティアより高かった場合、レシピは実行されません
    • レシピのティアがマシンのティアと同じだった場合、レシピは正常に動作します
    • レシピのティアがマシンのティアより低かった場合、レシピはオーバークロックされます。 オーバークロックされる場合は普通レシピの速度の2倍の速度で、2倍のエネルギーで実行されます。したがって、1ティックあたり普通レシピの4倍のエネルギーで実行されます
    レシピのオーバークロックは機械の電圧とレシピが必要とする電圧との差の電圧ティアの1づつに実行されます。 もし、30EU/t(LVティア)で20秒で作業が完了するレシピをHVのマシンで実行される場合、2つのティアの差によって480EU/tで5秒で作業が完了します
    GregTech:New Horizonsはバージョン2.0.2.5の時点で9つの電圧のティアが存在しています。そのうちの3つの電圧のティアは部分的な実装(*)で4つの到達不可能な電圧のティア(**)です
    ノート:ULVティアはティア0として扱います
    短縮形電圧名許容最高電圧
     ULV Ultra Low Voltage
     8
     LV Low Voltage
     32
     MV Medium Voltage 128
     HV High Voltage 512
     EV Extreme Voltage
     2048
     IV Insane Voltage
     8192
     LuV Ludicrous Voltage 32768
     ZPM ZPM Voltage 131072
     UV Ultimate Voltage 524288
     UHV(*) Highly Ultimate Voltage 2097152
     UEV(*) Extremely Ultimate Voltage 8388608
     UIV(*) Insanely Ultimate Voltage
     33554432
     UMV(**) Mega Ultimate Voltage
     134217728
     UXV(**) Extended Mega Ultimate Voltage
     536870912
     OpV(**) Overpowered Voltage
     1073741824
     MAX(**) Maximum Voltage
     2147483647

    3.電圧と電流
    GregTechには現在独自の電気システムがあるため、GregTechのマシンへの電源供給にはGregTechケーブルを使用する必要があります
    GregTechでIC2のEUを受け入れることができる唯一のマシンが[[GregTech5Uの変圧機(Transformer)|変圧器(Transformer)]](IC2の変圧器(Transformer)と混同しないでください)

    すべてのGregTechのケーブルには許容電圧と許容電流と損失があります:
    ・許容電圧より高いパケットを受け取るとケーブルは焼失してしまうでしょう
    ・許容電流より多くのアンペアを通電させたケーブルは焼失してしまうでしょう
     パケットが反発する可能性があることに注意してください。論理パスがEUをそこに流すべきではないとしても、そのケーブルに余分なパケットが流れない事を絶対に考えてはならない(必ずしも最適な電流がそのケーブルを流れるとは限らないという事。例えば分岐させたからと1Aのケーブルでも大丈夫ではなく、出力アンペアと同じかそれ以上に耐えうるケーブルを使わないと反発し合った時に焼失するおそれがある)