μ½˜ν…μΈ λ‘œ κ±΄λ„ˆλ›°κΈ°

K-ν΄λ“œ ꡐ차 검증을 ν†΅ν•œ Ultralytics

μ†Œκ°œ

이 μ’…ν•© κ°€μ΄λ“œλŠ” Ultralytics μ—μ½”μ‹œμŠ€ν…œ λ‚΄μ—μ„œ 객체 감지 데이터 μ„ΈνŠΈμ— λŒ€ν•œ K-Fold ꡐ차 검증을 κ΅¬ν˜„ν•˜λŠ” 방법을 μ„€λͺ…ν•©λ‹ˆλ‹€. YOLO 감지 ν˜•μ‹κ³Ό μ£Όμš” Python 라이브러리(예: sklearn, pandas, PyYaml)λ₯Ό ν™œμš©ν•˜μ—¬ ν•„μš”ν•œ μ„€μ •, νŠΉμ§• 벑터 생성 ν”„λ‘œμ„ΈμŠ€, K-Fold 데이터 μ„ΈνŠΈ λΆ„ν•  싀행을 μ•ˆλ‚΄ν•©λ‹ˆλ‹€.

K-ν΄λ“œ ꡐ차 검증 κ°œμš”

이 νŠœν† λ¦¬μ–Όμ€ ν”„λ‘œμ νŠΈμ— 과일 감지 데이터 μ„ΈνŠΈκ°€ ν¬ν•¨λ˜λ“  μ‚¬μš©μž 지정 데이터 μ†ŒμŠ€κ°€ ν¬ν•¨λ˜λ“  관계없이 λ¨Έμ‹  λŸ¬λ‹ λͺ¨λΈμ˜ μ‹ λ’°μ„±κ³Ό 견고성을 κ°•ν™”ν•˜κΈ° μœ„ν•΄ K-ν΄λ“œ ꡐ차 검증을 μ΄ν•΄ν•˜κ³  μ μš©ν•˜λŠ” 데 도움을 μ£ΌλŠ” 것을 λͺ©ν‘œλ‘œ ν•©λ‹ˆλ‹€. μ μš©ν•˜λŠ” λ™μ•ˆ k=5 ν΄λ“œμ— λŒ€ν•΄ μ„€λͺ…ν•˜μ§€λ§Œ, 졜적의 ν΄λ“œ μˆ˜λŠ” 데이터 μ„ΈνŠΈμ™€ ν”„λ‘œμ νŠΈμ˜ νŠΉμ„±μ— 따라 λ‹¬λΌμ§ˆ 수 μžˆλ‹€λŠ” 점에 μœ μ˜ν•˜μ„Έμš”.

더 이상 κ³ λ―Όν•  ν•„μš” 없이 λ°”λ‘œ μ‹œμž‘ν•˜κ² μŠ΅λ‹ˆλ‹€!

μ„€μ •

  • 주석은 YOLO 감지 ν˜•μ‹μœΌλ‘œ μž‘μ„±ν•΄μ•Ό ν•©λ‹ˆλ‹€.

  • 이 κ°€μ΄λ“œμ—μ„œλŠ” 주석 νŒŒμΌμ„ λ‘œμ»¬μ—μ„œ μ‚¬μš©ν•  수 μžˆλ‹€κ³  κ°€μ •ν•©λ‹ˆλ‹€.

  • 데λͺ¨μ—μ„œλŠ” 과일 감지 데이터 μ„ΈνŠΈλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

    • 이 데이터 μ„ΈνŠΈμ—λŠ” 총 8479개의 이미지가 ν¬ν•¨λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.
    • μ—¬κΈ°μ—λŠ” 6개의 클래슀 λ ˆμ΄λΈ”μ΄ ν¬ν•¨λ˜λ©°, 각 λ ˆμ΄λΈ”μ˜ 총 μΈμŠ€ν„΄μŠ€ μˆ˜λŠ” μ•„λž˜μ— λ‚˜μ—΄λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.
클래슀 λ ˆμ΄λΈ” μΈμŠ€ν„΄μŠ€ 수
Apple 7049
포도 7202
νŒŒμΈμ• ν”Œ 1613
μ˜€λ Œμ§€ 15549
λ°”λ‚˜λ‚˜ 3536
μˆ˜λ°• 1976
  • ν•„μš”ν•œ Python νŒ¨ν‚€μ§€κ°€ ν¬ν•¨λ©λ‹ˆλ‹€:

    • ultralytics
    • sklearn
    • pandas
    • pyyaml
  • 이 νŠœν† λ¦¬μ–Όμ€ λ‹€μŒμ—μ„œ μž‘λ™ν•©λ‹ˆλ‹€. k=5 ν΄λ“œ. κ·ΈλŸ¬λ‚˜ νŠΉμ • 데이터 집합에 κ°€μž₯ μ ν•©ν•œ ν΄λ“œ 수λ₯Ό κ²°μ •ν•΄μ•Ό ν•©λ‹ˆλ‹€.

  • μƒˆλ‘œμš΄ Python 가상 ν™˜κ²½(venv)λ₯Ό ν”„λ‘œμ νŠΈμ— μΆ”κ°€ν•˜κ³  ν™œμ„±ν™”ν•©λ‹ˆλ‹€. μ‚¬μš© pip (λ˜λŠ” μ„ ν˜Έν•˜λŠ” νŒ¨ν‚€μ§€ κ΄€λ¦¬μž)λ₯Ό λ‹€μš΄λ‘œλ“œν•˜μ—¬ μ„€μΉ˜ν•©λ‹ˆλ‹€:

    • Ultralytics 라이브러리: pip install -U ultralytics. λ˜λŠ” 곡식 repo.
    • Scikit-learn, νŒλ‹€, PyYAML: pip install -U scikit-learn pandas pyyaml.
  • 주석이 YOLO 감지 ν˜•μ‹μΈμ§€ ν™•μΈν•©λ‹ˆλ‹€.

    • 이 νŠœν† λ¦¬μ–Όμ˜ 경우 λͺ¨λ“  주석 νŒŒμΌμ€ Fruit-Detection/labels λ””λ ‰ν„°λ¦¬λ‘œ μ΄λ™ν•©λ‹ˆλ‹€.

객체 감지 데이터 집합을 μœ„ν•œ νŠΉμ§• 벑터 μƒμ„±ν•˜κΈ°

  1. λ¨Όμ € Python νŒŒμΌμ„ μƒˆλ‘œ λ§Œλ“€κ³  ν•„μš”ν•œ 라이브러리λ₯Ό κ°€μ Έμ˜΅λ‹ˆλ‹€.

    import datetime
    import shutil
    from pathlib import Path
    from collections import Counter
    
    import yaml
    import numpy as np
    import pandas as pd
    from ultralytics import YOLO
    from sklearn.model_selection import KFold
    
  2. 데이터 μ„ΈνŠΈμ— λŒ€ν•œ λͺ¨λ“  라벨 νŒŒμΌμ„ κ²€μƒ‰ν•©λ‹ˆλ‹€.

    dataset_path = Path('./Fruit-detection') # replace with 'path/to/dataset' for your custom data
    labels = sorted(dataset_path.rglob("*labels/*.txt")) # all data in 'labels'
    
  3. 이제 데이터 μ„ΈνŠΈ YAML 파일의 λ‚΄μš©μ„ 읽고 클래슀 λ ˆμ΄λΈ”μ˜ 인덱슀λ₯Ό μΆ”μΆœν•©λ‹ˆλ‹€.

    yaml_file = 'path/to/data.yaml'  # your data YAML with data directories and names dictionary
    with open(yaml_file, 'r', encoding="utf8") as y:
        classes = yaml.safe_load(y)['names']
    cls_idx = sorted(classes.keys())
    
  4. λΉ„μ–΄ μžˆλŠ” pandas λ°μ΄ν„°ν”„λ ˆμž„.

    indx = [l.stem for l in labels] # uses base filename as ID (no extension)
    labels_df = pd.DataFrame([], columns=cls_idx, index=indx)
    
  5. μ–΄λ…Έν…Œμ΄μ…˜ νŒŒμΌμ— μžˆλŠ” 각 클래슀 λ ˆμ΄λΈ”μ˜ μΈμŠ€ν„΄μŠ€λ₯Ό κ³„μ‚°ν•©λ‹ˆλ‹€.

    for label in labels:
        lbl_counter = Counter()
    
        with open(label,'r') as lf:
            lines = lf.readlines()
    
        for l in lines:
            # classes for YOLO label uses integer at first position of each line
            lbl_counter[int(l.split(' ')[0])] += 1
    
        labels_df.loc[label.stem] = lbl_counter
    
    labels_df = labels_df.fillna(0.0) # replace `nan` values with `0.0`
    
  6. λ‹€μŒμ€ μ±„μ›Œμ§„ λ°μ΄ν„°ν”„λ ˆμž„μ˜ μƒ˜ν”Œ λ³΄κΈ°μž…λ‹ˆλ‹€:

                                                           0    1    2    3    4    5
    '0000a16e4b057580_jpg.rf.00ab48988370f64f5ca8ea4...'  0.0  0.0  0.0  0.0  0.0  7.0
    '0000a16e4b057580_jpg.rf.7e6dce029fb67f01eb19aa7...'  0.0  0.0  0.0  0.0  0.0  7.0
    '0000a16e4b057580_jpg.rf.bc4d31cdcbe229dd022957a...'  0.0  0.0  0.0  0.0  0.0  7.0
    '00020ebf74c4881c_jpg.rf.508192a0a97aa6c4a3b6882...'  0.0  0.0  0.0  1.0  0.0  0.0
    '00020ebf74c4881c_jpg.rf.5af192a2254c8ecc4188a25...'  0.0  0.0  0.0  1.0  0.0  0.0
     ...                                                  ...  ...  ...  ...  ...  ...
    'ff4cd45896de38be_jpg.rf.c4b5e967ca10c7ced3b9e97...'  0.0  0.0  0.0  0.0  0.0  2.0
    'ff4cd45896de38be_jpg.rf.ea4c1d37d2884b3e3cbce08...'  0.0  0.0  0.0  0.0  0.0  2.0
    'ff5fd9c3c624b7dc_jpg.rf.bb519feaa36fc4bf630a033...'  1.0  0.0  0.0  0.0  0.0  0.0
    'ff5fd9c3c624b7dc_jpg.rf.f0751c9c3aa4519ea3c9d6a...'  1.0  0.0  0.0  0.0  0.0  0.0
    'fffe28b31f2a70d4_jpg.rf.7ea16bd637ba0711c53b540...'  0.0  6.0  0.0  0.0  0.0  0.0
    

행은 각각 데이터 μ„ΈνŠΈμ˜ 이미지에 ν•΄λ‹Ήν•˜λŠ” 라벨 파일의 색인을 μƒμ„±ν•˜κ³ , 열은 클래슀 라벨 μΈλ±μŠ€μ— ν•΄λ‹Ήν•©λ‹ˆλ‹€. 각 행은 데이터 μ„ΈνŠΈμ— μ‘΄μž¬ν•˜λŠ” 각 클래슀 라벨의 κ°œμˆ˜κ°€ ν¬ν•¨λœ μ˜μ‚¬ νŠΉμ§• 벑터λ₯Ό λ‚˜νƒ€λƒ…λ‹ˆλ‹€. 이 데이터 κ΅¬μ‘°λŠ” 객체 감지 데이터 μ„ΈνŠΈμ— K-Fold ꡐ차 검증을 μ μš©ν•  수 있게 ν•΄μ€λ‹ˆλ‹€.

K-ν΄λ“œ 데이터 μ„ΈνŠΈ λΆ„ν• 

  1. 이제 μš°λ¦¬λŠ” KFold ν΄λž˜μŠ€μ—μ„œ sklearn.model_selection λ₯Ό μƒμ„±ν•˜λ €λ©΄ k 데이터 μ„ΈνŠΈμ˜ λΆ„ν• .

    • μ€‘μš”:
      • μ„€μ • shuffle=True λŠ” μŠ€ν”Œλ¦Ώμ— ν΄λž˜μŠ€κ°€ λ¬΄μž‘μœ„λ‘œ λΆ„λ°°λ˜λ„λ‘ ν•©λ‹ˆλ‹€.
      • μ„€μ • κΈ°μ€€ random_state=M where M κ°€ μ„ νƒλœ μ •μˆ˜μΈ 경우 반볡 κ°€λŠ₯ν•œ κ²°κ³Όλ₯Ό 얻을 수 μžˆμŠ΅λ‹ˆλ‹€.
    ksplit = 5
    kf = KFold(n_splits=ksplit, shuffle=True, random_state=20)   # setting random_state for repeatable results
    
    kfolds = list(kf.split(labels_df))
    
  2. 이제 데이터 μ„ΈνŠΈκ°€ λ‹€μŒκ³Ό 같이 λΆ„ν• λ˜μ—ˆμŠ΅λ‹ˆλ‹€. k ν΄λ“œμ—λŠ” 각각 train 및 val 인덱슀λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€. μ΄λŸ¬ν•œ κ²°κ³Όλ₯Ό 보닀 λͺ…ν™•ν•˜κ²Œ ν‘œμ‹œν•˜κΈ° μœ„ν•΄ 데이터 ν”„λ ˆμž„μ„ κ΅¬μ„±ν•˜κ² μŠ΅λ‹ˆλ‹€.

    folds = [f'split_{n}' for n in range(1, ksplit + 1)]
    folds_df = pd.DataFrame(index=indx, columns=folds)
    
    for idx, (train, val) in enumerate(kfolds, start=1):
        folds_df[f'split_{idx}'].loc[labels_df.iloc[train].index] = 'train'
        folds_df[f'split_{idx}'].loc[labels_df.iloc[val].index] = 'val'
    
  3. 이제 각 ν΄λ“œμ— λŒ€ν•œ 클래슀 λ ˆμ΄λΈ”μ˜ 뢄포λ₯Ό λ‹€μŒμ— μ‘΄μž¬ν•˜λŠ” 클래슀의 λΉ„μœ¨λ‘œ 계산해 λ³΄κ² μŠ΅λ‹ˆλ‹€. val 에 μžˆλŠ” μ‚¬λžŒλ“€μ—κ²Œ train.

    fold_lbl_distrb = pd.DataFrame(index=folds, columns=cls_idx)
    
    for n, (train_indices, val_indices) in enumerate(kfolds, start=1):
        train_totals = labels_df.iloc[train_indices].sum()
        val_totals = labels_df.iloc[val_indices].sum()
    
        # To avoid division by zero, we add a small value (1E-7) to the denominator
        ratio = val_totals / (train_totals + 1E-7)
        fold_lbl_distrb.loc[f'split_{n}'] = ratio
    

이상적인 μ‹œλ‚˜λ¦¬μ˜€λŠ” λͺ¨λ“  클래슀 λΉ„μœ¨μ΄ 각 λΆ„ν•  및 클래슀 간에 ν•©λ¦¬μ μœΌλ‘œ μœ μ‚¬ν•΄μ§€λŠ” κ²ƒμž…λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ μ΄λŠ” 데이터 μ§‘ν•©μ˜ νŠΉμ„±μ— 따라 λ‹¬λΌμ§ˆ 수 μžˆμŠ΅λ‹ˆλ‹€.

  1. λ‹€μŒμœΌλ‘œ, 각 뢄할에 λŒ€ν•œ 디렉터리와 데이터 μ„ΈνŠΈ YAML νŒŒμΌμ„ μƒμ„±ν•©λ‹ˆλ‹€.

    supported_extensions = ['.jpg', '.jpeg', '.png']
    
    # Initialize an empty list to store image file paths
    images = []
    
    # Loop through supported extensions and gather image files
    for ext in supported_extensions:
        images.extend(sorted((dataset_path / 'images').rglob(f"*{ext}")))
    
    # Create the necessary directories and dataset YAML files (unchanged)
    save_path = Path(dataset_path / f'{datetime.date.today().isoformat()}_{ksplit}-Fold_Cross-val')
    save_path.mkdir(parents=True, exist_ok=True)
    ds_yamls = []
    
    for split in folds_df.columns:
        # Create directories
        split_dir = save_path / split
        split_dir.mkdir(parents=True, exist_ok=True)
        (split_dir / 'train' / 'images').mkdir(parents=True, exist_ok=True)
        (split_dir / 'train' / 'labels').mkdir(parents=True, exist_ok=True)
        (split_dir / 'val' / 'images').mkdir(parents=True, exist_ok=True)
        (split_dir / 'val' / 'labels').mkdir(parents=True, exist_ok=True)
    
        # Create dataset YAML files
        dataset_yaml = split_dir / f'{split}_dataset.yaml'
        ds_yamls.append(dataset_yaml)
    
        with open(dataset_yaml, 'w') as ds_y:
            yaml.safe_dump({
                'path': split_dir.as_posix(),
                'train': 'train',
                'val': 'val',
                'names': classes
            }, ds_y)
    
  2. λ§ˆμ§€λ§‰μœΌλ‘œ 이미지와 λ ˆμ΄λΈ”μ„ 각 뢄할에 λŒ€ν•œ 각 디렉토리('train' λ˜λŠ” 'val')에 λ³΅μ‚¬ν•©λ‹ˆλ‹€.

    • μ°Έκ³ : μ½”λ“œμ˜ 이 뢀뢄에 ν•„μš”ν•œ μ‹œκ°„μ€ 데이터 μ„ΈνŠΈμ˜ 크기와 μ‹œμŠ€ν…œ ν•˜λ“œμ›¨μ–΄μ— 따라 λ‹¬λΌμ§‘λ‹ˆλ‹€.
    for image, label in zip(images, labels):
        for split, k_split in folds_df.loc[image.stem].items():
            # Destination directory
            img_to_path = save_path / split / k_split / 'images'
            lbl_to_path = save_path / split / k_split / 'labels'
    
            # Copy image and label files to new directory (SamefileError if file already exists)
            shutil.copy(image, img_to_path / image.name)
            shutil.copy(label, lbl_to_path / label.name)
    

기둝 μ €μž₯(선택 사항)

선택 μ‚¬ν•­μœΌλ‘œ, λ‚˜μ€‘μ— μ°Έμ‘°ν•  수 μžˆλ„λ‘ K-Fold λΆ„ν•  및 라벨 배포 λ°μ΄ν„°ν”„λ ˆμž„μ˜ λ ˆμ½”λ“œλ₯Ό CSV 파일둜 μ €μž₯ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

folds_df.to_csv(save_path / "kfold_datasplit.csv")
fold_lbl_distrb.to_csv(save_path / "kfold_label_distribution.csv")

K-Fold 데이터 뢄할을 μ‚¬μš©ν•˜μ—¬ YOLO ꡐ윑

  1. λ¨Όμ € YOLO λͺ¨λΈμ„ λ‘œλ“œν•©λ‹ˆλ‹€.

    weights_path = 'path/to/weights.pt'
    model = YOLO(weights_path, task='detect')
    
  2. 그런 λ‹€μŒ 데이터 μ„ΈνŠΈ YAML νŒŒμΌμ„ λ°˜λ³΅ν•˜μ—¬ ν•™μŠ΅μ„ μ‹€ν–‰ν•©λ‹ˆλ‹€. κ²°κ³ΌλŠ” μ§€μ •λœ 디렉토리에 μ €μž₯λ©λ‹ˆλ‹€. project 및 name 인수λ₯Ό μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 기본적으둜 이 λ””λ ‰ν„°λ¦¬λŠ” 'exp/runs#'이며, μ—¬κΈ°μ„œ #은 μ •μˆ˜ μΈλ±μŠ€μž…λ‹ˆλ‹€.

    results = {}
    
    # Define your additional arguments here
    batch = 16
    project = 'kfold_demo'
    epochs = 100
    
    for k in range(ksplit):
        dataset_yaml = ds_yamls[k]
        model.train(data=dataset_yaml,epochs=epochs, batch=batch, project=project)  # include any train arguments
        results[k] = model.metrics  # save output metrics for further analysis
    

κ²°λ‘ 

이 κ°€μ΄λ“œμ—μ„œλŠ” YOLO 객체 감지 λͺ¨λΈμ„ ν›ˆλ ¨ν•˜κΈ° μœ„ν•΄ K-Fold ꡐ차 검증을 μ‚¬μš©ν•˜λŠ” ν”„λ‘œμ„ΈμŠ€λ₯Ό μ‚΄νŽ΄λ΄€μŠ΅λ‹ˆλ‹€. 데이터 μ„ΈνŠΈλ₯Ό K개의 νŒŒν‹°μ…˜μœΌλ‘œ λΆ„ν• ν•˜μ—¬ μ—¬λŸ¬ ν΄λ“œμ— 걸쳐 κ· ν˜• 작힌 클래슀 뢄포λ₯Ό 보μž₯ν•˜λŠ” 방법을 λ°°μ› μŠ΅λ‹ˆλ‹€.

λ˜ν•œ 데이터 λΆ„ν• κ³Ό μ΄λŸ¬ν•œ λΆ„ν• μ˜ λ ˆμ΄λΈ” 뢄포λ₯Ό μ‹œκ°ν™”ν•˜κΈ° μœ„ν•΄ λ³΄κ³ μ„œ λ°μ΄ν„°ν”„λ ˆμž„μ„ μƒμ„±ν•˜λŠ” 절차λ₯Ό μ‚΄νŽ΄λ΄„μœΌλ‘œμ¨ ν•™μŠ΅ 및 검증 μ„ΈνŠΈμ˜ ꡬ쑰에 λŒ€ν•œ λͺ…ν™•ν•œ μΈμ‚¬μ΄νŠΈλ₯Ό 얻을 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

μ„ νƒμ μœΌλ‘œ λ‚˜μ€‘μ— μ°Έμ‘°ν•  수 μžˆλ„λ‘ 기둝을 μ €μž₯ν–ˆλŠ”λ°, μ΄λŠ” λŒ€κ·œλͺ¨ ν”„λ‘œμ νŠΈλ‚˜ λͺ¨λΈ μ„±λŠ₯ 문제λ₯Ό ν•΄κ²°ν•  λ•Œ 특히 μœ μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

λ§ˆμ§€λ§‰μœΌλ‘œ, 각 뢄할을 λ£¨ν”„λ‘œ μ‚¬μš©ν•˜μ—¬ μ‹€μ œ λͺ¨λΈ ν•™μŠ΅μ„ κ΅¬ν˜„ν•˜κ³  μΆ”κ°€ 뢄석 및 비ꡐλ₯Ό μœ„ν•΄ ν•™μŠ΅ κ²°κ³Όλ₯Ό μ €μž₯ν–ˆμŠ΅λ‹ˆλ‹€.

μ΄λŸ¬ν•œ K-Fold ꡐ차 검증 기법은 μ‚¬μš© κ°€λŠ₯ν•œ 데이터λ₯Ό μ΅œλŒ€ν•œ ν™œμš©ν•˜λŠ” κ°•λ ₯ν•œ 방법이며, λ‹€μ–‘ν•œ 데이터 ν•˜μœ„ μ§‘ν•©μ—μ„œ λͺ¨λΈ μ„±λŠ₯을 μ‹ λ’°ν•  수 있고 일관성 있게 μœ μ§€ν•˜λŠ” 데 도움이 λ©λ‹ˆλ‹€. κ·Έ κ²°κ³Ό νŠΉμ • 데이터 νŒ¨ν„΄μ— κ³Όλ„ν•˜κ²Œ 맞좜 κ°€λŠ₯성이 적은 보닀 μΌλ°˜ν™” κ°€λŠ₯ν•˜κ³  μ‹ λ’°ν•  수 μžˆλŠ” λͺ¨λΈμ„ λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€.

이 κ°€μ΄λ“œμ—μ„œλŠ” YOLO 을 μ‚¬μš©ν–ˆμ§€λ§Œ, μ΄λŸ¬ν•œ λ‹¨κ³„λŠ” λŒ€λΆ€λΆ„ λ‹€λ₯Έ λ¨Έμ‹  λŸ¬λ‹ λͺ¨λΈμ—λ„ μ μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ΄λŸ¬ν•œ 단계λ₯Ό μ΄ν•΄ν•˜λ©΄ μžμ‹ μ˜ λ¨Έμ‹  λŸ¬λ‹ ν”„λ‘œμ νŠΈμ— ꡐ차 검증을 효과적으둜 μ μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 즐거운 μ½”λ”©ν•˜μ„Έμš”!



생성됨 2023-11-12, μ—…λ°μ΄νŠΈλ¨ 2023-12-03
μž‘μ„±μž: glenn-jocher (5), Burhan-Q (1)

λŒ“κΈ€