Как Вы интернационализируете текст в базе данных?

Список различий между кодом, написанным вручную, и кодом PyTorch

Оказывается, существует много различий между тем, что делают код, созданный вручную, и кодом PyTorch. Вот то, что я обнаружил, перечисляя примерно в порядке наименьшего влияния на результат:

  • Ваш код и код PyTorch используют две разные функции для сообщения о потере.
  • Ваш код и код PyTorch устанавливают начальные веса очень по-разному. Вы упоминаете об этом в своем вопросе, но оказывается, что это оказывает довольно значительное влияние на результаты.
  • По умолчанию слои torch.nn.Linear добавляют дополнительный набор весов смещения к модели. Таким образом, первый слой модели Pytorch эффективно имеет веса 3x5, а второй слой имеет веса 6x1. Слои в свернутом вручную коде имеют веса 2x5 и 5x1 соответственно.
    • Смещение, кажется, помогает модели учиться и адаптироваться несколько быстрее. Если вы отключите смещение, для модели Pytorch потребуется примерно вдвое больше тренировочных эпох, чтобы достичь почти 0 потерь.
  • Любопытно, что кажется, что модель Pytorch использует скорость обучения, которая фактически составляет половину от того, что вы указываете. В качестве альтернативы может оказаться, что есть какой-то случайный фактор 2, который где-то попал в вашу раскрученную математику / код.

Как получить идентичные результаты из кода, написанного вручную, и кода Pytorch

Тщательно учтя вышеуказанные 4 фактора, можно добиться полного паритета между кодом, выпущенным вручную, и кодом Pytorch. , При правильных настройках и настройках два фрагмента будут давать идентичные результаты:

enter image description here

Самый важный параметр - привести в соответствие функции отчетов о потерях

Критическим отличием является то, что в итоге вы используете две совершенно разные функции для измерения потерь в двух фрагментах кода:

  • В ручном коде вы измеряете потеря как layer2_error.mean(). Если вы распакуете переменную, вы увидите, что значение layer2_error.mean() несколько странное и бессмысленное:

    layer2_error.mean()
    == np.dot(cost_delta, cost_error).mean()
    == np.dot(mse_derivative(layer2, Y), mse(layer2, Y)).mean()
    == np.sum(.5 * (layer2 - Y) * ((layer2 - Y)**2).mean()).mean()
    
  • С другой стороны, в коде PyTorch потери измеряются в Термины традиционного определения mse, т. е. как эквивалент np.mean((layer2 - Y)**2). Вы можете доказать это себе, изменив цикл PyTorch следующим образом:

    def mse(x, y):
        return np.mean((x - y)**2)
    
    torch_losses = [] # Keeps track of the loses.
    torch_losses_manual = [] # for comparison
    
    # Step 2-4 of training routine.
    
    for _e in tqdm(range(num_epochs)):
        # Reset the gradient after every epoch. 
        optimizer.zero_grad() 
        # Step 2: Foward Propagation
        predictions = model(X)
    
        # Step 3: Back Propagation 
        # Calculate the cost between the predictions and the truth.
        loss = criterion(predictions, Y)
        # Remember to back propagate the loss you've computed above.
        loss.backward()
    
        # Step 4: Optimizer take a step and update the weights.
        optimizer.step()
    
        # Log the loss value as we proceed through the epochs.
        torch_losses.append(loss.data.item())
        torch_losses_manual.append(mse(predictions.detach().numpy(), Y.detach().numpy()))
    
    plt.plot(torch_losses, lw=5, label='torch_losses')
    plt.plot(torch_losses_manual, lw=2, label='torch_losses_manual')
    plt.legend()
    

Вывод:

enter image description here [ 1160]

Также важно - использовать те же начальные веса

PyTorch использует свою собственную специальную подпрограмму для установки начальных весов, которая дает очень разные результаты из np.random.rand. Я еще не смог точно воспроизвести это, но для следующего лучшего, что мы можем просто захватить Pytorch. Вот функция, которая получит те же начальные веса, которые использует модель Pytorch:

import torch
from torch import nn
torch.manual_seed(0)

def torch_weights(nodes_in, nodes_hidden, nodes_out, bias=None):
    model = nn.Sequential(
        nn.Linear(nodes_in, nodes_hidden, bias=bias),
        nn.Sigmoid(),
        nn.Linear(nodes_hidden, nodes_out, bias=bias),
        nn.Sigmoid()
    )

    return [t.detach().numpy() for t in model.parameters()]

Наконец - в Pytorch отключите все веса смещения и удвойте скорость обучения

В конце концов вы можете захотеть реализовать веса смещения в вашем собственном коде. На данный момент мы просто отключим смещение в модели Pytorch и сравним результаты свернутой вручную модели с результатами объективной модели Pytorch.

Кроме того, чтобы результаты совпали, вам нужно удвоить скорость обучения модели Pytorch. Это эффективно масштабирует результаты вдоль оси x (то есть удвоение скорости означает, что для достижения какого-то определенного признака на кривой потерь требуется вдвое меньше эпох).

Собираем все вместе

Чтобы воспроизвести данные hand_rolled_losses из сюжета в начале моего поста, все, что вам нужно сделать, это взять свой свернутый вручную код и заменить функцию mse с:

def mse(predicted, truth):
    return np.mean(np.square(predicted - truth))

линиями, которые инициализируют веса с:

W1,W2 = [w.T for w in torch_weights(input_dim, hidden_dim, output_dim)]

и линией, которая отслеживает потери с:

losses.append(cost_error)

, и вы должны быть хорошо идти.

Чтобы воспроизвести данные torch_losses из графика, нам также необходимо отключить весовые коэффициенты смещения в модели Pytorch. Для этого вам просто нужно изменить строки, определяющие модель Pytorch, следующим образом:

model = nn.Sequential(
    # Use nn.Linear to get our simple perceptron.
    nn.Linear(input_dim, hidden_dim, bias=None),
    # Use nn.Sigmoid to get our sigmoid non-linearity.
    nn.Sigmoid(),
    # Second layer neurons.
    nn.Linear(hidden_dim, output_dim, bias=None),
    nn.Sigmoid()
)

Вам также необходимо изменить строку, определяющую learning_rate:

learning_rate = 0.3 * 2

Полный список кодов

Код, свернутый вручную

[1170 ] Вот полный список моей версии свернутого вручную кода нейронной сети, чтобы помочь с воспроизведением моих результатов:

from itertools import chain
import matplotlib.pyplot as plt
import numpy as np
import scipy as sp
import scipy.stats
import torch
from torch import nn

np.random.seed(0)
torch.manual_seed(0)

def torch_weights(nodes_in, nodes_hidden, nodes_out, bias=None):
    model = nn.Sequential(
        nn.Linear(nodes_in, nodes_hidden, bias=bias),
        nn.Sigmoid(),
        nn.Linear(nodes_hidden, nodes_out, bias=bias),
        nn.Sigmoid()
    )

    return [t.detach().numpy() for t in model.parameters()]

def sigmoid(x): # Returns values that sums to one.
    return 1 / (1 + np.exp(-x))

def sigmoid_derivative(sx):
    # See https://math.stackexchange.com/a/1225116
    return sx * (1 - sx)

# Cost functions.
def mse(predicted, truth):
    return np.mean(np.square(predicted - truth))

def mse_derivative(predicted, truth):
    return predicted - truth

X = xor_input = np.array([[0,0], [0,1], [1,0], [1,1]])
Y = xor_output = np.array([[0,1,1,0]]).T

# Define the shape of the weight vector.
num_data, input_dim = X.shape
# Lets set the dimensions for the intermediate layer.
hidden_dim = 5
# Define the shape of the output vector. 
output_dim = len(Y.T)

W1,W2 = [w.T for w in torch_weights(input_dim, hidden_dim, output_dim)]

num_epochs = 5000
learning_rate = 0.3
losses = []

for epoch_n in range(num_epochs):
    layer0 = X
    # Forward propagation.

    # Inside the perceptron, Step 2. 
    layer1 = sigmoid(np.dot(layer0, W1))
    layer2 = sigmoid(np.dot(layer1, W2))

    # Back propagation (Y -> layer2)

    # In what direction is the target value?
    # Were we really close? If so, don't change too much.
    cost_delta = mse_derivative(layer2, Y)
    layer2_delta = cost_delta *  sigmoid_derivative(layer2)

    # Back propagation (layer2 -> layer1)
    # How much did each layer1 value contribute to the layer2 error (according to the weights)?
    layer1_error = np.dot(layer2_delta, W2.T)
    layer1_delta = layer1_error * sigmoid_derivative(layer1)

    # update weights
    W2 += - learning_rate * np.dot(layer1.T, layer2_delta)
    W1 += - learning_rate * np.dot(layer0.T, layer1_delta)

    # Log the loss value as we proceed through the epochs.
    losses.append(mse(layer2, Y))

# Visualize the losses
plt.plot(losses)
plt.show()

Код Pytorch

import matplotlib.pyplot as plt
from tqdm import tqdm
import numpy as np

import torch
from torch import nn
from torch import tensor
from torch import optim

torch.manual_seed(0)
device = 'gpu' if torch.cuda.is_available() else 'cpu'

num_epochs = 5000
learning_rate = 0.3 * 2

# XOR gate inputs and outputs.
X = tensor([[0,0], [0,1], [1,0], [1,1]]).float().to(device)
Y = tensor([[0],[1],[1],[0]]).float().to(device)

# Use tensor.shape to get the shape of the matrix/tensor.
num_data, input_dim = X.shape
num_data, output_dim = Y.shape

# Step 1: Initialization. 

# Initialize the model.
# Set the hidden dimension size.
hidden_dim = 5
# Use Sequential to define a simple feed-forward network.
model = nn.Sequential(
    # Use nn.Linear to get our simple perceptron.
    nn.Linear(input_dim, hidden_dim, bias=None),
    # Use nn.Sigmoid to get our sigmoid non-linearity.
    nn.Sigmoid(),
    # Second layer neurons.
    nn.Linear(hidden_dim, output_dim, bias=None),
    nn.Sigmoid()
)

# Initialize the optimizer
optimizer = optim.SGD(model.parameters(), lr=learning_rate)

# Initialize the loss function.
criterion = nn.MSELoss()

def mse(x, y):
    return np.mean((x - y)**2)

torch_losses = [] # Keeps track of the loses.
torch_losses_manual = [] # for comparison

# Step 2-4 of training routine.

for _e in tqdm(range(num_epochs)):
    # Reset the gradient after every epoch. 
    optimizer.zero_grad() 
    # Step 2: Foward Propagation
    predictions = model(X)

    # Step 3: Back Propagation 
    # Calculate the cost between the predictions and the truth.
    loss = criterion(predictions, Y)
    # Remember to back propagate the loss you've computed above.
    loss.backward()

    # Step 4: Optimizer take a step and update the weights.
    optimizer.step()

    # Log the loss value as we proceed through the epochs.
    torch_losses.append(loss.data.item())
    torch_losses_manual.append(mse(predictions.detach().numpy(), Y.detach().numpy()))

plt.plot(torch_losses, lw=5, c='C1', label='torch_losses')
plt.plot(torch_losses_manual, lw=2, c='C2', label='torch_losses_manual')
plt.legend()

Заметки

[ 1140] Веса смещения

Вы можете найти несколько очень поучительных примеров, которые показывают, что такое весы смещения и как их реализовать, в этого урока . Они перечисляют кучу реализаций нейронных сетей на чистом Python, очень похожих на вашу свернутую вручную, так что, вероятно, вы могли бы адаптировать часть их кода, чтобы сделать свою собственную реализацию смещения.

Функции для получения начального предположения о весах

Вот функция, которую я адаптировал из того же самого урока , который может производить разумные начальные значения для весов. Я думаю, что алгоритм, который Pytorch использует внутри, несколько отличается, но это дает схожие результаты:

import scipy as sp
import scipy.stats

def tnorm_weights(nodes_in, nodes_out, bias_node=0):
    # see https://www.python-course.eu/neural_network_mnist.php
    wshape = (nodes_out, nodes_in + bias_node)
    bound = 1 / np.sqrt(nodes_in)
    X = sp.stats.truncnorm(-bound, bound)
    return X.rvs(np.prod(wshape)).reshape(wshape) 
7
задан Dylan Beattie 24 November 2008 в 16:29
поделиться

6 ответов

Не храните текст, генерируемый системой, в базе данных. Вместо этого сохраните код (как номер сообщения) и затем интернационализируйте его на уровне GUI. Удостоверьтесь, что единственный текст, который появляется непосредственно из базы данных, является текстом, который пользователь вставил самостоятельно. Удостоверьтесь, что Ваша база данных установлена принять unicode текст.

6
ответ дан 6 December 2019 в 23:15
поделиться

Во-первых, очень знайте об ограничениях. Для созданного пользователями содержания Вы смотрите на общественный перевод (ошибочный), (ненадежный) машинный перевод или платите людям-переводчикам (дорогой!), если Вы хотите локализовать материал, что Ваши пользователи вводят в Ваше приложение. Можно ли хотеть попросить, чтобы пользователи обеспечили две версии - один для культуры по умолчанию (английский язык?) и один для их локализованной культуры, таким образом, можно предоставить перевод нейтрализации другим пользователям?

Во-вторых, будьте подготовлены к некоторым чрезвычайно долгим миграциям базы данных..., если у Вас есть четыре столбца текста в электронной таблице Excel, внезапно Вы имеете дело со вставкой каждого значения в Вашу систему перевода, получение локализованного идентификатора и затем хранение, которое в таблице Вы на самом деле импортируете - и SELECT * только даст Вам идентификаторы фразы, которые необходимо разрешить назад в строки путем локализации их против таблиц преобразования.

Это сказало - можно локализовать много справочных таблиц, выпадающих списков, и т.д. которые управляются базой данных в типичном проекте. Другие комментарии уже упомянули, что хранили значения StringId в базе данных, которые относятся к внешним файлам ресурсов или электронным таблицам, но если Вы интересуетесь содержанием ВСЕГО Вашего локализованного текста в базе данных вместе с самими данными, затем Вы могли бы найти этот подход полезным.

Мы использовали таблицу под названием Фраза, которая содержит идентификатор и (английское) содержание по умолчанию для каждой части текста в Вашем приложении.

Ваши другие таблицы заканчивают тем, что были похожи на это:

CREATE TABLE ProductType (
    Id int primary key,
    NamePhraseId int, -- link to the Phrase containing the name of this product type.
    DescriptionPhraseId int
)

Создайте вторую таблицу Culture, которая содержит определенные и нейтральные культуры, которые Вы поддерживаете. Для бонусных очков реализуйте эту таблицу как самосправочное дерево (каждая запись Культуры содержит ссылку nullable ParentCultureCode), таким образом, Вы можете нейтрализация от определенных культур ("CA франка" для канадского французского языка) к нейтральным культурам ("франк", если никакая региональная локализация не существует) к Вашему инварианту / культура по умолчанию (обычно 'en', потому что на этом так широко говорят),

Ваши фактические переводы находятся в таблице LocalizedPhrase, которая похожа:

CREATE TABLE LocalizedPhrase (
  PhraseId int primary key,
  CultureCode varchar(8) primary key,
  Content nvarchar(255) -- the actual localized content
)

Можно расширить эту модель, если Вы хотите обеспечить male/female-specific локализации:

CREATE TABLE GenderedLocalizedPhrase (
  PhraseId  int primary key,
  CultureCode varchar(8) primary key,
  GenderCode char(1) primary key, -- 'm', 'f' or '?' - links to Gender table
  Content nvarchar(255)
)

Вы захотите кэшировать этот весь график таблицы в памяти и изменить Ваши стратегии запроса/соединения соответственно - кэширование локализаций в классах Фразы и переопределения ToString (), метод на объекте Фразы осмотреть культуру текущего потока является одним подходом. При попытке сделать этот материал в своих запросах, то Вы понесете существенные расходы производительности, и каждый запрос закончит тем, что был похож на это:

-- assume @MyCulture contains the culture code ('ca-FR') that we are looking for:
SELECT 
    Product.Id, 
    Product.Name, 

    COALESCE(ProductStatusLocalizedPhrase.Content, ProductStatusPhrase.Content) as ProductStatus, 
    COALESCE(ProductTypeLocalizedPhrase.Content, ProductTypePhrase.Content) as ProductType, 
  FROM Product

    INNER JOIN ProductStatus ON Product.StatusId = ProductStatus.Id
    INNER JOIN Phrase as ProductStatusPhrase ON ProductStatus.NamePhraseId = Phrase.Id
    LEFT JOIN LocalizedPhrase as ProductStatusLocalizedPhrase 
      ON ProductStatus.NamePhraseId = ProductStatusLocalizedPhrase.Id and CultureCode = @MyCulture

    INNER JOIN ProductType ON Product.TypeId = ProductType.Id
    INNER JOIN Phrase as ProductTypePhrase ON ProductType.NamePhraseId = Phrase.Id
    LEFT JOIN LocalizedPhrase as ProductTypeLocalizedPhrase 
      ON ProductType.NamePhraseId = ProductTypeLocalizedPhrase.Id and CultureCode = @MyCulture
2
ответ дан 6 December 2019 в 23:15
поделиться

Какое обходное решение может помешать пользователям получать текст на языке, который они не понимают?

Это только было бы проблемой для вводимых данных пользователя. Таким образом, если Вы хотите избежать других пользователей, видящих содержание на языке, они не могли бы понять, сохранить код локали вместе с довольным и только отобразить то содержание любому с той же локалью / пользователь выбранный язык.

С другой стороны, пользователи могли бы знать несколько языков, таким образом, я не ограничу их в наблюдении содержания, я просто добавил бы, что уведомление как "Это содержание не доступно на языке по Вашему выбору...", и затем отобразите содержание на доступном языке. Таким образом, Вы увеличиваете вероятность, что пользователь получает содержание, которое она может понять.

1
ответ дан 6 December 2019 в 23:15
поделиться

Статические данные являются самыми легкими, я создал бы Таблицу преобразования, так вообразите таблицу UserStatus, которая имеет StatusId, TranslationToken, затем TranslationTable имеет Маркер, язык и текст.

Или simillary Вы могли просто возвратить маркер для приложения для обработки использования файлов ресурсов.

Что касается данных ввода данных пользователем это намного более сложно. Необходимо принять unicode символы как минимум, но затем вопрос становится Сортировкой и Сравнением. Сортировка является самой большой. Многое из того, что можно сделать, зависит от приложения. Таким образом, если Ваша база данных только должна поддерживать единственный язык в какой-либо точке (Вообразите, было ли Ваше приложение распределено Вашим клиентам), то сопоставление является спорным вопросом, так как можно установить его во время установки.

Однако, если необходимо поддерживать несколько языков в единой базе данных, необходимо будет обработать сопоставление правильно. Единственное мы, способ, которым мы нашли для изменения сопоставления на лету, состоял в том, чтобы установить его в наших запросах, и это потребовало, чтобы динамический sql был сгенерирован. Этот пример был бы Вами, хранят русский, английский и польский язык все в одном поле в той же таблице.

Мы никогда ничего не исследовали вне латинских и кириллических сопоставлений, но я предполагаю, что азиатские языки работали бы то же.

0
ответ дан 6 December 2019 в 23:15
поделиться

Мы используем XML-файл для нашей системы. Файл содержит ключевые связи с определенной частью наши модули. Таким образом, мы можем быстро сделать XPath для получения информации. У нас есть 1 файл для каждого языка (мы поддерживаем 2 языка в настоящий момент, но добавление языка является очень простой справедливой вставкой копии файл). Это решение не идеально, но имейте некоторые преимущества:

  1. Не в базе данных.
  2. Может быть отредактирован кем-то внешним к программированию.
  3. Легкий быть реализованным в нескольких представлениях (у нас есть WinForm и WebForm).
0
ответ дан 6 December 2019 в 23:15
поделиться

Мы меняем большую часть текста в нашей базе данных на «ключ: текст по умолчанию», а затем ищем «ключ» в наших файлах перевода. Это охватывает весь текст, который клиент не изменяет в базе данных (например, то, что называть «кредит-нотой»). Когда заказчик меняет текст, он может просто удалить ключ, чтобы всегда получать его значение.

В нашей системе есть несколько таблиц, содержащих данные конфигурации, которые нуждаются в вышеуказанном, таблицы, которые просто содержат текст, вводимый клиентами, являются не проблема, если каждому клиенту нужен только один язык.

1
ответ дан 6 December 2019 в 23:15
поделиться
Другие вопросы по тегам:

Похожие вопросы: