home *** CD-ROM | disk | FTP | other *** search
/ Delphi 5 for Professionals / DELPHI5.iso / AddOns / Components / TEECHART / Delphi1_And_Delphi2 / EXAMPLES / OTHER / NEURAL / NEURAL.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1998-10-24  |  16.3 KB  |  654 lines

  1. {**********************************************}
  2. {   TeeChart                                   }
  3. {   Neural Network Component                   }
  4. {   Copyright (c) 1996 by David Berneda        }
  5. {**********************************************}
  6. unit neural;
  7.  
  8. { This unit contains a complete set of Native Delphi components
  9.   to implement a Neural Network.
  10.  
  11.   Hierarchy:
  12.  
  13.   TNeuralLayer
  14.       ----------------   TInputLayer
  15.       ----------------   TMediumLayer
  16.       ----------------   TOutputLayer
  17.  
  18.   TNeuralNet
  19.  
  20. }
  21. interface
  22.  
  23. Uses Classes,SysUtils;
  24.  
  25. Type  Float=Single;
  26.       TNeuralNetMode=(nnmLearn,nnmProcess);
  27.       TNeuralNetAction=(nnaContinue,nnaStop);
  28.  
  29. Const MaxSamples = 250;  { max number of patterns }
  30.       MaxNeurodes= 50;   { max number of Layer neurodes }
  31.  
  32.       FactorSigmoid:Float=1.0;
  33.  
  34. type TNeuralSample = Array[1..MaxNeurodes] of Float;
  35.  
  36.      TNeuralSamples=class
  37.      private
  38.        FSamples:Array[1..MaxSamples] of ^TNeuralSample;
  39.      public
  40.        Constructor Create;
  41.        Destructor Destroy; override;
  42.        Procedure Load(Var f:Text; NumNeurodes,NumSample:Integer);
  43.        Function GetData(t,tt:Integer):Float;
  44.      { }
  45.        property Sample[t,tt:Integer]:Float read GetData;
  46.      end;
  47.  
  48.      TNeuralWeight= Array[1..MaxSamples] of Float;
  49.  
  50.      TNeuralWeights=class
  51.      private
  52.        FWeights:Array[1..MaxNeurodes] of ^TNeuralWeight;
  53.      protected
  54.        Function GetWeight(NumSample,NeurodeIndex:Integer):Float;
  55.      public
  56.        Constructor Create;
  57.        Destructor Destroy; override;
  58.        Procedure Randomize;
  59.        Procedure Load(Var f:Text; NumNeurodes,NumSamples:Integer);
  60.        Procedure Save(Var f:Text; NumNeurodes,NumSamples:Integer);
  61.      { }
  62.        property Weight[Neurode,Sample:Integer]:Float read GetWeight;
  63.      end;
  64.  
  65.      TNeuralLayer=class
  66.      private
  67.        FSize:Integer;
  68.      public
  69.        property Size:Integer read FSize;
  70.      end;
  71.  
  72.      TInputLayer=class(TNeuralLayer)
  73.      private
  74.        FNumSamples : Integer;
  75.        FSamples    : TNeuralSamples;
  76.      public
  77.        Constructor Create;
  78.        Destructor Destroy; override;
  79.        Procedure ReadData(Var f:Text);
  80.        Function CalcLength(NumSample:Integer):Float;
  81.  
  82.        property NumSamples:Integer read FNumSamples;
  83.        property Samples:TNeuralSamples read FSamples;
  84.      End;
  85.  
  86.      TOutputLayer=class;
  87.  
  88.      TMediumLayer=class(TNeuralLayer)
  89.      private
  90.        FWeights: TNeuralWeights;
  91.        FOutput,
  92.        FError  : TNeuralSample;
  93.      public
  94.        Constructor Create;
  95.        Destructor Destroy; override;
  96.        procedure DoForward(NumSample:Integer; Input:TInputLayer);
  97.        procedure DoError(Output:TOutputLayer);
  98.        procedure AdjustWeights( NumSample :Integer; Const Beta:Float; Input:TInputLayer );
  99.        Function  CalcLength:Float;
  100.        Procedure Randomize;
  101.        Procedure LoadWeights(Var f:Text);
  102.        procedure SaveWeights(Input:TInputLayer);
  103.        { }
  104.        property Weights:TNeuralWeights read FWeights;
  105.      end;
  106.  
  107.      TOutputLayer=class(TNeuralLayer)
  108.      private
  109.        FStandard,
  110.        FMaxStandard,
  111.        FFinalErr   : Float;
  112.        FWeights    : TNeuralWeights;
  113.        FOutput,
  114.        FError      : TNeuralSample;
  115.        FTotalError : TNeuralWeight;
  116.        FSamples    : TNeuralSamples;
  117.      public
  118.        Constructor Create;
  119.        Destructor Destroy; override;
  120.        Procedure DoForward(Medium:TMediumLayer);
  121.        procedure DoError(NumSample:Integer);
  122.        procedure AdjustWeights(Const Beta:Float; Medium:TMediumLayer);
  123.        Procedure Randomize;
  124.        procedure AppendWeights(Medium:TMediumLayer);
  125.        Procedure LoadWeights(Var f:Text);
  126.        function  CheckOutError( var tmpError : boolean; NumPats:Integer) : boolean;
  127.        Function GetOutput(NeurodeIndex:Longint):Float;
  128.        Function GetOutputSum:Float;
  129.        Procedure Load(Const FileName:String; NumSamples:Integer);
  130.        { }
  131.        property FinalErr:Float read FFinalErr;
  132.        property Samples:TNeuralSamples read FSamples;
  133.        property Weights:TNeuralWeights read FWeights;
  134.      End;
  135.  
  136.      TNeuralNet=class;
  137.  
  138.     TNeuralNetOnIteration=procedure (Sender:TNeuralNet; Var Action:TNeuralNetAction) of object;
  139.  
  140.     TNeuralNet=class(TComponent)
  141.     private
  142.       FAdjustBeta    : Boolean;
  143.       FIterationCount: Longint;
  144.       FLearned       : Boolean;
  145.       FCurrentMode   : TNeuralNetMode;
  146.       FInput  : TInputLayer;
  147.       FMedium : TMediumLayer;
  148.       FOutput : TOutputLayer;
  149.       FBeta   : Float;
  150.       { events }
  151.       FOnIteration:TNeuralNetOnIteration;
  152.     public
  153.       Constructor Create(AOwner:TComponent); override;
  154.       Destructor Destroy; override;
  155.       procedure DoBackPass( NumSample : Integer );
  156.       procedure DoForwardPass( NumSample:Integer);
  157.       procedure RandomizeWeights;
  158.       Procedure LoadWeights;
  159.       Procedure ReadDataFile(Const FileName:String);
  160.       Procedure Initialize;
  161.       Procedure StartLearning;
  162.       Procedure SaveWeights;
  163.       Function CalcGuessedOutput(NumSample:Integer):Float;
  164.       { }
  165.       property IterationCount:Longint read FIterationCount;
  166.       property Input:TInputLayer read FInput;
  167.       property Medium:TMediumLayer read FMedium;
  168.       property Output:TOutputLayer read FOutput;
  169.       property Learned:Boolean read FLearned write FLearned;
  170.       property Beta:Float read FBeta;
  171.     published
  172.       property OnIteration:TNeuralNetOnIteration read FOnIteration
  173.                                                  write FOnIteration;
  174.     End;
  175.  
  176. implementation
  177.  
  178. { Useful Functions }
  179. Function OpenText(Var f:Text; Const Path:String):Boolean;
  180. Begin
  181.   Assign(f,Path);
  182.   {$I-}
  183.   Reset(f);
  184.   {$I+}
  185.   result:=IOResult=0;
  186. End;
  187.  
  188. Function AppendText(Var f:Text; Const Path:String):Boolean;
  189. Begin
  190.   Assign(f,Path);
  191.   {$I-}
  192.   Append(f);
  193.   {$I+}
  194.   result:=IOResult=0;
  195. End;
  196.  
  197. Function OpenTextWrite(Var f:Text; Const Path:String):Boolean;
  198. Begin
  199.   Assign(f,Path);
  200.   {$I-}
  201.   Rewrite(f);
  202.   {$I+}
  203.   result:=IOResult=0;
  204. End;
  205.  
  206. { TNeuralSamples }
  207. Constructor TNeuralSamples.Create;
  208. var t:Integer;
  209. begin
  210.   inherited Create;
  211.   for t:=1 to MaxSamples do New(FSamples[t]);
  212. end;
  213.  
  214. Destructor TNeuralSamples.Destroy;
  215. var t:Integer;
  216. begin
  217.   for t:=1 to MaxSamples do Dispose(FSamples[t]);
  218.   inherited Destroy;
  219. end;
  220.  
  221. Procedure TNeuralSamples.Load(Var f:Text; NumNeurodes,NumSample:Integer);
  222. var i:Integer;
  223. begin
  224.   for i:= 1 to NumNeurodes do
  225.   begin
  226.     Read(f,FSamples[NumSample]^[i]);
  227.     if FSamples[NumSample]^[i]>=1 then FSamples[NumSample]^[i]:=0.999999
  228.     Else
  229.     if FSamples[NumSample]^[i]<=0 then FSamples[NumSample]^[i]:=0.000001;
  230.   end;
  231. end;
  232.  
  233. Function TNeuralSamples.GetData(t,tt:Integer):Float;
  234. begin
  235.   result:=FSamples[t]^[tt];
  236. end;
  237.  
  238. { TNeuralWeights }
  239. Constructor TNeuralWeights.Create;
  240. Var t:Longint;
  241. begin
  242.   inherited Create;
  243.   for t:=1 to MaxNeurodes do New(FWeights[t]);
  244. end;
  245.  
  246. Destructor TNeuralWeights.Destroy;
  247. Var t:Longint;
  248. begin
  249.   for t:=1 to MaxNeurodes do Dispose(FWeights[t]);
  250.   inherited Destroy;
  251. end;
  252.  
  253. Function TNeuralWeights.GetWeight(NumSample,NeurodeIndex:Integer):Float;
  254. begin
  255.   result:=FWeights[NeurodeIndex]^[NumSample];
  256. end;
  257.  
  258. Procedure TNeuralWeights.Randomize;
  259. Var neurode,i:integer;
  260. Begin
  261.   for neurode:=1 to MaxNeurodes do
  262.     for i:=1 to MaxSamples do
  263.         FWeights[neurode]^[i]:=((random(32768)/32767.0)-0.5)/2.0;
  264. End;
  265.  
  266. Procedure TNeuralWeights.Load(Var f:Text; NumNeurodes,NumSamples:Integer);
  267. Var neurode,i:integer;
  268. Begin
  269.   for neurode:=1 to NumNeurodes do
  270.       for i:=1 to NumSamples do readln(f,FWeights[neurode]^[i]);
  271. end;
  272.  
  273. Procedure TNeuralWeights.Save(Var f:Text; NumNeurodes,NumSamples:Integer);
  274. Var neurode,i:integer;
  275. Begin
  276.   for Neurode:=1 to NumNeurodes do
  277.     for i:=1 to NumSamples do writeln(f,FWeights[Neurode]^[i]:8:3 );
  278. end;
  279.  
  280. { TInputLayer }
  281. Constructor TInputLayer.Create;
  282. begin
  283.   inherited Create;
  284.   FSamples:=TNeuralSamples.Create;
  285. end;
  286.  
  287. Destructor TInputLayer.Destroy;
  288. begin
  289.   FSamples.Free;
  290.   inherited Destroy;
  291. end;
  292.  
  293. Procedure TInputLayer.ReadData(Var f:Text);
  294. Begin
  295.   FNumSamples:=0;
  296.   While not eof(f) do
  297.   begin
  298.     Inc(FNumSamples);
  299.     FSamples.Load(f,FSize,FNumSamples);
  300.   end;
  301.   Dec(FNumSamples);
  302. End;
  303.  
  304. Function TInputLayer.CalcLength(NumSample:Integer):Float;
  305. Var Weight:integer;
  306. Begin
  307.   result:=0;
  308.   for Weight:=1 to FSize do result:=result+sqr(FSamples.FSamples[NumSample]^[Weight]);
  309.   if result<0.1 then result:=0.1;
  310. End;
  311.  
  312. { TMediumLayer }
  313. Constructor TMediumLayer.Create;
  314. begin
  315.   inherited Create;
  316.   FWeights:=TNeuralWeights.Create;
  317. end;
  318.  
  319. Destructor TMediumLayer.Destroy;
  320. begin
  321.   FWeights.Free;
  322.   inherited Destroy;
  323. end;
  324.  
  325. Procedure TMediumLayer.AdjustWeights( NumSample : Integer; Const Beta:Float; Input:TInputLayer);
  326. var tmpBeta,Length : Float;
  327.     Weight, neurode : integer;
  328. Begin
  329.   Length:=Input.CalcLength(NumSample);
  330.   For Neurode := 1 to FSize do
  331.   Begin
  332.     tmpBeta:=Beta*FError[Neurode]/Length;
  333.     For Weight := 1 to Input.FSize do
  334.         FWeights.FWeights[Neurode]^[Weight]:=
  335.            FWeights.FWeights[Neurode]^[Weight]+
  336.              tmpBeta*Input.FSamples.FSamples[NumSample]^[Weight];
  337.   End;
  338. End;
  339.  
  340. Procedure TMediumLayer.DoForward(NumSample:Integer; Input:TInputLayer);
  341. var Sum : Float;
  342.     Neurode, i : integer;
  343. begin
  344.   for Neurode := 1 to FSize do
  345.   begin
  346.     Sum := 0.0;
  347.     for i:=1 to Input.FSize do
  348.         Sum:=Sum+(FWeights.FWeights[Neurode]^[i]*
  349.                   Input.FSamples.FSamples[NumSample]^[i]);
  350.     FOutput[Neurode]:=1/(1+exp(-FactorSigmoid*Sum));
  351.   end;
  352. end;
  353.  
  354. Procedure TMediumLayer.DoError(Output:TOutputLayer);
  355. var Sum : Float;
  356.     Neurode, i : integer;
  357. begin
  358.   for Neurode := 1 to FSize do
  359.   begin
  360.     Sum:=0;
  361.     for i:=1 to Output.FSize do Sum:=Sum+(Output.FWeights.FWeights[i]^[Neurode]*Output.FError[i]);
  362.     FError[Neurode]:=FOutput[Neurode]*(1-FOutput[Neurode])*Sum;
  363.   end;
  364. end;
  365.  
  366. Function TMediumLayer.CalcLength:Float;
  367. Var Weight:Integer;
  368. Begin
  369.   result:=0;
  370.   For Weight:=1 to FSize do result:=result+sqr(FOutput[Weight]);
  371.   if result<0.1 then result:=0.1;
  372. End;
  373.  
  374. Procedure TMediumLayer.Randomize;
  375. Begin
  376.   FWeights.Randomize;
  377. End;
  378.  
  379. Procedure TMediumLayer.LoadWeights(Var f:Text);
  380. var tmpInSize:integer;
  381. Begin
  382.   Readln(f,FSize,tmpInSize);
  383.   FWeights.Load(f,FSize,tmpInSize);
  384. End;
  385.  
  386. Procedure TMediumLayer.SaveWeights(Input:TInputLayer);
  387. var f:Text;
  388. begin
  389.   OpenTextWrite(f,'NUMBERS.OUT');
  390.   try
  391.     Writeln(f,FSize,' ',Input.FSize);
  392.     FWeights.Save(f,FSize,Input.FSize);
  393.   finally
  394.     Close(f);
  395.   End;
  396. End;
  397.  
  398. { TOutputLayer }
  399. Constructor TOutputLayer.Create;
  400. Begin
  401.   inherited Create;
  402.   FWeights:=TNeuralWeights.Create;
  403.   FSamples:=TNeuralSamples.Create;
  404.   FStandard:=0.00001;
  405.   FMaxStandard:=2;
  406. End;
  407.  
  408. Function TOutputLayer.CheckOutError(var tmpError:Boolean; NumPats:Integer):Boolean;
  409. Var i:integer;
  410. begin
  411.   result:=True;
  412.   tmpError:=False;
  413.   FFinalErr:=0.0;
  414.   for i:=1 to NumPats do
  415.   begin
  416.     FFinalErr:=FFinalErr+FTotalError[i];
  417.     if FTotalError[i]>=FSize*FStandard then result:=False;
  418.     if FTotalError[i]>=FSize*FMaxStandard then
  419.     begin
  420.       tmpError:=True;
  421.       result:=True;
  422.     end;
  423.   end;
  424. End;
  425.  
  426. Procedure TOutputLayer.AppendWeights(Medium:TMediumLayer);
  427. var f:Text;
  428. begin
  429.   AppendText(f,'NUMBERS.OUT');
  430.   try
  431.     Writeln(f,FSize,' ',Medium.FSize);
  432.     FWeights.Save(f,FSize,Medium.FSize);
  433.   finally
  434.     close(f);
  435.   end;
  436. end;
  437.  
  438. Procedure TOutputLayer.Randomize;
  439. Begin
  440.   FWeights.Randomize;
  441. end;
  442.  
  443. Procedure TOutputLayer.DoForward(Medium:TMediumLayer);
  444. var Sum       : Float;
  445.     Neurode,i : integer;
  446. begin
  447.   for Neurode:=1 to FSize do
  448.   begin
  449.     Sum:=0.0;
  450.     for i:=1 to Medium.FSize do Sum:=Sum+(FWeights.FWeights[Neurode]^[i]*Medium.FOutput[i]);
  451.     FOutput[Neurode]:=1.0/(1.0+exp(-FactorSigmoid*Sum));
  452.   end;
  453. end;
  454.  
  455. Destructor TOutputLayer.Destroy;
  456. Begin
  457.   FSamples.Free;
  458.   FWeights.Free;
  459.   inherited Destroy;
  460. end;
  461.  
  462. Procedure TOutputLayer.DoError(NumSample:Integer);
  463. var Neurode:integer;
  464. begin
  465.   FTotalError[NumSample]:=0;
  466.   for Neurode:=1 to FSize do
  467.   begin
  468.     FError[Neurode]:=FSamples.FSamples[NumSample]^[Neurode]-FOutput[Neurode];
  469.     FTotalError[NumSample]:=FTotalError[NumSample]+Abs(FError[Neurode]);
  470.   end;
  471. end;
  472.  
  473. Procedure TOutputLayer.LoadWeights(Var f:Text);
  474. var tmpYSize,tmpMidSize:integer;
  475. Begin
  476.   Readln(f,tmpYSize,tmpMidSize);
  477.   FWeights.Load(f,tmpYSize,tmpMidSize);
  478. End;
  479.  
  480. Function TOutputLayer.GetOutput(NeurodeIndex:Longint):Float;
  481. begin
  482.   result:=FOutput[NeurodeIndex];
  483. end;
  484.  
  485. Function TOutputLayer.GetOutputSum:Float;
  486. var t:Integer;
  487. begin
  488.   result:=0;
  489.   for t:=1 to FSize do result:=result+FOutput[t];
  490. end;
  491.  
  492. Procedure TOutputLayer.AdjustWeights(Const Beta:Float; Medium:TMediumLayer);
  493. var tmpBeta,tmpLength : Float;
  494.     Weight,Neurode    : integer;
  495. Begin
  496.   tmpLength:=Beta/Medium.CalcLength;
  497.   for Neurode:=1 to FSize do
  498.   Begin
  499.     tmpBeta:=FError[Neurode]*tmpLength;
  500.     for Weight:=1 to Medium.FSize do
  501.         FWeights.FWeights[Neurode]^[Weight]:=FWeights.FWeights[Neurode]^[Weight]+TmpBeta*Medium.FOutput[Weight];
  502.   end;
  503. End;
  504.  
  505. Procedure TOutputLayer.Load(Const FileName:String; NumSamples:Integer);
  506. var f:Text;
  507.     NumSample:Integer;
  508. Begin
  509.   OpenText(f,FileName);
  510.   try
  511.     readln(f,FSize);
  512.     for NumSample:=1 to NumSamples do
  513.         FSamples.Load(f,FSize,NumSample);
  514.   finally
  515.     close(f);
  516.   end;
  517. end;
  518.  
  519. { TNeuralNet }
  520. Procedure TNeuralNet.DoForwardPass(NumSample:Integer);
  521. begin
  522.   FMedium.DoForward(NumSample,FInput);
  523.   FOutput.DoForward(FMedium);
  524. end;
  525.  
  526. Procedure TNeuralNet.DoBackPass(NumSample:Integer);
  527. begin
  528.   FOutput.DoError(NumSample);
  529.   FMedium.DoError(FOutput);
  530.   FOutput.AdjustWeights(FBeta,FMedium);
  531.   FMedium.AdjustWeights(NumSample,FBeta,FInput);
  532. end;
  533.  
  534. Procedure TNeuralNet.RandomizeWeights;
  535. Begin
  536.   Randomize;
  537.   FMedium.Randomize;
  538.   FOutput.Randomize;
  539. End;
  540.  
  541. Procedure TNeuralNet.ReadDataFile(Const FileName:String);
  542. Var InFile : text;
  543.     InPath : string;
  544.     i      : integer;
  545. Begin
  546.   InPath := FileName;
  547.   i:=Pos('.',InPath);
  548.   if i=0 then InPath:=''
  549.          else InPath:=Copy(InPath,1,i-1);
  550.   if InPath = '' then raise Exception.Create('No input file specified.');
  551.  
  552.   OpenText(InFile,InPath+'.NET');
  553.   try
  554.     readln( InFile, FInput.FSize, FMedium.FSize );
  555.     FInput.ReadData(infile);
  556.   finally
  557.     close(InFile);
  558.   end;
  559.  
  560.   FOutput.Load(InPath+'.OUT',FInput.FNumSamples);
  561. End;
  562.  
  563. Procedure TNeuralNet.Initialize;
  564. begin
  565.   FIterationCount := 1;
  566.   RandomizeWeights;
  567. end;
  568.  
  569. Procedure TNeuralNet.StartLearning;
  570. Const MaxFinalErr=100000000;
  571. var NumSample  : Integer;
  572.     LearnError : Boolean;
  573.     OldFinalErr: Float;
  574.     tmpAction  : TNeuralNetAction;
  575. begin
  576.   LearnError    := False;
  577.   FCurrentMode  := nnmLearn;
  578.   Initialize;
  579.   OldfinalErr   :=MaxFinalErr;
  580.   FLearned:= False;
  581.   while not FLearned do
  582.   begin
  583.     for NumSample:=1 to FInput.FNumSamples do
  584.     begin
  585.       DoForwardPass(NumSample);
  586.       DoBackPass(NumSample);
  587.     end;
  588.     Inc(FIterationCount);
  589.     FLearned := FOutput.CheckOutError(LearnError ,FInput.FNumSamples);
  590.     if OldfinalErr=MaxFinalErr then OldfinalErr:=FOutput.FFinalErr;
  591.     if FAdjustBeta then
  592.       FBeta:=FBeta+FBeta*(100.0*(OldFinalErr-FOutput.FFinalErr)/OldFinalErr)/100.0;
  593.     OldFinalErr:=FOutput.FFinalErr;
  594.     if Assigned(FOnIteration) then
  595.     begin
  596.       tmpAction:=nnaContinue;
  597.       FOnIteration(Self,tmpAction);
  598.       Case tmpAction of
  599.         nnaStop: FLearned:=True;
  600.       end;
  601.     end;
  602.   end;
  603.   for NumSample:=1 to FInput.FNumSamples do DoForwardPass(NumSample);
  604. End;
  605.  
  606. Procedure TNeuralNet.LoadWeights;
  607. Var f:Text;
  608. begin
  609.   OpenText(f,'NUMBERS.OUT');
  610.   try
  611.     FMedium.LoadWeights(f);
  612.     FOutput.LoadWeights(f);
  613.   finally
  614.     close(f);
  615.   End;
  616. End;
  617.  
  618. Function TNeuralNet.CalcGuessedOutput(NumSample:Integer):Float;
  619. begin
  620.   FCurrentMode:=nnmProcess;
  621.   DoForwardPass(NumSample);
  622.   result:=FOutput.GetOutputSum;
  623. End;
  624.  
  625. Procedure TNeuralNet.SaveWeights;
  626. Begin
  627.   if FCurrentMode=nnmLearn then
  628.   begin
  629.     FMedium.SaveWeights(FInput);
  630.     FOutput.AppendWeights(FMedium);
  631.   end;
  632. end;
  633.  
  634. Constructor TNeuralNet.Create(AOwner:TComponent);
  635. Begin
  636.   inherited Create(AOwner);
  637.   FAdjustBeta:=True;
  638.   FBeta:=0.8;
  639.   FOutput:=TOutputLayer.Create;
  640.   FInput:=TInputLayer.Create;
  641.   FMedium:=TMediumLayer.Create;
  642.   Initialize;
  643. End;
  644.  
  645. Destructor TNeuralNet.Destroy;
  646. begin
  647.   FOutput.Free;
  648.   FInput.Free;
  649.   FMedium.Free;
  650.   inherited Destroy;
  651. end;
  652.  
  653. end.
  654.