Proposed Algorithm : Algorithm

The problem of computing the minimum redundancy codes as we observe symbols one by one has received a lot of attention. However, existing algorithms implicitly assumes that either we have a small alphabet — quite typically 256 symbols — or that we have an arbitrary amount of memory at our disposal for the creation of the tree. In real life applications one may need to encode symbols coming from a much larger alphabet, for e.g. coding integers. We now have to deal not with hundreds of symbols but possibly with millions of symbols. While other algorithms use a space proportional to the number of observed symbols, we here propose one that uses space proportional to the number of frequency classes, which is, quite interestingly, always smaller or equal to the size of the alphabet. 1. Proposed Algorithm: Algorithm M Of all compression algorithms, it is certainly Huffman’s algorithm [3] for generating minimum redundancy codes that is the most widely known. The general problem consists in assigning to each symbol s of a certain set S a binary code with an integer length that is the closest possible to ( ) − lg p( ) s , where p(s) is an estimate of the probability of symbol s, and lg x is a shorthand for log2 x. Huffman’s algorithm generates a set of codes for which the average code length is bounded by the interval [H( ),H( ) . ) S S p + + 0 086 , where p is the largest probability of all symbols s in S [8, p. 107]. While being rather efficient, this algorithm has several practical disadvantages. The first is that it is a static code. Huffman’s algorithm precomputes the code book C after having obtained an estimate for p(s) for all s in S, and then the compression proper uses the same code book C for all the data, which sometimes can lead to compression inefficiencies if the data in the sequence are not i.i. d. , that is, differs substantially from p(s). One must not forget that we are using p(s) instead of a more appropriate pt(s), which depends on t, the ‘time’ or the position in the sequentially processed data. If the code book was adaptive, one might capture some information leading to a better approximation of pt(s) which in turn leads to possibly better compression. The second major disadvantage is that the algorithm must see all the data before one can actually perform the compression. Since we are trying to estimate p(s) for a data set composed of symbols out of S, we must scan all the data in order to get a good estimate. Often, it is quite impractical to do so. One might not have wanted space to store all the data before starting compression or, as it is often the case in communication, ‘all the data’ might be an illusive concept. If we estimate p(s) out of a small sample of the data we expose ourselves to the possibility that the estimate of p(s) out of a small sample is a bad approximation of the real p( s). The memory space used by Huffman’s algorithm (whether the implementation is memory-efficient or not) is essentially O(n), where n is the number of symbols in S, which can lead to problems when n is large. In textbooks, one never finds an example of code books generated by Huffman’s algorithm with more than 256 symbols. In this context, the ‘worst case’ code book consists of assigning a code to each of the 256 possible bytes. In the real world, however, one might rather need a code book for a set of several thousands, or even of several million symbols, like all the m bits long integers. In certain special cases, one can use an off-the-shelf code like the socalled Golomb’s codes, or Rice’s codes or others [8,9], but it is in fact rare that the distribution function exactly match a geometric distribution function. Golomb’s codes are disastrous if used for a distribution that is not geometric! In general, the distribution is incompletely known at best so it might be hard to derive a standard parametric code for it. And, of course, one must generally reject the trivial solution of building up a look up table of several million entries in memory. Lastly, for the compressed data to be understandable by the decoder, the decoder must know the code book C. Either one transmits the code book itself or the function p(s). In both cases the expense of doing so when the code book is large might be so high as to completely lose the gain made in compression. If S has 256 symbols, as it is often the case, then the cost of transmitting the code book remains modest compared to the cost of transmitting the compressed data which can be still quite large. While actually having a certain loss of compression due to the transmission of the code book, it might still be relatively insignificant and thus still give a satisfying result. But when one is dealing with a very large set of symbols, the code book must also be very large. Even if a very clever way of encoding the code book is used, it might remain so costly to transmit that the very idea of compressing this data using a Huffman-like algorithm must be abandoned. In this paper, to address the problems of adaptivity, memory management and code book transmission problems, we propose a new algorithm for adaptive Huffman coding. The algorithm allows for very good adaptivity, efficient memory usage and efficient decoding algorithms. We present our algorithm which consists in three conceptual parts: set representation, set migration and tree rebalancing. We finally discuss various initialization schemes and compare our results against the Calgary Corpus and static Huffman coding [3] (the ordinary Huffman algorithm) and Vitter’s algorithm Λ [7,12]. 1.1. Algorithm M We will now present an algorithm that is well suited for large sets of symbols. They naturally arise in a variety of contexts, such as in the ‘deflate’ compression algorithm [15,16] and in JPEG [17]. In the ‘deflate’ compression algorithm, lengths and positions of matches made by a basic LZ77 compressor over a window 32K symbols long are encoded using a variable length coding scheme thus achieving better compression. In the JPEG still image compression standard, the image is first decomposed by a discrete cosine transform and the coefficients are then quantized. Once quantization is done, a scheme, not unlike the one in Fig. 1. [13], is used to represent the possible values of the coefficients. There are however fewer values possible in JPEG, but still a few thousands. There are many other situations where a large number of symbols is needed. The algorithm we now propose uses a tree with leaves that represent sets of symbols rather than individual symbols and uses only two operations to remain as close as possible to the optimal: set migration and rebalancing. The basic idea is to put in the same leaf all the symbols that have the exact same probability. Set migration happens when a symbol moves from one set to an other set. This is when a symbol, seen m times (and thus belonging to the set of all symbols seen m times) is seen one more time and is moved to the set of symbols seen m+1 times. If the set of symbols seen m+1 times does not exist, we create it as the sibling of the set of symbols seen m times. If the set of symbols seen m times is now empty, we destroy it. Rebalancing, when needed, will be performed in at most O(-lg p(s) ) operations. Let us now present in detail this new algorithm. Let us say that we have the symbols representing the set of the integers from 0 to B. The minimal initial configuration of the tree is a single leaf (which is also the root) containing the set {0,. ,B}, to which a zero frequency is assigned. It can be zero since we don’t work directly with the probability, but with the number of times a symbol was seen. At that point, the codes are simply the binary representation of the numbers 0 to B. When the first symbol is seen, say ‘2’, it has a count of 1. Since we don’t have a set of symbols seen exactly 1 time, we create it. That gives us a tree that has a root node and two leaves. The codes now have a one bit prefix, followed by either no bits, if it is the (only) symbol in the new set, or ( ) ⎡ ⎤ lg { , , ,. , } 0 1 3 B bits (for any other). As other symbols are seen for the first time the set of all symbols seen once grows and the set of all possible but yet unobserved symbols shrinks. When we encounter a symbol a second time, we create the set of symbols seen exactly 2 times, and so on. This situation is shown in fig. 2. Note that the numbers written on nodes are their weights (a parent’s weight is the sum of its two children’s weight, and the leaves’ weight are simply the number of time the symbols in its associated set have been seen so far times the number of symbols in its set). If a region of the tree is too much out of balance, we rebalance it, starting at the position where the new set was inserted. 1 If at the moment of the insertion of node z, the node x has already a sibling, we create a new parent node t in place of node x, such that its children are the node x and the node z. Codes Representable Values 1 0 10 + 1 bit -1,+1 1100 + 2 bits -3,-2,+2,+3 1101 + 3 bits -7,-6,-5,-4,4,5,6,7 11100 + 5 bits -15,-14,. ,-9,-8,8,9,. ,13,14,15 11101 + 6 bits -47,. ,-16,16,. ,47