feat: replace the handshape table with a model trained on real fingerspelling - #78
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
La tabla de handshapes escribía el 17% de las letras y deletreaba 0 de 456 palabras. La sustituye una GRU causal entrenada con CTC sobre LSE-FS-UVigo.
El resultado, sobre el mismo test y medido una sola vez
Cada motor va con la regla de su propia versión de la app: la tabla parpadeaba y pedía tres frames de acuerdo, la cabeza CTC pica y pide uno. Medir ambos con una sola regla favorecería a aquel para el que se eligió.
4,4 veces más letras escritas, y acierta nueve de cada diez en vez de menos de cinco. Gana en las 14 letras que la tabla intentaba, sin una sola excepción — cinco de ellas la tabla las tenía a 0%. Por eso no hay motor híbrido: no hay nada que conservar.
Validation: CER 0,255, sd 0,005 sobre semillas 7/13/29/41. Modelo de 0,71 MB, el 27% de lo que pesa el de vocabulario.
Por qué CTC, y por qué el blank es lo importante
LSE-FS etiqueta la palabra deletreada y nunca dice cuándo ocurre cada letra, así que CTC es lo que hace usable el corpus. Pero su clase blank es además un «aquí no hay letra» entrenado, que una tabla de similitud no puede expresar con ningún umbral. Son los negativos que
health_dataset.pyno pudo construir para el vocabulario, y aquí salen gratis del método.Causal no es preferencia: la app clasifica frame a frame en vivo, así que la GRU bidireccional que pediría CTC es inviable.
El único cambio en la app, y por qué era obligatorio
Los posteriores de CTC son picudos por construcción — la pérdida es invariante a cuánto dura una letra. Medido: las rachas no-blank son 26% de un frame y solo el 23% llega a tres. Con la regla vieja este modelo escribe 550 letras de 3.758 y ni una palabra entera; con una, escribe 2.981 y 53 palabras.
Así que el motor de alfabeto usa
CandidateStabilizer(1, 0.5)y se suelta el latch cuando el motor se abstiene. Con esa suelta, un frame de acuerdo más el latch de no-repetir es el colapso greedy de CTC: la regla ya estaba, con la constante equivocada.Fidelidad de puerto
gruStepsale del cuerpo degruPass, no es un GRU nuevo — dos copias de esas seis líneas coincidirían el día que se escriben y divergirían después. Un test comprueba que N pasos encadenados igualan ungruPass.Y la fixture de paridad reconstruye el modelo desde el blob exportado, no desde el checkpoint, así que prueba también el orden de tensores del
.bin. Seis frames × 28 clases a 5 decimales.Aumentación: qué compró y qué descarté
El cuello son los datos, no la capacidad: 18.887 letras para 27 clases, y el CER toca suelo y empeora. Una semilla cada una: ninguna 0,349 · temporal 0,309 · rotación+ruido+caída 0,272 · todas 0,262.
Dos que no están, y el motivo importa. El espejado parecía gratis y es dañino:
normalize_handya pliega las izquierdas a espacio derecho, así que negar la x fabrica formas que la app no produce. El jitter de escala es inútil por lo mismo.Aviso de método: ordenar por longitud para matar el relleno aceleró 2,4x y costó 0,036 de CER, porque la longitud correlaciona con la de la palabra y los batches dejan de ser diversos. Barajar dentro de ventanas de ocho batches devuelve la precisión.
Licencias
Los pesos del alfabeto son CC BY 4.0, sin la cláusula NC del vocabulario, porque LSE-FS no la tiene. Estar en la misma carpeta no contagia y
LICENSE.mdahora lo dice explícito: un fork comercial puede llevarselse-alphabet.*y nolse-vocabulary.*.Honestidad de cara al usuario
La UI listaba 12 letras «que no distingue». Ahora las intenta todas, pero seis salen bien menos de una de cada tres veces (J 5%, W 20%, Y 22%, Ñ 25%, Q 27%, Z 29%), y son exactamente las que el corpus apenas contiene. Se avisa de esas seis, con la nota de que es una medición y hay que re-derivarla al reentrenar.
HandshapeAlphabetClassifierse queda en el repo aunque la app ya no lo use: el banco lo necesita para la línea base.Lint, typecheck, 194 tests y build en verde.