YOLOv7:トレーニング可能なバッグ・オブ・フリービー
YOLOv7は、5 FPSから160 FPSの範囲において、スピードと精度の両方において、すべての既知の物体検出器を凌駕する最先端のリアルタイム物体検出器です。YOLOv7 は、GPU V100 上で、30 FPS 以上のすべての既知のリアルタイム物体検出器の中で最高の精度(AP 56.8%)を誇ります。さらに、YOLOv7は、YOLOR、YOLOX、Scaled-YOLOv4、YOLOv5 などの他のオブジェクト検出器を、速度と精度で上回っています。このモデルは、他のデータセットや事前に訓練された重みを使用することなく、MS COCOデータセットでゼロから訓練されています。YOLOv7のソースコードはGitHubで公開されている。
SOTA物体検出器の比較
YOLO 比較表の結果から、提案手法が総合的に最も優れた速度と精度のトレードオフを持っていることがわかる。YOLOv7-tiny-SiLUとYOLOv5(r6.1)を比較すると、提案手法は127fps速く、APで10.7%正確です。さらに、YOLOv7はフレームレート161fpsで51.4%のAPを持つが、同じAPを持つPPYOLOE-Lのフレームレートは78fpsしかない。パラメータ使用量では、YOLOv7はPPYOLOE-Lより41%少ない。
推論速度114fpsのYOLOv7-Xと推論速度99fpsのYOLOv5(r6.1)を比較すると、YOLOv7-XはAPを3.9%改善できる。YOLOv7-Xを同規模のYOLOv5(r6.1)と比較すると、YOLOv7-Xの推論速度は31fps速い。また、パラメータと計算量の面では、YOLOv7-XはYOLOv5(r6.1)と比較して、パラメータを22%、計算量を8%削減したが、APは2.2%改善した(出典)。
パフォーマンス
モデル | Params (M) |
FLOPs (G) |
サイズ (ピクセル) |
FPS | APtest/ val 50-95 |
APtest 50 |
APtest 75 |
APtest S |
APtest M |
APtest L |
---|---|---|---|---|---|---|---|---|---|---|
YOLOX-S | 9.0 | 26.8 | 640 | 102 | 40.5% / 40.5% | - | - | - | - | - |
YOLOX-M | 25.3 | 73.8 | 640 | 81 | 47.2% / 46.9% | - | - | - | - | - |
YOLOX-L | 54.2 | 155.6 | 640 | 69 | 50.1% / 49.7% | - | - | - | - | - |
YOLOX-X | 99.1 | 281.9 | 640 | 58 | 51.5% / 51.1% | - | - | - | - | - |
PPYOLOE-S | 7.9 | 17.4 | 640 | 208 | 43.1% / 42.7% | 60.5% | 46.6% | 23.2% | 46.4% | 56.9% |
PPYOLOE-M | 23.4 | 49.9 | 640 | 123 | 48.9% / 48.6% | 66.5% | 53.0% | 28.6% | 52.9% | 63.8% |
PPYOLOE-L | 52.2 | 110.1 | 640 | 78 | 51.4% / 50.9% | 68.9% | 55.6% | 31.4% | 55.3% | 66.1% |
PPYOLOE-X | 98.4 | 206.6 | 640 | 45 | 52.2% / 51.9% | 69.9% | 56.5% | 33.3% | 56.3% | 66.4% |
YOLOv5-N (r6.1) | 1.9 | 4.5 | 640 | 159 | - / 28.0% | - | - | - | - | - |
YOLOv5-S (r6.1) | 7.2 | 16.5 | 640 | 156 | - / 37.4% | - | - | - | - | - |
YOLOv5-M (r6.1) | 21.2 | 49.0 | 640 | 122 | - / 45.4% | - | - | - | - | - |
YOLOv5-L (r6.1) | 46.5 | 109.1 | 640 | 99 | - / 49.0% | - | - | - | - | - |
YOLOv5-X (r6.1) | 86.7 | 205.7 | 640 | 83 | - / 50.7% | - | - | - | - | - |
YOLOR-CSP | 52.9 | 120.4 | 640 | 106 | 51.1% / 50.8% | 69.6% | 55.7% | 31.7% | 55.3% | 64.7% |
YOLOR-CSP-X | 96.9 | 226.8 | 640 | 87 | 53.0% / 52.7% | 71.4% | 57.9% | 33.7% | 57.1% | 66.8% |
YOLOv7-tiny-SiLU | 6.2 | 13.8 | 640 | 286 | 38.7% / 38.7% | 56.7% | 41.7% | 18.8% | 42.4% | 51.9% |
YOLOv7 | 36.9 | 104.7 | 640 | 161 | 51.4% / 51.2% | 69.7% | 55.9% | 31.8% | 55.5% | 65.0% |
YOLOv7-X | 71.3 | 189.9 | 640 | 114 | 53.1% / 52.9% | 71.2% | 57.8% | 33.8% | 57.1% | 67.4% |
YOLOv5-N6 (r6.1) | 3.2 | 18.4 | 1280 | 123 | - / 36.0% | - | - | - | - | - |
YOLOv5-S6 (r6.1) | 12.6 | 67.2 | 1280 | 122 | - / 44.8% | - | - | - | - | - |
YOLOv5-M6 (r6.1) | 35.7 | 200.0 | 1280 | 90 | - / 51.3% | - | - | - | - | - |
YOLOv5-L6 (r6.1) | 76.8 | 445.6 | 1280 | 63 | - / 53.7% | - | - | - | - | - |
YOLOv5-X6 (r6.1) | 140.7 | 839.2 | 1280 | 38 | - / 55.0% | - | - | - | - | - |
YOLOR-P6 | 37.2 | 325.6 | 1280 | 76 | 53.9% / 53.5% | 71.4% | 58.9% | 36.1% | 57.7% | 65.6% |
YOLOR-W6 | 79.8 | 453.2 | 1280 | 66 | 55.2% / 54.8% | 72.7% | 60.5% | 37.7% | 59.1% | 67.1% |
YOLOR-E6 | 115.8 | 683.2 | 1280 | 45 | 55.8% / 55.7% | 73.4% | 61.1% | 38.4% | 59.7% | 67.7% |
YOLOR-D6 | 151.7 | 935.6 | 1280 | 34 | 56.5% / 56.1% | 74.1% | 61.9% | 38.9% | 60.4% | 68.7% |
YOLOv7-W6 | 70.4 | 360.0 | 1280 | 84 | 54.9% / 54.6% | 72.6% | 60.1% | 37.3% | 58.7% | 67.1% |
YOLOv7-E6 | 97.2 | 515.2 | 1280 | 56 | 56.0% / 55.9% | 73.5% | 61.2% | 38.0% | 59.9% | 68.4% |
YOLOv7-D6 | 154.7 | 806.8 | 1280 | 44 | 56.6% / 56.3% | 74.0% | 61.8% | 38.8% | 60.1% | 69.5% |
YOLOv7-E6E | 151.7 | 843.2 | 1280 | 36 | 56.8% / 56.8% | 74.4% | 62.1% | 39.3% | 60.5% | 69.0% |
概要
リアルタイム物体検出は、多オブジェクト追跡、自律走行、ロボット工学、医療画像解析など、多くのコンピュータビジョンシステムにおいて重要な要素である。近年、リアルタイム物体検出の開発は、効率的なアーキテクチャの設計と、様々なCPU、GPU、およびニューラル・プロセッシング・ユニット(NPU)の推論速度の向上に焦点を当てています。YOLOv7は、エッジからクラウドまで、モバイルGPU GPU デバイスの両方をサポートしています。
YOLOv7は、アーキテクチャの最適化に焦点を当てた従来のリアルタイム物体検出器とは異なり、学習プロセスの最適化に焦点を当てています。これには、推論コストを増加させることなく物体検出の精度を向上させるように設計されたモジュールと最適化手法が含まれ、「訓練可能なフリービーバッグ(trainable bag-of-freebies)」として知られる概念である。
主な特徴
YOLOv7にはいくつかの重要な特徴がある:
-
モデルの再パラメータ化YOLOv7は、計画的な再パラメータ化モデルを提案する。これは、勾配伝搬経路の概念を持つ異なるネットワークの層に適用可能な戦略である。
-
動的なラベルの割り当て:複数の出力層を持つモデルのトレーニングは、新たな問題を提示する:「異なる分岐の出力に対して、どのように動的なターゲットを割り当てるか?この問題を解決するために、YOLOv7は、粗から細へのリードガイドラベル割り当てと呼ばれる新しいラベル割り当て方法を導入している。
-
拡張と複合スケーリング:YOLOv7は、パラメータと計算を効果的に利用できるリアルタイム物体検出のための「拡張」と「複合スケーリング」手法を提案している。
-
効率:YOLOv7が提案する方法は、最先端のリアルタイム物体検出器の約40%のパラメータと50%の計算量を効果的に削減することができ、より速い推論速度とより高い検出精度を有する。
使用例
本稿執筆時点では、Ultralytics YOLOv7のONNX TensorRT 推論のみをサポートしている。
ONNX エクスポート
YOLOv7ONNX モデルをUltralytics使用するには:
- (オプション)Ultralytics インストールしてONNX モデルをエクスポートすると、必要な依存関係が自動的にインストールされます:
pip install ultralytics
yolo export model=yolo11n.pt format=onnx
- YOLOv7レポのエクスポーターを使って、必要なYOLOv7モデルをエクスポートする:
git clone https://github.com/WongKinYiu/yolov7
cd yolov7
python export.py --weights yolov7-tiny.pt --grid --end2end --simplify --topk-all 100 --iou-thres 0.65 --conf-thres 0.35 --img-size 640 640 --max-wh 640
- 以下のスクリプトを使用して、ONNX モデルグラフをUltralytics 互換性があるように修正する:
import numpy as np
import onnx
from onnx import helper, numpy_helper
# Load the ONNX model
model_path = "yolov7/yolov7-tiny.onnx" # Replace with your model path
model = onnx.load(model_path)
graph = model.graph
# Fix input shape to batch size 1
input_shape = graph.input[0].type.tensor_type.shape
input_shape.dim[0].dim_value = 1
# Define the output of the original model
original_output_name = graph.output[0].name
# Create slicing nodes
sliced_output_name = f"{original_output_name}_sliced"
# Define initializers for slicing (remove the first value)
start = numpy_helper.from_array(np.array([1], dtype=np.int64), name="slice_start")
end = numpy_helper.from_array(np.array([7], dtype=np.int64), name="slice_end")
axes = numpy_helper.from_array(np.array([1], dtype=np.int64), name="slice_axes")
steps = numpy_helper.from_array(np.array([1], dtype=np.int64), name="slice_steps")
graph.initializer.extend([start, end, axes, steps])
slice_node = helper.make_node(
"Slice",
inputs=[original_output_name, "slice_start", "slice_end", "slice_axes", "slice_steps"],
outputs=[sliced_output_name],
name="SliceNode",
)
graph.node.append(slice_node)
# Define segment slicing
seg1_start = numpy_helper.from_array(np.array([0], dtype=np.int64), name="seg1_start")
seg1_end = numpy_helper.from_array(np.array([4], dtype=np.int64), name="seg1_end")
seg2_start = numpy_helper.from_array(np.array([4], dtype=np.int64), name="seg2_start")
seg2_end = numpy_helper.from_array(np.array([5], dtype=np.int64), name="seg2_end")
seg3_start = numpy_helper.from_array(np.array([5], dtype=np.int64), name="seg3_start")
seg3_end = numpy_helper.from_array(np.array([6], dtype=np.int64), name="seg3_end")
graph.initializer.extend([seg1_start, seg1_end, seg2_start, seg2_end, seg3_start, seg3_end])
# Create intermediate tensors for segments
segment_1_name = f"{sliced_output_name}_segment1"
segment_2_name = f"{sliced_output_name}_segment2"
segment_3_name = f"{sliced_output_name}_segment3"
# Add segment slicing nodes
graph.node.extend(
[
helper.make_node(
"Slice",
inputs=[sliced_output_name, "seg1_start", "seg1_end", "slice_axes", "slice_steps"],
outputs=[segment_1_name],
name="SliceSegment1",
),
helper.make_node(
"Slice",
inputs=[sliced_output_name, "seg2_start", "seg2_end", "slice_axes", "slice_steps"],
outputs=[segment_2_name],
name="SliceSegment2",
),
helper.make_node(
"Slice",
inputs=[sliced_output_name, "seg3_start", "seg3_end", "slice_axes", "slice_steps"],
outputs=[segment_3_name],
name="SliceSegment3",
),
]
)
# Concatenate the segments
concat_output_name = f"{sliced_output_name}_concat"
concat_node = helper.make_node(
"Concat",
inputs=[segment_1_name, segment_3_name, segment_2_name],
outputs=[concat_output_name],
axis=1,
name="ConcatSwapped",
)
graph.node.append(concat_node)
# Reshape to [1, -1, 6]
reshape_shape = numpy_helper.from_array(np.array([1, -1, 6], dtype=np.int64), name="reshape_shape")
graph.initializer.append(reshape_shape)
final_output_name = f"{concat_output_name}_batched"
reshape_node = helper.make_node(
"Reshape",
inputs=[concat_output_name, "reshape_shape"],
outputs=[final_output_name],
name="AddBatchDimension",
)
graph.node.append(reshape_node)
# Get the shape of the reshaped tensor
shape_node_name = f"{final_output_name}_shape"
shape_node = helper.make_node(
"Shape",
inputs=[final_output_name],
outputs=[shape_node_name],
name="GetShapeDim",
)
graph.node.append(shape_node)
# Extract the second dimension
dim_1_index = numpy_helper.from_array(np.array([1], dtype=np.int64), name="dim_1_index")
graph.initializer.append(dim_1_index)
second_dim_name = f"{final_output_name}_dim1"
gather_node = helper.make_node(
"Gather",
inputs=[shape_node_name, "dim_1_index"],
outputs=[second_dim_name],
name="GatherSecondDim",
)
graph.node.append(gather_node)
# Subtract from 100 to determine how many values to pad
target_size = numpy_helper.from_array(np.array([100], dtype=np.int64), name="target_size")
graph.initializer.append(target_size)
pad_size_name = f"{second_dim_name}_padsize"
sub_node = helper.make_node(
"Sub",
inputs=["target_size", second_dim_name],
outputs=[pad_size_name],
name="CalculatePadSize",
)
graph.node.append(sub_node)
# Build the [2, 3] pad array:
# 1st row -> [0, 0, 0] (no padding at the start of any dim)
# 2nd row -> [0, pad_size, 0] (pad only at the end of the second dim)
pad_starts = numpy_helper.from_array(np.array([0, 0, 0], dtype=np.int64), name="pad_starts")
graph.initializer.append(pad_starts)
zero_scalar = numpy_helper.from_array(np.array([0], dtype=np.int64), name="zero_scalar")
graph.initializer.append(zero_scalar)
pad_ends_name = "pad_ends"
concat_pad_ends_node = helper.make_node(
"Concat",
inputs=["zero_scalar", pad_size_name, "zero_scalar"],
outputs=[pad_ends_name],
axis=0,
name="ConcatPadEnds",
)
graph.node.append(concat_pad_ends_node)
pad_values_name = "pad_values"
concat_pad_node = helper.make_node(
"Concat",
inputs=["pad_starts", pad_ends_name],
outputs=[pad_values_name],
axis=0,
name="ConcatPadStartsEnds",
)
graph.node.append(concat_pad_node)
# Create Pad operator to pad with zeros
pad_output_name = f"{final_output_name}_padded"
pad_constant_value = numpy_helper.from_array(
np.array([0.0], dtype=np.float32),
name="pad_constant_value",
)
graph.initializer.append(pad_constant_value)
pad_node = helper.make_node(
"Pad",
inputs=[final_output_name, pad_values_name, "pad_constant_value"],
outputs=[pad_output_name],
mode="constant",
name="PadToFixedSize",
)
graph.node.append(pad_node)
# Update the graph's final output to [1, 100, 6]
new_output_type = onnx.helper.make_tensor_type_proto(
elem_type=graph.output[0].type.tensor_type.elem_type, shape=[1, 100, 6]
)
new_output = onnx.helper.make_value_info(name=pad_output_name, type_proto=new_output_type)
# Replace the old output with the new one
graph.output.pop()
graph.output.extend([new_output])
# Save the modified model
onnx.save(model, "yolov7-ultralytics.onnx")
- その後、修正したONNX モデルをロードし、Ultralytics 普通に推論を実行することができます:
from ultralytics import ASSETS, YOLO
model = YOLO("yolov7-ultralytics.onnx", task="detect")
results = model(ASSETS / "bus.jpg")
TensorRT エクスポート
-
ONNX Exportセクションのステップ1-2に従ってください。
-
をインストールします。
TensorRT
Python パッケージ:
pip install tensorrt
- 以下のスクリプトを実行して、修正したONNX モデルをTensorRT エンジンに変換する:
# Based off of https://github.com/NVIDIA/TensorRT/blob/release/10.7/samples/python/introductory_parser_samples/onnx_resnet50.py
import tensorrt as trt
TRT_LOGGER = trt.Logger(trt.Logger.WARNING)
def GiB(val):
return val * 1 << 30
def build_engine_onnx(model_file):
builder = trt.Builder(TRT_LOGGER)
network = builder.create_network(0)
config = builder.create_builder_config()
parser = trt.OnnxParser(network, TRT_LOGGER)
config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, GiB(4))
with open(model_file, "rb") as model:
if not parser.parse(model.read()):
print("ERROR: Failed to parse the ONNX file.")
for error in range(parser.num_errors):
print(parser.get_error(error))
return None
config.set_flag(trt.BuilderFlag.FP16)
engine_bytes = builder.build_serialized_network(network, config)
with open(model_file.replace("onnx", "engine"), "wb") as f:
f.write(engine_bytes)
build_engine_onnx("yolov7-ultralytics.onnx") # path to the modified ONNX file
- Ultralyticsモデルをロードして実行します:
from ultralytics import ASSETS, YOLO
model = YOLO("yolov7-ultralytics.engine", task="detect")
results = model(ASSETS / "bus.jpg")
引用と謝辞
リアルタイム物体検出の分野におけるYOLOv7作者の多大な貢献に感謝したい:
@article{wang2022yolov7,
title={YOLOv7: Trainable bag-of-freebies sets new state-of-the-art for real-time object detectors},
author={Wang, Chien-Yao and Bochkovskiy, Alexey and Liao, Hong-Yuan Mark},
journal={arXiv preprint arXiv:2207.02696},
year={2022}
}
オリジナルのYOLOv7論文はarXivに掲載されている。著者らは彼らの研究を公開し、コードベースはGitHubでアクセスできる。我々は、この分野を発展させ、より広いコミュニティが彼らの研究にアクセスできるようにした彼らの努力に感謝している。
よくあるご質問
YOLOv7とは何か、そしてなぜリアルタイム物体検出のブレークスルーと言われるのか?
YOLOv7は、圧倒的なスピードと精度を実現した最先端のリアルタイム物体検出モデルです。YOLOX、YOLOv5 、PPYOLOEといった他のモデルを、パラメータ使用量と推論速度の両方で凌駕している。YOLOv7の特徴的な機能には、モデルの再パラメータ化と動的ラベル割り当てがあり、推論コストを増加させることなく性能を最適化している。YOLOv7のアーキテクチャの詳細や、他の最先端の物体検出器との比較指標については、YOLOv7の論文を参照してください。
YOLOv7は、YOLOv4やYOLOv5 のような以前のモデル(YOLO )をどのように改良したのですか?
YOLOv7は、モデルの再パラメータ化や動的なラベル割り当てなど、学習プロセスを強化し、推論精度を向上させるいくつかのイノベーションを導入している。YOLOv5 と比較して、YOLOv7はスピードと精度を大幅に向上させた。例えば、YOLOv7-Xは、YOLOv5-Xと比較して、精度を2.2%向上させ、パラメータを22%削減した。詳細な比較は、性能表YOLOv7とSOTA物体検出器の比較でご覧いただけます。
YOLOv7をUltralytics のツールやプラットフォームで使用できますか?
現在のところ、Ultralytics YOLOv7のONNX TensorRT 推論のみをサポートしています。YOLOv7のONNX TensorRT エクスポートバージョンをUltralytics実行するには、使用例セクションをチェックしてください。
自分のデータセットを使ってカスタムYOLOv7モデルをトレーニングするには?
YOLOv7のカスタムモデルをインストールしてトレーニングするには、以下の手順に従ってください:
- YOLOv7リポジトリをクローンする:
git clone https://github.com/WongKinYiu/yolov7
- クローンしたディレクトリに移動し、依存関係をインストールする:
cd yolov7 pip install -r requirements.txt
-
データセットを準備し、リポジトリで提供されている使用方法に従ってモデルパラメータを設定します。 さらなるガイダンスについては、YOLOv7のGitHubリポジトリで最新情報とアップデートをご覧ください。
-
学習後、使用例に示すように、Ultralytics 使用するモデルをONNX またはTensorRT エクスポートすることができます。
YOLOv7で導入された主な機能と最適化について教えてください。
YOLOv7は、リアルタイムの物体検出に革命をもたらすいくつかの重要な機能を提供する:
- モデルの再パラメータ化:勾配伝搬経路を最適化することで、モデルのパフォーマンスを向上させる。
- ダイナミック・ラベル割り当て:異なるブランチ間の出力にダイナミックターゲットを割り当てるために、粗いリードから細かいリードへ誘導する方法を使用し、精度を向上。
- 拡張および複合スケーリング:パラメータと計算を効率的に利用し、様々なリアルタイム・アプリケーション向けにモデルを拡張します。
- 効率:他の最先端モデルと比較して、パラメータ数を40%削減し、計算量を50%削減しながら、より高速な推論を実現。
これらの機能の詳細については、YOLOv7の概要セクションを参照してください。