Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
.git
.github
.ai
.agents
.claude
__pycache__
*.pyc
*.pyo
*.pyd
.Python
env
build
develop-eggs
dist
downloads
eggs
.eggs
lib
lib64
parts
sdist
var
wheels
*.egg-info
.installed.cfg
*.egg
MANIFEST
.mypy_cache
.pytest_cache
.tox
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis
.idea
.vscode
*.swp
*.swo
*~
docs
notebooks
tests
examples
benchmark
benchmark_v2
utils
i18n
scripts
docker
*.md
!README.md
CITATION.cff
CODE_OF_CONDUCT.md
CONTRIBUTING.md
ISSUES.md
LICENSE
SECURITY.md
Makefile
conftest.py
doctest_list.txt
56 changes: 56 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
FROM python:3.10-slim

LABEL maintainer="Hugging Face Team"
LABEL description="Docker image for Hugging Face Transformers - Initial Runtime Environment"
LABEL repository="https://github.com/huggingface/transformers"

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV PYTHONPATH=/app/src
ENV UV_PYTHON=/usr/local/bin/python
ENV HF_HOME=/app/.cache/huggingface
ENV HF_HUB_OFFLINE=0

WORKDIR /app

RUN apt-get update && apt-get install -y --no-install-recommends \
git \
make \
build-essential \
curl \
ca-certificates \
pkg-config \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

RUN pip install --no-cache-dir -U pip setuptools wheel uv

COPY setup.py README.md ./

RUN uv pip install --no-cache-dir --system \
"torch>=2.4" \
"huggingface-hub>=1.5.0,<2.0" \
"numpy>=1.17" \
"packaging>=20.0" \
"pyyaml>=5.1" \
"regex>=2025.10.22" \
"tokenizers>=0.22.0,<=0.23.0" \
"safetensors>=0.4.3" \
"tqdm>=4.27" \
"typer" \
"accelerate>=1.1.0"

COPY pyproject.toml ./
COPY src/ ./src/

RUN uv pip install --no-cache-dir --system .

RUN mkdir -p /app/.cache/huggingface \
&& chmod -R 777 /app/.cache

RUN python -c "import transformers; print(f'Transformers v{transformers.__version__} installed successfully!')"

EXPOSE 8000

CMD ["python", "-c", "import transformers; print(f'Welcome to Transformers v{transformers.__version__}!')"]

75 changes: 68 additions & 7 deletions examples/pytorch/text-classification/run_classification.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,8 +408,41 @@ def main():

if data_args.label_column_name is not None and data_args.label_column_name != "label":
for key in raw_datasets:
if data_args.label_column_name not in raw_datasets[key].features:
raise ValueError(
f"Label column '{data_args.label_column_name}' not found in dataset split '{key}'. "
f"Available columns are: {list(raw_datasets[key].features.keys())}. "
f"Please check your --label_column_name argument."
)
raw_datasets[key] = raw_datasets[key].rename_column(data_args.label_column_name, "label")

text_columns = ["sentence"] if data_args.text_column_names is None else data_args.text_column_names.split(",")
for split in raw_datasets:
available_columns = list(raw_datasets[split].features.keys())
for col in text_columns:
if col not in available_columns:
raise ValueError(
f"Text column '{col}' not found in dataset split '{split}'. "
f"Available columns are: {available_columns}. "
f"Please use --text_column_names to specify correct text column name(s)."
)
if split != "test" and "label" not in available_columns:
raise ValueError(
f"Label column 'label' not found in dataset split '{split}'. "
f"Available columns are: {available_columns}. "
f"Please use --label_column_name to specify correct label column name."
)

if not training_args.do_train and not is_regression:
if not (hasattr(model.config, "label2id") and model.config.label2id):
raise ValueError(
"Classification prediction/evaluation without training requires "
"the loaded model to have label2id configuration. "
"Please use a model fine-tuned on your classification task."
)
configured_labels = set(model.config.label2id.keys())
logger.info(f"Model configured labels: {sorted(configured_labels)}")

# Trying to have good defaults here, don't hesitate to tweak to your needs.

is_regression = (
Expand Down Expand Up @@ -543,26 +576,54 @@ def main():
max_seq_length = min(data_args.max_seq_length, tokenizer.model_max_length)

def multi_labels_to_ids(labels: list[str]) -> list[float]:
ids = [0.0] * len(label_to_id) # BCELoss requires float as target type
ids = [0.0] * len(label_to_id)
for label in labels:
ids[label_to_id[label]] = 1.0
label_str = str(label)
if label_str not in label_to_id:
raise ValueError(
f"Label '{label}' not found in label mapping. "
f"Available labels: {list(label_to_id.keys())}. "
f"Please ensure all labels in the dataset match the labels used during training."
)
ids[label_to_id[label_str]] = 1.0
return ids

def preprocess_function(examples):
if data_args.text_column_names is not None:
text_column_names = data_args.text_column_names.split(",")
# join together text columns into "sentence" column
examples["sentence"] = examples[text_column_names[0]]
for column in text_column_names[1:]:
for i in range(len(examples[column])):
examples["sentence"][i] += data_args.text_column_delimiter + examples[column][i]
# Tokenize the texts

if "sentence" not in examples:
raise ValueError(
"Text column 'sentence' not found in dataset. "
"Please use --text_column_names to specify the correct text column name(s)."
)

examples["sentence"] = [str(s) if s is not None else "" for s in examples["sentence"]]

result = tokenizer(examples["sentence"], padding=padding, max_length=max_seq_length, truncation=True)

if label_to_id is not None and "label" in examples:
if is_multi_label:
result["label"] = [multi_labels_to_ids(l) for l in examples["label"]]
else:
result["label"] = [(label_to_id[str(l)] if l != -1 else -1) for l in examples["label"]]
processed_labels = []
for l in examples["label"]:
if l == -1:
processed_labels.append(-1)
continue
label_str = str(l)
if label_str not in label_to_id:
raise ValueError(
f"Label '{l}' found in dataset is not recognized. "
f"Valid labels are: {sorted(label_to_id.keys())}. "
f"Please ensure all labels match the training label set."
)
processed_labels.append(label_to_id[label_str])
result["label"] = processed_labels
return result

# Running the preprocessing pipeline on all the datasets
Expand Down Expand Up @@ -721,10 +782,10 @@ def compute_metrics(p: EvalPrediction):
writer.write(f"{index}\t{item:3.3f}\n")
elif is_multi_label:
# recover from multi-hot encoding
item = [label_list[i] for i in range(len(item)) if item[i] == 1]
item = [model.config.id2label[i] for i in range(len(item)) if item[i] == 1]
writer.write(f"{index}\t{item}\n")
else:
item = label_list[item]
item = model.config.id2label[item]
writer.write(f"{index}\t{item}\n")
logger.info(f"Predict results saved at {output_predict_file}")
kwargs = {"finetuned_from": model_args.model_name_or_path, "tasks": "text-classification"}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
text,label
"This is a bad movie.",negative
"I really enjoyed this film.",positive
"Horrible, don't watch.",negative
"Amazing movie, I recommend it!",positive
"Terrible acting.",negative
"Great story!",positive
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
#!/usr/bin/env python
"""
边界情况测试:验证异常处理和报错机制
"""
import os
import sys
import subprocess
import tempfile
import csv

def run_test(args, description, expect_success=True):
"""运行测试并检查结果"""
print(f"\n{'='*60}")
print(f"测试: {description}")
print(f"{'='*60}")

cmd = f"cd /Users/pkcha/transformers && python examples/pytorch/text-classification/run_classification.py {args}"
print(f"命令: python run_classification.py {args[:100]}...")

result = subprocess.run(cmd, shell=True, capture_output=True, text=True)

if result.returncode == 0:
print(f"✅ 执行成功 (返回码: 0)")
if not expect_success:
print(f"⚠️ 警告: 预期失败但实际成功!")
return False
return True
else:
print(f"❌ 执行失败 (返回码: {result.returncode})")
last_lines = result.stderr.strip().split('\n')[-10:]
print("\n错误信息(最后10行):")
print("-" * 40)
for line in last_lines:
if 'ValueError' in line or 'Error' in line or 'error' in line or 'Found' in line:
print(f" >>> {line}")
elif line.strip():
print(f" {line}")
print("-" * 40)

if expect_success:
print(f"⚠️ 警告: 预期成功但实际失败!")
return False
return True

def create_test_csv(filename, rows):
"""创建测试CSV文件"""
filepath = os.path.join('/Users/pkcha/transformers/examples/pytorch/text-classification/test_label_mapping', filename)
with open(filepath, 'w', newline='') as f:
writer = csv.writer(f)
writer.writerows(rows)
return filepath

def main():
test_dir = '/Users/pkcha/transformers/examples/pytorch/text-classification/test_label_mapping'
os.chdir(test_dir)

print("开始边界情况测试...")

# 1. 测试未知标签报错
print("\n\n【测试1】未知标签报错")
train_csv_bad_label = create_test_csv('train_bad_label.csv', [
['text', 'label'],
['Good movie', 'positive'],
['Bad film', 'unknown_label']
])
val_csv = create_test_csv('val_simple.csv', [
['text', 'label'],
['Great!', 'positive'],
])

args = f"""--model_name_or_path distilbert-base-uncased \
--train_file {train_csv_bad_label} \
--validation_file {val_csv} \
--do_train \
--per_device_train_batch_size 2 \
--num_train_epochs 1 \
--max_seq_length 32 \
--output_dir /tmp/test_output 2>&1
"""
run_test(args, "数据包含未知标签,应抛出明确错误", expect_success=False)

# 2. 测试文本列缺失报错
print("\n\n【测试2】文本列缺失报错")
train_csv_no_text = create_test_csv('train_no_text.csv', [
['wrong_column', 'label'],
['Good movie', 'positive'],
['Bad film', 'negative']
])

args = f"""--model_name_or_path distilbert-base-uncased \
--train_file {train_csv_no_text} \
--validation_file {val_csv} \
--text_column_names text \
--do_train \
--max_seq_length 32 \
--output_dir /tmp/test_output 2>&1
"""
run_test(args, "文本列不存在,应抛出明确错误", expect_success=False)

# 3. 测试标签列缺失报错
print("\n\n【测试3】标签列缺失报错")
train_csv_no_label = create_test_csv('train_no_label.csv', [
['text', 'wrong_label'],
['Good movie', 'positive'],
])

args = f"""--model_name_or_path distilbert-base-uncased \
--train_file {train_csv_no_label} \
--validation_file {val_csv} \
--label_column_name label \
--do_train \
--max_seq_length 32 \
--output_dir /tmp/test_output 2>&1
"""
run_test(args, "标签列不存在,应抛出明确错误", expect_success=False)

# 4. 测试空文本处理 (应正常工作)
print("\n\n【测试4】空文本处理")
train_csv_empty = create_test_csv('train_empty_text.csv', [
['text', 'label'],
['', 'positive'],
[None, 'negative'],
['normal text', 'positive'],
])

args = f"""--model_name_or_path distilbert-base-uncased \
--train_file {train_csv_empty} \
--validation_file {val_csv} \
--do_train \
--per_device_train_batch_size 2 \
--num_train_epochs 1 \
--max_seq_length 32 \
--output_dir /tmp/test_output --overwrite_output_dir 2>&1
"""
run_test(args, "空文本应正常处理,不崩溃", expect_success=True)

# 5. 验证标签一致性完整流程
print("\n\n【测试5】完整流程标签一致性")
print("正在训练基础模型...")

# 6. 运行单元测试
print("\n\n【测试6】运行单元测试验证修复逻辑")
result = subprocess.run('python test_label_mapping_unit.py', shell=True, cwd=test_dir, capture_output=True, text=True)
print(result.stdout)
if result.returncode == 0:
print("✅ 单元测试全部通过!")

print("\n" + "="*60)
print("边界情况测试完成!")
print("="*60)

if __name__ == "__main__":
main()
Loading
Loading