第12章:実践的なワークフロー設計

12.1 ケーススタディ:AI協働型モデル開発プロジェクト

本章では、第2章で学んだAI協働パターンを大規模なチーム開発に適用した実例を紹介します。

プロジェクト概要

AI協働型画像分類モデル開発プロジェクト

project:
  name: "AI-Collaborative ImageNet Classification Model"
  description: "Production-grade image classification system with AI collaboration"
  
  # 第2章のAI協働パターンを各チームで活用
  teams:
    - ml_research: 
        role: "Model architecture development"
        ai_collaboration: "Claudeによる複数設計案の生成と評価、Perplexity/Semantic Scholar連携による論文要約と関連研究の自動抽出、実験計画の最適化提案"
        ai_tools: ["Claude", "Perplexity", "GitHub Copilot"]
        benefits: ["設計時間50%短縮", "論文調査効率3倍向上", "実験計画の網羅性向上"]
    - ml_engineering: 
        role: "Training pipeline and optimization"
        ai_collaboration: "GitHub Copilotによるパイプラインコード生成、Claudeによるハイパーパラメータ最適化戦略の提案、バグ修正とリファクタリング支援"
        ai_tools: ["GitHub Copilot", "Claude", "Amazon CodeWhisperer"]
        benefits: ["コード作成時間60%短縮", "バグ発生率40%削減", "最適化精度向上"]
    - data_engineering: 
        role: "Dataset preparation and augmentation"
        ai_collaboration: "Copilotによるデータ前処理コード生成、Claudeによるデータ品質チェックロジック設計、異常値検出アルゴリズムの自動生成"
        ai_tools: ["GitHub Copilot", "Claude", "Tabnine"]
        benefits: ["前処理時間70%短縮", "データ品質向上", "異常検出精度向上"]
    - mlops: 
        role: "Deployment and monitoring"
        ai_collaboration: "GitHub Actionsワークフロー自動生成、監視ダッシュボードコードの生成、アラート条件の最適化提案、インフラコードのレビューと改善"
        ai_tools: ["GitHub Copilot", "Claude", "AWS CodeGuru"]
        benefits: ["デプロイ時間80%短縮", "監視精度向上", "運用コスト削減"]
  
  # 第2章のテンプレートを標準化
  collaboration_standards:
    issue_template: "第2章のAI最適化Issueテンプレートを使用"
    pr_template: "AI協働履歴を含むPRテンプレートを使用"
    review_process: "AI協働メトリクスによる品質測定"
    
  phases:
    1_research:
      duration: "2 weeks"
      deliverables:
        - "Model architecture design"
        - "Baseline experiments"
        
    2_development:
      duration: "4 weeks"
      deliverables:
        - "Training pipeline"
        - "Hyperparameter tuning"
        - "Model optimization"
        
    3_productionization:
      duration: "2 weeks"
      deliverables:
        - "Model serving API"
        - "Monitoring dashboard"
        - "Performance benchmarks"

リポジトリ構造

モノレポ vs マルチレポ

# モノレポ構造(推奨)
image-classification/
├── .github/
│   ├── workflows/
│   │   ├── train.yml
│   │   ├── evaluate.yml
│   │   └── deploy.yml
│   └── CODEOWNERS
├── data/
│   ├── raw/              # Git LFSまたは外部ストレージ
│   ├── processed/
│   └── scripts/
├── models/
│   ├── architectures/
│   ├── checkpoints/      # Git LFSまたは外部ストレージ
│   └── configs/
├── src/
│   ├── data_loading/
│   ├── training/
│   ├── evaluation/
│   └── serving/
├── experiments/
│   ├── baseline/
│   ├── architecture_search/
│   └── hyperparameter_tuning/
├── notebooks/
│   ├── exploration/
│   └── visualization/
├── tests/
├── docs/
├── requirements/
│   ├── base.txt
│   ├── dev.txt
│   └── prod.txt
└── README.md

ブランチ戦略

ML開発向けGit Flow

gitGraph
    commit id: "Initial"
    branch develop
    checkout develop
    commit id: "Setup"
    
    branch feature/data-pipeline
    checkout feature/data-pipeline
    commit id: "Add loader"
    commit id: "Add augmentation"
    checkout develop
    merge feature/data-pipeline
    
    branch experiment/resnet50
    checkout experiment/resnet50
    commit id: "ResNet50 base"
    commit id: "Tune hyperparams"
    
    branch experiment/efficientnet
    checkout experiment/efficientnet
    commit id: "EfficientNet base"
    commit id: "Optimize"
    
    checkout develop
    merge experiment/resnet50
    
    branch release/v1.0
    checkout release/v1.0
    commit id: "Prepare release"
    
    checkout main
    merge release/v1.0 tag: "v1.0"
    
    checkout develop
    merge release/v1.0

実装例

プロジェクト初期設定スクリプト

# scripts/setup_project.py
import os
import subprocess
from pathlib import Path

class ProjectSetup:
    def __init__(self, project_name, github_org):
        self.project_name = project_name
        self.github_org = github_org
        self.repo_name = f"{github_org}/{project_name}"
    
    def create_repository(self):
        """GitHubリポジトリを作成"""
        subprocess.run([
            'gh', 'repo', 'create', 
            self.repo_name,
            '--private',
            '--description', 'Image classification model development',
            '--clone'
        ])
    
    def setup_branch_protection(self):
        """ブランチ保護ルールを設定"""
        rules = {
            'main': {
                'required_reviews': 2,
                'dismiss_stale_reviews': True,
                'require_code_owner_reviews': True,
                'required_status_checks': [
                    'test-suite',
                    'security-scan',
                    'model-validation'
                ]
            },
            'develop': {
                'required_reviews': 1,
                'required_status_checks': ['test-suite']
            }
        }
        
        for branch, config in rules.items():
            self._create_branch_protection(branch, config)
    
    def setup_labels(self):
        """Issue/PRラベルを設定"""
        labels = [
            # タイプ
            ('type:bug', 'd73a4a', 'Something isn\'t working'),
            ('type:feature', '0075ca', 'New feature or request'),
            ('type:experiment', '7057ff', 'ML experiment'),
            
            # 優先度
            ('priority:high', 'e11d21', 'High priority'),
            ('priority:medium', 'fbca04', 'Medium priority'),
            ('priority:low', '0e8a16', 'Low priority'),
            
            # コンポーネント
            ('component:data', '1d76db', 'Data pipeline'),
            ('component:model', '5319e7', 'Model architecture'),
            ('component:training', 'b60205', 'Training pipeline'),
            ('component:serving', 'ee0701', 'Model serving'),
            
            # ステータス
            ('status:in-progress', 'ededed', 'Work in progress'),
            ('status:blocked', 'd93f0b', 'Blocked by dependencies'),
            ('status:review-needed', '006b75', 'Needs review')
        ]
        
        for name, color, description in labels:
            subprocess.run([
                'gh', 'label', 'create',
                name,
                '--color', color,
                '--description', description,
                '--repo', self.repo_name
            ])
    
    def create_issue_templates(self):
        """Issueテンプレートを作成"""
        templates_dir = Path('.github/ISSUE_TEMPLATE')
        templates_dir.mkdir(parents=True, exist_ok=True)
        
        # 実験提案テンプレート
        experiment_template = """---
name: Experiment Proposal
about: Propose a new ML experiment
title: '[EXPERIMENT] '
labels: 'type:experiment'
assignees: ''
---

## Hypothesis
<!-- What do you want to validate? -->

## Proposed Approach
<!-- Describe the experiment setup -->

### Model Architecture
<!-- Describe the model architecture -->

### Training Configuration
- Dataset:
- Batch size:
- Learning rate:
- Epochs:
- Other hyperparameters:

## Success Metrics
<!-- How will you measure success? -->
- Primary metric:
- Secondary metrics:

## Resource Requirements
- GPU hours:
- Storage:
- Timeline:

## Risks and Mitigation
<!-- What could go wrong and how to handle it? -->
"""
        
        (templates_dir / 'experiment_proposal.md').write_text(experiment_template)

11.1.2 Issue駆動開発の実践

Issue管理のベストプラクティス

Issueの種類と使い分け

# issue_manager.py
from github import Github
from datetime import datetime, timedelta

class IssueManager:
    def __init__(self, repo):
        self.repo = repo
        self.issue_types = {
            'experiment': {
                'label': 'type:experiment',
                'template': 'experiment_proposal.md',
                'auto_assign': ['ml-research-team']
            },
            'bug': {
                'label': 'type:bug',
                'template': 'bug_report.md',
                'auto_assign': ['ml-engineering-team']
            },
            'feature': {
                'label': 'type:feature',
                'template': 'feature_request.md',
                'auto_assign': ['product-team']
            },
            'data': {
                'label': 'component:data',
                'template': 'data_issue.md',
                'auto_assign': ['data-team']
            }
        }
    
    def create_experiment_issue(self, hypothesis, approach, metrics):
        """実験用Issueを作成"""
        title = f"[EXPERIMENT] {hypothesis[:50]}..."
        
        body = f"""
## Hypothesis
{hypothesis}

## Approach
{approach}

## Success Metrics
{metrics}

## Tracking
- [ ] Baseline established
- [ ] Experiment implemented
- [ ] Results documented
- [ ] Decision made

## Results
<!-- To be filled after experiment -->

| Metric | Baseline | Experiment | Improvement |
|--------|----------|------------|-------------|
| Accuracy | - | - | - |
| F1 Score | - | - | - |
| Inference Time | - | - | - |
"""
        
        issue = self.repo.create_issue(
            title=title,
            body=body,
            labels=['type:experiment', 'status:planning']
        )
        
        # 実験用ブランチを自動作成
        branch_name = f"experiment/issue-{issue.number}"
        self.create_branch(branch_name)
        
        return issue
    
    def link_pr_to_issue(self, pr_number, issue_number):
        """PRとIssueを関連付け"""
        pr = self.repo.get_pull(pr_number)
        issue = self.repo.get_issue(issue_number)
        
        # PR本文を更新
        current_body = pr.body or ""
        updated_body = f"{current_body}\n\nCloses #{issue_number}"
        pr.edit(body=updated_body)
        
        # Issueにコメント
        issue.create_comment(
            f"Pull Request #{pr_number} has been created to address this issue."
        )
    
    def track_experiment_progress(self, issue_number, metrics):
        """実験の進捗を追跡"""
        issue = self.repo.get_issue(issue_number)
        
        # 結果テーブルを更新
        comment = f"""
## Experiment Update - {datetime.now().strftime('%Y-%m-%d')}

### Results
| Metric | Value | vs Baseline |
|--------|-------|-------------|
| Accuracy | {metrics['accuracy']:.4f} | {metrics['accuracy_diff']:+.4f} |
| F1 Score | {metrics['f1']:.4f} | {metrics['f1_diff']:+.4f} |
| Inference Time | {metrics['inference_ms']:.1f}ms | {metrics['time_diff']:+.1f}ms |

### Artifacts
- Model checkpoint: `experiments/issue-{issue_number}/model_best.pth`
- Training logs: `experiments/issue-{issue_number}/train.log`
- Evaluation report: `experiments/issue-{issue_number}/eval_report.md`
"""
        
        issue.create_comment(comment)
        
        # ラベルを更新
        issue.add_to_labels('status:results-available')

スプリント管理

プロジェクトボードの活用

# sprint_manager.py
class SprintManager:
    def __init__(self, repo, project_id):
        self.repo = repo
        self.project = self.get_project(project_id)
        
    def create_sprint(self, sprint_number, start_date, duration_days=14):
        """新しいスプリントを作成"""
        sprint_name = f"Sprint {sprint_number}"
        
        # マイルストーンを作成
        milestone = self.repo.create_milestone(
            title=sprint_name,
            due_on=start_date + timedelta(days=duration_days),
            description=f"Sprint {sprint_number} goals and deliverables"
        )
        
        # プロジェクトボードのカラムを準備
        columns = ['Backlog', 'To Do', 'In Progress', 'In Review', 'Done']
        
        return {
            'milestone': milestone,
            'start_date': start_date,
            'end_date': start_date + timedelta(days=duration_days),
            'columns': columns
        }
    
    def plan_sprint(self, sprint, issues):
        """スプリントの計画"""
        total_points = 0
        sprint_issues = []
        
        for issue in issues:
            # ストーリーポイントを取得(ラベルから)
            points = self.get_story_points(issue)
            
            if total_points + points <= self.team_velocity:
                # スプリントに追加
                issue.edit(milestone=sprint['milestone'])
                sprint_issues.append(issue)
                total_points += points
        
        return {
            'planned_issues': sprint_issues,
            'total_points': total_points,
            'capacity_usage': total_points / self.team_velocity
        }
    
    def generate_sprint_report(self, sprint):
        """スプリントレポートを生成"""
        milestone = sprint['milestone']
        
        # 完了したIssueを取得
        completed = list(self.repo.get_issues(
            milestone=milestone,
            state='closed'
        ))
        
        # 未完了のIssueを取得
        incomplete = list(self.repo.get_issues(
            milestone=milestone,
            state='open'
        ))
        
        report = f"""
# {milestone.title} Report

## Summary
- **Duration**: {sprint['start_date']} to {sprint['end_date']}
- **Completed**: {len(completed)} issues
- **Incomplete**: {len(incomplete)} issues
- **Completion Rate**: {len(completed) / (len(completed) + len(incomplete)) * 100:.1f}%

## Completed Items
{self._format_issues(completed)}

## Incomplete Items
{self._format_issues(incomplete)}

## Velocity Metrics
- **Planned**: {self._calculate_points(completed + incomplete)} points
- **Completed**: {self._calculate_points(completed)} points
- **Velocity**: {self._calculate_points(completed) / 14:.1f} points/day

## Key Achievements
{self._extract_achievements(completed)}

## Blockers and Challenges
{self._extract_blockers(incomplete)}
"""
        
        return report

11.1.3 Feature Branchワークフロー

ブランチ命名規則

構造化された命名

# branch_manager.py
class BranchManager:
    def __init__(self, repo):
        self.repo = repo
        self.branch_patterns = {
            'feature': 'feature/{issue_number}-{description}',
            'experiment': 'experiment/{model}-{variant}',
            'bugfix': 'bugfix/{issue_number}-{description}',
            'hotfix': 'hotfix/{severity}-{description}',
            'release': 'release/v{version}',
            'data': 'data/{dataset}-{version}'
        }
    
    def create_feature_branch(self, issue_number, description):
        """Feature branchを作成"""
        # 説明をURL-safeに変換
        safe_description = self._sanitize_description(description)
        
        branch_name = self.branch_patterns['feature'].format(
            issue_number=issue_number,
            description=safe_description
        )
        
        # ブランチを作成
        base_branch = self.repo.get_branch('develop')
        self.repo.create_git_ref(
            ref=f'refs/heads/{branch_name}',
            sha=base_branch.commit.sha
        )
        
        # ブランチ保護ルール(オプション)
        if self._should_protect_branch(branch_name):
            self._add_branch_protection(branch_name)
        
        return branch_name
    
    def create_experiment_branch(self, model_name, variant):
        """実験用ブランチを作成"""
        branch_name = self.branch_patterns['experiment'].format(
            model=model_name.lower(),
            variant=variant.lower()
        )
        
        # メタデータを含むREADMEを作成
        readme_content = f"""
# Experiment: {model_name} - {variant}

## Configuration
- Base Model: {model_name}
- Variant: {variant}
- Created: {datetime.now().isoformat()}

## Hypothesis
[To be filled]

## Results
[To be updated after experiments]
"""
        
        # ブランチを作成してREADMEをコミット
        self._create_branch_with_file(
            branch_name,
            f'experiments/{model_name}_{variant}/README.md',
            readme_content
        )
        
        return branch_name
    
    def merge_strategy(self, source_branch, target_branch):
        """マージ戦略を決定"""
        strategies = {
            ('experiment/*', 'develop'): 'squash',
            ('feature/*', 'develop'): 'merge',
            ('develop', 'main'): 'merge',
            ('hotfix/*', 'main'): 'merge',
            ('release/*', 'main'): 'merge'
        }
        
        for (source_pattern, target_pattern), strategy in strategies.items():
            if self._matches_pattern(source_branch, source_pattern) and \
               self._matches_pattern(target_branch, target_pattern):
                return strategy
        
        return 'merge'  # デフォルト

実験ブランチの管理

実験結果の追跡

# experiment_tracker.py
import json
import yaml
from pathlib import Path

class ExperimentTracker:
    def __init__(self, repo_path):
        self.repo_path = Path(repo_path)
        self.experiments_dir = self.repo_path / 'experiments'
    
    def start_experiment(self, name, config):
        """新しい実験を開始"""
        exp_dir = self.experiments_dir / name
        exp_dir.mkdir(parents=True, exist_ok=True)
        
        # 設定を保存
        with open(exp_dir / 'config.yaml', 'w') as f:
            yaml.dump(config, f)
        
        # 実験メタデータ
        metadata = {
            'name': name,
            'status': 'running',
            'start_time': datetime.now().isoformat(),
            'git_branch': self._get_current_branch(),
            'git_commit': self._get_current_commit()
        }
        
        with open(exp_dir / 'metadata.json', 'w') as f:
            json.dump(metadata, f, indent=2)
        
        return exp_dir
    
    def log_metrics(self, exp_name, epoch, metrics):
        """メトリクスを記録"""
        exp_dir = self.experiments_dir / exp_name
        metrics_file = exp_dir / 'metrics.jsonl'
        
        entry = {
            'epoch': epoch,
            'timestamp': datetime.now().isoformat(),
            **metrics
        }
        
        with open(metrics_file, 'a') as f:
            f.write(json.dumps(entry) + '\n')
    
    def compare_experiments(self, exp_names, metric='accuracy'):
        """複数の実験を比較"""
        comparison = {}
        
        for exp_name in exp_names:
            metrics_file = self.experiments_dir / exp_name / 'metrics.jsonl'
            
            if not metrics_file.exists():
                continue
            
            # 最良の結果を取得
            best_metric = 0
            with open(metrics_file) as f:
                for line in f:
                    data = json.loads(line)
                    if data.get(metric, 0) > best_metric:
                        best_metric = data[metric]
            
            comparison[exp_name] = {
                'best': best_metric,
                'config': self._load_config(exp_name)
            }
        
        return comparison
    
    def generate_comparison_report(self, experiments):
        """比較レポートを生成"""
        report = """# Experiment Comparison Report

## Summary

| Experiment | Accuracy | F1 Score | Parameters | Training Time |
|------------|----------|----------|------------|---------------|
"""
        
        for exp_name, data in experiments.items():
            report += f"| {exp_name} | {data['accuracy']:.4f} | "
            report += f"{data['f1']:.4f} | {data['params']:,} | "
            report += f"{data['time_hours']:.1f}h |\n"
        
        report += "\n## Detailed Results\n"
        
        for exp_name, data in experiments.items():
            report += f"\n### {exp_name}\n"
            report += f"```yaml\n{yaml.dump(data['config'])}```\n"
        
        return report

11.1.4 レビュープロセスの最適化

効率的なコードレビュー

レビューチェックリスト

# review_automation.py
class ReviewAutomation:
    def __init__(self, repo):
        self.repo = repo
        self.review_checklist = {
            'ml_code': [
                'Model architecture is documented',
                'Hyperparameters are configurable',
                'Training resumption is supported',
                'Evaluation metrics are comprehensive',
                'Memory usage is optimized'
            ],
            'data_pipeline': [
                'Data validation is implemented',
                'Error handling for corrupted data',
                'Reproducible data splits',
                'Data augmentation is configurable',
                'Performance profiling results'
            ],
            'api': [
                'Input validation',
                'Error responses are consistent',
                'Rate limiting implemented',
                'API documentation updated',
                'Performance benchmarks'
            ]
        }
    
    def create_review_comment(self, pr_number):
        """自動レビューコメントを作成"""
        pr = self.repo.get_pull(pr_number)
        
        # 変更されたファイルを分析
        changed_files = [f.filename for f in pr.get_files()]
        
        # 適用するチェックリストを決定
        checklists = self._determine_checklists(changed_files)
        
        # コメントを生成
        comment = "## Automated Review Checklist\n\n"
        
        for category, items in checklists.items():
            comment += f"### {category.replace('_', ' ').title()}\n"
            for item in items:
                comment += f"- [ ] {item}\n"
            comment += "\n"
        
        comment += """
### Additional Notes
- Please check all items before requesting final review
- Add comments for any unchecked items explaining why they're not applicable
- Performance benchmarks should be included for significant changes
"""
        
        pr.create_issue_comment(comment)
    
    def analyze_pr_complexity(self, pr_number):
        """PRの複雑さを分析"""
        pr = self.repo.get_pull(pr_number)
        
        metrics = {
            'files_changed': pr.changed_files,
            'additions': pr.additions,
            'deletions': pr.deletions,
            'commits': pr.commits
        }
        
        # 複雑さスコアを計算
        complexity_score = (
            metrics['files_changed'] * 2 +
            (metrics['additions'] + metrics['deletions']) / 100 +
            metrics['commits'] * 0.5
        )
        
        # レビュアー数の推奨
        if complexity_score < 10:
            recommended_reviewers = 1
            review_time_estimate = "30 minutes"
        elif complexity_score < 30:
            recommended_reviewers = 2
            review_time_estimate = "1-2 hours"
        else:
            recommended_reviewers = 3
            review_time_estimate = "2-4 hours"
        
        return {
            'complexity_score': complexity_score,
            'recommended_reviewers': recommended_reviewers,
            'estimated_review_time': review_time_estimate,
            'metrics': metrics
        }

レビューの自動化とメトリクス

レビュー効率化ダッシュボード

# review_metrics.py
class ReviewMetrics:
    def __init__(self, repo):
        self.repo = repo
    
    def calculate_review_metrics(self, days=30):
        """レビューメトリクスを計算"""
        since = datetime.now() - timedelta(days=days)
        
        pulls = list(self.repo.get_pulls(
            state='all',
            sort='created',
            direction='desc'
        ))
        
        metrics = {
            'total_prs': 0,
            'avg_review_time': timedelta(),
            'avg_reviewers': 0,
            'avg_iterations': 0,
            'by_reviewer': {},
            'by_author': {}
        }
        
        for pr in pulls:
            if pr.created_at < since:
                break
            
            metrics['total_prs'] += 1
            
            # レビュー時間
            if pr.merged_at:
                review_time = pr.merged_at - pr.created_at
                metrics['avg_review_time'] += review_time
            
            # レビュアー数
            reviews = list(pr.get_reviews())
            metrics['avg_reviewers'] += len(set(r.user.login for r in reviews))
            
            # イテレーション数(コミット数で近似)
            metrics['avg_iterations'] += pr.commits
            
            # レビュアー別統計
            for review in reviews:
                reviewer = review.user.login
                if reviewer not in metrics['by_reviewer']:
                    metrics['by_reviewer'][reviewer] = {
                        'count': 0,
                        'avg_response_time': timedelta()
                    }
                metrics['by_reviewer'][reviewer]['count'] += 1
        
        # 平均を計算
        if metrics['total_prs'] > 0:
            metrics['avg_review_time'] /= metrics['total_prs']
            metrics['avg_reviewers'] /= metrics['total_prs']
            metrics['avg_iterations'] /= metrics['total_prs']
        
        return metrics
    
    def generate_review_report(self, metrics):
        """レビューレポートを生成"""
        report = f"""
# Code Review Metrics Report

## Overview (Last 30 days)
- **Total PRs**: {metrics['total_prs']}
- **Average Review Time**: {self._format_timedelta(metrics['avg_review_time'])}
- **Average Reviewers per PR**: {metrics['avg_reviewers']:.1f}
- **Average Iterations**: {metrics['avg_iterations']:.1f}

## Top Reviewers
| Reviewer | Reviews | Avg Response Time |
|----------|---------|-------------------|
"""
        
        # レビュアーランキング
        top_reviewers = sorted(
            metrics['by_reviewer'].items(),
            key=lambda x: x[1]['count'],
            reverse=True
        )[:10]
        
        for reviewer, data in top_reviewers:
            report += f"| @{reviewer} | {data['count']} | "
            report += f"{self._format_timedelta(data['avg_response_time'])} |\n"
        
        return report

11.2 実験ブランチとメインラインの管理

実験結果の統合戦略

マージ判断基準

# merge_decision.py
class MergeDecisionMaker:
    def __init__(self, repo):
        self.repo = repo
        self.criteria = {
            'performance': {
                'accuracy_improvement': 0.01,  # 1%以上の改善
                'latency_regression': 0.1,     # 10%以内の遅延
                'memory_increase': 0.2         # 20%以内のメモリ増加
            },
            'code_quality': {
                'test_coverage': 0.8,          # 80%以上
                'code_complexity': 10,         # 循環的複雑度
                'documentation': True          # 必須
            },
            'stability': {
                'consecutive_successes': 3,    # 3回連続成功
                'error_rate': 0.01            # エラー率1%以下
            }
        }
    
    def evaluate_experiment(self, experiment_branch):
        """実験ブランチの評価"""
        evaluation = {
            'performance': self._evaluate_performance(experiment_branch),
            'code_quality': self._evaluate_code_quality(experiment_branch),
            'stability': self._evaluate_stability(experiment_branch),
            'overall': 'pending'
        }
        
        # 総合判定
        if all(cat['passed'] for cat in evaluation.values() if isinstance(cat, dict)):
            evaluation['overall'] = 'approved'
        else:
            evaluation['overall'] = 'rejected'
        
        return evaluation
    
    def generate_merge_report(self, experiment_branch):
        """マージ判断レポート生成"""
        eval_result = self.evaluate_experiment(experiment_branch)
        
        report = f"""
# Merge Decision Report: {experiment_branch}

## Summary
**Decision**: {eval_result['overall'].upper()}

## Performance Evaluation
- Accuracy: {eval_result['performance']['accuracy']} ({eval_result['performance']['accuracy_delta']:+.2%})
- Latency: {eval_result['performance']['latency']}ms ({eval_result['performance']['latency_delta']:+.1%})
- Memory: {eval_result['performance']['memory']}MB ({eval_result['performance']['memory_delta']:+.1%})

## Code Quality
- Test Coverage: {eval_result['code_quality']['coverage']:.1%}
- Complexity: {eval_result['code_quality']['complexity']}
- Documentation: {'✓' if eval_result['code_quality']['documented'] else '✗'}

## Stability
- Success Rate: {eval_result['stability']['success_rate']:.1%}
- Error Rate: {eval_result['stability']['error_rate']:.2%}

## Recommendations
{self._generate_recommendations(eval_result)}
"""
        
        return report

実験のアーカイブと参照

実験履歴管理

# experiment_archive.py
import sqlite3
from datetime import datetime

class ExperimentArchive:
    def __init__(self, db_path='experiments.db'):
        self.conn = sqlite3.connect(db_path)
        self._create_tables()
    
    def _create_tables(self):
        """実験管理用のテーブルを作成"""
        self.conn.execute("""
        CREATE TABLE IF NOT EXISTS experiments (
            id INTEGER PRIMARY KEY,
            name TEXT UNIQUE,
            branch TEXT,
            commit_sha TEXT,
            created_at TIMESTAMP,
            merged_at TIMESTAMP,
            status TEXT,
            config JSON,
            results JSON,
            artifacts_path TEXT
        )
        """)
        
        self.conn.execute("""
        CREATE TABLE IF NOT EXISTS experiment_metrics (
            id INTEGER PRIMARY KEY,
            experiment_id INTEGER,
            metric_name TEXT,
            metric_value REAL,
            epoch INTEGER,
            timestamp TIMESTAMP,
            FOREIGN KEY (experiment_id) REFERENCES experiments(id)
        )
        """)
    
    def archive_experiment(self, experiment_data):
        """実験をアーカイブ"""
        self.conn.execute("""
        INSERT INTO experiments 
        (name, branch, commit_sha, created_at, status, config, results, artifacts_path)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?)
        """, (
            experiment_data['name'],
            experiment_data['branch'],
            experiment_data['commit_sha'],
            datetime.now(),
            experiment_data['status'],
            json.dumps(experiment_data['config']),
            json.dumps(experiment_data['results']),
            experiment_data['artifacts_path']
        ))
        
        self.conn.commit()
        
        # S3やGCSに実験成果物をアップロード
        if experiment_data.get('upload_artifacts'):
            self._upload_artifacts(experiment_data['artifacts_path'])
    
    def search_experiments(self, criteria):
        """実験を検索"""
        query = "SELECT * FROM experiments WHERE 1=1"
        params = []
        
        if 'min_accuracy' in criteria:
            query += " AND json_extract(results, '$.accuracy') >= ?"
            params.append(criteria['min_accuracy'])
        
        if 'model_type' in criteria:
            query += " AND json_extract(config, '$.model_type') = ?"
            params.append(criteria['model_type'])
        
        if 'date_range' in criteria:
            query += " AND created_at BETWEEN ? AND ?"
            params.extend(criteria['date_range'])
        
        cursor = self.conn.execute(query, params)
        return cursor.fetchall()
    
    def reproduce_experiment(self, experiment_id):
        """実験を再現"""
        exp = self.conn.execute(
            "SELECT * FROM experiments WHERE id = ?",
            (experiment_id,)
        ).fetchone()
        
        if not exp:
            raise ValueError(f"Experiment {experiment_id} not found")
        
        # チェックアウト
        subprocess.run(['git', 'checkout', exp['commit_sha']])
        
        # 設定を復元
        config = json.loads(exp['config'])
        with open('experiment_config.yaml', 'w') as f:
            yaml.dump(config, f)
        
        # 実行スクリプトを生成
        script = f"""#!/bin/bash
# Reproduce experiment: {exp['name']}
# Original commit: {exp['commit_sha']}

python train.py --config experiment_config.yaml
"""
        
        with open('reproduce.sh', 'w') as f:
            f.write(script)
        
        print(f"Experiment {exp['name']} ready to reproduce")
        print("Run: bash reproduce.sh")

まとめ

本章では、AIモデル開発における実践的なワークフロー設計を学習しました:

  • プロジェクト構造とブランチ戦略の設計
  • Issue駆動開発で透明性の高い開発プロセス
  • Feature Branchワークフローで並行開発
  • 効率的なレビュープロセスとメトリクス
  • 実験管理とマージ判断の自動化

次章では、CI/CDパイプラインの構築について学習します。

確認事項

  • プロジェクトに適したブランチ戦略を選択できる
  • Issue駆動開発のプロセスを実践できる
  • 実験ブランチの管理方法を理解している
  • レビュープロセスを最適化できる
  • 実験結果のアーカイブと再現ができる