선택자 {선언(속성)}지난 시간에 배운 선택자 종류
위 두 가지는 기본적인 속성들에 많이 활용
→ 범위가 너무 넓기 때문에 중요하지 않은 스타일 정도에 활용이 됨(예: font-size, font-style 등)
디테일하게 그룹화를 시킨다거나 특정 내용을 반복시킬 때는 활용되지 않음
범위가 좁을수록 선택자의 우선 순위가 높아지기 때문에 아이디 선택자의 우선 순위가 가장 높다.

border: 1px solid black;를 총 3번 써야 하나? → 비효율적class="" 속성은 특정 그룹을 만드는 거라 적합하지 않음: 태그들에게 모두 적용할 거니까 태그를 묶어서 한번에 쓰면 됨table, th, td {
border: 1px solid black;
}

.tr-bg {
background-color: skyblue;
}
.td-align {
text-align: center;
}
HTML 속성으로 디자인 한 부분을 모두 CSS로 변환 → 업무 분배하기 편하고 관리하기 편해졌음 → "유지보수성"
내가 코드를 한 번만 수정하더라도 모든 태그가 이름만 갖고 있다면 한 번에 수정된 내용을 적용할 수 있음

*로 표시# 기호로 표시되며 특정한 요소 선택id=는 여러 개 있을 수 있는데 그 안의 value는 딱 하나씩만 있음

span을 이렇게 쓰면 원래 안 되긴 하지만 설명을 위해 넣었습니다.

(space)> ★★★+
~총정리: 선택자는 크게 3가지만 기억하자(id, class, 자식)





h1, span, li{
color: blue;
font-size: 50px;
}
:active → 마우스로 클릭할 때 선택:hover → 마우스를 올린 태그를 선택HTML 반응 선택자(pseudo-class)는 사용자의 특정 행동에 따라 HTML 요소의 스타일을 변경하는 CSS 선택자입니다. 예를 들어, 마우스 커서를 요소 위에 올렸을 때(hover), 클릭했을 때(active) 등 사용자의 동작에 반응하여 스타일을 적용할 수 있습니다.
:hover → 사용자가 마우스 커서를 요소 위에 올렸을 때 스타일을 적용:active → 사용자가 요소를 클릭했을 때 스타일을 적용:focus → 요소에 포커스가 맞춰졌을 때 스타일을 적용 (주로 입력 필드에서 사용):visited → 이미 방문한 링크에 스타일을 적용:link → 아직 방문하지 않은 링크에 스타일을 적용
<style>
a:hover {
color: red;
text-decoration: underline;
}
button:active {
background-color: lightgray;
}
input:focus {
border: 2px solid blue;
}
</style>
<a href="#">링크</a>
<button>버튼</button>
<input type="text" placeholder="입력하세요">
위 예시에서 <a> 태그는 마우스 커서가 위에 올라가면 빨간색 글씨에 밑줄이 생기고, <button> 태그는 클릭하면 배경색이 회색으로 변하고, <input> 태그는 포커스가 맞춰지면 테두리가 파란색으로 변함 → 반응 선택자를 사용하면 웹 페이지를 더욱 동적이고 사용하기 편리하게 만들 수 있다!
| 가중치 | 스타일 적용 | 예시 |
|---|---|---|
| 0 | 전체 선택자 | *{color:red'} |
| 1 | 타입 선택자 | p{color:red'} |
| 10 | 클래스 선택자 | .txt{color:red'} |
| 100 | 아이디 선택자 | #main{color:red'} |
#box p span( /* 100+1+1=102 */
color: yellow;
}
#box .my_color span{ /* 100+10+1 = 111 */
color: red;
}
<div>, <p>, <span>


flex 속성을 사용하면 블록 요소 옆에다 블록 요소를 배치할 수 있게 도와줌
기본적으로 block 요소 옆에 block 요소를 배치하는 일은 어려움 → 이미 차지하고 있는 공간을 깨고 들어가는 것이기 때문에 공간 배치가 어렵다
하지만 배치를 했을 때 깔끔하게 떨어진다는 장점도 있음(가로, 세로 지정할 수 있기 때문에 정확히 쪼개 쓸 수 있음)

display: blockdisplay: inlinedisplay: none


display: None은 언제 쓰나요?

span {
display: none;
}
/*
미션: h1 태그에 마우스를 올렸을 때 span 태그의 display를 inline으로 변경
* 핵심: 반드시 선택자는 마지막이 대상
* none 사용처: 메뉴 바 사용 → 마우스를 올리면 하위 메뉴가 등장, 떼면 사라진다
*/
h1:hover+span {
display: inline;
}
영역배치! → 예쁘게 꾸미는 건 사실 감각임 & 고객경험을 활용한 UI/UX
진짜 어려운 건 "배치"






from datasets import load_dataset
books = load_dataset("opus100", "en-ko", split="train")
books
Dataset({
features: ['translation'],
num_rows: 1000000
})
books = books.shuffle(seed=12).select(range(1000))
books
Dataset({
features: ['translation'],
num_rows: 1000
})
books = books.train_test_split(test_size=0.2, seed=12)
books
DatasetDict({
train: Dataset({
features: ['translation'],
num_rows: 800
})
test: Dataset({
features: ['translation'],
num_rows: 200
})
})
books["train"][0]
{'translation': {'en': 'Calling me uneducated!', 'ko': '나보고 문화가 없다니'}}
books["train"][1]
{'translation': {'en': 'So if the Dr.iver of the train was in on it, then the passenger did get off.',
'ko': '그러니까, 기관사는 그대로 있고 승객은 사라졌다?'}}
from transformers import AutoTokenizer
checkpoint="Helsinki-NLP/opus-mt-tc-big-en-ko"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
# T5 계열 모델을 사용시 prefix
# MarianMT 계열 모델은 prefix 사용 X
def preprocess_function(example):
# 원어(영어)
inputs = [ex["en"] for ex in example["translation"]]
# 번역어(한국어)
targets = [ex["ko"] for ex in example["translation"]]
# 토큰화
model_inputs = tokenizer(
inputs
, text_target = targets
, max_length=128
, truncation=True
) # 나머지는 DataCollator에서 할 거임
return model_inputs
# 데이터셋에 전처리 함수 적용
tokenized_books = books.map(preprocess_function, batched=True)
batched=True는 여러 샘플을 한 번에 토크나이징함으로써 tokenization 속도를 크게 향상시킵니다.
이는 내부적으로 텍스트 리스트를 한 번에 처리해 for 루프 등에서 개별적으로 처리할 때보다 훨씬 적은 반복과 함수 호출, I/O를 사용하게 되며, 특히 "빠른(fast) 토크나이저"의 경우 Rust 기반 코드의 병렬화와 최적화 덕분에 속도가 대폭 빨라집니다.
batched=True를 사용할 경우 약 10초, batched=False일 경우 59초가 소요되어 실제로 5~6배 이상 속도 차이가 발생합니다.따라서 batched=True 옵션은 토크나이저가 한 번에 더 많은 데이터를 효율적으로 처리할 수 있게 하여, 텍스트 데이터가 많을수록 속도 개선 효과가 더욱 커집니다.
batched=True는 map 함수에서 한 번에 여러 샘플(예시)을 한꺼번에 전처리 함수에 전달하도록 하여, 함수가 리스트 또는 딕셔너리 형태(배치 단위)로 데이터를 처리하게 만듭니다. 즉, 하나씩 처리하는 게 아니라 여러 개를 동시에 처리하므로 처리 속도가 훨씬 빨라집니다.
tokenizer와의 관련성:
huggingface tokenizer는 batch 처리를 지원합니다. 예를 들어, 하나의 문장이 아니라 여러 문장 리스트를 동시에 토크나이즈할 수 있는데, batched=True가 있어야 전처리 함수에서 예시들이 리스트로 들어와 batch로 토크나이징됩니다. 이런 식으로 전처리할 때 padding, truncation 등을 batch 전체 기준으로 맞춰 줄 수 있어 효율적입니다.
datacollator와의 관련성:
DataCollator는 모델 입력 직전에 여러 샘플을 하나의 tensor 배치로 만드는 역할을 합니다. 이 단계에서는 이미 tokenized 된 데이터(예: 여러 샘플의 input_ids)를 받아서 길이가 다르면 가장 긴 샘플 기준으로 padding해서 최종적으로 동일 길이 Tensor(batch)를 만듭니다.
따라서 batched=True는 토크나이저가 여러 샘플을 한 번에 처리할 수 있도록 해주고, DataCollator는 이미 토크나이즈된 여러 샘플들을 Tensor 배치 구조로 변환·패딩합니다.
| 단계 | 기능 | batch와의 관련성 |
|---|---|---|
| map(..., batched=True) | tokenizer가 여러 샘플을 한 번에 전처리 | 전처리 속도가 빠르고, batch별 패딩 등 처리 가능 |
| datacollator | 이미 토크나이즈된 샘플들을 Tensor batch(고정 길이)로 변환 | 각각의 샘플을 padding 등으로 맞춰 주는 역할 |
정리하자면, batched=True는 데이터셋 map 함수에서 여러 샘플을 한 번에 처리해 tokenizer의 batch 기능을 활용하는 것이며, DataCollator는 tokenized 데이터를 이용해 모델 입력으로 쓸 Tensor batch를 만들어주는 별도의 역할을 합니다. 둘 다 batch와 관련 있지만, 적용 시점과 목적이 다릅니다.
from transformers import DataCollatorForSeq2Seq
data_collator = DataCollatorForSeq2Seq(
tokenizer=tokenizer
, model=checkpoint
)
Gradio sacrebleu 창에 복사-붙여넣기:
predictions (모델이 번역한 문장 리스트)
[
"This plugin lets you translate web pages between several languages automatically.",
"I like deep learning."
]
references (각 예측 문장에 대응하는 정답 문장들의 리스트의 리스트)
[
["This plugin allows you to automatically translate web pages between several languages."],
["I love deep learning.", "I like deep learning."]
]
Gradio sacrebleu 인터페이스에서는 아래처럼
자동으로 BLEU 점수가 계산되어 나옵니다.
요점:
[...]) 형식이어야 하며!pip install -q sacrebleu
!pip install -q evaluate
import evaluate
metric = evaluate.load("sacrebleu")
import numpy as np
# BLEU 평가 지표를 활용하기 위한 format → 양식 맞춰주기
# 정답 데이터: List[List[str]]
def postprocess_function (preds, labels):
# 모델의 결과값에 양 끝의 공백을 제거
preds = [pred.strip() for pred in preds]
labels = [[label.strip()] for label in labels]
return preds, labels
# 평가 함수
def compute_metrics(eval_preds):
preds, labels = eval_preds
# 전처리: preds가 가끔 array 형태로 나오는 경우가 있어서 그것까지 고려
if isinstance(preds, tuple):
preds = preds[0]
# 인코딩된 데이터를 사람이 볼 수 있는 텍스트로 변환 → decode
decode_preds = tokenizer.batch_decode(preds, skip_special_tokens=True)
# 예측값은 padding이 따로 붙지 않기 때문에 디코딩만 하면 끝남
# 문제는 정답이다… → 특수 토큰(-100)을 처리해야 함
# 정답 데이터 decode
# 각 값들에 특수토큰(시작 토큰, 패딩 등)이 -100으로 변환되어 있는 상태
# 각 모델이 가지고 있는 특수 토큰을 의미하는 id(pad_token_id에 들어 있음)로 재변환
labels = np.where(labels != -100, labels, tokenizer.pad_token_id)
decode_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)
# 문자열로 변환한 데이터를 BLEU에 맞게 list[list[str]] 변환
decode_preds, decode_labels = postprocess_function(decode_preds, decode_labels)
# BLEU 점수 계산
result = metric.compute(predictions=decode_preds, references=decode_labels)
result = {"bleu": result["score"]}
# 생성된 결과에서 [PAD] 토큰을 제외한 실제 길이를 계산
prediction_len = [np.count_nonzero(pred!=tokenizer.pad_token_id) for pred in preds]
# 평균값을 출력
result["gen_len"] = np.mean(prediction_len)
# 소수점 4번째 자리 수까지만 출력
result = {k: round(v, 4) for k, v in result.items()}
return result
List[List[str]][문장1, 문장2, 문장3, …]이 아니라 [[문장1], [문장2], [문장3], …][[문장1-1, 문장1-2, …], [문장2-1, 문장2-2, …], [문장3-1, 문장3-2, …], …] 형태의 정답 데이터를 받는 경우 → 하나의 문장에 대한 복수 정답 인정from transformers import AutoModelForSeq2SeqLM, Seq2SeqTrainingArguments, Seq2SeqTrainer
model = AutoModelForSeq2SeqLM.from_pretrained(checkpoint)
training_args = Seq2SeqTrainingArguments(
output_dir="./results/my_awesome_opus_books_model"
, eval_strategy="epoch"
, save_strategy="epoch"
, learning_rate=2e-5
, weight_decay=0.01
, per_device_train_batch_size=16
, per_device_eval_batch_size=16
, save_total_limit=3
, num_train_epochs=2
, predict_with_generate=True
, logging_strategy="steps"
, logging_steps=2 # 평가 지표를 기준으로 훈련이 끝났을 때 가장 성능이 좋은 모델을 자동으로 로드
, load_best_model_at_end=True
, fp16=True
, push_to_hub=False
)
trainer = Seq2SeqTrainer(
model=model
, args=training_args
, train_dataset=tokenized_books["train"]
, eval_dataset=tokenized_books["test"]
, data_collator=data_collator
, processing_class=tokenizer
, compute_metrics=compute_metrics
)
trainer.train()

%cd /content/drive/MyDrive/Colab Notebooks/NLP
# 허깅페이스 로그인
from huggingface_hub import login
# 파일 형태의 api_key 불러오기
with open("./key/huggingface_api_key", 'r') as f:
api_key = f.read().strip()
login(token=api_key)
# 허깅페이스 업로드
repo_id = "★유저아이디★/opus-translation-en-ko"
trainer.save_model(repo_id)
model.save_pretrained(repo_id)
tokenizer.save_pretrained(repo_id)
trainer.push_to_hub(repo_id)
# pipeline 사용해서 불러오기
# task: translation_xx_to_yy
from transformers import pipeline
checkpoint_mymodel="be2be2/opus-translation-en-ko"
translation = pipeline(
task="translation_xx_to_yy"
, model=checkpoint_mymodel
, tokenizer=checkpoint_mymodel
)
text = "So if the Dr.iver of the train was in on it, then the passenger did get off."
result = translation(text)
result
[{'translation_text': '- 네, 괜찮아요?'}]
데이터를 조금밖에 넣지 않아서 성능이 좋지 않음😅