Lempel–Ziv–Welch

出典: フリー百科事典『ウィキペディア(Wikipedia)』
移動先: 案内検索

Lempel–Ziv–Welchは、辞書式圧縮である Lempel-Ziv法 (LZ78) を、スペリー社のテリー・ウェルチが改良したアルゴリズムで、開発者のLempel、Ziv、Welchの頭文字を取って命名された。略称はLZW

圧縮効率と高速化の両面を追求している為、LZSSハフマン符号化を組み合わせたDeflateアルゴリズム(LZHZIPなどが採用)と比べると30%ほど圧縮効率が悪い。GIFTIFFなどの圧縮で利用されている。UNIX Compressで使える。

アルゴリズム[編集]

LZ78と違い、最初に入力可能なすべての文字を辞書に追加して初期化しておくため、部分文字列は辞書に必ず存在し、出力はコードだけの配列となる。「コード」とは辞書に登録されている文字列に対応するインデックスのことである。Welchの1984年の論文[1]では8ビットが並んだデータを12ビット固定長のコード列としてエンコードしていた。0から255のコードは対応する1つの8ビット文字を表し、256から4095のコードは辞書にない文字列がデータに出現し、辞書にその文字列を追加するときに順次割り振られる。

このアルゴリズムは同じパターンが繰り返されるデータに最適に働く。辞書に追加しながらエンコードするため文字列の最初の部分は低圧縮率となるが、文字列が増えるに連れ圧縮率はしだいに最大へと近づく。[2]

「文字列」と表現しているが入力は文字列でなくともよいため、他のデータの圧縮にもすぐに用いられた。例えば、カラーテーブルを使った画像では1文字はカラーテーブルのインデックスに対応する。しかし、1980年台には多くの画像が16色程度の小さなカラ―テーブルしか持っていなかったため、画像が大きくない限り12ビット幅のコードでは小さな圧縮率しか得られなかった。このため可変幅コードのアイディアが導入された。コードはエンコードしているシンボルより一般的には1ビット広い幅から始め、各コードサイズが使い切られるにつれて、コード幅は1ビットずつ広げられ、予め決められた最大値(一般的には12ビット)まで広げられる。最大コード値まで達した時は、エンコーディングは既存の辞書を使用して続けられるが、新たなコードが作られたり、辞書に追加されることはない。

その他の改善には、辞書をクリアーして初期状態に復元することを示すコード(クリアーコード、一般的には個別のアルファベット文字の値のすぐ後の最初の値)や、データの終わり(ストップコード、一般的にはクリアーコードより1大きい)を示すコードを辞書に確保することが含まれる。クリアーコードはテーブルが満杯になった後に再初期化し、エンコーディングが入力データのパターンの変化に対応することを可能にする。賢いエンコーダーは圧縮効率を監視し、既存のテーブルが入力に合っていないときはいつでも辞書をクリアーすることができる。

デコーダーは出力されたコード列だけでエンコーダに使われたのと同じ辞書をデコードしながら再び作ることができるため、完全な辞書をエンコードされたデータと一緒に送る必要はない。このためエンコーダーとデコーダーがどの種類のLZWが使われているかー―1文字のサイズ、最大辞書サイズ(とコード幅)、可変幅のエンコーディングが使われているかどうか、初期コード幅、クリアーコード・ストップコードが使われているかどうか(そしてコード値はなにか)ーーについて合意していることが重要である。LZWを採用している多くのフォーマットではこの情報はフォーマット仕様に盛り込まれているか、圧縮データのヘッダーにこれらの情報のための明確なフィールドが確保されている。

エンコーディング[編集]

エンコーディングアルゴリズムは以下の通り。

  1. すべての入力可能な文字(使用される場合はクリアーコード・ストップコードも)で辞書を初期化する
  2. 現在の入力文字列と最も長く一致する文字列Wを辞書から探す
  3. 出力にWの辞書のインデックス(コード)を送出し、Wを入力文字列から削除する
  4. 入力で後ろに続く1文字sを付け足したW + sを辞書に追加する
  5. 2に戻る

デコーディング[編集]

デコーディングアルゴリズムは以下の通り。

  1. 辞書を初期化する(エンコーディングの1と同じ)
  2. 入力からコードを1つ読み込み、入力から削除する
  3. そのコードに対応する文字列Wを辞書から得る
  4. 出力にWを送出する
  5. 入力から次のコードを読み込む
  6. 次のコードに対応する文字列の最初の文字sをWに付け足したW + sを辞書に追加する
  7. 2に戻る

可変幅コード[編集]

もし可変幅コードが使われている場合、エンコーダーとデコーダーはエンコードされたデータの同じ位置でコード幅の変更が行われなくてはならない。一般的なバージョンではエンコーダーは文字列W + sが辞書になかったが、次に辞書で利用可能なコードが2pであったときに幅をpからp + 1へ増やす(コードを辞書に追加しなければならないため)。エンコーダーはWのコードを幅pで出力に送出する。そして次のコードからp + 1ビット幅で送出できるようにコード幅を増やす。

デコーダーはいつも辞書の作成でエンコーダーより1コード分遅れており、Wのコードを見るとき、それは2p − 1のコードを生成する。エンコーダーがコード幅を増やすポイントであるから、デコーダーもpビットで最大のコードを生成するポイントであるここで同じように幅を増やさなければならない。

不幸なことに、初期に実装されたいくつかのエンコーディングアルゴリズムはコード幅を増やした後、古い幅ではなく新しい幅でWを送出する。デコーダーには1コード分早く変化したと見えるため、これは"Early Change"と呼ばれる。この違いは大きな混乱を招くため、アドビはPDFファイルではどちらのバージョンも許容しているが、それぞれのLZW圧縮ストリームのヘッダーにEarly Changeが使われているかどうかを示す明示的なフラグを含めている。LZW圧縮が使用可能な画像ファイルフォーマットのうち、TIFFはEarly Changeを使うが、GIFとその他多くの画像ファイルフォーマットでは使っていない。

クリアーコードによって辞書がクリアーされた時、エンコーダーとデコーダーの両方はコード幅をクリアーコードのあと初期のコード幅に戻し、クリアーコードの後すぐにそのコードから開始する。

パッキング順序[編集]

コードの送出は一般的にはバイト境界に一致しないため、エンコーダーとデコーダーはどのようにコードをバイトに詰め込むかを合意しておかなければならない。一般的な2つの方法はLSB-First("Least Significant Bit First")とMSB-First("Most Significant Bit First")である。

GIFはパッキング順序にLSB-Firstを使い、TIFFとPDFはMSB-Firstを使う。

実装[編集]

以下、Groovyでの実装。まず、ビット列を扱うストリームを用意する。

class BitStream {
  BitSet bs = new BitSet(); int len = 0, pos = 0;
  void write(int v, int bits) {
    for (int i in 0..<bits) { bs[len++] = ((v >>> i) & 1) != 0 }
  }
  int read(int bits) {
    int v = 0; for (int i in 0..<bits) { if (bs[pos++]) { v |= 1 << i } }
    return v
  }
  String toString() { "length = $len, {" + (0..<len).findAll({ bs[it] }).join(", ") + "}" }
}

圧縮は以下の通り。

BitStream compress(byte[] data) {
  BitStream bs = new BitStream(); List str = []; int maxCode = 255, maxCodeBits = 8;
  Map table = [:]; for (int i in 0..maxCode) { table[[(byte) i]] = i }

  for (byte c in data) {
    str << c
    if (!table.containsKey(str)) {
      bs.write(table[str[0..(str.size() - 2)]], maxCodeBits)
      table[str] = ++maxCode
      if (maxCode == (1 << maxCodeBits)) maxCodeBits++
      str = [c]
    }
  }
  bs.write(table[str], maxCodeBits)
  return bs
}

解凍は以下の通り。

byte[] decompress(BitStream bs) {
  List bytes = []; int maxCode = 255, maxCodeBits = 8, prevCode; byte c;
  List table = []; for (byte v in 0..maxCode) { table << [v] }

  bs.pos = 0
  bytes << (c = prevCode = bs.read(maxCodeBits))
  while (bs.pos < bs.len) {
    if (++maxCode == (1 << maxCodeBits)) maxCodeBits++
    int code = bs.read(maxCodeBits)
    List str = (code == maxCode) ? table[prevCode] + c : table[code]
    bytes.addAll(str)
    table << table[prevCode] + (c = str[0])
    prevCode = code
  }
  return bytes as byte[]
}

Example[編集]

今回圧縮する平文(アルファベットの大文字のみで表される)は

TOKYOTOKKYOKYOKAKYOKU#

である。#は文字列の終端を表す。 この時、使用される文字は27種類(アルファベットおよび#)である。 この例では、1~26の数字をアルファベットに、 0を#に当てはめる。

Encoding[編集]

Current Sequence Next Char Output Extended Dictionary Comments
Code Bits
NULL T
T O 20 10100 27: TO 27 = first available code after 0 through 26
O K 15 01111 28: OK
K Y 22 10110 29: KY
Y O 25 11001 30: YO
O T 15 01111 31: OT
TO K 27 11011 32: TOK 32 requires 6 bits, so for next output use 6 bits
K K 22 010110 33: KK
KY O 29 011101 34: KYO
OK Y 28 011100 35: OKY
YO K 30 011110 36: YOK
K A 22 010110 37: KA
A K 1 000001 38: AK
KYO K 34 100010 39: KYOK
K U 22 010110 40: KU
U # 21 010101 # stops the algorithm; send the cur seq
0 000000 and the stop code

Decoding[編集]

Input Output Sequence New Dictionary Entry Comments
Bits Code Full Conjecture
10100 20 T 27: T?
01111 15 O 27: TO 28: O?
10110 22 K 28: OK 29: K?
11001 25 Y 29: KY 30: Y?
01111 15 O 30: YO 31: O?
11011 27 TO 31: OT 32: TO? created code 31 (last to fit in 5 bits)
010110 22 K 32: TOK 33: K? so start reading input at 6 bits
011101 29 KY 33: KK 34: KY?
011100 28 OK 34: KYO 35: OK? 35 = OK + 1st symbol (Y) of
011110 30 YO 35: OKY 36: YO? next coded sequence received (YO)
010110 22 K 36: YOK 37: K?
000001 1 A 37: KA 38: A?
100010 34 KYO 38: AK 39: KYO?
010110 22 K 39: KYOK 40: K?
010101 21 U 40: KU 41: U?
000000 0 #

また、

TANBANANAS#

などをエンコードしたものを、またデコードする際には、辞書が不完全な状態で使用されることとなるが、その場合も辞書の一文字目を最後の文字として付け足して、使用すればよい。

特許[編集]

LZWは1984年に発表された。当初スペリー社が特許を保有していた。のちスペリー社はバロース社と合併し1986年ユニシス社となり、本アルゴリズムの特許権もユニシス社に引き継がれた。

前述の通りGIF画像の圧縮に用いられており、その特許料に関するユニシス社の姿勢が問題となった。詳細はGIF特許問題を参照。

日本では1984年6月20日に特許が出願され、2004年6月20日に期限切れとなった。以下、日本の特許庁産業財産権情報より:

  • 発明の名称:圧縮装置および圧縮方法
  • 出願日:1984年6月20日
    • 出願番号:特許出願平7-341868
  • 公開日:1996年9月13日
    • 公開番号:特許公開平8-237138

出典[編集]

  1. ^ Welch, Terry (1984). “A Technique for High-Performance Data Compression”. Computer 17 (6): 8–19. doi:10.1109/MC.1984.1659158. http://www.cs.duke.edu/courses/spring03/cps296.5/papers/welch_1984_technique_for.pdf. 
  2. ^ Ziv, J.; Lempel, A. (1978). “Compression of individual sequences via variable-rate coding”. IEEE Transactions on Information Theory 24 (5): 530. doi:10.1109/TIT.1978.1055934. http://www.cs.duke.edu/courses/spring03/cps296.5/papers/ziv_lempel_1978_variable-rate.pdf.