ホームページ >テクノロジー周辺機器 >AI >実際の展開: エンドツーエンドの検出と追跡のための動的シーケンシャル ネットワーク
この記事は自動運転ハート公式アカウントの許可を得て転載しておりますので、転載については出典元にご連絡ください。
自社開発チップを開発する一部の大手メーカーを除いて、ほとんどの自動運転企業は NVIDIA チップを使用すると思いますが、TensorRT は、さまざまな NVIDIA GPU ハードウェア上で動作する C 推論です。プラットフォームフレーム。 Pytorch、TF、またはその他のフレームワークを使用してトレーニングしたモデルは、まず onnx 形式に変換し、次に TensorRT 形式に変換してから、TensorRT 推論エンジンを使用してモデルを実行することで、NVIDIA GPU でのこのモデルの実行速度が向上します。 。
一般的に、onnx と TensorRT は比較的固定されたモデル (すべてのレベルでの固定入力および出力形式、単一分岐などを含む) のみをサポートし、最大でも最も外側の動的入力をサポートします (onnx は設定によってエクスポートできます)。 Dynamic_axes パラメータは、動的な変更を可能にする次元を決定します) しかし、認識アルゴリズムの最前線で活動している友人なら、重要な開発トレンドがエンドツーエンド (エンドツーエンド) であることを知っているでしょう。目標追跡、軌道予測、意思決定や計画など自動運転のあらゆる側面に対応し、前後のフレームと密接に関連するタイミングモデルである必要があるエンドツーエンドの目標検出と目標を実現するMUTR3Dモデルトラッキングは典型的な例として使用できます (モデルの紹介については、次を参照してください:)
MOTR/MUTR3D では、真のエンドツーエンドを実現するためのラベル割り当てメカニズムの理論と例を詳細に説明します。マルチターゲット追跡を終了します。詳細については、リンクをクリックしてください: https://zhuanlan.zhihu.com/p/609123786
このモデルを TensorRT 形式に変換し、fp16 の精度アライメントを含む精度アライメントを実現すると、一連の問題に直面する可能性があります。複数の if-else 分岐、サブネットワーク入力形状の動的変更、動的処理を必要とするその他の操作や演算子などの動的要素
#Picture
#MUTR3D アーキテクチャ 全体のプロセスには多くの詳細が含まれるため、状況は異なります。ネットワーク全体の参考資料を見ても、Google で検索しても、プラグアンドプレイのソリューションを見つけるのは困難です。私たちが解決するしかありません。解決策: ブロガーによる 1 か月以上の熱心な探索と実践の後 (私はこれまで TensorRT の経験があまりなく、その性質を理解していませんでした)、私はたくさんのツールを使用しました。多くの落とし穴を踏み、最終的に変換と実装に成功した fp32/fp16 は正確に位置合わせされており、単純なターゲット検出に比べて遅延の増加は非常にわずかです。ここで簡単にまとめて、皆さんの参考にしたいと思います (はい、私はレビューを書いてきて、ついに書く練習をしてきました!) 1. データ形式の問題1 つ目は、 MUTR3D のデータ 形式はかなり特殊で、インスタンスの形式になっています。これは、各クエリが多くの情報にバインドされており、1 対 1 のアクセスを容易にするためにインスタンスにパッケージ化されているためです。ただし、デプロイメントの場合は、入力と出力テンソルのみにすることができるため、まずインスタンス データを複数のテンソル変数に分解する必要があります。また、現在のフレームのクエリとその他の変数はモデル内で生成されるため、前のフレームで保持されているクエリとその他の変数を入力するだけで済みます2.padding は入力の動的形状の問題を解決します入力プレフレーム クエリとその他の変数の場合、重要な問題は形状が不確かな。これは、MUTR3D が前のフレームでターゲットを検出したクエリのみを保持するためです。この問題は比較的簡単に解決でき、最も簡単な方法はパディング、つまり固定サイズにパディングすることです。クエリの場合、パディングにすべて 0 を使用できます。適切な数は、独自のデータに基づいた実験によって決定できます。少なすぎるとターゲットを外しやすくなり、多すぎるとスペースが無駄になります。 onnxのdynamic_axesパラメータは動的な入力を実現できるものの、後段のtransformerで計算されるサイズを伴うため問題があるはずです。私は試していませんが、読者は試してみてください3. メイントランスフォーマーのセルフアテンションモジュールに対するパディングの影響特別な演算子を使用しない場合は、正常に変換できますONNX と TensorRT へ。実際には、この状況に遭遇する必要がありますが、それはこの記事の範囲を超えています。たとえば、MUTR3D では、フレーム間で参照点を移動する場合、torch.linalg.inv オペレーターを使用して擬似逆行列を見つけることはサポートされていません。サポートされていない演算子が見つかった場合は、それを置き換えることしかできません。機能しない場合は、モデルの外でのみ使用できます。経験豊富なユーザーは、独自の演算子を作成することもできます。ただし、このステップはモデルの前処理と後処理に配置できるため、モデルの外に移動することにしました。独自の演算子を記述するのはより困難になります。変換が成功したという意味ではありません。すべてがスムーズに進んでいると言えますが、その答えは否定的なものであることがよくあります。精度の差が非常に大きいことがわかります。これはモデルに多くのモジュールがあるためです。まず最初の理由について説明します。 Transformer のセルフアテンション段階では、複数のクエリ間の情報の相互作用が発生します。ただし、元のモデルは、ターゲットが一度検出されたクエリ (モデルではアクティブ クエリと呼ばれます) のみを保持し、これらのクエリのみが現在のフレームのクエリと対話する必要があります。現在、多くの無効なクエリが入力されているため、すべてのクエリが相互作用すると、必然的に結果に影響を及ぼしますこの問題に対する解決策は、DN-DETR[1] からインスピレーションを得たもので、nn.MultiheadAttendant の 'attn_mask' パラメーターに対応する、attention_mask を使用することで、情報のやり取りを必要としないクエリをブロックします。当初、これは NLP ではセット内の各文の長さが一貫していないためであり、現在のニーズを正確に満たしています。ただし、True はブロックする必要があるクエリを表し、False は有効なクエリを表すことに注意する必要があります。
写真
アテンション マスク図attention_mask の計算ロジックは少し複雑なので、多くの操作を TensorRT に変換すると新たな問題が発生する可能性があるため、計算する必要があります。モデルの外でモデルに入力変数として入力され、トランスフォーマーに渡されます。サンプル コードは次のとおりです:
data['attn_masks'] = attn_masks_init.clone().to(device)data['attn_masks'][active_prev_num:max_num, :] = Truedata['attn_masks'][:, active_prev_num:max_num] = True[1]DN-DETR: Accelerate DETR Training by Introducing Query DeNoising
QIM に対するパディングの影響は次のとおりです。トランスフォーマによって出力されたクエリに対する MUTR3D の後処理モジュールです。主に 3 つのステップに分かれています。最初のステップは、アクティブなクエリをフィルタリングすることです。つまり、現在のフレームで検出されたターゲットのクエリは、 obj_idxs >= 0 かどうか (トレーニング フェーズには、推論フェーズには関係しない、ランダムなドロップ クエリと fp クエリのランダムな追加も含まれます)。2 番目のステップは、最初のステップ用の更新クエリです。セルフ アテンション、クエリ出力値の ffn、およびクエリ入力値とのショートカット接続を含むフィルタリングされたクエリ。3 番目のステップは、更新されたクエリを、次のフレームの入力として再生成された初期クエリと結合することです。ポイント 3 で述べた問題が 2 番目のステップでもまだ存在していることがわかります。つまり、セルフ アテンションはすべてのクエリと相互作用するわけではなく、アクティブなクエリ間の情報とのみ相互作用するため、ここでもアテンション マスクを使用する必要があります。
QIM モジュールはオプションですが、実験によると、モデルの精度を向上させるのに役立つことがわかっています。QIM を使用したい場合は、このアテンション マスクはモデルの外部で取得できないため、モデル内で計算する必要があります。モデル. 現在のフレームの検出結果を知る. tensorRT の構文制限により、多くの操作は変換に失敗するか、望ましい結果が得られません. 多くの実験の後、結論は、インデックス スライスの割り当てを直接使用することです (同様のポイント 3 の例 コード) 演算は通常サポートされていません。行列計算を使用するのが最適ですが、計算に関しては、アテンション マスクの bool 型を float 型に変換する必要があります。最後に、アテンション マスクは必要があります。使用する前に bool 型に変換し直す必要があります。コード例は次のとおりです:
obj_mask = (obj_idxs >= 0).float()attn_mask = torch.matmul(obj_mask.unsqueeze(-1), obj_mask.unsqueeze(0)).bool()attn_mask = ~attn_mask
上記 4 点を完了した後モデル変換 tensorRT のロジックには基本的に問題がないことは確認できますが、検証を重ねても一部のフレームで出力結果に問題が発生するので困惑しますが、データをフレームごとに解析してみると次のことがわかります。一部のフレームのパディング クエリがトランスフォーマーの計算に参加していない場合でも、より高いスコアが得られ、間違った結果が得られる可能性があります。この場合、データ量が多い場合は、パディング クエリのみが実行されるため、実際にこのような可能性があります。の初期値は 0 で、参照点も [0,0] で、他のランダムに初期化されたクエリと同じ操作を実行しますが、結局はパディング クエリなので、その結果を使用するつもりはありません。フィルタリングする必要があります。
入力クエリの結果をフィルタリングするにはどうすればよいですか?クエリに入力されるトークンはインデックス位置のみであり、他の情報は特定されません。インデックス情報は実際にはポイント 3 で使用されるアテンション マスクに記録され、モデルの外部から渡されます。このマスクは 2 次元であり、いずれかの次元 (任意の行または列) を使用して、塗りつぶされた track_score を直接 0 に設定できます。ステップ 4 の注意事項に注意してください。つまり、インデックス付きスライス割り当ての代わりに行列計算を使用するようにしてください。また、計算は float 型に変換する必要があります。以下はコード例です:
mask = (~attention_mask[-1]).float()track_scores = track_scores * mask
モデル本体に加えて、実際には track_id を動的に更新する非常に重要なステップがあります。モデルがエンドツーエンドで達成できることは重要な要素ですが、元のモデルの track_id を更新する方法は比較的複雑なループ判定、つまりスコア thresh より高く、新しいターゲットである場合は代入するという比較的複雑なループ判定になります。新しい obj_idx であり、それがフィルター スコアしきい値より低く、古いターゲットである場合、対応する消失時間は 1、消失時間が miss_tolerance を超える場合、対応する obj_idx は -1 に設定され、つまりターゲットは破棄されます。
tensorRT が if-else マルチブランチ ステートメントをサポートしていないことはわかっています (まあ、私は知りませんが始めました)、これは頭痛の種です。更新された track_id もモデルの外に配置されている場合、 QIM は更新された track_id に基づいてクエリをフィルタリングするため、モデルのエンドツーエンドのアーキテクチャに影響を与えるだけでなく、QIM を使用できなくなります。そこで、私は更新された track_id をモデルに組み込むために知恵を絞りました。
また工夫を凝らしてください(もう限界です)、マスクを使って並列処理するなど、if-else 文は代替不可能ではありません。たとえば、条件付きでマスクに変換します (例: tensor[mask] = 0)。幸いなことに、ポイント 4 と 5 で説明した tensorRT はインデックス スライスの割り当て操作をサポートしていませんが、ブール値のインデックスの割り当てはサポートしています。スライス操作が暗黙的であるためであると推測されます。テンソルの形状を変更します。実験では、ブール値インデックスの割り当てはすべての場合にサポートされるわけではありません。次のような問題が発生しました:需要重新写的内容是:赋值的值必须是一个,不能是多个。例如,当我更新新出现的目标时,我不会统一赋值为某个ID,而是需要为每个目标赋予连续递增的ID。我想到的解决办法是先统一赋值为一个比较大且不可能出现的数字,比如1000,以避免与之前的ID重复,然后在后续处理中将1000替换为唯一且连续递增的数字。(我真是个天才)
如果要进行递增操作(+=1),只能使用简单的掩码,即不能涉及复杂的逻辑计算。例如,对disappear_time的更新,本来需要同时判断obj_idx >= 0且track_scores = 0这个条件。虽然看似不合理,但经过分析发现,即使将obj_idx=-1的非目标的disappear_time递增,因为后续这些目标并不会被选入,所以对整体逻辑影响不大
综上,最后的动态更新track_id示例代码如下,在后处理环节要记得替换obj_idx为1000的数值.:
def update_trackid(self, track_scores, disappear_time, obj_idxs):disappear_time[track_scores >= 0.4] = 0obj_idxs[(obj_idxs == -1) & (track_scores >= 0.4)] = 1000disappear_time[track_scores 5] = -1
至此模型部分的处理就全部结束了,是不是比较崩溃,但是没办法,部署端到端模型肯定比一般模型要复杂很多.模型最后会输出固定shape的结果,还需要在后处理阶段根据obj_idx是否>0判断需要保留到下一帧的query,再根据track_scores是否>filter score thresh判断当前最终的输出结果.总体来看,需要在模型外进行的操作只有三步:帧间移动reference_points,对输入query进行padding,对输出结果进行过滤和转换格式,基本上实现了端到端的目标检测+目标跟踪.
需要重新写的内容是:以上六点的操作顺序需要说明一下。我在这里按照问题分类来写,实际上可能的顺序是1->2->3->5->6->4,因为第五点和第六点是使用QIM的前提,它们之间也存在依赖关系。另外一个问题是我没有使用memory bank,即时序融合的模块,因为经过实验发现这个模块的提升效果并不明显,而且对于端到端跟踪机制来说,已经天然地使用了时序融合(因为直接将前序帧的查询信息带到下一帧),所以时序融合并不是非常必要
好了,现在我们可以对比TensorRT的推理结果和PyTorch的推理结果,会发现在FP32精度下可以实现精度对齐,非常棒!但是,如果需要转换为FP16(可以大幅降低部署时延),第一次推理会发现结果完全变成None(再次崩溃)。导致FP16结果为None一般都是因为出现数据溢出,即数值大小超限(FP16最大支持范围是-65504~+65504)。如果你的代码使用了一些特殊的操作,或者你的数据天然数值较大,例如内外参、姿态等数据很可能超限,一般可以通过缩放等方式解决。这里再说一下和我以上6点相关的一个原因:
7.使用attention_mask导致的fp16结果为none的问题
这个问题非常隐蔽,因为问题隐藏在torch.nn.MultiheadAttention源码中,具体在torch.nn.functional.py文件中,有以下几句:
if attn_mask is not None and attn_mask.dtype == torch.bool:new_attn_mask = torch.zeros_like(attn_mask, dtype=q.dtype)new_attn_mask.masked_fill_(attn_mask, float("-inf"))attn_mask = new_attn_mask
可以看到,这一步操作是对attn_mask中值为True的元素用float("-inf")填充,这也是attention mask的原理所在,也就是值为1的位置会被替换成负无穷,这样在后续的softmax操作中,这个位置的输入会被加上负无穷,输出的结果就可以忽略不记,不会对其他位置的输出产生影响.大家也能看出来了,这个float("-inf")是fp32精度,肯定超过fp16支持的范围了,所以导致结果为none.我在这里把它替换为fp16支持的下限,即-65504,转fp16就正常了,虽然说一般不要修改源码,但这个确实没办法.不要问我怎么知道这么隐蔽的问题的,因为不是我一个人想到的.但如果使用attention_mask之前仔细研究了原理,想到也不难.
好的,以下是我在端到端模型部署方面的全部经验分享,我保证这不是标题党。由于我对tensorRT的接触时间不长,所以可能有些描述不准确的地方
需要进行改写的内容是:原文链接:https://mp.weixin.qq.com/s/EcmNH2to2vXBsdnNvpo0xw
以上が実際の展開: エンドツーエンドの検出と追跡のための動的シーケンシャル ネットワークの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。