نمودارها

نمودار

یک پروتو CalculatorGraphConfig توپولوژی و عملکرد یک گراف MediaPipe را مشخص می کند. هر node در نمودار یک ماشین‌حساب یا زیرگراف خاص را نشان می‌دهد و پیکربندی‌های لازم را مشخص می‌کند، مانند نوع ماشین‌حساب/زیرگراف ثبت‌شده، ورودی‌ها، خروجی‌ها و فیلدهای اختیاری، مانند گزینه‌های خاص گره، خط‌مشی ورودی و اجراکننده، که در Synchronization بحث شده‌اند.

CalculatorGraphConfig چندین فیلد دیگر برای پیکربندی تنظیمات سطح نمودار سراسری دارد، به عنوان مثال پیکربندی‌های اجرایی گراف، تعداد رشته‌ها و حداکثر اندازه صف جریان‌های ورودی. چندین تنظیمات در سطح نمودار برای تنظیم عملکرد نمودار در پلتفرم های مختلف (مثلاً دسکتاپ در مقابل موبایل) مفید هستند. به عنوان مثال، در تلفن همراه، اتصال یک ماشین حساب استنتاج مدل سنگین به یک مجری مجزا می‌تواند عملکرد یک برنامه بلادرنگ را بهبود بخشد، زیرا محلی بودن رشته را امکان‌پذیر می‌کند.

در زیر یک مثال بی اهمیت CalculatorGraphConfig است که در آن ما مجموعه ای از ماشین حساب های عبور داریم:

# This graph named main_pass_throughcals_nosubgraph.pbtxt contains 4
# passthrough calculators.
input_stream: "in"
output_stream: "out"
node {
    calculator: "PassThroughCalculator"
    input_stream: "in"
    output_stream: "out1"
}
node {
    calculator: "PassThroughCalculator"
    input_stream: "out1"
    output_stream: "out2"
}
node {
    calculator: "PassThroughCalculator"
    input_stream: "out2"
    output_stream: "out3"
}
node {
    calculator: "PassThroughCalculator"
    input_stream: "out3"
    output_stream: "out"
}

MediaPipe یک نمایش C++ جایگزین برای نمودارهای پیچیده (به عنوان مثال خطوط لوله ML، مدیریت ابرداده مدل، گره های اختیاری و غیره) ارائه می دهد. نمودار بالا ممکن است به شکل زیر باشد:

CalculatorGraphConfig BuildGraphConfig() {
  Graph graph;

  // Graph inputs
  Stream<AnyType> in = graph.In(0).SetName("in");

  auto pass_through_fn = [](Stream<AnyType> in,
                            Graph& graph) -> Stream<AnyType> {
    auto& node = graph.AddNode("PassThroughCalculator");
    in.ConnectTo(node.In(0));
    return node.Out(0);
  };

  Stream<AnyType> out1 = pass_through_fn(in, graph);
  Stream<AnyType> out2 = pass_through_fn(out1, graph);
  Stream<AnyType> out3 = pass_through_fn(out2, graph);
  Stream<AnyType> out4 = pass_through_fn(out3, graph);

  // Graph outputs
  out4.SetName("out").ConnectTo(graph.Out(0));

  return graph.GetConfig();
}

جزئیات بیشتر را در Building Graphs در C++ مشاهده کنید.

زیرگراف

برای مدولار کردن CalculatorGraphConfig به زیر ماژول ها و کمک به استفاده مجدد از راه حل های درک، یک نمودار MediaPipe را می توان به عنوان یک Subgraph تعریف کرد. رابط عمومی یک زیرگراف شامل مجموعه ای از جریان های ورودی و خروجی شبیه به رابط عمومی یک ماشین حساب است. سپس می‌توان زیرگراف را در CalculatorGraphConfig گنجاند که انگار یک ماشین‌حساب است. هنگامی که یک نمودار MediaPipe از یک CalculatorGraphConfig بارگیری می شود، هر گره زیرگراف با نمودار مربوطه از ماشین حساب ها جایگزین می شود. در نتیجه، معنایی و عملکرد زیرگراف با نمودار مربوط به ماشین حساب یکسان است.

در زیر مثالی از نحوه ایجاد زیرگراف به نام TwoPassThroughSubgraph آورده شده است.

  1. تعریف زیرگراف

    # This subgraph is defined in two_pass_through_subgraph.pbtxt
    # and is registered as "TwoPassThroughSubgraph"
    
    type: "TwoPassThroughSubgraph"
    input_stream: "out1"
    output_stream: "out3"
    
    node {
        calculator: "PassThroughCalculator"
        input_stream: "out1"
        output_stream: "out2"
    }
    node {
        calculator: "PassThroughCalculator"
        input_stream: "out2"
        output_stream: "out3"
    }
    

    رابط عمومی زیرگراف شامل موارد زیر است:

    • جریان های ورودی نمودار
    • جریان های خروجی نمودار
    • بسته های جانبی ورودی نمودار
    • بسته های جانبی خروجی نمودار
  2. زیرگراف را با استفاده از قانون BUILD mediapipe_simple_subgraph ثبت کنید. پارامتر register_as نام جزء را برای زیرگراف جدید تعریف می کند.

    # Small section of BUILD file for registering the "TwoPassThroughSubgraph"
    # subgraph for use by main graph main_pass_throughcals.pbtxt
    
    mediapipe_simple_subgraph(
        name = "twopassthrough_subgraph",
        graph = "twopassthrough_subgraph.pbtxt",
        register_as = "TwoPassThroughSubgraph",
        deps = [
                "//mediapipe/calculators/core:pass_through_calculator",
                "//mediapipe/framework:calculator_graph",
        ],
    )
    
  3. از زیرگراف در نمودار اصلی استفاده کنید.

    # This main graph is defined in main_pass_throughcals.pbtxt
    # using subgraph called "TwoPassThroughSubgraph"
    
    input_stream: "in"
    node {
        calculator: "PassThroughCalculator"
        input_stream: "in"
        output_stream: "out1"
    }
    node {
        calculator: "TwoPassThroughSubgraph"
        input_stream: "out1"
        output_stream: "out3"
    }
    node {
        calculator: "PassThroughCalculator"
        input_stream: "out3"
        output_stream: "out4"
    }
    

گزینه های نمودار

می‌توان یک پروتوباف «گزینه‌های نمودار» را برای یک گراف MediaPipe مشابه پروتوباف Calculator Options مشخص‌شده برای یک ماشین حساب MediaPipe تعیین کرد. این "گزینه های نمودار" را می توان در جایی که یک گراف فراخوانی می شود مشخص کرد و برای پر کردن گزینه های ماشین حساب و گزینه های فرعی در نمودار استفاده کرد.

در CalculatorGraphConfig ، گزینه های گراف را می توان برای یک زیرگراف دقیقاً مانند گزینه های ماشین حساب مشخص کرد، همانطور که در زیر نشان داده شده است:

node {
  calculator: "FlowLimiterCalculator"
  input_stream: "image"
  output_stream: "throttled_image"
  node_options: {
    [type.googleapis.com/mediapipe.FlowLimiterCalculatorOptions] {
      max_in_flight: 1
    }
  }
}

node {
  calculator: "FaceDetectionSubgraph"
  input_stream: "IMAGE:throttled_image"
  node_options: {
    [type.googleapis.com/mediapipe.FaceDetectionOptions] {
      tensor_width: 192
      tensor_height: 192
    }
  }
}

در CalculatorGraphConfig ، گزینه‌های نمودار را می‌توان پذیرفت و برای پر کردن گزینه‌های ماشین حساب، مانند شکل زیر، استفاده کرد:

graph_options: {
  [type.googleapis.com/mediapipe.FaceDetectionOptions] {}
}

node: {
  calculator: "ImageToTensorCalculator"
  input_stream: "IMAGE:image"
  node_options: {
    [type.googleapis.com/mediapipe.ImageToTensorCalculatorOptions] {
        keep_aspect_ratio: true
        border_mode: BORDER_ZERO
    }
  }
  option_value: "output_tensor_width:options/tensor_width"
  option_value: "output_tensor_height:options/tensor_height"
}

node {
  calculator: "InferenceCalculator"
  node_options: {
    [type.googleapis.com/mediapipe.InferenceCalculatorOptions] {}
  }
  option_value: "delegate:options/delegate"
  option_value: "model_path:options/model_path"
}

در این مثال، FaceDetectionSubgraph گزینه گراف protobuf FaceDetectionOptions را می پذیرد. FaceDetectionOptions برای تعریف برخی از مقادیر فیلد در گزینه های ماشین حساب ImageToTensorCalculatorOptions و برخی از مقادیر فیلد در گزینه های فرعی InferenceCalculatorOptions استفاده می شود. مقادیر فیلد با استفاده از syntax option_value: تعریف می شوند.

در CalculatorGraphConfig::Node protobuf، فیلدهای node_options: و option_value: با هم مقادیر گزینه را برای ماشین حسابی مانند ImageToTensorCalculator تعریف می کنند. فیلد node_options: مجموعه ای از مقادیر ثابت تحت اللفظی را با استفاده از نحو متنی protobuf تعریف می کند. هر فیلد option_value: مقدار یک فیلد پروتوباف را با استفاده از اطلاعات نمودار محصور کننده، به‌ویژه از مقادیر فیلد گزینه‌های گراف گراف محصور، تعیین می‌کند. در مثال بالا، option_value: "output_tensor_width:options/tensor_width" فیلد ImageToTensorCalculatorOptions.output_tensor_width را با استفاده از مقدار FaceDetectionOptions.tensor_width تعریف می‌کند.

نحو option_value: شبیه نحو input_stream: . نحو عبارت option_value: "LHS:RHS" . LHS یک فیلد گزینه ماشین حساب و RHS یک فیلد گزینه گراف را مشخص می کند. به طور خاص، LHS و RHS هر کدام از یک سری نام فیلدهای protobuf تشکیل شده‌اند که پیام‌های protobuf تودرتو و فیلدهایی را که با «/» از هم جدا شده‌اند، شناسایی می‌کنند. این به عنوان نحو "ProtoPath" شناخته می شود. پیام‌های تودرتو که در LHS یا RHS ارجاع می‌شوند باید قبلاً در protobuf محتوی تعریف شده باشند تا با استفاده از option_value: .

چرخه ها

به‌طور پیش‌فرض، MediaPipe نیاز دارد که نمودارهای ماشین حساب غیرچرخه باشند و چرخه‌های یک نمودار را به‌عنوان خطا در نظر می‌گیرد. اگر قرار است یک نمودار دارای چرخه باشد، چرخه ها باید در پیکربندی گراف حاشیه نویسی شوند. در این صفحه نحوه انجام این کار توضیح داده شده است.

توجه: رویکرد فعلی آزمایشی است و ممکن است تغییر کند. ما از بازخورد شما استقبال می کنیم.

لطفاً از آزمون واحد CalculatorGraphTest.Cycle در mediapipe/framework/calculator_graph_test.cc به عنوان کد نمونه استفاده کنید. در زیر نمودار چرخه ای در آزمون نشان داده شده است. sum خروجی جمع کننده مجموع اعداد صحیح تولید شده توسط ماشین حساب منبع عدد صحیح است.

a cyclic graph that adds a stream of integers

این نمودار ساده تمام مسائل مربوط به پشتیبانی از نمودارهای چرخه ای را نشان می دهد.

حاشیه نویسی لبه پشتی

ما نیاز داریم که یک یال در هر چرخه به عنوان لبه پشتی حاشیه نویسی شود. این اجازه می دهد تا مرتب سازی توپولوژیکی MediaPipe پس از حذف تمام لبه های پشتی کار کند.

معمولاً راه های مختلفی برای انتخاب لبه های پشتی وجود دارد. اینکه کدام لبه‌ها به‌عنوان لبه‌های پشتی علامت‌گذاری می‌شوند، بر کدام گره‌ها به‌عنوان بالادست و کدام گره‌ها به‌عنوان پایین‌دست در نظر گرفته می‌شوند، که به نوبه خود بر اولویت‌هایی که MediaPipe به گره‌ها اختصاص می‌دهد تأثیر می‌گذارد.

به عنوان مثال، تست CalculatorGraphTest.Cycle لبه old_sum را به عنوان یک لبه پشتی علامت گذاری می کند، بنابراین گره Delay به عنوان گره پایین دست گره جمع کننده در نظر گرفته می شود و اولویت بیشتری دارد. یا می‌توانیم sum ورودی به گره تاخیر را به عنوان لبه پشتی علامت‌گذاری کنیم، در این صورت گره تاخیری به عنوان گره بالادست گره جمع‌کننده در نظر گرفته می‌شود و اولویت کمتری دارد.

بسته اولیه

برای اینکه ماشین‌حساب جمع‌کننده زمانی که اولین عدد صحیح از منبع عدد صحیح می‌رسد قابل اجرا باشد، به یک بسته اولیه با مقدار 0 و با همان مهر زمانی روی جریان ورودی old_sum به جمع‌کننده نیاز داریم. این بسته اولیه باید توسط ماشین حساب تاخیر در روش Open() خروجی شود.

تاخیر در یک حلقه

هر حلقه باید برای تراز کردن خروجی sum قبلی با ورودی عدد صحیح بعدی تاخیر داشته باشد. این نیز توسط گره تاخیر انجام می شود. بنابراین گره تاخیر باید موارد زیر را در مورد مُهر زمانی ماشین حساب منبع عدد صحیح بداند:

  • مهر زمانی اولین خروجی

  • دلتای مهر زمانی بین خروجی های متوالی.

ما قصد داریم یک خط‌مشی زمان‌بندی جایگزین اضافه کنیم که فقط به سفارش بسته‌ها اهمیت می‌دهد و مهرهای زمانی بسته را نادیده می‌گیرد، که این ناراحتی را از بین می‌برد.

خاتمه زودهنگام ماشین حساب هنگامی که یک جریان ورودی انجام می شود

به‌طور پیش‌فرض، MediaPipe زمانی که تمام جریان‌های ورودی آن تمام شد، متد Close() یک ماشین‌حساب غیر منبع را فراخوانی می‌کند. در نمودار مثال، می خواهیم به محض اینکه منبع عدد صحیح تمام شد، گره جمع کننده را متوقف کنیم. این کار با پیکربندی گره جمع کننده با یک کنترل کننده جریان ورودی جایگزین، EarlyCloseInputStreamHandler انجام می شود.

کد منبع مربوطه

ماشین حساب تاخیر

به کد موجود در Open() که بسته اولیه را خروجی می دهد و کد موجود در Process() که یک تاخیر (واحد) به بسته های ورودی اضافه می کند توجه کنید. همانطور که در بالا ذکر شد، این گره تاخیری فرض می کند که جریان خروجی آن در کنار یک جریان ورودی با مهرهای زمانی بسته 0، 1، 2، 3، ... استفاده می شود.

class UnitDelayCalculator : public Calculator {
 public:
  static absl::Status FillExpectations(
      const CalculatorOptions& extendable_options, PacketTypeSet* inputs,
      PacketTypeSet* outputs, PacketTypeSet* input_side_packets) {
    inputs->Index(0)->Set<int>("An integer.");
    outputs->Index(0)->Set<int>("The input delayed by one time unit.");
    return absl::OkStatus();
  }

  absl::Status Open() final {
    Output()->Add(new int(0), Timestamp(0));
    return absl::OkStatus();
  }

  absl::Status Process() final {
    const Packet& packet = Input()->Value();
    Output()->AddPacket(packet.At(packet.Timestamp().NextAllowedInStream()));
    return absl::OkStatus();
  }
};

پیکربندی نمودار

به حاشیه نویسی back_edge و input_stream_handler جایگزین توجه کنید.

node {
  calculator: 'GlobalCountSourceCalculator'
  input_side_packet: 'global_counter'
  output_stream: 'integers'
}
node {
  calculator: 'IntAdderCalculator'
  input_stream: 'integers'
  input_stream: 'old_sum'
  input_stream_info: {
    tag_index: ':1'  # 'old_sum'
    back_edge: true
  }
  output_stream: 'sum'
  input_stream_handler {
    input_stream_handler: 'EarlyCloseInputStreamHandler'
  }
}
node {
  calculator: 'UnitDelayCalculator'
  input_stream: 'sum'
  output_stream: 'old_sum'
}