AI, we don't believe that you can fly bro pt 2

This commit is contained in:
2026-01-11 15:30:49 -06:00
parent ad33523ae2
commit 10880809a7
2 changed files with 794 additions and 89 deletions

View File

@@ -28,6 +28,7 @@ if torch.cuda.is_available():
print(f"✓ GPU: {torch.cuda.get_device_name(0)}") print(f"✓ GPU: {torch.cuda.get_device_name(0)}")
device = torch.device('cuda:0') device = torch.device('cuda:0')
torch.backends.cudnn.benchmark = True torch.backends.cudnn.benchmark = True
torch.backends.cudnn.enabled = True
else: else:
print("✗ CUDA not available, using CPU") print("✗ CUDA not available, using CPU")
device = torch.device('cpu') device = torch.device('cpu')
@@ -36,7 +37,7 @@ print("=" * 60)
# =============================== # ===============================
# DATA LOADING WITH NaN HANDLING # DATA LOADING - HANDLES PARTIAL NaN
# =============================== # ===============================
def load_kaggle_asl_data(base_path): def load_kaggle_asl_data(base_path):
train_df = pd.read_csv(os.path.join(base_path, "train.csv")) train_df = pd.read_csv(os.path.join(base_path, "train.csv"))
@@ -46,7 +47,7 @@ def load_kaggle_asl_data(base_path):
def extract_hand_landmarks_from_parquet(path): def extract_hand_landmarks_from_parquet(path):
"""Extract hand landmarks, handling NaN values properly""" """Extract hand landmarks - ONLY uses frames with valid (non-NaN) data"""
try: try:
df = pd.read_parquet(path) df = pd.read_parquet(path)
@@ -54,56 +55,70 @@ def extract_hand_landmarks_from_parquet(path):
left = df[df["type"] == "left_hand"] left = df[df["type"] == "left_hand"]
right = df[df["type"] == "right_hand"] right = df[df["type"] == "right_hand"]
# Choose hand with more non-NaN data if len(left) == 0 and len(right) == 0:
left_valid = left[['x', 'y', 'z']].notna().all(axis=1).sum()
right_valid = right[['x', 'y', 'z']].notna().all(axis=1).sum()
if left_valid == 0 and right_valid == 0:
return None # No valid hand data
hand = left if left_valid >= right_valid else right
if len(hand) == 0:
return None return None
# Get frames with valid data # Count valid (non-NaN) rows for each hand
left_valid = 0
right_valid = 0
if len(left) > 0:
left_valid = left[['x', 'y', 'z']].notna().all(axis=1).sum()
if len(right) > 0:
right_valid = right[['x', 'y', 'z']].notna().all(axis=1).sum()
# No valid data at all
if left_valid == 0 and right_valid == 0:
return None
# Choose hand with more valid data
hand = left if left_valid >= right_valid else right
# Get unique frames
frames = sorted(hand['frame'].unique()) frames = sorted(hand['frame'].unique())
landmarks_seq = [] landmarks_seq = []
for frame in frames: for frame in frames:
lm_frame = hand[hand['frame'] == frame] lm_frame = hand[hand['frame'] == frame]
# Check if this frame has valid data # Count how many valid landmarks this frame has
valid_rows = lm_frame[['x', 'y', 'z']].notna().all(axis=1) valid_count = lm_frame[['x', 'y', 'z']].notna().all(axis=1).sum()
if valid_rows.sum() < 10: # Need at least 10 valid landmarks
# Skip frames with too few valid landmarks
if valid_count < 10:
continue continue
lm_list = [] # Extract landmarks for this frame
frame_has_data = False frame_landmarks = []
valid_landmarks_in_frame = 0
for i in range(21): for i in range(21):
lm = lm_frame[lm_frame['landmark_index'] == i] lm = lm_frame[lm_frame['landmark_index'] == i]
if len(lm) == 0: if len(lm) == 0:
lm_list.append([0.0, 0.0, 0.0]) frame_landmarks.append([0.0, 0.0, 0.0])
else: else:
x = lm['x'].iloc[0] x = float(lm['x'].iloc[0])
y = lm['y'].iloc[0] y = float(lm['y'].iloc[0])
z = lm['z'].iloc[0] z = float(lm['z'].iloc[0])
# Check for NaN # Check if valid
if pd.isna(x) or pd.isna(y) or pd.isna(z): if pd.notna(x) and pd.notna(y) and pd.notna(z):
lm_list.append([0.0, 0.0, 0.0]) frame_landmarks.append([x, y, z])
valid_landmarks_in_frame += 1
else: else:
lm_list.append([float(x), float(y), float(z)]) frame_landmarks.append([0.0, 0.0, 0.0])
frame_has_data = True
if frame_has_data: # Only add frame if it has enough valid landmarks
landmarks_seq.append(lm_list) if valid_landmarks_in_frame >= 10:
landmarks_seq.append(frame_landmarks)
if len(landmarks_seq) == 0: # Need at least 3 valid frames
if len(landmarks_seq) < 3:
return None return None
return np.array(landmarks_seq, dtype=np.float32) return np.array(landmarks_seq, dtype=np.float32)
except Exception as e: except Exception as e:
return None return None
@@ -113,43 +128,57 @@ def get_features_sequence(landmarks_seq, max_frames=100):
if landmarks_seq is None or len(landmarks_seq) == 0: if landmarks_seq is None or len(landmarks_seq) == 0:
return None, None return None, None
# Center on wrist # Center on wrist (landmark 0)
landmarks_seq = landmarks_seq - landmarks_seq[:, 0:1, :] wrist = landmarks_seq[:, 0:1, :].copy()
landmarks_seq = landmarks_seq - wrist
# Scale using wrist middle finger MCP distance # Scale normalization using wrist to middle finger MCP (landmark 9)
scale = np.linalg.norm(landmarks_seq[:, 0] - landmarks_seq[:, 9], axis=1, keepdims=True) scale = np.linalg.norm(landmarks_seq[:, 9, :] - np.zeros(3), axis=1, keepdims=True)
scale = np.maximum(scale, 1e-6) scale = np.maximum(scale, 1e-6) # Avoid division by zero
landmarks_seq = landmarks_seq / scale[:, np.newaxis, :] landmarks_seq = landmarks_seq / scale[:, np.newaxis, :]
# Replace any remaining NaN/Inf with 0 # Clean up any remaining NaN/Inf
landmarks_seq = np.nan_to_num(landmarks_seq, nan=0.0, posinf=0.0, neginf=0.0) landmarks_seq = np.nan_to_num(landmarks_seq, nan=0.0, posinf=0.0, neginf=0.0)
# Finger curl distances # Clip extreme values
tips = [4, 8, 12, 16, 20] landmarks_seq = np.clip(landmarks_seq, -10, 10)
bases = [1, 5, 9, 13, 17]
# Calculate finger curl features
tips = [4, 8, 12, 16, 20] # Thumb, index, middle, ring, pinky tips
bases = [1, 5, 9, 13, 17] # Corresponding base joints
curl_features = [] curl_features = []
for b, t in zip(bases, tips): for b, t in zip(bases, tips):
curl = np.linalg.norm(landmarks_seq[:, t] - landmarks_seq[:, b], axis=1) curl = np.linalg.norm(landmarks_seq[:, t] - landmarks_seq[:, b], axis=1)
curl_features.append(curl) curl_features.append(curl)
curl_features = np.stack(curl_features, axis=1) curl_features = np.stack(curl_features, axis=1) # (T, 5)
# Temporal deltas # Temporal deltas (motion)
deltas = np.zeros_like(landmarks_seq) deltas = np.zeros_like(landmarks_seq)
if len(landmarks_seq) > 1:
deltas[1:] = landmarks_seq[1:] - landmarks_seq[:-1] deltas[1:] = landmarks_seq[1:] - landmarks_seq[:-1]
# Flatten features # Combine all features
seq = np.concatenate([landmarks_seq, deltas, curl_features[:, :, np.newaxis]], axis=2) seq = np.concatenate([
landmarks_seq, # (T, 21, 3)
deltas, # (T, 21, 3)
curl_features[:, :, np.newaxis] # (T, 5, 1)
], axis=2)
# Flatten spatial dimensions: (T, 21*3 + 21*3 + 5) = (T, 131)
seq = seq.reshape(seq.shape[0], -1) seq = seq.reshape(seq.shape[0], -1)
# Pad or truncate # Pad or truncate to max_frames
T, F = seq.shape T, F = seq.shape
if T < max_frames: if T < max_frames:
# Pad with zeros
pad = np.zeros((max_frames - T, F), dtype=np.float32) pad = np.zeros((max_frames - T, F), dtype=np.float32)
seq = np.concatenate([seq, pad], axis=0) seq = np.concatenate([seq, pad], axis=0)
elif T > max_frames: elif T > max_frames:
# Truncate
seq = seq[:max_frames, :] seq = seq[:max_frames, :]
# Create mask # Create attention mask (True for valid positions)
valid_mask = np.zeros(max_frames, dtype=bool) valid_mask = np.zeros(max_frames, dtype=bool)
valid_mask[:min(T, max_frames)] = True valid_mask[:min(T, max_frames)] = True
@@ -157,26 +186,30 @@ def get_features_sequence(landmarks_seq, max_frames=100):
def process_row(row, base_path, max_frames=100): def process_row(row, base_path, max_frames=100):
"""Process a single row""" """Process a single row - worker function for multiprocessing"""
path = os.path.join(base_path, row["path"]) path = os.path.join(base_path, row["path"])
if not os.path.exists(path): if not os.path.exists(path):
return None, None, None return None, None, None
try: try:
# Extract landmarks
lm = extract_hand_landmarks_from_parquet(path) lm = extract_hand_landmarks_from_parquet(path)
if lm is None: if lm is None:
return None, None, None return None, None, None
# Get features
feat, mask = get_features_sequence(lm, max_frames) feat, mask = get_features_sequence(lm, max_frames)
if feat is None: if feat is None:
return None, None, None return None, None, None
# Final NaN check # Final safety check
if np.isnan(feat).any() or np.isinf(feat).any(): if np.isnan(feat).any() or np.isinf(feat).any():
return None, None, None return None, None, None
return feat, mask, row["sign"] return feat, mask, row["sign"]
except:
except Exception as e:
return None, None, None return None, None, None
@@ -198,12 +231,15 @@ class PositionalEncoding(nn.Module):
class TransformerASL(nn.Module): class TransformerASL(nn.Module):
def __init__(self, input_dim=68, num_classes=250, d_model=256, nhead=8, num_layers=4): def __init__(self, input_dim, num_classes, d_model=256, nhead=8, num_layers=4):
super().__init__() super().__init__()
# Input projection
self.proj = nn.Linear(input_dim, d_model) self.proj = nn.Linear(input_dim, d_model)
self.norm_in = nn.LayerNorm(d_model) self.norm_in = nn.LayerNorm(d_model)
self.pos = PositionalEncoding(d_model) self.pos = PositionalEncoding(d_model, max_len=128)
# Transformer encoder
enc_layer = nn.TransformerEncoderLayer( enc_layer = nn.TransformerEncoderLayer(
d_model=d_model, d_model=d_model,
nhead=nhead, nhead=nhead,
@@ -215,6 +251,7 @@ class TransformerASL(nn.Module):
) )
self.encoder = nn.TransformerEncoder(enc_layer, num_layers=num_layers) self.encoder = nn.TransformerEncoder(enc_layer, num_layers=num_layers)
# Classification head
self.head = nn.Sequential( self.head = nn.Sequential(
nn.LayerNorm(d_model), nn.LayerNorm(d_model),
nn.Dropout(0.25), nn.Dropout(0.25),
@@ -222,11 +259,17 @@ class TransformerASL(nn.Module):
) )
def forward(self, x, key_padding_mask=None): def forward(self, x, key_padding_mask=None):
# x: (batch, seq_len, input_dim)
# key_padding_mask: (batch, seq_len) - True for padding positions
x = self.proj(x) x = self.proj(x)
x = self.norm_in(x) x = self.norm_in(x)
x = self.pos(x) x = self.pos(x)
x = self.encoder(x, src_key_padding_mask=key_padding_mask) x = self.encoder(x, src_key_padding_mask=key_padding_mask)
# Global average pooling over valid positions
x = x.mean(dim=1) x = x.mean(dim=1)
return self.head(x) return self.head(x)
@@ -236,7 +279,7 @@ class TransformerASL(nn.Module):
def main(): def main():
base_path = "asl_kaggle" base_path = "asl_kaggle"
max_frames = 100 max_frames = 100
MIN_SAMPLES_PER_CLASS = 6 MIN_SAMPLES_PER_CLASS = 5
print("\nLoading metadata...") print("\nLoading metadata...")
train_df, sign_to_idx = load_kaggle_asl_data(base_path) train_df, sign_to_idx = load_kaggle_asl_data(base_path)
@@ -244,7 +287,10 @@ def main():
rows = [row for _, row in train_df.iterrows()] rows = [row for _, row in train_df.iterrows()]
print("\nProcessing sequences with NaN handling...") print("\nProcessing sequences (this will take a few minutes)...")
print("Expected: ~36,000 valid sequences based on diagnostic")
# Process with multiprocessing
with Pool(cpu_count()) as pool: with Pool(cpu_count()) as pool:
results = list(tqdm( results = list(tqdm(
pool.imap( pool.imap(
@@ -259,27 +305,30 @@ def main():
# Filter valid results # Filter valid results
X_list, masks_list, y_list = [], [], [] X_list, masks_list, y_list = [], [], []
for feat, mask, sign in results: for feat, mask, sign in results:
if feat is not None and feat.shape[0] == max_frames: if feat is not None and mask is not None and sign is not None:
if feat.shape[0] == max_frames:
X_list.append(feat) X_list.append(feat)
masks_list.append(mask) masks_list.append(mask)
y_list.append(sign) y_list.append(sign)
print(f"\nValid sequences: {len(X_list)} out of {len(train_df)}") print(f"\nSuccessfully extracted: {len(X_list)} valid sequences")
print(f" Success rate: {len(X_list) / len(train_df) * 100:.1f}%")
if not X_list: if len(X_list) < 100:
print("No valid sequences found!") print("Too few valid sequences found!")
print("\nPossible issues:") print(" This shouldn't happen - please share this output for debugging")
print(" 1. Most files contain only NaN hand landmarks")
print(" 2. Hand detection failed in most videos")
print(" 3. Dataset might be corrupted")
return return
# Stack into arrays
X = np.stack(X_list) X = np.stack(X_list)
masks = np.stack(masks_list) masks = np.stack(masks_list)
print(f"Data shape: {X.shape}")
print(f"\nData shape: {X.shape}")
print(f"Feature dimension: {X.shape[2]}")
# Global normalization # Global normalization
X = np.clip(X, -5.0, 5.0) print("Normalizing features...")
X = np.clip(X, -10.0, 10.0)
mean = X.mean(axis=(0, 1), keepdims=True) mean = X.mean(axis=(0, 1), keepdims=True)
std = X.std(axis=(0, 1), keepdims=True) + 1e-8 std = X.std(axis=(0, 1), keepdims=True) + 1e-8
X = (X - mean) / std X = (X - mean) / std
@@ -292,22 +341,29 @@ def main():
counts = Counter(y) counts = Counter(y)
valid_classes = [cls for cls, cnt in counts.items() if cnt >= MIN_SAMPLES_PER_CLASS] valid_classes = [cls for cls, cnt in counts.items() if cnt >= MIN_SAMPLES_PER_CLASS]
mask_valid = np.isin(y, valid_classes) mask_valid = np.isin(y, valid_classes)
X = X[mask_valid] X = X[mask_valid]
masks = masks[mask_valid] masks = masks[mask_valid]
y = y[mask_valid] y = y[mask_valid]
# Re-encode # Re-encode after filtering
le = LabelEncoder() le = LabelEncoder()
y = le.fit_transform(y) y = le.fit_transform(y)
print(f"Final dataset: {len(X)} samples | {len(le.classes_)} classes") print(f"\nFinal dataset after filtering:")
print(f" Samples: {len(X):,}")
print(f" Classes: {len(le.classes_)}")
print(f" Sign examples: {list(le.classes_[:10])}")
# Train-test split # Train-test split
X_train, X_test, masks_train, masks_test, y_train, y_test = train_test_split( X_train, X_test, masks_train, masks_test, y_train, y_test = train_test_split(
X, masks, y, test_size=0.15, stratify=y, random_state=42 X, masks, y, test_size=0.15, stratify=y, random_state=42
) )
# Dataset print(f"\nTrain set: {len(X_train):,} samples")
print(f"Test set: {len(X_test):,} samples")
# Dataset wrapper
class ASLSequenceDataset(Dataset): class ASLSequenceDataset(Dataset):
def __init__(self, X, masks, y): def __init__(self, X, masks, y):
self.X = torch.from_numpy(X).float() self.X = torch.from_numpy(X).float()
@@ -320,18 +376,27 @@ def main():
def __getitem__(self, idx): def __getitem__(self, idx):
return self.X[idx], self.masks[idx], self.y[idx] return self.X[idx], self.masks[idx], self.y[idx]
# DataLoaders
batch_size = 128 if device.type == 'cuda' else 64 batch_size = 128 if device.type == 'cuda' else 64
train_loader = DataLoader( train_loader = DataLoader(
ASLSequenceDataset(X_train, masks_train, y_train), ASLSequenceDataset(X_train, masks_train, y_train),
batch_size=batch_size, shuffle=True, num_workers=4, pin_memory=True batch_size=batch_size,
) shuffle=True,
test_loader = DataLoader( num_workers=4,
ASLSequenceDataset(X_test, masks_test, y_test), pin_memory=True if device.type == 'cuda' else False
batch_size=batch_size * 2, shuffle=False, num_workers=4, pin_memory=True
) )
# Model test_loader = DataLoader(
ASLSequenceDataset(X_test, masks_test, y_test),
batch_size=batch_size * 2,
shuffle=False,
num_workers=4,
pin_memory=True if device.type == 'cuda' else False
)
# Initialize model
print("\nInitializing model...")
model = TransformerASL( model = TransformerASL(
input_dim=X.shape[2], input_dim=X.shape[2],
num_classes=len(le.classes_), num_classes=len(le.classes_),
@@ -341,35 +406,41 @@ def main():
).to(device) ).to(device)
total_params = sum(p.numel() for p in model.parameters()) total_params = sum(p.numel() for p in model.parameters())
print(f"\nModel parameters: {total_params:,}") print(f"Model parameters: {total_params:,}")
# Training setup
criterion = nn.CrossEntropyLoss(label_smoothing=0.05) criterion = nn.CrossEntropyLoss(label_smoothing=0.05)
optimizer = optim.AdamW(model.parameters(), lr=5e-4, weight_decay=1e-4) optimizer = optim.AdamW(model.parameters(), lr=5e-4, weight_decay=1e-4)
scheduler = optim.lr_scheduler.CosineAnnealingWarmRestarts(optimizer, T_0=10) scheduler = optim.lr_scheduler.CosineAnnealingWarmRestarts(optimizer, T_0=10, T_mult=2)
# Training # Training loop
best_acc = 0.0 best_acc = 0.0
patience = 15 patience = 15
wait = 0 wait = 0
epochs = 70 epochs = 60
print("\nStarting training...") print("\n" + "=" * 60)
print("STARTING TRAINING")
print("=" * 60) print("=" * 60)
for epoch in range(epochs): for epoch in range(epochs):
# Train
model.train() model.train()
total_loss = 0 total_loss = 0
correct = total = 0 correct = total = 0
for x, mask, yb in tqdm(train_loader, desc=f"Epoch {epoch + 1}/{epochs}"): for x, mask, yb in tqdm(train_loader, desc=f"Epoch {epoch + 1}/{epochs}", leave=False):
x, mask, yb = x.to(device), mask.to(device), yb.to(device) x, mask, yb = x.to(device), mask.to(device), yb.to(device)
# Invert mask: True for padding positions
key_mask = ~mask key_mask = ~mask
optimizer.zero_grad(set_to_none=True) optimizer.zero_grad(set_to_none=True)
logits = model(x, key_padding_mask=key_mask) logits = model(x, key_padding_mask=key_mask)
loss = criterion(logits, yb) loss = criterion(logits, yb)
loss.backward() loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=0.8)
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step() optimizer.step()
total_loss += loss.item() total_loss += loss.item()
@@ -378,7 +449,7 @@ def main():
train_acc = correct / total * 100 train_acc = correct / total * 100
# Eval # Evaluate
model.eval() model.eval()
correct = total = 0 correct = total = 0
with torch.no_grad(): with torch.no_grad():
@@ -391,11 +462,13 @@ def main():
test_acc = correct / total * 100 test_acc = correct / total * 100
# Print progress
print(f"[{epoch + 1:2d}/{epochs}] Loss: {total_loss / len(train_loader):.4f} | " print(f"[{epoch + 1:2d}/{epochs}] Loss: {total_loss / len(train_loader):.4f} | "
f"Train: {train_acc:.2f}% | Test: {test_acc:.2f}%") f"Train: {train_acc:.2f}% | Test: {test_acc:.2f}%", end="")
scheduler.step() scheduler.step()
# Save best model
if test_acc > best_acc: if test_acc > best_acc:
best_acc = test_acc best_acc = test_acc
wait = 0 wait = 0
@@ -406,19 +479,25 @@ def main():
'acc': best_acc, 'acc': best_acc,
'epoch': epoch, 'epoch': epoch,
'input_dim': X.shape[2], 'input_dim': X.shape[2],
'num_classes': len(le.classes_) 'num_classes': len(le.classes_),
'd_model': 256,
'nhead': 8,
'num_layers': 4
}, "best_asl_transformer.pth") }, "best_asl_transformer.pth")
print(f" → New best: {best_acc:.2f}%") print(f" → New best: {best_acc:.2f}%")
else: else:
wait += 1 wait += 1
print()
if wait >= patience: if wait >= patience:
print("Early stopping") print(f"\nEarly stopping triggered at epoch {epoch + 1}")
break break
print("=" * 60) print("\n" + "=" * 60)
print(f"\n✓ Training complete!") print(f"✓ Training complete!")
print(f"✓ Best test accuracy: {best_acc:.2f}%") print(f"✓ Best test accuracy: {best_acc:.2f}%")
print(f"✓ Model saved: best_asl_transformer.pth") print(f"✓ Model saved: best_asl_transformer.pth")
print("=" * 60)
if __name__ == "__main__": if __name__ == "__main__":

626
valid_files.json Normal file
View File

@@ -0,0 +1,626 @@
[
{
"path": "train_landmark_files/16069/100015657.parquet",
"sign": "cloud",
"hand": "left",
"total_frames": 105,
"valid_frames": 28,
"nan_ratio": 0.7333333333333333
},
{
"path": "train_landmark_files/32319/1000278229.parquet",
"sign": "lips",
"hand": "left",
"total_frames": 57,
"valid_frames": 36,
"nan_ratio": 0.3684210526315789
},
{
"path": "train_landmark_files/36257/1000536928.parquet",
"sign": "apple",
"hand": "left",
"total_frames": 13,
"valid_frames": 10,
"nan_ratio": 0.23076923076923078
},
{
"path": "train_landmark_files/22343/1000638205.parquet",
"sign": "puzzle",
"hand": "left",
"total_frames": 19,
"valid_frames": 11,
"nan_ratio": 0.42105263157894735
},
{
"path": "train_landmark_files/27610/1000697904.parquet",
"sign": "there",
"hand": "left",
"total_frames": 43,
"valid_frames": 15,
"nan_ratio": 0.6511627906976745
},
{
"path": "train_landmark_files/61333/1000909322.parquet",
"sign": "shirt",
"hand": "left",
"total_frames": 22,
"valid_frames": 22,
"nan_ratio": 0.0
},
{
"path": "train_landmark_files/27610/1000956928.parquet",
"sign": "owl",
"hand": "left",
"total_frames": 100,
"valid_frames": 98,
"nan_ratio": 0.02
},
{
"path": "train_landmark_files/22343/1001223069.parquet",
"sign": "not",
"hand": "left",
"total_frames": 18,
"valid_frames": 6,
"nan_ratio": 0.6666666666666666
},
{
"path": "train_landmark_files/32319/1001258102.parquet",
"sign": "zipper",
"hand": "left",
"total_frames": 13,
"valid_frames": 8,
"nan_ratio": 0.38461538461538464
},
{
"path": "train_landmark_files/55372/1001471195.parquet",
"sign": "cheek",
"hand": "left",
"total_frames": 17,
"valid_frames": 17,
"nan_ratio": 0.0
},
{
"path": "train_landmark_files/36257/1001560021.parquet",
"sign": "shoe",
"hand": "left",
"total_frames": 63,
"valid_frames": 63,
"nan_ratio": 0.0
},
{
"path": "train_landmark_files/34503/1001685690.parquet",
"sign": "empty",
"hand": "left",
"total_frames": 8,
"valid_frames": 8,
"nan_ratio": 0.0
},
{
"path": "train_landmark_files/61333/1001819372.parquet",
"sign": "balloon",
"hand": "left",
"total_frames": 27,
"valid_frames": 16,
"nan_ratio": 0.4074074074074074
},
{
"path": "train_landmark_files/36257/1001899025.parquet",
"sign": "same",
"hand": "left",
"total_frames": 44,
"valid_frames": 22,
"nan_ratio": 0.5
},
{
"path": "train_landmark_files/28656/1001919956.parquet",
"sign": "orange",
"hand": "left",
"total_frames": 53,
"valid_frames": 48,
"nan_ratio": 0.09433962264150944
},
{
"path": "train_landmark_files/32319/1001958254.parquet",
"sign": "go",
"hand": "left",
"total_frames": 24,
"valid_frames": 23,
"nan_ratio": 0.041666666666666664
},
{
"path": "train_landmark_files/61333/1002052130.parquet",
"sign": "TV",
"hand": "left",
"total_frames": 116,
"valid_frames": 109,
"nan_ratio": 0.0603448275862069
},
{
"path": "train_landmark_files/16069/1002113535.parquet",
"sign": "another",
"hand": "left",
"total_frames": 6,
"valid_frames": 3,
"nan_ratio": 0.5
},
{
"path": "train_landmark_files/55372/1002129762.parquet",
"sign": "giraffe",
"hand": "left",
"total_frames": 23,
"valid_frames": 14,
"nan_ratio": 0.391304347826087
},
{
"path": "train_landmark_files/29302/1002284514.parquet",
"sign": "can",
"hand": "left",
"total_frames": 41,
"valid_frames": 8,
"nan_ratio": 0.8048780487804879
},
{
"path": "train_landmark_files/55372/100230619.parquet",
"sign": "say",
"hand": "left",
"total_frames": 20,
"valid_frames": 19,
"nan_ratio": 0.05
},
{
"path": "train_landmark_files/55372/1002734054.parquet",
"sign": "that",
"hand": "left",
"total_frames": 6,
"valid_frames": 6,
"nan_ratio": 0.0
},
{
"path": "train_landmark_files/22343/1002776784.parquet",
"sign": "black",
"hand": "left",
"total_frames": 15,
"valid_frames": 9,
"nan_ratio": 0.4
},
{
"path": "train_landmark_files/37055/1003007869.parquet",
"sign": "moon",
"hand": "left",
"total_frames": 62,
"valid_frames": 4,
"nan_ratio": 0.9354838709677419
},
{
"path": "train_landmark_files/61333/1003093029.parquet",
"sign": "pizza",
"hand": "left",
"total_frames": 23,
"valid_frames": 18,
"nan_ratio": 0.21739130434782608
},
{
"path": "train_landmark_files/37055/1003109377.parquet",
"sign": "shhh",
"hand": "left",
"total_frames": 23,
"valid_frames": 20,
"nan_ratio": 0.13043478260869565
},
{
"path": "train_landmark_files/36257/1003335907.parquet",
"sign": "now",
"hand": "left",
"total_frames": 14,
"valid_frames": 8,
"nan_ratio": 0.42857142857142855
},
{
"path": "train_landmark_files/22343/1003347075.parquet",
"sign": "TV",
"hand": "left",
"total_frames": 89,
"valid_frames": 69,
"nan_ratio": 0.2247191011235955
},
{
"path": "train_landmark_files/55372/1003483016.parquet",
"sign": "jump",
"hand": "left",
"total_frames": 6,
"valid_frames": 4,
"nan_ratio": 0.3333333333333333
},
{
"path": "train_landmark_files/34503/1003483410.parquet",
"sign": "sleep",
"hand": "left",
"total_frames": 8,
"valid_frames": 8,
"nan_ratio": 0.0
},
{
"path": "train_landmark_files/37055/100393298.parquet",
"sign": "uncle",
"hand": "left",
"total_frames": 60,
"valid_frames": 38,
"nan_ratio": 0.36666666666666664
},
{
"path": "train_landmark_files/32319/1004200659.parquet",
"sign": "dryer",
"hand": "left",
"total_frames": 59,
"valid_frames": 33,
"nan_ratio": 0.4406779661016949
},
{
"path": "train_landmark_files/16069/10042041.parquet",
"sign": "green",
"hand": "left",
"total_frames": 105,
"valid_frames": 6,
"nan_ratio": 0.9428571428571428
},
{
"path": "train_landmark_files/16069/1004211348.parquet",
"sign": "bug",
"hand": "left",
"total_frames": 163,
"valid_frames": 97,
"nan_ratio": 0.4049079754601227
},
{
"path": "train_landmark_files/29302/1004285193.parquet",
"sign": "brother",
"hand": "left",
"total_frames": 20,
"valid_frames": 4,
"nan_ratio": 0.8
},
{
"path": "train_landmark_files/22343/1004302418.parquet",
"sign": "sad",
"hand": "left",
"total_frames": 34,
"valid_frames": 15,
"nan_ratio": 0.5588235294117647
},
{
"path": "train_landmark_files/16069/100438640.parquet",
"sign": "penny",
"hand": "left",
"total_frames": 75,
"valid_frames": 23,
"nan_ratio": 0.6933333333333334
},
{
"path": "train_landmark_files/27610/1004456612.parquet",
"sign": "mitten",
"hand": "left",
"total_frames": 43,
"valid_frames": 26,
"nan_ratio": 0.3953488372093023
},
{
"path": "train_landmark_files/55372/1004789884.parquet",
"sign": "brown",
"hand": "left",
"total_frames": 18,
"valid_frames": 11,
"nan_ratio": 0.3888888888888889
},
{
"path": "train_landmark_files/27610/100498908.parquet",
"sign": "drink",
"hand": "left",
"total_frames": 11,
"valid_frames": 11,
"nan_ratio": 0.0
},
{
"path": "train_landmark_files/16069/1005009451.parquet",
"sign": "stay",
"hand": "left",
"total_frames": 25,
"valid_frames": 14,
"nan_ratio": 0.44
},
{
"path": "train_landmark_files/55372/1005052722.parquet",
"sign": "tooth",
"hand": "left",
"total_frames": 20,
"valid_frames": 20,
"nan_ratio": 0.0
},
{
"path": "train_landmark_files/34503/1005088445.parquet",
"sign": "awake",
"hand": "left",
"total_frames": 8,
"valid_frames": 8,
"nan_ratio": 0.0
},
{
"path": "train_landmark_files/32319/100515039.parquet",
"sign": "hot",
"hand": "left",
"total_frames": 30,
"valid_frames": 17,
"nan_ratio": 0.43333333333333335
},
{
"path": "train_landmark_files/36257/1005189210.parquet",
"sign": "like",
"hand": "left",
"total_frames": 7,
"valid_frames": 7,
"nan_ratio": 0.0
},
{
"path": "train_landmark_files/16069/1005223850.parquet",
"sign": "where",
"hand": "left",
"total_frames": 15,
"valid_frames": 7,
"nan_ratio": 0.5333333333333333
},
{
"path": "train_landmark_files/36257/1005422610.parquet",
"sign": "potty",
"hand": "left",
"total_frames": 16,
"valid_frames": 16,
"nan_ratio": 0.0
},
{
"path": "train_landmark_files/61333/1005476876.parquet",
"sign": "down",
"hand": "left",
"total_frames": 9,
"valid_frames": 8,
"nan_ratio": 0.1111111111111111
},
{
"path": "train_landmark_files/16069/1005492440.parquet",
"sign": "old",
"hand": "left",
"total_frames": 76,
"valid_frames": 15,
"nan_ratio": 0.8026315789473685
},
{
"path": "train_landmark_files/37055/1005734995.parquet",
"sign": "no",
"hand": "left",
"total_frames": 6,
"valid_frames": 4,
"nan_ratio": 0.3333333333333333
},
{
"path": "train_landmark_files/61333/1005826772.parquet",
"sign": "head",
"hand": "left",
"total_frames": 26,
"valid_frames": 4,
"nan_ratio": 0.8461538461538461
},
{
"path": "train_landmark_files/22343/100585188.parquet",
"sign": "down",
"hand": "left",
"total_frames": 71,
"valid_frames": 23,
"nan_ratio": 0.676056338028169
},
{
"path": "train_landmark_files/32319/1005865446.parquet",
"sign": "orange",
"hand": "left",
"total_frames": 58,
"valid_frames": 36,
"nan_ratio": 0.3793103448275862
},
{
"path": "train_landmark_files/36257/1005866711.parquet",
"sign": "food",
"hand": "left",
"total_frames": 52,
"valid_frames": 47,
"nan_ratio": 0.09615384615384616
},
{
"path": "train_landmark_files/16069/1006058791.parquet",
"sign": "apple",
"hand": "left",
"total_frames": 14,
"valid_frames": 9,
"nan_ratio": 0.35714285714285715
},
{
"path": "train_landmark_files/37055/1006223282.parquet",
"sign": "pretty",
"hand": "left",
"total_frames": 27,
"valid_frames": 21,
"nan_ratio": 0.2222222222222222
},
{
"path": "train_landmark_files/55372/1006402771.parquet",
"sign": "nuts",
"hand": "left",
"total_frames": 18,
"valid_frames": 17,
"nan_ratio": 0.05555555555555555
},
{
"path": "train_landmark_files/16069/1006443357.parquet",
"sign": "animal",
"hand": "left",
"total_frames": 33,
"valid_frames": 23,
"nan_ratio": 0.30303030303030304
},
{
"path": "train_landmark_files/34503/1006554061.parquet",
"sign": "say",
"hand": "left",
"total_frames": 47,
"valid_frames": 6,
"nan_ratio": 0.8723404255319149
},
{
"path": "train_landmark_files/22343/1006584947.parquet",
"sign": "finish",
"hand": "left",
"total_frames": 26,
"valid_frames": 19,
"nan_ratio": 0.2692307692307692
},
{
"path": "train_landmark_files/37055/1006625592.parquet",
"sign": "beside",
"hand": "left",
"total_frames": 42,
"valid_frames": 4,
"nan_ratio": 0.9047619047619048
},
{
"path": "train_landmark_files/28656/1006698125.parquet",
"sign": "noisy",
"hand": "left",
"total_frames": 311,
"valid_frames": 273,
"nan_ratio": 0.12218649517684887
},
{
"path": "train_landmark_files/22343/1006778422.parquet",
"sign": "happy",
"hand": "left",
"total_frames": 36,
"valid_frames": 3,
"nan_ratio": 0.9166666666666666
},
{
"path": "train_landmark_files/34503/1006899334.parquet",
"sign": "stuck",
"hand": "left",
"total_frames": 225,
"valid_frames": 219,
"nan_ratio": 0.02666666666666667
},
{
"path": "train_landmark_files/27610/1006973536.parquet",
"sign": "bug",
"hand": "left",
"total_frames": 42,
"valid_frames": 41,
"nan_ratio": 0.023809523809523808
},
{
"path": "train_landmark_files/36257/1007054441.parquet",
"sign": "say",
"hand": "left",
"total_frames": 48,
"valid_frames": 48,
"nan_ratio": 0.0
},
{
"path": "train_landmark_files/16069/1007073382.parquet",
"sign": "sticky",
"hand": "left",
"total_frames": 76,
"valid_frames": 51,
"nan_ratio": 0.32894736842105265
},
{
"path": "train_landmark_files/16069/1007127288.parquet",
"sign": "owie",
"hand": "left",
"total_frames": 105,
"valid_frames": 47,
"nan_ratio": 0.5523809523809524
},
{
"path": "train_landmark_files/61333/1007285542.parquet",
"sign": "high",
"hand": "left",
"total_frames": 24,
"valid_frames": 21,
"nan_ratio": 0.125
},
{
"path": "train_landmark_files/55372/1007289801.parquet",
"sign": "fine",
"hand": "left",
"total_frames": 116,
"valid_frames": 43,
"nan_ratio": 0.6293103448275862
},
{
"path": "train_landmark_files/61333/1007294943.parquet",
"sign": "shirt",
"hand": "left",
"total_frames": 13,
"valid_frames": 11,
"nan_ratio": 0.15384615384615385
},
{
"path": "train_landmark_files/32319/1007313467.parquet",
"sign": "finish",
"hand": "left",
"total_frames": 19,
"valid_frames": 16,
"nan_ratio": 0.15789473684210525
},
{
"path": "train_landmark_files/16069/1007343357.parquet",
"sign": "boat",
"hand": "left",
"total_frames": 15,
"valid_frames": 9,
"nan_ratio": 0.4
},
{
"path": "train_landmark_files/32319/1007376023.parquet",
"sign": "all",
"hand": "left",
"total_frames": 39,
"valid_frames": 13,
"nan_ratio": 0.6666666666666666
},
{
"path": "train_landmark_files/36257/1007470312.parquet",
"sign": "pencil",
"hand": "left",
"total_frames": 8,
"valid_frames": 8,
"nan_ratio": 0.0
},
{
"path": "train_landmark_files/16069/1007589714.parquet",
"sign": "sleepy",
"hand": "left",
"total_frames": 12,
"valid_frames": 4,
"nan_ratio": 0.6666666666666666
},
{
"path": "train_landmark_files/32319/1007595679.parquet",
"sign": "moon",
"hand": "left",
"total_frames": 21,
"valid_frames": 16,
"nan_ratio": 0.23809523809523808
},
{
"path": "train_landmark_files/27610/1007714989.parquet",
"sign": "puzzle",
"hand": "left",
"total_frames": 145,
"valid_frames": 59,
"nan_ratio": 0.593103448275862
}
]