~ubuntu-branches/ubuntu/precise/bioperl/precise

« back to all changes in this revision

Viewing changes to Bio/Tools/HMM.pm

  • Committer: Bazaar Package Importer
  • Author(s): Ilya Barygin
  • Date: 2010-01-27 22:48:22 UTC
  • mfrom: (3.1.4 squeeze)
  • Revision ID: james.westby@ubuntu.com-20100127224822-ebot4qbrjxcv38au
Tags: 1.6.1-1ubuntu1
* Merge from Debian testing, remaining changes:
  - disable tests, they produce a FTBFS trying to access the network 
    during run.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
## $Id: HMM.pm 11480 2007-06-14 14:16:21Z sendu $
2
 
 
3
 
# BioPerl module for Bio::Tools::HMM
4
 
#
5
 
# Cared for by Yee Man Chan <ymc@yahoo.com>
6
 
#
7
 
# Copyright Yee Man Chan
8
 
#
9
 
# You may distribute this module under the same terms as perl itself
10
 
 
11
 
# POD documentation - main docs before the code
12
 
 
13
 
=head1 NAME
14
 
 
15
 
Bio::Tools::HMM - Perl extension to perform Hidden Markov Model calculations
16
 
 
17
 
=head1 SYNOPSIS
18
 
 
19
 
  use Bio::Tools::HMM;
20
 
  use Bio::SeqIO;
21
 
  use Bio::Matrix::Scoring;
22
 
 
23
 
  # create a HMM object
24
 
  # ACGT are the bases NC mean non-coding and coding
25
 
  $hmm = Bio::Tools::HMM->new('-symbols' => "ACGT", '-states' => "NC");
26
 
 
27
 
  # initialize some training observation sequences
28
 
  $seq1 = Bio::SeqIO->new(-file => $ARGV[0], -format => 'fasta');
29
 
  $seq2 = Bio::SeqIO->new(-file => $ARGV[1], -format => 'fasta');
30
 
  @seqs = ($seq1, $seq2);
31
 
 
32
 
  # train the HMM with the observation sequences
33
 
  $hmm->baum_welch_training(\@seqs);
34
 
 
35
 
  # get parameters
36
 
  $init = $hmm->init_prob; # returns an array reference
37
 
  $matrix1 = $hmm->transition_prob; # returns Bio::Matrix::Scoring
38
 
  $matrix2 = $hmm->emission_prob; # returns Bio::Matrix::Scoring
39
 
 
40
 
  # initialize training hidden state sequences
41
 
  $hs1 = "NCNCNNNNNCCCCNNCCCNNNNC";
42
 
  $hs2 = "NCNNCNNNNNNCNCNCNNNCNCN";
43
 
  @hss = ($hs1, $hs2);
44
 
 
45
 
  # train the HMM with both observation sequences and hidden state
46
 
  # sequences
47
 
  $hmm->statistical_training(\@seqs, \@hss);
48
 
 
49
 
  # with the newly calibrated HMM, we can use viterbi algorithm
50
 
  # to obtain the hidden state sequence underlying an observation 
51
 
  # sequence
52
 
  $hss = $hmm->viterbi($seq); # returns a string of hidden states
53
 
 
54
 
=head1 DESCRIPTION
55
 
 
56
 
Hidden Markov Model (HMM) was first introduced by Baum and his colleagues
57
 
in a series of classic papers in the late 1960s and 1970s. It was first
58
 
applied to the field of speech recognition with great success in the 1970s.
59
 
 
60
 
Explosion in the amount sequencing data in the 1990s opened the field
61
 
of Biological Sequence Analysis. Seeing HMM's effectiveness in detecing
62
 
signals in biological sequences, Krogh, Mian and Haussler used HMM to find
63
 
genes in E. coli DNA in a classical paper in 1994. Since then, there have
64
 
been extensive application of HMM to other area of Biology, for example,
65
 
multiple sequence alignment, CpG island detection and so on.
66
 
 
67
 
=head1 DEPENDENCIES
68
 
 
69
 
This package comes with the main bioperl distribution. You also need
70
 
to install the lastest bioperl-ext package which contains the XS code
71
 
that implements the algorithms. This package won't work if you haven't
72
 
compiled the bioperl-ext package.
73
 
 
74
 
=head1 TO-DO
75
 
 
76
 
 
77
 
=over 3
78
 
 
79
 
=item 1.
80
 
 
81
 
Allow people to set and get the tolerance level in the EM algorithm.
82
 
 
83
 
=item 2.
84
 
 
85
 
Allow people to set and get the maximum number of iterations 
86
 
to run in the EM algorithm.
87
 
 
88
 
=item 3.
89
 
 
90
 
A function to calculate the probability of an observation sequence
91
 
 
92
 
=item 4.
93
 
 
94
 
A function to do posterior decoding, ie to find the probabilty of
95
 
seeing a certain observation symbol at position i.
96
 
 
97
 
=back
98
 
 
99
 
=head1 FEEDBACK
100
 
 
101
 
=head2 Mailing Lists
102
 
 
103
 
User feedback is an integral part of the evolution of this and other
104
 
Bioperl modules.  Send your comments and suggestions preferably to one
105
 
of the Bioperl mailing lists.  Your participation is much appreciated.
106
 
 
107
 
  bioperl-l@bioperl.org                  - General discussion
108
 
  http://bioperl.org/wiki/Mailing_lists  - About the mailing lists
109
 
 
110
 
=head2 Reporting Bugs
111
 
 
112
 
Report bugs to the Bioperl bug tracking system to help us keep track
113
 
the bugs and their resolution. Bug reports can be submitted via the
114
 
web:
115
 
 
116
 
  http://bugzilla.open-bio.org/
117
 
 
118
 
=head1 AUTHOR
119
 
 
120
 
        This implementation was written by Yee Man Chan (ymc@yahoo.com).
121
 
        Copyright (c) 2005 Yee Man Chan. All rights reserved. This program
122
 
        is free software; you can redistribute it and/or modify it under
123
 
        the same terms as Perl itself. All the code are written by Yee
124
 
        Man Chan without borrowing any code from anywhere.
125
 
 
126
 
=cut
127
 
 
128
 
package Bio::Tools::HMM;
129
 
 
130
 
use base qw(Bio::Root::Root);
131
 
 
132
 
BEGIN {
133
 
    eval {
134
 
        require Bio::Ext::HMM;
135
 
    };
136
 
    if ( $@ ) {
137
 
        die("\nThe C-compiled engine for Hidden Markov Model (HMM) has not been installed.\n Please read the install the bioperl-ext package\n\n");
138
 
        exit(1);
139
 
    }
140
 
}
141
 
 
142
 
sub new {
143
 
   my ($class, @args) = @_;
144
 
 
145
 
   my $self = $class->SUPER::new(@args);
146
 
 
147
 
   my ($symbols, $states, $init, $a_mat, $e_mat) = $self->_rearrange([qw(SYMBOLS
148
 
                                                                STATES
149
 
                                                                INIT
150
 
                                                                AMAT
151
 
                                                                EMAT
152
 
                                                        )], @args);
153
 
 
154
 
   $self->throw("Observation Symbols are not defined!") unless defined $symbols; 
155
 
   $self->throw("Hidden States are not defined!") unless defined $states; 
156
 
 
157
 
   if (defined $symbols) {
158
 
      if (scalar($symbols)) {
159
 
         # check duplicate symbols
160
 
         if ($self->symbols($symbols) < 0) {
161
 
            $self->throw("Duplicate symbols!\n");
162
 
         }
163
 
      }
164
 
      else {
165
 
         $self->throw("We don't support list of symbols in this version.\n");
166
 
      }
167
 
   }
168
 
 
169
 
   if (defined $states) {
170
 
      if (scalar($states)) {
171
 
         # check duplicate states
172
 
         if ($self->states($states) < 0) {
173
 
            $self->throw("Duplicate states!\n");
174
 
         }
175
 
      }
176
 
      else {
177
 
         $self->throw("We don't support list of states in this version.\n");
178
 
      }
179
 
   }
180
 
 
181
 
   $self->{'hmm'} = Bio::Ext::HMM::HMM->new($symbols, $states);
182
 
   return $self;
183
 
}
184
 
 
185
 
=head2 likelihood
186
 
 
187
 
 Title   : likelihood
188
 
 Usage   : $prob = $hmm->likelihood($seq)
189
 
 Function: Calculate the probability of an observation sequence given an HMM
190
 
 Returns : An floating point number that is the logarithm of the probability
191
 
           of an observation sequence given an HMM
192
 
 Args    : The only argument is a string that is the observation sequence
193
 
           you are interested in. Note that the sequence must not contain
194
 
           any character that is not in the alphabet of observation symbols.
195
 
 
196
 
=cut
197
 
 
198
 
sub likelihood {
199
 
   my ($self, $seq) = @_;
200
 
   my $valid_symbols;
201
 
 
202
 
   if( ! defined $seq) {
203
 
      $self->warn("Cannot calculate without supply an observation sequence!");
204
 
      return;
205
 
   }
206
 
   my $s = $self->{'symbols'};
207
 
   $_ = $seq;
208
 
   $valid_symbols = eval "tr/$s//;"; 
209
 
   if ($valid_symbols != length($seq)) {
210
 
      $self->throw("Observation Sequence contains characters that is not in the
211
 
                    alphabet of observation symbols!\n");
212
 
   }
213
 
   return Bio::Ext::HMM->HMM_likelihood($self->{'hmm'}, $seq);
214
 
}
215
 
 
216
 
=head2 statistical_training
217
 
 
218
 
 Title   : statistical_training
219
 
 Usage   : $hmm->statistical_training(\@seqs, \@hss)
220
 
 Function: Estimate the parameters of an HMM given an array of observation 
221
 
           sequence and an array of the corresponding hidden state 
222
 
           sequences
223
 
 Returns : Returns nothing. The parameters of the HMM will be set to the 
224
 
           estimated values
225
 
 Args    : The first argument is a reference to an array of observation 
226
 
           sequences. The second argument is a reference to an array of
227
 
           hidden state sequences. Note that the lengths of an observation
228
 
           sequence and a hidden state sequence must be the same.
229
 
 
230
 
=cut
231
 
 
232
 
sub statistical_training {
233
 
   my ($self, $seqs, $hss) = @_;
234
 
   my $valid_symbols;
235
 
   my $seq_cnt, $hs_cnt;
236
 
   my $i;
237
 
 
238
 
   if( ! defined $seqs or ! defined $hss) {
239
 
      $self->warn("Cannot calculate without supply an observation and a hidden state sequence!");
240
 
      return;
241
 
   }
242
 
   $seq_cnt = @{$seqs};
243
 
   $hs_cnt = @{$seqs};
244
 
   if ($seq_cnt != $hs_cnt) {
245
 
      $self->throw("There must be the same number of observation sequences and 
246
 
                    hidden state sequences!\n");
247
 
   }
248
 
   for ($i = 0; $i < $seq_cnt; ++$i) {
249
 
      if (length(@{$seqs}[$i]) != length(@{$hss}[$i])) {
250
 
         $self->throw("The corresponding observation sequences and hidden state sequences must be of the same length!\n");
251
 
      }
252
 
   }
253
 
   foreach $seq (@{$seqs}) {
254
 
      my $s = $self->{'symbols'};
255
 
      $_ = $seq;
256
 
      $valid_symbols = eval "tr/$s//;"; 
257
 
      if ($valid_symbols != length($seq)) {
258
 
         $self->throw("Observation Sequence contains characters that is not in the
259
 
                alphabet of observation symbols!\n");
260
 
      }
261
 
   }
262
 
   foreach $seq (@{$hss}) {
263
 
      my $s = $self->{'states'};
264
 
      $_ = $seq;
265
 
      $valid_symbols = eval "tr/$s//;"; 
266
 
      if ($valid_symbols != length($seq)) {
267
 
         $self->throw("Hidden State Sequence contains characters that is not in the
268
 
                alphabet of hidden states!\n");
269
 
      }
270
 
   }
271
 
   Bio::Ext::HMM->HMM_statistical_training($self->{'hmm'}, $seqs, $hss);
272
 
}
273
 
 
274
 
=head2 baum_welch_training
275
 
 
276
 
 Title   : baum_welch_training
277
 
 Usage   : $hmm->baum_welch_training(\@seqs)
278
 
 Function: Estimate the parameters of an HMM given an array of observation 
279
 
           sequence
280
 
 Returns : Returns nothing. The parameters of the HMM will be set to the 
281
 
           estimated values
282
 
 Args    : The only argument is a reference to an array of observation 
283
 
           sequences. 
284
 
 
285
 
=cut
286
 
 
287
 
sub baum_welch_training {
288
 
   my ($self, $seqs) = @_;
289
 
   my $valid_symbols;
290
 
 
291
 
   if( ! defined $seqs) {
292
 
      $self->warn("Cannot calculate without supply an observation sequence!");
293
 
      return;
294
 
   }
295
 
   foreach $seq (@{$seqs}) {
296
 
      my $s = $self->{'symbols'};
297
 
      $_ = $seq;
298
 
      $valid_symbols = eval "tr/$s//;"; 
299
 
      if ($valid_symbols != length($seq)) {
300
 
         $self->throw("Observation Sequence contains characters that is not in the
301
 
                alphabet of observation symbols!\n");
302
 
      }
303
 
   }
304
 
   Bio::Ext::HMM->HMM_baum_welch_training($self->{'hmm'}, $seqs);
305
 
}
306
 
 
307
 
=head2 viterbi
308
 
 
309
 
 Title   : viterbi
310
 
 Usage   : $hss = $hmm->viterbi($seq)
311
 
 Function: Find out the hidden state sequence that can maximize the 
312
 
           probability of seeing observation sequence $seq.
313
 
 Returns : Returns a string that is the hidden state sequence that maximizes
314
 
           the probability of seeing $seq.
315
 
 Args    : The only argument is an observation sequence.
316
 
 
317
 
=cut
318
 
 
319
 
sub viterbi {
320
 
   my ($self, $seq) = @_;
321
 
   my $valid_symbols;
322
 
 
323
 
   if( ! defined $seq) {
324
 
      $self->warn("Cannot calculate without supply an observation sequence!");
325
 
      return;
326
 
   }
327
 
   my $s = $self->{'symbols'};
328
 
   $_ = $seq;
329
 
   $valid_symbols = eval "tr/$s//;"; 
330
 
   if ($valid_symbols != length($seq)) {
331
 
      $self->throw("Observation Sequence contains characters that is not in the
332
 
             alphabet of observation symbols!\n");
333
 
   }
334
 
   return Bio::Ext::HMM->HMM_viterbi($self->{'hmm'}, $seq);
335
 
}
336
 
 
337
 
=head2 symbols
338
 
 
339
 
 Title     : symbols 
340
 
 Usage     : $symbols = $hmm->symbols() #get
341
 
           : $hmm->symbols($value) #set
342
 
 Function  : the set get for the observation symbols
343
 
 Example   :
344
 
 Returns   : symbols string
345
 
 Arguments : new value
346
 
 
347
 
=cut
348
 
 
349
 
sub symbols {
350
 
   my ($self,$val) = @_;
351
 
   my %alphabets = ();
352
 
   my $c;
353
 
 
354
 
   if ( defined $val ) {
355
 
# find duplicate
356
 
      
357
 
      for ($i = 0; $i < length($val); ++$i) {
358
 
         $c = substr($val, $i, 1);
359
 
         if (defined $alphabets{$c}) {
360
 
            $self->throw("Can't have duplicate symbols!");
361
 
         }
362
 
         else {
363
 
            $alphabets{$c} = 1;
364
 
         }
365
 
      }
366
 
      $self->{'symbols'} = $val;
367
 
   }
368
 
   return $self->{'symbols'};
369
 
}
370
 
 
371
 
 
372
 
=head2 states
373
 
 
374
 
 Title     : states
375
 
 Usage     : $states = $hmm->states() #get
376
 
           : $hmm->states($value) #set
377
 
 Function  : the set get for the hidden states
378
 
 Example   :
379
 
 Returns   : states string
380
 
 Arguments : new value
381
 
 
382
 
=cut
383
 
 
384
 
sub states {
385
 
   my ($self,$val) = @_;
386
 
   my %alphabets = ();
387
 
   my $c;
388
 
 
389
 
   if ( defined $val ) {
390
 
# find duplicate
391
 
      
392
 
      for ($i = 0; $i < length($val); ++$i) {
393
 
         $c = substr($val, $i, 1);
394
 
         if (defined $alphabets{$c}) {
395
 
            $self->throw("Can't have duplicate states!");
396
 
         }
397
 
         else {
398
 
            $alphabets{$c} = 1;
399
 
         }
400
 
      }
401
 
      $self->{'states'} = $val;
402
 
   }
403
 
   return $self->{'states'};
404
 
}
405
 
 
406
 
=head2 init_prob
407
 
 
408
 
 Title     : init_prob
409
 
 Usage     : $init = $hmm->init_prob() #get
410
 
           : $hmm->transition_prob(\@init) #set
411
 
 Function  : the set get for the initial probability array
412
 
 Example   :
413
 
 Returns   : reference to double array
414
 
 Arguments : new value
415
 
 
416
 
=cut
417
 
 
418
 
sub init_prob {
419
 
   my ($self, $init) = @_;
420
 
   my $i;
421
 
   my @A;
422
 
 
423
 
   if (defined $init) {
424
 
      if (ref($init)) {
425
 
         my $size = @{$init};
426
 
         my $sum = 0.0;
427
 
         foreach (@{$init}) {
428
 
            $sum += $_;
429
 
         }
430
 
         if ($sum != 1.0) {
431
 
            $self->throw("The sum of initial probability array must be 1.0!\n");
432
 
         }
433
 
         if ($size != length($self->{'states'})) {
434
 
            $self->throw("The size of init array $size is different from the number of HMM's hidden states!\n");
435
 
         }
436
 
         for ($i = 0; $i < $size; ++$i) {
437
 
            Bio::Ext::HMM::HMM->set_init_entry($self->{'hmm'}, substr($self->{'states'}, $i, 1), @{$init}[$i]);
438
 
         }
439
 
      }
440
 
      else {
441
 
         $self->throw("Initial Probability array must be a reference!\n");
442
 
      }
443
 
   }
444
 
   else {
445
 
      for ($i = 0; $i < length($self->{'states'}); ++$i) {
446
 
         $A[$i] = Bio::Ext::HMM::HMM->get_init_entry($self->{'hmm'}, substr($self->{'states'}, $i, 1));
447
 
      }
448
 
      return \@A;
449
 
   } 
450
 
}
451
 
 
452
 
=head2 transition_prob
453
 
 
454
 
 Title     : transition_prob
455
 
 Usage     : $transition_matrix = $hmm->transition_prob() #get
456
 
           : $hmm->transition_prob($matrix) #set
457
 
 Function  : the set get for the transition probability mairix
458
 
 Example   :
459
 
 Returns   : Bio::Matrix::Scoring 
460
 
 Arguments : new value
461
 
 
462
 
=cut
463
 
 
464
 
sub transition_prob {
465
 
   my ($self, $matrix) = @_;
466
 
   my $i, $j;
467
 
   my @A;
468
 
 
469
 
   if (defined $matrix) {
470
 
      if ($matrix->isa('Bio::Matrix::Scoring')) {
471
 
         my $row = join("", $matrix->row_names);
472
 
         my $col = join("", $matrix->column_names);
473
 
         if ($row ne $self->{'states'}) {
474
 
            $self->throw("Names of the rows ($row) is different from the states of HMM " . $self->{'states'});
475
 
         } 
476
 
         if ($col ne $self->{'states'}) {
477
 
            $self->throw("Names of the columns ($col) is different from the states of HMM " . $self->{'states'});
478
 
         }
479
 
         for ($i = 0; $i < length($self->{'states'}); ++$i) {
480
 
            my $sum = 0.0;
481
 
            my $a = substr($self->{'states'}, $i, 1);
482
 
            for ($j = 0; $j < length($self->{'states'}); ++$j) {
483
 
               my $b = substr($self->{'states'}, $j, 1);
484
 
               $sum += $matrix->entry($a, $b);
485
 
            }
486
 
            if ($sum != 1.0) {
487
 
               $self->throw("Sum of probabilities for each from-state must be 1.0!\n");
488
 
            }
489
 
         }
490
 
         for ($i = 0; $i < length($self->{'states'}); ++$i) {
491
 
            my $a = substr($self->{'states'}, $i, 1);
492
 
            for ($j = 0; $j < length($self->{'states'}); ++$j) {
493
 
               my $b = substr($self->{'states'}, $j, 1);
494
 
               Bio::Ext::HMM::HMM->set_a_entry($self->{'hmm'}, $a, $b, $matrix->entry($a, $b));
495
 
            }
496
 
         }
497
 
      }
498
 
      else {
499
 
         $self->throw("Transition Probability matrix must be of type Bio::Matrix::Scoring.\n");
500
 
      }
501
 
   }
502
 
   else {
503
 
      for ($i = 0; $i < length($self->{'states'}); ++$i) {
504
 
         for ($j = 0; $j < length($self->{'states'}); ++$j) {
505
 
            $A[$i][$j] = Bio::Ext::HMM::HMM->get_a_entry($self->{'hmm'}, substr($self->{'states'}, $i, 1), substr($self->{'states'}, $j, 1));
506
 
         }
507
 
      }
508
 
      my @rows = split(//, $self->{'states'});
509
 
      return $matrix = Bio::Matrix::Scoring->new(-values => \@A, -rownames => \@rows, -colnames => \@rows);
510
 
   } 
511
 
}
512
 
 
513
 
=head2 emission_prob
514
 
 
515
 
 Title     : emission_prob
516
 
 Usage     : $emission_matrix = $hmm->emission_prob() #get
517
 
           : $hmm->emission_prob($matrix) #set
518
 
 Function  : the set get for the emission probability mairix
519
 
 Example   :
520
 
 Returns   : Bio::Matrix::Scoring 
521
 
 Arguments : new value
522
 
 
523
 
=cut
524
 
 
525
 
sub emission_prob {
526
 
   my ($self, $matrix) = @_;
527
 
   my $i, $j;
528
 
   my @A;
529
 
 
530
 
   if (defined $matrix) {
531
 
      if ($matrix->isa('Bio::Matrix::Scoring')) {
532
 
         my $row = join("", $matrix->row_names);
533
 
         my $col = join("", $matrix->column_names);
534
 
         if ($row ne $self->{'states'}) {
535
 
            $self->throw("Names of the rows ($row) is different from the states of HMM " . $self->{'states'});
536
 
         } 
537
 
         if ($col ne $self->{'symbols'}) {
538
 
            $self->throw("Names of the columns ($col) is different from the symbols of HMM " . $self->{'symbols'});
539
 
         }
540
 
         for ($i = 0; $i < length($self->{'states'}); ++$i) {
541
 
            my $sum = 0.0;
542
 
            my $a = substr($self->{'states'}, $i, 1);
543
 
            for ($j = 0; $j < length($self->{'symbols'}); ++$j) {
544
 
               my $b = substr($self->{'symbols'}, $j, 1);
545
 
               $sum += $matrix->entry($a, $b);
546
 
            }
547
 
            if ($sum != 1.0) {
548
 
               $self->throw("Sum of probabilities for each state must be 1.0!\n");
549
 
            }
550
 
         }
551
 
         for ($i = 0; $i < length($self->{'states'}); ++$i) {
552
 
            my $a = substr($self->{'states'}, $i, 1);
553
 
            for ($j = 0; $j < length($self->{'symbols'}); ++$j) {
554
 
               my $b = substr($self->{'symbols'}, $j, 1);
555
 
               Bio::Ext::HMM::HMM->set_e_entry($self->{'hmm'}, $a, $b, $matrix->entry($a, $b));
556
 
            }
557
 
         }
558
 
      }
559
 
      else {
560
 
         $self->throw("Emission Probability matrix must be of type Bio::Matrix::Scoring.\n");
561
 
      }
562
 
   }
563
 
   else {
564
 
      for ($i = 0; $i < length($self->{'states'}); ++$i) {
565
 
         for ($j = 0; $j < length($self->{'symbols'}); ++$j) {
566
 
            $A[$i][$j] = Bio::Ext::HMM::HMM->get_e_entry($self->{'hmm'}, substr($self->{'states'}, $i, 1), substr($self->{'symbols'}, $j, 1));
567
 
         }
568
 
      }
569
 
      my @rows = split(//, $self->{'states'});
570
 
      my @cols = split(//, $self->{'symbols'});
571
 
      return $matrix = Bio::Matrix::Scoring->new(-values => \@A, -rownames => \@rows, -colnames => \@cols);
572
 
   } 
573
 
}
574
 
 
575
 
1;