**Description**

Similarly to `@dag` and `@task` decorators, we can add a decorator to easily generate `TaskGroup` instances. 

**Use case / motivation**

This would be used for reusable pieces of DAGs. An example may be to hyperparameter tune an ML pipeline. You may want to generate several parallel subpipelines with different settings/parameters to be executed in parallel. At the end pull the result and decide the best parametrization.  

```py
@task
collect_dataset(...)
  pass

@task
def train_model(...)
   pass

@task_group
def movielens_model_pipeline(learning_rate: int, feature_list: list, dataset: XComArg):  
  dataset = filter_dataset(dataset, feature_list)
  train_model(dataset, learning_rate)

@dag
def movielens_hpt(dataset_path: str, feature_list:list=['year', 'director']):
  dataset = load_dataset(dataset_path)
  for i in range(0.1, 0.9, step=0.1):
    movielens_model_pipeline(i, feature_list, dataset)
  
  decide_best_model(...)
```




**Related Issues**

https://github.com/apache/airflow/issues?q=is%3Aopen+is%3Aissue+label%3AAIP-31


airflow/utils/task_group.py
"""Create a root TaskGroup with no group_id or parent."""
        return cls(group_id=None, dag=dag)
    @property
    def is_root(self) -> bool:
        """Returns True if this TaskGroup is the root TaskGroup. Otherwise False"""
        return not self.group_id
    def __iter__(self):
        for child in self.children.values():
            if isinstance(child, TaskGroup):
                yield from child
            else:
                yield child
    def add(self, task: Union["BaseOperator", "TaskGroup"]) -> None:
        """Add a task to this TaskGroup."""
        key = task.group_id if isinstance(task, TaskGroup) else task.task_id
        if key in self.children:
            raise DuplicateTaskIdFound(f"Task id '{key}' has already been added to the DAG")
        if isinstance(task, TaskGroup):
            if task.children:
                raise AirflowException("Cannot add a non-empty TaskGroup")
        self.children[key] = task  
    @property
    def group_id(self) -> Optional[str]:
        """group_id of this TaskGroup."""
        if self._parent_group and self._parent_group.prefix_group_id and self._parent_group.group_id:
            return self._parent_group.child_id(self._group_id)
        return self._group_id
    @property
    def label(self) -> Optional[str]: