英文文本分类实战总结

之前参加了一个英文文本的分类比赛。比赛结束到了过年,加上开学又有一些事情,所以总结的工作就一直没有进行。现在空了一些,所以把之前的工作写一写,比赛中用到的代码也会放到github上。

对这个比赛的任务介绍以及数据可以看这篇写过的文章。简单说,就是一个英文文本的有监督二分类问题,对一条英文问句判断是否为一条有意义的问题。这篇文章里还对数据处理介绍了两种简单的方法,一个是embedding词向量,另一个是基于tfidf的统计特征抽取,两种方法分别是深度学习和机器学习在nlp领域对特征表示上常用的方法。整体上使用的Keras框架。

好,下面主要从两个方面对我们做的工作进行总结,一是数据处理和句子表示,另一个是模型的选取和融合。

(一)数据处理
1.数据清洗

对数据的处理首先是简单的数据清洗,包括标点符号的额整理,颜文字等特殊符号的整理,最后是大小写转换。因为这个比赛是不允许使用额外数据的,没法使用额外词库进行拼写错误的检查,所以拼写错误我们使用了一个手写的小字典进行纠正。
在进行完数据清洗后,我们使用Keras的tokenizer接口将句子进行符号化,为将来的向量化做准备。
下面是数据清洗的代码:这里贴两个数据清洗的代码

puncts = [',', '.', '"', ':', ')', '(', '-', '!', '?', '|', ';', "'", '$', '&', '/', '[', ']', '>', '%', '=', '#', '*', '+', '\\', '•',  '~', '@', '£', 
 '·', '_', '{', '}', '©', '^', '®', '`',  '<', '→', '°', '€', '™', '›',  '♥', '←', '×', '§', '″', '′', 'Â', '█', '½', 'à', '…', 
 '“', '★', '”', '–', '●', 'â', '►', '−', '¢', '²', '¬', '░', '¶', '↑', '±', '¿', '▾', '═', '¦', '║', '―', '¥', '▓', '—', '‹', '─', 
 '▒', ':', '¼', '⊕', '▼', '▪', '†', '■', '’', '▀', '¨', '▄', '♫', '☆', 'é', '¯', '♦', '¤', '▲', 'è', '¸', '¾', 'Ã', '⋅', '‘', '∞', 
 '∙', ')', '↓', '、', '│', '(', '»', ',', '♪', '╩', '╚', '³', '・', '╦', '╣', '╔', '╗', '▬', '❤', 'ï', 'Ø', '¹', '≤', '‡', '√', ]

punct_mapping = {"‘": "'", "₹": "e", "´": "'", "°": "", "€": "e", "™": "tm", "√": " sqrt ", "×": "x", "²": "2", "—": "-", "–": "-", "’": "'", "_": "-", "`": "'", 
                 '“': '"', '”': '"', '“': '"', "£": "e", '∞': 'infinity', 'θ': 'theta', '÷': '/', 'α': 'alpha', '•': '.', 'à': 'a', '−': '-', 'β': 'beta', '∅': '', 
                 '³': '3', 'π': 'pi', }

def clean_text(x):
    x = str(x)
    for p in punct_mapping:
        x = x.replace(p, punct_mapping[p])
    for punct in puncts:
        x = x.replace(punct, f' {punct} ')
    return x

def clean_numbers(x):
    x = re.sub('[0-9]{5,}', '#####', x)
    x = re.sub('[0-9]{4}', '####', x)
    x = re.sub('[0-9]{3}', '###', x)
    x = re.sub('[0-9]{2}', '##', x)
    return x

mispell_dict = {"ain't": "is not", "aren't": "are not","can't": "cannot"} // 这里简写了,为了看起来方便些,完整的字典可以去github上看

def _get_mispell(mispell_dict):
    mispell_re = re.compile('(%s)' % '|'.join(mispell_dict.keys()))
    return mispell_dict, mispell_re

mispellings, mispellings_re = _get_mispell(mispell_dict)

def replace_typical_misspell(text):
    def replace(match):
        return mispellings[match.group(0)]
    return mispellings_re.sub(replace, text)

def load_and_prec():
    train_df = pd.read_csv("../input/train.csv")
    test_df = pd.read_csv("../input/test.csv")
    
    # lower
    train_df["question_text"] = train_df["question_text"].apply(lambda x: x.lower())
    test_df["question_text"] = test_df["question_text"].apply(lambda x: x.lower())

    # Clean the text
    train_df["question_text"] = train_df["question_text"].apply(lambda x: clean_text(x))
    test_df["question_text"] = test_df["question_text"].apply(lambda x: clean_text(x))

    # Clean numbers
    train_df["question_text"] = train_df["question_text"].apply(lambda x: clean_numbers(x))
    test_df["question_text"] = test_df["question_text"].apply(lambda x: clean_numbers(x))

    # Clean speelings
    train_df["question_text"] = train_df["question_text"].apply(lambda x: replace_typical_misspell(x))
    test_df["question_text"] = test_df["question_text"].apply(lambda x: replace_typical_misspell(x))
def clean_text(text):
    print(text)
    
    ## Remove puncuation
    text = text.translate(string.punctuation)
    
    ## Convert words to lower case and split them
    text = text.lower()
    
    ## Remove stop words
    #text = text.split()
    #stops = set(stopwords.words("english"))
    #text = [w for w in text if not w in stops and len(w) >= 3]
    
    #text = " ".join(text)

    # Clean the text
    text = re.sub(r"[^A-Za-z0-9^,!.\/'+-=]", " ", text)
    text = re.sub(r"what's", "what is ", text)
    text = re.sub(r"\'s", " ", text)
    text = re.sub(r"\'ve", " have ", text)
    text = re.sub(r"n't", " not ", text)
    text = re.sub(r"i'm", "i am ", text)
    text = re.sub(r"\'re", " are ", text)
    text = re.sub(r"\'d", " would ", text)
    text = re.sub(r"\'ll", " will ", text)
    text = re.sub(r",", " ", text)
    text = re.sub(r"\.", " ", text)
    text = re.sub(r"!", " ! ", text)
    text = re.sub(r"\/", " ", text)
    text = re.sub(r"\^", " ^ ", text)
    text = re.sub(r"\+", " + ", text)
    text = re.sub(r"\-", " - ", text)
    text = re.sub(r"\=", " = ", text)
    text = re.sub(r"'", " ", text)
    text = re.sub(r"(\d+)(k)", r"\g<1>000", text)
    text = re.sub(r":", " : ", text)
    text = re.sub(r" e g ", " eg ", text)
    text = re.sub(r" b g ", " bg ", text)
    text = re.sub(r" u s ", " american ", text)
    text = re.sub(r"\0s", "0", text)
    text = re.sub(r" 9 11 ", "911", text)
    text = re.sub(r"e - mail", "email", text)
    text = re.sub(r"j k", "jk", text)
    text = re.sub(r"\s{2,}", " ", text)
    
    #text = text.split()
    #stemmer = SnowballStemmer('english')
    #stemmed_words = [stemmer.stem(word) for word in text]
    #text = " ".join(stemmed_words)

    print(text)
    print("")
    return text

在数据清洗完成后,就可以进行词向量的映射了。有两种方式,一种是使用预训练好的词向量,比如glove,paragram,wiki-news以及Google等等。另一种是自训练词向量,就是我们针对自己的数据单独训练词向量。我们两种方式都使用过,分别进行介绍。

2.词向量表示
2.1预训练词向量

因为我们有多个预训练的词向量可选,所以需要做一个选择工作,也就是哪一个词向量效果会更好一些。这一点上之前写了一篇文章,可以参考,这里就不多说了。通过分析,glove,paragram,wiki-news这三个词向量的表示是较好的,所以实验中使用了这三个。三个词向量的使用方式都是一样的,所以这里只贴出paragram的代码,其他的可以看github。

def load_para(word_index):
    EMBEDDING_FILE = '../input/embeddings/paragram_300_sl999/paragram_300_sl999.txt'
    def get_coefs(word,*arr): return word, np.asarray(arr, dtype='float32')
    embeddings_index = dict(get_coefs(*o.split(" ")) for o in open(EMBEDDING_FILE, encoding="utf8", errors='ignore') if len(o)>100)

    all_embs = np.stack(embeddings_index.values())
    emb_mean,emb_std = -0.0053247833,0.49346462
    embed_size = all_embs.shape[1]
    print(emb_mean,emb_std,"para")

    # word_index = tokenizer.word_index
    nb_words = min(max_features, len(word_index))
    embedding_matrix = np.random.normal(emb_mean, emb_std, (nb_words, embed_size))
    for word, i in word_index.items():
        if i >= max_features: continue
        embedding_vector = embeddings_index.get(word)
        if embedding_vector is not None: embedding_matrix[i] = embedding_vector
    
    return embedding_matrix
2.2自训练词向量

自训练的词向量这篇文章中有介绍,其中也贴了完整的代码,这里就不多说了。使用自训练的词向量要比使用预训练的词向量效果要好一些,毕竟是更为贴近语料的。

3.句向量表示

上面词向量的表示方法会将每一个单词表示成一个300维的向量,这样一个句子就被表示成了一个二维的矩阵。但有时候我们的需求是让一个句子被表示为一个一维的向量,也就是我们这里要说的句向量的表示方法。句向量的表示方法有很多,也是
工业界经常使用的,面试时也多次被问到对这方面的了解,有基于统计的方法,也有基于神经网络的方法,这里就不展开讲了,感兴趣的同学可以查一下。
这里想说的是在这个比赛中用到的一种句向量的表示方法,也是一个创新的地方,是将TFIDF与embedding词向量融合的表示方法。简单来说就是将tfidf视为一种权重,将embedding词向量进行加权表示,以得到一种句向量的表示。这一部分在之前也介绍过,具体可以查看这两篇文章:文本分类实战—tfidf+embedding—1,文本分类实战–-tfidf+embedding—2

到这里,我们就把数据的表示方法介绍完了,下面的工作就是将得到的表示向量放大分类模型中进行分类训练了。工作主要集中在模型的选取以及调参上。

(二)模型的选取及融合
1.模型的选取

模型的选择上我们开始尝试了很多模型,总的来说,深度学习模型会比机器学习模型效果好,所最终使用的模型是深度的,但他们内部之间的差异不大,一般说来是LSTM和GRU比CNN好一些,胶囊网络效果与LSTM差不多,加入attention机制会比不好一些。
这一部分我们只把建立模型的主体代码贴出来,整个项目的代码可以从后面给出的github地址中查看。

下面贴出实验中用到的模型:

1.1 xgboost

xgboost是结合tfidf以及句子特征做的。完整代码可以参见:https://github.com/pnnngchg/kaggle_quora/blob/master/xgboost-tfidf.ipynb

def build_xgb(train_X, train_y, valid_X, valid_y=None, subsample=0.75):

    xgtrain = xgb.DMatrix(train_X, label=train_y)
    if valid_y is not None:
        xgvalid = xgb.DMatrix(valid_X, label=valid_y)
    else:
        xgvalid = None
    
    model_params = {}
    # binary 0 or 1
    model_params['objective'] = 'binary:logistic'
    # eta is the learning_rate, [default=0.3]
    model_params['eta'] = 0.3
    # depth of the tree, deeper more complex.
    model_params['max_depth'] = 6
    # 0 [default] print running messages, 1 means silent mode
    model_params['silent'] = 1
    model_params['eval_metric'] = 'auc'
    # will give up further partitioning [default=1]
    model_params['min_child_weight'] = 1
    # subsample ratio for the training instance
    model_params['subsample'] = subsample
    # subsample ratio of columns when constructing each tree
    model_params['colsample_bytree'] = subsample
    # random seed
    model_params['seed'] = 2018
    # imbalance data ratio
    #model_params['scale_pos_weight'] = 
    
    # convert params to list
    model_params = list(model_params.items())
    
    return xgtrain, xgvalid, model_params


def train_xgboost(xgtrain, xgvalid, model_params, num_rounds=500, patience=20):
    
    if xgvalid is not None:
        # watchlist what information should be printed. specify validation monitoring
        watchlist = [ (xgtrain, 'train'), (xgvalid, 'test') ]
        #early_stopping_rounds = stop if performance does not improve for k rounds
        model = xgb.train(model_params, xgtrain, num_rounds, watchlist, early_stopping_rounds=patience)
    else:
        model = xgb.train(model_params, xgtrain, num_rounds)
    
    return model
1.2 lightgbm

lightgbm对数据的处理方式以及表示方式与xgboost是一样的,只是模型进行了改变。https://github.com/pnnngchg/kaggle_quora/blob/master/lightgbm.ipynb

# create dataset for lightgbm  
lgb_train = lgb.Dataset(input_train, y_train )  
lgb_eval = lgb.Dataset(input_valid, y_val, reference=lgb_train)  
# specify your configurations as a dict  
params = {  
    'boosting_type': 'gbdt',  
    'objective': 'binary',  
    'metric': {'binary_logloss', 'auc'},  #二进制对数损失
    'num_leaves': 5,  
    'max_depth': 6,  
    'min_data_in_leaf': 450,  
    'learning_rate': 0.1,  
    'feature_fraction': 0.9,  
    'bagging_fraction': 0.95,  
    'bagging_freq': 5,  
    'lambda_l1': 1,    
    'lambda_l2': 0.001,  # 越小l2正则程度越高  
    'min_gain_to_split': 0.2,  
    'verbose': 5,  
    'is_unbalance': True  
}  

# train  
print('Start training...')  
gbm = lgb.train(params,  
                lgb_train,  
                num_boost_round=10000,  
                valid_sets=lgb_eval,  
                early_stopping_rounds=500)  

print('Start predicting...')  

preds = gbm.predict(input_valid, num_iteration=gbm.best_iteration)  # 输出的是概率结果 
1.3 胶囊网络

胶囊网络模型准确率很高,但是训练时间也很长,主要是因为动态路由机制需要迭代,并且对向量的处理要比对标量的计算规模更大。从胶囊网络开始下面的网络都是深度学习的网络了,使用的是Keras框架,数据的表示也就都是二维的了。
https://github.com/pnnngchg/kaggle_quora/blob/master/capsule.ipynb

def squash(x, axis=-1):
    s_squared_norm = K.sum(K.square(x), axis, keepdims=True)
    scale = K.sqrt(s_squared_norm + K.epsilon())
    return x / scale

class Capsule(Layer):
    def __init__(self, num_capsule, dim_capsule, routings=3, kernel_size=(9, 1), share_weights=True,
                 activation='default', **kwargs):
        super(Capsule, self).__init__(**kwargs)
        self.num_capsule = num_capsule
        self.dim_capsule = dim_capsule
        self.routings = routings
        self.kernel_size = kernel_size
        self.share_weights = share_weights
        if activation == 'default':
            self.activation = squash
        else:
            self.activation = Activation(activation)

    def build(self, input_shape):
        super(Capsule, self).build(input_shape)
        input_dim_capsule = input_shape[-1]
        if self.share_weights:
            self.W = self.add_weight(name='capsule_kernel',
                                     shape=(1, input_dim_capsule,
                                            self.num_capsule * self.dim_capsule),
                                     # shape=self.kernel_size,
                                     initializer='glorot_uniform',
                                     trainable=True)
        else:
            input_num_capsule = input_shape[-2]
            self.W = self.add_weight(name='capsule_kernel',
                                     shape=(input_num_capsule,
                                            input_dim_capsule,
                                            self.num_capsule * self.dim_capsule),
                                     initializer='glorot_uniform',
                                     trainable=True)

    def call(self, u_vecs):
        if self.share_weights:
            u_hat_vecs = K.conv1d(u_vecs, self.W)
        else:
            u_hat_vecs = K.local_conv1d(u_vecs, self.W, [1], [1])

        batch_size = K.shape(u_vecs)[0]
        input_num_capsule = K.shape(u_vecs)[1]
        u_hat_vecs = K.reshape(u_hat_vecs, (batch_size, input_num_capsule,
                                            self.num_capsule, self.dim_capsule))
        u_hat_vecs = K.permute_dimensions(u_hat_vecs, (0, 2, 1, 3))
        # final u_hat_vecs.shape = [None, num_capsule, input_num_capsule, dim_capsule]

        b = K.zeros_like(u_hat_vecs[:, :, :, 0])  # shape = [None, num_capsule, input_num_capsule]
        for i in range(self.routings):
            b = K.permute_dimensions(b, (0, 2, 1))  # shape = [None, input_num_capsule, num_capsule]
            c = K.softmax(b)
            c = K.permute_dimensions(c, (0, 2, 1))
            b = K.permute_dimensions(b, (0, 2, 1))
            outputs = self.activation(tf.keras.backend.batch_dot(c, u_hat_vecs, [2, 2]))
            if i < self.routings - 1:
                b = tf.keras.backend.batch_dot(outputs, u_hat_vecs, [2, 3])

        return outputs

    def compute_output_shape(self, input_shape):
        return (None, self.num_capsule, self.dim_capsule)
1.4 CNN

给出两种CNN,一维的和二维的。因为我们最终模型融合使用的5个模型,CNN,LSTM(两个),GRU(两个),他们的整体代码在同一个ipynb文件中。https://github.com/pnnngchg/kaggle_quora/blob/master/embedding-tfidf-blend-models.ipynb

def model_conv1d():
    filters = 128
    
    inp = Input(shape=(maxlen, ))
    embed = Embedding(max_features, embed_size * 3, weights=[embedding_matrix], trainable=False)(inp)
    x = embed
    
    x = Conv1D(filters, 1, activation='relu')(x)
    x = Dropout(0.1)(x)
    
    x = Conv1D(filters, 2, activation='relu')(x)
    x = Dropout(0.1)(x)
    
    x = Conv1D(filters, 3, activation='relu')(x)
    x = Dropout(0.1)(x)
    
    x = Conv1D(filters, 5, activation='relu')(x)
    x = Dropout(0.1)(x)
    
    #x = Flatten()(x)
    x = GlobalAveragePooling1D()(x)
    
    x = Dropout(0.3)(x)
    x = Dense(128, activation='relu')(x)
    outp = Dense(1, activation="sigmoid")(x)
    model = Model(inputs=inp, outputs=outp)
    model.compile(loss='binary_crossentropy',
                  optimizer='adam',
                  metrics=['accuracy'])    
    return model
def model_cnn(embedding_matrix):  # 1 epoch enough or none
    filter_sizes = [1,2,3,5]
    num_filters = 36

#     inp = Input(shape=(maxlen,))
    inp = Input(shape=(maxlen*2, ))  # (?, 140)
    inp_emb = Lambda(row_slice, arguments={'start':0, 'end':70})(inp)  # (?, 70)  <dtype: 'float32'>
    inp_emb = Lambda(change_type)(inp_emb)  # tensor <dtype: 'int32'>
    x = Embedding(max_features, embed_size*2, weights=[embedding_matrix])(inp_emb)
    x = Reshape((maxlen, embed_size*2, 1))(x)

    maxpool_pool = []
    for i in range(len(filter_sizes)):
        conv = Conv2D(num_filters, kernel_size=(filter_sizes[i], embed_size*2),
                                     kernel_initializer='he_normal', activation='elu')(x)
        maxpool_pool.append(MaxPool2D(pool_size=(maxlen - filter_sizes[i] + 1, 1))(conv))

    z = Concatenate(axis=1)(maxpool_pool)   
    z = Flatten()(z)
    z = Dropout(0.1)(z)

    outp = Dense(1, activation="sigmoid")(z)

    model = Model(inputs=inp, outputs=outp)
    model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
    
    return model
1.5 LSTM_attention

数据表示方式是embedding词向量

def model_lstm_atten(embedding_matrix):  # 2 epoches enough
    inp = Input(shape=(maxlen,))
    x = Embedding(max_features, embed_size*2, weights=[embedding_matrix], trainable=False)(inp)
    x = Bidirectional(CuDNNLSTM(128, return_sequences=True))(x)
    x = Bidirectional(CuDNNLSTM(64, return_sequences=True))(x)
    x = Attention(maxlen)(x)
    x = Dense(64, activation="relu")(x)
    x = Dense(1, activation="sigmoid")(x)
    model = Model(inputs=inp, outputs=x)
    model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
    
    return model
1.6 GRU_attention

数据表示方式是embedding词向量结合tfidf

def model_gru_srk_atten(embedding_matrix): # 2 epoches enough
#     inp = Input(shape=(maxlen,))
    inp = Input(shape=(maxlen*2, ))  # (?, 140)
    inp_emb = Lambda(row_slice, arguments={'start':0, 'end':70})(inp)  # (?, 70)  <dtype: 'float32'>
    inp_emb = Lambda(change_type)(inp_emb)  # tensor <dtype: 'int32'>
    x = Embedding(max_features, embed_size*2, weights=[embedding_matrix])(inp_emb)
    x = Bidirectional(CuDNNGRU(64, return_sequences=True))(x)
    x = Attention(maxlen)(x) # New
    x = Dense(16, activation="relu")(x)
    x = Dropout(0.1)(x)
    x = Dense(1, activation="sigmoid")(x)
    model = Model(inputs=inp, outputs=x)
    model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
    
    return model  
1.7 LSTM

数据表示方式是embedding词向量结合tfidf

def model_lstm_du(embedding_matrix):  # 3 epoches enough
#     inp = Input(shape=(maxlen,))
    inp = Input(shape=(maxlen*2, ))  # (?, 140)
    inp_emb = Lambda(row_slice, arguments={'start':0, 'end':70})(inp)  # (?, 70)  <dtype: 'float32'>
    inp_emb = Lambda(change_type)(inp_emb)  # tensor <dtype: 'int32'>
    x = Embedding(max_features, embed_size*2, weights=[embedding_matrix])(inp_emb)
    x = Bidirectional(CuDNNGRU(64, return_sequences=True))(x)
    avg_pool = GlobalAveragePooling1D()(x)
    max_pool = GlobalMaxPooling1D()(x)
    conc = concatenate([avg_pool, max_pool])
    conc = Dense(64, activation="relu")(conc)
    conc = Dropout(0.1)(conc)
    outp = Dense(1, activation="sigmoid")(conc)
    
    model = Model(inputs=inp, outputs=outp)
    model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
    return model
1.8 GRU_attention with 3 epoch
def model_gru_atten_3(embedding_matrix):  # 3 epoches enough
#     inp = Input(shape=(maxlen,))
    inp = Input(shape=(maxlen*2, ))  # (?, 140)
    inp_emb = Lambda(row_slice, arguments={'start':0, 'end':70})(inp)  # (?, 70)  <dtype: 'float32'>
    inp_emb = Lambda(change_type)(inp_emb)  # tensor <dtype: 'int32'>
    x = Embedding(max_features, embed_size*2, weights=[embedding_matrix], trainable=False)(inp_emb)
    x = Bidirectional(CuDNNGRU(128, return_sequences=True))(x)
    x = Bidirectional(CuDNNGRU(100, return_sequences=True))(x)
    x = Bidirectional(CuDNNGRU(64, return_sequences=True))(x)
    x = Attention(maxlen)(x)
    x = Dense(1, activation="sigmoid")(x)
    model = Model(inputs=inp, outputs=x)
    model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
    
    return model

到这里,实验中主要用到的模型就介绍完了。需要注意的是,机器学习的模型都是用了特征向量和tfidf作为输入,深度学习的模型可以接受二维的输入,比如embedding和embedding与tfidf融合。

2.模型的融合

我们最终的结果是通过模型融合得出的,具体做法是,我们分别在训练集上训练5个模型,然后针对结果使用LR尝试得到每个模型的融合系数,这里的融合系数就是LR的回归系数,然后在验证集上用融合后的模型计算最佳阈值。

高准确率的模型通常会消耗更多的训练时间,在这个任务中,时间消耗最多的是胶囊网络和LSTM,CNN会快一些。为了平衡准确率和时间的问题,在模型融合时,我们选取了5个模型进行了融合。

计算融合系数的代码如下:

from sklearn.linear_model import LinearRegression
X = np.asarray([outputs[i][0] for i in range(len(outputs))])
X = X[...,0]
reg = LinearRegression().fit(X.T, y_val)
print(reg.score(X.T, y_val),reg.coef_)

计算阈值的代码如下:

pred_val_y = np.sum([outputs[i][0] * reg.coef_[i] for i in range(len(outputs))], axis = 0)
# pred_val_y = np.mean([outputs[i][0] for i in range(len(outputs))], axis = 0) # to avoid overfitting, just take average

thresholds = []
for thresh in np.arange(0.1, 0.501, 0.01):
    thresh = np.round(thresh, 2)
    res = metrics.f1_score(y_val, (pred_val_y > thresh).astype(int))
    thresholds.append([thresh, res])
    print("F1 score at threshold {0} is {1}".format(thresh, res))
    
thresholds.sort(key=lambda x: x[1], reverse=True)
best_thresh = thresholds[0][0]
print("Best threshold: ", best_thresh)

最终的结果:

pred_test_y = np.sum([outputs[i][1] * reg.coef_[i] for i in range(len(outputs))], axis = 0)
# pred_test_y = np.mean([outputs[i][1] for i in range(len(outputs))], axis = 0)

pred_test_y = (pred_test_y > best_thresh).astype(int)
test_df = pd.read_csv("../input/test.csv", usecols=["qid"])
out_df = pd.DataFrame({"qid":test_df["qid"].values})
out_df['prediction'] = pred_test_y
out_df.to_csv("submission.csv", index=False)
总结

在这里简单做一下总结:
1.词向量的选取很重要,不同的词向量一般效果是不一样的。另外,自训练的词向量一般会比与训练好的词向量好一些,但这只是针对确定的数据集而言的,如果数据集发生变化,那么自训练的词向量效果会变差。还有一种选择是,使用预训练好的词向量,但是在训练模型的过程中对词向量进行微调。
2.找nlp相关工作的同学需要关注一下句向量的表示方法,工业界貌似对将一句话表示成为一个一维向量很看重。
3.深度学习模型会比机器学习模型效果好一些,但这是建立在大量时间消耗的基础上的。同时深度学习模型对输入的要求也会宽泛一些,可以做端到端的应用。
4.模型其实差别不会太大。模型的融合时提升准确率的利器,bagging,boosting等等都需要了解一些。
5.并不是数据处理的越干净越好,要视具体任务而言。比如在这个任务中,如果过多的处理掉一些杂乱的符号,最终的分类效果反而不好。我猜测这可能是因为包含这个杂乱符号的句子更多的是无意义的句子,而有意义的句子本身更加标准整洁,所以有没有这个符号本身就是一种区分的特征,如果把这些特征去掉,反而效果不好了。
6.交叉验证是提升效果的重要方法
7.数据不平衡的问题,这个问题其实我没有搞清楚,如果测试集与训练集同样是不平衡的数据,我们是否需要针对数据不平衡问题做处理?我们尝试过使用过采样和降采样对训练数据进行处理,结果是验证集准确率大大提升,但是测试集下降很多。因为我们看不到测试集数据的分布,所以对这个问题一直没有弄清楚。如果有同学了解,欢迎留言,学习一下~~
8.另外还有一些模型和代码是在实验中尝试过的,一起放到github上了。有兴趣可以看一下。
https://github.com/pnnngchg/kaggle_quora

  • 9
    点赞
  • 63
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
BERT(Bidirectional Encoder Representations from Transformers)模型是一种预训练的语言表示模型,用于处理自然语言处理任务,例如文本分类。要实战BERT模型进行文本分类,首先需要准备好训练数据集和测试数据集。然后按照以下步骤进行代码实现: 1. 导入必要的库和模型:首先导入必要的Python库,例如tensorflow和transformers。然后加载预训练的BERT模型,例如使用transformers库中的BertForSequenceClassification模型。 2. 数据预处理:将文本数据转换为BERT模型的输入格式。可以使用tokenizer对文本进行编码,然后将编码后的文本转换为模型输入的格式。 3. 构建模型:基于BERT模型构建文本分类模型。可以使用BertForSequenceClassification模型构建一个分类器,并根据实际情况调整模型的超参数。 4. 模型训练:使用准备好的训练数据集对构建的BERT文本分类模型进行训练。可以使用适当的优化器和损失函数来训练模型,并根据验证集的表现来调整模型。 5. 模型评估:使用准备好的测试数据集对训练好的BERT文本分类模型进行评估。可以计算模型的准确率、召回率和F1值等指标来评估模型的性能。 6. 模型应用:使用训练好的BERT文本分类模型对新的文本数据进行分类预测。可以将模型应用到实际的文本分类任务中,例如对新闻文本进行分类、对电影评论进行情感分析等。 通过以上步骤,可以实战BERT模型进行文本分类任务,并根据实际情况对模型进行调整和优化,从而得到更好的分类效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值