⭐ If you would like to buy me a coffee, well thank you very much that is mega kind! : https://www.buymeacoffee.com/honeyvig Hire a web Developer and Designer to upgrade and boost your online presence with cutting edge Technologies
Showing posts with label conv-neural-network. Show all posts
Showing posts with label conv-neural-network. Show all posts

Sunday, April 24, 2022

PyTorch Iterative FGVM: Targeted Adversarial Samples for Traffic-Sign Recognition

 

Inspired by the progress of driverless cars and by the fact that this subject is not thoroughly discussed I decided to give it a shot at creating smooth targeted adversarial samples that are interpreted as legit traffic signs with a high confidence by a PyTorch Convolutional Neural Network (CNN) classifier trained on the GTSRB dataset.

I’ll be using the Fast Gradient Value Method (FGVM) in an iterative manner - which is also called the Basic Iterative Method (BIM). I noticed that most articles only present PyTorch code for non-targeted Fast Gradient Sign Method (FGSM) - which performs well in evading classifiers but is, in my opinion, somehow limited.

I’ll try to discuss in this article only the important aspects of this problem. However, I also prepared a Google Colab Notebook which includes complete source code and results.
V

Targeted Network

For this experiment, I’ve constructed a basic LeNet5 inspired CNN in PyTorch. It performs 2 convolutions of size 5x5 on 32x32 grayscale images, separated by max-pooling. The dataset is slightly unbalanced, but this was compensated for during the training process.

This network is represented using the following PyTorch snippet:


class LeNet(nn.Module):
  def __init__(self, num_classes=47, affine=True):

      super().__init__()
      self.conv1 = nn.Conv2d(1, 32, 5)
      self.in1 = nn.InstanceNorm2d(32, affine=affine)

      self.conv2 = nn.Conv2d(32, 64, 5)
      self.in2 = nn.InstanceNorm2d(64, affine=affine)
      
      self.fc1 = nn.Linear(64 * 5 * 5, 256)
      self.fc2 = nn.Linear(256, 128)
      self.fc3 = nn.Linear(128, num_classes)


  def forward(self, x):
      out = F.relu(self.in1(self.conv1(x)))
      out = F.max_pool2d(out, 2)

      out = F.relu(self.in2(self.conv2(out)))
      out = F.max_pool2d(out, 2)
      
      out = out.view(out.size(0), -1)
      
      out = F.relu(self.fc1(out))
      out = F.relu(self.fc2(out))
      out = self.fc3(out)

      return out

The architecture is not optimal for the sake of simplicity; additionally, achieving state-of-the-art traffic-sign recognition is not in the scope of this article. Evaluation results on the GTSRB testing set are as follows:

  • Accuracy: ~95%
  • Precision: ~93%
  • Recall: ~93%

Targeted Adversarial Samples with Iterative FGVM

When training a neural network the focus is on optimizing parameters (i.e. weights) in order to minimize the loss (e.g.: Mean Squared Error, Cross Entropy, etc.) between the current output and desired output while the inputs are fixed. This is done through gradient descent. As an example, if a neural network models the function below, the

(weight) and

(bias) variables are adjusted during the training.

When talking about targeted FGVM,

and are fixed and the input

is adjusted through gradient descent (computed w.r.t. different variables, obviously). Usually this implies minimizing the error between the targeted adversarial output and the current output - basically shifting the current output towards the targeted output.

Moreover, when the input is in image-format, additional constraints must be addressed:

  • images (inputs) must be clamped between 0 and 1 (float representation)
  • images must be smooth in order to mitigate basic noise filtering mechanisms

PyTorch: Generating Adversarial Samples

The code I ended up with is posted below; further implementation details will also be presented.


targeted_adversarial_class = torch.tensor([INV_TRAFFIC_SIGNS_LABELS['stop']])
adversarial_sample = torch.rand((1, 1, 32, 32)).requires_grad_() 

# optimizer for the adversarial sample
adversarial_optimizer = torch.optim.Adam([adversarial_sample], lr=1e-3)

for i in range(10000):

  adversarial_optimizer.zero_grad()

  prediction = net(adversarial_sample)
  
  # classification loss + 0.05 * image smoothing loss
  loss = torch.nn.CrossEntropyLoss()(prediction, targeted_adversarial_class) + \
          0.05*((torch.nn.functional.conv2d(torch.nn.functional.pad(adversarial_sample, (1,1,1,1), 'reflect'), torch.FloatTensor([[[0, 0, 0], [0, -3, 1], [0, 1, 1]]]).view(1,1,3,3))**2).sum())
  

  # this is the predicted class number
  predicted_class = np.argmax(prediction.detach().numpy(), axis=1)

  # updates gradient and backpropagates errors to the input
  loss.backward()
  adversarial_optimizer.step()

  # ensuring that the image is valid
  adversarial_sample.data = torch.clamp(adversarial_sample.data, 0, 1)

  if i % 500 == 0:
    plt.imshow(adversarial_sample.data.view(32, 32), cmap='gray')
    plt.show()

    print('Predicted:', TRAFFIC_SIGNS_LABELS[predicted_class[0]])
    print('Loss:', loss)

The current CNN is trained on 32x32 grayscale images so it makes sense to start with an adversarial sample of same size which consists of random noise distributed over one channel. It is also required to indicate through requires_grad_() that this variable should be updated by Autograd.


adversarial_sample = torch.rand((1, 1, 32, 32)).requires_grad_() 

Next, an optimizer is created that instead of tweaking weights will tweak the adversarial_sample defined above:


adversarial_optimizer = torch.optim.Adam([adversarial_sample], lr=1e-3)

The loss function is defined using torch.nn.CrossEntropyLoss() - which is the same criterion used for training. In this example, I’ll try to create a sample that is classified as a stop sign (targeted_adversarial_class).


targeted_adversarial_class = torch.tensor([INV_TRAFFIC_SIGNS_LABELS['stop']])

prediction = net(adversarial_sample)

# classification loss
loss = torch.nn.CrossEntropyLoss()(prediction, targeted_adversarial_class)

This loss function does well in generating adversarial images but the results have a noisy aspect (e.g., powerful contrasts between small groups of pixels) and might look suspicious. Since this noise can be easily removed using basic filtering, smooth images are wanted.

Defining a smooth-image constraint can be done by minimizing the Mean Squared Error between adjacent pixels. Think of it as applying an edge-detection filter and attempting to minimize the overall result. However, this has an impact on the efficiency of the generated sample as it adds dependencies between pixels. To minimize the loss of freedom, only the adjacent pixels from the bottom-right side are taken into account. The following 3x3 convolution kernel is used to determine the color difference between a pixel and its 3 other neighbors:

K    
0 0 0
0 -3 1
0 1 1

In PyTorch, I implemented the aforementioned method using torch.nn.functional.conv2d() and torch.nn.functional.pad():


# image smoothing loss
loss += (torch.nn.functional.conv2d(torch.nn.functional.pad(adversarial_sample, (1,1,1,1), 'reflect'), torch.FloatTensor([[[0, 0, 0], [0, -3, 1], [0, 1, 1]]]).view(1, 1, 3, 3))**2).sum()

Finally, the image is clamped to create a valid float tensor using:


adversarial_sample.data = torch.clamp(adversarial_sample.data, 0, 1)

Multiple iterations are required in order to properly optimize the input.

Conclusions

FGVM proves reliable in crafting smooth targeted adversarial samples for basic classifiers implemented with CNNs. However, additional problems need to be addressed in order to become a feasible attack. The crafted sample must be picked up by the segmentation algorithm as a possible traffic sign in the detection phase. Next, the adversarial sample’s efficiency should not be impacted by small affine transformations (e.g., being shifted 3 pixels to the left) - this might be fixed through data augmentation. Additionally, factors such as brightness, contrast or various camera properties can still reduce the success rate of an adversarial sample.

Finally, samples which are more resistant to uniformly distributed noise can be obtained by removing the image smoothing constraint.

 

 

 

Improving Tesseract 4's OCR Accuracy through Image Preprocessing

 

In this work I took a look at Tesseract 4’s performance at recognizing characters from a challenging dataset and proposed a minimalistic convolution-based approach for input image preprocessing that can boost the character-level accuracy from 13.4% to 61.6% (+359% relative change), and the F1 score from 16.3% to 72.9% (+347% relative change) on the aforementioned dataset. The convolution kernels are determined using reinforcement learning; moreover, to simulate the lack of ground truth in realistic scenarios, the training set consists of only 30 images while the testing set includes 10,000.

The dataset in cause is called Brno Mobile, and contains colored photographs of typed text, taken with handheld devices. Factors such as blurriness, low resolution, contrast, brightness are contributing to making the images challenging for an OCR engine.

Resized image from the Brno dataset which contains text that was not recognized by Tesseract 4 during the evaluation (an empty string was returned)

During this experiment, the out of the box version of Tesseract 4 has been used, which implies:

  • no retraining of the OCR engine
  • no lexicon / dictionary augmentations
  • no hints about the language used in the dataset
  • no hints about segmentation methods; default (automatic) segmentation is used
  • default settings for the recognition engine (LSTM + Tesseract)

Problem Analysis

Tesseract 4 has proven great performance when tested on favorable datasets by achieving good balance between precision and recall. It is presumed that this evaluation is performed on images that resemble scanned documents or book pages (with or without additional preprocessing) in which the number of camera-caused distortions is minimal. Tests on the Brno dataset led to much worse performance that will be discussed later in the article.

In the above figure, a high precision indicates favorable True-Positives to False-Positives ratio thus revealing proper differentiation between characters (i.e. a relatively small number of misclassifications). Despite this, almost no improvements in recall can be observed when switching from the base classification method to the Long Short-Term Memory (LSTM) based Convolutional Recurrent Neural Network (CRNN) for sequence to sequence mapping.

“Despite being designed over 20 years ago, the current Tesseract classifier is incredibly difficult to beat with so-called modern methods.” - Ray Smith, author of Tesseract

I assume that further training for different fonts might not provide significant improvements and neither will a different model of classifier. Is there a chance that the classifier doesn’t receive the correct input?

It was pointed out in a previous article that Tesseract is not robust to noise; certain salt-and-pepper noise patterns disrupt the character recognition process, leading to large segments of text being completely ignored by the OCR engine - the infamous empty string. From empirical observations, these errors seem to occur either for a whole word or sentence or not at all thus suggesting a weakness in the segmentation methodology.

The existence of similar behavior, given images which present more natural distortions, is questioned - hence this experiment.

Black-box Considerations

Since analyzing Tesseract’s segmentation methods is a daunting task, I opted for an adaptive external image correction method. To avoid diving into Tesseract 4’s source code, the OCR engine is considered a black-box; in this case, an unsupervised learning method must be employed. This ensures easier transitions to other OCR engines as it doesn’t directly rely on concrete implementations but only on outputs - at the cost of processing power and optimality.

Proposed Solution

The solution consists in directly preprocessing images before they are fed to Tesseract 4. An adaptive preprocessing operation is required, in order to properly compensate for any image features that cause problems in the segmentation process. In other words, an input image must be adapted so it complies with Tesseract 4’s preferences and maximizes the chance of producing the correct output, preferably without performing down-sampling.

I choose a convolution-based approach for flexibility and speed; other articles tend to perform more rigid image adjustments (such as global changes in brightness, fixed-constant conversion to grayscale, histogram equalization, etc.). I preferred an approach that can properly learn to highlight or mask regions of the image according to various features. For this, the kernels are optimized using reinforcement learning using an actor-critic model. To be more specific, it relies on Twin Delayed Deep Deterministic Policy Gradient (TD3 for short), for discovering features which minimize the Levenshtein distance between the recognized text and the ground truth. I’ll not dive into implementation details of TD3 here as it would be somehow out of scope but think of it as a method of optimizing the following formula:

Where

is a kernel, and

is a tuple from the training set.

A short (simpler) proof of concept of the convolutional preprocessor is presented in this Google Colab. It uses a different architecture than the final one and has the purpose of verifying if the idea of using convolutions is feasible and offers good results. A comparison is presented between original and preprocessed images including recognized texts for each sample.

The final model is illustrated below, with ReLU activations after each convolution to capture nonlinearities and prevent having negative values as pixels’ colors.

To properly compensate for image coloring and reduce the number of channels (R, G, B), 1x1 convolutions are used. This prevents overfitting up to a point while also ensuring grayscale output. Further convolutions are applied only on the grayscale image.

Symmetry constraints are additionally enforced for each 3x3 kernel in order to minimize the number of trainable parameters and avoid overfitting. This means that for a 3x3 kernel only 6 variables out of 9 must be determined while the rest can be generated through mirroring. Below are the values I got for the five kernels (bold to emphasize symmetry):

#1 #2     #3    
0.7 0.2573 -0.3 0.3 0.3 -0.2996 0.3
1.3 0.3 1.3 -0.295 0.3 1.2949 0.3
1.3 0.2573 -0.3 0.3 -0.2802 0.2922 -0.2802

 

Comparison

I used 10,000 images from the testing set for the evaluation of the current methodology and compiled the following graphs. The differences between original and preprocessed samples are illustrated with three metrics of interest: Character Error Rate (CER), Word Error Rate (WER) and Longest Common Subsequence Error (LCSE). In this article, LCSE is computed as follows:

Additionally, I plotted everything in histogram format to properly see the distributions of errors. For CER and WER, it is easy to observe the spikes around 1 (100%) that suggest the aforementioned segmentation problem (at block-of-text level) produces the most frequent error (empty strings are returned so all characters are wrong). In certain situations, the WER is larger than 1 because the preprocessing step introduces artifacts near the border of the image thus leading to recognition of non-existent characters. When looking at the LCSE plot, a distribution shift can be seen from the original approximately gaussian shape with its peak (mode) near the average number of characters in an image (56.95) to a more favorable shape with overall lower error rates.

A numeric comparison is presented below:

Metric Original (Avg.) Preprocessed (Avg.)
CER 0.866 0.384
WER 0.903 0.593
LCSE 48.834 24.987
Precision 0.155 0.725
Recall 0.172 0.734
F1 Score 0.163 0.729

Takeaways

Significant improvements can be observed through this preprocessing operation. Moreover, the majority of errors probably do not occur in the sequence to sequence classifier (since all the recognized characters are erroneous and would contradict previous performance analysis). A page-segmentation issue when automatic mode is used seems more plausible. It is shown that an array of convolutions is sufficient, in this case, to decrease error rates substantially.

The OCR performance on the preprocessed images is overall better but not good enough to be reliable. A 38% character error rate is still a large setback. I’m pretty sure that better recognitions can be obtained with more fine-tuning, a more complex architecture for the convolutional preprocessor and a more diverse training set. However, the current implementation is already very slow to train which makes me question if the entire methodology is feasible from this point of view.

Cite

If you found this relevant to your work, you can cite the article using:


@article{sporici2020improving,
  title={Improving the Accuracy of Tesseract 4.0 OCR Engine Using Convolution-Based Preprocessing},
  author={Sporici, Dan and Cușnir, Elena and Boiangiu, Costin-Anton},
  journal={Symmetry},
  volume={12},
  number={5},
  pages={715},
  year={2020},
  publisher={Multidisciplinary Digital Publishing Institute}
}

 

#4     #5    
-0.2793 0.2395 0.2885 -0.294 -0.2905 -0.2939
0.2395 0.7119 0.3 0.3 1.162 -0.2905
0.28850.3-0.2828-0.23280.3-0.294