Códigos Convolucionales

Anuncio
Teoría de la Información y Teoría de Códigos
Códigos Convolucionales
Carlos Sánchez Sánchez
3º de Grado en Ingeniería Informática
Índice
1. Introducción
5
2. Aspectos Generales
6
3. Codificación
8
4. Decodificación: Algoritmo de Viterbi.
14
5. Distancia Libre
19
6. Capacidad Correctora.
21
7. Mejoras de los Códigos Convolucionales
26
8. Aplicaciones
28
Anexo A. Programa de Simulación Codice.
29
Bibliografía
32
32
3
1.
Introducción
En este trabajo se hace una introducción a los códigos convolucionales como un tipo de
códigos correctores de errores diferente a los códigos de bloques. La principal diferencia entre
ellos es la introducción del concepto de memoria, esto es, la codificación en un momento dado
no dependerá solo de la palabra a codificar, también de las codificadas anteriormente. Estos
códigos presentan una gran ventaja frente a los de bloques en canales con mucho ruido (alta
probabilidad de error). Las comunicaciones inalámbricas o comunicaciones con satélites destacan entre sus usos.
Los códigos convolucionales son un campo de trabajo abierto dentro de la teoría de códigos y
actualmente se pueden encontrar desde distintos enfoques y definiciones. Aquí se tratará de dar
una visión general tratando de destacar los aspectos que suponen una diferencia respecto a los
códigos de bloques. En todos los casos posibles se han incluido ejemplos de su funcionamiento.
Para los conceptos que por su complejidad excedían el objetivo del trabajo se han añadido
algunas referencias.
En primer lugar se describirán las características y parámetros que definen estos códigos. En
la sección 3 se explica la codificación, tanto desde un punto de vista matemático como desde el
necesario para su implementación en hardware.
Los tres apartados siguientes se dedican a la decodificación. En el apartado 4 se muestra el
proceso de decodificación, para el que se describe el agoritmo de Viterbi. En el 5 se introduce el
concepto de distancia, que determinará la capacidad correctora del código y en el 6 se mostrarán algunas cotas y ejemplos de esta capacidad. También se realizará una comparación teórica
y práctica de estos códigos con códigos de bloques desde el punto de vista de los errores que
pueden corregir.
En la sección 7 se mostrará un repaso breve de algunas técnicas que permiten mejorar la codificación mediante códigos convolucionales. También, de forma breve, algunos usos de códigos
convolucionales en combinación con otros códigos que abren nuevas vías en la teoría de códigos.
Finalmente, en la última sección se dará un repaso por algunas aplicaciones y usos prácticos
de los códigos convolucionales haciendo especial hincapié en la importancia que han tenido para
la historia de la exploración espacial
Junto al trabajo se ha realizado un pequeño simulador de códigos que permite realizar la codificación, transmisión y posterior corrección y decodificación. Los resultados de este programa
se utilizarán en algunas de las secciones del trabajo y una descripción de su funcionamiento
puede ser consultada en el Anexo A.
5
2.
Aspectos Generales
En los códigos de bloques el mensaje se dividía en palabras y la codificación era la transformación de cada palabra de información u ∈ Fq k en una palabra del código x ∈ Fq n mediante
el producto por la matriz generadora G de forma que x = u · G. En este caso la codificación
es constante en el tiempo. Sin embargo, en los códigos convolucionales, la codificación de una
palabra variará en función de las palabras transmitidas anteriormente. El número de palabras
anteriores de las que depende la codificación se denomina memoria m del código.
La introducción a los códigos convolucionales fue obra de P. Elias 1955 como alternativa a
los códigos de bloques existentes. Posteriormente, en 1970 Shun Lin trato de forma analítica la
codificación convolucional, lo que impulsó el uso de estos códigos, un desarrollo desde el punto
de vista algebraico fue realizado por G.D. Forney Jr. en el mismo año y posteriormente fue
reformulado por R. McEliece en 1998. [6]
Otros nombres dados a estos códigos son los de códigos recurrentes o códigos de árbol, debido
a que es posible realizar una representación gráfica estructurada en forma de árbol.
En estos códigos, entonces, a partir de la palabra que se codifica y las m anteriores se generarán símbolos de redundancia. Sin embargo, a diferencia de los códigos de bloques, no se
envían estos símbolos como un añadido a la palabra a codificar. Es habitual que sean estos los
únicos símbolos enviados.
Teniendo esto en cuenta, en el caso de un código convolucional C para un alfabeto A se
tienen tres parámetros (n, k, L) , dos equivalentes a los vistos para los códigos de bloques y
la restricción de distancia:
k: número de símbolos de la palabra de datos que se codifica en cada paso.
n: número de símbolos de la palabra codificada a partir de k bits.
L: restricción de distancia. Siendo m la memoría del código, L = m + 1 es el número
de fases en las que una palabra ejerce influencia sobre la salida. Es decir, influye sobre
sí mismo y m más. Es también habitual que la restricción de distancia se represente con
la letra K, pero aquí se usará la notación L para distinguirla del número de bits de la
palabra a codificar. En ocasiones en lugar de darse en cantidad de símbolos se hace en bits.
Partiendo de estos parámetros es posible entender los códigos de bloques como códigos convolucionales con m = 0, pues este es el caso en el que no se tiene en cuenta ninguna de las
codificaciones anteriores.
El ratio o tasa del código, definida de igual forma que para los códigos de bloques es
r=
BIT S que se codif ican
k
=
BIT S enviados
n
Sin embargo, generalmente los símbolos se codifican uno a uno, por tanto k = 1 y r = n1 ,
sin agrupación en bloques. Esto permite transmitir grandes corrientes de bits cuyas salidas se
combinarán de forma preestablecida en la codificación. Será en estos códigos, por ser los más
sencillos y más utilizados, en los que nos centraremos principalmente en este trabajo.
6
En este caso tenemos que para una palabra de un símbolo ui enviada en el momento i-ésimo
se obtiene una serie de bits x1 , x2 , ..., xn . Y, teniendo en cuenta la memoria, la codificación de
esta palabra dependerá de los símbolos ui , ui−1 , ..., ui−m .
La palabra codificada será siempre una combinación lineal de ui , ui−1 , ..., ui−m , y la entrada
nula (0, ..., 0) siempre da la salida nula. Por tanto, pese a no ser códigos de bloques, sí que se
pueden estudiar como códigos lineales.
Los códigos convolucionales pueden tomar sus elementos de cualquier cuerpo finito, sin embargo, el más común es F2 y será el utilizado en los ejemplos.
7
3.
Codificación
La realización física de un codificador puede realizarse mediante circuitos secuenciales lineales. Se pueden utilizar L registros cuyas salidas se suman de forma que se obtiene una palabra
del código para cada entrada. Con la entrada de una nueva palabra a codificar las anteriores se
desplazan al siguiente registro y se obtiene una nueva palabra codificada.
Tomamos como ejemplo el código k = 1 en el que se desea que al codificar el bit i-ésimo se
obtengan dos bits. El primero será la suma del bit a codificar y los dos anteriores y el otro la
suma del actual y el que se codificó en la penúltima codificación. El codificador sería el de la
siguiente imagen:
El número de registros utilizados es 3 y este es el número de entradas que afectan a cada
salida, por tanto la restricción de distancia L es 3 y la memoria m es 2. Se hace referencia a la
palabra actual y, como mucho, a las dos anteriores.
Una visión de caja negra de este codificador implicaría una entrada de la palabra ui del
código y la salida de dos bits que forman la palabra codificada.
Sin embargo, es posible expresar cada una de las salidas como una función, denominada
función generadora, de ui , ..., ui−m . En el caso anterior se tendrían:
x1 = 1 · ui + 0 · ui−1 + 1 · ui−2
x2 = 1 · ui + 1 · ui−1 + 1 · ui−2
De este modo, es posible establecer el codificador G del código con k = 1 como una aplicación
lineal entre los subespacios FL y Fn de la siguiente forma:
G : FL2 −→ Fn2
(ui , ui−1 , ..., ui−m ) 7−→ (x1 , x2 , ..., xn )
donde i indica el momento de la codificación en el que estamos.
Para el caso visto, en concreto:
8
G : F32 → F22
(ui ui−1 , ui−2 ) 7−→ (x1 , x2 )
Las palabras del [n, 1, L] − código convolucional son, por tanto, el subespacio generado por
la imagen de esta aplicación
C ≡ ImgG
Y el resultado de la codificación será el vector formado por los elementos
xi =
m
P
u(m−k) · (gij )
k=0
siendo gi las ecuaciones para cada una de las salidas.
Para verlo más claro tomamos como ejemplo el [2, 1, 3]-código convolucional visto antes con:
g1 = (1, 0, 1)
g2 = (1, 1, 1)
Si codificamos el vector (1, 0, 1, 0), la salida será:
Al entrar el primer elemento (1), se tienen en cuenta las dos anteriores entradas ficticias
como ceros, por tanto, en t = 1:
 
1
x11 = (1, 0, 0)  0  = 1 + 0 + 0 = 1
1

1
= (1, 0, 0)  1  = 1 + 0 + 0 = 1
1

x12
La salida sería: x1 = (1, 1)
Para la segunda entrada (0), se tendrá que las entradas anteriores son 1 y 0:
 
1
x21 = (0, 1, 0)  0  = 0 + 0 + 0 = 0
1

x22

1
= (0, 1, 0)  1  = 0 + 1 + 0 = 1
1
Y como consecuencia: x2 = (0, 1)
Siguiendo del mismo modo se tendrá como salida (x1 , x2 , x3 , x4 ), el vector (1, 1, 0, 1, 0, 0, 0, 1).
Es posible ver en el ejemplo que para la codificación de los primeros dos bits (1, 0) se tiene
como resultado (1, 1, 0, 1). Sin embargo, para los dos siguientes, aún siendo iguales, la salida
9
es (0, 0, 0, 1). Esto se debe, como se ha dicho, a que la salida depende también de las entradas
anteriores.
La codificación de un mensaje con N palabras ui de k bits se puede generalizar como la
aplicación tomando polinomios:
G(z) : F2 [z]k → F2 [z]n
u(z) =
PN −1
i=0
ui z i 7−→ (x1 (z), ..., xn (z)) | xj = u(z) · gj (z)
Donde u(z) es un vector de k polinomios de grado N − 1 que representan la entrada. Y los
(x1 (z), x2 (z), ..., xn (z)) son n polinomios de salida resultado del producto de la entrada por
una matriz de k × n polinomios de grado hasta m cuyas columnas son los gj .
Ejemplo de codificación con k = 1
Para ver esto mejor se realiza utilizando este método la misma codificación realizada antes
para el código con k = 1:
G(z) : F2 [z] → F2 [z]n
u(z) =
PN −1
i=0
ui z i 7−→ (x1 (z), x2 (z))|xj = u(z) · gj (z)
Los polinomios generadores de esta aplicación son los polinomios de grado m con los
coeficientes de las secuencias generadoras:
g1 (z) = 1 + z 2
g2 (z) = 1 + z + z 2 .
Sin embargo suelen representarse con notación octal. Para este caso 1012 = 58 y 1112 = 78 ,
sería por tanto un código con restricción de distancia 3, longitud 2 y polinomios (5, 7).
La expresión polinómica de la entrada (1, 0, 1, 0) codificada anteriormente es u(z) = (1 + z 2 ).
Por lo que la codificación se realiza del siguiente modo:
x1 (z) = (1 + z 2 ) · (1 + z 2 ) = 1 + z 4 −→ (1, 0, 0, 0, 1)
x2 (z) = (1 + z 2 ) · (1 + z + z 2 ) = 1 + z + z 3 + z 4 −→ (1, 1, 0, 1, 1)
E intercalando ambas salidas tenemos (1, 1, 0, 1, 0, 0, 0, 1, 1, 1). Esta salida corresponde con la
anterior, excepto porque se han añadido algunos bits al final. Esto se debe a que esta codificación corresponde a la anterior añadiendo ceros al final de modo que todos los registros estén a
cero al acabar.
Esto se denomina codificación zero-tailed y el número de ceros que se deben añadir es igual a
m. Por tanto la salida anterior se corresponde a codificar (101000), que sería (1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0),
teniendo en cuenta los ceros al final para los grados restantes. [13]
10
Esta codificación supone una reducción del tasa del código, que ahora será, para un mensaje
de m bits igual a n·(NN+m) .
Para mensajes cortos esto supondría una gran pérdida pero para secuencias muy largas de
bits el aumento es despreciable.
Otra opción, aunque más compleja, se denomina código convolucional con bits de cola (tailbiting) [13]. En estos se establece el inicio del codificador en el mismo estado que se encontraría
al final, lo cual complica la decodificación pero elimina la disminución de la tasa del código.
Ejemplo de codificación con k > 1
Esta forma de codificación es válida también para códigos con k > 1. Por lo que si estuvieramos hablando de un código con k = 2, n = 3 y L = 2 para un mensaje de 6 bits se tiene que
N = 3.
G(z) : F2 [z]2 → F2 [z]3
u(z) =
P2
i=0
ui z i 7−→ (x1 (z), ..., xn (z)) | xj = u(z) · gj (z)
La matriz para este codigo será:
1+z
z
1+z
G(z) =
z
1
1
Lo que significa que la primera salida será la suma del primer y segundo elemento de ui con el
segundo de ui−1 . La segunda salida la suma del segundo elemento de ui con el primero de ui−1 . Y
la tercera salida de nuevo el primer y segundo elemento de ui pero ahora con el primero de ui−1 .
Si se desea transmitir el mensaje u = 110110, este queda dividido en las palabras de 2 bits
u0 = 11, u1 = 01 y u2 = 10, y se tiene:
u(z) =
P1
i=0
ui z i = (1, 1) · z 0 + (0, 1) · z 1 + (1, 0) · z 2 = (1 + z 2 , 1 + z)
Por tanto:
2
x(z) = u(z) · G(z) = (1 + z , 1 + z)
1+z
z
z
1
1+z
1
= (1 + z 3 , 1 + z 3 , z 2 + z 3 )
Y las xi resultantes, tomadas como los coeficientes de los polinomios son:
x0 = (1, 0, 0, 1)
x1 = (1, 0, 0, 1)
x2 = (0, 0, 1, 1)
Por lo que el mensaje codificado, al reordenar las salidas es: x = (1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1)
11
Diagrama de Trellis
Para facilitar la comprensión de la codificación, se puede utilizar una representación gráfica
que permite ver las salidas asociadas con el paso del tiempo. Se denomina diagrama de Trellis.
En la siguiente imagen se ve el ejemplo para el anterior codificador.
En el diagrama de Trellis se ven las entradas anteriores a la actual como estados que se
representan en el eje vertical. Por tanto el número de estados sería de q m . En este caso,
22 = 4, ya que es un código binario de memoria 2.
En el eje horizontal se muestran las distintas entradas. A cada una corresponde una flecha,
con linea continua si la entrada i es un 0 y discontinua para las entradas 1. Junto a cada flecha
se indica la salida correspondiente a esa entrada.
En un primer momento el estado es 00, ya que se suponen dos entradas anteriores de 0. Si la
primera entrada ui es 0, el estado al que se
pasa es, de
 00(tanto esta
 como la

 nuevo,
 entrada
1
1
anterior son cero) y la salida es (x1 , x2 ) = (0, 0, 0)  0  , (0, 0, 0)  1  = (0, 0).
1
1
En caso de que la primera entrada sea ui = 1. El nuevo 
estado
de 
esta entrada
 será (1,
 0),1
 y 0
1
1







, (1, 0, 0) 1  =
de la entrada anterior. La salida en este caso es (x1 , x2 ) =
(1, 0, 0) 0
1
1
(1, 1). El diagrama de Trellis para este paso es el de la imagen.
Aparece marcada en rojo la entrada uno estudiada en ejemplos anteriores, y, siguiendo el
mismo mecanismo, se llega al diagrama de Trellis para la entrada (1010) con ceros de cola.
12
Y la salida coincide, de nuevo, con la vista anteriormente: (1, 1, 0, 1, 0, 0, 0, 1, 1, 1).
Esta idea de que un estado se corresponde con las entradas anteriores es posible representarla
también con un diagrama de estados.
13
4.
Decodificación: Algoritmo de Viterbi.
La decodificación consiste en encontrar la secuencia de palabras del código correspondiente
a la secuencia recibida. Dado que pueden haberse producido errores, se buscará la palabra más
probable. Esta será aquella para la que la distancia de Hamming entre su salida y la secuencia
recibida sea menor.
La decodificación es más compleja que la codificación. Es posible hacerla calculando todas las
distancias entre la secuencia recibida y las posibles secuencias transmitidas, eligiendo después
la que tiene una menor distancia de Hamming. Sin embargo este método, cuando se tienen
grandes cantidades de información, hace que el número de cálculos sea demasiado alto como
para ser práctico, pues implica una carga computacional excesiva.
Como solución a esto una de las opciones que se utilizan es el algoritmo de Viterbi. Este
algoritmo, además de ser sencillo, es capaz de encontrar la entrada con mayor probabilidad de
acierto.
Un decodificador basado en este método almacena cada secuencia de bits recibida y calcula
la secuencia de estados más probable siguiendo los caminos en el diagrama de Trellis con
menor distancia acumulada, descartando los caminos no viables y los menos probables. Una
vez encontrado este camino se obtiene la cadena de bits asociada a esas transiciones.
Tomemos como ejemplo la recepción de los bits 1010 codificados con el codificador de polinomios (5, 7) utilizado hasta ahora. El diagrama de Trellis de la codificación es el visto antes
y el resultado la secuencia 11010001. Supongamos que en la transmisión se ha producido un
error y se recibe 11000001.
El decodificador partirá del estado cero, y calculará todos los posibles caminos desde ahí, a
cada uno de los estados resultantes les asignará un valor llamado path metric. Vamos a tomar
que este valor sea la distancia de Hamming entre la salida asignada a esa transición y el par de
bits recibidos (branch metric).
Para los dos primeros bits recibidos se tiene que d(00, 11) = 2 y d (11, 11) = 1, por tanto:
En el nuevo paso se explorarán las rutas que parten de todos los los estados a los que se ha
llegado, en este caso 00 y 01. Al valor de cada estado se le sumará la distancia entre los 2 bits
siguientes y las nuevas transiciones :
14
En una nueva iteración, habrá varios caminos para llegar a un nuevo estado. Se selecciona
el que llegue con un menor valor asociado. En caso de que sean iguales, se selecciona uno cualquiera.
Aparecen en verde los valores de los caminos seleccionados y en gris los que se han descartado.
Y para finalizar, con los dos últimos bits se completa el diagrama de Trellis.
Llegado a este punto, se seleccionara el estado con el valor menor, en este caso 01. Y el
mensaje codificado corregido será el único camino superviviente que llega a ese estado, en este
caso: 11010010, que coincide con el enviado.
15
La palabra decodificada será el camino que se ha seguido para esa salida 1010, marcado en
rojo en el diagrama
En este caso no se han añadido los ceros finales. Si fuera el diagrama debería acabar en el
estado 00 y sería siempre ese el estado del que se parte, independientemente de su valor.
Un posible pseudocódigo para este algoritmo es el siguiente.
1 Vector Viterbi ( Vector codificada )
2 {
3
int numero_estados = (2 ^ ( m +1)) -1
4
Vector resultado ;
5
6
7
Matriz estados ( numero_estados , m +1)
8
estados . RellenarEstados ();
9
10
Vector aemAnterior ( numero_estados );
11
Vector aem ( nestados );
12
13
aem . fill ( -1);
14
aemAnterior . fill (0);
15
aem (0)=0;
16
17
int pasos = codificada . rows ();
18
Matriz caminos ( num_estados , pasos );
19
caminos . fill ( -1);
20
caminos (0 ,0)=1;
21
22
int acumulador ;
23
for ( int i =0; i < pasos ; i ++)
24
{
25
for ( int j =0; j < nestados )
26
{
27
if ( caminos (i , j ) != -1)
16
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58 }
{
for ( int trans = 0; trans <=1; trans ++)
{
acumulador = aemAnterior ( j )
+ sum ( abs ( palabra . row ( i ) - salida (j , k ));
if ( aem ( destino (j , k ) ) > acum || aem ( j )== -1 )
{
aem ( destino (j , k ) ) = acum ;
caminos ( i +1 , destino (j , k )) = j ;
}
}
}
}
aemAnterior = aem ;
}
// 0 si se usa zero - tailing
int anterior = aem . minIndex ();
// el primer bit del estado es la última entrada codifiada .
resultado ( pasos -1) = estados ( efin ,0 );
for ( int i = pasos ; i >1; i - -)
{
resultado (i -2 ,0) = estados ( caminos (i , efin ) , 0 );
efin = caminos (i , efin );
}
return resultado ;
La función devolverá un vector con los bits codificados, para ello hace uso de una matriz de
caminos que se va rellenando con las rutas de peso mínimo. Los vectores aem y aemAnterior se
usan para almacenar entre pasos cuales son los valores de cada una de estas rutas.
Al completar la matriz de caminos, se iterará del final al principió rellenando el vector de
resultado con los caminos elegidos, que corresponden al primer bit del estado, dado que este es
el bit que entró en la ocasión anterior.
Este código necesita almacenar una gran cantidad de información, la relativa al mensaje
completo. Para mensajes muy largos, u ocasiones en las que se envían datos de forma continua
esto no se puede realizar en la práctica.
Una solución es establecer una profundidad de decodificación. En lugar de decodificar el mensaje completo, este se divide en secuencias que se decodifican de forma independiente. Gracias
a algunas investigaciones se sabe que una profundidad de 5 · L es suficiente para no perder
capacidad de corrección mientras que se reduce el tiempo de respuesta y el espacio necesario
17
para la decodificación.
La complejidad del algoritmo de Viterbi depende linealmente de la longitud del mensaje
y exponencialmente de la restricción de distancia, dado que el número de estados es 2L − 1 y se
itera en un bucle por cada estado. Como consecuencia de esto para códigos con una memoria
muy grande el rendimiento decrece de forma muy rápida y para valores grandes de L el algoritmo de Viterbi no es una opción. Normalmente se restringe a códigos de, como mucho, L = 9.
Existen otros algoritmos de decodificación, entre los que se encuentran los algoritmos de decodificación umbral (threshold decoding), que sólo es posible aplicar a algunas clases de códigos
y no llegan a una decodificación óptima. También existe otro tipo de algoritmos denominados
de decodificación secuencial con mejores resultados, y, pese a no ser tampoco óptimos, son utilizados en códigos con una restricción de memoria para la que no es posible utilizar el algoritmo
de Viterbi.
Todos estos algoritmos, incluido el de Viterbi, son de decodificación dura (hard decoding).
Esto significa que el decodificador solamente recibe datos binarios del canal. Los decodificadores de decisión blanda reciben más de un bit en lugar de sólo uno, con lo que se tiene una
estimación de como de fiable es la información recibida. Normalmente son suficientes tres bits
de decisión blanda. 000 sería el “cero fuerte”, 111 el “uno fuerte” y en medio se tendrían otros
valores.
Para los algoritmos de decisión blanda, en lugar de utilizar una distancia de Hamming se
utiliza otra llamada distancia Euclidea.
18
5.
Distancia Libre
En los códigos convolucionales se ha visto que se puede definir la distancia de Hamming igual
que en los códigos de bloques, siendo el número de símbolos en los que difieren dos secuencias
de bits codificadas.
La distancia libre df ree de un código convolucional se define como la mínima distancia de
Hamming entre dos secuencias de bits codificadas y de esta depende el número de errores que es
capaz de corregir el código. Del mismo modo que en los códigos de bloques, esto es equivalente
a comparar el resto de salidas con la entrada nula. Teniendo esto en cuenta, la distancia libre
coincide con el número de unos de la secuencia no nula que, empezando en el estado cero, vuelve
a él con la salida con menor número de unos entre todas las posibles.
La importancia de la distancia libre se debe a que de este parámetro dependerá la capacidad
correctora del código.
En el caso del código con polinomios (5, 7) que hemos visto, este recorrido es el correspondiente a la entrada 100 con salida 110111, y por tanto df ree = 5.
Este concepto se puede definir de un modo más formal de la siguiente forma, sea C un código
convolucional y wt(x) el peso de una secuencia (distancia de Hamming a la palabra nula) .
df ree (C) = mı́n {wt(x)| x ∈ C, x 6= 0}
La distancia de un [n, k, L] − código convolucional cumple la cota de Singleton generalizada:
df ree (C) ≤ (n − k)
δ
k
+1 +δ+1
donde δ es lo que se llama grado externo del código convolucional. [14] El grado externo
de un código convolucional es la suma de los grados máximos de las filas en la aplicación que
relaciona una palabra de la entrada con una de la salida para cada momento de la codificación:
G(z) : F2 [z]k → F2 [z]n
En los códigos con k = 1, el grado externo coincide con la memoria del código, ya que la
matriz de la aplicación G tiene dimensiones k × n.
Así, para un código C con k = 1, n = 2 y L = 3, δ = L − 1 = 2, y:
19
df ree (C) ≤ (2 − 1)
2
1
+1 +2+1=5
Y dado que hemos visto antes que df ree (C) = 5, podemos concluir que el código tiene la
distancia libre máxima para un código con esos parámetros. Este tipo de códigos se llaman
MDS (Maximum-distance separable).
A continuación se incluye una imagen con códigos de distancia máxima para n = 2 y distintos
valores de L.[1]
L
g1
g2
df ree
3
5
7
5
4
15
17
6
5
23
35
7
6
53
75
8
7
133
171
10
8
247
371
10
9
561
753
12
10 1167 1545
12
11 2335 3661
14
12 4335 5723
15
13 10533 17661 16
14 21675 27123 16
Se puede ver que una mayor memoria aumenta la distancia libre, pero eso tiene un
coste computacional mayor y no es posible aumentarla sin límite.
Un mayor n hace la distancia libre mayor, aunque la tasa de información será menor y
habrá que enviar un número mayor de bits.
Para el cálculo de la distancia libre en códigos con k = 2 es posible utilizar el algoritmo de
Viterbi. El algoritmo sería similar al usado en el caso de zero-tailing, que se utilizará para una
entrada de ceros con el añadido de que el resultado no sea cero.[5]
Para conseguir esto basta con obligar a que la primera transición sea un uno. Dado que se
comienza en el estado cero, si esta fuera cero seguiría en el mismo estado y la salida sería de n
ceros. Para que la salida del camino completo no sea cero en algún momento tiene que haber
una primera transición correspondiente a la entrada de un uno. Como todas las anteriores no
aportarían nada al peso de la palabra se puede tomar esta como la primera.
20
6.
Capacidad Correctora.
Determinar la capacidad correctora de un decodificador convolucional es complejo [8], por
lo que en este apartado solo se verán algunos ejemplos y se darán algunas cotas que permitan
aproximar la probabilidad de recibir un mensaje correctamente.
Se podrán detectar errores en un mensaje recibido si el digrama sigue un camino prohibido.
Es decir, si en el resultado del algoritmo de Viterbi no tenemos un camino cuya distancia al
recibido es cero (valor de la métrica de camino o pathmetric).
El error se podrá corregir si el camino ofrecido por el algoritmo de Viterbi coincide con el
original. Si el número de errores es demasiado grande no será posible encontrar este camino y
se llegará a un camino erroneo, con lo que no se podrán corregir correctamente los errores.
Para estimar la probabilidad de errores es posible analizar un mensaje recibido en cadenas de
nL bits, donde L es la restricción de distancia y n la longitud de la palabra del código. De forma
similar a la que se vio para los códigos de bloques, en el caso de los convolucionales se htiene que
i
df ree −1
será posible la correción, en cada tira de nL bits, de un máximo de t errores si t ≤
.
2
Sin embargo, no siempre se podrán corregir t errores, dado que esto depende de si la decodificación se realiza para un número suficientemente grande de bits y de la distribución de los
errores. Este dato es solo una cota y en otras ocasiones este número será menor, pero en general
se puede decir que corregirá t errores si estos se encuentran a la suficiente distancia [4]. Esta
no es la mejor forma de medir la capacidad correctora de un código pero puede darnos una
estimación.
A continuación se muestran algunos casos en los que es posible, o no, corregir un mensaje.
Usando el código de ejemplos anteriores se tiene que nL = 6 y se podrían corrige hasta t
errores si t ≤ 2 y estos se encuentran lo suficientemente separados.
Vemos un ejemplo en el que se envía el mensaje 101011001100 y hay tres errores muy próximos:
21
Como se ve, el mensaje no se ha decodificado correctamente. El diagrama de Trellis es el
siguiente:
Se supera la cota vista y el camino rojo tiene la misma distancia de Hamming al recibido que
el correcto. Dado que el algoritmo uno cualquiera entre ellos y hay dos posibles, no podemos
tener la certeza de cuales fueron las palabras enviadas.
En esta otra ocasión, enviando 101011001100100 se producen 5 errores pero están todos lo
suficientemente separados como para que el decodificador los corrija.
Por último, un caso en el que no hay más de 2 errores en una secuencia de 6 bits, pero sin
embargo no es posible corregirlos debido a su distribución.
22
En este caso el camino más probable no es el correcto sino el marcado en rojo. Las dos lineas
azules tienen igual probabilidad, por lo que ambas podrían haber sido tomadas, aunque en este
caso ese camino sea cortado más adelante.
A continuación vamos a comparar varios códigos convolucionales con otros códigos en mensajes más largos (de N bits) para un canal con probabilidad de error p. De este canal vamos a
suponer que los errores se producen de manera aleatoria y que no se producen ráfagas. Esto se
da en canales con una probabilidad de error baja, en este caso se ha probado hasta p = 10−1 .
Además el mensaje tendrá un largo suficiente y la decodificación se producirá utilizando un
número suficiente de bits.
En un supuesto como este el número de errores que se puede corregir se aproximará a t.
Teniendo esto en cuenta calculamos la probabilidad de que una secuencia de n · L llegue
correctamente de modo similar a como se haría con los códigos de blques. Esto es, la probabilidad de que no haya errores más la probabilidad de que haya un número de errores que
pueda corregir.
n·L
La probabilidad de que en n · L haya t errores es
pk (1 − p)(n·L)−k .
t
Entonces, la probabilidad de que un bloque de n · L llegue correctamente con un código capaz
de corregir t errores es:
t
P
n·L
pB =
pt (1 − p)(n·L)−t
t
k=1
23
Con esto podemos hacer una estimación para un mensaje de N bits. La probabilidad de que
llegue correcto es:
pM ≥ (pB )N ∗n
Es decir, la probabilidad de ir desplazando una secuencia de n · L bits por cada uno de los
bits codificados.
A continuación se muestra una tabla para N = 10000 bits y distintos códigos con la probabilidad de que llegue correcto. Para los códigos convolucionales se muestra la estimación de la
cota inferior vista y entre paréntesis el resultlado de una simulación para 500 transmisiones:
p = 0,001
p = 0,01
bits enviados
Sin código
0
0
10000
Convolucional L = 3,n = 2 - (5, 7)
0,998 (100 %) 0,676(72 %)
20000
Convolucional L = 4,n = 3 - (11, 15, 17)
1 (100 %)
0,998 (100 %)
30000
Convolucional L = 7,n = 2 - (115, 147)
1 (100 %)
0,996 (100 %)
20000
Triple Repetición
0,97
0,05
30000
Hamming(3)
0,94
0,006
17500
Hammin(4)
0,91
0,00015
10714
A partir de estos datos podemos ver que los códigos convolucionales son una buena opción
en canales con mucha probabilidad de error, ya que son capaces de corregir grandes cantidades de errores sin necesidad de enviar muchos bits redundantes. En las capturas siguientes
es posible ver la diferencia entre utilizar un código Hamming y un código convolucional con
memoria 3. Enviando un 14 % más de bits es capaz de decodificar el código en un número
mucho mayor de ocasiones. Utilizando otro de los códigos convolucionales vistos en la tabla,
esta mejora sería aún mayor.
24
25
7.
Mejoras de los Códigos Convolucionales
En esta sección se muestran algunas de las mejoras que se pueden hacer sobre los códigos
convolucionales y formas de usarlos para obtener un mayor rendimiento.
Codificadores recursivos
Existen codificadores recursivos. Son aquellos en los que a la entrada se le suma alguna
de las sumas anteriores como en el siguiente ejemplo.
Es posible utilizar un codificador de este tipo para conseguir resultados como los de un codificador no recursivo pero utilizando una circuitería más sencilla.
Este codificador, además, es sistemático. Esto significa que la entrada está incluida en la
salida (salida X2 ), generalmente los codificadores recursivos son sistematicos mientras que los
no-recursivos no lo son. Por lo general un código sistemático tiene una menor distancia libre
que uno no-sistemático.
Códigos catastróficos.
Son aquellos cuyo diagrama de estados contiene un ciclo en el que para una secuencia que no
sea de ceros tiene una salida completa de ceros. Esto supone que entradas que difieren en un
número infinito de bits pueden producir salidas que difieran en un número finito pequeño de
ellos. Este tipo de códigos se debe evitar ya que un número pequeño de errores puede producir
un gran número de errores después de codificar.
Solamente 1/(2n − >) de los códigos con ratio 1/n son catastróficos, sin embargo es necesario
prestar atención para evitarlos y un conjunto de condiciones suficientes para evitarlo han sido
probadas [11].
26
Para este código la entrada 000 . . . 000 produciría una salida de ceros, y la entrada 111 . . . 111
tiene como salida 1001000 . . . 000. De este modo, con que se produjeran solo dos errores en la
codificación de una palabra tan larga como quisiéramos, la decodificación sería incorrecta.
Los códigos sistemáticos nombrados en el apartado anterior no son, en ningún caso, códigos
catastróficos.
Códigos perforados
Mediante esta técnica se eliminan algunos de los bits de salida de forma que no se envíen el
canal. Con esto se consigue un código con un ratio mayor, aunque la capacidad de corrección
sea menor.
Se puede definir un código perforado por una matriz cuyos elementos son 1 si se debe enviar
el bit correspondiente o 0 en caso contrario.
Un ejemplo es la matriz siguiente, que genera un código con ratio 2/3 a partir de uno con
ratio 1/2.
1 0
P =
1 1
El número de filas es el número de polinomios generadores. La matriz se aplicará de forma
cíclica. De este modo se indica que del primer polinomio se enviará el bit resultado solo una de
cada dos veces.
Posteriormente el decodificador de Viterbi ignorará estos bits.
Mejora ante rachas de errores
Los códigos convolucionales con k = 1 y memoria pequeña, tal como hemos visto, no permiten la corrección de largas rachas de errores (ráfagas). Para evitar esto se suelen utilizar en
conjunto con otros códigos.
Una de las opciones mas comunes es hacerlo junto a códigos de Reed-Solomon. La técnica habitual consiste en una codificación con un código RS, posteriormente un entrelazado y
finalmente la codificación convolucional. El mismo proceso se realizará al contrario en el decodificador.
De este modo se consiguen códigos con capacidad de corregir un gran número de errores gracias al código convolucional, y aún cuando esto se encuentren por rachas, gracias a los códigos
RS y el entrelazado.
Tubo códigos
Un camino a seguir después de los códigos convolucionales es el de los turbo códigos. Estos
hacen uso de varios códigos combinados y consiguen alcanzar la capacidad correctora de
códigos convolucionales con memorias mayores sin que su decodificación suponga una complejidad equivalente a la de los convolucionales.
Un caso simple es en el que se hacen varias copias de la entrada y mientras que una copia se
envía por un código convolucional, otra se permuta y se envía por uno distinto.
27
8.
Aplicaciones
Estos códigos, teniendo en cuenta las mejoras vistas, son especialmente útiles en aquellos
canales con una probablidad de error muy alta. Usados en conjunto con otros códigos
serían muy adecuados también en los que por sus carácteristas se producen ráfagas de errores.
Por esto junto a otro código se usan en la televisión digital DVB (por cable, DVB-C; satélite, DVB-S; o terrestre, DVB-T) y en telefonía movil GSM. En concreto, se utiliza un código
convolucional con parámetros L = 5, k = 1, n = 2.
También encontramos estos códigos los antiguos modems telefónicos o los estándares actuales
ADSL2+, o en transmisiones en el espacio con sondas o en la dirección de misiones no tripuladas.
El uso de códigos convolucionales ha tenido un gran peso en la exploración espacial.
Anteriormente a 1969 no se utilizaban códigos en las transmisiones y antes de utilizar códigos
convolucionales se utilizó un código llamado código biortogonal de bloques. A partir de 1977 se
comenzó a utilizar un código convolucional con decodificación por el algoritmo de Viterbi.
Un ejemplo del uso de este código fue en las misiones de ambas Voyager donde se utilizaron
códigos convolucionales de parámetros L = 7, k = 1, n = 2. La Voyager 1 actualmente se
encuentra en el límite del sistema solar y aún hoy se siguen recibiendo datos.
En 1986 se empezaron a utilizar códigos con mayor memoria, en concreto con L = 15 y su
combinación con códigos RS. Se han usado, por ejemplo, en la sonda Cassini para la exploración
de Saturno o en los rovers enviados a Marte.
Una muestra de la mejora que han supuesto estos códigos en la exploración espacial se puede
ver en la siguiente imagen. En el eje horizontal se muestra lo que se llama SNR por bit, utilizada
para medir el rendimiento del código para cada probabilidad de error del canal en el eje vertical.
Más información sobre los códigos de la exploración espacial se puede encontrar en [9].
28
Anexo A. Programa de Simulación Codice.
Junto al trabajo se ha desarrollado esta aplicación que permite simular códigos Convolucionales con k = 1. Se ha añadido además la posibilidad de utilizar códigos de Hamming o
mensajes sin codificar para compararlos.
No se ha puesto especial atención al rendimiento o al diseño del software. Sin embargo ha
sido útil para mostrar ejemplos, estudiar las probabilidades de distintos códigos y crear los
diagramas de Trellis de los códigos convolucionales.
Uso del programa
La ventana que se muestra al abrir la aplicación se divide en tres partes. En la parte izquierda
es posible elegir el código y las propiedades así como la probabilidad de error de canal. Tras
una simulación también se mostrará en esta zona, en la parte inferior la distancia calculada
para el código, la dimensión y la longitud.
La forma de introducir los polinomios de un código convolucional es poniendo los coeficientes
de menor a mayor grado y separar cada uno mediante punto y coma (;) respetando los espacios
de la forma siguiente:
a0 , a1 , ..., am ; b0 , b1 , ..., bm ...
29
En el centro de la ventana hay cuatro cajas de texto que permiten visualizar el proceso de
la transmisión. En la primera de ellas se podrá introducir el mensaje que se desea enviar. Este
puede ser en binario o texto (ASCII), y la opción se debe elegir en la parte superior.
También es posible generar un mensaje aleatorio de un número determinado de bits mediante
la opción correspondiente.
Una vez escrito el mensaje, al pulsar la tecla Simular, aparecerá en el resto de las cajas de
texto, por este orden, el mensaje codificado, el mensaje recibido y por último el mensaje decodificado con el número de errores que haya sido posible corregir corregidos. En los dos últimos
aparecerán en verde los bits o símbolos correctos y en rojo los incorrectos.
En los mensajes codificados se muestra una palabra por fila para una visualización más sencilla.
Por último, a la derecha aparecerá el número de errores en el mensaje recibido (er) y el
número de mensajes en el código decodificado (ef ). Más abajo se puede ver una estimación de
la probabilidad de que el mensaje llegue correctamente (P b, Probabilidad de mensaje bueno).
En el caso de los códigos convolucionales esta probabilidad será una cota inferior teniendo en
cuenta que el mensaje tenga una longitud suficiente y el canal no tenga ráfagas.
Detalles de implementación
Para realizar el programa se ha utilizado el lenguaje de programación C++. La interfaz gráfica se ha hecho utilizando las librerías Qt por ser multiplataforma. Para las operaciones con
30
matrices se utiliza la librería Armadillo que permite hacer productos de matrices, modificaciones, etc. de forma sencilla y eficiente.
En el caso de los códigos de Hamming es necesario crear las matrices generadora y de control.
En primer lugar se crea la matriz de control y se permutan sus columnas de forma que aparezca
la identidad. A partir de esta se crea la generadora de forma trivial.
La codificación consistirá en el producto de una matriz con las palabras a codificar por la
generadora y la decodificación se hace mediante síndromes. Para esto último se mantendrá una
lista de síndromes con su error asociado con la que se comparara en la decodificación de cada
palabra.
En el caso de los códigos convolucionales se utiliza el enfoque similar a la visión mediante
registros hardware vista. Se creará una matriz de elementos binarios con los coeficientes de los
polinomios generadores y otra en la que para cada bit se tendrá una fila formada por ese bit
y los m anteriores. De este modo la codificación consistiría en el producto de esta matriz por
la generadora. Una implementación más eficiente de esto se podría realizar utilizando lo visto
sobre codificación en el apartado correspondiente de este trabajo.
La decodificación y el cálculo de la distancia libre se hacen mediante el algoritmo de Viterbi
tal como está explicado en el pseudocódigo mostrado en el trabajo.
31
Bibliografía
[1] J.G. Proakis, M.Salehi, Communication Systems Engineering. Prentice Hall, 2nd Edition, 2001. Section 9.7
[2] David Forney Principles of Digital Communication II course. MIT OpenCourseWare,
2007
http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/
6-451-principles-of-digital-communication-ii-spring-2005/
[3] Volker Kuhn Introduction into Error Correcting Codes. Institute of Communications Engineering - www.
euitt.upm.es/uploaded/519/Introduction.pdf
[4] Digital Communication Systems Lectures. Digital Communication Systems, Massachusetts Institute of
Technology - http://web.mit.edu/6.02/www/s2010/
[5] Richard D. Wesel Convolutional Codes. University of California - http://www.ee.ucla.edu/~wesel/
documents/Misc/eot309.pdf
[6] J.I. Iglesias Curto, A Study on Convolutional Codes. Classification, New Families and Decoding . Universidad de Salamanca, 2008 - http://gredos.usal.es/jspui/handle/10366/22484
[7] Prof. David Forney Introduccion a los Codigos Convolucionales. Asignatura de Comunicaciones Eléctricas,
Universidad Nacional de Rosario - http://comunicaciones.eie.fceia.unr.edu.ar/cx/conv.pdf
[8] Inmaculada Hernáez Rioja Modulaciones Codificadas Trellis. Asignatura de Procesado Digital de la Señal
en Comunicaciones, Universidad del Pais Vasco - http://aholab.ehu.es/~inma/psc/tema5.pdf
[9] Viterbi’s Impact on the Exploration of the Solar System. California Institute of Technology - www.ee.
caltech.edu/EE/Faculty/rjm/papers/Viterbi70.pdf
[10] Robin Schriebman Error Correcting Code. Harvey Mudd College, 2006 - www.cs.hmc.edu/~mike/public_
html/courses/security/s06/projects/robin.pdf
[11] Patric Ostergard Convolutional Codes. http://www.comlab.hut.fi/studies/3410/slides_08_8_4.pdf
[12] Viterbi Algorithm for Decoding of Convolutional Codes. Core Technologies http://www.1-core.com/
library/comm/viterbi/
[13] Tail-Biting Convolutional Coding. MathWorks Documentation Center http://www.mathworks.es/es/
help/comm/examples/tail-biting-convolutional-coding.html
[14] J.M. Muñoz Porras Códigos convolucionales y geometría algebraica. http://www.fme.upc.edu/Farxius/
butlleti-digital/riemann/080403_conferencia_porrasdominguez_volum.pdf
32
Descargar