# Neuroauthentication: An Introduction to Biometric Authentication

Hi, Habr. I decided to write about the use of neural networks in a completely non-traditional sphere: authentication. This is beyond the scope of machine learning, and what ML is trying to get rid of is encouraged.

The minimum of theory is the maximum of practice.

Interested in? Then welcome to cat.

I must say right away that for many terms I will write their English version, since I think that most, like me, are very new to their Russian versions.

In general, biometric authentication takes place in several stages:

I. Stage of training.

1) Reading biometric data

2) Converting and normalizing data

3) Training model

II. Stage of use

1) Read biometric data

2) Conversion and normalization of data

3) Classification of the input vector into two classes: 0 - disable, 1 - enable.

The chip itself in the third paragraphs. Therefore, at the moment, we omit the reading of biometric data (this is the task of special devices) and move on to the spherical horse in vacuum:

Let the spherical horse in vacuum have an imprint in the form of a string with a length in the range from 0 to 255. Our task will be to authenticate the spherical horse. Consider that the imprint of the horse may vary depending on various factors, so we must take into account the error.

## We reduce the biometric authentication problem to the binary classification problem

Yes, yes, you did not hear. What is authentication in general? This is a response to the input either allow authorization or not.

Therefore, our task is to classify the input vector of values by 0 (disable) or 1 (enable). Let me remind you, if someone doesn’t remember / don’t know, Feed-Forward NN with fully connected layers is ideal for binary classification.

I will use Keras as the simplest for the design of neural networks. The full code will be at the end.

The model creation function will look like this:

```
def build_model(x):
model = models.Sequential()
model.add(layers.Dense(64, activation='relu', input_shape=(255,)))
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
opt = optimizers.Adam()
model.compile(optimizer=opt,
loss='binary_crossentropy',
metrics=['accuracy'])
return model
```

We specified a model that receives a 2D tensor of dimension (None, 255) at the input (according to the TensorFlow notation). In other words, an array of vectors with biometric characteristics.

The model has two hidden layers with 64 neurons in each and relu activation (relu (x) = max (0, x))

At the output, 1 neuron with sigmoid activation. As a result, we get the value [0; 1]. Deviation from 1 will be our error, which will have a threshold after which we will take the value as 0. We will select Adam as the optimizer, simply because I like it.

*Those who are not familiar with the principles of fuzzy logic - there, instead of true false, values between 0 and 1 are used, and the reduction to clear logic occurs through defuzzification - special functions*

As a loss function, binary crossentropy, this function returns a classification error. The task of the adam optimizer will be to adjust the weights of the neural network so as to minimize the value of the loss function

## Data normalization

So, we agreed that the imprint of a spherical horse is a string of arbitrary size, less than 256 characters. Therefore, we need to normalize the input string to the 255th vector. The easiest way is to take the bytes of the string and append them to zero to get 255 bytes. But due to the specifics of training, this will be non-comme il faut, because the neural network will begin to return a very close value to 1 for strings of “similar” length, due to filthy zeros. Therefore, we simply repeat the string n-times to size 255, and then divide each byte by 255.

Why divide? ASCII string is encoded with bytes [0; 255], but we need to bring the input data to [0; 1], therefore, divide by the upper limit.

```
def GetString():
def f(inp, i):
if (len(inp) - 1) > i:
return inp[i]
else:
return f(inp, i - len(inp))
input_value = bytearray()
input_value.extend(map(ord, str(input("Passphrase> "))))
assert len(input_value) <= 255, 'Maximum string length exeeded'
return np.reshape(np.array([f(input_value, i) for i in range(255)], dtype=np.float32) / 255., (1, 255))
```

The function returns the entered string normalized to a 255-dimensional vector, where each element lies in the range from 0 to 1. Pay attention to (1, 255) - this is a 1x255 matrix - similar to a regular vector, but in numpy this representation is of particular importance, since in the future we will combine these vectors into a 2D tensor along the vertical axis.

## Training sample

As you probably guessed by the phrase about the association, we need more training examples that will not be fingerprints of the registered user (horse). To do this, we will generate randomized normalized sequences and combine them with the fingerprint.

```
def GetTrainTensor(input_value):
x = np.append(np.random.uniform(size=(1000, 255)),
input_value,
axis=0)
y = np.append(np.array( [[0] * (x.shape[0] - 1)], dtype=np.float32), [1])
return x, np.reshape(y, (y.shape[0], 1))
```

In this code, we create a matrix with a size of 1000x255 (1000 255-dimensional vectors) with uniformly distributed values [0; 1], connect it with the fingerprint and get a tensor that we will pass to the model for training. It consists of 1000 vectors that are not the fingerprint of the user and one.

In the same code, we create labels for our prints with a dimension of 1001x1, obviously, the first thousand are zeros and only one unit.

## Training

And here then the

**complete discrepancy begins with the practices adopted in ML**. The fact is that ordinary classifiers train on a training sample, validate on a validating and final test on a test. There will be only a training sample. Yes! And the most unusual thing is that so hated overfitting in neural networks will be just our main chip. We need to achieve a level of overfitting at which the model memorizes our fingerprint and will return values close to it only with the most insignificant discrepancies. To do this, we will not break into batch, take 1k eras and run.

In other words, in classical problems, the NS is used for generalization (generalization), and for us in memorization (fitting). Our task is not to achieve a high level of generalization, but to make the neuron work like a dictionary that maps keys to values, but accepts inaccurate keys. This will be our authentication with an error, basic in biometric systems.

```
train_x, train_y = GetTrainTensor(GetString())
model = build_model(train_x)
model.fit(train_x,
train_y,
epochs=1000,
)
```

## Testing

I must say right away that sometimes the NS does not converge on some fingerprints, so you need to make a dynamic selection of global parameters, but more on that in the following parts, until it does. Despite the fact that after the 500th era we have accuracy 1, we need the smallest loss, for sufficient overfitting. We see that the initial value returns full compliance, small deviation mutations (sometimes 100% certainty that the same thing). Absolutely alien prints give values close to zero.

`Passphrase> password_konyasha_v_vacuume`

Epoch 1/1000

1001/1001 [==============================] - 0s - loss: 0.0712 - acc: 0.9870

Epoch 2/1000

1001/1001 [==============================] - 0s - loss: 0.0082 - acc: 0.9990

... а тут уже используем модель обученную

Epoch 998/1000

1001/1001 [==============================] - 0s - loss: 1.0002e-07 - acc: 1.0000

Epoch 999/1000

1001/1001 [==============================] - 0s - loss: 1.0002e-07 - acc: 1.0000

Epoch 1000/1000

1001/1001 [==============================] - 0s - loss: 1.0002e-07 - acc: 1.0000

Passphrase> password_konyasha_v_vacuume

1.0

Passphrase> paSsword_konyasha_v_vacuume

0.999857

Passphrase> password_koNyaSHa_v_vacuume

0.999999

Passphrase> pasSwOrD

3.85486e-16

Passphrase> password_KonAyASha_v_vaaacuuume

4.14147e-15

Passphrase> passw0rd_KoNyAsHa_V_vacuum3

1.0

Passphrase> test test

2.29619e-11

Passphrase> nothing

4.83392e-20

Passphrase> blablabla_konyashka_hehe

2.20884e-21

## Conclusion

The time has come for the conclusion. Today we have implemented almost all the steps except collecting prints. Now it’s up to the small thing - to count the imprint, or the iris of the eye, or the

Thank you all for your attention.

PS But I forgot the code;)

**The code**

```
import numpy as np
from keras import models
from keras import layers
from keras import optimizers
import matplotlib.pyplot as plt
def GetString():
def f(inp, i):
if (len(inp) - 1) > i:
return inp[i]
else:
return f(inp, i - len(inp))
input_value = bytearray()
input_value.extend(map(ord, str(input("Passphrase> "))))
assert len(input_value) <= 255, 'Maximum string length exeeded'
return np.reshape(np.array([f(input_value, i) for i in range(255)], dtype=np.float32) / 255., (1, 255))
def GetTrainTensor(input_value):
x = np.append(np.random.uniform(size=(1000, 255)),
input_value,
axis=0)
y = np.append(np.array( [[0] * (x.shape[0] - 1)], dtype=np.float32), [1])
return x, np.reshape(y, (y.shape[0], 1))
train_x, train_y = GetTrainTensor(GetString())
def build_model(x):
model = models.Sequential()
model.add(layers.Dense(64, activation='relu', input_shape=(255,)))
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
opt = optimizers.Adam()
model.compile(optimizer=opt,
loss='binary_crossentropy',
metrics=['accuracy'])
return model
model = build_model(train_x)
model.fit(train_x,
train_y,
epochs=1000,
)
for i in range(20):
print(model.predict(GetString())[0][0])
```

Only registered users can participate in the survey. Please come in.

## Need to continue?

**85.7%**Yes 48**14.2%**No 8