2026-07-08 21:58:01 +08:00
|
|
|
# %%
|
|
|
|
|
import tensorflow as tf
|
|
|
|
|
import torch
|
|
|
|
|
import torchvision
|
|
|
|
|
from torch.utils.data import IterableDataset, DataLoader
|
|
|
|
|
from matplotlib import pyplot as plt
|
|
|
|
|
import numpy as np
|
|
|
|
|
import torch.nn as nn
|
|
|
|
|
from PIL import Image
|
|
|
|
|
def parse_tfrecord(example_proto):
|
|
|
|
|
feature_description = {
|
|
|
|
|
'image': tf.io.FixedLenFeature([], tf.string),
|
|
|
|
|
'class': tf.io.FixedLenFeature([], tf.int64),
|
|
|
|
|
'id' : tf.io.FixedLenFeature([], tf.string),
|
|
|
|
|
}
|
|
|
|
|
parsed = tf.io.parse_single_example(example_proto, feature_description)
|
|
|
|
|
image = tf.image.decode_jpeg(parsed['image'], channels=3)
|
|
|
|
|
image = tf.image.resize(image, [224, 224])
|
|
|
|
|
#image = tf.image.convert_image_dtype(image, tf.float32)
|
|
|
|
|
label = parsed['class']
|
|
|
|
|
idd = parsed['id']
|
|
|
|
|
return image, label,idd
|
|
|
|
|
|
|
|
|
|
def load_tfrecord_dataset(pattern):
|
|
|
|
|
files = tf.io.gfile.glob(pattern)
|
|
|
|
|
if not files:
|
|
|
|
|
raise ValueError(f"No files found for pattern {pattern}")
|
|
|
|
|
dataset = tf.data.TFRecordDataset(files)
|
|
|
|
|
dataset = dataset.map(parse_tfrecord)
|
|
|
|
|
# 可选:打乱、批处理等,但此处我们只返回样本级别的数据集
|
|
|
|
|
return dataset
|
|
|
|
|
|
|
|
|
|
class TFRecordToPyTorch(IterableDataset):
|
|
|
|
|
def __init__(self, tfrecord_pattern,transform=None):
|
|
|
|
|
self.tfrecord_pattern = tfrecord_pattern
|
|
|
|
|
self.transform=transform
|
|
|
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
|
# 每次迭代创建新的数据集,保证可重复使用
|
|
|
|
|
dataset = load_tfrecord_dataset(self.tfrecord_pattern)
|
|
|
|
|
# 使用 as_numpy_iterator() 获取 NumPy 数组,便于转换为 PyTorch 张量
|
|
|
|
|
for image_np, label_np,idd in dataset.as_numpy_iterator():
|
|
|
|
|
# image_np shape: (224,224,3), dtype float32, label_np scalar int64
|
|
|
|
|
# 转为 PyTorch 张量,并调整为 CxHxW
|
|
|
|
|
image_pil = Image.fromarray((image_np).astype('uint8'))
|
|
|
|
|
if self.transform:
|
|
|
|
|
image_tensor = self.transform(image_pil)
|
|
|
|
|
else:
|
|
|
|
|
# 如果不需要 transform,至少转为 tensor
|
|
|
|
|
image_tensor = torch.from_numpy(image_np).permute(2,0,1)
|
|
|
|
|
#image_torch = torch.from_numpy(image_np).permute(2, 0, 1) # (3,224,224)
|
|
|
|
|
label_torch = torch.tensor(label_np, dtype=torch.long)
|
|
|
|
|
id_torch = idd
|
|
|
|
|
yield image_tensor, label_torch,id_torch
|
|
|
|
|
|
|
|
|
|
# 使用
|
|
|
|
|
transform = torchvision.transforms.Compose([
|
|
|
|
|
torchvision.transforms.ToTensor(),
|
|
|
|
|
torchvision.transforms.Normalize(mean=[0.485, 0.456, 0.406],
|
|
|
|
|
std=[0.229, 0.224, 0.225])
|
|
|
|
|
])
|
|
|
|
|
tfrecord_path = '/kaggle/input/competitions/tpu-getting-started/tfrecords-jpeg-224x224/train/*'
|
|
|
|
|
dataset = TFRecordToPyTorch(tfrecord_path,transform)
|
|
|
|
|
tfrecord_path = '/kaggle/input/competitions/tpu-getting-started/tfrecords-jpeg-224x224/val/*'
|
|
|
|
|
dataset2 = TFRecordToPyTorch(tfrecord_path,transform)
|
|
|
|
|
# 可以配合 DataLoader 使用
|
|
|
|
|
train_dataloader = DataLoader(dataset, batch_size=32, num_workers=0) # num_workers 设为0,因为 TF 数据集内部已并行
|
|
|
|
|
val_dataloader = DataLoader(dataset2, batch_size=32, num_workers=0)
|
|
|
|
|
for batch in train_dataloader:
|
|
|
|
|
plt.imshow(batch[0][1].permute(1,2,0).numpy())
|
|
|
|
|
break
|
|
|
|
|
plt.axis('off')
|
|
|
|
|
plt.show()
|
|
|
|
|
# %%
|
|
|
|
|
pretrained_net = torchvision.models.resnet50(pretrained=True)
|
|
|
|
|
# %%
|
|
|
|
|
pretrained_net.fc=nn.Linear(pretrained_net.fc.in_features,104)
|
|
|
|
|
nn.init.xavier_uniform_(pretrained_net.fc.weight)
|
|
|
|
|
# %%
|
|
|
|
|
for parm in pretrained_net.conv1.parameters():
|
|
|
|
|
parm.requires_grad=False
|
|
|
|
|
for parm in pretrained_net.bn1.parameters():
|
|
|
|
|
parm.requires_grad=False
|
|
|
|
|
for parm in pretrained_net.layer1.parameters():
|
|
|
|
|
parm.requires_grad=False
|
|
|
|
|
for parm in pretrained_net.layer2.parameters():
|
|
|
|
|
parm.requires_grad=False
|
|
|
|
|
for parm in pretrained_net.layer3.parameters():
|
|
|
|
|
parm.requires_grad=False
|
|
|
|
|
# %%
|
2026-07-09 21:42:42 +08:00
|
|
|
def print_trainable_info(model):
|
2026-07-08 21:58:01 +08:00
|
|
|
frozen = sum(p.numel() for p in model.parameters() if not p.requires_grad)
|
|
|
|
|
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
|
|
|
|
|
total = frozen + trainable
|
|
|
|
|
print(f" 冻结参数: {frozen:,} 可训练参数: {trainable:,} ({100.*trainable/total:.1f}%)")
|
|
|
|
|
# %%
|
|
|
|
|
print_trainable_info(pretrained_net)
|
|
|
|
|
# %%
|
|
|
|
|
@torch.no_grad()
|
|
|
|
|
def validate(model,loader):
|
|
|
|
|
model.eval()
|
|
|
|
|
acc=0
|
|
|
|
|
total=0
|
|
|
|
|
for batch in loader:
|
|
|
|
|
X = batch[0]
|
|
|
|
|
labels = batch[1]
|
|
|
|
|
X = X.to(device)
|
|
|
|
|
labels = labels.to(device)
|
|
|
|
|
pred=torch.argmax(model(X),dim=1)
|
|
|
|
|
acc+=pred.eq(labels).sum()
|
|
|
|
|
total+=labels.size(0)
|
|
|
|
|
print(f"acc:{acc/total}")
|
|
|
|
|
# %%
|
|
|
|
|
from tqdm import tqdm
|
|
|
|
|
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
|
|
|
|
pretrained_net=pretrained_net.to(device)
|
|
|
|
|
loss_func = nn.CrossEntropyLoss()
|
|
|
|
|
optimizer = torch.optim.AdamW(pretrained_net.parameters(), lr=2e-4)
|
|
|
|
|
scheduler=torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=10)
|
|
|
|
|
epochs = 20
|
|
|
|
|
for epoch in range(epochs):
|
|
|
|
|
pretrained_net.train()
|
|
|
|
|
training_loss = 0
|
|
|
|
|
|
|
|
|
|
# 使用 tqdm 包装 dataloader,并设置描述信息
|
|
|
|
|
progress_bar = tqdm(train_dataloader, desc=f"Epoch {epoch+1}/{epochs}")
|
|
|
|
|
lens=0
|
|
|
|
|
for batch in progress_bar:
|
|
|
|
|
optimizer.zero_grad()
|
|
|
|
|
X = batch[0].to(device)
|
|
|
|
|
labels = batch[1].to(device)
|
|
|
|
|
|
|
|
|
|
outputs = pretrained_net(X)
|
|
|
|
|
loss = loss_func(outputs, labels)
|
|
|
|
|
loss.backward()
|
|
|
|
|
optimizer.step()
|
|
|
|
|
|
|
|
|
|
training_loss += loss.item()
|
|
|
|
|
lens+=labels.size(0)
|
|
|
|
|
# 更新进度条显示当前 batch 的损失
|
|
|
|
|
progress_bar.set_postfix({
|
|
|
|
|
'loss': f'{loss.item():.4f}',
|
|
|
|
|
'avg_loss': f'{training_loss / (progress_bar.n+1):.4f}' # progress_bar.n 是已处理 batch 数
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
scheduler.step()
|
|
|
|
|
|
|
|
|
|
# 计算平均训练损失(注意:len(train_dataloader) 才是 batch 总数)
|
|
|
|
|
avg_train_loss = training_loss / lens
|
|
|
|
|
print(f"Epoch {epoch+1} train_loss: {avg_train_loss:.4f}")
|
|
|
|
|
|
|
|
|
|
# 验证(你也可以为验证添加进度条,见下方建议)
|
|
|
|
|
validate(pretrained_net, val_dataloader)
|
|
|
|
|
|
|
|
|
|
# %%
|
|
|
|
|
import pandas as pd
|
|
|
|
|
def parse_tfrecord_test(example_proto):
|
|
|
|
|
feature_description = {
|
|
|
|
|
'image': tf.io.FixedLenFeature([], tf.string),
|
|
|
|
|
'id' : tf.io.FixedLenFeature([], tf.string)
|
|
|
|
|
}
|
|
|
|
|
parsed = tf.io.parse_single_example(example_proto, feature_description)
|
|
|
|
|
image = tf.image.decode_jpeg(parsed['image'], channels=3)
|
|
|
|
|
image = tf.image.resize(image, [224, 224])
|
|
|
|
|
#image = tf.image.convert_image_dtype(image, tf.float32)
|
|
|
|
|
idd = parsed['id']
|
|
|
|
|
return image,idd
|
|
|
|
|
def load_tfrecord_dataset_test(pattern):
|
|
|
|
|
files = tf.io.gfile.glob(pattern)
|
|
|
|
|
if not files:
|
|
|
|
|
raise ValueError(f"No files found for pattern {pattern}")
|
|
|
|
|
dataset = tf.data.TFRecordDataset(files)
|
|
|
|
|
dataset = dataset.map(parse_tfrecord_test)
|
|
|
|
|
# 可选:打乱、批处理等,但此处我们只返回样本级别的数据集
|
|
|
|
|
return dataset
|
|
|
|
|
class TFRecordToPyTorchTest(IterableDataset):
|
|
|
|
|
def __init__(self, tfrecord_pattern,transform=None):
|
|
|
|
|
self.tfrecord_pattern = tfrecord_pattern
|
|
|
|
|
self.transform=transform
|
|
|
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
|
# 每次迭代创建新的数据集,保证可重复使用
|
|
|
|
|
dataset = load_tfrecord_dataset_test(self.tfrecord_pattern)
|
|
|
|
|
# 使用 as_numpy_iterator() 获取 NumPy 数组,便于转换为 PyTorch 张量
|
|
|
|
|
for image_np,idd in dataset.as_numpy_iterator():
|
|
|
|
|
# image_np shape: (224,224,3), dtype float32, label_np scalar int64
|
|
|
|
|
# 转为 PyTorch 张量,并调整为 CxHxW
|
|
|
|
|
image_pil = Image.fromarray((image_np).astype('uint8'))
|
|
|
|
|
if self.transform:
|
|
|
|
|
image_tensor = self.transform(image_pil)
|
|
|
|
|
else:
|
|
|
|
|
# 如果不需要 transform,至少转为 tensor
|
|
|
|
|
image_tensor = torch.from_numpy(image_np).permute(2,0,1)
|
|
|
|
|
#image_torch = torch.from_numpy(image_np).permute(2, 0, 1) # (3,224,224)
|
|
|
|
|
#label_torch = torch.tensor(label_np, dtype=torch.long)
|
|
|
|
|
id_torch = idd
|
|
|
|
|
yield image_tensor,id_torch
|
|
|
|
|
tfrecord_path = '/kaggle/input/competitions/tpu-getting-started/tfrecords-jpeg-224x224/test/*'
|
|
|
|
|
dataset3 = TFRecordToPyTorchTest(tfrecord_path,transform)
|
|
|
|
|
test_dataloader = DataLoader(dataset3, batch_size=32, num_workers=0)
|
|
|
|
|
id_array=[]
|
|
|
|
|
all_preds=[]
|
|
|
|
|
with torch.no_grad():
|
|
|
|
|
for batch in test_dataloader:
|
|
|
|
|
input_ids = batch[0].to(device)
|
|
|
|
|
idd = batch[1]
|
|
|
|
|
outputs = pretrained_net(input_ids)
|
|
|
|
|
preds = torch.argmax(outputs, dim=1)
|
|
|
|
|
all_preds.extend(preds.cpu().numpy())
|
|
|
|
|
id_array.extend(idd)
|
|
|
|
|
submission = pd.DataFrame({
|
|
|
|
|
'id':id_array,
|
|
|
|
|
'label': all_preds
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
# %%
|
|
|
|
|
submission['id'] = submission['id'].apply(lambda x: x.decode('utf-8'))
|
|
|
|
|
print(submission)
|
|
|
|
|
submission.to_csv('submission.csv', index=False)
|
|
|
|
|
print("Submission saved!")
|
|
|
|
|
# %%
|