Как я могу обучить нейронную сеть OCR?
Для моего финального проекта APCS я подаю заявку, которая:
- позволяет пользователю рисовать цифры на панели рисования;
- масштабирует / переводит каждый штрих (представленный списком координат xy) в 100x100;
- создает изображение из масштабированного штриха;
- создает двоичный двумерный массив из этого изображения (0 для белого, иначе 1);
- и передает этот двоичный массив объекту нейрона для распознавания символов.
Следующий класс представляет нейрон:
import java.awt.*;
import java.util.*;
import java.io.*;
public class Neuron
{
private double[][] weights;
public static double LEARNING_RATE = 0.01;
/**
*Initialize weights
*Assign random double values to weights
*/
public Neuron(int r, int c)
{
weights = new double[r][c];
PrintWriter printer = null;
try
{
printer = new PrintWriter("training.txt");
}
catch (FileNotFoundException e) {};
for (int i = 0; i < weights.length; i++)
{
for (int j = 0; j < weights[i].length; j++)
{
weights[i][j] = 2 * Math.random() - 1; //Generates random number between -1 and 1
if (j < weights[i].length - 1)
printer.print(weights[i][j] + " ");
else
printer.print(weights[i][j]);
}
printer.println();
}
printer.close();
}
public Neuron(String fileName)
{
File data = new File(fileName);
Scanner input = null;
try
{
input = new Scanner(data);
}
catch (FileNotFoundException e)
{
System.out.println("Error: could not open " + fileName);
System.exit(1);
}
int r = Drawing.DEF_HEIGHT, c = Drawing.DEF_WIDTH;
weights = new double[r][c];
int i = 0, j = 0;
while (input.hasNext())
{
weights[i][j] = input.nextDouble();
j++;
if (j > weights[i].length - 1)
{
i++;
j = 0;
}
}
for (double[] a : weights)
System.out.println(Arrays.toString(a));
}
/**
*1. Initialize a sum variable
*2. Multiply each index of weights by each index of bin
*3. Sum these values
*4. Return the activated sum
*/
public int feedforward(int[][] bin) //bin represents 2D array of binary values for a binary image
{
double sum = 0;
for (int i = 0; i < weights.length; i++)
{
for (int j = 0; j < weights[i].length; j++)
sum += weights[i][j] * bin[i][j];
}
return activate(sum);
}
/**
*1. Generate a sigmoid (logistic) value from a sum
*2. "Digitize" the sigmoid value
*3. Return the digitized value, which corresponds to a number
*/
public int activate(double n)
{
double sig = 1.0/(1+Math.exp(-1*n));
int digitized = 0;
if (sig < 0.1)
digitized = 0;
else if (sig >= 0.1 && sig < 0.2)
digitized = 1;
else if (sig >= 0.2 && sig < 0.3)
digitized = 2;
else if (sig >= 0.3 && sig < 0.4)
digitized = 3;
else if (sig >= 0.4 && sig < 0.5)
digitized = 4;
else if (sig >= 0.5 && sig < 0.6)
digitized = 5;
else if (sig >= 0.6 && sig < 0.7)
digitized = 6;
else if (sig >= 0.7 && sig < 0.8)
digitized = 7;
else if (sig >= 0.8 && sig < 0.9)
digitized = 8;
else if (sig >= 0.9)
digitized = 9;
System.out.println("Sigmoid value: " + sig + "\nDigitized value: " + digitized);
return digitized;
}
/**
* 1. Provide inputs and "known" answer
* 2. Guess according to the inputs using feedforward(inputs)
* 3. Compute the error
* 4. Adjust all weights according to the error and learning rate
*/
public void train(int[][] bin, int desired)
{
int guess = feedforward(bin);
int error = desired-guess;
for (int i = 0; i < weights.length; i++)
{
for (int j = 0; j < weights[i].length; j++)
weights[i][j] += LEARNING_RATE * error * bin[i][j];
}
}
}
Я использую другой класс, чтобы "тренировать" нейрон. Этот другой класс - TrainingConsole.java - в основном берет "training.txt" со случайно сгенерированными компонентами, передает ему обучающие примеры (изображения -> двоичные двумерные массивы) и корректирует весовые коэффициенты на основе ошибки, скорости обучения и соответствующего значения. для бина:
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Arrays;
import java.util.Scanner;
import javax.imageio.ImageIO;
public class TrainingConsole
{
private File folder;
private File data;
public TrainingConsole(String dataFileName, String folderName)
{
data = new File(dataFileName);
folder = new File(folderName);
}
public void changeFolder(String folderName)
{
folder = new File(folderName);
}
public void feedAll(int desired)
{
System.out.println(Arrays.toString(folder.listFiles()));
for (int i = 1; i < folder.listFiles().length; i++) //To exclude folder
{
BufferedImage img = new BufferedImage(Drawing.DEF_WIDTH,Drawing.DEF_HEIGHT,BufferedImage.TYPE_INT_RGB);
try
{
String name = folder.listFiles()[i].getName();
if (name.substring(name.length()-4).equals(".png"))
img = ImageIO.read(folder.listFiles()[i]);
}
catch(IOException e)
{System.out.println("Error?");}
int[][] bin = new int[Drawing.DEF_WIDTH][Drawing.DEF_HEIGHT];
if (img != null)
{
for (int y = 0; y < img.getHeight(); y++)
{
for (int x = 0; x < img.getWidth(); x++)
{
int rgb = img.getRGB(x,y);
//System.out.println(rgb);
if (rgb == -1) //White
bin[y][x] = 0;
else
bin[y][x] = 1;
}
}
for (int[] a : bin)
System.out.println(Arrays.toString(a));
train(bin,desired);
}
}
}
public void train(int[][] bin, int desired) {
int guess = feedforward(bin);
int error = desired - guess;
Scanner input = null;
try {
input = new Scanner(data);
} catch (FileNotFoundException e) {
System.exit(1);
}
double[][] weights = new double[Drawing.DEF_HEIGHT][Drawing.DEF_WIDTH];
int i = 0, j = 0;
while (input.hasNext() && i < Drawing.DEF_HEIGHT) {
weights[i][j] = input.nextDouble();
j++;
if (j > weights[i].length - 1) {
i++;
j = 0;
}
}
for (int k = 0; k < weights.length; k++) {
for (int l = 0; l < weights[k].length; l++)
weights[k][l] += IMGNeuron.LEARNING_RATE * error * bin[k][l];
}
data = new File(data.getName());
PrintWriter output = null;
try {
output = new PrintWriter(data);
} catch (FileNotFoundException e) {
System.out.println("Cannot find data");
}
for (int m = 0; m < weights.length; m++) {
for (int n = 0; n < weights[m].length - 1; n++)
output.print(weights[m][n] + " ");
output.print(weights[m][weights[m].length - 1]);
output.println();
}
output.close();
}
public int feedforward(int[][] bin)
{
double sum = 0;
Scanner input = null;
try
{
input = new Scanner(data);
}
catch(FileNotFoundException e)
{
System.out.println("Could not locate data");
}
double[][] weights = new double[Drawing.DEF_HEIGHT][Drawing.DEF_WIDTH];
int i = 0, j = 0;
while (i < Drawing.DEF_HEIGHT && j < Drawing.DEF_WIDTH)
{
//System.out.println("( " + i + " , " + j + " )");
weights[i][j] = input.nextDouble();
j++;
if (j > weights[i].length - 1)
{
i++;
j = 0;
}
}
for (int m = 0; m < weights.length; m++)
{
for (int n = 0; n < weights[m].length; n++)
sum += weights[m][n] * bin[m][n];
}
return activate(sum);
}
public int activate(double n)
{
double sig = 1.0/(1+Math.exp(-1*n));
int digitized = 0;
if (sig < 0.1)
digitized = 0;
else if (sig >= 0.1 && sig < 0.2)
digitized = 1;
else if (sig >= 0.2 && sig < 0.3)
digitized = 2;
else if (sig >= 0.3 && sig < 0.4)
digitized = 3;
else if (sig >= 0.4 && sig < 0.5)
digitized = 4;
else if (sig >= 0.5 && sig < 0.6)
digitized = 5;
else if (sig >= 0.6 && sig < 0.7)
digitized = 6;
else if (sig >= 0.7 && sig < 0.8)
digitized = 7;
else if (sig >= 0.8 && sig < 0.9)
digitized = 8;
else if (sig >= 0.9)
digitized = 9;
return digitized;
}
public static void main(String[] args)
{
Scanner input = new Scanner(System.in);
TrainingConsole trainer = new TrainingConsole("training.txt","Training_000");
System.out.println("--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------");
System.out.println("Training Console");
System.out.println("--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------");
for (int i = 0; i <= 9; i++) {
//System.out.print("Folder with training data for desired = " + i + ", or enter \"skip\" to skip: ");
//String folderName = input.nextLine().trim();
String folderName = "Training_00" + i;
//System.out.println(folderName);
if (!folderName.toLowerCase().equals("skip"))
{
trainer.changeFolder(folderName);
// System.out.print("Press enter to run: ");
// String noReason = input.nextLine();
trainer.feedAll(i);
}
System.out.println("----------------------------------------------------------------------------------------------------ava----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------");
}
}
}
Для последующих конструкций Neuron я передаю "training.txt" в качестве матрицы весов. Однако это, очевидно, не работает: введите описание изображения здесь
Пожалуйста помоги! Я действительно плохо знаком с нейронными сетями и машинным обучением. На данный момент, я не знаю, что я делаю неправильно: мне нужно больше обучающих примеров? реализовать плохую функцию активации? Любой совет будет принят во внимание. Кроме того, не стесняйтесь запрашивать дополнительный код при необходимости.
1 ответ
Как отмечено в комментариях, есть две основные проблемы, я опишу их чуть подробнее.
Вся ваша модель представляет собой один персептрон, что означает, что вы создаете линейную модель из вашего пространства ввода (пикселей) в классы (цифры). Это просто не может работать, это не нейронная сеть в современном смысле. "Современный" NN, предназначенный для обработки изображений, будет состоять из тысяч нейронов, соединенных слоями с нелинейной активацией между ними, возможно, расположенных в виде сверточных ядер (поскольку это современная архитектура для распознавания изображений).
Предполагается, что вы решаете проблему мультикласса, но в действительности вы занимаетесь ранжированием Чтобы сделать NN, который классифицирует по K классам, вы должны иметь K выходных нейронов, каждый из которых будет генерировать сигнал, интерпретируемый как "вероятность" (не в строгом математическом смысле) принадлежности к определенному классу, таким образом, чтобы классифицировать - вы должны взять arg max (количество нейронов с наибольшим значением).
Как только вы исправите эти две важные проблемы со всей архитектурой, вы должны начать получать разумные результаты, тогда единственными недостающими частями будут настройка гиперпараметров и получение большего количества обучающих данных.