Why Not Train Your Own
The task, the baseline, and the case for starting from somebody else's weights
Week 10 trained a language model from nothing and produced something that had learned an invented language and nothing else. The alternative is to start from a model somebody has already trained on a great deal of real text, which is what almost everybody actually does.
The data
# Two vocabularies. The model trains on one and is evaluated on both,
# which is what separates a model that learned words from a model that
# learned what the words mean.
TRAIN_GOOD = ['excellent', 'reliable', 'comfortable', 'sturdy']
TRAIN_BAD = ['broken', 'flimsy', 'noisy', 'faulty']
NEW_GOOD = ['superb', 'dependable', 'pleasant', 'robust']
NEW_BAD = ['damaged', 'fragile', 'loud', 'defective']
THING = ['the delivery', 'the battery', 'the screen', 'the handle',
'the packaging', 'the software', 'the fabric', 'the motor']
OPENER = ['I found that ', 'Honestly, ', 'After a week, ', '',
'On the whole ']
CLOSER = [' overall.', ' for the price.', ' so far.', '.',
' in daily use.']
def make_reviews(n, good, bad, seed):
"""Short product reviews, balanced, with the sentiment carried by
one adjective."""
g = torch.Generator().manual_seed(seed)
def pick(xs):
return xs[int(torch.randint(len(xs), (1,), generator=g))]
texts, labels = [], []
for _ in range(n):
positive = int(torch.randint(2, (1,), generator=g))
word = pick(good if positive else bad)
texts.append(pick(OPENER) + pick(THING) + ' is ' + word
+ pick(CLOSER))
labels.append(positive)
return texts, torch.tensor(labels)
train_texts, train_y = make_reviews(1200, TRAIN_GOOD, TRAIN_BAD, 0)
seen_texts, seen_y = make_reviews(400, TRAIN_GOOD, TRAIN_BAD, 1)
new_texts, new_y = make_reviews(400, NEW_GOOD, NEW_BAD, 2)
print('training reviews %d, balance %.3f'
% (len(train_texts), train_y.float().mean()))
print()
for i in range(6):
print('[%s] %s' % ('positive' if train_y[i] else 'negative',
train_texts[i]))
[negative] the delivery is faulty.
[positive] the battery is sturdy.
[positive] Honestly, the motor is comfortable.
[negative] the screen is broken.
[negative] Honestly, the fabric is faulty in daily use.
[positive] On the whole the software is reliable overall.
# Two vocabularies. The model trains on one and is evaluated on both,
# which is what separates a model that learned words from a model that
# learned what the words mean.
TRAIN_GOOD = ['excellent', 'reliable', 'comfortable', 'sturdy']
TRAIN_BAD = ['broken', 'flimsy', 'noisy', 'faulty']
NEW_GOOD = ['superb', 'dependable', 'pleasant', 'robust']
NEW_BAD = ['damaged', 'fragile', 'loud', 'defective']
THING = ['the delivery', 'the battery', 'the screen', 'the handle',
'the packaging', 'the software', 'the fabric', 'the motor']
OPENER = ['I found that ', 'Honestly, ', 'After a week, ', '',
'On the whole ']
CLOSER = [' overall.', ' for the price.', ' so far.', '.',
' in daily use.']
def make_reviews(n, good, bad, seed):
"""Short product reviews, balanced, with the sentiment carried by
one adjective."""
g = torch.Generator().manual_seed(seed)
def pick(xs):
return xs[int(torch.randint(len(xs), (1,), generator=g))]
texts, labels = [], []
for _ in range(n):
positive = int(torch.randint(2, (1,), generator=g))
word = pick(good if positive else bad)
texts.append(pick(OPENER) + pick(THING) + ' is ' + word
+ pick(CLOSER))
labels.append(positive)
return texts, torch.tensor(labels)
train_texts, train_y = make_reviews(1200, TRAIN_GOOD, TRAIN_BAD, 0)
seen_texts, seen_y = make_reviews(400, TRAIN_GOOD, TRAIN_BAD, 1)
new_texts, new_y = make_reviews(400, NEW_GOOD, NEW_BAD, 2)
print('reviews using the training vocabulary:')
for i in range(2):
print(' [%s] %s' % ('positive' if seen_y[i] else 'negative',
seen_texts[i]))
print('\nreviews using words the training set never contained:')
for i in range(2):
print(' [%s] %s' % ('positive' if new_y[i] else 'negative',
new_texts[i]))
print('\ntraining adjectives:', TRAIN_GOOD + TRAIN_BAD)
print('new adjectives :', NEW_GOOD + NEW_BAD)
[positive] On the whole the delivery is sturdy.
[positive] Honestly, the motor is sturdy so far.
reviews using words the training set never contained:
[negative] Honestly, the delivery is defective.
[positive] I found that the delivery is pleasant for the price.
training adjectives: ['excellent', 'reliable', 'comfortable', 'sturdy', 'broken', 'flimsy', 'noisy', 'faulty']
new adjectives : ['superb', 'dependable', 'pleasant', 'robust', 'damaged', 'fragile', 'loud', 'defective']
A bag of words baseline
# Two vocabularies. The model trains on one and is evaluated on both,
# which is what separates a model that learned words from a model that
# learned what the words mean.
TRAIN_GOOD = ['excellent', 'reliable', 'comfortable', 'sturdy']
TRAIN_BAD = ['broken', 'flimsy', 'noisy', 'faulty']
NEW_GOOD = ['superb', 'dependable', 'pleasant', 'robust']
NEW_BAD = ['damaged', 'fragile', 'loud', 'defective']
THING = ['the delivery', 'the battery', 'the screen', 'the handle',
'the packaging', 'the software', 'the fabric', 'the motor']
OPENER = ['I found that ', 'Honestly, ', 'After a week, ', '',
'On the whole ']
CLOSER = [' overall.', ' for the price.', ' so far.', '.',
' in daily use.']
def make_reviews(n, good, bad, seed):
"""Short product reviews, balanced, with the sentiment carried by
one adjective."""
g = torch.Generator().manual_seed(seed)
def pick(xs):
return xs[int(torch.randint(len(xs), (1,), generator=g))]
texts, labels = [], []
for _ in range(n):
positive = int(torch.randint(2, (1,), generator=g))
word = pick(good if positive else bad)
texts.append(pick(OPENER) + pick(THING) + ' is ' + word
+ pick(CLOSER))
labels.append(positive)
return texts, torch.tensor(labels)
train_texts, train_y = make_reviews(1200, TRAIN_GOOD, TRAIN_BAD, 0)
seen_texts, seen_y = make_reviews(400, TRAIN_GOOD, TRAIN_BAD, 1)
new_texts, new_y = make_reviews(400, NEW_GOOD, NEW_BAD, 2)
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
print('%-16s %14s %14s' % ('', 'same words', 'new words'))
for label, ngrams in [('unigrams', (1, 1)), ('1 to 2 grams', (1, 2))]:
model = make_pipeline(TfidfVectorizer(ngram_range=ngrams),
LogisticRegression(max_iter=2000))
model.fit(train_texts, train_y.numpy())
print('%-16s %14.4f %14.4f'
% (label, model.score(seen_texts, seen_y.numpy()),
model.score(new_texts, new_y.numpy())))
unigrams 1.0000 0.5000
1 to 2 grams 1.0000 0.4925
The second column is the whole point of this week
A bag of words does well on reviews written with the words it was trained on, and falls to chance on reviews written with synonyms. It has not learned that a product can be good or bad. It has learned that the string reliable predicts one label, and nothing in the method could have done otherwise.
A model pretrained on a great deal of English already knows that dependable and reliable are used in similar places. That knowledge is what you are borrowing, and the rest of this week is about how to borrow it.