/*
File name: mlp.c
Created by: Ljubomir Buturovic
Created: 03/11/2001
Purpose: multi-layer perceptron (MLP) learning and prediction.
*/
/*
Copyright 2004 Ljubomir J. Buturovic
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/*
The principal function in this file is mlp_learn(). It trains a MLP
network using various optimization methods, including conjugate
gradient and gradient descent.
p_mlp_learn() serves as an interface between the menu system and
mlp_learn().
The MLP algorithms and variable names in mlp_learn() largely follow
notation and theory given in: Christopher M. Bishop, Neural Networks
for Pattern Recognition, Chapter 4. Oxford University Press, Oxford,
1995.
*/
static char rcsid[] = "$Id: mlp.c,v 1.201 2005/09/03 09:03:04 ljubomir Exp $";
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <errno.h>
#include "mlp.h"
#include "pau.h"
#include "lau.h"
#include "lmat.h"
#include "conjg.h"
#include "simplex.h"
#include "lerr.h"
#include "bagging.h"
/*
The structure used to pass parameters to mlp_function() through
wn_conj_gradient_method().
*/
struct mlp_parameters
{
/*
'int iteration' has to be the first member of the structure. MLP
optimization function sets it to the current iteration.
*/
int iteration;
float **t;
int errc;
struct mlp *perceptron;
struct dataset *dset;
FILE *outdev;
FILE *fdbg;
};
static float sigmoid(float a)
{
float g;
g = 1.0/(1.0+exp(-a));
return g;
}
/*
Calculate node inputs 'a' and activations 'z' for inputs 'x' and
network given in 'perceptron'.
*/
static void calculate_network(struct mlp *perceptron, float *x)
{
int nodes;
int i;
int j;
int d;
int c;
int layer;
int nlayers;
int cix;
int cl;
int iw;
int *npl;
float *w;
float *a;
float *z;
float *delta;
w = perceptron->w;
nodes = perceptron->nodes;
nlayers = perceptron->nlayers;
npl = perceptron->npl;
d = perceptron->d;
c = perceptron->npl[nlayers-1];
a = perceptron->a;
z = perceptron->z;
delta = perceptron->delta;
/*
'cix' is index of first node in previous layer. Current node
takes activations from nodes cix, cix+1, ... cix+npl[layer-1].
*/
cix = 0;
iw = 0;
layer = 0;
cl = npl[0]; /* cumulative number of nodes up to the current layer */
for (j = 0; j < nodes; j++)
{
if (j == cl)
{
layer++;
cl += npl[layer];
if (layer >= 2)
cix += npl[layer-2];
}
a[j] = w[iw++]; /* bias term */
if (layer == 0)
for (i = 0; i < d; i++)
a[j] += w[iw++]*x[i];
else
{
for (i = 0; i < npl[layer-1]; i++)
a[j] += w[iw++]*z[cix+i];
}
z[j] = sigmoid(a[j]);
}
}
/*
Compute sample error, defined as sum of squares of differences
between output and target values for each of the 'c' outputs.
*/
static double output_error(float *output, float *target, int c)
{
int i;
double deviation;
double dev;
dev = 0.0;
for (i = 0; i < c; i++)
{
deviation = output[i]-target[i];
dev += deviation*deviation;
}
return dev;
}
/*
Test functions.
*/
/*
Test function. It calculates the value of error function for the
perceptron described by 'perceptron' for a single sample 'x' and
target value 't'.
*/
static double mlp_check_criterion(struct mlp *perceptron, float *x, float *t)
{
int cix;
int c;
int nlayers;
double dev;
int *npl;
float *z;
nlayers = perceptron->nlayers;
npl = perceptron->npl;
c = perceptron->npl[nlayers-1];
z = perceptron->z;
cix = ivec_sum(npl, nlayers-1); /* cix is index of first output node */
/*
Calculate network outputs for sample 'x'.
*/
calculate_network(perceptron, x);
/*
Calculate output error.
*/
dev = output_error(&z[cix], t, c);
dev = 0.5*dev;
return dev;
}
/*
Test function. It calculates gradient 'gw' numerically at 'pw'. The
function has signature and semantics identical to mlp_gradient().
*/
static void mlp_check_gradient(double *gw, double *pw, void *parameters)
{
int i;
int n;
int wlen;
int nsamples;
float epsilon;
float depsilon;
float error_plus;
float error_minus;
float factor;
float wx;
float w;
float **t;
float **x;
struct mlp_parameters *mlp_params;
struct mlp *perceptron;
FILE *fdbg;
mlp_params = (struct mlp_parameters *) parameters;
perceptron = mlp_params->perceptron;
fdbg = mlp_params->fdbg;
nsamples = mlp_params->dset->nv;
t = mlp_params->t;
x = mlp_params->dset->x;
wlen = perceptron->wlen;
for (i = 0; i < wlen; i++)
{
gw[i] = 0.0;
perceptron->w[i] = pw[i];
}
factor = 0.001;
epsilon = factor;
/*
Calculate approximate gradient by perturbing each weight in
turn. Use formula (4.47) from Bishop.
*/
for (n = 0; n < nsamples; n++)
for (i = 0; i < wlen; i++)
{
wx = perceptron->w[i];
/*epsilon = wx*factor;*/
depsilon = epsilon+epsilon;
w = wx+epsilon;
perceptron->w[i] = w;
error_plus = mlp_check_criterion(perceptron, x[n], t[n]);
w -= depsilon;
perceptron->w[i] = w;
error_minus = mlp_check_criterion(perceptron, x[n], t[n]);
gw[i] += (error_plus-error_minus)/depsilon;
perceptron->w[i] = wx;
}
if (fdbg)
for (i = 0; i < wlen; i++)
fprintf(fdbg, "mlp_check_gradient(); leaving; perceptron->w[%d]: %f; "
"pw[%d]: %f; gw[%d]: %f\n", i, perceptron->w[i], i, pw[i], i, gw[i]);
}
/*
Free mlp struct. Returns (struct mlp *) 0.
*/
struct mlp *mlp_free(struct mlp *perceptron)
{
if (perceptron)
{
free(perceptron->npl);
free(perceptron->w);
free(perceptron->a);
free(perceptron->z);
free(perceptron->delta);
free(perceptron->fname);
free(perceptron);
}
return (struct mlp *) 0;
}
/*
Return deep copy of 'perceptron'.
*/
struct mlp *mlp_clone(struct mlp *perceptron)
{
int i;
struct mlp *clone = (struct mlp *) 0;
clone = calloc(1, sizeof(struct mlp));
if (clone != (struct mlp *) 0)
{
clone->d = perceptron->d;
clone->nlayers = perceptron->nlayers;
clone->npl = malloc(perceptron->nlayers*sizeof(int));
for (i = 0; i < perceptron->nlayers; i++)
clone->npl[i] = perceptron->npl[i];
clone->wlen = perceptron->wlen;
clone->w = malloc(perceptron->wlen*sizeof(float));
fvec_copy(clone->w, perceptron->w, perceptron->wlen);
clone->nodes = perceptron->nodes;
clone->a = malloc(perceptron->nodes*sizeof(float));
fvec_copy(clone->a, perceptron->a, perceptron->nodes);
clone->z = malloc(perceptron->nodes*sizeof(float));
fvec_copy(clone->z, perceptron->z, perceptron->nodes);
clone->delta = malloc(perceptron->nodes*sizeof(float));
fvec_copy(clone->delta, perceptron->delta, perceptron->nodes);
clone->noff = perceptron->noff;
clone->seed = perceptron->seed;
clone->range = perceptron->range;
clone->iterations = perceptron->iterations;
clone->p_iter = perceptron->p_iter;
clone->itmax = perceptron->itmax;
clone->error = perceptron->error;
clone->mce = perceptron->mce;
if (perceptron->fname)
clone->fname = strdup(perceptron->fname);
}
return clone;
}
/*
Calculate derivative of sigmoid function, _given value of sigmoid_
'g' (as opposed to being given input to sigmoid). This is obviously
faster.
*/
static float dsigmoid(float g)
{
float gprime;
gprime = g*(1.0-g);
return gprime;
}
/*
Generate target outputs for MLP, given input categorized in 'nc'
classes with 'nd[i]' samples in class i.
In case of malloc() failure, return NULL and set errno.
*/
float **mlp_target(int nc, int *nd)
{
int idx;
int jdx;
int nsamples;
int cthd;
int current_class;
float **t;
nsamples = ivec_sum(nd, nc);
t = fmx_alloc(nsamples, nc);
if (t)
{
cthd = nd[0];
current_class = 0;
for (idx = 0; idx < nsamples; idx++)
{
if (idx >= cthd)
cthd += nd[++current_class];
for (jdx = 0; jdx < nc; jdx++)
{
if (jdx == current_class)
t[idx][jdx] = MLP_TARGET_HIGH;
else
t[idx][jdx] = MLP_TARGET_LOW;
}
}
}
return t;
}
/*
Determine index of first (left-most) node within 'layer'.
*/
static int get_layer_index(int layer, int *npl)
{
int i;
int idx;
idx = 0;
for (i = 0; i < layer; i++)
idx += npl[i];
return idx;
}
/*
Determine index (within 'w' array) of weight from node 'j', which
is in 'layer', to first (left-most) node in next layer. 'd' is
number of inputs.
*/
static int get_weight_index(int j, int d, int layer, int *npl)
{
int i;
int iw;
/*
First, there are (d+1)*npl[0] weights before layer 0.
*/
iw = (d+1)*npl[0];
/*
Then, previous hidden layers.
*/
for (i = 0; i < layer; i++)
iw += (npl[i]+1)*npl[i+1];
/*
Current layer.
*/
iw += j-get_layer_index(layer, npl)+1;
return iw;
}
/*
Writer function for mlp_save(), mlp_write().
*/
static void mlp_fprintf(FILE *outfile, struct mlp *perceptron, int mode, int index, float weight)
{
int i;
int j;
int l;
int iw;
int inode;
int idx = 1;
float wt = 1.0;
float *w;
if (mode == MLP_MODE_APPEND)
{
idx = index;
wt = weight;
}
w = perceptron->w;
fprintf(outfile, "combined model / combined weight: %d / %f\n", idx, wt);
fprintf(outfile, "iterations: %d\n", perceptron->p_iter+perceptron->iterations);
if (perceptron->method == MLP_OPT_CGPLUS)
fprintf(outfile, "optimization method: %s\n", MLP_STR_CGPLUS);
else if (perceptron->method == MLP_OPT_GRADIENT_DESCENT)
fprintf(outfile, "optimization method: %s\n", MLP_STR_GRADIENT_DESCENT);
else if (perceptron->method == MLP_OPT_WNLIB)
fprintf(outfile, "optimization method: %s\n", MLP_STR_WNLIB);
else
fprintf(outfile, "optimization method: %s\n", MLP_STR_UNSPECIFIED);
fprintf(outfile, "seed: %d\n", perceptron->seed);
fprintf(outfile, "range of initial weights: %f\n", perceptron->range);
fprintf(outfile, "misclassified samples: %d\n", perceptron->mce);
fprintf(outfile, "avg. error per node and sample (MSE): %f\n", perceptron->error);
fprintf(outfile, "number of layers: %d\n", perceptron->nlayers);
for (i = 0; i < perceptron->nlayers; i++)
fprintf(outfile, "nodes in layer %d: %d\n", i, perceptron->npl[i]);
fprintf(outfile, "number of inputs: %d\n", perceptron->d);
fprintf(outfile, "number of weights: %d\n", perceptron->wlen);
iw = 0;
for (i = 0; i < perceptron->npl[0]; i++)
{
fprintf(outfile, "weights from inputs to node %3d: ", i);
for (j = 0; j <= perceptron->d; j++)
{
fprintf(outfile, "%f ", w[iw]);
iw++;
}
fprintf(outfile, "\n");
}
inode = 0;
for (i = 0; i < perceptron->nlayers-1; i++)
{
inode += perceptron->npl[i];
for (j = 0; j < perceptron->npl[i+1]; j++)
{
fprintf(outfile, "weights from layer %1d to node %3d: ", i, j+inode);
for (l = 0; l <= perceptron->npl[i]; l++)
{
fprintf(outfile, "%f ", w[iw]);
iw++;
}
fprintf(outfile, "\n");
}
}
/*
One final newline needed to make bagging MLP file more
readable.
*/
fprintf(outfile, "\n");
}
/*
Save multi-layer perceptron model described by 'perceptron' in
perceptron->fname. The model is written (mode == MLP_MODE_WRITE) or
appended (mode == MLP_MODE_APPEND) to the perceptron->fname file.
If mode == MLP_MODE_APPEND, prepend 'index'/'weight' to the model
saved, otherwise prepend the default values (1/1.0).
In case of success, return 0. In case of failure, return -1 and set
errno.
*/
int mlp_save(struct mlp *perceptron, int mode, int index, float weight)
{
int status;
FILE *outfile = (FILE *) 0;
status = 0;
if (perceptron->fname)
{
if (mode == MLP_MODE_WRITE)
outfile = fopen(perceptron->fname, "w");
else
outfile = fopen(perceptron->fname, "a");
if (outfile)
{
mlp_fprintf(outfile, perceptron, mode, index, weight);
fclose(outfile);
}
else
status = -1;
}
return status;
}
/*
File-pointer-based version of mlp_save().
In case of success, return 0. In case of failure, return -1 and set
errno.
*/
int mlp_write(FILE *fptr, struct mlp *perceptron, int mode, int index, float weight)
{
int status;
status = 0;
if (fptr)
{
mlp_fprintf(fptr, perceptron, mode, index, weight);
fflush(fptr);
}
else
status = -1;
return status;
}
/*
The function calculates gradient 'gw' of the criterion function for
MLP defined by 'parameters->perceptron'. The gradient is calculated
at point 'pw'.
Internally, the function copies 'pw' into
perceptron->w. perceptron->w is then used in calculations.
*/
static void mlp_gradient(double *gw, double *pw, void *parameters)
{
int i;
int j;
int k;
int n;
int cix;
int hix;
int wlen;
int layer;
int nlayers;
int c;
int d;
int idx;
int kdx;
int shift;
int iw;
int *npl;
float sum;
float zk;
float *z;
float *delta;
double *dwt; /* instance (single vector) derivatives */
float *wx;
float **t;
float **x;
struct mlp_parameters *mlp_params;
struct mlp *perceptron;
struct dataset *dset;
FILE *fdbg;
mlp_params = (struct mlp_parameters *) parameters;
perceptron = mlp_params->perceptron;
t = mlp_params->t;
dset = mlp_params->dset;
x = dset->x;
fdbg = mlp_params->fdbg;
fvec_dcopy(perceptron->w, pw, perceptron->wlen);
wx = perceptron->w;
d = perceptron->d;
nlayers = perceptron->nlayers;
c = perceptron->npl[nlayers-1];
npl = perceptron->npl;
wlen = perceptron->wlen;
delta = perceptron->delta;
z = perceptron->z;
dvec_set(gw, wlen, 0.0);
dwt = malloc(wlen*sizeof(double));
if (fdbg)
for (i = 0; i < wlen; i++)
fprintf(fdbg, "mlp_gradient(); perceptron->w[%d]: %12.5g\n", i, perceptron->w[i]);
cix = ivec_sum(npl, nlayers-1);
for (n = 0; n < dset->nv; n++)
{
/*
Calculate network for sample 'n'.
*/
calculate_network(perceptron, dset->x[n]);
/*
Calculate deltas.
Output nodes:
*/
for (k = cix; k < cix+c; k++)
{
zk = z[k];
delta[k] = zk*(1.0-zk)*(zk-t[n][k-cix]);
if (fdbg)
fprintf(fdbg, "mlp_gradient(); vector %d; output z[%d]: %12.5g; delta[%d]: %12.5g\n",
n, k, zk, k, delta[k]);
}
/*
Hidden nodes:
*/
idx = cix; /* in the following loop, idx is index of first (left-most) node in the current layer */
for (layer = nlayers-2; layer >= 0; layer--)
{
kdx = idx; /* index of first node in layer+1 */
idx -= npl[layer];
for (j = idx; j < idx+npl[layer]; j++)
{
sum = 0.0;
shift = npl[layer]+1;
/* iw is index of weight from node j to first node in next layer */
iw = get_weight_index(j, d, layer, npl);
for (k = kdx; k < kdx+npl[layer+1]; k++)
{
sum += wx[iw]*delta[k];
iw += shift;
}
delta[j] = dsigmoid(z[j])*sum;
}
}
/*
All deltas done. Calculate derivatives for sample 'n'.
First calculate derivatives with respect to weights before
layer 0 (i.e., weights connecting network inputs to layer 0).
*/
j = 0;
i = 0;
for (iw = 0; iw < (d+1)*npl[0]; iw++)
{
if ((iw%(d+1)) == 0) /* bias weight */
dwt[iw] = delta[j];
else
dwt[iw] = delta[j]*x[n][i++]; /* non-bias weight */
if (i == d)
{
i = 0;
j++;
}
}
/*
Hidden layer derivatives.
*/
layer = 0; /* 'from' layer */
j = 0; /* current 'from' node */
k = npl[0]; /* current 'to' node */
hix = 0; /* left-most node in 'layer' */
idx = 0; /* total number of weights in current layer */
for (iw = (d+1)*npl[0]; iw < wlen; iw++)
{
if ((idx%(npl[layer]+1)) == 0) /* bias weight */
dwt[iw] = delta[k];
else
dwt[iw] = z[j++]*delta[k]; /* non-bias weight */
if (j == hix+npl[layer])
{
j = hix;
k++;
}
idx++;
if (idx == (npl[layer]+1)*npl[layer+1]) /* next layer? */
{
hix += npl[layer];
j = hix;
layer++;
idx = 0;
}
}
/*
Update total derivatives.
*/
for (iw = 0; iw < wlen; iw++)
gw[iw] += dwt[iw];
}
#ifdef TESTING
/*
If the file is compiled with -DTESTING, true and approximate
gradient values are printed. The values should be very close;
examples (gw: actual gradient; dwt: approximate gradient):
gw[0]: -0.127655; dwt[0]: -0.127599
gw[1]: -0.939811; dwt[1]: -0.939935
gw[2]: -0.218290; dwt[2]: -0.218108
gw[3]: -1.097668; dwt[3]: -1.097396
*/
mlp_check_gradient(dwt, pw, parameters);
fprintf(stdout, "perceptron->w: weights; pw: point; gw: gradient; dwt: app. gradient\n");
for (iw = 0; iw < wlen; iw++)
fprintf(stdout, "perceptron->w[%d]: %f; pw[%d]: %f; gw[%d]: %f; dwt[%d]: %f\n",
iw, perceptron->w[iw], iw, pw[iw], iw, gw[iw], iw, dwt[iw]);
pwait();
#endif
if (fdbg)
for (iw = 0; iw < wlen; iw++)
fprintf(fdbg, "mlp_gradient(); leaving; perceptron->w[%d]: %f; pw[%d]: %f; "
"gw[%d]: %f; dwt[%d]: %f\n",
iw, perceptron->w[iw], iw, pw[iw], iw, gw[iw], iw, dwt[iw]);
free(dwt);
}
static void mlp_dgradient(double *pw, int n, double *gw, void *parameters, int *errc)
{
mlp_gradient(gw, pw, parameters);
}
/*
Calculate outputs for MLP defined in 'perceptron' for a given input
'x'. The function allocates space for the returned vector - it is
the caller's responsiblity to free() it.
*/
float *mlp_output(struct mlp *perceptron, float *x)
{
int i;
int nlayers;
int *npl;
int c;
int cix;
int iz;
float *z;
float *output;
nlayers = perceptron->nlayers;
npl = perceptron->npl;
c = perceptron->npl[nlayers-1];
output = malloc(c*sizeof(float));
z = perceptron->z;
cix = ivec_sum(npl, nlayers-1); /* cix is index of first output node */
calculate_network(perceptron, x);
iz = cix;
for (i = 0; i < c; i++)
output[i] = z[iz++];
return output;
}
/*
Testing function. Calculates derivatives dw[1..wlen]
numerically. The function has signature and semantics identical to
mlp_derivative().
*/
static void mlp_check_derivative(float *wt, float *dw, struct mlp *perceptron, float **x,
int nsamples, float **t, FILE *outdev, FILE *fdbg)
{
int i;
int n;
int wlen;
float epsilon;
float depsilon;
float error_plus;
float error_minus;
float factor;
float wx;
float w;
factor = 0.001;
wlen = perceptron->wlen;
for (i = 0; i < wlen; i++)
perceptron->w[i] = wt[i+1];
/*
This loop used for gdb debugging only.
error_plus = 0;
for (n = 0; n < nsamples; n++)
error_plus += mlp_check_criterion(perceptron, x[n], t[n]);
*/
/*
Calculate approximate derivatives by perturbing each weight in
turn. Use formula (4.47) from Bishop. Note that Numerical Recipes
function frprmn() requires that the derivatives be placed in
dw[1..wlen].
*/
for (i = 0; i < wlen; i++)
dw[i+1] = 0.0;
for (n = 0; n < nsamples; n++)
for (i = 0; i < wlen; i++)
{
wx = perceptron->w[i];
/*epsilon = wx*factor;*/
epsilon = factor;
depsilon = epsilon+epsilon;
w = wx+epsilon;
perceptron->w[i] = w;
error_plus = mlp_check_criterion(perceptron, x[n], t[n]);
w -= depsilon;
perceptron->w[i] = w;
error_minus = mlp_check_criterion(perceptron, x[n], t[n]);
dw[i+1] += (error_plus-error_minus)/depsilon;
perceptron->w[i] = wx;
}
}
/*
Return the number of MLPs in 'fptr'.
*/
static int get_nmlp(FILE *fptr, char *line, int llen)
{
int nmlp = 0;
while (fgets(line, llen+2, fptr))
if (strncmp(line, MLP_SIGNATURE_STRING, strlen(MLP_SIGNATURE_STRING)) == 0)
nmlp++;
rewind(fptr);
return nmlp;
}
/*
Convert mnemonic optimization algorithm name 'name' into numeric
code.
*/
static int convert_opt_method(char *name)
{
int code;
code = MLP_OPT_CGPLUS;
if (name)
{
if (strstr(name, MLP_STR_CGPLUS))
code = MLP_OPT_CGPLUS;
else if (strstr(name, MLP_STR_GRADIENT_DESCENT))
code = MLP_OPT_GRADIENT_DESCENT;
else if (strstr(name, MLP_STR_WNLIB))
code = MLP_OPT_WNLIB;
}
return code;
}
/*
Load one or more multi-layer perceptrons described in 'fname' into
(struct mlp **) array. The last element in the array is NULL. In
case of success, returns 0. In case of failure, returns -1 and sets
errno.
*/
struct mlp **mlp_load(char *fname)
{
int i;
int j;
int llen;
int nl;
int status;
int idx;
int imlp;
int nmlp;
int index;
float model_weight;
char *xtr;
char *str;
char **str_weights;
FILE *fptr;
struct mlp *perceptron = (struct mlp *) 0;
struct mlp **perceptrons = (struct mlp **) 0;
status = file_info(fname, &llen, (int *) 0, '\0');
if (status != -1)
{
fptr = fopen(fname, "r");
if (fptr != 0)
{
xtr = malloc(llen+2);
str = malloc(llen+2);
nmlp = get_nmlp(fptr, str, llen);
if (nmlp > 0)
{
status = 0;
perceptrons = calloc(nmlp+1, sizeof(struct mlp *));
if (!perceptrons)
status = -1;
for (imlp = 0; (imlp < nmlp) && (status == 0); imlp++)
{
perceptron = calloc(1, sizeof(struct mlp));
fscanf(fptr, "%40c%d/%f\n", xtr, &index, &model_weight);
fscanf(fptr, "%40c%d\n", xtr, &perceptron->p_iter);
fscanf(fptr, "%40c%s\n", xtr, str);
perceptron->method = convert_opt_method(str);
fscanf(fptr, "%40c%u\n", xtr, &perceptron->seed);
fscanf(fptr, "%40c%f\n", xtr, &perceptron->range);
fscanf(fptr, "%40c%d\n", xtr, &perceptron->mce);
fscanf(fptr, "%40c%f\n", xtr, &perceptron->error);
fscanf(fptr, "%40c%d\n", xtr, &perceptron->nlayers);
nl = perceptron->nlayers;
perceptron->npl = malloc(nl*sizeof(int));
perceptron->nodes = 0;
for (i = 0; i < nl; i++)
{
fscanf(fptr, "%40c%d\n", xtr, &perceptron->npl[i]);
perceptron->nodes += perceptron->npl[i];
}
perceptron->noff = perceptron->nodes-perceptron->npl[nl-1];
perceptron->a = malloc(perceptron->nodes*sizeof(float));
perceptron->z = malloc(perceptron->nodes*sizeof(float));
perceptron->delta = malloc(perceptron->nodes*sizeof(float));
fscanf(fptr, "%40c%d\n", xtr, &perceptron->d);
fscanf(fptr, "%40c%d\n", xtr, &perceptron->wlen);
perceptron->w = malloc(perceptron->wlen*sizeof(float));
idx = 0;
for (i = 0; i < perceptron->nodes; i++)
{
fgets(str, llen+2, fptr);
str_weights = str_tokenize(str+PCP_HFT, WHITESPACE);
j = 0;
if (str_weights)
{
while (str_weights[j])
{
perceptron->w[idx] = atof(str_weights[j]);
idx++;
j++;
}
str_free(str_weights);
}
}
perceptrons[imlp]= perceptron;
}
}
else
{
status = -1;
errno = PERR_UNRECOGNIZED_MLP;
}
vx_free(xtr);
vx_free(str);
if (!status)
status = fclose(fptr);
else
fptr_close(fptr);
if (status)
perceptrons = (struct mlp **) 0;
}
}
return perceptrons;
}
/*
The function calculates the value of error function for the
perceptron described by 'perceptron' at weight vector 'w'.
*/
static double mlp_function(double *w, void *parameters)
{
int i;
int status;
int cix;
int n;
int c;
int nlayers;
int iter;
int mce;
int class;
int mlp_class;
int nclass;
double dev;
int *npl;
float *z;
float **t;
struct mlp_parameters *mlp_params;
struct mlp *perceptron;
struct dataset *dset;
mlp_params = (struct mlp_parameters *) parameters;
mlp_params->errc = 0;
perceptron = mlp_params->perceptron;
dset = mlp_params->dset;
t = mlp_params->t;
/*
Copy weight vector 'w' to 'perceptron'. perceptron->w is then used
in calculations.
*/
for (i = 0; i < perceptron->wlen; i++)
perceptron->w[i] = w[i];
nlayers = perceptron->nlayers;
npl = perceptron->npl;
c = perceptron->npl[nlayers-1];
z = perceptron->z;
cix = ivec_sum(npl, nlayers-1); /* cix is index of first output node */
mce = 0;
class = 0; /* actual class of sample 'n' */
nclass = dset->nd[0]; /* number of samples in categories 0..class */
dev = 0.0;
for (n = 0; n < dset->nv; n++)
{
/*
Calculate network outputs for sample 'n'.
*/
calculate_network(perceptron, dset->x[n]);
/*
Determine predicted sample class (i.e., output node with maximum
output).
*/
mlp_class = fvec_argmax(&z[cix], c);
/*
Update cumulative error, defined as sum of squares of
differences between output and target errors across all
outputs and for all vectors.
TBD: using double precision 'dev' variable to calculate the total
deviation has a dramatic positive effect on the learning
performance. Strange, perhaps to be further investigated.
*/
dev += output_error(&z[cix], t[n], c);
/*
Update misclassification count.
*/
if (class != mlp_class)
mce++;
if ((n == nclass-1))
{
class++;
if (class < c)
nclass += dset->nd[class];
}
}
perceptron->error = dev/dset->nv;
perceptron->mce = mce;
iter = mlp_params->iteration;
if ((iter%10 == 0) || (perceptron->itmax == iter))
{
if (mlp_params->outdev)
fprintf(mlp_params->outdev,
"Epoch: %8d; avg. error per output node: %10.7f; error rate: %6.2f \n",
perceptron->p_iter+iter, perceptron->error/c, 100.0*perceptron->mce/dset->nv);
/*dev = dev/2.0;*/ /* The Great Bug - fixed 09/02/2004 */
status = mlp_save(perceptron, MLP_MODE_WRITE, 0, 0.0);
if (status == -1)
mlp_params->errc = errno;
}
perceptron->iterations = iter;
dev = 0.5*dev; /* The Great Bug fix */
return dev;
}
/*
Converter between the mlp_func typedef and mlp_function().
*/
static double mlp_dfunction(double *w, int n, int iter, void *parameters, int *errc)
{
struct mlp_parameters *mlp_params;
mlp_params = (struct mlp_parameters *) parameters;
mlp_params->iteration = iter;
return mlp_function(w, parameters);
}
/*
TBD: horrible kludge. Merge it with mlp_function() if it starts
working. ljb, 07/24/2004
*/
static float mlp_simplex_function(float *w, int n, int iteration, void *parameters, int *errc)
{
int i;
double *wd;
struct mlp_parameters *mlp_params;
mlp_params = (struct mlp_parameters *) parameters;
mlp_params->iteration = iteration;
wd = malloc(n*sizeof(double));
for (i = 0; i < n; i++)
wd[i] = w[i];
return mlp_function(wd, parameters);
free(wd);
}
static void set_parameters(struct mlp_parameters *parameters, struct dataset *dset,
float **t, struct mlp *mlp_perceptron, FILE *outdev,
FILE *fdbg)
{
parameters->iteration = mlp_perceptron->iterations;
parameters->dset = dset;
parameters->t = t;
parameters->perceptron = mlp_perceptron;
parameters->outdev = outdev;
parameters->fdbg = fdbg;
}
/*
Optimize weights of an MLP neural network using modified
Polak-Ribiere conjugate gradient method. 't' are 'dset->nv' target
output vectors. The function calculates the set of weights which
optimize the square error and stores them in mlp->w, and saves them
in mlp->fname. It periodically reports results on 'outdev'.
In case of success, return 0, otherwise return -1 and set error code
in 'errc'.
*/
int mlp_optimize(struct dataset *dset, float **t, struct mlp *perceptron,
FILE *outdev, int *errc, FILE *fdbg)
{
int iflag;
int iter;
int status;
int irest;
int method;
double eps;
double *vect;
struct mlp_parameters *parameters;
status = 0;
parameters = calloc(1, sizeof(struct mlp_parameters));
set_parameters(parameters, dset, t, perceptron, outdev, fdbg);
vect = double_clone(perceptron->w, perceptron->wlen);
eps = 1.0e-5;
method = 2; /* 1: FR; 2: PR; 3: PR+ */
irest = 1;
cgfam(perceptron->wlen, vect, mlp_dfunction, mlp_dgradient, irest, eps, method,
(void *) parameters, perceptron->itmax, &iter, &iflag, errc);
if (errc)
{
if (iflag == -3)
*errc = EINVAL;
else if (iflag == -2)
*errc = LERR_DESCENT;
else if (iflag == -1)
*errc = LERR_LNSEARCH;
else
*errc = 0;
}
if (errc && (*errc != 0))
status = -1;
free(vect);
free(parameters);
return status;
}
/*
Memory allocator for mlp_optimize_gradient_descent(). Return -1 in
case of failure.
*/
static int mlp_gradient_descent_alloc(struct mlp_parameters **parameters, int length,
double **gw, double **pw, double **deltaw)
{
int status;
status = -1;
*parameters = calloc(1, sizeof(struct mlp_parameters));
if (*parameters)
{
*gw = malloc(length*sizeof(double));
if (*gw)
{
*pw = calloc(length, sizeof(double));
if (*pw)
{
*deltaw = calloc(length, sizeof(double));
if (*deltaw)
status = 0;
}
}
}
return status;
}
/*
Optimize weights of an MLP neural network using gradient descent
algorithm defined by step size 'eta' and using momentum term defined
by 'mu'. 't' are 'dset->nv' target output vectors. The function
calculates the set of weights which optimize the square error and
stores them in mlp->w, and saves them in mlp->fname. It periodically
reports results on 'outdev'.
In case of success, return 0, otherwise return -1 and set error code
in 'errc'.
*/
int mlp_optimize_gradient_descent(struct dataset *dset, float **t, struct mlp *perceptron,
float eta, float mu, FILE *outdev, int *errc, FILE *fdbg)
{
int i;
int c;
int nlayers;
int status;
int iter;
double *gw;
double *pw;
double *deltaw;
struct mlp_parameters *parameters;
status = 0;
parameters = (struct mlp_parameters *) 0;
gw = (double *) 0;
pw = (double *) 0;
deltaw = (double *) 0;
if (dset && perceptron)
{
nlayers = perceptron->nlayers;
c = perceptron->npl[nlayers-1];
status = mlp_gradient_descent_alloc(¶meters, perceptron->wlen, &gw, &pw, &deltaw);
if (!status)
{
set_parameters(parameters, dset, t, perceptron, outdev, fdbg);
dvec_fcopy(pw, perceptron->w, perceptron->wlen);
iter = 0;
while (iter <= perceptron->itmax)
{
mlp_gradient(gw, pw, parameters);
if (mu != 0.0)
for (i = 0; i < perceptron->wlen; i++)
perceptron->w[i] += mu*deltaw[i];
for (i = 0; i < perceptron->wlen; i++)
perceptron->w[i] -= eta*gw[i];
if (mu != 0.0)
for (i = 0; i < perceptron->wlen; i++)
deltaw[i] = perceptron->w[i]-pw[i];
dvec_fcopy(pw, perceptron->w, perceptron->wlen);
mlp_function(pw, parameters);
iter++;
parameters->iteration = iter;
}
status = mlp_save(perceptron, MLP_MODE_WRITE, 0, 0.0);
}
else if (errc)
*errc = errno;
}
else if (errc)
*errc = errno;
free(parameters);
free(gw);
free(pw);
free(deltaw);
return status;
}
static int mlp_simplex_alloc(float **fval, float ***smx, int n)
{
int i;
int status;
float **mx;
status = 0;
mx = malloc((n+1)*sizeof(float *));
if (mx)
{
for (i = 0; (i < n+1) && !status; i++)
{
mx[i] = malloc(n*sizeof(float));
if (!mx[i])
status = -1;
}
if (!status)
{
*fval = malloc((n+1)*sizeof(float));
if (!*fval)
status = -1;
else
*smx = mx;
}
}
else
status = -1;
return status;
}
/*
Optimize weights of an MLP neural network using Simplex algorithm of
Nelder and Mead. 't' are 'dset->nv' target output vectors. The
function calculates the set of weights which optimize the square
error and stores them in mlp->w, and saves them in mlp->fname. It
periodically reports results on 'outdev'.
In case of success, return 0, otherwise return -1 and set error code
in 'errc'.
*/
static int mlp_optimize_simplex(struct dataset *dset, float **t, struct mlp *perceptron,
FILE *outdev, int *errc, FILE *fdbg)
{
int i;
int j;
int iter;
int status;
int ndim;
float ftol;
float lambda;
float *fval;
float **smx;
struct mlp_parameters *parameters;
status = 0;
ndim = perceptron->wlen;
ftol = 1e-6;
status = 0;
parameters = calloc(1, sizeof(struct mlp_parameters));
set_parameters(parameters, dset, t, perceptron, outdev, fdbg);
status = mlp_simplex_alloc(&fval, &smx, ndim);
if (!status)
{
for (i = 0; i < ndim; i++)
smx[0][i] = (2*perceptron->range*rand()/(RAND_MAX+1.0))-perceptron->range;
fval[0] = mlp_simplex_function(smx[0], ndim, 0, parameters, errc);
if (*errc == 0)
{
/*
Initialize the remaining ndim vertices of the
simplex, using equation (10.4.1) from Numerical
Recipes in C.
*/
lambda = 1.0; /* TBD: hardcoded for now. */
for (i = 1; (i < ndim+1) && (*errc == 0); i++)
{
for (j = 0; j < ndim; j++)
smx[i][j] = smx[0][j];
smx[i][i-1] += lambda;
fval[i] = mlp_simplex_function(smx[i], ndim, 0, parameters, errc);
}
simplex(smx, ndim, fval, mlp_simplex_function, (void *) parameters,
perceptron->itmax, ftol, &iter, errc);
if (errc && *errc)
status = -1;
}
else
status = -1;
}
free(parameters);
return status;
}
/*
Optimize weights of an MLP neural network using up to 'itmax'
iterations of 'opt_method' optimization method. 'x' are 'nsamples'
input vectors of length 'd' with 'nd[i]' samples per class 'i'. 't'
are 'nsamples' target output vectors. 'eta' and 'mu' are the step
size and momentum parameters for gradient descent optimization
(therefore used only if 'opt_method' is
MLP_OPT_GRADIENT_DESCENT). 'mlp_continue' is 0 to begin learning, 1
to continue starting with network in 'fname'. The function
calculates the set of weights which optimize the square error and
stores them in 'fname'. It periodically reports results on 'outdev'.
The possible values for opt_method are defined in mlp.h.
In case of success, the function returns 'mlp' structure which
contains the trained neural network and sets 'errc' to 0. In case of
error, return NULL and set 'errc', except for LERR_LNSEARCH (line
search failure in optimization routine) and LERR_DESCENT
(optimization procedure unable to find descent direction) errors. In
those cases, the function returns the trained network, but sets
'errc' to LERR_LNSEARCH or LERR_DESCENT. This is done because
optimization function often reports these errors, and yet finds a
decent minimum. The caller may choose to ignore these two error
codes.
*/
struct mlp *mlp_learn(int opt_method, float **x, int nsamples, int *nd, int d,
float **t, int nlayers, int *npl, int itmax, float range,
float eta, float mu, FILE *outdev, int mlp_continue,
char *fname, unsigned int seed, int *errc, FILE *fdbg)
{
int status;
int i;
int wlen;
int nodes;
int c;
float *w;
float *dw;
struct dataset *dset;
struct mlp *perceptron = (struct mlp *) 0;
struct mlp_parameters *parameters;
struct mlp **perceptrons;
c = 0;
status = 0;
if (errc)
*errc = 0;
/*
If begin learning, initialize 'perceptron' pseudo-randomly,
otherwise read it from 'fname'.
*/
if (mlp_continue == 1)
{
perceptrons = mlp_load(fname);
if (perceptrons)
{
perceptron = perceptrons[0];
perceptron->fname = strdup(fname);
perceptron->itmax = itmax;
wlen = perceptron->wlen;
w = malloc((wlen+1)*sizeof(float));
for (i = 0; i < wlen; i++)
w[i] = perceptron->w[i];
}
}
else
{
c = npl[nlayers-1];
perceptron = calloc(1, sizeof(struct mlp));
perceptron->itmax = itmax;
perceptron->method = opt_method;
if (fname && *fname)
perceptron->fname = strdup(fname);
wlen = (d+1)*npl[0];
for (i = 0; i < nlayers-1; i++)
wlen += (npl[i]+1)*npl[i+1];
/*
Allocate space for weights and derivatives vectors. Use 'wlen+1'
because 'w' and 'dw' are 1-based vectors, as required by frprmn()
API.
*/
w = malloc((wlen+1)*sizeof(float));
dw = malloc((wlen+1)*sizeof(float));
perceptron->d = d;
perceptron->nlayers = nlayers;
nodes = 0;
for (i = 0; i < nlayers; i++)
nodes += npl[i];
perceptron->nodes = nodes;
perceptron->npl = malloc(nlayers*sizeof(int));
for (i = 0; i < nlayers; i++)
perceptron->npl[i] = npl[i];
perceptron->a = malloc(nodes*sizeof(float));
perceptron->z = malloc(nodes*sizeof(float));
perceptron->delta = malloc(nodes*sizeof(float));
perceptron->range = range;
perceptron->seed = seed;
perceptron->wlen = wlen;
perceptron->w = malloc(wlen*sizeof(float));
for (i = 0; i < wlen; i++)
w[i] = (2*perceptron->range*rand()/(RAND_MAX+1.0))-perceptron->range;
for (i = 0; i < wlen; i++)
perceptron->w[i] = w[i];
if (fdbg)
for (i = 0; i < wlen; i++)
fprintf(fdbg, "mlp_learn(); perceptron->w[%d]: %12.5g\n", i, perceptron->w[i]);
}
if (outdev)
fprintf(outdev, "\n");
parameters = calloc(1, sizeof(struct mlp_parameters));
dset = dataset_lt(d, c, nd, nsamples, (char **) 0, x);
set_parameters(parameters, dset, t, perceptron, outdev, fdbg);
if (*errc != 0)
{
perceptron = mlp_free(perceptron);
status = -1;
}
else if (itmax > 0)
{
perceptron->method = opt_method;
if (opt_method == MLP_OPT_SIMPLEX)
status = mlp_optimize_simplex(dset, t, perceptron, outdev, errc, fdbg);
else if (opt_method == MLP_OPT_CGPLUS)
status = mlp_optimize(dset, t, perceptron, outdev, errc, fdbg);
else if (opt_method == MLP_OPT_GRADIENT_DESCENT)
status = mlp_optimize_gradient_descent(dset, t, perceptron, eta, mu, outdev,
errc, fdbg);
if (status == -1)
{
if (errc && (*errc != LERR_LNSEARCH) && (*errc != LERR_DESCENT))
perceptron = mlp_free(perceptron);
}
}
return perceptron;
}
/*
Accept input parameters from the user and pass them to MLP learning
function mlp_learn().
In case of error, set 'errc'. If error is file access error, set
'xname'.
*/
void p_mlp_learn(int *errc, char **xname, int *dbg)
{
int status;
int opt_method;
int mlp_continue;
int mlp_nlayers; /* mlp_nlayers */
int mlp_itmax; /* mlp_itmax */
float mlp_range; /* mlp_range */
float eta;
float mu;
int *mlp_npl; /* mlp_npl */
char *mlp_fname; /* mlp_fname */
float **t;
unsigned int mlp_seed;
struct mlp *perceptron;
FILE *outdev;
FILE *fdbg = (FILE *) 0;
*errc = 0;
if (tds)
{
outdev = stdout;
fflush(outdev);
if (*dbg > 0)
fdbg = fopen(PCP_DBG, "a");
t = mlp_target(tds->c, tds->nd);
mlp_continue = 0;
status = input_mlp(outdev, tds->d, tds->c, tds->nv, &mlp_continue, &mlp_nlayers,
&mlp_npl, &mlp_itmax, &mlp_range, &opt_method, &eta, &mu,
(int *) 0, 0, (int *) 0, &mlp_seed, &mlp_fname);
if (status == 0)
{
inverse_video();
srand(mlp_seed);
perceptron = mlp_learn(opt_method, tds->x, tds->nv, tds->nd, tds->d, t,
mlp_nlayers, mlp_npl, mlp_itmax, mlp_range, eta,
mu, outdev, mlp_continue, mlp_fname, mlp_seed,
errc, fdbg);
reset_video();
if (!perceptron)
status = -1;
else if (mlp_itmax != -1)
{
printf("\n");
print_line(stdout, "Done.", PCP_QLEN);
pwait();
}
}
if (status == -1)
{
*errc = errno;
*xname = strdup(mlp_fname);
}
}
else
*errc = PERR_UNDEFINED_TDS;
}
/*
Classify dataset 'dset' using MLP defined in 'fname', for dataset
'dset'. The MLP outputs are placed in the returned struct mlp, and
the classification results are placed in dset->cx. The function
assigns each vector to the class corresponding to the node with the
largest output, unless 'thd' is > 0. In that case, the assignment is
performed only if the largest node output exceeds 'thd'.
In case of error, return NULL and place error code in 'errc'.
*/
static struct mlp *recall(struct dataset *dset, float thd, char *fname, int *errc)
{
struct mlp **perceptrons;
struct mlp *perceptron;
*errc = 0;
perceptron = (struct mlp *) 0;
/*
Read MLP from 'fname' into mlp structure.
*/
perceptrons = mlp_load(fname);
if (perceptrons)
perceptron = mlp_predict(dset, perceptrons, thd, errc);
else
*errc = errno;
return perceptron;
}
/*
Classify dataset 'dset' using MLP defined in 'perceptrons'. The MLP
outputs are placed in the returned struct mlp, and the
classification results are placed in dset->cx. The function assigns
each vector to the class corresponding to the node with the largest
output, unless 'thd' is > 0. In that case, the assignment is
performed only if the largest node output exceeds 'thd'.
'perceptrons' is an array of 'struct mlp' objects. It can be
created, for example, from a file using calling mlp_load().
In case of error, return NULL and place error code in 'errc'.
*/
struct mlp *mlp_predict(struct dataset *dset, struct mlp **perceptrons, float thd, int *errc)
{
int i;
int j;
int nl;
int noff;
int imax;
int icx;
int nmlp;
int imlp;
int nout;
float cmax;
float *mlp_output;
struct mlp *perceptron;
perceptron = (struct mlp *) 0;
if (errc && perceptrons)
{
*errc = 0;
nmlp = 0;
while (perceptrons[nmlp])
nmlp++;
perceptron = perceptrons[0];
nl = perceptron->nlayers-1;
/*
Check that the number of inputs equals number of features in
'dset', and, in case the number of classes in dset is greater
than 1, that it equals the number of outputs in the network.
Otherwise the network and the dataset are incompatible.
If the number of classes in 'dset' is 1, it means that the
dataset is unlabeled, which is OK.
*/
if (((dset->c == 1) || ((dset->c > 1) && (perceptron->npl[nl] == dset->c))) &&
(perceptron->d == dset->d))
{
/*
For each MLP, calculate outputs and store results in dset->prediction.
*/
dset->prediction = malloc(dset->nv*sizeof(int));
if (dset->prediction)
{
noff = perceptron->noff;
nl = perceptron->nlayers-1;
nout = perceptron->npl[nl];
mlp_output = malloc(nout*sizeof(float));
for (i = 0; i < dset->nv; i++)
{
fvec_set(mlp_output, nout, 0.0);
for (imlp = 0; imlp < nmlp; imlp++)
{
calculate_network(perceptrons[imlp], dset->x[i]);
for (j = 0; j < nout; j++)
mlp_output[j] += perceptron->z[noff+j];
}
/*
For nmlp > 1 (MLP bagging classifier), voting is
done by finding average outputs from all nmlp
classifiers, then assigning the class
corresponding to the largest output. This is
consistent with the rule recommended in: Amanda
J. C. Sharkey (Ed.), Combining Artificial Neural
Nets, Chapter 3, Section 3.2.1. Springer, London,
1999.
*/
for (j = 0; j < nout; j++)
mlp_output[j] /= nmlp;
cmax = mlp_output[0];
imax = 0;
for (j = 1; j < nout; j++)
{
if (cmax < mlp_output[j])
{
imax = j;
cmax = mlp_output[j];
}
}
dset->prediction[i] = imax;
icx = dataset_class(i, teds->c, teds->nd);
}
free(mlp_output);
}
else
*errc = errno;
}
else
*errc = PERR_INCONSISTENT_MLP;
if (*errc)
{
for (i = 0; i < nmlp; i++)
mlp_free(perceptrons[i]);
free(perceptrons);
perceptron = (struct mlp *) 0;
}
}
else if (errc)
*errc = EINVAL;
return perceptron;
}
void p_mlp_predict(int *errc, char **xname)
{
int verbose;
int min_range;
int max_range;
int i;
float thd;
char *mlp_fname;
char *output_fname;
FILE *outdev;
FILE *fptr;
struct mlp *perceptron;
clear_screen();
outdev = stdout;
cursor_on();
mlp_fname = input_filename(PCP_UMSG_FNAME_MLP, PCP_MLP, outdev);
fptr = fopen(mlp_fname, "r");
if (fptr)
{
fclose(fptr);
output_fname = strdup(PCP_RCL);
fptr = fopen(output_fname, "w");
if (fptr)
{
thd = 0.0;
thd = input_float(stdin, outdev, PCP_UMSG_DTHD, PCP_QLEN,
&thd, (float *) 0, (float *) 0);
perceptron = recall(teds, thd, mlp_fname, errc);
if (perceptron)
{
min_range = 0;
max_range = 1;
verbose = input_integer(stdin, outdev, OUTPUT_MSG, PCP_QLEN,
&min_range, &min_range, &max_range);
/*
Store predictions in PCP_RCL.
*/
for (i = 0; i < teds->nv; i++)
write_rcl(fptr, i, teds, tds);
if (!fclose(fptr))
{
/*
Display results.
*/
predict_disp(teds, verbose, PALG_MLP);
pwait();
}
else
{
*errc = errno;
*xname = strdup(output_fname);
}
}
else
*xname = strdup(mlp_fname);
}
else
{
*errc = errno;
*xname = strdup(output_fname);
}
}
else
{
*errc = errno;
*xname = strdup(mlp_fname);
}
}
syntax highlighted by Code2HTML, v. 0.9.1