Update README.md
Browse files
    	
        README.md
    CHANGED
    
    | @@ -11,10 +11,288 @@ metrics: | |
| 11 | 
             
            ---
         | 
| 12 | 
             
            # Model Name : maxseats/SungBeom-whisper-small-ko-set0
         | 
| 13 | 
             
            # Description
         | 
|  | |
| 14 |  | 
| 15 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 16 |  | 
| 17 | 
             
            # 설명
         | 
| 18 | 
            -
            - 주요 영역별 회의 음성  | 
| 19 | 
            -
            -  | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 20 |  | 
|  | |
| 11 | 
             
            ---
         | 
| 12 | 
             
            # Model Name : maxseats/SungBeom-whisper-small-ko-set0
         | 
| 13 | 
             
            # Description
         | 
| 14 | 
            +
            - 파인튜닝 데이터셋 : maxseats/aihub-464-preprocessed-680GB-set-1
         | 
| 15 |  | 
| 16 | 
            +
            # 설명
         | 
| 17 | 
            +
            - AI hub의 주요 영역별 회의 음성 데이터셋을 학습 중이에요.
         | 
| 18 | 
            +
            - 680GB 중 첫번째 데이터(10GB)를 파인튜닝한 모델을 불러와서, 두번째 데이터를 학습한 모델입니다.
         | 
| 19 | 
            +
            - 링크 : https://huggingface.co/datasets/maxseats/aihub-464-preprocessed-680GB-set-0, https://huggingface.co/datasets/maxseats/aihub-464-preprocessed-680GB-set-1
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            - 다음 코드를 통해 작성했어요.
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            ```
         | 
| 24 | 
            +
            from datasets import load_dataset
         | 
| 25 | 
            +
            import torch
         | 
| 26 | 
            +
            from dataclasses import dataclass
         | 
| 27 | 
            +
            from typing import Any, Dict, List, Union
         | 
| 28 | 
            +
            import evaluate
         | 
| 29 | 
            +
            from transformers import WhisperTokenizer, WhisperFeatureExtractor, WhisperProcessor, WhisperForConditionalGeneration, Seq2SeqTrainingArguments, Seq2SeqTrainer
         | 
| 30 | 
            +
            import mlflow
         | 
| 31 | 
            +
            from mlflow.tracking.client import MlflowClient
         | 
| 32 | 
            +
            import subprocess
         | 
| 33 | 
            +
            from huggingface_hub import create_repo, Repository
         | 
| 34 | 
            +
            import os
         | 
| 35 | 
            +
            import shutil
         | 
| 36 | 
            +
            import math # 임시 테스트용
         | 
| 37 | 
            +
            model_dir = "./tmpp" # 수정 X
         | 
| 38 | 
            +
             | 
| 39 | 
            +
             | 
| 40 | 
            +
            #########################################################################################################################################
         | 
| 41 | 
            +
            ################################################### 사용자 설정 변수 #####################################################################
         | 
| 42 | 
            +
            #########################################################################################################################################
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            model_description = """
         | 
| 45 | 
            +
            - 파인튜닝 데이터셋 : maxseats/aihub-464-preprocessed-680GB-set-1
         | 
| 46 |  | 
| 47 | 
             
            # 설명
         | 
| 48 | 
            +
            - AI hub의 주요 영역별 회의 음성 데이터셋을 학습 중이에요.
         | 
| 49 | 
            +
            - 680GB 중 첫번째 데이터(10GB)를 파인튜닝한 모델을 불러와서, 두번째 데이터를 학습한 모델입니다.
         | 
| 50 | 
            +
            - 링크 : https://huggingface.co/datasets/maxseats/aihub-464-preprocessed-680GB-set-0, https://huggingface.co/datasets/maxseats/aihub-464-preprocessed-680GB-set-1
         | 
| 51 | 
            +
            """
         | 
| 52 | 
            +
             | 
| 53 | 
            +
            # model_name = "openai/whisper-base"
         | 
| 54 | 
            +
            model_name = "maxseats/SungBeom-whisper-small-ko-set0" # 대안 : "SungBeom/whisper-small-ko"
         | 
| 55 | 
            +
            # dataset_name = "maxseats/aihub-464-preprocessed-680GB-set-1"  # 불러올 데이터셋(허깅페이스 기준)
         | 
| 56 | 
            +
            dataset_name = "maxseats/aihub-464-preprocessed-680GB-set-1"  # 불러올 데이터셋(허깅페이스 기준)
         | 
| 57 | 
            +
             | 
| 58 | 
            +
            CACHE_DIR = '/mnt/a/maxseats/.finetuning_cache'  # 캐시 디렉토리 지정
         | 
| 59 | 
            +
            is_test = False  # True: 소량의 샘플 데이터로 테스트, False: 실제 파인튜닝
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            token = "hf_" # 허깅페이스 토큰 입력
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            training_args = Seq2SeqTrainingArguments(
         | 
| 64 | 
            +
                output_dir=model_dir,  # 원하는 리포지토리 이름을 입력한다.
         | 
| 65 | 
            +
                per_device_train_batch_size=16,
         | 
| 66 | 
            +
                gradient_accumulation_steps=2,  # 배치 크기가 2배 감소할 때마다 2배씩 증가
         | 
| 67 | 
            +
                learning_rate=1e-5,
         | 
| 68 | 
            +
                warmup_steps=500,
         | 
| 69 | 
            +
                # max_steps=2,  # epoch 대신 설정
         | 
| 70 | 
            +
                num_train_epochs=1,     # epoch 수 설정 / max_steps와 이것 중 하나만 설정
         | 
| 71 | 
            +
                gradient_checkpointing=True,
         | 
| 72 | 
            +
                fp16=True,
         | 
| 73 | 
            +
                evaluation_strategy="steps",
         | 
| 74 | 
            +
                per_device_eval_batch_size=16,
         | 
| 75 | 
            +
                predict_with_generate=True,
         | 
| 76 | 
            +
                generation_max_length=225,
         | 
| 77 | 
            +
                save_steps=1000,
         | 
| 78 | 
            +
                eval_steps=1000,
         | 
| 79 | 
            +
                logging_steps=25,
         | 
| 80 | 
            +
                report_to=["tensorboard"],
         | 
| 81 | 
            +
                load_best_model_at_end=True,
         | 
| 82 | 
            +
                metric_for_best_model="cer",  # 한국어의 경우 'wer'보다는 'cer'이 더 적합할 것
         | 
| 83 | 
            +
                greater_is_better=False,
         | 
| 84 | 
            +
                push_to_hub=True,
         | 
| 85 | 
            +
                save_total_limit=5,           # 최대 저장할 모델 수 지정
         | 
| 86 | 
            +
            )
         | 
| 87 | 
            +
             | 
| 88 | 
            +
            #########################################################################################################################################
         | 
| 89 | 
            +
            ################################################### 사용자 설정 변수 #####################################################################
         | 
| 90 | 
            +
            #########################################################################################################################################
         | 
| 91 | 
            +
             | 
| 92 | 
            +
             | 
| 93 | 
            +
            @dataclass
         | 
| 94 | 
            +
            class DataCollatorSpeechSeq2SeqWithPadding:
         | 
| 95 | 
            +
                processor: Any
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                def __call__(self, features: List[Dict[str, Union[List[int], torch.Tensor]]]) -> Dict[str, torch.Tensor]:
         | 
| 98 | 
            +
                    # 인풋 데이터와 라벨 데이터의 길이가 다르며, 따라서 서로 다른 패딩 방법이 적용되어야 한다. 그러므로 두 데이터를 분리해야 한다.
         | 
| 99 | 
            +
                    # 먼저 오디오 인풋 데이터를 간단히 토치 텐서로 반환하는 작업을 수행한다.
         | 
| 100 | 
            +
                    input_features = [{"input_features": feature["input_features"]} for feature in features]
         | 
| 101 | 
            +
                    batch = self.processor.feature_extractor.pad(input_features, return_tensors="pt")
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                    # Tokenize된 레이블 시퀀스를 가져온다.
         | 
| 104 | 
            +
                    label_features = [{"input_ids": feature["labels"]} for feature in features]
         | 
| 105 | 
            +
                    # 레이블 시퀀스에 대해 최대 길이만큼 패딩 작업을 실시한다.
         | 
| 106 | 
            +
                    labels_batch = self.processor.tokenizer.pad(label_features, return_tensors="pt")
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                    # 패딩 토큰을 -100으로 치환하여 loss 계산 과정에서 무시되도록 한다.
         | 
| 109 | 
            +
                    labels = labels_batch["input_ids"].masked_fill(labels_batch.attention_mask.ne(1), -100)
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                    # 이전 토크나이즈 과정에서 bos 토큰이 추가되었다면 bos 토큰을 잘라낸다.
         | 
| 112 | 
            +
                    # 해당 토큰은 이후 언제든 추가할 수 있다.
         | 
| 113 | 
            +
                    if (labels[:, 0] == self.processor.tokenizer.bos_token_id).all().cpu().item():
         | 
| 114 | 
            +
                        labels = labels[:, 1:]
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                    batch["labels"] = labels
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                    return batch
         | 
| 119 | 
            +
             | 
| 120 | 
            +
             | 
| 121 | 
            +
            def compute_metrics(pred):
         | 
| 122 | 
            +
                pred_ids = pred.predictions
         | 
| 123 | 
            +
                label_ids = pred.label_ids
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                # pad_token을 -100으로 치환
         | 
| 126 | 
            +
                label_ids[label_ids == -100] = tokenizer.pad_token_id
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                # metrics 계산 시 special token들을 빼고 계산하도록 설정
         | 
| 129 | 
            +
                pred_str = tokenizer.batch_decode(pred_ids, skip_special_tokens=True)
         | 
| 130 | 
            +
                label_str = tokenizer.batch_decode(label_ids, skip_special_tokens=True)
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                cer = 100 * metric.compute(predictions=pred_str, references=label_str)
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                return {"cer": cer}
         | 
| 135 | 
            +
             | 
| 136 | 
            +
             | 
| 137 | 
            +
            # model_dir, ./repo 초기화
         | 
| 138 | 
            +
            if os.path.exists(model_dir):
         | 
| 139 | 
            +
                shutil.rmtree(model_dir)
         | 
| 140 | 
            +
            os.makedirs(model_dir)
         | 
| 141 | 
            +
             | 
| 142 | 
            +
            if os.path.exists('./repo'):
         | 
| 143 | 
            +
                shutil.rmtree('./repo')
         | 
| 144 | 
            +
            os.makedirs('./repo')
         | 
| 145 | 
            +
             | 
| 146 | 
            +
            # 파인튜닝을 진행하고자 하는 모델의 processor, tokenizer, feature extractor, model 로드
         | 
| 147 | 
            +
            processor = WhisperProcessor.from_pretrained(model_name, language="Korean", task="transcribe")
         | 
| 148 | 
            +
            tokenizer = WhisperTokenizer.from_pretrained(model_name, language="Korean", task="transcribe")
         | 
| 149 | 
            +
            feature_extractor = WhisperFeatureExtractor.from_pretrained(model_name)
         | 
| 150 | 
            +
            model = WhisperForConditionalGeneration.from_pretrained(model_name)
         | 
| 151 | 
            +
             | 
| 152 | 
            +
            data_collator = DataCollatorSpeechSeq2SeqWithPadding(processor=processor)
         | 
| 153 | 
            +
            metric = evaluate.load('cer')
         | 
| 154 | 
            +
            model.config.forced_decoder_ids = None
         | 
| 155 | 
            +
            model.config.suppress_tokens = []
         | 
| 156 | 
            +
             | 
| 157 | 
            +
             | 
| 158 | 
            +
            # Hub로부터 "모든 전처리가 완료된" 데이터셋을 로드(이게 진짜 오래걸려요.)
         | 
| 159 | 
            +
            preprocessed_dataset = load_dataset(dataset_name, cache_dir=CACHE_DIR)
         | 
| 160 | 
            +
             | 
| 161 | 
            +
            # 30%까지의 valid 데이터셋 선택(코드 작동 테스트를 위함)
         | 
| 162 | 
            +
            if is_test:
         | 
| 163 | 
            +
                preprocessed_dataset["valid"] = preprocessed_dataset["valid"].select(range(math.ceil(len(preprocessed_dataset) * 0.3)))
         | 
| 164 | 
            +
             | 
| 165 | 
            +
            # training_args 객체를 JSON 형식으로 변환
         | 
| 166 | 
            +
            training_args_dict = training_args.to_dict()
         | 
| 167 | 
            +
             | 
| 168 | 
            +
            # MLflow UI 관리 폴더 지정
         | 
| 169 | 
            +
            mlflow.set_tracking_uri("sqlite:////content/drive/MyDrive/STT_test/mlflow.db")
         | 
| 170 | 
            +
             | 
| 171 | 
            +
            # MLflow 실험 이름을 모델 이름으로 설정
         | 
| 172 | 
            +
            experiment_name = model_name
         | 
| 173 | 
            +
            existing_experiment = mlflow.get_experiment_by_name(experiment_name)
         | 
| 174 | 
            +
             | 
| 175 | 
            +
            if existing_experiment is not None:
         | 
| 176 | 
            +
                experiment_id = existing_experiment.experiment_id
         | 
| 177 | 
            +
            else:
         | 
| 178 | 
            +
                experiment_id = mlflow.create_experiment(experiment_name)
         | 
| 179 | 
            +
             | 
| 180 | 
            +
             | 
| 181 | 
            +
            model_version = 1  # 로깅 하려는 모델 버전(이미 존재하면, 자동 할당)
         | 
| 182 | 
            +
             | 
| 183 | 
            +
            # MLflow 로깅
         | 
| 184 | 
            +
            with mlflow.start_run(experiment_id=experiment_id, description=model_description):
         | 
| 185 | 
            +
                # training_args 로깅
         | 
| 186 | 
            +
                for key, value in training_args_dict.items():
         | 
| 187 | 
            +
                    mlflow.log_param(key, value)
         | 
| 188 | 
            +
             | 
| 189 | 
            +
             | 
| 190 | 
            +
                mlflow.set_tag("Dataset", dataset_name) # 데이터셋 로깅
         | 
| 191 | 
            +
             | 
| 192 | 
            +
                trainer = Seq2SeqTrainer(
         | 
| 193 | 
            +
                    args=training_args,
         | 
| 194 | 
            +
                    model=model,
         | 
| 195 | 
            +
                    train_dataset=preprocessed_dataset["train"],
         | 
| 196 | 
            +
                    eval_dataset=preprocessed_dataset["valid"],  # or "test"
         | 
| 197 | 
            +
                    data_collator=data_collator,
         | 
| 198 | 
            +
                    compute_metrics=compute_metrics,
         | 
| 199 | 
            +
                    tokenizer=processor.feature_extractor,
         | 
| 200 | 
            +
                )
         | 
| 201 | 
            +
             | 
| 202 | 
            +
                trainer.train()
         | 
| 203 | 
            +
                trainer.save_model(model_dir)  # 학습 후 모델 저장
         | 
| 204 | 
            +
             | 
| 205 | 
            +
                # Metric 로깅
         | 
| 206 | 
            +
                metrics = trainer.evaluate()
         | 
| 207 | 
            +
                for metric_name, metric_value in metrics.items():
         | 
| 208 | 
            +
                    mlflow.log_metric(metric_name, metric_value)
         | 
| 209 | 
            +
             | 
| 210 | 
            +
                # MLflow 모델 레지스터
         | 
| 211 | 
            +
                model_uri = "runs:/{run_id}/{artifact_path}".format(run_id=mlflow.active_run().info.run_id, artifact_path=model_dir)
         | 
| 212 | 
            +
             | 
| 213 | 
            +
                # 이 값 이용해서 허깅페이스 모델 이름 설정 예정
         | 
| 214 | 
            +
                model_details = mlflow.register_model(model_uri=model_uri, name=model_name.replace('/', '-'))   # 모델 이름에 '/'를 '-'로 대체
         | 
| 215 | 
            +
             | 
| 216 | 
            +
                # 모델 Description
         | 
| 217 | 
            +
                client = MlflowClient()
         | 
| 218 | 
            +
                client.update_model_version(name=model_details.name, version=model_details.version, description=model_description)
         | 
| 219 | 
            +
                model_version = model_details.version   # 버전 정보 허깅페이스 업로드 시 사용
         | 
| 220 | 
            +
             | 
| 221 | 
            +
             | 
| 222 | 
            +
             | 
| 223 | 
            +
            ## 허깅페이스 로그인
         | 
| 224 | 
            +
            while True:
         | 
| 225 | 
            +
             | 
| 226 | 
            +
                if token =="exit":
         | 
| 227 | 
            +
                    break
         | 
| 228 | 
            +
             | 
| 229 | 
            +
                try:
         | 
| 230 | 
            +
                    result = subprocess.run(["huggingface-cli", "login", "--token", token])
         | 
| 231 | 
            +
                    if result.returncode != 0:
         | 
| 232 | 
            +
                        raise Exception()
         | 
| 233 | 
            +
                    break
         | 
| 234 | 
            +
                except Exception as e:
         | 
| 235 | 
            +
                    token = input("Please enter your Hugging Face API token: ")
         | 
| 236 | 
            +
             | 
| 237 | 
            +
             | 
| 238 | 
            +
            os.environ["HUGGINGFACE_HUB_TOKEN"] = token
         | 
| 239 | 
            +
             | 
| 240 | 
            +
            # 리포지토리 이름 설정
         | 
| 241 | 
            +
            repo_name = "maxseats/" + model_name.replace('/', '-') + '-' + str(model_version)  # 허깅페이스 레포지토리 이름 설정
         | 
| 242 | 
            +
             | 
| 243 | 
            +
            # 리포지토리 생성
         | 
| 244 | 
            +
            create_repo(repo_name, exist_ok=True, token=token)
         | 
| 245 | 
            +
             | 
| 246 | 
            +
             | 
| 247 | 
            +
             | 
| 248 | 
            +
            # 리포지토리 클론
         | 
| 249 | 
            +
            repo = Repository(local_dir='./repo', clone_from=f"{repo_name}", use_auth_token=token)
         | 
| 250 | 
            +
             | 
| 251 | 
            +
             | 
| 252 | 
            +
            # model_dir 필요한 파일 복사
         | 
| 253 | 
            +
            max_depth = 1  # 순회할 최대 깊이
         | 
| 254 | 
            +
             | 
| 255 | 
            +
            for root, dirs, files in os.walk(model_dir):
         | 
| 256 | 
            +
                depth = root.count(os.sep) - model_dir.count(os.sep)
         | 
| 257 | 
            +
                if depth < max_depth:
         | 
| 258 | 
            +
                    for file in files:
         | 
| 259 | 
            +
                        # 파일 경로 생성
         | 
| 260 | 
            +
                        source_file = os.path.join(root, file)
         | 
| 261 | 
            +
                        # 대상 폴더에 복사
         | 
| 262 | 
            +
                        shutil.copy(source_file, './repo')
         | 
| 263 | 
            +
             | 
| 264 | 
            +
             | 
| 265 | 
            +
            # 토크나이저 다운로드 및 로컬 디렉토리에 저장
         | 
| 266 | 
            +
            tokenizer.save_pretrained('./repo')
         | 
| 267 | 
            +
             | 
| 268 | 
            +
             | 
| 269 | 
            +
            readme = f"""
         | 
| 270 | 
            +
            ---
         | 
| 271 | 
            +
            language: ko
         | 
| 272 | 
            +
            tags:
         | 
| 273 | 
            +
            - whisper
         | 
| 274 | 
            +
            - speech-recognition
         | 
| 275 | 
            +
            datasets:
         | 
| 276 | 
            +
            - {dataset_name}
         | 
| 277 | 
            +
            metrics:
         | 
| 278 | 
            +
            - cer
         | 
| 279 | 
            +
            ---
         | 
| 280 | 
            +
            # Model Name : {model_name}
         | 
| 281 | 
            +
            # Description
         | 
| 282 | 
            +
            {model_description}
         | 
| 283 | 
            +
            """
         | 
| 284 | 
            +
             | 
| 285 | 
            +
             | 
| 286 | 
            +
            # 모델 카드 및 기타 메타데이터 파일 작성
         | 
| 287 | 
            +
            with open("./repo/README.md", "w") as f:
         | 
| 288 | 
            +
                f.write(readme)
         | 
| 289 | 
            +
             | 
| 290 | 
            +
            # 파일 커밋 푸시
         | 
| 291 | 
            +
            repo.push_to_hub(commit_message="Initial commit")
         | 
| 292 | 
            +
             | 
| 293 | 
            +
            # 폴더와 하위 내용 삭제
         | 
| 294 | 
            +
            shutil.rmtree(model_dir)
         | 
| 295 | 
            +
            shutil.rmtree('./repo')
         | 
| 296 | 
            +
            ```
         | 
| 297 | 
            +
             | 
| 298 |  |