
Hi, Habr. I decided to write about the use of neural networks in a field that was not traditional for them: authentication. This lies outside the tasks of machine learning, and something that ML is trying to get rid of is encouraged here.
The minimum of the theory is the maximum of practice.
')
Interested? Then welcome under cat.
I will say straight away that for many terms I will write their English version, since I think that the majority, like me, are very new to their Russian versions.
In general, biometric authentication occurs in several stages:
I. The stage of learning.
1) Read biometric data
2) Transform and normalize data
3) Model training
Ii. Stage of use
1) Read biometric data
2) Transform and normalize data
3) Classification of the input vector into two classes: 0 - disable, 1 - allow.
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 proceed to the spherical horse in a vacuum:
Let a spherical horse in vacuum have a footprint 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. We take into account that the footprint of a horse can vary depending on various factors, so it is necessary to take into account the error.
We reduce the problem of biometric authentication to the problem of binary classification
Yes, yes, you did not hear. What is authentication in general? This is a response to the input either to allow authorization or not.
Therefore, our task is to classify the input vector of values ​​as 0 (forbid) or 1 (allow). Let me remind you, if someone does not remember / does not know - Feed-Forward NN with fully connected layers are ideal for binary classification.
I will use Keras as the simplest for designing neural networks. The full code will be at the end.
The function of creating a model 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 set the model to which the 2D tensor of dimension (None, 255) is fed to 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))
Output 1 is a neuron with sigmoid activation. As a result, we obtain 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 an optimizer, simply because I like it.
Who is not familiar with the principles of fuzzy logic - instead of true false, values ​​between 0 and 1 are used there, and coercion to clear logic takes place through defuzzification - special functionsAs 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 add zeroes to them to get 255 bytes. But because of the specific nature of the training, this will be non-comme il faut, because the neural network will begin to return a very close value for 1 for strings of “similar” length, due to rotten zeros. Therefore, we simply repeat the line n-times to the size of 255, and then divide each byte by 255.
Why share? The ASCII string is encoded by bytes [0; 255], we also need to bring the input data to [0; 1], so we divide it 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 normal vector, but in numpy it has a special meaning since in the future we will combine these vectors into a 2D tensor along the vertical axis.
Learning set
As for the phrase about the association, you probably already guessed, we need more examples for training, which will not be the prints of the registered user (horse). To do this, we will generate randomized, already 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 of 1000x255 in size (1000 255-dimensional vectors) with uniformly distributed values ​​[0; 1], connect to the fingerprint, and obtain a tensor that will be passed to the model for training. It consists of 1000 vectors that are not the user's fingerprint and one such.
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
begins the complete discrepancies with the ML practices . The fact is that ordinary classifiers are trained on a training set, validated on a validating and final test on a test sample. There will only be a training set. Yes! And the most unusual thing is that so hated overfitting in neural networks will be our main chip. We need to achieve such a level of overfitting at which the model will memorize our footprint and will return values ​​close to it only with the smallest discrepancies. To do this, we will not break into batch, take 1k epochs and run.
In other words, in classical problems the NA is used for generalization (generalization), and in our case it is used for memorialization (fitting). Our task is not to achieve a high level of generalization, but to make the neuron work as a dictionary that matches keys with values, but accepts inaccurate keys. This will be our authentication with an error that is fundamental to biometric systems.
train_x, train_y = GetTrainTensor(GetString()) model = build_model(train_x) model.fit(train_x, train_y, epochs=1000, )
Testing
I will say straight away that occasionally on some of the prints of the National Assembly does not converge, therefore it is necessary to make a dynamic selection of global parameters, but this will be in the following parts, until it comes down to it. Despite the fact that already after the 500th epoch we have accuracy 1, we need the smallest loss, for sufficient overfitting.
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
We see that the initial value returns full compliance, small deviation mutations (sometimes it is 100% certain that it is the same). Completely alien prints give values ​​close to zero.
Conclusion
It's time for a conclusion. Today, we have implemented almost all stages except for collecting prints. Now it's up to you to calculate the imprint, or the iris, or the
composition of the feces and convert it into a vector. Authentication with the error you already know how.
Thank you all for your attention.
PS I forgot the code;)
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])