Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文
Kaggle上的Twitter的情感分析竞赛。在这个案例中,将使用预训练的模型BERT来完成对整个竞赛的数据分析。
! i7 b* _' d7 K4 {/ G# g导入需要的库
8 k0 S/ y) I- r0 D) D7 limport numpy as np. p) A. ~" w+ R8 D& a. `% q% s
import pandas as pd
* ^4 @4 e6 s  M; e0 t: P7 Wfrom math import ceil, floor
0 K9 K* Q: Z3 G* b7 Qimport tensorflow as tf
. C; i1 d8 M6 C3 \  @% q- B3 Nimport tensorflow.keras.layers as L8 c1 ^0 H/ T) r. i
from tensorflow.keras.initializers import TruncatedNormal' V# Y+ p5 e1 a" r
from sklearn import model_selection) G. H3 v! f. Q* z
from transformers import BertConfig, TFBertPreTrainedModel, TFBertMainLayer
% J9 X9 a9 {8 w; s& U( a6 F+ m2 Cfrom tokenizers import BertWordPieceTokenizer; N, Q+ g% ]5 y  k
读取并解释数据( q6 p4 g+ h& g1 p" i3 @9 }9 M, z2 }2 }
在竞赛中,对数据的理解是非常关键的。因此我们首先要做的就是读取数据,然后查看数据的内容以及特点。0 K8 u5 |! C; M
先用pandas来读取csv数据,+ q! z( C/ u- h/ C9 n9 H

. R0 O6 H* K8 W3 ~/ {train_df = pd.read_csv('train.csv')" _# w) J' w+ W6 U
train_df.dropna(inplace=True)
6 ?# y8 _6 X0 ?1 G  R. ]test_df = pd.read_csv('test.csv')
0 Q, u; s3 ^& H' ktest_df.loc[:, "selected_text"] = test_df.text.values+ u4 M& R9 D& U% h
submission_df = pd.read_csv('sample_submission.csv')
; Z1 h% K( z0 I) X再查看下我们的数据的数量,我们一共有27485条训练数据,3535条测试数据,
  ], i% K7 ~- @, Aprint("train numbers =", train_df.shape)
# f9 O" e+ K! F5 zprint("test numbers =", test_df.shape): K- _1 S5 z& s1 C8 j& R$ B8 B
紧接着查看训练数据和测试数据前10条表单的字段跟数据,表单中包含了一下几个数据字段:+ U! M8 g0 Z! \
* i/ h$ j% p9 c0 N- [5 }# _
textID: 文本数据记录的唯一ID;/ ?8 ]1 }0 f  F/ v+ F! \3 [# H

; b2 d4 M5 P8 T* ptext: 原始语句;  v; N3 z; J' ?6 P! t. |
$ x" j6 r: ^5 D6 v5 R- T4 G% ^
selected_text: 表示情感的语句;
7 I$ f4 \8 c! x0 F2 E) \. u& w" H
sentiment: 情感类型, neutral中立, positive积极, negative消极;" |6 v* D/ h: f6 [+ K$ @1 G$ Z3 {8 h
- c4 N- V! i. `+ S+ z( `1 s
/ k; P" t. H$ R& x) I& c
从数据中我们可以得出,目标就是根据现有的情感从原本是的语句中选出能代表这个情感的语句部分。
' c1 o, D% \5 a1 j5 C7 Z$ ctrain_df.head(10)
1 N- G1 a; n+ a) a9 V' wtest_df.head(10)# P1 @# o1 v5 E+ s
定义常量
$ y5 Q& G! [% O4 x* r9 Z# bert预训练权重跟数据存放的目录
1 V. g0 s! `! C  X- h# J& @; Z4 yPATH = "./bert-base-uncased/"% O+ p- s6 Z! S
# 语句最大长度
. J2 C/ M) k( @+ t7 G( dMAX_SEQUENCE_LENGTH = 1288 h  w! J. C9 ?+ A
载入词向量7 U" x, Z: \+ G
BERT是依据一个固定的词向量来进行训练的。因此在竞赛中需要先使用BertWordPieceTokenizer来加载这些词向量,其中的lowercase=True表示所有的词向量都是小写。设置大小写不敏感可以减少模型对资源的占用。" r0 p5 s; P% a- ]2 l6 L+ Q3 L
TOKENIZER = BertWordPieceTokenizer(f"{PATH}/vocab.txt", lowercase=True)
3 T  m9 B1 ^& S# t定义数据加载器2 J/ n) a# t1 p; ^" @3 I& l
定义数据预处理函数
; j5 L: q9 D7 Z4 I# B7 d: a& q5 H8 |2 s
def preprocess(tweet, selected_text, sentiment):
! g5 l9 a# R+ Y( \7 D* Y    ! l4 m$ t, s& {
# 将被转成byte string的原始字符串转成utf-8的字符串
$ B$ \* s8 W# {0 X    tweet = tweet.decode('utf-8')3 l/ F, [' t9 O& k
    selected_text = selected_text.decode('utf-8')
% Z" k( X" ?' Y% \: }9 E# b    sentiment = sentiment.decode('utf-8')
7 A3 g: @- M3 X6 k# X    tweet = " ".join(str(tweet).split())
) X' ?# F* y. O  u+ i  X1 `    selected_text = " ".join(str(selected_text).split())
; V3 a: P" i( ~5 S- I   
7 D+ H& Z# D9 ~: q8 m7 F# 标记出selected text和text共有的单词3 D  H  I, y# u6 f  L  E3 b
    idx_start, idx_end = None, None
: \4 a1 {( z  x# s    for index in (i for i, c in enumerate(tweet) if c == selected_text[0]):  J9 a' r( J$ k) e2 Q- l  ^- [9 Y
        if tweet[index:index+len(selected_text)] == selected_text:3 N3 s" w6 h6 }' t, R7 {/ r3 }2 @; c
            idx_start = index
& n. V! u% m0 f            idx_end = index + len(selected_text)+ G  E/ V8 Q9 W  Y
            break4 I& K9 f* y* T, J
    intersection = [0] * len(tweet). B$ T: I& m( k! a
    if idx_start != None and idx_end != None:7 {; P* U9 H, E' w! j; s  f6 ]
        for char_idx in range(idx_start, idx_end):! q* A$ ^2 h7 ?- a2 a$ y
            intersection[char_idx] = 12 x: [+ S; {. n4 a
    # V% V  x% z: M
# 对原始数据用词向量进行编码, 这里会返回原始数据中的词在词向量中的下标
0 {/ l1 Z& o  H% q5 F! D# 和原始数据中每个词向量的单词在文中的起始位置跟结束位置
) m4 f# N8 T; ]+ }# G    enc = TOKENIZER.encode(tweet)
8 z7 f9 M# [) R    input_ids_orig, offsets = enc.ids, enc.offsets
$ ?+ V5 E0 h. ?    target_idx = []( L( U" r4 {1 ], d8 O8 \' H; ]
    for i, (o1, o2) in enumerate(offsets):
; z( e+ i' k9 F/ ?        if sum(intersection[o1: o2]) > 0:! @+ \3 [/ k4 y! x1 a
            target_idx.append(i)" U: e# T4 ^! o) Z9 ]: A5 v8 K; p( a
    target_start = target_idx[0]9 h/ ^& J- ^% j$ @+ ]
    target_end = target_idx[-1]
. z& Z6 o, l) {: p; j$ n4 [3 j- @    sentiment_map = {
* f% d/ \8 y+ f5 o, E) B/ E! V" H/ w        'positive': 3893,
* s; ?( |3 ^  Y8 K: h        'negative': 4997,/ c% U. I4 v4 Z4 k! |) t! l+ t' {
        'neutral': 8699,
4 S& s/ w: H- |1 U0 {" x  T# J5 F    }
/ n1 ~/ E3 F7 Q& }" _    2 T4 R7 }6 r" s' d$ T  c! i/ u! r
# 将情感标签和原始的语句的词向量组合在一起组成我们新的数据* i, G3 J% g- w; K
    input_ids = [101] + [sentiment_map[sentiment]] + [102] + input_ids_orig + [102]6 r# G# D' I2 I; z
    input_type_ids = [0] * (len(input_ids_orig) + 4)
8 e- Y+ k7 E/ V  d) b' d    attention_mask = [1] * (len(input_ids_orig) + 4)$ f. {* _2 h: R( F4 D6 G3 W, k
    offsets = [(0, 0), (0, 0), (0, 0)] + offsets + [(0, 0)]1 k9 p7 L& {# o
    target_start += 3
" L  r# M5 I( o* x, g8 m# e$ s    target_end += 39 J( B$ Z. m3 B* I* S! k  R
# 计算需要paddning的长度, BERT是以固定长度进行输入的,因此对于不足的我们需要做pandding
$ ]( M* ?: I/ Q$ q- {    padding_length = MAX_SEQUENCE_LENGTH - len(input_ids)
- Q4 Z8 y5 ^  m9 K+ W. Z    if padding_length > 0:7 C4 T0 t7 V9 }, O0 E! Z* ~
        input_ids = input_ids + ([0] * padding_length)
, s# y# O. c$ k. s/ z' F4 f* K        attention_mask = attention_mask + ([0] * padding_length)- ~( j7 ^9 g, Q  p& l) u8 p, z
        input_type_ids = input_type_ids + ([0] * padding_length)
1 w( g4 ]4 ?  ?2 O        offsets = offsets + ([(0, 0)] * padding_length)
' D3 I2 {- q( [: X. O& M* D& ~    elif padding_length + {# z2 b) ~4 O5 |; J) \% Q
定义数据加载器
$ y5 X  z' |; ?2 v& J' r7 V' i2 P( l2 Y
class TweetDataset(tf.data.Dataset):5 y" L* v! _4 @' |! `$ M, p
    + J* A! h3 z! T: D" }
    outputTypes = (
9 D1 L+ y! C2 S( g' L8 J        tf.dtypes.int32,  tf.dtypes.int32,   tf.dtypes.int32,
1 J7 r5 l4 P: u# r        tf.dtypes.int32,  tf.dtypes.float32, tf.dtypes.float32,, u4 f3 ^; k/ k8 i: l# E" T% l
        tf.dtypes.string, tf.dtypes.string,  tf.dtypes.string," ]+ y- O% H2 B  z4 ^$ |# f2 f
    )
1 Q$ I% z! B& A  r' h3 u   
  C9 x7 B) O4 _7 d& n2 K    outputShapes = (
, J7 }3 s( r7 b  i5 @) g% P" Z        (128,),   (128,), (128,), $ g; b0 o5 D( t: {/ |& @
        (128, 2), (),     (),
7 v: V, Z* X, Z        (),       (),     (),
% d) \1 Z9 V! k& Y9 k    )8 {( a0 D/ L4 i" w2 `3 E- [
    , {8 |( p- K5 Q; ~. f' U
    def _generator(tweet, selected_text, sentiment):
; W  Y2 l8 w) L        for tw, st, se in zip(tweet, selected_text, sentiment):  Q2 ~% T+ n8 m! h- @& a
            yield preprocess(tw, st, se)3 {. F  Z& q# `1 Y  _: b$ E: ^
   
1 h. p; J7 V+ q6 g- x    def __new__(cls, tweet, selected_text, sentiment):
) B: _1 ]1 a7 A6 y# V        return tf.data.Dataset.from_generator(2 R1 w% h' e) f# D* L7 c) n# L- t
            cls._generator,
0 R% o# N/ ^- a: G; n            output_types=cls.outputTypes,( b. Y, ?) h; f: K; b
            output_shapes=cls.outputShapes,$ o" I0 d7 |8 ?) W% M
            args=(tweet, selected_text, sentiment)  i7 r4 ^) r( J. h; }
        )- N' q1 u5 c; \: C6 m: y, }) J" b
    8 ~5 `' \1 T- U# j5 f4 g4 ~
    @staticmethod0 n2 f3 q3 [, b, l, y$ {: a
    def create(dataframe, batch_size, shuffle_buffer_size=-1):
. u3 N' \- v5 U        dataset = TweetDataset(& N; }: a3 R0 C4 ?6 j
            dataframe.text.values, . b% M6 Z% F2 a: w* j
            dataframe.selected_text.values, ! S. r4 a$ d* c0 `
            dataframe.sentiment.values: E$ o+ D, l2 V
        )
+ ?/ e/ q* |+ ], `/ n        dataset = dataset.cache()2 q5 C; `/ e" E7 \
        if shuffle_buffer_size != -1:# J' s& b# i1 v1 B
            dataset = dataset.shuffle(shuffle_buffer_size)
: i$ s: o/ {1 V/ r. k  T        dataset = dataset.batch(batch_size)" ]4 C0 L1 _5 b5 l/ a
        dataset = dataset.prefetch(tf.data.experimental.AUTOTUNE)& J$ [( y8 S# F6 t, O7 Z
        return dataset
/ e+ A" Z% g7 _& K9 S( I; T( z定义模型
  I0 O$ y7 l/ x  T5 q我们使用BERT模型来进行这次竞赛,这里对BERT模型做一些简单的介绍。
. Z) o( a2 L3 }9 [! [BERT的全称是Bidirectional Encoder Representation from Transformers,即双向Transformer的Encoder,因为decoder是不能获要预测的信息的。3 y+ ~; [! r7 m7 ?3 _/ G* [: B* R# N
模型的主要创新点都在pre-train方法上,即用了Masked LM和Next Sentence Prediction两种方法分别捕捉词语和句子级别representation。
8 M* \5 I8 |- W. s" F  }% l3 m3 Y8 mBERT主要特点如下:
/ R8 H9 t1 q% n' l" B! d9 I  [
2 r. N. z5 X0 Z7 T使用了Transformer作为算法的主要框架,Trabsformer能更彻底的捕捉语句中的双向关系;
! n$ I% ^9 X! I9 t% R' p* U4 }0 k
; L0 h8 f4 X3 U8 w) O( ?使用了Mask Language Model 和 Next Sentence Prediction的多任务训练目标;4 {4 i3 v1 Q0 u3 B) O7 c5 M

1 \& z$ ]7 T2 e- g/ S; W: X# y使用更强大的机器训练更大规模的数据,Google开源了BERT模型,我们可以直接使用BERT作为Word2Vec的转换矩阵并高效的将其应用到自己的任务中。( {; E$ ~7 X9 o( n

6 F, X3 X5 I$ T/ |0 ^7 ~" X7 `+ d: H
BERT的本质是在海量的语料基础上,运行自监督学习方法让单词学习得到一个较好的特征表示。
" p+ O3 J0 J2 n6 f' ?在之后特定任务中,可以直接使用BERT的特征表示作为该任务的词嵌入特征。所以BERT提供的是一个供其它任务迁移学习的模型,该模型可以根据任务微调或者固定之后作为特征提取器。
  U5 b1 y" ?' ]) c, v5 \+ N7 @在竞赛中,我们定义了一个BertModel类,里面使用TFBertPreTrainedModel来进行推理。
+ q) Z3 x! n' z& e1 jBERT的输出我们保存在hidden_states中,然后将这个得到的hidden_states结果在加入到Dense Layer,最后输出我们需要提取的表示情感的文字的起始位置跟结束位置。
' H3 O9 `/ K  Q' q+ T9 F% M( N4 M这两个位置信息就是我们需要从原文中提取的词向量的位置。
2 l8 {6 ]# n# b  h; Q" H& ]" L3 O; o* p, P& n2 s8 ]# A6 W
class BertModel(TFBertPreTrainedModel):
! E; Z- A  \/ M5 `; d6 C9 I  u0 F   
  h/ R. f$ }* ?6 E/ N, a# drop out rate, 防止过拟合
' ?  k( C2 J8 W9 r    dr = 0.1
% _5 `1 Z3 g" @  p9 Z# hidden state数量
. i: d2 F( b1 w' B( y0 u    hs = 2
- [/ Y) E! K% d7 t) u   
& V- F0 [+ @; }5 r    def __init__(self, config, *inputs, **kwargs):
, W& `/ G: b9 W5 K        super().__init__(config, *inputs, **kwargs), ?3 e6 C4 `- M8 `2 p
        
2 \7 q8 q" D+ z2 s' P        self.bert = TFBertMainLayer(config, name="bert")
! p8 ?1 B8 N7 Y& e; @3 r2 J        self.concat = L.Concatenate()5 M$ T3 F" B) |+ F& v- }5 }
        self.dropout = L.Dropout(self.dr)1 }) K* q# n. a; E
        self.qa_outputs = L.Dense(9 {  Z, P1 _2 S$ ]6 G
            config.num_labels,
9 d  O9 v" X8 P) T% m  X' C            kernel_initializer=TruncatedNormal(stddev=config.initializer_range),' Z$ |. A8 O5 i" L- @* {0 D& N+ ]
            dtype='float32',
0 S( s5 g" n1 g3 @0 r5 O* \2 e            name="qa_outputs")6 e5 N1 @5 x3 z5 T# |) U
        
- Y% Q. t( Q1 \- p+ U( L/ L+ A    @tf.function
, l/ R$ n9 ~4 o    def call(self, inputs, **kwargs):2 A& S% R' L6 K' L/ q+ p- `! k" ~
        _, _, hidden_states = self.bert(inputs, **kwargs): ?- ~4 X: F/ |+ L$ j" t, b/ `
        9 _6 Z6 M, N  ]5 t# Z( `0 ^
        hidden_states = self.concat([
6 i+ L4 f& _: E, r' ]; A            hidden_states[-i] for i in range(1, self.hs+1)  M3 z  P# |; A8 M
        ])2 F; A6 P! J8 {, G" z5 u9 B3 ]
        # @4 J+ h, U' X1 b
        hidden_states = self.dropout(hidden_states, training=kwargs.get("training", False))
% h5 @! o) I/ v4 J8 ]        logits = self.qa_outputs(hidden_states)
- e7 x$ |1 ?  e9 C9 F8 h        start_logits, end_logits = tf.split(logits, 2, axis=-1)
: y; @# G8 [: V; c        start_logits = tf.squeeze(start_logits, axis=-1)% }6 \/ O5 H( C- W0 m0 j
        end_logits = tf.squeeze(end_logits, axis=-1); Y% a' d+ m5 O8 S' E
        8 P, W) t6 v2 L5 Q* W
        return start_logits, end_logits
+ Q% U4 m# a0 l/ a% U定义训练函数
+ I) O2 z9 O! q, ]- b6 A* O, z! B8 Z! M; G
def train(model, dataset, loss_fn, optimizer):
9 ~. s& R' S3 e. [1 N! y6 F    " E  b' J* S7 b8 X8 h. h" U
    @tf.function) D. T; k6 B# E4 `# ~0 {) r/ V
    def train_step(model, inputs, y_true, loss_fn, optimizer):$ I1 W- P* z. _7 W
        with tf.GradientTape() as tape:) U& ?! b: I6 i8 h) L0 |
            y_pred = model(inputs, training=True)
, _1 v3 u. _" w( S# V            loss  = loss_fn(y_true[0], y_pred[0])
5 D, C$ x+ I! E4 J3 k: v7 D/ e5 p            loss += loss_fn(y_true[1], y_pred[1])% X% ]- n$ P) m: m
            scaled_loss = optimizer.get_scaled_loss(loss)7 c7 i. ^# k. ^6 k4 s9 x. [! T
   
6 w* @/ k; U* p% ?        scaled_gradients = tape.gradient(scaled_loss, model.trainable_variables)# ?8 g  k/ I+ q2 T
        gradients = optimizer.get_unscaled_gradients(scaled_gradients)
4 z# b) ^0 t" q        optimizer.apply_gradients(zip(gradients, model.trainable_variables))
6 a% \7 \) b4 b7 k        return loss, y_pred
# [; e0 z" B+ V  t" f( D    epoch_loss = 0./ |' c& Y5 q) S3 ~6 C( A
    for batch_num, sample in enumerate(dataset):# }- I  J  M) k. k7 H9 |& t' C
        loss, y_pred = train_step(model, sample[:3], sample[4:6], loss_fn, optimizer)7 t0 Y5 r$ G/ Z- `9 t+ S
        epoch_loss += loss
1 A4 w9 e6 X5 X5 j$ X: |        print(
! a! b. K- V( ~  u+ @1 e0 V            f"training ... batch {batch_num+1:03d} : "- m/ `9 a3 X5 s) v# g& @+ z
            f"train loss {epoch_loss/(batch_num+1):.3f} ",
0 j* z; H. x) y9 G& ~            end='\r')
  G" U: x5 w, K' f# s. O" S: _  W: G定义预制函数$ @5 U. r/ G/ n& _
def predict(model, dataset, loss_fn, optimizer):* t" }8 V3 r( }! `0 m1 d5 s- `; v
   
% U7 I* A% M( O    @tf.function" u/ {; E. H2 v3 b2 y0 o
    def predict_step(model, inputs):
/ v& o, V. L2 h/ t6 ^. y        return model(inputs)
' \9 Z! k6 q& B. t0 T) N: B8 X" N        3 N! g9 S8 T) I  ?
    def to_numpy(*args):
5 N7 g) L- {4 p        out = []
3 O* _  M& r3 T3 O8 R! B        for arg in args:# S% c* z+ O6 |4 _
            if arg.dtype == tf.string:
/ H4 M  \7 X, c                arg = [s.decode('utf-8') for s in arg.numpy()]' `% c2 B( @8 i! s
                out.append(arg)
+ v1 M/ q7 }6 h9 S& f9 I            else:
& F$ r5 R( o% o0 e# [* m                arg = arg.numpy()+ e5 K! J! M; I* ~0 u" Z4 G1 k, y
                out.append(arg)9 M+ Q6 U" Q& C1 k5 R' R( g7 Y' M
        return out% M: W6 ^4 l  w- {. Q
   
9 ?" V& d0 q& @) R( i    offset = tf.zeros([0, 128, 2], dtype=tf.dtypes.int32)
) a( y' o4 w# B6 X5 F! y    text = tf.zeros([0,], dtype=tf.dtypes.string)# y! m2 F) y: H5 l' g
    selected_text = tf.zeros([0,], dtype=tf.dtypes.string)
. ~- P: i2 t* H) V    sentiment = tf.zeros([0,], dtype=tf.dtypes.string)
5 s, j5 z7 `' L) N; B' w! N( t    pred_start = tf.zeros([0, 128], dtype=tf.dtypes.float32)1 g* ?; b' [  x3 }
    pred_end = tf.zeros([0, 128], dtype=tf.dtypes.float32)
" \( {2 p3 T9 t; ?& r; L, C    : h4 u3 N$ c6 Z7 g
    for batch_num, sample in enumerate(dataset):. S; E- s3 b* d6 k
        8 Q/ T/ [% W9 f" j2 W9 F# R
        print(f"predicting ... batch {batch_num+1:03d}"+" "*20, end='\r')/ H5 G- \' _' {, ~$ U/ z4 [7 m* }
        
& C/ I  V6 l- |* N6 Q' E' N8 {, C        y_pred = predict_step(model, sample[:3]), E& `# A4 S+ `0 I9 c6 n/ W' T
        
$ p, e; C) J) s" t1 H6 n: \        # add batch to accumulators
$ C7 f8 i1 ?* {2 j& Z        pred_start = tf.concat((pred_start, y_pred[0]), axis=0)
/ P! w' p% B3 `  j$ i5 P$ f        pred_end = tf.concat((pred_end, y_pred[1]), axis=0)% `6 v6 u0 j. A: u6 O7 Q/ N) h
        offset = tf.concat((offset, sample[3]), axis=0)
& R: `/ e/ ~3 q, |        text = tf.concat((text, sample[6]), axis=0)
* E2 A2 z- t; B  B        selected_text = tf.concat((selected_text, sample[7]), axis=0)
8 b( }( f% ]1 ^* x4 q7 r        sentiment = tf.concat((sentiment, sample[8]), axis=0)( O6 a( h: U' e/ Q, H: }1 `
    9 O, k' s7 P. f# M; F( U
    pred_start, pred_end, text, selected_text, sentiment, offset = \
  J) H8 y$ @$ Q/ C  t( ~        to_numpy(pred_start, pred_end, text, selected_text, sentiment, offset)- y7 c; n, d+ |+ K+ J) V! |
   
3 M, I1 E, ^0 c- Q- i; i% D& [$ ~    return pred_start, pred_end, text, selected_text, sentiment, offset
( Q) S/ h( `1 z- I7 |; T判断函数( r5 L% |6 j$ s8 e  f0 X7 r/ s
这个竞赛采用单词级Jaccard系数,计算公式如下
/ K# w9 v) y9 {% O' P  t
, X' F( ^  m( E$ w9 @+ qJaccard系数计算的是你预测的单词在数据集中的个数,/ b! m& B( I) [+ X! \/ L8 n) E
def jaccard(str1, str2):3 e. P# _( }" N4 U% h# q! J
    a = set(str1.lower().split())5 B5 R9 q5 Q. O. b5 X
    b = set(str2.lower().split())
: K5 ~0 D( |9 e5 {    c = a.intersection(b)
" S" L" A# G' J6 G6 U6 _    return float(len(c)) / (len(a) + len(b) - len(c)), M& |( ?- S+ ^. y+ P
定义预测结果解码函数; F" H  l7 p* M. |4 e, G: k
解码函数通过模型预测拿到的start和end的index位置信息,然后和之前拿到的词向量在样本句子中的位置进行比较,将这个区间内的所有的单词都提取出来作为我们的预测结果。
: G$ |5 L, c# i# y% f3 |) Z
. B, b9 _$ Y/ d! udef decode_prediction(pred_start, pred_end, text, offset, sentiment):! i* b; H/ l* _+ P# b( s
   
0 s2 H0 y9 F* |9 b) h    def decode(pred_start, pred_end, text, offset):3 c6 D; I9 w' v, x* h! d$ P8 [
        decoded_text = ""5 u  F5 E/ D7 o$ G; S4 x! }/ J
        for i in range(pred_start, pred_end+1):
& i3 z' `! m0 {  ~            decoded_text += text[offset[0]:offset[1]]
# Y0 F; K4 m" n1 E% s: D  V8 O            if (i+1)  idx_end:
8 }  E5 f9 p% w! G                idx_end = idx_start
& C! V; D+ d+ J# P            decoded_text = str(decode(idx_start, idx_end, text, offset))  c6 s) {  L. r0 U- ]1 X' O  v
            if len(decoded_text) == 0:
& i% J; o( D3 f$ k9 y                decoded_text = text
% J, R% N. E+ k0 [1 t  Q        decoded_predictions.append(decoded_text)
4 }# A- ~6 P/ E( O  C) P   
: i/ A- ?: ~4 T( N& W% V1 |, }    return decoded_predictions
& ~0 J3 @% X* U) a3 M7 _9 m* g开始训练" t( H8 W0 U4 `
将训练数据分成5个folds,每个folds训练5个epoch,使用adam优化器,learning rate设置成3e-5,batch size使用32。# G5 ]. O8 F& }# F

1 p: b9 m# G; cnum_folds = 55 L5 V' Z! v& w) ~2 I
num_epochs = 5; v/ a8 Z2 e5 M9 |! V* P2 \
batch_size = 32
2 M5 {! N2 V, J4 blearning_rate = 3e-5. b' c7 M/ J6 [" U4 l  O
optimizer =  tf.keras.optimizers.Adam(learning_rate)
5 Q6 s2 H: F# e- K. Y7 yoptimizer = tf.keras.mixed_precision.experimental.LossScaleOptimizer(
: X. g& }- _# H: p% V    optimizer, 'dynamic')1 M% e4 O1 j4 S$ t1 V* E& \
config = BertConfig(output_hidden_states=True, num_labels=2)' d) w5 ]5 J  `
model = BertModel.from_pretrained(PATH, config=config)
, a( E6 {2 a3 Z$ h; l$ R( q# Jloss_fn = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)* O$ @0 x$ f; ~0 K# ?) h+ m
kfold = model_selection.KFold(2 B% j1 {( C$ D# B7 y0 t
    n_splits=num_folds, shuffle=True, random_state=42)# t2 a. d1 ]+ {! W; }7 u) p+ i
test_preds_start = np.zeros((len(test_df), 128), dtype=np.float32)% g0 e# d5 r/ u1 d' R
test_preds_end = np.zeros((len(test_df), 128), dtype=np.float32)
: w/ ?6 f& L# {# Efor fold_num, (train_idx, valid_idx) in enumerate(kfold.split(train_df.text)):
* }! `# F; N( D+ U" y- Q% `3 i    print("\nfold %02d" % (fold_num+1))5 f* t2 J$ K, o' p4 G2 ?8 i
   
5 g$ g6 N/ q7 \, W: ^# 创建train, valid, test数据集( D5 d8 ^+ S( Z
    train_dataset = TweetDataset.create(
1 _! r2 E+ q2 ?9 J3 b5 m( }) W        train_df.iloc[train_idx], batch_size, shuffle_buffer_size=2048)
$ k1 F- R* Z/ t    valid_dataset = TweetDataset.create(/ |, G/ H3 {9 h# g4 P
        train_df.iloc[valid_idx], batch_size, shuffle_buffer_size=-1)+ @2 L1 y( }! |$ b% D# W
    test_dataset = TweetDataset.create(/ a# W  Z: h+ G  H3 k3 x
        test_df, batch_size, shuffle_buffer_size=-1)2 C0 _1 i8 l1 c& h1 [5 }* B
    - [4 T+ Z: S; j9 C! p
    best_score = float('-inf')
& A) K1 z) Q' Y1 g# g& T    for epoch_num in range(num_epochs):2 `. A# x  Y; ]
        print("\nepoch %03d" % (epoch_num+1))/ D+ d7 f  l! P2 W3 q2 |, p+ n
        0 q" W+ Y/ q) Z5 e1 y  N0 N+ O
        train(model, train_dataset, loss_fn, optimizer)
, X! R- I' z0 B6 t" ~        $ I0 a/ a$ t( l" b; T: D
        pred_start, pred_end, text, selected_text, sentiment, offset = \
2 h, c7 q2 ]# U9 J            predict(model, valid_dataset, loss_fn, optimizer)* j0 V/ \3 t/ ~; ]
        
' I6 T( e2 T- C) ~- y        selected_text_pred = decode_prediction(* a( g) o/ K) c* t* W, q$ }, h  L
            pred_start, pred_end, text, offset, sentiment)
; r9 l% y( g9 @. H  p% H+ K- N        jaccards = []
# O, \0 [2 v+ A) O; t5 T7 f        for i in range(len(selected_text)):! G! C) [0 L$ A; m5 Z; `! v
            jaccards.append(
6 L: `: ~2 F- t+ a- c                jaccard(selected_text, selected_text_pred))# l- @$ g! v8 m% e0 Y+ [) H
        
+ _) O$ s1 ^# g/ z        score = np.mean(jaccards)0 m* D; n7 G( s. O
        print(f"valid jaccard epoch {epoch_num+1:03d}: {score}"+" "*15)
5 u8 @3 h9 ~) V2 I        
3 i( Q' [' M" l- b        if score > best_score:
, I+ Z* H% ^/ ]5 d            best_score = score
0 |7 N* j) c1 R: k$ Q            + Z: h4 g  W- w6 e) T; H3 L
# predict test set
5 ~, ?9 x8 V2 i2 ?/ v2 Z            test_pred_start, test_pred_end, test_text, _, test_sentiment, test_offset = \
4 V+ Y* [5 `. I# ~: m. }  n                predict(model, test_dataset, loss_fn, optimizer)0 O9 J. }! @: I0 x" G
    3 E, ]' b# s; _
    test_preds_start += test_pred_start * 0.27 k  E4 M# C( A: A. G4 _
    test_preds_end += test_pred_end * 0.2
& p  k( n/ s" H  J5 H   
+ O1 [1 k" p) I. R: v  @4 z7 q# 重置模型,避免OOM
  ^+ L, |% b$ b" G: d7 y  H    session = tf.compat.v1.get_default_session()
# |  e$ U6 ~) Z( a% ?    graph = tf.compat.v1.get_default_graph()! l1 n4 ?$ n9 W( t
    del session, graph, model
0 O: n$ P0 b+ f, ?8 U- @6 m    model = BertModel.from_pretrained(PATH, config=config)) _( M/ p/ _( x  w5 y" z
预测测试数据,并生成提交文件; |: r3 n; t" n$ c  D
selected_text_pred = decode_prediction(
0 ]) h) K; V" U6 u+ H" U    test_preds_start, test_preds_end, test_text, test_offset, test_sentiment)9 `3 {& f" ~2 q9 ^0 v" G' d2 E
def f(selected):3 Q: T* a: @( `* C; v$ O! N
    return " ".join(set(selected.lower().split()))9 M3 J1 b9 h. V) Y; g" C* i
submission_df.loc[:, 'selected_text'] = selected_text_pred
, p% h: E, r: z/ T0 zsubmission_df['selected_text'] = submission_df['selected_text'].map(f)5 t3 ]8 Y" v; n9 V. z$ L* x
submission_df.to_csv("submission.csv", index=False)& c1 O6 m( E3 A
这个方案在提交的时候在553个队伍中排名153位, 分数为0.68。
: |8 l/ K3 l0 A0 S4 ~
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

lf0517 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    1